From 2ac82f6b49a976f5f355d98491e27045ed62d188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Mon, 30 May 2022 16:11:12 +0000 Subject: [PATCH 01/18] database: index uniqueness is a deferred constraint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Index duplication is a commit constraint and should be reported as an additional operation result. Verification should be deferred until all operations have been processed and can be taken into account. From RFC: If "indexes" is specified, it must be an array of zero or more s. A is an array of one or more strings, each of which names a column. Each is a set of columns whose values, taken together within any given row, must be unique within the table. This is a "deferred" constraint, enforced only at transaction commit time, after unreferenced rows are deleted and dangling weak references are removed. Ephemeral columns may not be part of indexes. and: if all of the operations succeed, but the results cannot be committed, then "result" will have one more element than "params", with the additional element being an . Signed-off-by: Jaime Caamaño Ruiz --- cache/cache.go | 19 ++++-- server/server.go | 4 +- server/server_integration_test.go | 4 +- server/transact.go | 98 +++++++++++++++---------------- 4 files changed, 66 insertions(+), 59 deletions(-) diff --git a/cache/cache.go b/cache/cache.go index 1763c95e..c2f93c6a 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -56,14 +56,14 @@ type ErrIndexExists struct { Value interface{} Index string New string - Existing string + Existing []string } func (e *ErrIndexExists) Error() string { return fmt.Sprintf("cannot insert %s in the %s table. item %s has identical indexes. index: %s, value: %v", e.New, e.Table, e.Existing, e.Index, e.Value) } -func NewIndexExistsError(table string, value interface{}, index string, new, existing string) *ErrIndexExists { +func NewIndexExistsError(table string, value interface{}, index string, new string, existing []string) *ErrIndexExists { return &ErrIndexExists{ table, value, index, new, existing, } @@ -166,6 +166,13 @@ func (r *RowCache) Row(uuid string) model.Model { return r.rowByUUID(uuid) } +func (r *RowCache) HasRow(uuid string) bool { + r.mutex.RLock() + defer r.mutex.RUnlock() + _, found := r.cache[uuid] + return found +} + func (r *RowCache) rowsByModel(m model.Model, useClientIndexes bool) map[string]model.Model { r.mutex.RLock() defer r.mutex.RUnlock() @@ -257,7 +264,7 @@ func (r *RowCache) Create(uuid string, m model.Model, checkIndexes bool) error { vals := r.indexes[index] if existing, ok := vals[val]; ok && !existing.empty() && checkIndexes && indexSpec.isSchemaIndex() { - return NewIndexExistsError(r.name, val, string(index), uuid, existing.getAny()) + return NewIndexExistsError(r.name, val, string(index), uuid, existing.list()) } uuidset := newUUIDSet(uuid) @@ -322,7 +329,7 @@ func (r *RowCache) Update(uuid string, m model.Model, checkIndexes bool) (model. newVal, string(index), uuid, - existing.getAny(), + existing.list(), )) } @@ -352,6 +359,8 @@ func (r *RowCache) Update(uuid string, m model.Model, checkIndexes bool) (model. return oldRow, nil } +// IndexExists checks if any of the provided model primary indexes (schema index +// or client primary index) is already in the cache. func (r *RowCache) IndexExists(row model.Model) error { info, err := r.dbModel.NewModelInfo(row) if err != nil { @@ -380,7 +389,7 @@ func (r *RowCache) IndexExists(row model.Model) error { val, string(index), uuid, - existing.getAny(), + existing.list(), ) } } diff --git a/server/server.go b/server/server.go index 9be6886c..3dd57e8a 100644 --- a/server/server.go +++ b/server/server.go @@ -237,9 +237,9 @@ func (o *OvsdbServer) Transact(client *rpc2.Client, args []json.RawMessage, repl } response, updates := o.transact(db, ops) *reply = response - for i, operResult := range response { + for _, operResult := range response { if operResult.Error != "" { - o.logger.Error(errors.New("failed to process operation"), "Skipping transaction DB commit due to error", "operations", ops, "failed operation", ops[i], "operation error", operResult.Error) + o.logger.Error(errors.New("failed to process operation"), "Skipping transaction DB commit due to error", "operations", ops, "results", response, "operation error", operResult.Error) return nil } } diff --git a/server/server_integration_test.go b/server/server_integration_test.go index 3d51409c..1325c1f6 100644 --- a/server/server_integration_test.go +++ b/server/server_integration_test.go @@ -400,9 +400,9 @@ func TestClientServerInsertDuplicate(t *testing.T) { reply, err = ovs.Transact(context.Background(), ops...) require.Nil(t, err) opErrs, err := ovsdb.CheckOperationResults(reply, ops) + require.Nil(t, opErrs) require.Error(t, err) - require.Error(t, opErrs[0]) - require.IsTypef(t, &ovsdb.ConstraintViolation{}, opErrs[0], opErrs[0].Error()) + require.IsTypef(t, &ovsdb.ConstraintViolation{}, err, err.Error()) } func TestClientServerInsertAndUpdate(t *testing.T) { diff --git a/server/transact.go b/server/transact.go index 33be01ee..a5dccd36 100644 --- a/server/transact.go +++ b/server/transact.go @@ -92,6 +92,21 @@ func (o *OvsdbServer) transact(name string, operations []ovsdb.Operation) ([]ovs return nil, updates } } + + if err := transaction.checkIndexes(); err != nil { + if indexExists, ok := err.(*cache.ErrIndexExists); ok { + e := ovsdb.ConstraintViolation{} + results = append(results, ovsdb.OperationResult{ + Error: e.Error(), + Details: newIndexExistsDetails(*indexExists), + }) + } else { + results = append(results, ovsdb.OperationResult{ + Error: err.Error(), + }) + } + } + return results, updates } @@ -125,15 +140,40 @@ func (t *Transaction) rowsFromTransactionCacheAndDatabase(table string, where [] return rows, nil } -func (t *Transaction) checkIndexes(table string, model model.Model) error { - // check for index conflicts. First check on transaction cache, followed by - // the database's - targetTable := t.Cache.Table(table) - err := targetTable.IndexExists(model) - if err == nil { - err = t.Database.CheckIndexes(t.DbName, table, model) +// checkIndexes checks that there are no index conflicts: +// - no duplicate indexes among any two rows operated with in the transaction +// - no duplicate indexes of any transaction row with any database row +func (t *Transaction) checkIndexes() error { + // check for index conflicts. + tables := t.Cache.Tables() + for _, table := range tables { + tc := t.Cache.Table(table) + for _, row := range tc.RowsShallow() { + err := tc.IndexExists(row) + if err != nil { + return err + } + err = t.Database.CheckIndexes(t.DbName, table, row) + errIndexExists, isErrIndexExists := err.(*cache.ErrIndexExists) + if !isErrIndexExists { + return err + } + for _, existing := range errIndexExists.Existing { + if _, isDeleted := t.DeletedRows[existing]; isDeleted { + // this model is deleted in the transaction, ignore it + continue + } + if tc.HasRow(existing) { + // this model is updated in the transaction and was not + // detected as a duplicate, so an index must have been + // updated, ignore it + continue + } + return err + } + } } - return err + return nil } func (t *Transaction) Insert(table string, rowUUID string, row ovsdb.Row) (ovsdb.OperationResult, ovsdb.TableUpdates2) { @@ -179,20 +219,6 @@ func (t *Transaction) Insert(table string, rowUUID string, row ovsdb.Row) (ovsdb }, nil } - // check for index conflicts - if err := t.checkIndexes(table, model); err != nil { - if indexExists, ok := err.(*cache.ErrIndexExists); ok { - e := ovsdb.ConstraintViolation{} - return ovsdb.OperationResult{ - Error: e.Error(), - Details: newIndexExistsDetails(*indexExists), - }, nil - } - return ovsdb.OperationResult{ - Error: err.Error(), - }, nil - } - result := ovsdb.OperationResult{ UUID: ovsdb.UUID{GoUUID: rowUUID}, } @@ -327,20 +353,6 @@ func (t *Transaction) Update(database, table string, where []ovsdb.Condition, ro panic(err) } - // check for index conflicts - if err := t.checkIndexes(table, new); err != nil { - if indexExists, ok := err.(*cache.ErrIndexExists); ok { - e := ovsdb.ConstraintViolation{} - return ovsdb.OperationResult{ - Error: e.Error(), - Details: newIndexExistsDetails(*indexExists), - }, nil - } - return ovsdb.OperationResult{ - Error: err.Error(), - }, nil - } - tableUpdate.AddRowUpdate(uuid, &ovsdb.RowUpdate2{ Modify: &rowDelta, Old: &oldRow, @@ -452,20 +464,6 @@ func (t *Transaction) Mutate(database, table string, where []ovsdb.Condition, mu } } - // check indexes - if err := t.checkIndexes(table, new); err != nil { - if indexExists, ok := err.(*cache.ErrIndexExists); ok { - e := ovsdb.ConstraintViolation{} - return ovsdb.OperationResult{ - Error: e.Error(), - Details: newIndexExistsDetails(*indexExists), - }, nil - } - return ovsdb.OperationResult{ - Error: err.Error(), - }, nil - } - newRow, err := m.NewRow(newInfo) if err != nil { panic(err) From fd89b7a117100763581d27cb8010f6e149e50499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Mon, 30 May 2022 18:58:32 +0000 Subject: [PATCH 02/18] database: move database & transaction to own package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To be able to reuse it in the client without incurring in circular dependencies. Signed-off-by: Jaime Caamaño Ruiz --- client/client_test.go | 3 +- {server => database}/database.go | 2 +- {server => database}/errors.go | 2 +- server/transact.go => database/transaction.go | 95 ++++++----- .../transaction_test.go | 148 +++++++++--------- example/ovsdb-server/main.go | 3 +- {server => ovsdb}/mutate.go | 20 ++- {server => ovsdb}/mutate_test.go | 91 ++++++----- server/server.go | 57 ++++--- server/server_integration_test.go | 118 +++++--------- server/server_test.go | 13 +- .../ovslite.json => test/test_data.go | 36 ++++- 12 files changed, 296 insertions(+), 292 deletions(-) rename {server => database}/database.go (99%) rename {server => database}/errors.go (96%) rename server/transact.go => database/transaction.go (86%) rename server/transact_test.go => database/transaction_test.go (85%) rename {server => ovsdb}/mutate.go (93%) rename {server => ovsdb}/mutate_test.go (77%) rename server/testdata/ovslite.json => test/test_data.go (71%) diff --git a/client/client_test.go b/client/client_test.go index 2850268f..b4a5e0a4 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -16,6 +16,7 @@ import ( "github.com/cenkalti/rpc2" "github.com/google/uuid" "github.com/ovn-org/libovsdb/cache" + db "github.com/ovn-org/libovsdb/database" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" "github.com/ovn-org/libovsdb/ovsdb/serverdb" @@ -870,7 +871,7 @@ func newOVSDBServer(t *testing.T, dbModel model.ClientDBModel, schema ovsdb.Data require.NoError(t, err) serverSchema := serverdb.Schema() - db := server.NewInMemoryDatabase(map[string]model.ClientDBModel{ + db := db.NewInMemoryDatabase(map[string]model.ClientDBModel{ schema.Name: dbModel, serverSchema.Name: serverDBModel, }) diff --git a/server/database.go b/database/database.go similarity index 99% rename from server/database.go rename to database/database.go index 0c0e55de..727b5769 100644 --- a/server/database.go +++ b/database/database.go @@ -1,4 +1,4 @@ -package server +package database import ( "fmt" diff --git a/server/errors.go b/database/errors.go similarity index 96% rename from server/errors.go rename to database/errors.go index a0845d98..979752c0 100644 --- a/server/errors.go +++ b/database/errors.go @@ -1,4 +1,4 @@ -package server +package database import ( "fmt" diff --git a/server/transact.go b/database/transaction.go similarity index 86% rename from server/transact.go rename to database/transaction.go index a5dccd36..9543a4a1 100644 --- a/server/transact.go +++ b/database/transaction.go @@ -1,99 +1,112 @@ -package server +package database import ( "fmt" "reflect" "time" + "github.com/go-logr/logr" "github.com/google/uuid" "github.com/ovn-org/libovsdb/cache" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" ) -func (o *OvsdbServer) transact(name string, operations []ovsdb.Operation) ([]ovsdb.OperationResult, ovsdb.TableUpdates2) { - o.modelsMutex.Lock() - dbModel := o.models[name] - o.modelsMutex.Unlock() - transaction := o.NewTransaction(dbModel, name, o.db) +type Transaction struct { + ID uuid.UUID + Cache *cache.TableCache + DeletedRows map[string]struct{} + Model model.DatabaseModel + DbName string + Database Database +} +func NewTransaction(model model.DatabaseModel, dbName string, database Database, logger *logr.Logger) Transaction { + if logger != nil { + l := logger.WithName("transaction") + logger = &l + } + cache, err := cache.NewTableCache(model, nil, logger) + if err != nil { + panic(err) + } + return Transaction{ + ID: uuid.New(), + Cache: cache, + DeletedRows: make(map[string]struct{}), + Model: model, + DbName: dbName, + Database: database, + } +} + +func (t *Transaction) Transact(operations []ovsdb.Operation) ([]ovsdb.OperationResult, ovsdb.TableUpdates2) { results := []ovsdb.OperationResult{} updates := make(ovsdb.TableUpdates2) - // simple case: database name does not exist - if !o.db.Exists(name) { - r := ovsdb.OperationResult{ - Error: "database does not exist", - } - for range operations { - results = append(results, r) - } - return results, updates - } - for _, op := range operations { switch op.Op { case ovsdb.OperationInsert: - r, tu := transaction.Insert(op.Table, op.UUIDName, op.Row) + r, tu := t.Insert(op.Table, op.UUIDName, op.Row) results = append(results, r) if tu != nil { updates.Merge(tu) - if err := transaction.Cache.Populate2(tu); err != nil { + if err := t.Cache.Populate2(tu); err != nil { panic(err) } } case ovsdb.OperationSelect: - r := transaction.Select(op.Table, op.Where, op.Columns) + r := t.Select(op.Table, op.Where, op.Columns) results = append(results, r) case ovsdb.OperationUpdate: - r, tu := transaction.Update(name, op.Table, op.Where, op.Row) + r, tu := t.Update(op.Table, op.Where, op.Row) results = append(results, r) if tu != nil { updates.Merge(tu) - if err := transaction.Cache.Populate2(tu); err != nil { + if err := t.Cache.Populate2(tu); err != nil { panic(err) } } case ovsdb.OperationMutate: - r, tu := transaction.Mutate(name, op.Table, op.Where, op.Mutations) + r, tu := t.Mutate(op.Table, op.Where, op.Mutations) results = append(results, r) if tu != nil { updates.Merge(tu) - if err := transaction.Cache.Populate2(tu); err != nil { + if err := t.Cache.Populate2(tu); err != nil { panic(err) } } case ovsdb.OperationDelete: - r, tu := transaction.Delete(name, op.Table, op.Where) + r, tu := t.Delete(op.Table, op.Where) results = append(results, r) if tu != nil { updates.Merge(tu) - if err := transaction.Cache.Populate2(tu); err != nil { + if err := t.Cache.Populate2(tu); err != nil { panic(err) } } case ovsdb.OperationWait: - r := transaction.Wait(name, op.Table, op.Timeout, op.Where, op.Columns, op.Until, op.Rows) + r := t.Wait(op.Table, op.Timeout, op.Where, op.Columns, op.Until, op.Rows) results = append(results, r) case ovsdb.OperationCommit: durable := op.Durable - r := transaction.Commit(name, op.Table, *durable) + r := t.Commit(op.Table, *durable) results = append(results, r) case ovsdb.OperationAbort: - r := transaction.Abort(name, op.Table) + r := t.Abort(op.Table) results = append(results, r) case ovsdb.OperationComment: - r := transaction.Comment(name, op.Table, *op.Comment) + r := t.Comment(op.Table, *op.Comment) results = append(results, r) case ovsdb.OperationAssert: - r := transaction.Assert(name, op.Table, *op.Lock) + r := t.Assert(op.Table, *op.Lock) results = append(results, r) default: return nil, updates } } - if err := transaction.checkIndexes(); err != nil { + if err := t.checkIndexes(); err != nil { if indexExists, ok := err.(*cache.ErrIndexExists); ok { e := ovsdb.ConstraintViolation{} results = append(results, ovsdb.OperationResult{ @@ -259,7 +272,7 @@ func (t *Transaction) Select(table string, where []ovsdb.Condition, columns []st } } -func (t *Transaction) Update(database, table string, where []ovsdb.Condition, row ovsdb.Row) (ovsdb.OperationResult, ovsdb.TableUpdates2) { +func (t *Transaction) Update(table string, where []ovsdb.Condition, row ovsdb.Row) (ovsdb.OperationResult, ovsdb.TableUpdates2) { dbModel := t.Model m := dbModel.Mapper schema := dbModel.Schema.Table(table) @@ -367,7 +380,7 @@ func (t *Transaction) Update(database, table string, where []ovsdb.Condition, ro } } -func (t *Transaction) Mutate(database, table string, where []ovsdb.Condition, mutations []ovsdb.Mutation) (ovsdb.OperationResult, ovsdb.TableUpdates2) { +func (t *Transaction) Mutate(table string, where []ovsdb.Condition, mutations []ovsdb.Mutation) (ovsdb.OperationResult, ovsdb.TableUpdates2) { dbModel := t.Model m := dbModel.Mapper schema := dbModel.Schema.Table(table) @@ -431,7 +444,7 @@ func (t *Transaction) Mutate(database, table string, where []ovsdb.Condition, mu if err != nil { panic(err) } - newValue, _ := mutate(current, mutation.Mutator, nativeValue) + newValue, _ := ovsdb.Mutate(current, mutation.Mutator, nativeValue) if err := newInfo.SetField(mutation.Column, newValue); err != nil { panic(err) } @@ -483,7 +496,7 @@ func (t *Transaction) Mutate(database, table string, where []ovsdb.Condition, mu } } -func (t *Transaction) Delete(database, table string, where []ovsdb.Condition) (ovsdb.OperationResult, ovsdb.TableUpdates2) { +func (t *Transaction) Delete(table string, where []ovsdb.Condition) (ovsdb.OperationResult, ovsdb.TableUpdates2) { dbModel := t.Model m := dbModel.Mapper tableUpdate := make(ovsdb.TableUpdate2) @@ -513,7 +526,7 @@ func (t *Transaction) Delete(database, table string, where []ovsdb.Condition) (o } } -func (t *Transaction) Wait(database, table string, timeout *int, where []ovsdb.Condition, columns []string, until string, rows []ovsdb.Row) ovsdb.OperationResult { +func (t *Transaction) Wait(table string, timeout *int, where []ovsdb.Condition, columns []string, until string, rows []ovsdb.Row) ovsdb.OperationResult { start := time.Now() if until != "!=" && until != "==" { @@ -612,22 +625,22 @@ Loop: return ovsdb.OperationResult{Error: e.Error()} } -func (t *Transaction) Commit(database, table string, durable bool) ovsdb.OperationResult { +func (t *Transaction) Commit(table string, durable bool) ovsdb.OperationResult { e := ovsdb.NotSupported{} return ovsdb.OperationResult{Error: e.Error()} } -func (t *Transaction) Abort(database, table string) ovsdb.OperationResult { +func (t *Transaction) Abort(table string) ovsdb.OperationResult { e := ovsdb.NotSupported{} return ovsdb.OperationResult{Error: e.Error()} } -func (t *Transaction) Comment(database, table string, comment string) ovsdb.OperationResult { +func (t *Transaction) Comment(table string, comment string) ovsdb.OperationResult { e := ovsdb.NotSupported{} return ovsdb.OperationResult{Error: e.Error()} } -func (t *Transaction) Assert(database, table, lock string) ovsdb.OperationResult { +func (t *Transaction) Assert(table, lock string) ovsdb.OperationResult { e := ovsdb.NotSupported{} return ovsdb.OperationResult{Error: e.Error()} } diff --git a/server/transact_test.go b/database/transaction_test.go similarity index 85% rename from server/transact_test.go rename to database/transaction_test.go index 272ad82f..e779e883 100644 --- a/server/transact_test.go +++ b/database/transaction_test.go @@ -1,46 +1,49 @@ -package server +package database import ( "testing" "time" "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/ovn-org/libovsdb/mapper" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + + . "github.com/ovn-org/libovsdb/test" ) func TestWaitOpEquals(t *testing.T) { defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}}) + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) if err != nil { t.Fatal(err) } - schema, err := getSchema() + schema, err := GetSchema() if err != nil { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + db := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + err = db.CreateDatabase("Open_vSwitch", schema) + require.NoError(t, err) dbModel, errs := model.NewDatabaseModel(schema, defDB) require.Empty(t, errs) - o, err := NewOvsdbServer(ovsDB, dbModel) - require.Nil(t, err) ovsUUID := uuid.NewString() bridgeUUID := uuid.NewString() m := mapper.NewMapper(schema) - ovs := ovsType{} + ovs := OvsType{} info, err := dbModel.NewModelInfo(&ovs) require.NoError(t, err) ovsRow, err := m.NewRow(info) require.Nil(t, err) - bridge := bridgeType{ + bridge := BridgeType{ Name: "foo", ExternalIds: map[string]string{ "foo": "bar", @@ -53,7 +56,7 @@ func TestWaitOpEquals(t *testing.T) { bridgeRow, err := m.NewRow(bridgeInfo) require.Nil(t, err) - transaction := o.NewTransaction(dbModel, "Open_vSwitch", o.db) + transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) res, updates := transaction.Insert("Open_vSwitch", ovsUUID, ovsRow) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) @@ -64,13 +67,12 @@ func TestWaitOpEquals(t *testing.T) { require.Nil(t, err) updates.Merge(update2) - err = o.db.Commit("Open_vSwitch", uuid.New(), updates) + err = db.Commit("Open_vSwitch", uuid.New(), updates) require.NoError(t, err) timeout := 0 // Attempt to wait for row with name foo to appear gotResult := transaction.Wait( - "Open_vSwitch", "Bridge", &timeout, []ovsdb.Condition{ovsdb.NewCondition("name", ovsdb.ConditionEqual, "foo")}, @@ -83,7 +85,6 @@ func TestWaitOpEquals(t *testing.T) { // Attempt to wait for 2 rows, where one does not exist gotResult = transaction.Wait( - "Open_vSwitch", "Bridge", &timeout, []ovsdb.Condition{ovsdb.NewCondition("name", ovsdb.ConditionEqual, "foo")}, @@ -102,7 +103,6 @@ func TestWaitOpEquals(t *testing.T) { require.Nil(t, err) // Attempt to wait for a row, with multiple columns specified gotResult = transaction.Wait( - "Open_vSwitch", "Bridge", &timeout, []ovsdb.Condition{ovsdb.NewCondition("name", ovsdb.ConditionEqual, "foo")}, @@ -115,7 +115,6 @@ func TestWaitOpEquals(t *testing.T) { // Attempt to wait for a row, with multiple columns, but not specified in row filtering gotResult = transaction.Wait( - "Open_vSwitch", "Bridge", &timeout, []ovsdb.Condition{ovsdb.NewCondition("name", ovsdb.ConditionEqual, "foo")}, @@ -129,7 +128,6 @@ func TestWaitOpEquals(t *testing.T) { // Attempt to get something with a non-zero timeout that will fail timeout = 400 gotResult = transaction.Wait( - "Open_vSwitch", "Bridge", &timeout, []ovsdb.Condition{ovsdb.NewCondition("name", ovsdb.ConditionEqual, "foo")}, @@ -144,33 +142,33 @@ func TestWaitOpEquals(t *testing.T) { func TestWaitOpNotEquals(t *testing.T) { defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}}) + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) if err != nil { t.Fatal(err) } - schema, err := getSchema() + schema, err := GetSchema() if err != nil { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + db := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + err = db.CreateDatabase("Open_vSwitch", schema) + require.NoError(t, err) dbModel, errs := model.NewDatabaseModel(schema, defDB) require.Empty(t, errs) - o, err := NewOvsdbServer(ovsDB, dbModel) - require.Nil(t, err) ovsUUID := uuid.NewString() bridgeUUID := uuid.NewString() m := mapper.NewMapper(schema) - ovs := ovsType{} + ovs := OvsType{} info, err := dbModel.NewModelInfo(&ovs) require.NoError(t, err) ovsRow, err := m.NewRow(info) require.Nil(t, err) - bridge := bridgeType{ + bridge := BridgeType{ Name: "foo", ExternalIds: map[string]string{ "foo": "bar", @@ -183,7 +181,7 @@ func TestWaitOpNotEquals(t *testing.T) { bridgeRow, err := m.NewRow(bridgeInfo) require.Nil(t, err) - transaction := o.NewTransaction(dbModel, "Open_vSwitch", o.db) + transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) res, updates := transaction.Insert("Open_vSwitch", ovsUUID, ovsRow) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) @@ -194,13 +192,12 @@ func TestWaitOpNotEquals(t *testing.T) { require.Nil(t, err) updates.Merge(update2) - err = o.db.Commit("Open_vSwitch", uuid.New(), updates) + err = db.Commit("Open_vSwitch", uuid.New(), updates) require.NoError(t, err) timeout := 0 // Attempt a wait where no entry with name blah should exist gotResult := transaction.Wait( - "Open_vSwitch", "Bridge", &timeout, []ovsdb.Condition{ovsdb.NewCondition("name", ovsdb.ConditionEqual, "foo")}, @@ -213,7 +210,6 @@ func TestWaitOpNotEquals(t *testing.T) { // Attempt another wait with multiple rows specified, one that would match, and one that doesn't gotResult = transaction.Wait( - "Open_vSwitch", "Bridge", &timeout, []ovsdb.Condition{ovsdb.NewCondition("name", ovsdb.ConditionEqual, "foo")}, @@ -233,7 +229,6 @@ func TestWaitOpNotEquals(t *testing.T) { require.Nil(t, err) // Attempt to wait for a row, with multiple columns specified and one is not a match gotResult = transaction.Wait( - "Open_vSwitch", "Bridge", &timeout, []ovsdb.Condition{ovsdb.NewCondition("name", ovsdb.ConditionEqual, "foo")}, @@ -248,7 +243,6 @@ func TestWaitOpNotEquals(t *testing.T) { start := time.Now() timeout = 200 gotResult = transaction.Wait( - "Open_vSwitch", "Bridge", &timeout, []ovsdb.Condition{ovsdb.NewCondition("name", ovsdb.ConditionEqual, "foo")}, @@ -266,33 +260,33 @@ func TestWaitOpNotEquals(t *testing.T) { func TestMutateOp(t *testing.T) { defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}}) + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) if err != nil { t.Fatal(err) } - schema, err := getSchema() + schema, err := GetSchema() if err != nil { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + db := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + err = db.CreateDatabase("Open_vSwitch", schema) + require.NoError(t, err) dbModel, errs := model.NewDatabaseModel(schema, defDB) require.Empty(t, errs) - o, err := NewOvsdbServer(ovsDB, dbModel) - require.Nil(t, err) ovsUUID := uuid.NewString() bridgeUUID := uuid.NewString() m := mapper.NewMapper(schema) - ovs := ovsType{} + ovs := OvsType{} info, err := dbModel.NewModelInfo(&ovs) require.NoError(t, err) ovsRow, err := m.NewRow(info) require.Nil(t, err) - bridge := bridgeType{ + bridge := BridgeType{ Name: "foo", ExternalIds: map[string]string{ "foo": "bar", @@ -305,7 +299,7 @@ func TestMutateOp(t *testing.T) { bridgeRow, err := m.NewRow(bridgeInfo) require.Nil(t, err) - transaction := o.NewTransaction(dbModel, "Open_vSwitch", o.db) + transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) res, updates := transaction.Insert("Open_vSwitch", ovsUUID, ovsRow) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) @@ -316,11 +310,10 @@ func TestMutateOp(t *testing.T) { require.Nil(t, err) updates.Merge(update2) - err = o.db.Commit("Open_vSwitch", uuid.New(), updates) + err = db.Commit("Open_vSwitch", uuid.New(), updates) require.NoError(t, err) gotResult, gotUpdate := transaction.Mutate( - "Open_vSwitch", "Open_vSwitch", []ovsdb.Condition{ ovsdb.NewCondition("_uuid", ovsdb.ConditionEqual, ovsdb.UUID{GoUUID: ovsUUID}), @@ -330,7 +323,7 @@ func TestMutateOp(t *testing.T) { }, ) assert.Equal(t, ovsdb.OperationResult{Count: 1}, gotResult) - err = o.db.Commit("Open_vSwitch", uuid.New(), gotUpdate) + err = db.Commit("Open_vSwitch", uuid.New(), gotUpdate) require.NoError(t, err) bridgeSet, err := ovsdb.NewOvsSet([]ovsdb.UUID{{GoUUID: bridgeUUID}}) @@ -359,7 +352,6 @@ func TestMutateOp(t *testing.T) { keyValueDelete, err := ovsdb.NewOvsMap(map[string]string{"baz": "quux"}) assert.Nil(t, err) gotResult, gotUpdate = transaction.Mutate( - "Open_vSwitch", "Bridge", []ovsdb.Condition{ ovsdb.NewCondition("_uuid", ovsdb.ConditionEqual, ovsdb.UUID{GoUUID: bridgeUUID}), @@ -465,24 +457,24 @@ func TestDiff(t *testing.T) { func TestOvsdbServerInsert(t *testing.T) { t.Skip("need a helper for comparing rows as map elements aren't in same order") defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}}) + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) if err != nil { t.Fatal(err) } - schema, err := getSchema() + schema, err := GetSchema() if err != nil { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + db := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + err = db.CreateDatabase("Open_vSwitch", schema) + require.NoError(t, err) dbModel, errs := model.NewDatabaseModel(schema, defDB) require.Empty(t, errs) - o, err := NewOvsdbServer(ovsDB, dbModel) - require.Nil(t, err) m := mapper.NewMapper(schema) gromit := "gromit" - bridge := bridgeType{ + bridge := BridgeType{ Name: "foo", DatapathType: "bar", DatapathID: &gromit, @@ -498,17 +490,17 @@ func TestOvsdbServerInsert(t *testing.T) { bridgeRow, err := m.NewRow(bridgeInfo) require.Nil(t, err) - transaction := o.NewTransaction(dbModel, "Open_vSwitch", o.db) + transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) res, updates := transaction.Insert("Bridge", bridgeUUID, bridgeRow) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) require.NoError(t, err) - err = ovsDB.Commit("Open_vSwitch", uuid.New(), updates) + err = db.Commit("Open_vSwitch", uuid.New(), updates) assert.NoError(t, err) bridge.UUID = bridgeUUID - br, err := o.db.Get("Open_vSwitch", "Bridge", bridgeUUID) + br, err := db.Get("Open_vSwitch", "Bridge", bridgeUUID) assert.NoError(t, err) assert.Equal(t, &bridge, br) assert.Equal(t, ovsdb.TableUpdates2{ @@ -523,23 +515,23 @@ func TestOvsdbServerInsert(t *testing.T) { func TestOvsdbServerUpdate(t *testing.T) { defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}}) + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) if err != nil { t.Fatal(err) } - schema, err := getSchema() + schema, err := GetSchema() if err != nil { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + db := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + err = db.CreateDatabase("Open_vSwitch", schema) + require.NoError(t, err) dbModel, errs := model.NewDatabaseModel(schema, defDB) require.Empty(t, errs) - o, err := NewOvsdbServer(ovsDB, dbModel) - require.Nil(t, err) m := mapper.NewMapper(schema) - bridge := bridgeType{ + bridge := BridgeType{ Name: "foo", ExternalIds: map[string]string{ "foo": "bar", @@ -553,13 +545,13 @@ func TestOvsdbServerUpdate(t *testing.T) { bridgeRow, err := m.NewRow(bridgeInfo) require.Nil(t, err) - transaction := o.NewTransaction(dbModel, "Open_vSwitch", o.db) + transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) res, updates := transaction.Insert("Bridge", bridgeUUID, bridgeRow) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) require.NoError(t, err) - err = ovsDB.Commit("Open_vSwitch", uuid.New(), updates) + err = db.Commit("Open_vSwitch", uuid.New(), updates) assert.NoError(t, err) halloween, _ := ovsdb.NewOvsSet([]string{"halloween"}) @@ -590,7 +582,7 @@ func TestOvsdbServerUpdate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { res, updates := transaction.Update( - "Open_vSwitch", "Bridge", + "Bridge", []ovsdb.Condition{{ Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: bridgeUUID}, }}, tt.row) @@ -598,9 +590,9 @@ func TestOvsdbServerUpdate(t *testing.T) { require.NoErrorf(t, err, "%+v", errs) bridge.UUID = bridgeUUID - row, err := o.db.Get("Open_vSwitch", "Bridge", bridgeUUID) + row, err := db.Get("Open_vSwitch", "Bridge", bridgeUUID) assert.NoError(t, err) - br := row.(*bridgeType) + br := row.(*BridgeType) assert.NotEqual(t, br, bridgeRow) assert.Equal(t, tt.expected.Modify, updates["Bridge"][bridgeUUID].Modify) }) @@ -609,24 +601,24 @@ func TestOvsdbServerUpdate(t *testing.T) { func TestMultipleOps(t *testing.T) { defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}}) + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) if err != nil { t.Fatal(err) } - schema, err := getSchema() + schema, err := GetSchema() if err != nil { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + db := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + err = db.CreateDatabase("Open_vSwitch", schema) + require.NoError(t, err) dbModel, errs := model.NewDatabaseModel(schema, defDB) require.Empty(t, errs) - o, err := NewOvsdbServer(ovsDB, dbModel) - require.Nil(t, err) m := mapper.NewMapper(schema) bridgeUUID := uuid.NewString() - bridge := bridgeType{ + bridge := BridgeType{ Name: "a_bridge_to_nowhere", Ports: []string{ "port1", @@ -638,13 +630,13 @@ func TestMultipleOps(t *testing.T) { bridgeRow, err := m.NewRow(bridgeInfo) require.Nil(t, err) - transaction := o.NewTransaction(dbModel, "Open_vSwitch", o.db) + transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) res, updates := transaction.Insert("Bridge", bridgeUUID, bridgeRow) _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) require.Nil(t, err) - err = o.db.Commit("Open_vSwitch", uuid.New(), updates) + err = db.Commit("Open_vSwitch", uuid.New(), updates) require.NoError(t, err) var ops []ovsdb.Operation @@ -678,13 +670,13 @@ func TestMultipleOps(t *testing.T) { } ops = append(ops, op2) - results, updates := o.transact("Open_vSwitch", ops) + results, updates := transaction.Transact(ops) require.Len(t, results, len(ops)) for _, result := range results { assert.Equal(t, "", result.Error) } - err = o.db.Commit("Open_vSwitch", uuid.New(), updates) + err = db.Commit("Open_vSwitch", uuid.New(), updates) require.NoError(t, err) modifiedPorts, err := ovsdb.NewOvsSet([]ovsdb.UUID{{GoUUID: "portA"}, {GoUUID: "portB"}, {GoUUID: "portC"}}) diff --git a/example/ovsdb-server/main.go b/example/ovsdb-server/main.go index f65f0c20..e958280c 100644 --- a/example/ovsdb-server/main.go +++ b/example/ovsdb-server/main.go @@ -13,6 +13,7 @@ import ( "time" "github.com/ovn-org/libovsdb/client" + "github.com/ovn-org/libovsdb/database" "github.com/ovn-org/libovsdb/example/vswitchd" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" @@ -57,7 +58,7 @@ func main() { log.Fatal(err) } - ovsDB := server.NewInMemoryDatabase(map[string]model.ClientDBModel{ + ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{ schema.Name: clientDBModel, }) diff --git a/server/mutate.go b/ovsdb/mutate.go similarity index 93% rename from server/mutate.go rename to ovsdb/mutate.go index 0a62cde6..d6d404ae 100644 --- a/server/mutate.go +++ b/ovsdb/mutate.go @@ -1,9 +1,7 @@ -package server +package ovsdb import ( "reflect" - - "github.com/ovn-org/libovsdb/ovsdb" ) func removeFromSlice(a, b reflect.Value) (reflect.Value, bool) { @@ -25,32 +23,32 @@ func insertToSlice(a, b reflect.Value) (reflect.Value, bool) { return reflect.Append(a, b), true } -func mutate(current interface{}, mutator ovsdb.Mutator, value interface{}) (interface{}, interface{}) { +func Mutate(current interface{}, mutator Mutator, value interface{}) (interface{}, interface{}) { switch current.(type) { case bool, string: return current, value } switch mutator { - case ovsdb.MutateOperationInsert: + case MutateOperationInsert: // for insert, the delta will be the new value added return mutateInsert(current, value) - case ovsdb.MutateOperationDelete: + case MutateOperationDelete: return mutateDelete(current, value) - case ovsdb.MutateOperationAdd: + case MutateOperationAdd: // for add, the delta is the new value new := mutateAdd(current, value) return new, new - case ovsdb.MutateOperationSubtract: + case MutateOperationSubtract: // for subtract, the delta is the new value new := mutateSubtract(current, value) return new, new - case ovsdb.MutateOperationMultiply: + case MutateOperationMultiply: new := mutateMultiply(current, value) return new, new - case ovsdb.MutateOperationDivide: + case MutateOperationDivide: new := mutateDivide(current, value) return new, new - case ovsdb.MutateOperationModulo: + case MutateOperationModulo: new := mutateModulo(current, value) return new, new } diff --git a/server/mutate_test.go b/ovsdb/mutate_test.go similarity index 77% rename from server/mutate_test.go rename to ovsdb/mutate_test.go index 3915abcc..6febb1b3 100644 --- a/server/mutate_test.go +++ b/ovsdb/mutate_test.go @@ -1,9 +1,8 @@ -package server +package ovsdb import ( "testing" - "github.com/ovn-org/libovsdb/ovsdb" "github.com/stretchr/testify/assert" ) @@ -11,42 +10,42 @@ func TestMutateAdd(t *testing.T) { tests := []struct { name string current interface{} - mutator ovsdb.Mutator + mutator Mutator value interface{} want interface{} }{ { "add int", 1, - ovsdb.MutateOperationAdd, + MutateOperationAdd, 1, 2, }, { "add float", 1.0, - ovsdb.MutateOperationAdd, + MutateOperationAdd, 1.0, 2.0, }, { "add float set", []float64{1.0, 2.0, 3.0}, - ovsdb.MutateOperationAdd, + MutateOperationAdd, 1.0, []float64{2.0, 3.0, 4.0}, }, { "add int set float", []int{1, 2, 3}, - ovsdb.MutateOperationAdd, + MutateOperationAdd, 1, []int{2, 3, 4}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, diff := mutate(tt.current, tt.mutator, tt.value) + got, diff := Mutate(tt.current, tt.mutator, tt.value) assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, diff) }) @@ -57,7 +56,7 @@ func TestMutateSubtract(t *testing.T) { tests := []struct { name string current interface{} - mutator ovsdb.Mutator + mutator Mutator value interface{} want interface{} }{ @@ -65,35 +64,35 @@ func TestMutateSubtract(t *testing.T) { { "subtract int", 1, - ovsdb.MutateOperationSubtract, + MutateOperationSubtract, 1, 0, }, { "subtract float", 1.0, - ovsdb.MutateOperationSubtract, + MutateOperationSubtract, 1.0, 0.0, }, { "subtract float set", []float64{1.0, 2.0, 3.0}, - ovsdb.MutateOperationSubtract, + MutateOperationSubtract, 1.0, []float64{0.0, 1.0, 2.0}, }, { "subtract int set", []int{1, 2, 3}, - ovsdb.MutateOperationSubtract, + MutateOperationSubtract, 1, []int{0, 1, 2}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, diff := mutate(tt.current, tt.mutator, tt.value) + got, diff := Mutate(tt.current, tt.mutator, tt.value) assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, diff) }) @@ -104,7 +103,7 @@ func TestMutateMultiply(t *testing.T) { tests := []struct { name string current interface{} - mutator ovsdb.Mutator + mutator Mutator value interface{} want interface{} }{ @@ -112,35 +111,35 @@ func TestMutateMultiply(t *testing.T) { { "multiply int", 1, - ovsdb.MutateOperationMultiply, + MutateOperationMultiply, 2, 2, }, { "multiply float", 1.0, - ovsdb.MutateOperationMultiply, + MutateOperationMultiply, 2.0, 2.0, }, { "multiply float set", []float64{1.0, 2.0, 3.0}, - ovsdb.MutateOperationMultiply, + MutateOperationMultiply, 2.0, []float64{2.0, 4.0, 6.0}, }, { "multiply int set", []int{1, 2, 3}, - ovsdb.MutateOperationMultiply, + MutateOperationMultiply, 2, []int{2, 4, 6}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, diff := mutate(tt.current, tt.mutator, tt.value) + got, diff := Mutate(tt.current, tt.mutator, tt.value) assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, diff) }) @@ -151,42 +150,42 @@ func TestMutateDivide(t *testing.T) { tests := []struct { name string current interface{} - mutator ovsdb.Mutator + mutator Mutator value interface{} want interface{} }{ { "divide int", 10, - ovsdb.MutateOperationDivide, + MutateOperationDivide, 2, 5, }, { "divide float", 1.0, - ovsdb.MutateOperationDivide, + MutateOperationDivide, 2.0, 0.5, }, { "divide float set", []float64{1.0, 2.0, 4.0}, - ovsdb.MutateOperationDivide, + MutateOperationDivide, 2.0, []float64{0.5, 1.0, 2.0}, }, { "divide int set", []int{10, 20, 30}, - ovsdb.MutateOperationDivide, + MutateOperationDivide, 5, []int{2, 4, 6}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, diff := mutate(tt.current, tt.mutator, tt.value) + got, diff := Mutate(tt.current, tt.mutator, tt.value) assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, diff) }) @@ -197,28 +196,28 @@ func TestMutateModulo(t *testing.T) { tests := []struct { name string current interface{} - mutator ovsdb.Mutator + mutator Mutator value interface{} want interface{} }{ { "modulo int", 3, - ovsdb.MutateOperationModulo, + MutateOperationModulo, 2, 1, }, { "modulo int set", []int{3, 5, 7}, - ovsdb.MutateOperationModulo, + MutateOperationModulo, 2, []int{1, 1, 1}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, diff := mutate(tt.current, tt.mutator, tt.value) + got, diff := Mutate(tt.current, tt.mutator, tt.value) assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, diff) }) @@ -231,7 +230,7 @@ func TestMutateInsert(t *testing.T) { tests := []struct { name string current interface{} - mutator ovsdb.Mutator + mutator Mutator value interface{} want interface{} diff interface{} @@ -239,7 +238,7 @@ func TestMutateInsert(t *testing.T) { { "insert single string", []string{"foo", "bar"}, - ovsdb.MutateOperationInsert, + MutateOperationInsert, "baz", []string{"foo", "bar", "baz"}, "baz", @@ -247,7 +246,7 @@ func TestMutateInsert(t *testing.T) { { "insert in to nil value", nil, - ovsdb.MutateOperationInsert, + MutateOperationInsert, []string{"foo"}, []string{"foo"}, []string{"foo"}, @@ -255,7 +254,7 @@ func TestMutateInsert(t *testing.T) { { "insert in to nil slice", nilSlice, - ovsdb.MutateOperationInsert, + MutateOperationInsert, []string{"foo"}, []string{"foo"}, []string{"foo"}, @@ -263,7 +262,7 @@ func TestMutateInsert(t *testing.T) { { "insert existing string", []string{"foo", "bar", "baz"}, - ovsdb.MutateOperationInsert, + MutateOperationInsert, "baz", []string{"foo", "bar", "baz"}, nil, @@ -271,7 +270,7 @@ func TestMutateInsert(t *testing.T) { { "insert multiple string", []string{"foo", "bar"}, - ovsdb.MutateOperationInsert, + MutateOperationInsert, []string{"baz", "quux", "foo"}, []string{"foo", "bar", "baz", "quux"}, []string{"baz", "quux"}, @@ -281,7 +280,7 @@ func TestMutateInsert(t *testing.T) { map[string]string{ "foo": "bar", }, - ovsdb.MutateOperationInsert, + MutateOperationInsert, map[string]string{ "foo": "ignored", "baz": "quux", @@ -297,7 +296,7 @@ func TestMutateInsert(t *testing.T) { { "insert key value pairs on nil value", nil, - ovsdb.MutateOperationInsert, + MutateOperationInsert, map[string]string{ "foo": "bar", }, @@ -311,7 +310,7 @@ func TestMutateInsert(t *testing.T) { { "insert key value pairs on nil map", nilMap, - ovsdb.MutateOperationInsert, + MutateOperationInsert, map[string]string{ "foo": "bar", }, @@ -325,7 +324,7 @@ func TestMutateInsert(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, diff := mutate(tt.current, tt.mutator, tt.value) + got, diff := Mutate(tt.current, tt.mutator, tt.value) assert.Equal(t, tt.want, got) assert.Equal(t, tt.diff, diff) }) @@ -336,7 +335,7 @@ func TestMutateDelete(t *testing.T) { tests := []struct { name string current interface{} - mutator ovsdb.Mutator + mutator Mutator value interface{} want interface{} diff interface{} @@ -344,7 +343,7 @@ func TestMutateDelete(t *testing.T) { { "delete single string", []string{"foo", "bar"}, - ovsdb.MutateOperationDelete, + MutateOperationDelete, "bar", []string{"foo"}, "bar", @@ -352,7 +351,7 @@ func TestMutateDelete(t *testing.T) { { "delete multiple string", []string{"foo", "bar", "baz"}, - ovsdb.MutateOperationDelete, + MutateOperationDelete, []string{"bar", "baz"}, []string{"foo"}, []string{"bar", "baz"}, @@ -363,7 +362,7 @@ func TestMutateDelete(t *testing.T) { "foo": "bar", "baz": "quux", }, - ovsdb.MutateOperationDelete, + MutateOperationDelete, map[string]string{ "foo": "ignored", "baz": "quux", @@ -381,7 +380,7 @@ func TestMutateDelete(t *testing.T) { "foo": "bar", "baz": "quux", }, - ovsdb.MutateOperationDelete, + MutateOperationDelete, []string{"foo"}, map[string]string{ "baz": "quux", @@ -393,7 +392,7 @@ func TestMutateDelete(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, diff := mutate(tt.current, tt.mutator, tt.value) + got, diff := Mutate(tt.current, tt.mutator, tt.value) assert.Equal(t, tt.want, got) assert.Equal(t, tt.diff, diff) }) diff --git a/server/server.go b/server/server.go index 3dd57e8a..e6967952 100644 --- a/server/server.go +++ b/server/server.go @@ -14,7 +14,7 @@ import ( "github.com/go-logr/logr" "github.com/go-logr/stdr" "github.com/google/uuid" - "github.com/ovn-org/libovsdb/cache" + "github.com/ovn-org/libovsdb/database" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" ) @@ -24,7 +24,7 @@ type OvsdbServer struct { srv *rpc2.Server listener net.Listener done chan struct{} - db Database + db database.Database ready bool readyMutex sync.RWMutex models map[string]model.DatabaseModel @@ -36,7 +36,7 @@ type OvsdbServer struct { } // NewOvsdbServer returns a new OvsdbServer -func NewOvsdbServer(db Database, models ...model.DatabaseModel) (*OvsdbServer, error) { +func NewOvsdbServer(db database.Database, models ...model.DatabaseModel) (*OvsdbServer, error) { l := stdr.NewWithOptions(log.New(os.Stderr, "", log.LstdFlags), stdr.Options{LogCaller: stdr.All}).WithName("server") stdr.SetVerbosity(5) o := &OvsdbServer{ @@ -163,30 +163,6 @@ func (o *OvsdbServer) GetSchema(client *rpc2.Client, args []interface{}, reply * return nil } -type Transaction struct { - ID uuid.UUID - Cache *cache.TableCache - DeletedRows map[string]struct{} - Model model.DatabaseModel - DbName string - Database Database -} - -func (o *OvsdbServer) NewTransaction(model model.DatabaseModel, dbName string, database Database) Transaction { - cache, err := cache.NewTableCache(model, nil, &o.logger) - if err != nil { - panic(err) - } - return Transaction{ - ID: uuid.New(), - Cache: cache, - DeletedRows: make(map[string]struct{}), - Model: model, - DbName: dbName, - Database: database, - } -} - // Transact issues a new database transaction and returns the results func (o *OvsdbServer) Transact(client *rpc2.Client, args []json.RawMessage, reply *[]ovsdb.OperationResult) error { // While allowing other rpc handlers to run in parallel, this ovsdb server expects transactions @@ -248,6 +224,27 @@ func (o *OvsdbServer) Transact(client *rpc2.Client, args []json.RawMessage, repl return o.db.Commit(db, transactionID, updates) } +func (o *OvsdbServer) transact(name string, operations []ovsdb.Operation) ([]ovsdb.OperationResult, ovsdb.TableUpdates2) { + o.modelsMutex.Lock() + dbModel := o.models[name] + o.modelsMutex.Unlock() + transaction := database.NewTransaction(dbModel, name, o.db, &o.logger) + + // simple case: database name does not exist + if !o.db.Exists(name) { + r := ovsdb.OperationResult{ + Error: "database does not exist", + } + results := []ovsdb.OperationResult{} + for range operations { + results = append(results, r) + } + return results, nil + } + + return transaction.Transact(operations) +} + func deepCopy(a ovsdb.TableUpdates) (ovsdb.TableUpdates, error) { var b ovsdb.TableUpdates raw, err := json.Marshal(a) @@ -301,7 +298,7 @@ func (o *OvsdbServer) Monitor(client *rpc2.Client, args []json.RawMessage, reply o.modelsMutex.Lock() dbModel := o.models[db] o.modelsMutex.Unlock() - transaction := o.NewTransaction(dbModel, db, o.db) + transaction := database.NewTransaction(dbModel, db, o.db, &o.logger) tableUpdates := make(ovsdb.TableUpdates) for t, request := range request { @@ -348,7 +345,7 @@ func (o *OvsdbServer) MonitorCond(client *rpc2.Client, args []json.RawMessage, r o.modelsMutex.Lock() dbModel := o.models[db] o.modelsMutex.Unlock() - transaction := o.NewTransaction(dbModel, db, o.db) + transaction := database.NewTransaction(dbModel, db, o.db, &o.logger) tableUpdates := make(ovsdb.TableUpdates2) for t, request := range request { @@ -393,7 +390,7 @@ func (o *OvsdbServer) MonitorCondSince(client *rpc2.Client, args []json.RawMessa o.modelsMutex.Lock() dbModel := o.models[db] o.modelsMutex.Unlock() - transaction := o.NewTransaction(dbModel, db, o.db) + transaction := database.NewTransaction(dbModel, db, o.db, &o.logger) tableUpdates := make(ovsdb.TableUpdates2) for t, request := range request { diff --git a/server/server_integration_test.go b/server/server_integration_test.go index 1325c1f6..ca7289ef 100644 --- a/server/server_integration_test.go +++ b/server/server_integration_test.go @@ -5,7 +5,6 @@ import ( "fmt" "math/rand" "os" - "path/filepath" "reflect" "sync" "testing" @@ -13,58 +12,25 @@ import ( "github.com/ovn-org/libovsdb/cache" "github.com/ovn-org/libovsdb/client" + "github.com/ovn-org/libovsdb/database" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" -) - -// bridgeType is the simplified ORM model of the Bridge table -type bridgeType struct { - UUID string `ovsdb:"_uuid"` - Name string `ovsdb:"name"` - DatapathType string `ovsdb:"datapath_type"` - DatapathID *string `ovsdb:"datapath_id"` - OtherConfig map[string]string `ovsdb:"other_config"` - ExternalIds map[string]string `ovsdb:"external_ids"` - Ports []string `ovsdb:"ports"` - Status map[string]string `ovsdb:"status"` -} -// ovsType is the simplified ORM model of the Bridge table -type ovsType struct { - UUID string `ovsdb:"_uuid"` - Bridges []string `ovsdb:"bridges"` -} - -func getSchema() (ovsdb.DatabaseSchema, error) { - wd, err := os.Getwd() - if err != nil { - return ovsdb.DatabaseSchema{}, err - } - path := filepath.Join(wd, "testdata", "ovslite.json") - f, err := os.Open(path) - if err != nil { - return ovsdb.DatabaseSchema{}, err - } - defer f.Close() - schema, err := ovsdb.SchemaFromFile(f) - if err != nil { - return ovsdb.DatabaseSchema{}, err - } - return schema, nil -} + . "github.com/ovn-org/libovsdb/test" +) func TestClientServerEcho(t *testing.T) { defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}}) + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) require.Nil(t, err) - schema, err := getSchema() + schema, err := GetSchema() require.Nil(t, err) - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) rand.Seed(time.Now().UnixNano()) tmpfile := fmt.Sprintf("/tmp/ovsdb-%d.sock", rand.Intn(10000)) @@ -94,14 +60,14 @@ func TestClientServerEcho(t *testing.T) { func TestClientServerInsert(t *testing.T) { defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}}) + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) require.Nil(t, err) - schema, err := getSchema() + schema, err := GetSchema() require.Nil(t, err) - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) rand.Seed(time.Now().UnixNano()) tmpfile := fmt.Sprintf("/tmp/ovsdb-%d.sock", rand.Intn(10000)) defer os.Remove(tmpfile) @@ -128,7 +94,7 @@ func TestClientServerInsert(t *testing.T) { require.NoError(t, err) wallace := "wallace" - bridgeRow := &bridgeType{ + bridgeRow := &BridgeType{ Name: "foo", DatapathType: "bar", DatapathID: &wallace, @@ -144,12 +110,12 @@ func TestClientServerInsert(t *testing.T) { uuid := reply[0].UUID.GoUUID require.Eventually(t, func() bool { - br := &bridgeType{UUID: uuid} + br := &BridgeType{UUID: uuid} err := ovs.Get(context.Background(), br) return err == nil }, 2*time.Second, 500*time.Millisecond) - br := &bridgeType{UUID: uuid} + br := &BridgeType{UUID: uuid} err = ovs.Get(context.Background(), br) require.NoError(t, err) @@ -161,18 +127,18 @@ func TestClientServerInsert(t *testing.T) { func TestClientServerMonitor(t *testing.T) { defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}}) + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) if err != nil { t.Fatal(err) } - schema, err := getSchema() + schema, err := GetSchema() if err != nil { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) rand.Seed(time.Now().UnixNano()) tmpfile := fmt.Sprintf("/tmp/ovsdb-%d.sock", rand.Intn(10000)) defer os.Remove(tmpfile) @@ -196,10 +162,10 @@ func TestClientServerMonitor(t *testing.T) { err = ovs.Connect(context.Background()) require.NoError(t, err) - ovsRow := &ovsType{ + ovsRow := &OvsType{ UUID: "ovs", } - bridgeRow := &bridgeType{ + bridgeRow := &BridgeType{ UUID: "foo", Name: "foo", ExternalIds: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, @@ -212,7 +178,7 @@ func TestClientServerMonitor(t *testing.T) { ovs.Cache().AddEventHandler(&cache.EventHandlerFuncs{ AddFunc: func(table string, model model.Model) { if table == "Bridge" { - br := model.(*bridgeType) + br := model.(*BridgeType) assert.Equal(t, bridgeRow.Name, br.Name) assert.Equal(t, bridgeRow.ExternalIds, br.ExternalIds) seenMutex.Lock() @@ -227,7 +193,7 @@ func TestClientServerMonitor(t *testing.T) { }, UpdateFunc: func(table string, old, new model.Model) { if table == "Open_vSwitch" { - ov := new.(*ovsType) + ov := new.(*OvsType) assert.Equal(t, 1, len(ov.Bridges)) seenMutex.Lock() seenMutation = true @@ -287,14 +253,14 @@ func TestClientServerMonitor(t *testing.T) { func TestClientServerInsertAndDelete(t *testing.T) { defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}}) + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) require.Nil(t, err) - schema, err := getSchema() + schema, err := GetSchema() require.Nil(t, err) - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) rand.Seed(time.Now().UnixNano()) tmpfile := fmt.Sprintf("/tmp/ovsdb-%d.sock", rand.Intn(10000)) defer os.Remove(tmpfile) @@ -320,7 +286,7 @@ func TestClientServerInsertAndDelete(t *testing.T) { _, err = ovs.MonitorAll(context.Background()) require.NoError(t, err) - bridgeRow := &bridgeType{ + bridgeRow := &BridgeType{ Name: "foo", ExternalIds: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, } @@ -334,7 +300,7 @@ func TestClientServerInsertAndDelete(t *testing.T) { uuid := reply[0].UUID.GoUUID assert.Eventually(t, func() bool { - br := &bridgeType{UUID: uuid} + br := &BridgeType{UUID: uuid} err := ovs.Get(context.Background(), br) return err == nil }, 2*time.Second, 500*time.Millisecond) @@ -352,15 +318,15 @@ func TestClientServerInsertAndDelete(t *testing.T) { func TestClientServerInsertDuplicate(t *testing.T) { defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}, + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}, }) require.Nil(t, err) - schema, err := getSchema() + schema, err := GetSchema() require.Nil(t, err) - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) rand.Seed(time.Now().UnixNano()) tmpfile := fmt.Sprintf("/tmp/ovsdb-%d.sock", rand.Intn(10000)) defer os.Remove(tmpfile) @@ -384,7 +350,7 @@ func TestClientServerInsertDuplicate(t *testing.T) { err = ovs.Connect(context.Background()) require.NoError(t, err) - bridgeRow := &bridgeType{ + bridgeRow := &BridgeType{ Name: "foo", ExternalIds: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, } @@ -407,14 +373,14 @@ func TestClientServerInsertDuplicate(t *testing.T) { func TestClientServerInsertAndUpdate(t *testing.T) { defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}}) + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) require.Nil(t, err) - schema, err := getSchema() + schema, err := GetSchema() require.Nil(t, err) - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) rand.Seed(time.Now().UnixNano()) tmpfile := fmt.Sprintf("/tmp/ovsdb-%d.sock", rand.Intn(10000)) defer os.Remove(tmpfile) @@ -442,7 +408,7 @@ func TestClientServerInsertAndUpdate(t *testing.T) { _, err = ovs.MonitorAll(context.Background()) require.NoError(t, err) - bridgeRow := &bridgeType{ + bridgeRow := &BridgeType{ Name: "br-update", ExternalIds: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, } @@ -456,7 +422,7 @@ func TestClientServerInsertAndUpdate(t *testing.T) { uuid := reply[0].UUID.GoUUID assert.Eventually(t, func() bool { - br := &bridgeType{UUID: uuid} + br := &BridgeType{UUID: uuid} err := ovs.Get(context.Background(), br) return err == nil }, 2*time.Second, 500*time.Millisecond) @@ -481,7 +447,7 @@ func TestClientServerInsertAndUpdate(t *testing.T) { require.NoErrorf(t, err, "%+v", opErrs) require.Eventually(t, func() bool { - br := &bridgeType{UUID: uuid} + br := &BridgeType{UUID: uuid} err = ovs.Get(context.Background(), br) if err != nil { return false @@ -499,7 +465,7 @@ func TestClientServerInsertAndUpdate(t *testing.T) { require.NoErrorf(t, err, "%+v", opErr) assert.Eventually(t, func() bool { - br := &bridgeType{UUID: uuid} + br := &BridgeType{UUID: uuid} err = ovs.Get(context.Background(), br) if err != nil { return false @@ -507,7 +473,7 @@ func TestClientServerInsertAndUpdate(t *testing.T) { return reflect.DeepEqual(br.ExternalIds, bridgeRow.ExternalIds) }, 2*time.Second, 500*time.Millisecond) - br := &bridgeType{UUID: uuid} + br := &BridgeType{UUID: uuid} err = ovs.Get(context.Background(), br) assert.NoError(t, err) diff --git a/server/server_test.go b/server/server_test.go index fde1823e..900f57cf 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -5,10 +5,13 @@ import ( "testing" "github.com/google/uuid" + "github.com/ovn-org/libovsdb/database" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + . "github.com/ovn-org/libovsdb/test" ) func TestExpandNamedUUID(t *testing.T) { @@ -67,16 +70,16 @@ func TestExpandNamedUUID(t *testing.T) { func TestOvsdbServerMonitor(t *testing.T) { defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}}) + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) if err != nil { t.Fatal(err) } - schema, err := getSchema() + schema, err := GetSchema() if err != nil { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) + ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) dbModel, errs := model.NewDatabaseModel(schema, defDB) require.Empty(t, errs) o, err := NewOvsdbServer(ovsDB, dbModel) @@ -98,7 +101,7 @@ func TestOvsdbServerMonitor(t *testing.T) { bazUUID := uuid.NewString() quuxUUID := uuid.NewString() - transaction := o.NewTransaction(dbModel, "Open_vSwitch", o.db) + transaction := database.NewTransaction(dbModel, "Open_vSwitch", o.db, &o.logger) _, updates := transaction.Insert("Bridge", fooUUID, ovsdb.Row{"name": "foo"}) _, update2 := transaction.Insert("Bridge", barUUID, ovsdb.Row{"name": "bar"}) diff --git a/server/testdata/ovslite.json b/test/test_data.go similarity index 71% rename from server/testdata/ovslite.json rename to test/test_data.go index d8c402f1..ecd77743 100644 --- a/server/testdata/ovslite.json +++ b/test/test_data.go @@ -1,3 +1,12 @@ +package test + +import ( + "encoding/json" + + "github.com/ovn-org/libovsdb/ovsdb" +) + +const schema = ` { "name": "Open_vSwitch", "version": "0.0.1", @@ -78,4 +87,29 @@ ] } } -} \ No newline at end of file +} +` + +// BridgeType is the simplified ORM model of the Bridge table +type BridgeType struct { + UUID string `ovsdb:"_uuid"` + Name string `ovsdb:"name"` + DatapathType string `ovsdb:"datapath_type"` + DatapathID *string `ovsdb:"datapath_id"` + OtherConfig map[string]string `ovsdb:"other_config"` + ExternalIds map[string]string `ovsdb:"external_ids"` + Ports []string `ovsdb:"ports"` + Status map[string]string `ovsdb:"status"` +} + +// OvsType is the simplified ORM model of the Bridge table +type OvsType struct { + UUID string `ovsdb:"_uuid"` + Bridges []string `ovsdb:"bridges"` +} + +func GetSchema() (ovsdb.DatabaseSchema, error) { + var dbSchema ovsdb.DatabaseSchema + err := json.Unmarshal([]byte(schema), &dbSchema) + return dbSchema, err +} From 6cc3a672b9dc3f29d37f542c5e24f1428a65acc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Wed, 1 Jun 2022 13:15:25 +0000 Subject: [PATCH 03/18] client: validate transaction option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a client option that enables transaction verification. When transaction verification is enabled, transactions are serialized in the client. This avoids the verification of a transaction when other transactions are on flight ensuring the consistency of such verification. The verification is done by running the transaction in a similar way it is done server-side but on a throw-away database built on top of the client-cache. The primary use case for this verification is to check for duplicate indexes client-side instead of server-side and specifically client indexes which can not be checked server-side. Although support for this specific case will be introduced in later commit. Signed-off-by: Jaime Caamaño Ruiz --- client/client.go | 70 ++---------------- client/options.go | 12 ++++ client/transact.go | 154 ++++++++++++++++++++++++++++++++++++++++ database/transaction.go | 6 +- 4 files changed, 176 insertions(+), 66 deletions(-) create mode 100644 client/transact.go diff --git a/client/client.go b/client/client.go index 7008488a..6c7410c5 100644 --- a/client/client.go +++ b/client/client.go @@ -105,6 +105,9 @@ type ovsdbClient struct { handlerShutdown *sync.WaitGroup logger *logr.Logger + + // used to serialize transactions when validation is enabled + transactionLockCh chan struct{} } // database is everything needed to map between go types and an ovsdb Database @@ -150,9 +153,10 @@ func newOVSDBClient(clientDBModel model.ClientDBModel, opts ...Option) (*ovsdbCl deferredUpdates: make([]*bufferedUpdate, 0), }, }, - errorCh: make(chan error), - handlerShutdown: &sync.WaitGroup{}, - disconnect: make(chan struct{}), + errorCh: make(chan error), + handlerShutdown: &sync.WaitGroup{}, + disconnect: make(chan struct{}), + transactionLockCh: make(chan struct{}, 1), } var err error ovs.options, err = newOptions(opts...) @@ -729,66 +733,6 @@ func (o *ovsdbClient) listDbs(ctx context.Context) ([]string, error) { return dbs, err } -// Transact performs the provided Operations on the database -// RFC 7047 : transact -func (o *ovsdbClient) Transact(ctx context.Context, operation ...ovsdb.Operation) ([]ovsdb.OperationResult, error) { - o.rpcMutex.RLock() - if o.rpcClient == nil || !o.connected { - o.rpcMutex.RUnlock() - if o.options.reconnect { - o.logger.V(5).Info("blocking transaction until reconnected", "operations", - fmt.Sprintf("%+v", operation)) - ticker := time.NewTicker(50 * time.Millisecond) - defer ticker.Stop() - ReconnectWaitLoop: - for { - select { - case <-ctx.Done(): - return nil, fmt.Errorf("%w: while awaiting reconnection", ctx.Err()) - case <-ticker.C: - o.rpcMutex.RLock() - if o.rpcClient != nil && o.connected { - break ReconnectWaitLoop - } - o.rpcMutex.RUnlock() - } - } - } else { - return nil, ErrNotConnected - } - } - defer o.rpcMutex.RUnlock() - return o.transact(ctx, o.primaryDBName, operation...) -} - -func (o *ovsdbClient) transact(ctx context.Context, dbName string, operation ...ovsdb.Operation) ([]ovsdb.OperationResult, error) { - var reply []ovsdb.OperationResult - db := o.databases[dbName] - db.modelMutex.RLock() - schema := o.databases[dbName].model.Schema - db.modelMutex.RUnlock() - if reflect.DeepEqual(schema, ovsdb.DatabaseSchema{}) { - return nil, fmt.Errorf("cannot transact to database %s: schema unknown", dbName) - } - if ok := schema.ValidateOperations(operation...); !ok { - return nil, fmt.Errorf("validation failed for the operation") - } - - args := ovsdb.NewTransactArgs(dbName, operation...) - if o.rpcClient == nil { - return nil, ErrNotConnected - } - o.logger.V(4).Info("transacting operations", "database", dbName, "operations", fmt.Sprintf("%+v", operation)) - err := o.rpcClient.CallWithContext(ctx, "transact", args, &reply) - if err != nil { - if err == rpc2.ErrShutdown { - return nil, ErrNotConnected - } - return nil, err - } - return reply, nil -} - // MonitorAll is a convenience method to monitor every table/column func (o *ovsdbClient) MonitorAll(ctx context.Context) (MonitorCookie, error) { m := newMonitor() diff --git a/client/options.go b/client/options.go index 71e25240..5a83f568 100644 --- a/client/options.go +++ b/client/options.go @@ -26,6 +26,7 @@ type options struct { logger *logr.Logger registry prometheus.Registerer shouldRegisterMetrics bool // in case metrics are changed after-the-fact + validateTransactions bool } type Option func(o *options) error @@ -127,3 +128,14 @@ func WithMetricsRegistry(r prometheus.Registerer) Option { return nil } } + +// WithTransactionValidation enables transaction validation (i.e. check for +// duplicate indexes) before it is sent to the server. In order to do this +// consistently, transactions through this client are serialized. May incur in +// performance impact, disabled by default. +func WithTransactionValidation(validateTransactions bool) Option { + return func(o *options) error { + o.validateTransactions = validateTransactions + return nil + } +} diff --git a/client/transact.go b/client/transact.go new file mode 100644 index 00000000..771ba39f --- /dev/null +++ b/client/transact.go @@ -0,0 +1,154 @@ +package client + +import ( + "context" + "fmt" + "reflect" + "time" + + "github.com/cenkalti/rpc2" + "github.com/google/uuid" + "github.com/ovn-org/libovsdb/cache" + db "github.com/ovn-org/libovsdb/database" + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" +) + +// Transact performs the provided Operations on the database +// RFC 7047 : transact +func (o *ovsdbClient) Transact(ctx context.Context, operation ...ovsdb.Operation) ([]ovsdb.OperationResult, error) { + // if transactions are to be validated, they need to be serialized as well + if o.options.validateTransactions { + return o.transactSerial(ctx, operation...) + } + return o.transactReconnect(ctx, operation...) +} + +func (o *ovsdbClient) transactSerial(ctx context.Context, operation ...ovsdb.Operation) ([]ovsdb.OperationResult, error) { + select { + case o.transactionLockCh <- struct{}{}: + res, err := o.transactReconnect(ctx, operation...) + <-o.transactionLockCh + return res, err + case <-ctx.Done(): + return nil, fmt.Errorf("%w: while awaiting previous transaction to complete", ctx.Err()) + } +} + +func (o *ovsdbClient) transactReconnect(ctx context.Context, operation ...ovsdb.Operation) ([]ovsdb.OperationResult, error) { + o.rpcMutex.RLock() + if o.rpcClient == nil || !o.connected { + o.rpcMutex.RUnlock() + if o.options.reconnect { + o.logger.V(5).Info("blocking transaction until reconnected", "operations", + fmt.Sprintf("%+v", operation)) + ticker := time.NewTicker(50 * time.Millisecond) + defer ticker.Stop() + ReconnectWaitLoop: + for { + select { + case <-ctx.Done(): + return nil, fmt.Errorf("%w: while awaiting reconnection", ctx.Err()) + case <-ticker.C: + o.rpcMutex.RLock() + if o.rpcClient != nil && o.connected { + break ReconnectWaitLoop + } + o.rpcMutex.RUnlock() + } + } + } else { + return nil, ErrNotConnected + } + } + defer o.rpcMutex.RUnlock() + + return o.transact(ctx, o.primaryDBName, operation...) +} + +func (o *ovsdbClient) transact(ctx context.Context, dbName string, operation ...ovsdb.Operation) ([]ovsdb.OperationResult, error) { + if err := o.ValidateOperations(dbName, operation...); err != nil { + return nil, err + } + + args := ovsdb.NewTransactArgs(dbName, operation...) + if o.rpcClient == nil { + return nil, ErrNotConnected + } + o.logger.V(4).Info("transacting operations", "database", dbName, "operations", fmt.Sprintf("%+v", operation)) + var reply []ovsdb.OperationResult + err := o.rpcClient.CallWithContext(ctx, "transact", args, &reply) + if err != nil { + if err == rpc2.ErrShutdown { + return nil, ErrNotConnected + } + return nil, err + } + return reply, nil +} + +func (o *ovsdbClient) ValidateOperations(dbName string, operations ...ovsdb.Operation) error { + database := o.databases[dbName] + database.modelMutex.RLock() + schema := o.databases[dbName].model.Schema + database.modelMutex.RUnlock() + if reflect.DeepEqual(schema, ovsdb.DatabaseSchema{}) { + return fmt.Errorf("cannot transact to database %s: schema unknown", dbName) + } + + // validate operations against the schema + if ok := schema.ValidateOperations(operations...); !ok { + return fmt.Errorf("validation failed for the operation") + } + + // don't do any further validations unless the option is enabled + if !o.options.validateTransactions { + return nil + } + + // validate operations against a temporary database built from the cache + database.cacheMutex.RLock() + defer database.cacheMutex.RUnlock() + cacheDatabase := cacheDatabase{database.cache} + transaction := db.NewTransaction(database.model, dbName, &cacheDatabase, o.logger) + results, _ := transaction.Transact(operations) + _, err := ovsdb.CheckOperationResults(results, operations) + + return err +} + +type cacheDatabase struct { + cache *cache.TableCache +} + +func (c *cacheDatabase) CreateDatabase(database string, model ovsdb.DatabaseSchema) error { + panic("not implemented") // TODO: Implement +} + +func (c *cacheDatabase) Exists(database string) bool { + panic("not implemented") // TODO: Implement +} + +func (c *cacheDatabase) Commit(database string, id uuid.UUID, updates ovsdb.TableUpdates2) error { + panic("not implemented") // TODO: Implement +} + +func (c *cacheDatabase) CheckIndexes(database string, table string, m model.Model) error { + targetTable := c.cache.Table(table) + if targetTable == nil { + return fmt.Errorf("table does not exist") + } + return targetTable.IndexExists(m) +} + +func (c *cacheDatabase) List(database string, table string, conditions ...ovsdb.Condition) (map[string]model.Model, error) { + targetTable := c.cache.Table(table) + if targetTable == nil { + return nil, fmt.Errorf("table does not exist") + } + return targetTable.RowsByCondition(conditions) +} + +func (c *cacheDatabase) Get(database string, table string, uuid string) (model.Model, error) { + panic("not implemented") // TODO: Implement +} diff --git a/database/transaction.go b/database/transaction.go index 9543a4a1..072b6c7e 100644 --- a/database/transaction.go +++ b/database/transaction.go @@ -23,7 +23,7 @@ type Transaction struct { func NewTransaction(model model.DatabaseModel, dbName string, database Database, logger *logr.Logger) Transaction { if logger != nil { - l := logger.WithName("transaction") + l := logger.WithName("transaction") logger = &l } cache, err := cache.NewTableCache(model, nil, logger) @@ -153,7 +153,7 @@ func (t *Transaction) rowsFromTransactionCacheAndDatabase(table string, where [] return rows, nil } -// checkIndexes checks that there are no index conflicts: +// checkIndexes checks that there are no index conflicts: // - no duplicate indexes among any two rows operated with in the transaction // - no duplicate indexes of any transaction row with any database row func (t *Transaction) checkIndexes() error { @@ -172,7 +172,7 @@ func (t *Transaction) checkIndexes() error { return err } for _, existing := range errIndexExists.Existing { - if _, isDeleted := t.DeletedRows[existing]; isDeleted { + if _, isDeleted := t.DeletedRows[existing]; isDeleted { // this model is deleted in the transaction, ignore it continue } From 74f2338092c82bd6632856766df272a9cc5f97e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Wed, 1 Jun 2022 13:16:11 +0000 Subject: [PATCH 04/18] cache: support primary and secondary client indexes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces two types of client indexes: primary and secondary. Primary indexes are verified to no be duplicate at transaction verification, when enable, otherwise there is no difference between them. Signed-off-by: Jaime Caamaño Ruiz --- README.md | 14 +++++++++++++- cache/cache.go | 22 +++++++++++++++------- model/client.go | 14 ++++++++++++++ 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 25339d99..e42f125e 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Additional indexes can be specified for a client instance to track. Just as sche where each set consists of the columns that compose the index. Unlike schema indexes, a key within a column can be addressed if the column type is a map. -Client indexes are leveraged through `Where`, and `WhereAll`. Since client indexes value uniqueness is not enforced as it happens with schema indexes, +Client indexes are leveraged through `Where`, and `WhereAll`. Since client indexes value uniqueness is not guaranteed as it happens with schema indexes, conditions based on them can match multiple rows. Indexed based operations generally provide better performance than operations based on explicit conditions. @@ -116,6 +116,18 @@ can now be improved with: // quick indexed result ovn.Where(lb).List(ctx, &results) +Client indexes can be one of two types: + +* **Primary**: Primary client indexes are verified to be unique in the context of a specific client instance in an optional transaction validation that happens +before sending the transaction to the server. Client option `ValidateTransactions` needs to be enabled. When it is, if a transaction includes a primary client +index that already exists for a different row in the client cache or the transaction itself, the transaction will fail. Be aware that this transaction validation +comes at a performance cost. Primary client indexes are the default unless otherwise specified but `ValidateTransactions` option is not enabled by default. +* **Secondary**: Secondary client indexes don't have any additional validation. + +**Note**: `ValidateTransactions` does not consider strongly referenced rows that should be garbage collected and may report a duplicate index when it shouldn't. +The workarounds would be either to not use validation, to explicitly remove strong referenced rows in the transaction, or to use a separate transaction than the +one that removes the strongly referenced row to create a similar one with the same index. + ## Documentation This package is divided into several sub-packages. Documentation for each sub-package is available at [pkg.go.dev][doc]: diff --git a/cache/cache.go b/cache/cache.go index c2f93c6a..cb7685d4 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -83,9 +83,14 @@ type indexType uint const ( schemaIndexType indexType = iota - clientIndexType + primaryClientIndexType + secondaryClientIndexType ) +func clientIndexTypeToCacheIndexType(modelIndexType model.IndexType) indexType { + return indexType(modelIndexType + 1) +} + // indexSpec contains details about an index type indexSpec struct { index index @@ -94,13 +99,17 @@ type indexSpec struct { } func (s indexSpec) isClientIndex() bool { - return s.indexType == clientIndexType + return s.indexType >= primaryClientIndexType } func (s indexSpec) isSchemaIndex() bool { return s.indexType == schemaIndexType } +func (s indexSpec) isPrimaryIndex() bool { + return s.indexType < secondaryClientIndexType +} + // newIndex builds a index from a list of columns func newIndexFromColumns(columns ...string) index { sort.Strings(columns) @@ -372,10 +381,8 @@ func (r *RowCache) IndexExists(row model.Model) error { } uuid := field.(string) for _, indexSpec := range r.indexSpecs { - if !indexSpec.isSchemaIndex() { - // Given the ordered indexSpecs, we can break here if we reach the - // first non schema index - break + if !indexSpec.isPrimaryIndex() { + continue } index := indexSpec.index val, err := valueFromIndex(info, indexSpec.columns) @@ -1117,7 +1124,8 @@ func newRowCache(name string, dbModel model.DatabaseModel, dataType reflect.Type if _, ok := indexes[index]; ok { continue } - spec := indexSpec{index: index, columns: columnKeys, indexType: clientIndexType} + indexType := clientIndexTypeToCacheIndexType(clientIndex.Type) + spec := indexSpec{index: index, columns: columnKeys, indexType: indexType} r.indexSpecs = append(r.indexSpecs, spec) indexes[index] = spec } diff --git a/model/client.go b/model/client.go index 5eb68624..220c2916 100644 --- a/model/client.go +++ b/model/client.go @@ -14,9 +14,22 @@ type ColumnKey struct { Key interface{} } +// IndexType is the type of client index +type IndexType uint + +const ( + // PrimaryIndexType are client indexes for which uniqueness is optionally + // enforced in the context of a specific client instance transaction before + // it is sent to the server. + PrimaryIndexType IndexType = iota + // SecondaryIndexType are indexes for which uniqueness is not enforced. + SecondaryIndexType +) + // ClientIndex defines a client index by a set of columns type ClientIndex struct { Columns []ColumnKey + Type IndexType } // ClientDBModel contains the client information needed to build a DatabaseModel @@ -161,6 +174,7 @@ func copyIndexes(src map[string][]ClientIndex) map[string][]ClientIndex { dst[table] = make([]ClientIndex, 0, len(indexSets)) for _, indexSet := range indexSets { indexSetCopy := ClientIndex{ + Type: indexSet.Type, Columns: make([]ColumnKey, len(indexSet.Columns)), } copy(indexSetCopy.Columns, indexSet.Columns) From 6bcda1d9a15a419a5069eb3cff3930bcb1f10b70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Wed, 1 Jun 2022 13:15:41 +0000 Subject: [PATCH 05/18] database: add transaction validation tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Caamaño Ruiz --- client/client_test.go | 315 ++++++++++++++++++++++++++++++++--- client/monitor_test.go | 4 +- database/transaction_test.go | 195 +++++++++++++++++++++- 3 files changed, 491 insertions(+), 23 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index b4a5e0a4..fc2e54f8 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -14,6 +14,8 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/cenkalti/rpc2" + "github.com/go-logr/logr" + "github.com/go-logr/stdr" "github.com/google/uuid" "github.com/ovn-org/libovsdb/cache" db "github.com/ovn-org/libovsdb/database" @@ -97,12 +99,15 @@ type OpenvSwitch struct { SystemVersion *string `ovsdb:"system_version"` } -var defDB, _ = model.NewClientDBModel("Open_vSwitch", - map[string]model.Model{ - "Open_vSwitch": &OpenvSwitch{}, - "Bridge": &Bridge{}, - }, -) +func defDB() model.ClientDBModel { + dbModel, _ := model.NewClientDBModel("Open_vSwitch", + map[string]model.Model{ + "Open_vSwitch": &OpenvSwitch{}, + "Bridge": &Bridge{}, + }, + ) + return dbModel +} var schema = `{ "name": "Open_vSwitch", @@ -564,7 +569,7 @@ func newOvsRow(bridges ...string) string { } func BenchmarkUpdate1(b *testing.B) { - ovs, err := newOVSDBClient(defDB) + ovs, err := newOVSDBClient(defDB()) require.NoError(b, err) var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) @@ -590,7 +595,7 @@ func BenchmarkUpdate1(b *testing.B) { } func BenchmarkUpdate2(b *testing.B) { - ovs, err := newOVSDBClient(defDB) + ovs, err := newOVSDBClient(defDB()) require.NoError(b, err) var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) @@ -617,7 +622,7 @@ func BenchmarkUpdate2(b *testing.B) { } func BenchmarkUpdate3(b *testing.B) { - ovs, err := newOVSDBClient(defDB) + ovs, err := newOVSDBClient(defDB()) require.NoError(b, err) var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) @@ -645,7 +650,7 @@ func BenchmarkUpdate3(b *testing.B) { } func BenchmarkUpdate5(b *testing.B) { - ovs, err := newOVSDBClient(defDB) + ovs, err := newOVSDBClient(defDB()) require.NoError(b, err) var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) @@ -675,7 +680,7 @@ func BenchmarkUpdate5(b *testing.B) { } func BenchmarkUpdate8(b *testing.B) { - ovs, err := newOVSDBClient(defDB) + ovs, err := newOVSDBClient(defDB()) require.NoError(b, err) var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) @@ -710,7 +715,7 @@ func BenchmarkUpdate8(b *testing.B) { func TestEcho(t *testing.T) { req := []interface{}{"hi"} var reply []interface{} - ovs, err := newOVSDBClient(defDB) + ovs, err := newOVSDBClient(defDB()) require.NoError(t, err) err = ovs.echo(req, &reply) if err != nil { @@ -722,7 +727,7 @@ func TestEcho(t *testing.T) { } func TestUpdate(t *testing.T) { - ovs, err := newOVSDBClient(defDB) + ovs, err := newOVSDBClient(defDB()) require.NoError(t, err) var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) @@ -753,7 +758,7 @@ func TestUpdate(t *testing.T) { } func TestOperationWhenNeverConnected(t *testing.T) { - ovs, err := newOVSDBClient(defDB) + ovs, err := newOVSDBClient(defDB()) require.NoError(t, err) var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) @@ -800,7 +805,7 @@ func TestOperationWhenNeverConnected(t *testing.T) { } func TestOperationWhenNotConnected(t *testing.T) { - ovs, err := newOVSDBClient(defDB) + ovs, err := newOVSDBClient(defDB()) require.NoError(t, err) var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) @@ -851,7 +856,7 @@ func TestOperationWhenNotConnected(t *testing.T) { } func TestSetOption(t *testing.T) { - o, err := newOVSDBClient(defDB) + o, err := newOVSDBClient(defDB()) require.NoError(t, err) o.options, err = newOptions() @@ -866,7 +871,7 @@ func TestSetOption(t *testing.T) { assert.EqualError(t, err, "cannot set option when client is connected") } -func newOVSDBServer(t *testing.T, dbModel model.ClientDBModel, schema ovsdb.DatabaseSchema) (*server.OvsdbServer, string) { +func newOVSDBServer(t testing.TB, dbModel model.ClientDBModel, schema ovsdb.DatabaseSchema) (*server.OvsdbServer, string) { serverDBModel, err := serverdb.FullDatabaseModel() require.NoError(t, err) serverSchema := serverdb.Schema() @@ -911,7 +916,7 @@ func newClientServerPair(t *testing.T, connectCounter *int32, isLeader bool) (Cl require.NoError(t, err) // Create server - s, sock := newOVSDBServer(t, defDB, defSchema) + s, sock := newOVSDBServer(t, defDB(), defSchema) s.OnConnect(func(_ *rpc2.Client) { atomic.AddInt32(connectCounter, 1) }) @@ -928,7 +933,7 @@ func newClientServerPair(t *testing.T, connectCounter *int32, isLeader bool) (Cl sid := fmt.Sprintf("%04x", rand.Uint32()) row := &serverdb.Database{ UUID: uuid.NewString(), - Name: defDB.Name(), + Name: defDB().Name(), Connected: true, Leader: isLeader, Model: serverdb.DatabaseModelClustered, @@ -963,7 +968,7 @@ func TestClientReconnectLeaderOnly(t *testing.T) { cli2, row2, endpoint2 := newClientServerPair(t, &connected2, false) // Create client to test reconnection for - ovs, err := newOVSDBClient(defDB, + ovs, err := newOVSDBClient(defDB(), WithLeaderOnly(true), WithReconnect(5*time.Second, &backoff.ZeroBackOff{}), WithEndpoint(endpoint1), @@ -1014,3 +1019,273 @@ func TestClientReconnectLeaderOnly(t *testing.T) { return atomic.LoadInt32(&connected2) > 2 }, 2*time.Second, 10*time.Millisecond) } + +func TestClientValidateTransaction(t *testing.T) { + var defSchema ovsdb.DatabaseSchema + err := json.Unmarshal([]byte(schema), &defSchema) + require.NoError(t, err) + + // Create server with default model + _, sock := newOVSDBServer(t, defDB(), defSchema) + + // Create a client with primary and secondary indexes + // and transaction validation + dbModel := defDB() + dbModel.SetIndexes( + map[string][]model.ClientIndex{ + "Bridge": { + model.ClientIndex{ + Type: model.PrimaryIndexType, + Columns: []model.ColumnKey{ + { + Column: "datapath_type", + }, + { + Column: "datapath_version", + }, + }, + }, + model.ClientIndex{ + Type: model.SecondaryIndexType, + Columns: []model.ColumnKey{ + { + Column: "datapath_type", + }, + }, + }, + }, + }, + ) + + endpoint := fmt.Sprintf("unix:%s", sock) + cli, err := newOVSDBClient(dbModel, WithEndpoint(endpoint), WithTransactionValidation(true)) + require.NoError(t, err) + err = cli.Connect(context.Background()) + require.NoError(t, err) + _, err = cli.MonitorAll(context.Background()) + require.NoError(t, err) + + tests := []struct { + desc string + create *Bridge + update *Bridge + delete *Bridge + expectedErrorType interface{} + }{ + { + "Creating a first bridge should succeed", + &Bridge{ + Name: "bridge", + DatapathType: "type1", + DatapathVersion: "1", + }, + nil, + nil, + nil, + }, + { + "Creating a duplicate schema index should fail", + &Bridge{ + Name: "bridge", + DatapathType: "type2", + DatapathVersion: "2", + }, + nil, + nil, + &ovsdb.ConstraintViolation{}, + }, + { + "Creating a duplicate primary client index should fail", + &Bridge{ + Name: "bridge2", + DatapathType: "type1", + DatapathVersion: "1", + }, + nil, + nil, + &ovsdb.ConstraintViolation{}, + }, + { + "Creating a duplicate secondary client index should succeed", + &Bridge{ + Name: "bridge2", + DatapathType: "type1", + DatapathVersion: "2", + }, + nil, + nil, + nil, + }, + { + "Updating to duplicate a primary client index should fail", + nil, + &Bridge{ + Name: "bridge2", + DatapathType: "type1", + DatapathVersion: "1", + }, + nil, + &ovsdb.ConstraintViolation{}, + }, + { + "Changing an existing index and creating it again in the same transaction should succeed", + &Bridge{ + Name: "bridge3", + DatapathType: "type1", + DatapathVersion: "1", + }, + &Bridge{ + Name: "bridge", + DatapathVersion: "3", + }, + nil, + nil, + }, + { + "Deleting an existing index and creating it again in the same transaction should succeed", + &Bridge{ + Name: "bridge4", + DatapathType: "type1", + DatapathVersion: "1", + }, + nil, + &Bridge{ + Name: "bridge3", + DatapathType: "type1", + DatapathVersion: "1", + }, + nil, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + ops := []ovsdb.Operation{} + if tt.delete != nil { + deleteOps, err := cli.Where(tt.delete).Delete() + require.NoError(t, err) + ops = append(ops, deleteOps...) + } + if tt.update != nil { + updateOps, err := cli.Where(tt.update).Update(tt.update) + require.NoError(t, err) + ops = append(ops, updateOps...) + } + if tt.create != nil { + createOps, err := cli.Create(tt.create) + require.NoError(t, err) + ops = append(ops, createOps...) + } + + res, err := cli.Transact(context.Background(), ops...) + if tt.expectedErrorType != nil { + require.Error(t, err) + require.IsTypef(t, tt.expectedErrorType, err, err.Error()) + } else { + require.NoError(t, err) + _, err = ovsdb.CheckOperationResults(res, ops) + require.NoError(t, err) + } + }) + } +} + +func BenchmarkClientValidateTransaction(b *testing.B) { + var defSchema ovsdb.DatabaseSchema + err := json.Unmarshal([]byte(schema), &defSchema) + require.NoError(b, err) + + // Create server with default model + _, sock := newOVSDBServer(b, defDB(), defSchema) + verbosity := stdr.SetVerbosity(0) + b.Cleanup(func() { + stdr.SetVerbosity(verbosity) + }) + + // Create a client with transaction validation + getClient := func(opts ...Option) *ovsdbClient { + dbModel := defDB() + endpoint := fmt.Sprintf("unix:%s", sock) + l := logr.Discard() + cli, err := newOVSDBClient(dbModel, append(opts, WithEndpoint(endpoint), WithLogger(&l))...) + stdr.SetVerbosity(0) + require.NoError(b, err) + err = cli.Connect(context.Background()) + require.NoError(b, err) + _, err = cli.MonitorAll(context.Background()) + require.NoError(b, err) + return cli + } + + cli := getClient() + + numRows := 1000 + models := []*Bridge{} + for i := 0; i < numRows; i++ { + model := &Bridge{ + Name: fmt.Sprintf("Name-%d", i), + DatapathVersion: fmt.Sprintf("DatapathVersion-%d", i), + } + ops, err := cli.Create(model) + require.NoError(b, err) + _, err = cli.Transact(context.Background(), ops...) + require.NoError(b, err) + models = append(models, model) + } + + rand.Seed(int64(b.N)) + + benchmarks := []struct { + name string + client *ovsdbClient + ops int + }{ + { + "1 update ops with validating client", + getClient(WithTransactionValidation(true)), + 1, + }, + { + "1 update ops with non validating client", + getClient(), + 1, + }, + { + "10 update ops with validating client", + getClient(WithTransactionValidation(true)), + 10, + }, + { + "10 update ops with non validating client", + getClient(), + 10, + }, + { + "100 update ops with validating client", + getClient(WithTransactionValidation(true)), + 100, + }, + { + "100 update ops with non validating client", + getClient(), + 100, + }, + } + for _, bm := range benchmarks { + b.Run(bm.name, func(b *testing.B) { + cli := bm.client + ops := []ovsdb.Operation{} + for j := 0; j < bm.ops; j++ { + model := models[rand.Intn(numRows)] + model.DatapathVersion = fmt.Sprintf("%s-Updated", model.DatapathVersion) + op, err := cli.Where(model).Update(model) + require.NoError(b, err) + ops = append(ops, op...) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := cli.Transact(context.Background(), ops...) + require.NoError(b, err) + } + }) + } +} diff --git a/client/monitor_test.go b/client/monitor_test.go index a5336f6d..7039ca38 100644 --- a/client/monitor_test.go +++ b/client/monitor_test.go @@ -10,7 +10,7 @@ import ( ) func TestWithTable(t *testing.T) { - client, err := newOVSDBClient(defDB) + client, err := newOVSDBClient(defDB()) assert.NoError(t, err) m := newMonitor() opt := WithTable(&OpenvSwitch{}) @@ -37,7 +37,7 @@ func populateClientModel(t *testing.T, client *ovsdbClient) { } func TestWithTableAndFields(t *testing.T) { - client, err := newOVSDBClient(defDB) + client, err := newOVSDBClient(defDB()) assert.NoError(t, err) populateClientModel(t, client) diff --git a/database/transaction_test.go b/database/transaction_test.go index e779e883..6a8f28cf 100644 --- a/database/transaction_test.go +++ b/database/transaction_test.go @@ -11,7 +11,7 @@ import ( "github.com/ovn-org/libovsdb/mapper" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" - + . "github.com/ovn-org/libovsdb/test" ) @@ -709,3 +709,196 @@ func TestMultipleOps(t *testing.T) { }, updates) } + +func TestCheckIndexes(t *testing.T) { + clientDbModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ + "Open_vSwitch": &OvsType{}, + "Bridge": &BridgeType{}}) + if err != nil { + t.Fatal(err) + } + clientDbModel.SetIndexes( + map[string][]model.ClientIndex{ + "Bridge": { + model.ClientIndex{ + Type: model.PrimaryIndexType, + Columns: []model.ColumnKey{ + { + Column: "external_ids", + Key: "primary_key", + }, + }, + }, + model.ClientIndex{ + Type: model.SecondaryIndexType, + Columns: []model.ColumnKey{ + { + Column: "external_ids", + Key: "secondary_key", + }, + }, + }, + }, + }, + ) + schema, err := GetSchema() + if err != nil { + t.Fatal(err) + } + db := NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": clientDbModel}) + err = db.CreateDatabase("Open_vSwitch", schema) + require.NoError(t, err) + dbModel, errs := model.NewDatabaseModel(schema, clientDbModel) + require.Empty(t, errs) + m := mapper.NewMapper(schema) + + bridgeUUID := uuid.NewString() + bridge := BridgeType{ + Name: "a_bridge_to_nowhere", + ExternalIds: map[string]string{ + "primary_key": "primary_key_1", + "secondary_key": "secondary_key_1", + }, + } + bridgeInfo, err := dbModel.NewModelInfo(&bridge) + require.NoError(t, err) + bridgeRow, err := m.NewRow(bridgeInfo) + require.Nil(t, err) + + transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) + + res, updates := transaction.Insert("Bridge", bridgeUUID, bridgeRow) + _, err = ovsdb.CheckOperationResults([]ovsdb.OperationResult{res}, []ovsdb.Operation{{Op: "insert"}}) + require.Nil(t, err) + + err = db.Commit("Open_vSwitch", uuid.New(), updates) + require.NoError(t, err) + + tests := []struct { + desc string + ops func() []ovsdb.Operation + expectedErrorType interface{} + }{ + { + "Create to duplicate a schema index should fail", + func() []ovsdb.Operation { + return []ovsdb.Operation{ + { + Table: "Bridge", + Op: ovsdb.OperationInsert, + Row: ovsdb.Row{ + "name": "a_bridge_to_nowhere", + }, + }, + } + }, + &ovsdb.ConstraintViolation{}, + }, + { + "Create to a duplicate primary client index should fail", + func() []ovsdb.Operation { + return []ovsdb.Operation{ + { + Table: "Bridge", + Op: ovsdb.OperationInsert, + Row: ovsdb.Row{ + "name": "a_bridge_to_nowhere_2", + "external_ids": ovsdb.OvsMap{GoMap: map[interface{}]interface{}{ + "primary_key": "primary_key_1", + }}, + }, + }, + } + }, + &ovsdb.ConstraintViolation{}, + }, + { + "Creating a duplicate secondary client index should succeed", + func() []ovsdb.Operation { + return []ovsdb.Operation{ + { + Table: "Bridge", + Op: ovsdb.OperationInsert, + Row: ovsdb.Row{ + "name": "a_bridge_to_nowhere_2", + "external_ids": ovsdb.OvsMap{GoMap: map[interface{}]interface{}{ + "secondary_key": "secondary_key_1", + }}, + }, + }, + } + }, + nil, + }, + { + "Changing an existing index and creating it again in the same transaction should succeed", + func() []ovsdb.Operation { + return []ovsdb.Operation{ + { + Table: "Bridge", + Op: ovsdb.OperationInsert, + Row: ovsdb.Row{ + "name": "a_bridge_to_nowhere_2", + "external_ids": ovsdb.OvsMap{GoMap: map[interface{}]interface{}{ + "primary_key": "primary_key_1", + }}, + }, + }, + { + Table: "Bridge", + Op: ovsdb.OperationUpdate, + Row: ovsdb.Row{ + "external_ids": ovsdb.OvsMap{GoMap: map[interface{}]interface{}{ + "primary_key": "primary_key_2", + }}, + }, + Where: []ovsdb.Condition{ + ovsdb.NewCondition("_uuid", ovsdb.ConditionEqual, ovsdb.UUID{GoUUID: bridgeUUID}), + }, + }, + } + }, + nil, + }, + { + "Deleting an existing index and creating it again in the same transaction should succeed", + func() []ovsdb.Operation { + return []ovsdb.Operation{ + { + Table: "Bridge", + Op: ovsdb.OperationInsert, + Row: ovsdb.Row{ + "name": "a_bridge_to_nowhere_2", + "external_ids": ovsdb.OvsMap{GoMap: map[interface{}]interface{}{ + "primary_key": "primary_key_1", + }}, + }, + }, + { + Table: "Bridge", + Op: ovsdb.OperationDelete, + Where: []ovsdb.Condition{ + ovsdb.NewCondition("_uuid", ovsdb.ConditionEqual, ovsdb.UUID{GoUUID: bridgeUUID}), + }, + }, + } + }, + nil, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + transaction := NewTransaction(dbModel, "Open_vSwitch", db, nil) + ops := tt.ops() + res, _ := transaction.Transact(ops) + _, err = ovsdb.CheckOperationResults(res, ops) + if tt.expectedErrorType != nil { + require.Error(t, err) + require.IsTypef(t, tt.expectedErrorType, err, err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} From 2090b8d61525a716b9e56625e53734c9fec861de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Wed, 1 Jun 2022 18:24:57 +0000 Subject: [PATCH 06/18] Make some tests use a reference test model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So that we have DeepCopy of models available and benchmarks throw out more realistic numbers. Signed-off-by: Jaime Caamaño Ruiz --- client/client_test.go | 565 ++-------- client/monitor_test.go | 15 +- database/transaction_test.go | 78 +- server/server_integration_test.go | 89 +- server/server_test.go | 6 +- test/ovs/ovs_integration_test.go | 170 +-- test/test_data.go | 115 -- test/test_model.go | 1693 +++++++++++++++++++++++++++++ 8 files changed, 1893 insertions(+), 838 deletions(-) delete mode 100644 test/test_data.go create mode 100644 test/test_model.go diff --git a/client/client_test.go b/client/client_test.go index fc2e54f8..8f33119f 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -25,6 +25,8 @@ import ( "github.com/ovn-org/libovsdb/server" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + . "github.com/ovn-org/libovsdb/test" ) var ( @@ -34,464 +36,6 @@ var ( aUUID3 = "2f77b348-9768-4866-b761-89d5177ecda3" ) -type ( - BridgeFailMode = string - BridgeProtocols = string -) - -const ( - BridgeFailModeStandalone BridgeFailMode = "standalone" - BridgeFailModeSecure BridgeFailMode = "secure" - BridgeProtocolsOpenflow10 BridgeProtocols = "OpenFlow10" - BridgeProtocolsOpenflow11 BridgeProtocols = "OpenFlow11" - BridgeProtocolsOpenflow12 BridgeProtocols = "OpenFlow12" - BridgeProtocolsOpenflow13 BridgeProtocols = "OpenFlow13" - BridgeProtocolsOpenflow14 BridgeProtocols = "OpenFlow14" - BridgeProtocolsOpenflow15 BridgeProtocols = "OpenFlow15" -) - -// Bridge defines an object in Bridge table -type Bridge struct { - UUID string `ovsdb:"_uuid"` - AutoAttach *string `ovsdb:"auto_attach"` - Controller []string `ovsdb:"controller"` - DatapathID *string `ovsdb:"datapath_id"` - DatapathType string `ovsdb:"datapath_type"` - DatapathVersion string `ovsdb:"datapath_version"` - ExternalIDs map[string]string `ovsdb:"external_ids"` - FailMode *BridgeFailMode `ovsdb:"fail_mode"` - FloodVLANs [4096]int `ovsdb:"flood_vlans"` - FlowTables map[int]string `ovsdb:"flow_tables"` - IPFIX *string `ovsdb:"ipfix"` - McastSnoopingEnable bool `ovsdb:"mcast_snooping_enable"` - Mirrors []string `ovsdb:"mirrors"` - Name string `ovsdb:"name"` - Netflow *string `ovsdb:"netflow"` - OtherConfig map[string]string `ovsdb:"other_config"` - Ports []string `ovsdb:"ports"` - Protocols []BridgeProtocols `ovsdb:"protocols"` - RSTPEnable bool `ovsdb:"rstp_enable"` - RSTPStatus map[string]string `ovsdb:"rstp_status"` - Sflow *string `ovsdb:"sflow"` - Status map[string]string `ovsdb:"status"` - STPEnable bool `ovsdb:"stp_enable"` -} - -// OpenvSwitch defines an object in Open_vSwitch table -type OpenvSwitch struct { - UUID string `ovsdb:"_uuid"` - Bridges []string `ovsdb:"bridges"` - CurCfg int `ovsdb:"cur_cfg"` - DatapathTypes []string `ovsdb:"datapath_types"` - Datapaths map[string]string `ovsdb:"datapaths"` - DbVersion *string `ovsdb:"db_version"` - DpdkInitialized bool `ovsdb:"dpdk_initialized"` - DpdkVersion *string `ovsdb:"dpdk_version"` - ExternalIDs map[string]string `ovsdb:"external_ids"` - IfaceTypes []string `ovsdb:"iface_types"` - ManagerOptions []string `ovsdb:"manager_options"` - NextCfg int `ovsdb:"next_cfg"` - OtherConfig map[string]string `ovsdb:"other_config"` - OVSVersion *string `ovsdb:"ovs_version"` - SSL *string `ovsdb:"ssl"` - Statistics map[string]string `ovsdb:"statistics"` - SystemType *string `ovsdb:"system_type"` - SystemVersion *string `ovsdb:"system_version"` -} - -func defDB() model.ClientDBModel { - dbModel, _ := model.NewClientDBModel("Open_vSwitch", - map[string]model.Model{ - "Open_vSwitch": &OpenvSwitch{}, - "Bridge": &Bridge{}, - }, - ) - return dbModel -} - -var schema = `{ - "name": "Open_vSwitch", - "version": "8.2.0", - "tables": { - "Bridge": { - "columns": { - "auto_attach": { - "type": { - "key": { - "type": "uuid", - "refTable": "AutoAttach" - }, - "min": 0, - "max": 1 - } - }, - "controller": { - "type": { - "key": { - "type": "uuid", - "refTable": "Controller" - }, - "min": 0, - "max": "unlimited" - } - }, - "datapath_id": { - "type": { - "key": { - "type": "string" - }, - "min": 0, - "max": 1 - }, - "ephemeral": true - }, - "datapath_type": { - "type": "string" - }, - "datapath_version": { - "type": "string" - }, - "external_ids": { - "type": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - }, - "min": 0, - "max": "unlimited" - } - }, - "fail_mode": { - "type": { - "key": { - "type": "string", - "enum": [ - "set", - [ - "standalone", - "secure" - ] - ] - }, - "min": 0, - "max": 1 - } - }, - "flood_vlans": { - "type": { - "key": { - "type": "integer", - "minInteger": 0, - "maxInteger": 4095 - }, - "min": 0, - "max": 4096 - } - }, - "flow_tables": { - "type": { - "key": { - "type": "integer", - "minInteger": 0, - "maxInteger": 254 - }, - "value": { - "type": "uuid", - "refTable": "Flow_Table" - }, - "min": 0, - "max": "unlimited" - } - }, - "ipfix": { - "type": { - "key": { - "type": "uuid", - "refTable": "IPFIX" - }, - "min": 0, - "max": 1 - } - }, - "mcast_snooping_enable": { - "type": "boolean" - }, - "mirrors": { - "type": { - "key": { - "type": "uuid", - "refTable": "Mirror" - }, - "min": 0, - "max": "unlimited" - } - }, - "name": { - "type": "string", - "mutable": false - }, - "netflow": { - "type": { - "key": { - "type": "uuid", - "refTable": "NetFlow" - }, - "min": 0, - "max": 1 - } - }, - "other_config": { - "type": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - }, - "min": 0, - "max": "unlimited" - } - }, - "ports": { - "type": { - "key": { - "type": "uuid", - "refTable": "Port" - }, - "min": 0, - "max": "unlimited" - } - }, - "protocols": { - "type": { - "key": { - "type": "string", - "enum": [ - "set", - [ - "OpenFlow10", - "OpenFlow11", - "OpenFlow12", - "OpenFlow13", - "OpenFlow14", - "OpenFlow15" - ] - ] - }, - "min": 0, - "max": "unlimited" - } - }, - "rstp_enable": { - "type": "boolean" - }, - "rstp_status": { - "type": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - }, - "min": 0, - "max": "unlimited" - }, - "ephemeral": true - }, - "sflow": { - "type": { - "key": { - "type": "uuid", - "refTable": "sFlow" - }, - "min": 0, - "max": 1 - } - }, - "status": { - "type": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - }, - "min": 0, - "max": "unlimited" - }, - "ephemeral": true - }, - "stp_enable": { - "type": "boolean" - } - }, - "indexes": [ - [ - "name" - ] - ] - }, - "Open_vSwitch": { - "columns": { - "bridges": { - "type": { - "key": { - "type": "uuid", - "refTable": "Bridge" - }, - "min": 0, - "max": "unlimited" - } - }, - "cur_cfg": { - "type": "integer" - }, - "datapath_types": { - "type": { - "key": { - "type": "string" - }, - "min": 0, - "max": "unlimited" - } - }, - "datapaths": { - "type": { - "key": { - "type": "string" - }, - "value": { - "type": "uuid", - "refTable": "Datapath" - }, - "min": 0, - "max": "unlimited" - } - }, - "db_version": { - "type": { - "key": { - "type": "string" - }, - "min": 0, - "max": 1 - } - }, - "dpdk_initialized": { - "type": "boolean" - }, - "dpdk_version": { - "type": { - "key": { - "type": "string" - }, - "min": 0, - "max": 1 - } - }, - "external_ids": { - "type": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - }, - "min": 0, - "max": "unlimited" - } - }, - "iface_types": { - "type": { - "key": { - "type": "string" - }, - "min": 0, - "max": "unlimited" - } - }, - "manager_options": { - "type": { - "key": { - "type": "uuid", - "refTable": "Manager" - }, - "min": 0, - "max": "unlimited" - } - }, - "next_cfg": { - "type": "integer" - }, - "other_config": { - "type": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - }, - "min": 0, - "max": "unlimited" - } - }, - "ovs_version": { - "type": { - "key": { - "type": "string" - }, - "min": 0, - "max": 1 - } - }, - "ssl": { - "type": { - "key": { - "type": "uuid", - "refTable": "SSL" - }, - "min": 0, - "max": 1 - } - }, - "statistics": { - "type": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - }, - "min": 0, - "max": "unlimited" - }, - "ephemeral": true - }, - "system_type": { - "type": { - "key": { - "type": "string" - }, - "min": 0, - "max": 1 - } - }, - "system_version": { - "type": { - "key": { - "type": "string" - }, - "min": 0, - "max": 1 - } - } - } - } - } - }` - func testOvsSet(t *testing.T, set interface{}) ovsdb.OvsSet { oSet, err := ovsdb.NewOvsSet(set) assert.Nil(t, err) @@ -569,17 +113,13 @@ func newOvsRow(bridges ...string) string { } func BenchmarkUpdate1(b *testing.B) { - ovs, err := newOVSDBClient(defDB()) + clientDbModel, err := FullDatabaseModel() require.NoError(b, err) - var s ovsdb.DatabaseSchema - err = json.Unmarshal([]byte(schema), &s) + ovs, err := newOVSDBClient(clientDbModel) require.NoError(b, err) - clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Bridge": &Bridge{}, - "Open_vSwitch": &OpenvSwitch{}, - }) + s, err := Schema() require.NoError(b, err) - dbModel, errs := model.NewDatabaseModel(s, clientDBModel) + dbModel, errs := model.NewDatabaseModel(s, clientDbModel) require.Empty(b, errs) ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, nil, nil) require.NoError(b, err) @@ -595,10 +135,11 @@ func BenchmarkUpdate1(b *testing.B) { } func BenchmarkUpdate2(b *testing.B) { - ovs, err := newOVSDBClient(defDB()) + clientDbModel, err := FullDatabaseModel() + require.NoError(b, err) + ovs, err := newOVSDBClient(clientDbModel) require.NoError(b, err) - var s ovsdb.DatabaseSchema - err = json.Unmarshal([]byte(schema), &s) + s, err := Schema() require.NoError(b, err) clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, @@ -622,10 +163,11 @@ func BenchmarkUpdate2(b *testing.B) { } func BenchmarkUpdate3(b *testing.B) { - ovs, err := newOVSDBClient(defDB()) + clientDbModel, err := FullDatabaseModel() require.NoError(b, err) - var s ovsdb.DatabaseSchema - err = json.Unmarshal([]byte(schema), &s) + ovs, err := newOVSDBClient(clientDbModel) + require.NoError(b, err) + s, err := Schema() require.NoError(b, err) clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, @@ -650,10 +192,11 @@ func BenchmarkUpdate3(b *testing.B) { } func BenchmarkUpdate5(b *testing.B) { - ovs, err := newOVSDBClient(defDB()) + clientDbModel, err := FullDatabaseModel() + require.NoError(b, err) + ovs, err := newOVSDBClient(clientDbModel) require.NoError(b, err) - var s ovsdb.DatabaseSchema - err = json.Unmarshal([]byte(schema), &s) + s, err := Schema() require.NoError(b, err) clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, @@ -680,10 +223,11 @@ func BenchmarkUpdate5(b *testing.B) { } func BenchmarkUpdate8(b *testing.B) { - ovs, err := newOVSDBClient(defDB()) + clientDbModel, err := FullDatabaseModel() require.NoError(b, err) - var s ovsdb.DatabaseSchema - err = json.Unmarshal([]byte(schema), &s) + ovs, err := newOVSDBClient(clientDbModel) + require.NoError(b, err) + s, err := Schema() require.NoError(b, err) clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, @@ -715,7 +259,9 @@ func BenchmarkUpdate8(b *testing.B) { func TestEcho(t *testing.T) { req := []interface{}{"hi"} var reply []interface{} - ovs, err := newOVSDBClient(defDB()) + clientDbModel, err := FullDatabaseModel() + require.NoError(t, err) + ovs, err := newOVSDBClient(clientDbModel) require.NoError(t, err) err = ovs.echo(req, &reply) if err != nil { @@ -727,10 +273,11 @@ func TestEcho(t *testing.T) { } func TestUpdate(t *testing.T) { - ovs, err := newOVSDBClient(defDB()) + clientDbModel, err := FullDatabaseModel() require.NoError(t, err) - var s ovsdb.DatabaseSchema - err = json.Unmarshal([]byte(schema), &s) + ovs, err := newOVSDBClient(clientDbModel) + require.NoError(t, err) + s, err := Schema() require.NoError(t, err) clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, @@ -758,10 +305,11 @@ func TestUpdate(t *testing.T) { } func TestOperationWhenNeverConnected(t *testing.T) { - ovs, err := newOVSDBClient(defDB()) + clientDbModel, err := FullDatabaseModel() + require.NoError(t, err) + ovs, err := newOVSDBClient(clientDbModel) require.NoError(t, err) - var s ovsdb.DatabaseSchema - err = json.Unmarshal([]byte(schema), &s) + s, err := Schema() require.NoError(t, err) tests := []struct { @@ -805,10 +353,11 @@ func TestOperationWhenNeverConnected(t *testing.T) { } func TestOperationWhenNotConnected(t *testing.T) { - ovs, err := newOVSDBClient(defDB()) + clientDbModel, err := FullDatabaseModel() require.NoError(t, err) - var s ovsdb.DatabaseSchema - err = json.Unmarshal([]byte(schema), &s) + ovs, err := newOVSDBClient(clientDbModel) + require.NoError(t, err) + s, err := Schema() require.NoError(t, err) var errs []error fullModel, errs := model.NewDatabaseModel(s, ovs.primaryDB().model.Client()) @@ -856,7 +405,9 @@ func TestOperationWhenNotConnected(t *testing.T) { } func TestSetOption(t *testing.T) { - o, err := newOVSDBClient(defDB()) + clientDbModel, err := FullDatabaseModel() + require.NoError(t, err) + o, err := newOVSDBClient(clientDbModel) require.NoError(t, err) o.options, err = newOptions() @@ -908,15 +459,16 @@ func newOVSDBServer(t testing.TB, dbModel model.ClientDBModel, schema ovsdb.Data } func newClientServerPair(t *testing.T, connectCounter *int32, isLeader bool) (Client, *serverdb.Database, string) { - var defSchema ovsdb.DatabaseSchema - err := json.Unmarshal([]byte(schema), &defSchema) + defSchema, err := Schema() require.NoError(t, err) serverDBModel, err := serverdb.FullDatabaseModel() require.NoError(t, err) // Create server - s, sock := newOVSDBServer(t, defDB(), defSchema) + clientDBModel, err := FullDatabaseModel() + require.NoError(t, err) + s, sock := newOVSDBServer(t, clientDBModel, defSchema) s.OnConnect(func(_ *rpc2.Client) { atomic.AddInt32(connectCounter, 1) }) @@ -933,7 +485,7 @@ func newClientServerPair(t *testing.T, connectCounter *int32, isLeader bool) (Cl sid := fmt.Sprintf("%04x", rand.Uint32()) row := &serverdb.Database{ UUID: uuid.NewString(), - Name: defDB().Name(), + Name: clientDBModel.Name(), Connected: true, Leader: isLeader, Model: serverdb.DatabaseModelClustered, @@ -968,7 +520,9 @@ func TestClientReconnectLeaderOnly(t *testing.T) { cli2, row2, endpoint2 := newClientServerPair(t, &connected2, false) // Create client to test reconnection for - ovs, err := newOVSDBClient(defDB(), + clientDBModel, err := FullDatabaseModel() + require.NoError(t, err) + ovs, err := newOVSDBClient(clientDBModel, WithLeaderOnly(true), WithReconnect(5*time.Second, &backoff.ZeroBackOff{}), WithEndpoint(endpoint1), @@ -1021,17 +575,19 @@ func TestClientReconnectLeaderOnly(t *testing.T) { } func TestClientValidateTransaction(t *testing.T) { - var defSchema ovsdb.DatabaseSchema - err := json.Unmarshal([]byte(schema), &defSchema) + defSchema, err := Schema() require.NoError(t, err) // Create server with default model - _, sock := newOVSDBServer(t, defDB(), defSchema) + serverDBModel, err := FullDatabaseModel() + require.NoError(t, err) + _, sock := newOVSDBServer(t, serverDBModel, defSchema) // Create a client with primary and secondary indexes // and transaction validation - dbModel := defDB() - dbModel.SetIndexes( + clientDBModel, err := FullDatabaseModel() + require.NoError(t, err) + clientDBModel.SetIndexes( map[string][]model.ClientIndex{ "Bridge": { model.ClientIndex{ @@ -1058,7 +614,7 @@ func TestClientValidateTransaction(t *testing.T) { ) endpoint := fmt.Sprintf("unix:%s", sock) - cli, err := newOVSDBClient(dbModel, WithEndpoint(endpoint), WithTransactionValidation(true)) + cli, err := newOVSDBClient(clientDBModel, WithEndpoint(endpoint), WithTransactionValidation(true)) require.NoError(t, err) err = cli.Connect(context.Background()) require.NoError(t, err) @@ -1190,12 +746,14 @@ func TestClientValidateTransaction(t *testing.T) { } func BenchmarkClientValidateTransaction(b *testing.B) { - var defSchema ovsdb.DatabaseSchema - err := json.Unmarshal([]byte(schema), &defSchema) + defSchema, err := Schema() + require.NoError(b, err) + + dbModel, err := FullDatabaseModel() require.NoError(b, err) // Create server with default model - _, sock := newOVSDBServer(b, defDB(), defSchema) + _, sock := newOVSDBServer(b, dbModel, defSchema) verbosity := stdr.SetVerbosity(0) b.Cleanup(func() { stdr.SetVerbosity(verbosity) @@ -1203,7 +761,6 @@ func BenchmarkClientValidateTransaction(b *testing.B) { // Create a client with transaction validation getClient := func(opts ...Option) *ovsdbClient { - dbModel := defDB() endpoint := fmt.Sprintf("unix:%s", sock) l := logr.Discard() cli, err := newOVSDBClient(dbModel, append(opts, WithEndpoint(endpoint), WithLogger(&l))...) diff --git a/client/monitor_test.go b/client/monitor_test.go index 7039ca38..49eb3dd6 100644 --- a/client/monitor_test.go +++ b/client/monitor_test.go @@ -1,16 +1,18 @@ package client import ( - "encoding/json" "testing" "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" "github.com/stretchr/testify/assert" + + . "github.com/ovn-org/libovsdb/test" ) func TestWithTable(t *testing.T) { - client, err := newOVSDBClient(defDB()) + dbModel, err := FullDatabaseModel() + assert.NoError(t, err) + client, err := newOVSDBClient(dbModel) assert.NoError(t, err) m := newMonitor() opt := WithTable(&OpenvSwitch{}) @@ -22,8 +24,7 @@ func TestWithTable(t *testing.T) { } func populateClientModel(t *testing.T, client *ovsdbClient) { - var s ovsdb.DatabaseSchema - err := json.Unmarshal([]byte(schema), &s) + s, err := Schema() assert.NoError(t, err) clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, @@ -37,7 +38,9 @@ func populateClientModel(t *testing.T, client *ovsdbClient) { } func TestWithTableAndFields(t *testing.T) { - client, err := newOVSDBClient(defDB()) + dbModel, err := FullDatabaseModel() + assert.NoError(t, err) + client, err := newOVSDBClient(dbModel) assert.NoError(t, err) populateClientModel(t, client) diff --git a/database/transaction_test.go b/database/transaction_test.go index 6a8f28cf..293c82ee 100644 --- a/database/transaction_test.go +++ b/database/transaction_test.go @@ -16,13 +16,11 @@ import ( ) func TestWaitOpEquals(t *testing.T) { - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + defDB, err := FullDatabaseModel() if err != nil { t.Fatal(err) } - schema, err := GetSchema() + schema, err := Schema() if err != nil { t.Fatal(err) } @@ -37,15 +35,15 @@ func TestWaitOpEquals(t *testing.T) { m := mapper.NewMapper(schema) - ovs := OvsType{} + ovs := OpenvSwitch{} info, err := dbModel.NewModelInfo(&ovs) require.NoError(t, err) ovsRow, err := m.NewRow(info) require.Nil(t, err) - bridge := BridgeType{ + bridge := Bridge{ Name: "foo", - ExternalIds: map[string]string{ + ExternalIDs: map[string]string{ "foo": "bar", "baz": "quux", "waldo": "fred", @@ -141,13 +139,11 @@ func TestWaitOpEquals(t *testing.T) { } func TestWaitOpNotEquals(t *testing.T) { - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + defDB, err := FullDatabaseModel() if err != nil { t.Fatal(err) } - schema, err := GetSchema() + schema, err := Schema() if err != nil { t.Fatal(err) } @@ -162,15 +158,15 @@ func TestWaitOpNotEquals(t *testing.T) { m := mapper.NewMapper(schema) - ovs := OvsType{} + ovs := OpenvSwitch{} info, err := dbModel.NewModelInfo(&ovs) require.NoError(t, err) ovsRow, err := m.NewRow(info) require.Nil(t, err) - bridge := BridgeType{ + bridge := Bridge{ Name: "foo", - ExternalIds: map[string]string{ + ExternalIDs: map[string]string{ "foo": "bar", "baz": "quux", "waldo": "fred", @@ -259,13 +255,11 @@ func TestWaitOpNotEquals(t *testing.T) { } func TestMutateOp(t *testing.T) { - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + defDB, err := FullDatabaseModel() if err != nil { t.Fatal(err) } - schema, err := GetSchema() + schema, err := Schema() if err != nil { t.Fatal(err) } @@ -280,15 +274,15 @@ func TestMutateOp(t *testing.T) { m := mapper.NewMapper(schema) - ovs := OvsType{} + ovs := OpenvSwitch{} info, err := dbModel.NewModelInfo(&ovs) require.NoError(t, err) ovsRow, err := m.NewRow(info) require.Nil(t, err) - bridge := BridgeType{ + bridge := Bridge{ Name: "foo", - ExternalIds: map[string]string{ + ExternalIDs: map[string]string{ "foo": "bar", "baz": "quux", "waldo": "fred", @@ -363,7 +357,7 @@ func TestMutateOp(t *testing.T) { ) assert.Equal(t, ovsdb.OperationResult{Count: 1}, gotResult) - oldExternalIds, _ := ovsdb.NewOvsMap(bridge.ExternalIds) + oldExternalIds, _ := ovsdb.NewOvsMap(bridge.ExternalIDs) newExternalIds, _ := ovsdb.NewOvsMap(map[string]string{"waldo": "fred"}) diffExternalIds, _ := ovsdb.NewOvsMap(map[string]string{"foo": "bar", "baz": "quux"}) @@ -456,13 +450,11 @@ func TestDiff(t *testing.T) { func TestOvsdbServerInsert(t *testing.T) { t.Skip("need a helper for comparing rows as map elements aren't in same order") - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + defDB, err := FullDatabaseModel() if err != nil { t.Fatal(err) } - schema, err := GetSchema() + schema, err := Schema() if err != nil { t.Fatal(err) } @@ -474,11 +466,11 @@ func TestOvsdbServerInsert(t *testing.T) { m := mapper.NewMapper(schema) gromit := "gromit" - bridge := BridgeType{ + bridge := Bridge{ Name: "foo", DatapathType: "bar", DatapathID: &gromit, - ExternalIds: map[string]string{ + ExternalIDs: map[string]string{ "foo": "bar", "baz": "qux", "waldo": "fred", @@ -514,13 +506,11 @@ func TestOvsdbServerInsert(t *testing.T) { } func TestOvsdbServerUpdate(t *testing.T) { - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + defDB, err := FullDatabaseModel() if err != nil { t.Fatal(err) } - schema, err := GetSchema() + schema, err := Schema() if err != nil { t.Fatal(err) } @@ -531,9 +521,9 @@ func TestOvsdbServerUpdate(t *testing.T) { require.Empty(t, errs) m := mapper.NewMapper(schema) - bridge := BridgeType{ + bridge := Bridge{ Name: "foo", - ExternalIds: map[string]string{ + ExternalIDs: map[string]string{ "foo": "bar", "baz": "qux", "waldo": "fred", @@ -592,7 +582,7 @@ func TestOvsdbServerUpdate(t *testing.T) { bridge.UUID = bridgeUUID row, err := db.Get("Open_vSwitch", "Bridge", bridgeUUID) assert.NoError(t, err) - br := row.(*BridgeType) + br := row.(*Bridge) assert.NotEqual(t, br, bridgeRow) assert.Equal(t, tt.expected.Modify, updates["Bridge"][bridgeUUID].Modify) }) @@ -600,13 +590,11 @@ func TestOvsdbServerUpdate(t *testing.T) { } func TestMultipleOps(t *testing.T) { - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + defDB, err := FullDatabaseModel() if err != nil { t.Fatal(err) } - schema, err := GetSchema() + schema, err := Schema() if err != nil { t.Fatal(err) } @@ -618,7 +606,7 @@ func TestMultipleOps(t *testing.T) { m := mapper.NewMapper(schema) bridgeUUID := uuid.NewString() - bridge := BridgeType{ + bridge := Bridge{ Name: "a_bridge_to_nowhere", Ports: []string{ "port1", @@ -711,9 +699,7 @@ func TestMultipleOps(t *testing.T) { } func TestCheckIndexes(t *testing.T) { - clientDbModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + clientDbModel, err := FullDatabaseModel() if err != nil { t.Fatal(err) } @@ -741,7 +727,7 @@ func TestCheckIndexes(t *testing.T) { }, }, ) - schema, err := GetSchema() + schema, err := Schema() if err != nil { t.Fatal(err) } @@ -753,9 +739,9 @@ func TestCheckIndexes(t *testing.T) { m := mapper.NewMapper(schema) bridgeUUID := uuid.NewString() - bridge := BridgeType{ + bridge := Bridge{ Name: "a_bridge_to_nowhere", - ExternalIds: map[string]string{ + ExternalIDs: map[string]string{ "primary_key": "primary_key_1", "secondary_key": "secondary_key_1", }, diff --git a/server/server_integration_test.go b/server/server_integration_test.go index ca7289ef..880316f5 100644 --- a/server/server_integration_test.go +++ b/server/server_integration_test.go @@ -22,12 +22,10 @@ import ( ) func TestClientServerEcho(t *testing.T) { - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + defDB, err := FullDatabaseModel() require.Nil(t, err) - schema, err := GetSchema() + schema, err := Schema() require.Nil(t, err) ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) @@ -59,12 +57,10 @@ func TestClientServerEcho(t *testing.T) { } func TestClientServerInsert(t *testing.T) { - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + defDB, err := FullDatabaseModel() require.Nil(t, err) - schema, err := GetSchema() + schema, err := Schema() require.Nil(t, err) ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) @@ -94,11 +90,11 @@ func TestClientServerInsert(t *testing.T) { require.NoError(t, err) wallace := "wallace" - bridgeRow := &BridgeType{ + bridgeRow := &Bridge{ Name: "foo", DatapathType: "bar", DatapathID: &wallace, - ExternalIds: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, + ExternalIDs: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, } ops, err := ovs.Create(bridgeRow) @@ -110,30 +106,28 @@ func TestClientServerInsert(t *testing.T) { uuid := reply[0].UUID.GoUUID require.Eventually(t, func() bool { - br := &BridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err := ovs.Get(context.Background(), br) return err == nil }, 2*time.Second, 500*time.Millisecond) - br := &BridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err = ovs.Get(context.Background(), br) require.NoError(t, err) assert.Equal(t, bridgeRow.Name, br.Name) - assert.Equal(t, bridgeRow.ExternalIds, br.ExternalIds) + assert.Equal(t, bridgeRow.ExternalIDs, br.ExternalIDs) assert.Equal(t, bridgeRow.DatapathType, br.DatapathType) assert.Equal(t, *bridgeRow.DatapathID, wallace) } func TestClientServerMonitor(t *testing.T) { - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + defDB, err := FullDatabaseModel() if err != nil { t.Fatal(err) } - schema, err := GetSchema() + schema, err := Schema() if err != nil { t.Fatal(err) } @@ -162,13 +156,13 @@ func TestClientServerMonitor(t *testing.T) { err = ovs.Connect(context.Background()) require.NoError(t, err) - ovsRow := &OvsType{ + ovsRow := &OpenvSwitch{ UUID: "ovs", } - bridgeRow := &BridgeType{ + bridgeRow := &Bridge{ UUID: "foo", Name: "foo", - ExternalIds: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, + ExternalIDs: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, } seenMutex := sync.RWMutex{} @@ -178,9 +172,9 @@ func TestClientServerMonitor(t *testing.T) { ovs.Cache().AddEventHandler(&cache.EventHandlerFuncs{ AddFunc: func(table string, model model.Model) { if table == "Bridge" { - br := model.(*BridgeType) + br := model.(*Bridge) assert.Equal(t, bridgeRow.Name, br.Name) - assert.Equal(t, bridgeRow.ExternalIds, br.ExternalIds) + assert.Equal(t, bridgeRow.ExternalIDs, br.ExternalIDs) seenMutex.Lock() seenInsert = true seenMutex.Unlock() @@ -193,7 +187,7 @@ func TestClientServerMonitor(t *testing.T) { }, UpdateFunc: func(table string, old, new model.Model) { if table == "Open_vSwitch" { - ov := new.(*OvsType) + ov := new.(*OpenvSwitch) assert.Equal(t, 1, len(ov.Bridges)) seenMutex.Lock() seenMutation = true @@ -252,12 +246,10 @@ func TestClientServerMonitor(t *testing.T) { } func TestClientServerInsertAndDelete(t *testing.T) { - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + defDB, err := FullDatabaseModel() require.Nil(t, err) - schema, err := GetSchema() + schema, err := Schema() require.Nil(t, err) ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) @@ -286,9 +278,9 @@ func TestClientServerInsertAndDelete(t *testing.T) { _, err = ovs.MonitorAll(context.Background()) require.NoError(t, err) - bridgeRow := &BridgeType{ + bridgeRow := &Bridge{ Name: "foo", - ExternalIds: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, + ExternalIDs: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, } ops, err := ovs.Create(bridgeRow) @@ -300,7 +292,7 @@ func TestClientServerInsertAndDelete(t *testing.T) { uuid := reply[0].UUID.GoUUID assert.Eventually(t, func() bool { - br := &BridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err := ovs.Get(context.Background(), br) return err == nil }, 2*time.Second, 500*time.Millisecond) @@ -317,13 +309,10 @@ func TestClientServerInsertAndDelete(t *testing.T) { } func TestClientServerInsertDuplicate(t *testing.T) { - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}, - }) + defDB, err := FullDatabaseModel() require.Nil(t, err) - schema, err := GetSchema() + schema, err := Schema() require.Nil(t, err) ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) @@ -350,9 +339,9 @@ func TestClientServerInsertDuplicate(t *testing.T) { err = ovs.Connect(context.Background()) require.NoError(t, err) - bridgeRow := &BridgeType{ + bridgeRow := &Bridge{ Name: "foo", - ExternalIds: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, + ExternalIDs: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, } ops, err := ovs.Create(bridgeRow) @@ -372,12 +361,10 @@ func TestClientServerInsertDuplicate(t *testing.T) { } func TestClientServerInsertAndUpdate(t *testing.T) { - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + defDB, err := FullDatabaseModel() require.Nil(t, err) - schema, err := GetSchema() + schema, err := Schema() require.Nil(t, err) ovsDB := database.NewInMemoryDatabase(map[string]model.ClientDBModel{"Open_vSwitch": defDB}) @@ -408,9 +395,9 @@ func TestClientServerInsertAndUpdate(t *testing.T) { _, err = ovs.MonitorAll(context.Background()) require.NoError(t, err) - bridgeRow := &BridgeType{ + bridgeRow := &Bridge{ Name: "br-update", - ExternalIds: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, + ExternalIDs: map[string]string{"go": "awesome", "docker": "made-for-each-other"}, } ops, err := ovs.Create(bridgeRow) @@ -422,7 +409,7 @@ func TestClientServerInsertAndUpdate(t *testing.T) { uuid := reply[0].UUID.GoUUID assert.Eventually(t, func() bool { - br := &BridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err := ovs.Get(context.Background(), br) return err == nil }, 2*time.Second, 500*time.Millisecond) @@ -437,7 +424,7 @@ func TestClientServerInsertAndUpdate(t *testing.T) { // update many fields bridgeRow.UUID = uuid bridgeRow.Name = "br-update" - bridgeRow.ExternalIds["baz"] = "foobar" + bridgeRow.ExternalIDs["baz"] = "foobar" bridgeRow.OtherConfig = map[string]string{"foo": "bar"} ops, err = ovs.Where(bridgeRow).Update(bridgeRow) require.NoError(t, err) @@ -447,7 +434,7 @@ func TestClientServerInsertAndUpdate(t *testing.T) { require.NoErrorf(t, err, "%+v", opErrs) require.Eventually(t, func() bool { - br := &BridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err = ovs.Get(context.Background(), br) if err != nil { return false @@ -456,8 +443,8 @@ func TestClientServerInsertAndUpdate(t *testing.T) { }, 2*time.Second, 50*time.Millisecond) newExternalIds := map[string]string{"foo": "bar"} - bridgeRow.ExternalIds = newExternalIds - ops, err = ovs.Where(bridgeRow).Update(bridgeRow, &bridgeRow.ExternalIds) + bridgeRow.ExternalIDs = newExternalIds + ops, err = ovs.Where(bridgeRow).Update(bridgeRow, &bridgeRow.ExternalIDs) require.NoError(t, err) reply, err = ovs.Transact(context.Background(), ops...) require.NoError(t, err) @@ -465,15 +452,15 @@ func TestClientServerInsertAndUpdate(t *testing.T) { require.NoErrorf(t, err, "%+v", opErr) assert.Eventually(t, func() bool { - br := &BridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err = ovs.Get(context.Background(), br) if err != nil { return false } - return reflect.DeepEqual(br.ExternalIds, bridgeRow.ExternalIds) + return reflect.DeepEqual(br.ExternalIDs, bridgeRow.ExternalIDs) }, 2*time.Second, 500*time.Millisecond) - br := &BridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err = ovs.Get(context.Background(), br) assert.NoError(t, err) diff --git a/server/server_test.go b/server/server_test.go index 900f57cf..33c581a6 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -69,13 +69,11 @@ func TestExpandNamedUUID(t *testing.T) { } func TestOvsdbServerMonitor(t *testing.T) { - defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &OvsType{}, - "Bridge": &BridgeType{}}) + defDB, err := FullDatabaseModel() if err != nil { t.Fatal(err) } - schema, err := GetSchema() + schema, err := Schema() if err != nil { t.Fatal(err) } diff --git a/test/ovs/ovs_integration_test.go b/test/ovs/ovs_integration_test.go index 6df944c2..3cefe5c1 100644 --- a/test/ovs/ovs_integration_test.go +++ b/test/ovs/ovs_integration_test.go @@ -19,6 +19,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + + . "github.com/ovn-org/libovsdb/test" ) // OVSIntegrationSuite runs tests against a real Open vSwitch instance @@ -76,8 +78,12 @@ func (suite *OVSIntegrationSuite) SetupTest() { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() endpoint := "tcp::56640" + dbModel, err := FullDatabaseModel() + if err != nil { + return err + } ovs, err := client.NewOVSDBClient( - defDB, + dbModel, client.WithEndpoint(endpoint), client.WithLeaderOnly(true), ) @@ -98,8 +104,8 @@ func (suite *OVSIntegrationSuite) SetupTest() { _, err = suite.client.Monitor(context.TODO(), suite.client.NewMonitor( - client.WithTable(&ovsType{}), - client.WithTable(&bridgeType{}), + client.WithTable(&OpenvSwitch{}), + client.WithTable(&Bridge{}), ), ) require.NoError(suite.T(), err) @@ -121,76 +127,16 @@ func TestOVSIntegrationTestSuite(t *testing.T) { suite.Run(t, new(OVSIntegrationSuite)) } -type BridgeFailMode = string - -var ( - BridgeFailModeStandalone BridgeFailMode = "standalone" - BridgeFailModeSecure BridgeFailMode = "secure" -) - -// bridgeType is the simplified ORM model of the Bridge table -type bridgeType struct { - UUID string `ovsdb:"_uuid"` - Name string `ovsdb:"name"` - OtherConfig map[string]string `ovsdb:"other_config"` - ExternalIds map[string]string `ovsdb:"external_ids"` - Ports []string `ovsdb:"ports"` - Status map[string]string `ovsdb:"status"` - BridgeFailMode *BridgeFailMode `ovsdb:"fail_mode"` - IPFIX *string `ovsdb:"ipfix"` -} - -// ovsType is the ORM model of the OVS table -type ovsType struct { - UUID string `ovsdb:"_uuid"` - Bridges []string `ovsdb:"bridges"` - CurCfg int `ovsdb:"cur_cfg"` - DatapathTypes []string `ovsdb:"datapath_types"` - Datapaths map[string]string `ovsdb:"datapaths"` - DbVersion *string `ovsdb:"db_version"` - DpdkInitialized bool `ovsdb:"dpdk_initialized"` - DpdkVersion *string `ovsdb:"dpdk_version"` - ExternalIDs map[string]string `ovsdb:"external_ids"` - IfaceTypes []string `ovsdb:"iface_types"` - ManagerOptions []string `ovsdb:"manager_options"` - NextCfg int `ovsdb:"next_cfg"` - OtherConfig map[string]string `ovsdb:"other_config"` - OVSVersion *string `ovsdb:"ovs_version"` - SSL *string `ovsdb:"ssl"` - Statistics map[string]string `ovsdb:"statistics"` - SystemType *string `ovsdb:"system_type"` - SystemVersion *string `ovsdb:"system_version"` -} - -// ipfixType is a simplified ORM model for the IPFIX table -type ipfixType struct { - UUID string `ovsdb:"_uuid"` - Targets []string `ovsdb:"targets"` -} - -// queueType is the simplified ORM model of the Queue table -type queueType struct { - UUID string `ovsdb:"_uuid"` - DSCP *int `ovsdb:"dscp"` -} - -var defDB, _ = model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ - "Open_vSwitch": &ovsType{}, - "Bridge": &bridgeType{}, - "IPFIX": &ipfixType{}, - "Queue": &queueType{}, -}) - func (suite *OVSIntegrationSuite) TestConnectReconnect() { assert.True(suite.T(), suite.client.Connected()) err := suite.client.Echo(context.TODO()) require.NoError(suite.T(), err) bridgeName := "br-discoreco" - brChan := make(chan *bridgeType) + brChan := make(chan *Bridge) suite.client.Cache().AddEventHandler(&cache.EventHandlerFuncs{ AddFunc: func(table string, model model.Model) { - br, ok := model.(*bridgeType) + br, ok := model.(*Bridge) if !ok { return } @@ -241,7 +187,7 @@ func (suite *OVSIntegrationSuite) TestConnectReconnect() { err = suite.client.Connect(ctx) require.NoError(suite.T(), err) - br := &bridgeType{ + br := &Bridge{ UUID: bridgeUUID, } @@ -254,8 +200,8 @@ func (suite *OVSIntegrationSuite) TestConnectReconnect() { _, err = suite.client.Monitor(context.TODO(), suite.client.NewMonitor( - client.WithTable(&ovsType{}), - client.WithTable(&bridgeType{}), + client.WithTable(&OpenvSwitch{}), + client.WithTable(&Bridge{}), ), ) require.NoError(suite.T(), err) @@ -308,10 +254,10 @@ func (suite *OVSIntegrationSuite) TestWithReconnect() { // add a bridge and verify our handler gets called bridgeName := "recon-b4" - brChan := make(chan *bridgeType) + brChan := make(chan *Bridge) suite.client.Cache().AddEventHandler(&cache.EventHandlerFuncs{ AddFunc: func(table string, model model.Model) { - br, ok := model.(*bridgeType) + br, ok := model.(*Bridge) if !ok { return } @@ -403,7 +349,7 @@ func (suite *OVSIntegrationSuite) TestInsertTransactIntegration() { uuid, err := suite.createBridge(bridgeName) require.NoError(suite.T(), err) require.Eventually(suite.T(), func() bool { - br := &bridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err := suite.client.Get(context.Background(), br) return err == nil }, 2*time.Second, 500*time.Millisecond) @@ -414,18 +360,18 @@ func (suite *OVSIntegrationSuite) TestMultipleOpsTransactIntegration() { uuid, err := suite.createBridge(bridgeName) require.NoError(suite.T(), err) require.Eventually(suite.T(), func() bool { - br := &bridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err := suite.client.Get(context.Background(), br) return err == nil }, 2*time.Second, 500*time.Millisecond) var operations []ovsdb.Operation - ovsRow := bridgeType{} - br := &bridgeType{UUID: uuid} + ovsRow := Bridge{} + br := &Bridge{UUID: uuid} op1, err := suite.client.Where(br). Mutate(&ovsRow, model.Mutation{ - Field: &ovsRow.ExternalIds, + Field: &ovsRow.ExternalIDs, Mutator: ovsdb.MutateOperationInsert, Value: map[string]string{"one": "1"}, }) @@ -434,17 +380,17 @@ func (suite *OVSIntegrationSuite) TestMultipleOpsTransactIntegration() { op2Mutations := []model.Mutation{ { - Field: &ovsRow.ExternalIds, + Field: &ovsRow.ExternalIDs, Mutator: ovsdb.MutateOperationInsert, Value: map[string]string{"two": "2", "three": "3"}, }, { - Field: &ovsRow.ExternalIds, + Field: &ovsRow.ExternalIDs, Mutator: ovsdb.MutateOperationDelete, Value: []string{"docker"}, }, { - Field: &ovsRow.ExternalIds, + Field: &ovsRow.ExternalIDs, Mutator: ovsdb.MutateOperationInsert, Value: map[string]string{"podman": "made-for-each-other"}, }, @@ -471,7 +417,7 @@ func (suite *OVSIntegrationSuite) TestMultipleOpsTransactIntegration() { "two": "2", "three": "3", } - require.Exactly(suite.T(), expectedExternalIds, br.ExternalIds) + require.Exactly(suite.T(), expectedExternalIds, br.ExternalIDs) } func (suite *OVSIntegrationSuite) TestInsertAndDeleteTransactIntegration() { @@ -480,16 +426,16 @@ func (suite *OVSIntegrationSuite) TestInsertAndDeleteTransactIntegration() { require.NoError(suite.T(), err) require.Eventually(suite.T(), func() bool { - br := &bridgeType{UUID: bridgeUUID} + br := &Bridge{UUID: bridgeUUID} err := suite.client.Get(context.Background(), br) return err == nil }, 2*time.Second, 500*time.Millisecond) - deleteOp, err := suite.client.Where(&bridgeType{Name: bridgeName}).Delete() + deleteOp, err := suite.client.Where(&Bridge{Name: bridgeName}).Delete() require.NoError(suite.T(), err) - ovsRow := ovsType{} - delMutateOp, err := suite.client.WhereCache(func(*ovsType) bool { return true }). + ovsRow := OpenvSwitch{} + delMutateOp, err := suite.client.WhereCache(func(*OpenvSwitch) bool { return true }). Mutate(&ovsRow, model.Mutation{ Field: &ovsRow.Bridges, Mutator: ovsdb.MutateOperationDelete, @@ -511,7 +457,7 @@ func (suite *OVSIntegrationSuite) TestInsertAndDeleteTransactIntegration() { } require.Eventually(suite.T(), func() bool { - br := &bridgeType{UUID: bridgeUUID} + br := &Bridge{UUID: bridgeUUID} err := suite.client.Get(context.Background(), br) return err != nil }, 2*time.Second, 500*time.Millisecond) @@ -566,7 +512,7 @@ func (suite *OVSIntegrationSuite) TestMonitorCancelIntegration() { monitorID, err := suite.client.Monitor( context.TODO(), suite.client.NewMonitor( - client.WithTable(&queueType{}), + client.WithTable(&Queue{}), ), ) require.NoError(suite.T(), err) @@ -574,7 +520,7 @@ func (suite *OVSIntegrationSuite) TestMonitorCancelIntegration() { uuid, err := suite.createQueue("test1", 0) require.NoError(suite.T(), err) require.Eventually(suite.T(), func() bool { - q := &queueType{UUID: uuid} + q := &Queue{UUID: uuid} err = suite.client.Get(context.Background(), q) return err == nil }, 2*time.Second, 500*time.Millisecond) @@ -585,7 +531,7 @@ func (suite *OVSIntegrationSuite) TestMonitorCancelIntegration() { uuid, err = suite.createQueue("test2", 1) require.NoError(suite.T(), err) assert.Never(suite.T(), func() bool { - q := &queueType{UUID: uuid} + q := &Queue{UUID: uuid} err = suite.client.Get(context.Background(), q) return err == nil }, 2*time.Second, 500*time.Millisecond) @@ -596,7 +542,7 @@ func (suite *OVSIntegrationSuite) TestInsertDuplicateTransactIntegration() { require.NoError(suite.T(), err) require.Eventually(suite.T(), func() bool { - br := &bridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err := suite.client.Get(context.Background(), br) return err == nil }, 2*time.Second, 500*time.Millisecond) @@ -611,12 +557,12 @@ func (suite *OVSIntegrationSuite) TestUpdate() { require.NoError(suite.T(), err) require.Eventually(suite.T(), func() bool { - br := &bridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err := suite.client.Get(context.Background(), br) return err == nil }, 2*time.Second, 500*time.Millisecond) - bridgeRow := &bridgeType{UUID: uuid} + bridgeRow := &Bridge{UUID: uuid} err = suite.client.Get(context.Background(), bridgeRow) require.NoError(suite.T(), err) @@ -626,7 +572,7 @@ func (suite *OVSIntegrationSuite) TestUpdate() { require.Error(suite.T(), err) bridgeRow.Name = "br-update" // update many fields - bridgeRow.ExternalIds["baz"] = "foobar" + bridgeRow.ExternalIDs["baz"] = "foobar" bridgeRow.OtherConfig = map[string]string{"foo": "bar"} ops, err := suite.client.Where(bridgeRow).Update(bridgeRow) require.NoError(suite.T(), err) @@ -636,7 +582,7 @@ func (suite *OVSIntegrationSuite) TestUpdate() { require.NoErrorf(suite.T(), err, "%+v", opErrs) require.Eventually(suite.T(), func() bool { - br := &bridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err = suite.client.Get(context.Background(), br) if err != nil { return false @@ -645,8 +591,8 @@ func (suite *OVSIntegrationSuite) TestUpdate() { }, 2*time.Second, 50*time.Millisecond) newExternalIds := map[string]string{"foo": "bar"} - bridgeRow.ExternalIds = newExternalIds - ops, err = suite.client.Where(bridgeRow).Update(bridgeRow, &bridgeRow.ExternalIds) + bridgeRow.ExternalIDs = newExternalIds + ops, err = suite.client.Where(bridgeRow).Update(bridgeRow, &bridgeRow.ExternalIDs) require.NoError(suite.T(), err) reply, err = suite.client.Transact(context.Background(), ops...) require.NoError(suite.T(), err) @@ -654,7 +600,7 @@ func (suite *OVSIntegrationSuite) TestUpdate() { require.NoErrorf(suite.T(), err, "%Populate2+v", opErr) assert.Eventually(suite.T(), func() bool { - br := &bridgeType{UUID: uuid} + br := &Bridge{UUID: uuid} err = suite.client.Get(context.Background(), br) if err != nil { return false @@ -666,22 +612,22 @@ func (suite *OVSIntegrationSuite) TestUpdate() { func (suite *OVSIntegrationSuite) createBridge(bridgeName string) (string, error) { // NamedUUID is used to add multiple related Operations in a single Transact operation namedUUID := "gopher" - br := bridgeType{ + br := Bridge{ UUID: namedUUID, Name: bridgeName, - ExternalIds: map[string]string{ + ExternalIDs: map[string]string{ "go": "awesome", "docker": "made-for-each-other", }, - BridgeFailMode: &BridgeFailModeSecure, + FailMode: &BridgeFailModeSecure, } insertOp, err := suite.client.Create(&br) require.NoError(suite.T(), err) // Inserting a Bridge row in Bridge table requires mutating the open_vswitch table. - ovsRow := ovsType{} - mutateOp, err := suite.client.WhereCache(func(*ovsType) bool { return true }). + ovsRow := OpenvSwitch{} + mutateOp, err := suite.client.WhereCache(func(*OpenvSwitch) bool { return true }). Mutate(&ovsRow, model.Mutation{ Field: &ovsRow.Bridges, Mutator: ovsdb.MutateOperationInsert, @@ -702,14 +648,14 @@ func (suite *OVSIntegrationSuite) TestCreateIPFIX() { uuid, err := suite.createBridge("br-ipfix") require.NoError(suite.T(), err) namedUUID := "gopher" - ipfix := ipfixType{ + ipfix := IPFIX{ UUID: namedUUID, Targets: []string{"127.0.0.1:6650"}, } insertOp, err := suite.client.Create(&ipfix) require.NoError(suite.T(), err) - bridge := bridgeType{ + bridge := Bridge{ UUID: uuid, IPFIX: &namedUUID, } @@ -740,7 +686,7 @@ func (suite *OVSIntegrationSuite) TestCreateIPFIX() { require.NoError(suite.T(), err) //Assert the IPFIX table is empty - ipfixes := []ipfixType{} + ipfixes := []IPFIX{} err = suite.client.List(context.Background(), &ipfixes) require.NoError(suite.T(), err) require.Empty(suite.T(), ipfixes) @@ -752,7 +698,7 @@ func (suite *OVSIntegrationSuite) TestWait() { brName := "br-wait-for-it" // Use Wait to ensure bridge does not exist yet - bridgeRow := &bridgeType{ + bridgeRow := &Bridge{ Name: brName, } conditions := []model.Condition{ @@ -776,20 +722,20 @@ func (suite *OVSIntegrationSuite) TestWait() { require.NoError(suite.T(), err) // Use wait to verify bridge's existence - bridgeRow = &bridgeType{ - Name: brName, - BridgeFailMode: &BridgeFailModeSecure, + bridgeRow = &Bridge{ + Name: brName, + FailMode: &BridgeFailModeSecure, } conditions = []model.Condition{ { - Field: &bridgeRow.BridgeFailMode, + Field: &bridgeRow.FailMode, Function: ovsdb.ConditionEqual, Value: &BridgeFailModeSecure, }, } timeout = 2 * 1000 // 2 seconds (in milliseconds) ops, err = suite.client.Where(bridgeRow, conditions...).Wait( - ovsdb.WaitConditionEqual, &timeout, bridgeRow, &bridgeRow.BridgeFailMode) + ovsdb.WaitConditionEqual, &timeout, bridgeRow, &bridgeRow.FailMode) require.NoError(suite.T(), err) reply, err = suite.client.Transact(context.Background(), ops...) require.NoError(suite.T(), err) @@ -799,7 +745,7 @@ func (suite *OVSIntegrationSuite) TestWait() { // Use wait to get a txn error due to until condition that is not happening timeout = 222 // milliseconds ops, err = suite.client.Where(bridgeRow, conditions...).Wait( - ovsdb.WaitConditionNotEqual, &timeout, bridgeRow, &bridgeRow.BridgeFailMode) + ovsdb.WaitConditionNotEqual, &timeout, bridgeRow, &bridgeRow.FailMode) require.NoError(suite.T(), err) reply, err = suite.client.Transact(context.Background(), ops...) require.NoError(suite.T(), err) @@ -808,7 +754,7 @@ func (suite *OVSIntegrationSuite) TestWait() { } func (suite *OVSIntegrationSuite) createQueue(queueName string, dscp int) (string, error) { - q := queueType{ + q := Queue{ DSCP: &dscp, } @@ -823,7 +769,7 @@ func (suite *OVSIntegrationSuite) createQueue(queueName string, dscp int) (strin func (suite *OVSIntegrationSuite) TestOpsWaitForReconnect() { namedUUID := "trozet" - ipfix := ipfixType{ + ipfix := IPFIX{ UUID: namedUUID, Targets: []string{"127.0.0.1:6650"}, } diff --git a/test/test_data.go b/test/test_data.go deleted file mode 100644 index ecd77743..00000000 --- a/test/test_data.go +++ /dev/null @@ -1,115 +0,0 @@ -package test - -import ( - "encoding/json" - - "github.com/ovn-org/libovsdb/ovsdb" -) - -const schema = ` -{ - "name": "Open_vSwitch", - "version": "0.0.1", - "tables": { - "Open_vSwitch": { - "columns": { - "bridges": { - "type": { - "key": { - "type": "uuid", - "refTable": "Bridge" - }, - "min": 0, - "max": "unlimited" - } - } - }, - "isRoot": true, - "maxRows": 1 - }, - "Bridge": { - "columns": { - "name": { - "type": "string", - "mutable": false - }, - "datapath_type": { - "type": "string" - }, - "datapath_id": { - "type": { - "key": "string", - "min": 0, - "max": 1 - }, - "ephemeral": true - }, - "ports": { - "type": { - "key": { - "type": "uuid", - "refTable": "Port" - }, - "min": 0, - "max": "unlimited" - } - }, - "status": { - "type": { - "key": "string", - "value": "string", - "min": 0, - "max": "unlimited" - }, - "ephemeral": true - }, - "other_config": { - "type": { - "key": "string", - "value": "string", - "min": 0, - "max": "unlimited" - } - }, - "external_ids": { - "type": { - "key": "string", - "value": "string", - "min": 0, - "max": "unlimited" - } - } - }, - "indexes": [ - [ - "name" - ] - ] - } - } -} -` - -// BridgeType is the simplified ORM model of the Bridge table -type BridgeType struct { - UUID string `ovsdb:"_uuid"` - Name string `ovsdb:"name"` - DatapathType string `ovsdb:"datapath_type"` - DatapathID *string `ovsdb:"datapath_id"` - OtherConfig map[string]string `ovsdb:"other_config"` - ExternalIds map[string]string `ovsdb:"external_ids"` - Ports []string `ovsdb:"ports"` - Status map[string]string `ovsdb:"status"` -} - -// OvsType is the simplified ORM model of the Bridge table -type OvsType struct { - UUID string `ovsdb:"_uuid"` - Bridges []string `ovsdb:"bridges"` -} - -func GetSchema() (ovsdb.DatabaseSchema, error) { - var dbSchema ovsdb.DatabaseSchema - err := json.Unmarshal([]byte(schema), &dbSchema) - return dbSchema, err -} diff --git a/test/test_model.go b/test/test_model.go new file mode 100644 index 00000000..ddee2138 --- /dev/null +++ b/test/test_model.go @@ -0,0 +1,1693 @@ +package test + +import ( + "encoding/json" + + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" +) + +// FullDatabaseModel returns the DatabaseModel object to be used in libovsdb +func FullDatabaseModel() (model.ClientDBModel, error) { + return model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ + "Bridge": &Bridge{}, + "Open_vSwitch": &OpenvSwitch{}, + "IPFIX": &IPFIX{}, + "Queue": &Queue{}, + }) +} + +var schema = `{ + "name": "Open_vSwitch", + "version": "8.3.0", + "tables": { + "Bridge": { + "columns": { + "auto_attach": { + "type": { + "key": { + "type": "uuid", + "refTable": "AutoAttach" + }, + "min": 0, + "max": 1 + } + }, + "controller": { + "type": { + "key": { + "type": "uuid", + "refTable": "Controller" + }, + "min": 0, + "max": "unlimited" + } + }, + "datapath_id": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": 1 + }, + "ephemeral": true + }, + "datapath_type": { + "type": "string" + }, + "datapath_version": { + "type": "string" + }, + "external_ids": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "fail_mode": { + "type": { + "key": { + "type": "string", + "enum": [ + "set", + [ + "standalone", + "secure" + ] + ] + }, + "min": 0, + "max": 1 + } + }, + "flow_tables": { + "type": { + "key": { + "type": "integer", + "minInteger": 0, + "maxInteger": 254 + }, + "value": { + "type": "uuid", + "refTable": "Flow_Table" + }, + "min": 0, + "max": "unlimited" + } + }, + "ipfix": { + "type": { + "key": { + "type": "uuid", + "refTable": "IPFIX" + }, + "min": 0, + "max": 1 + } + }, + "mirrors": { + "type": { + "key": { + "type": "uuid", + "refTable": "Mirror" + }, + "min": 0, + "max": "unlimited" + } + }, + "name": { + "type": "string", + "mutable": false + }, + "netflow": { + "type": { + "key": { + "type": "uuid", + "refTable": "NetFlow" + }, + "min": 0, + "max": 1 + } + }, + "other_config": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "ports": { + "type": { + "key": { + "type": "uuid", + "refTable": "Port" + }, + "min": 0, + "max": "unlimited" + } + }, + "protocols": { + "type": { + "key": { + "type": "string", + "enum": [ + "set", + [ + "OpenFlow10", + "OpenFlow11", + "OpenFlow12", + "OpenFlow13", + "OpenFlow14", + "OpenFlow15" + ] + ] + }, + "min": 0, + "max": "unlimited" + } + }, + "rstp_status": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + }, + "ephemeral": true + }, + "sflow": { + "type": { + "key": { + "type": "uuid", + "refTable": "sFlow" + }, + "min": 0, + "max": 1 + } + }, + "status": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + }, + "ephemeral": true + } + }, + "indexes": [ + [ + "name" + ] + ] + }, + "IPFIX": { + "columns": { + "cache_active_timeout": { + "type": { + "key": { + "type": "integer", + "minInteger": 0, + "maxInteger": 4200 + }, + "min": 0, + "max": 1 + } + }, + "cache_max_flows": { + "type": { + "key": { + "type": "integer", + "minInteger": 0, + "maxInteger": 4294967295 + }, + "min": 0, + "max": 1 + } + }, + "external_ids": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "obs_domain_id": { + "type": { + "key": { + "type": "integer", + "minInteger": 0, + "maxInteger": 4294967295 + }, + "min": 0, + "max": 1 + } + }, + "obs_point_id": { + "type": { + "key": { + "type": "integer", + "minInteger": 0, + "maxInteger": 4294967295 + }, + "min": 0, + "max": 1 + } + }, + "other_config": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "sampling": { + "type": { + "key": { + "type": "integer", + "minInteger": 1, + "maxInteger": 4294967295 + }, + "min": 0, + "max": 1 + } + }, + "targets": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + } + } + }, + "Open_vSwitch": { + "columns": { + "bridges": { + "type": { + "key": { + "type": "uuid", + "refTable": "Bridge" + }, + "min": 0, + "max": "unlimited" + } + }, + "cur_cfg": { + "type": "integer" + }, + "datapath_types": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "datapaths": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "uuid", + "refTable": "Datapath" + }, + "min": 0, + "max": "unlimited" + } + }, + "db_version": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": 1 + } + }, + "dpdk_version": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": 1 + } + }, + "external_ids": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "iface_types": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "manager_options": { + "type": { + "key": { + "type": "uuid", + "refTable": "Manager" + }, + "min": 0, + "max": "unlimited" + } + }, + "next_cfg": { + "type": "integer" + }, + "other_config": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "ovs_version": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": 1 + } + }, + "ssl": { + "type": { + "key": { + "type": "uuid", + "refTable": "SSL" + }, + "min": 0, + "max": 1 + } + }, + "statistics": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + }, + "ephemeral": true + }, + "system_type": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": 1 + } + }, + "system_version": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": 1 + } + } + } + }, + "Queue": { + "columns": { + "dscp": { + "type": { + "key": { + "type": "integer", + "minInteger": 0, + "maxInteger": 63 + }, + "min": 0, + "max": 1 + } + }, + "external_ids": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "other_config": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + } + } + } + } + }` + +func Schema() (ovsdb.DatabaseSchema, error) { + var s ovsdb.DatabaseSchema + err := json.Unmarshal([]byte(schema), &s) + return s, err +} + +// OpenvSwitch defines an object in Open_vSwitch table +type OpenvSwitch struct { + UUID string `ovsdb:"_uuid"` + Bridges []string `ovsdb:"bridges"` + CurCfg int `ovsdb:"cur_cfg"` + DatapathTypes []string `ovsdb:"datapath_types"` + Datapaths map[string]string `ovsdb:"datapaths"` + DbVersion *string `ovsdb:"db_version"` + DpdkInitialized bool //`ovsdb:"dpdk_initialized"` + DpdkVersion *string `ovsdb:"dpdk_version"` + ExternalIDs map[string]string `ovsdb:"external_ids"` + IfaceTypes []string `ovsdb:"iface_types"` + ManagerOptions []string `ovsdb:"manager_options"` + NextCfg int `ovsdb:"next_cfg"` + OtherConfig map[string]string `ovsdb:"other_config"` + OVSVersion *string `ovsdb:"ovs_version"` + SSL *string `ovsdb:"ssl"` + Statistics map[string]string `ovsdb:"statistics"` + SystemType *string `ovsdb:"system_type"` + SystemVersion *string `ovsdb:"system_version"` +} + +func copyOpenvSwitchBridges(a []string) []string { + if a == nil { + return nil + } + b := make([]string, len(a)) + copy(b, a) + return b +} + +func equalOpenvSwitchBridges(a, b []string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for i, v := range a { + if b[i] != v { + return false + } + } + return true +} + +func copyOpenvSwitchDatapathTypes(a []string) []string { + if a == nil { + return nil + } + b := make([]string, len(a)) + copy(b, a) + return b +} + +func equalOpenvSwitchDatapathTypes(a, b []string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for i, v := range a { + if b[i] != v { + return false + } + } + return true +} + +func copyOpenvSwitchDatapaths(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalOpenvSwitchDatapaths(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func copyOpenvSwitchDbVersion(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalOpenvSwitchDbVersion(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyOpenvSwitchDpdkVersion(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalOpenvSwitchDpdkVersion(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyOpenvSwitchExternalIDs(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalOpenvSwitchExternalIDs(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func copyOpenvSwitchIfaceTypes(a []string) []string { + if a == nil { + return nil + } + b := make([]string, len(a)) + copy(b, a) + return b +} + +func equalOpenvSwitchIfaceTypes(a, b []string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for i, v := range a { + if b[i] != v { + return false + } + } + return true +} + +func copyOpenvSwitchManagerOptions(a []string) []string { + if a == nil { + return nil + } + b := make([]string, len(a)) + copy(b, a) + return b +} + +func equalOpenvSwitchManagerOptions(a, b []string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for i, v := range a { + if b[i] != v { + return false + } + } + return true +} + +func copyOpenvSwitchOtherConfig(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalOpenvSwitchOtherConfig(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func copyOpenvSwitchOVSVersion(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalOpenvSwitchOVSVersion(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyOpenvSwitchSSL(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalOpenvSwitchSSL(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyOpenvSwitchStatistics(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalOpenvSwitchStatistics(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func copyOpenvSwitchSystemType(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalOpenvSwitchSystemType(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyOpenvSwitchSystemVersion(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalOpenvSwitchSystemVersion(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func (a *OpenvSwitch) DeepCopyInto(b *OpenvSwitch) { + *b = *a + b.Bridges = copyOpenvSwitchBridges(a.Bridges) + b.DatapathTypes = copyOpenvSwitchDatapathTypes(a.DatapathTypes) + b.Datapaths = copyOpenvSwitchDatapaths(a.Datapaths) + b.DbVersion = copyOpenvSwitchDbVersion(a.DbVersion) + b.DpdkVersion = copyOpenvSwitchDpdkVersion(a.DpdkVersion) + b.ExternalIDs = copyOpenvSwitchExternalIDs(a.ExternalIDs) + b.IfaceTypes = copyOpenvSwitchIfaceTypes(a.IfaceTypes) + b.ManagerOptions = copyOpenvSwitchManagerOptions(a.ManagerOptions) + b.OtherConfig = copyOpenvSwitchOtherConfig(a.OtherConfig) + b.OVSVersion = copyOpenvSwitchOVSVersion(a.OVSVersion) + b.SSL = copyOpenvSwitchSSL(a.SSL) + b.Statistics = copyOpenvSwitchStatistics(a.Statistics) + b.SystemType = copyOpenvSwitchSystemType(a.SystemType) + b.SystemVersion = copyOpenvSwitchSystemVersion(a.SystemVersion) +} + +func (a *OpenvSwitch) DeepCopy() *OpenvSwitch { + b := new(OpenvSwitch) + a.DeepCopyInto(b) + return b +} + +func (a *OpenvSwitch) CloneModelInto(b model.Model) { + c := b.(*OpenvSwitch) + a.DeepCopyInto(c) +} + +func (a *OpenvSwitch) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *OpenvSwitch) Equals(b *OpenvSwitch) bool { + return a.UUID == b.UUID && + equalOpenvSwitchBridges(a.Bridges, b.Bridges) && + a.CurCfg == b.CurCfg && + equalOpenvSwitchDatapathTypes(a.DatapathTypes, b.DatapathTypes) && + equalOpenvSwitchDatapaths(a.Datapaths, b.Datapaths) && + equalOpenvSwitchDbVersion(a.DbVersion, b.DbVersion) && + a.DpdkInitialized == b.DpdkInitialized && + equalOpenvSwitchDpdkVersion(a.DpdkVersion, b.DpdkVersion) && + equalOpenvSwitchExternalIDs(a.ExternalIDs, b.ExternalIDs) && + equalOpenvSwitchIfaceTypes(a.IfaceTypes, b.IfaceTypes) && + equalOpenvSwitchManagerOptions(a.ManagerOptions, b.ManagerOptions) && + a.NextCfg == b.NextCfg && + equalOpenvSwitchOtherConfig(a.OtherConfig, b.OtherConfig) && + equalOpenvSwitchOVSVersion(a.OVSVersion, b.OVSVersion) && + equalOpenvSwitchSSL(a.SSL, b.SSL) && + equalOpenvSwitchStatistics(a.Statistics, b.Statistics) && + equalOpenvSwitchSystemType(a.SystemType, b.SystemType) && + equalOpenvSwitchSystemVersion(a.SystemVersion, b.SystemVersion) +} + +func (a *OpenvSwitch) EqualsModel(b model.Model) bool { + c := b.(*OpenvSwitch) + return a.Equals(c) +} + +var _ model.CloneableModel = &OpenvSwitch{} +var _ model.ComparableModel = &OpenvSwitch{} + +type ( + BridgeFailMode = string + BridgeProtocols = string +) + +var ( + BridgeFailModeStandalone BridgeFailMode = "standalone" + BridgeFailModeSecure BridgeFailMode = "secure" + BridgeProtocolsOpenflow10 BridgeProtocols = "OpenFlow10" + BridgeProtocolsOpenflow11 BridgeProtocols = "OpenFlow11" + BridgeProtocolsOpenflow12 BridgeProtocols = "OpenFlow12" + BridgeProtocolsOpenflow13 BridgeProtocols = "OpenFlow13" + BridgeProtocolsOpenflow14 BridgeProtocols = "OpenFlow14" + BridgeProtocolsOpenflow15 BridgeProtocols = "OpenFlow15" +) + +// Bridge defines an object in Bridge table +type Bridge struct { + UUID string `ovsdb:"_uuid"` + AutoAttach *string `ovsdb:"auto_attach"` + Controller []string `ovsdb:"controller"` + DatapathID *string `ovsdb:"datapath_id"` + DatapathType string `ovsdb:"datapath_type"` + DatapathVersion string `ovsdb:"datapath_version"` + ExternalIDs map[string]string `ovsdb:"external_ids"` + FailMode *BridgeFailMode `ovsdb:"fail_mode"` + FloodVLANs [4096]int //`ovsdb:"flood_vlans"` + FlowTables map[int]string `ovsdb:"flow_tables"` + IPFIX *string `ovsdb:"ipfix"` + McastSnoopingEnable bool //`ovsdb:"mcast_snooping_enable"` + Mirrors []string `ovsdb:"mirrors"` + Name string `ovsdb:"name"` + Netflow *string `ovsdb:"netflow"` + OtherConfig map[string]string `ovsdb:"other_config"` + Ports []string `ovsdb:"ports"` + Protocols []BridgeProtocols `ovsdb:"protocols"` + RSTPEnable bool //`ovsdb:"rstp_enable"` + RSTPStatus map[string]string `ovsdb:"rstp_status"` + Sflow *string `ovsdb:"sflow"` + Status map[string]string `ovsdb:"status"` + STPEnable bool //`ovsdb:"stp_enable"` +} + +func copyBridgeAutoAttach(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalBridgeAutoAttach(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyBridgeController(a []string) []string { + if a == nil { + return nil + } + b := make([]string, len(a)) + copy(b, a) + return b +} + +func equalBridgeController(a, b []string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for i, v := range a { + if b[i] != v { + return false + } + } + return true +} + +func copyBridgeDatapathID(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalBridgeDatapathID(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyBridgeExternalIDs(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalBridgeExternalIDs(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func copyBridgeFailMode(a *BridgeFailMode) *BridgeFailMode { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalBridgeFailMode(a, b *BridgeFailMode) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyBridgeFlowTables(a map[int]string) map[int]string { + if a == nil { + return nil + } + b := make(map[int]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalBridgeFlowTables(a, b map[int]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func copyBridgeIPFIX(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalBridgeIPFIX(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyBridgeMirrors(a []string) []string { + if a == nil { + return nil + } + b := make([]string, len(a)) + copy(b, a) + return b +} + +func equalBridgeMirrors(a, b []string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for i, v := range a { + if b[i] != v { + return false + } + } + return true +} + +func copyBridgeNetflow(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalBridgeNetflow(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyBridgeOtherConfig(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalBridgeOtherConfig(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func copyBridgePorts(a []string) []string { + if a == nil { + return nil + } + b := make([]string, len(a)) + copy(b, a) + return b +} + +func equalBridgePorts(a, b []string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for i, v := range a { + if b[i] != v { + return false + } + } + return true +} + +func copyBridgeProtocols(a []BridgeProtocols) []BridgeProtocols { + if a == nil { + return nil + } + b := make([]BridgeProtocols, len(a)) + copy(b, a) + return b +} + +func equalBridgeProtocols(a, b []BridgeProtocols) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for i, v := range a { + if b[i] != v { + return false + } + } + return true +} + +func copyBridgeRSTPStatus(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalBridgeRSTPStatus(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func copyBridgeSflow(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalBridgeSflow(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyBridgeStatus(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalBridgeStatus(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func (a *Bridge) DeepCopyInto(b *Bridge) { + *b = *a + b.AutoAttach = copyBridgeAutoAttach(a.AutoAttach) + b.Controller = copyBridgeController(a.Controller) + b.DatapathID = copyBridgeDatapathID(a.DatapathID) + b.ExternalIDs = copyBridgeExternalIDs(a.ExternalIDs) + b.FailMode = copyBridgeFailMode(a.FailMode) + b.FlowTables = copyBridgeFlowTables(a.FlowTables) + b.IPFIX = copyBridgeIPFIX(a.IPFIX) + b.Mirrors = copyBridgeMirrors(a.Mirrors) + b.Netflow = copyBridgeNetflow(a.Netflow) + b.OtherConfig = copyBridgeOtherConfig(a.OtherConfig) + b.Ports = copyBridgePorts(a.Ports) + b.Protocols = copyBridgeProtocols(a.Protocols) + b.RSTPStatus = copyBridgeRSTPStatus(a.RSTPStatus) + b.Sflow = copyBridgeSflow(a.Sflow) + b.Status = copyBridgeStatus(a.Status) +} + +func (a *Bridge) DeepCopy() *Bridge { + b := new(Bridge) + a.DeepCopyInto(b) + return b +} + +func (a *Bridge) CloneModelInto(b model.Model) { + c := b.(*Bridge) + a.DeepCopyInto(c) +} + +func (a *Bridge) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *Bridge) Equals(b *Bridge) bool { + return a.UUID == b.UUID && + equalBridgeAutoAttach(a.AutoAttach, b.AutoAttach) && + equalBridgeController(a.Controller, b.Controller) && + equalBridgeDatapathID(a.DatapathID, b.DatapathID) && + a.DatapathType == b.DatapathType && + a.DatapathVersion == b.DatapathVersion && + equalBridgeExternalIDs(a.ExternalIDs, b.ExternalIDs) && + equalBridgeFailMode(a.FailMode, b.FailMode) && + a.FloodVLANs == b.FloodVLANs && + equalBridgeFlowTables(a.FlowTables, b.FlowTables) && + equalBridgeIPFIX(a.IPFIX, b.IPFIX) && + a.McastSnoopingEnable == b.McastSnoopingEnable && + equalBridgeMirrors(a.Mirrors, b.Mirrors) && + a.Name == b.Name && + equalBridgeNetflow(a.Netflow, b.Netflow) && + equalBridgeOtherConfig(a.OtherConfig, b.OtherConfig) && + equalBridgePorts(a.Ports, b.Ports) && + equalBridgeProtocols(a.Protocols, b.Protocols) && + a.RSTPEnable == b.RSTPEnable && + equalBridgeRSTPStatus(a.RSTPStatus, b.RSTPStatus) && + equalBridgeSflow(a.Sflow, b.Sflow) && + equalBridgeStatus(a.Status, b.Status) && + a.STPEnable == b.STPEnable +} + +func (a *Bridge) EqualsModel(b model.Model) bool { + c := b.(*Bridge) + return a.Equals(c) +} + +var _ model.CloneableModel = &Bridge{} +var _ model.ComparableModel = &Bridge{} + +// IPFIX defines an object in IPFIX table +type IPFIX struct { + UUID string `ovsdb:"_uuid"` + CacheActiveTimeout *int `ovsdb:"cache_active_timeout"` + CacheMaxFlows *int `ovsdb:"cache_max_flows"` + ExternalIDs map[string]string `ovsdb:"external_ids"` + ObsDomainID *int `ovsdb:"obs_domain_id"` + ObsPointID *int `ovsdb:"obs_point_id"` + OtherConfig map[string]string `ovsdb:"other_config"` + Sampling *int `ovsdb:"sampling"` + Targets []string `ovsdb:"targets"` +} + +func copyIPFIXCacheActiveTimeout(a *int) *int { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalIPFIXCacheActiveTimeout(a, b *int) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyIPFIXCacheMaxFlows(a *int) *int { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalIPFIXCacheMaxFlows(a, b *int) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyIPFIXExternalIDs(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalIPFIXExternalIDs(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func copyIPFIXObsDomainID(a *int) *int { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalIPFIXObsDomainID(a, b *int) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyIPFIXObsPointID(a *int) *int { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalIPFIXObsPointID(a, b *int) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyIPFIXOtherConfig(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalIPFIXOtherConfig(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func copyIPFIXSampling(a *int) *int { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalIPFIXSampling(a, b *int) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyIPFIXTargets(a []string) []string { + if a == nil { + return nil + } + b := make([]string, len(a)) + copy(b, a) + return b +} + +func equalIPFIXTargets(a, b []string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for i, v := range a { + if b[i] != v { + return false + } + } + return true +} + +func (a *IPFIX) DeepCopyInto(b *IPFIX) { + *b = *a + b.CacheActiveTimeout = copyIPFIXCacheActiveTimeout(a.CacheActiveTimeout) + b.CacheMaxFlows = copyIPFIXCacheMaxFlows(a.CacheMaxFlows) + b.ExternalIDs = copyIPFIXExternalIDs(a.ExternalIDs) + b.ObsDomainID = copyIPFIXObsDomainID(a.ObsDomainID) + b.ObsPointID = copyIPFIXObsPointID(a.ObsPointID) + b.OtherConfig = copyIPFIXOtherConfig(a.OtherConfig) + b.Sampling = copyIPFIXSampling(a.Sampling) + b.Targets = copyIPFIXTargets(a.Targets) +} + +func (a *IPFIX) DeepCopy() *IPFIX { + b := new(IPFIX) + a.DeepCopyInto(b) + return b +} + +func (a *IPFIX) CloneModelInto(b model.Model) { + c := b.(*IPFIX) + a.DeepCopyInto(c) +} + +func (a *IPFIX) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *IPFIX) Equals(b *IPFIX) bool { + return a.UUID == b.UUID && + equalIPFIXCacheActiveTimeout(a.CacheActiveTimeout, b.CacheActiveTimeout) && + equalIPFIXCacheMaxFlows(a.CacheMaxFlows, b.CacheMaxFlows) && + equalIPFIXExternalIDs(a.ExternalIDs, b.ExternalIDs) && + equalIPFIXObsDomainID(a.ObsDomainID, b.ObsDomainID) && + equalIPFIXObsPointID(a.ObsPointID, b.ObsPointID) && + equalIPFIXOtherConfig(a.OtherConfig, b.OtherConfig) && + equalIPFIXSampling(a.Sampling, b.Sampling) && + equalIPFIXTargets(a.Targets, b.Targets) +} + +func (a *IPFIX) EqualsModel(b model.Model) bool { + c := b.(*IPFIX) + return a.Equals(c) +} + +var _ model.CloneableModel = &IPFIX{} +var _ model.ComparableModel = &IPFIX{} + +// Queue defines an object in Queue table +type Queue struct { + UUID string `ovsdb:"_uuid"` + DSCP *int `ovsdb:"dscp"` + ExternalIDs map[string]string `ovsdb:"external_ids"` + OtherConfig map[string]string `ovsdb:"other_config"` +} + +func copyQueueDSCP(a *int) *int { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalQueueDSCP(a, b *int) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyQueueExternalIDs(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalQueueExternalIDs(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func copyQueueOtherConfig(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalQueueOtherConfig(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func (a *Queue) DeepCopyInto(b *Queue) { + *b = *a + b.DSCP = copyQueueDSCP(a.DSCP) + b.ExternalIDs = copyQueueExternalIDs(a.ExternalIDs) + b.OtherConfig = copyQueueOtherConfig(a.OtherConfig) +} + +func (a *Queue) DeepCopy() *Queue { + b := new(Queue) + a.DeepCopyInto(b) + return b +} + +func (a *Queue) CloneModelInto(b model.Model) { + c := b.(*Queue) + a.DeepCopyInto(c) +} + +func (a *Queue) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *Queue) Equals(b *Queue) bool { + return a.UUID == b.UUID && + equalQueueDSCP(a.DSCP, b.DSCP) && + equalQueueExternalIDs(a.ExternalIDs, b.ExternalIDs) && + equalQueueOtherConfig(a.OtherConfig, b.OtherConfig) +} + +func (a *Queue) EqualsModel(b model.Model) bool { + c := b.(*Queue) + return a.Equals(c) +} + +var _ model.CloneableModel = &Queue{} +var _ model.ComparableModel = &Queue{} From 54b443607de5ab2ae1a2d56cebd9556685252e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Wed, 1 Jun 2022 18:22:24 +0000 Subject: [PATCH 07/18] cache: lazy initialization of the event channel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating the event channel has a performance cost due to being buffered and high capacity. There is no use for it in some situations, like a transaction or database cache and in such cases the event processor wont be run. Delay initialization of the event channel until that time. Signed-off-by: Jaime Caamaño Ruiz --- cache/cache.go | 41 ++++++++++++++++++++++++++++++----------- cache/cache_test.go | 1 + 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/cache/cache.go b/cache/cache.go index cb7685d4..e9eab2aa 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -1154,19 +1154,28 @@ type event struct { // eventProcessor handles the queueing and processing of cache events type eventProcessor struct { events chan event - // handlersMutex locks the handlers array when we add a handler or dispatch events - // we don't need a RWMutex in this case as we only have one thread reading and the write - // volume is very low (i.e only when AddEventHandler is called) - handlersMutex sync.Mutex - handlers []EventHandler - logger *logr.Logger + // eventMutex locks the events channel and handlers array when we add a + // handler or dispatch events + eventMutex sync.RWMutex + handlers []EventHandler + logger *logr.Logger + capacity int } func newEventProcessor(capacity int, logger *logr.Logger) *eventProcessor { return &eventProcessor{ - events: make(chan event, capacity), handlers: []EventHandler{}, logger: logger, + capacity: capacity, + } +} + +// initEventsChannel is a lazy initializer for the events channel as it has +// performance cost and not everyone has a use for it. eventMutex needs to be +// held before calling this method. +func (e *eventProcessor) initEventsChannel() { + if e.events == nil { + e.events = make(chan event, e.capacity) } } @@ -1175,13 +1184,20 @@ func newEventProcessor(capacity int, logger *logr.Logger) *eventProcessor { // to be processed by the client. Long Running handler functions adversely affect // other handlers and MAY cause loss of data if the channel buffer is full func (e *eventProcessor) AddEventHandler(handler EventHandler) { - e.handlersMutex.Lock() - defer e.handlersMutex.Unlock() + e.eventMutex.Lock() + defer e.eventMutex.Unlock() + e.initEventsChannel() e.handlers = append(e.handlers, handler) } // AddEvent writes an event to the channel func (e *eventProcessor) AddEvent(eventType string, table string, old model.Model, new model.Model) { + e.eventMutex.RLock() + hasEventChannel := e.events != nil + e.eventMutex.RUnlock() + if !hasEventChannel { + return + } // We don't need to check for error here since there // is only a single writer. RPC is run in blocking mode event := event{ @@ -1204,12 +1220,15 @@ func (e *eventProcessor) AddEvent(eventType string, table string, old model.Mode // Otherwise it will wait for events to arrive on the event channel // Once received, it will dispatch the event to each registered handler func (e *eventProcessor) Run(stopCh <-chan struct{}) { + e.eventMutex.Lock() + e.initEventsChannel() + e.eventMutex.Unlock() for { select { case <-stopCh: return case event := <-e.events: - e.handlersMutex.Lock() + e.eventMutex.RLock() for _, handler := range e.handlers { switch event.eventType { case addEvent: @@ -1220,7 +1239,7 @@ func (e *eventProcessor) Run(stopCh <-chan struct{}) { handler.OnDelete(event.table, event.old) } } - e.handlersMutex.Unlock() + e.eventMutex.RUnlock() } } } diff --git a/cache/cache_test.go b/cache/cache_test.go index 8982b7d9..85d16d5b 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -1666,6 +1666,7 @@ func TestTableCachePopulate2BrokenIndexes(t *testing.T) { func TestEventProcessor_AddEvent(t *testing.T) { logger := logr.Discard() ep := newEventProcessor(16, &logger) + ep.events = make(chan event, ep.capacity) var events []event for i := 0; i < 17; i++ { events = append(events, event{ From bcd7e745912625077db31354e7665fd05bd4d2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Wed, 1 Jun 2022 18:49:53 +0000 Subject: [PATCH 08/18] database: run transaction on shallow model copies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can run the transaction on shallow model copies of its own cache as it never modifies them directly, it uses tableUpdates instead. We can do the same for the temporary database of the validating client transaction. Signed-off-by: Jaime Caamaño Ruiz --- cache/cache.go | 22 ++++++++++++++++++---- client/transact.go | 2 +- database/transaction.go | 2 +- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cache/cache.go b/cache/cache.go index e9eab2aa..ef994308 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -632,8 +632,10 @@ func (r *RowCache) uuidsByConditionsAsIndexes(conditions []ovsdb.Condition, nati return matching, err } -// RowsByCondition searches models in the cache that match all conditions -func (r *RowCache) RowsByCondition(conditions []ovsdb.Condition) (map[string]model.Model, error) { +type cloner func(model.Model) model.Model + +// rowsByCondition searches models in the cache that match all conditions +func (r *RowCache) rowsByCondition(conditions []ovsdb.Condition, cloner cloner) (map[string]model.Model, error) { r.mutex.RLock() defer r.mutex.RUnlock() results := make(map[string]model.Model) @@ -642,7 +644,7 @@ func (r *RowCache) RowsByCondition(conditions []ovsdb.Condition) (map[string]mod // no conditions matches all rows if len(conditions) == 0 { for uuid := range r.cache { - results[uuid] = r.rowByUUID(uuid) + results[uuid] = cloner(r.cache[uuid]) } return results, nil } @@ -731,12 +733,24 @@ func (r *RowCache) RowsByCondition(conditions []ovsdb.Condition) (map[string]mod } for uuid := range matching { - results[uuid] = r.rowByUUID(uuid) + results[uuid] = cloner(r.cache[uuid]) } return results, nil } +func (r *RowCache) RowsByCondition(conditions []ovsdb.Condition) (map[string]model.Model, error) { + return r.rowsByCondition(conditions, func(m model.Model) model.Model { + return model.Clone(m) + }) +} + +func (r *RowCache) RowsByConditionShallow(conditions []ovsdb.Condition) (map[string]model.Model, error) { + return r.rowsByCondition(conditions, func(m model.Model) model.Model { + return m + }) +} + // Len returns the length of the cache func (r *RowCache) Len() int { r.mutex.RLock() diff --git a/client/transact.go b/client/transact.go index 771ba39f..1f43fe3c 100644 --- a/client/transact.go +++ b/client/transact.go @@ -146,7 +146,7 @@ func (c *cacheDatabase) List(database string, table string, conditions ...ovsdb. if targetTable == nil { return nil, fmt.Errorf("table does not exist") } - return targetTable.RowsByCondition(conditions) + return targetTable.RowsByConditionShallow(conditions) } func (c *cacheDatabase) Get(database string, table string, uuid string) (model.Model, error) { diff --git a/database/transaction.go b/database/transaction.go index 072b6c7e..9c7b4d69 100644 --- a/database/transaction.go +++ b/database/transaction.go @@ -124,7 +124,7 @@ func (t *Transaction) Transact(operations []ovsdb.Operation) ([]ovsdb.OperationR } func (t *Transaction) rowsFromTransactionCacheAndDatabase(table string, where []ovsdb.Condition) (map[string]model.Model, error) { - txnRows, err := t.Cache.Table(table).RowsByCondition(where) + txnRows, err := t.Cache.Table(table).RowsByConditionShallow(where) if err != nil { return nil, fmt.Errorf("failed getting rows for table %s from transaction cache: %v", table, err) } From b7c86f61307262d3ae7724b76774279b22408497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Wed, 1 Jun 2022 20:04:48 +0000 Subject: [PATCH 09/18] cache: don't clone old replaced model on update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Caamaño Ruiz --- cache/cache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cache/cache.go b/cache/cache.go index ef994308..5b2647cb 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -301,7 +301,7 @@ func (r *RowCache) Update(uuid string, m model.Model, checkIndexes bool) (model. if _, ok := r.cache[uuid]; !ok { return nil, NewErrCacheInconsistent(fmt.Sprintf("cannot update row %s as it does not exist in the cache", uuid)) } - oldRow := model.Clone(r.cache[uuid]) + oldRow := r.cache[uuid] oldInfo, err := r.dbModel.NewModelInfo(oldRow) if err != nil { return nil, err From d4b14973ab056fafaa394a0d2f26fbd51ab514d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 2 Jun 2022 00:04:41 +0000 Subject: [PATCH 10/18] update golangci-lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Caamaño Ruiz --- .github/workflows/ci.yml | 2 +- ovsdb/schema.go | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e7d919e..6ff26c82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.40.1 + version: v1.46.2 skip-go-installation: true skip-pkg-cache: true skip-build-cache: true diff --git a/ovsdb/schema.go b/ovsdb/schema.go index 30ecb1b4..cf80aa50 100644 --- a/ovsdb/schema.go +++ b/ovsdb/schema.go @@ -321,9 +321,7 @@ func (b *BaseType) UnmarshalJSON(data []byte) error { oSet := bt.Enum.([]interface{}) innerSet := oSet[1].([]interface{}) b.Enum = make([]interface{}, len(innerSet)) - for k, val := range innerSet { - b.Enum[k] = val - } + copy(b.Enum, innerSet) default: b.Enum = []interface{}{bt.Enum} } From 47b94f3e3484653f1b3f57eef1903e7c6d05d462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 2 Jun 2022 00:59:28 +0000 Subject: [PATCH 11/18] client: don't evaluate log arguments unless enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Caamaño Ruiz --- client/transact.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/transact.go b/client/transact.go index 1f43fe3c..f7e6c744 100644 --- a/client/transact.go +++ b/client/transact.go @@ -40,8 +40,7 @@ func (o *ovsdbClient) transactReconnect(ctx context.Context, operation ...ovsdb. if o.rpcClient == nil || !o.connected { o.rpcMutex.RUnlock() if o.options.reconnect { - o.logger.V(5).Info("blocking transaction until reconnected", "operations", - fmt.Sprintf("%+v", operation)) + o.logger.V(5).Info("blocking transaction until reconnected", "operations", operation) ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() ReconnectWaitLoop: @@ -75,7 +74,7 @@ func (o *ovsdbClient) transact(ctx context.Context, dbName string, operation ... if o.rpcClient == nil { return nil, ErrNotConnected } - o.logger.V(4).Info("transacting operations", "database", dbName, "operations", fmt.Sprintf("%+v", operation)) + o.logger.V(4).Info("transacting operations", "database", dbName, "operations", operation) var reply []ovsdb.OperationResult err := o.rpcClient.CallWithContext(ctx, "transact", args, &reply) if err != nil { From a66d7826862b866044c57377e23f8b21467eac96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 2 Jun 2022 10:37:23 +0000 Subject: [PATCH 12/18] mapper: improve performance of NewRow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Caamaño Ruiz --- mapper/mapper.go | 62 +++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/mapper/mapper.go b/mapper/mapper.go index 7b762003..2d9d87bf 100644 --- a/mapper/mapper.go +++ b/mapper/mapper.go @@ -85,45 +85,53 @@ func (m Mapper) getData(ovsData ovsdb.Row, result *Info) error { // By default, default or null values are skipped. This behavior can be modified by specifying // a list of fields (pointers to fields in the struct) to be added to the row func (m Mapper) NewRow(data *Info, fields ...interface{}) (ovsdb.Row, error) { - columns := make(map[string]*ovsdb.ColumnSchema) - for k, v := range data.Metadata.TableSchema.Columns { - columns[k] = v + fieldsRequested := make(map[string]interface{}, len(fields)) + for _, f := range fields { + col, err := data.ColumnByPtr(f) + if err != nil { + return nil, err + } + fieldsRequested[col] = f } - columns["_uuid"] = &ovsdb.UUIDColumn - ovsRow := make(map[string]interface{}, len(columns)) - for name, column := range columns { + + ovsRow := make(map[string]interface{}, len(data.Metadata.TableSchema.Columns)+1) + setColumn := func(name string, column *ovsdb.ColumnSchema) error { + // ignore if field was not requested + _, fieldRequested := fieldsRequested[name] + if len(fieldsRequested) > 0 && !fieldRequested { + return nil + } + nativeElem, err := data.FieldByColumn(name) if err != nil { // If provided struct does not have a field to hold this value, skip it - continue + return nil } - // add specific fields - if len(fields) > 0 { - found := false - for _, f := range fields { - col, err := data.ColumnByPtr(f) - if err != nil { - return nil, err - } - if col == name { - found = true - break - } - } - if !found { - continue - } - } - if len(fields) == 0 && ovsdb.IsDefaultValue(column, nativeElem) { - continue + // ignore default values if not requested + if len(fieldsRequested) == 0 && ovsdb.IsDefaultValue(column, nativeElem) { + return nil } + ovsElem, err := ovsdb.NativeToOvs(column, nativeElem) if err != nil { - return nil, fmt.Errorf("table %s, column %s: failed to generate ovs element. %s", data.Metadata.TableName, name, err.Error()) + return fmt.Errorf("table %s, column %s: failed to generate ovs element. %s", data.Metadata.TableName, name, err.Error()) } ovsRow[name] = ovsElem + return nil + } + + err := setColumn("_uuid", &ovsdb.UUIDColumn) + if err != nil { + return nil, err + } + for name, column := range data.Metadata.TableSchema.Columns { + err := setColumn(name, column) + if err != nil { + return nil, err + } } + return ovsRow, nil } From bd57ef7649d1f21be0542e12610e891ce2d28c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 2 Jun 2022 10:46:48 +0000 Subject: [PATCH 13/18] database: allocate transaction results capacity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Caamaño Ruiz --- database/transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/transaction.go b/database/transaction.go index 9c7b4d69..0a78cd83 100644 --- a/database/transaction.go +++ b/database/transaction.go @@ -41,7 +41,7 @@ func NewTransaction(model model.DatabaseModel, dbName string, database Database, } func (t *Transaction) Transact(operations []ovsdb.Operation) ([]ovsdb.OperationResult, ovsdb.TableUpdates2) { - results := []ovsdb.OperationResult{} + results := make([]ovsdb.OperationResult, 0, len(operations)+1) updates := make(ovsdb.TableUpdates2) for _, op := range operations { From 28f0131c2f8d49a789f2a5cad2b28459ef78a274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Mon, 20 Jun 2022 15:32:28 +0000 Subject: [PATCH 14/18] Add transaction validation benchmark with Mutates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Caamaño Ruiz --- client/client_test.go | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index 8f33119f..d0fc2f78 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -745,7 +745,7 @@ func TestClientValidateTransaction(t *testing.T) { } } -func BenchmarkClientValidateTransaction(b *testing.B) { +func BenchmarkClientValidateMutateTransaction(b *testing.B) { defSchema, err := Schema() require.NoError(b, err) @@ -775,12 +775,14 @@ func BenchmarkClientValidateTransaction(b *testing.B) { cli := getClient() - numRows := 1000 + numRows := 500 models := []*Bridge{} for i := 0; i < numRows; i++ { model := &Bridge{ - Name: fmt.Sprintf("Name-%d", i), - DatapathVersion: fmt.Sprintf("DatapathVersion-%d", i), + Name: fmt.Sprintf("Name-%d", i), + } + for j := 0; j < numRows; j++ { + model.Ports = append(model.Ports, uuid.New().String()) } ops, err := cli.Create(model) require.NoError(b, err) @@ -797,32 +799,32 @@ func BenchmarkClientValidateTransaction(b *testing.B) { ops int }{ { - "1 update ops with validating client", + "1 mutate ops with validating client", getClient(WithTransactionValidation(true)), 1, }, { - "1 update ops with non validating client", + "1 mutate ops with non validating client", getClient(), 1, }, { - "10 update ops with validating client", + "10 mutate ops with validating client", getClient(WithTransactionValidation(true)), 10, }, { - "10 update ops with non validating client", + "10 mutate ops with non validating client", getClient(), 10, }, { - "100 update ops with validating client", + "100 mutate ops with validating client", getClient(WithTransactionValidation(true)), 100, }, { - "100 update ops with non validating client", + "100 mutate ops with non validating client", getClient(), 100, }, @@ -832,9 +834,12 @@ func BenchmarkClientValidateTransaction(b *testing.B) { cli := bm.client ops := []ovsdb.Operation{} for j := 0; j < bm.ops; j++ { - model := models[rand.Intn(numRows)] - model.DatapathVersion = fmt.Sprintf("%s-Updated", model.DatapathVersion) - op, err := cli.Where(model).Update(model) + m := models[rand.Intn(numRows)] + op, err := cli.Where(m).Mutate(m, model.Mutation{ + Field: &m.Ports, + Mutator: ovsdb.MutateOperationInsert, + Value: []string{uuid.New().String()}, + }) require.NoError(b, err) ops = append(ops, op...) } From 58bfc3b8d3dc51198436b09a969a106c7457ece5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Mon, 20 Jun 2022 15:41:01 +0000 Subject: [PATCH 15/18] database: optimize transaction diff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Caamaño Ruiz --- database/transaction.go | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/database/transaction.go b/database/transaction.go index 0a78cd83..28fb0029 100644 --- a/database/transaction.go +++ b/database/transaction.go @@ -653,29 +653,22 @@ func diff(a interface{}, b interface{}) interface{} { // replacement value replacement := b.(ovsdb.OvsSet) var c []interface{} + // it is important to keep this operation optimized as a typical + // scenario might have us calculating diffs over sets with a large + // number of uuids + replacementMap := make(map[interface{}]bool, len(replacement.GoSet)) + for _, replacementElem := range replacement.GoSet { + replacementMap[replacementElem] = false + } for _, originalElem := range original.GoSet { - found := false - for _, replacementElem := range replacement.GoSet { - if originalElem == replacementElem { - found = true - break - } - } - if !found { - // remove from client + if _, hasReplacement := replacementMap[originalElem]; !hasReplacement { c = append(c, originalElem) + } else { + replacementMap[originalElem] = true } } - for _, replacementElem := range replacement.GoSet { - found := false - for _, originalElem := range original.GoSet { - if replacementElem == originalElem { - found = true - break - } - } - if !found { - // add to client + for replacementElem, hasOriginal := range replacementMap { + if !hasOriginal { c = append(c, replacementElem) } } From 105c00460a4a48763eeed02376caf3b686f162f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Mon, 20 Jun 2022 15:42:13 +0000 Subject: [PATCH 16/18] database: don't diff updates on validating transactions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For updates and mutates, the difference for each column is calculated and set in the Modify row of an update2 notification. Calculating this difference has a performance cost when the column is a set with thousands of unordered values. While calculating the Modify row is a spec requirement over the wire, internally updates could be handled through the Old & New rows and a transaction that is specific for validating purposes can take advantage of this. Make the cache capable of processing an update2 with nil Modify and non-nil Old & New as if it were a normal update notification and avoid setting Modify for updates on validating transactions. Signed-off-by: Jaime Caamaño Ruiz --- cache/cache.go | 19 ++++++++++++++++++ client/transact.go | 2 +- database/transaction.go | 43 ++++++++++++++++++++++++++++++----------- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/cache/cache.go b/cache/cache.go index 5b2647cb..f331da05 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -942,7 +942,12 @@ func (t *TableCache) Disconnected() { func (t *TableCache) Populate(tableUpdates ovsdb.TableUpdates) error { t.mutex.Lock() defer t.mutex.Unlock() + return t.populate(tableUpdates) +} +// populate adds data to the cache and places an event on the channel, needs to +// be called with the table mutex locked +func (t *TableCache) populate(tableUpdates ovsdb.TableUpdates) error { for table := range t.dbModel.Types() { updates, ok := tableUpdates[table] if !ok { @@ -1054,6 +1059,20 @@ func (t *TableCache) Populate2(tableUpdates ovsdb.TableUpdates2) error { } t.eventProcessor.AddEvent(updateEvent, table, existing, modified) } + case row.Old != nil && row.New != nil: + // internally, we handle Mutates with Modify, but Updates with + // Old/New as calculating Modify from Old/New has a cost + updates1 := ovsdb.TableUpdates{ + table: ovsdb.TableUpdate{ + uuid: &ovsdb.RowUpdate{ + Old: row.Old, + New: row.New, + }, + }, + } + if err := t.populate(updates1); err != nil { + return err + } case row.Delete != nil: fallthrough default: diff --git a/client/transact.go b/client/transact.go index f7e6c744..078e7bd0 100644 --- a/client/transact.go +++ b/client/transact.go @@ -109,7 +109,7 @@ func (o *ovsdbClient) ValidateOperations(dbName string, operations ...ovsdb.Oper database.cacheMutex.RLock() defer database.cacheMutex.RUnlock() cacheDatabase := cacheDatabase{database.cache} - transaction := db.NewTransaction(database.model, dbName, &cacheDatabase, o.logger) + transaction := db.NewValidatingTransaction(database.model, dbName, &cacheDatabase, o.logger) results, _ := transaction.Transact(operations) _, err := ovsdb.CheckOperationResults(results, operations) diff --git a/database/transaction.go b/database/transaction.go index 28fb0029..588476e0 100644 --- a/database/transaction.go +++ b/database/transaction.go @@ -19,6 +19,7 @@ type Transaction struct { Model model.DatabaseModel DbName string Database Database + validating bool } func NewTransaction(model model.DatabaseModel, dbName string, database Database, logger *logr.Logger) Transaction { @@ -40,6 +41,15 @@ func NewTransaction(model model.DatabaseModel, dbName string, database Database, } } +// NewValidatingTransaction is a transaction only used for validating purposes. +// It generates updates that are valid to apply to our implementation of the +// database butthey might not be conformant to spec over the wire. +func NewValidatingTransaction(model model.DatabaseModel, dbName string, database Database, logger *logr.Logger) Transaction { + t := NewTransaction(model, dbName, database, logger) + t.validating = true + return t +} + func (t *Transaction) Transact(operations []ovsdb.Operation) ([]ovsdb.OperationResult, ovsdb.TableUpdates2) { results := make([]ovsdb.OperationResult, 0, len(operations)+1) updates := make(ovsdb.TableUpdates2) @@ -309,7 +319,15 @@ func (t *Transaction) Update(table string, where []ovsdb.Condition, row ovsdb.Ro panic(err) } - rowDelta := ovsdb.NewRow() + // A validating transaction does not calculate a Modify delta to avoid + // the performance cost, instead updates will be processed replacing Old + // with New + var rowDelta *ovsdb.Row + if !t.validating { + r := ovsdb.NewRow() + rowDelta = &r + } + for column, value := range row { colSchema := schema.Column(column) if colSchema == nil { @@ -349,15 +367,18 @@ func (t *Transaction) Update(table string, where []ovsdb.Condition, row ovsdb.Ro if err != nil { panic(err) } - // convert the native to an ovs value - // since the value in the RowUpdate hasn't been normalized - newValue, err := ovsdb.NativeToOvs(colSchema, native) - if err != nil { - panic(err) - } - diff := diff(oldValue, newValue) - if diff != nil { - rowDelta[column] = diff + + if rowDelta != nil { + // convert the native to an ovs value + // since the value in the RowUpdate hasn't been normalized + newValue, err := ovsdb.NativeToOvs(colSchema, native) + if err != nil { + panic(err) + } + diff := diff(oldValue, newValue) + if diff != nil { + (*rowDelta)[column] = diff + } } } @@ -367,7 +388,7 @@ func (t *Transaction) Update(table string, where []ovsdb.Condition, row ovsdb.Ro } tableUpdate.AddRowUpdate(uuid, &ovsdb.RowUpdate2{ - Modify: &rowDelta, + Modify: rowDelta, Old: &oldRow, New: &newRow, }) From 68aec7106ce83a035c54605474eb2223e25ef978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Mon, 20 Jun 2022 17:26:19 +0000 Subject: [PATCH 17/18] database: optimize transaction diff for mutates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Caamaño Ruiz --- database/transaction.go | 71 ++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/database/transaction.go b/database/transaction.go index 588476e0..67274f01 100644 --- a/database/transaction.go +++ b/database/transaction.go @@ -321,7 +321,7 @@ func (t *Transaction) Update(table string, where []ovsdb.Condition, row ovsdb.Ro // A validating transaction does not calculate a Modify delta to avoid // the performance cost, instead updates will be processed replacing Old - // with New + // with New var rowDelta *ovsdb.Row if !t.validating { r := ovsdb.NewRow() @@ -439,9 +439,7 @@ func (t *Transaction) Mutate(table string, where []ovsdb.Condition, mutations [] } rowDelta := ovsdb.NewRow() - mutateCols := make(map[string]struct{}) for _, mutation := range mutations { - mutateCols[mutation.Column] = struct{}{} column := schema.Column(mutation.Column) var nativeValue interface{} // Usually a mutation value is of the same type of the value being mutated @@ -465,36 +463,17 @@ func (t *Transaction) Mutate(table string, where []ovsdb.Condition, mutations [] if err != nil { panic(err) } - newValue, _ := ovsdb.Mutate(current, mutation.Mutator, nativeValue) + newValue, diff := ovsdb.Mutate(current, mutation.Mutator, nativeValue) if err := newInfo.SetField(mutation.Column, newValue); err != nil { panic(err) } - } - for changed := range mutateCols { - colSchema := schema.Column(changed) - oldValueNative, err := oldInfo.FieldByColumn(changed) - if err != nil { - panic(err) - } - newValueNative, err := newInfo.FieldByColumn(changed) - if err != nil { - panic(err) - } - - oldValue, err := ovsdb.NativeToOvs(colSchema, oldValueNative) - if err != nil { - panic(err) - } - - newValue, err := ovsdb.NativeToOvs(colSchema, newValueNative) - if err != nil { - panic(err) - } - - delta := diff(oldValue, newValue) - if delta != nil { - rowDelta[changed] = delta + if diff != nil { + diffValue, err := ovsdb.NativeToOvs(column, diff) + if err != nil { + panic(err) + } + rowDelta[mutation.Column] = updateDiff(rowDelta[mutation.Column], diffValue) } } @@ -732,3 +711,37 @@ func diff(a interface{}, b interface{}) interface{} { return b } } + +func updateDiff(a interface{}, b interface{}) interface{} { + switch a.(type) { + case ovsdb.OvsSet: + aSet := a.(ovsdb.OvsSet).GoSet + bSet := b.(ovsdb.OvsSet).GoSet + bMap := make(map[interface{}]bool, len(bSet)) + for _, bElem := range bSet { + bMap[bElem] = true + } + for _, aElem := range aSet { + if _, hasB := bMap[aElem]; hasB { + bMap[aElem] = false + } + } + for bElem, hasA := range bMap { + if !hasA { + aSet = append(aSet, bElem) + } + } + set := a.(ovsdb.OvsSet) + set.GoSet = aSet + return set + case ovsdb.OvsMap: + aMap := a.(ovsdb.OvsMap).GoMap + bMap := b.(ovsdb.OvsMap).GoMap + for k, v := range bMap { + aMap[k] = v + } + return a + default: + return b + } +} From 8678b458355d33b297a88d68065a9e80801e938c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Tue, 21 Jun 2022 09:28:16 +0000 Subject: [PATCH 18/18] ovsdb: pre-allocate OvsSet capacity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Caamaño Ruiz --- ovsdb/bindings.go | 19 ++++++++++--------- ovsdb/set.go | 7 +++++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/ovsdb/bindings.go b/ovsdb/bindings.go index 4c675858..7c10e922 100644 --- a/ovsdb/bindings.go +++ b/ovsdb/bindings.go @@ -257,23 +257,24 @@ func NativeToOvs(column *ColumnSchema, rawElem interface{}) (interface{}, error) case TypeSet: var ovsSet OvsSet if column.TypeObj.Key.Type == TypeUUID { - ovsSlice := []interface{}{} - if _, ok := rawElem.([]string); ok { - for _, v := range rawElem.([]string) { + var ovsSlice []interface{} + switch t := rawElem.(type) { + case []string: + ovsSlice = make([]interface{}, 0, len(t)) + for _, v := range t { uuid := UUID{GoUUID: v} ovsSlice = append(ovsSlice, uuid) } - } else if _, ok := rawElem.(*string); ok { - v := rawElem.(*string) - if v != nil { - uuid := UUID{GoUUID: *v} + case *string: + ovsSlice = make([]interface{}, 0, 1) + if t != nil { + uuid := UUID{GoUUID: *t} ovsSlice = append(ovsSlice, uuid) } - } else { + default: return nil, fmt.Errorf("uuid slice was neither []string or *string") } ovsSet = OvsSet{GoSet: ovsSlice} - } else { var err error ovsSet, err = NewOvsSet(rawElem) diff --git a/ovsdb/set.go b/ovsdb/set.go index d5fab1ec..057c346a 100644 --- a/ovsdb/set.go +++ b/ovsdb/set.go @@ -19,20 +19,21 @@ type OvsSet struct { // NewOvsSet creates a new OVSDB style set from a Go interface (object) func NewOvsSet(obj interface{}) (OvsSet, error) { - ovsSet := make([]interface{}, 0) var v reflect.Value if reflect.TypeOf(obj).Kind() == reflect.Ptr { v = reflect.ValueOf(obj).Elem() if v.Kind() == reflect.Invalid { // must be a nil pointer, so just return an empty set - return OvsSet{ovsSet}, nil + return OvsSet{[]interface{}{}}, nil } } else { v = reflect.ValueOf(obj) } + var ovsSet []interface{} switch v.Kind() { case reflect.Slice, reflect.Array: + ovsSet = make([]interface{}, 0, v.Len()) for i := 0; i < v.Len(); i++ { ovsSet = append(ovsSet, v.Index(i).Interface()) } @@ -40,9 +41,11 @@ func NewOvsSet(obj interface{}) (OvsSet, error) { reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.Bool: + ovsSet = make([]interface{}, 0, 1) ovsSet = append(ovsSet, v.Interface()) case reflect.Struct: if v.Type() == reflect.TypeOf(UUID{}) { + ovsSet = make([]interface{}, 0, 1) ovsSet = append(ovsSet, v.Interface()) } else { return OvsSet{}, fmt.Errorf("ovsset supports only go slice/string/numbers/uuid or pointers to those types")