diff --git a/README.md b/README.md index 491ad5ec..ce7e4632 100644 --- a/README.md +++ b/README.md @@ -32,16 +32,16 @@ To make the API use go idioms, the following mappings occur: 1. OVSDB Map = Map 1. OVSDB Scalar Type = Equivalent scalar Go type -A Open vSwitch Database is modeled using a DBModel which is a created by assigning table names to pointers to these structs: +A Open vSwitch Database is modeled using a ClientDBModel which is a created by assigning table names to pointers to these structs: - dbModel, _ := model.NewDBModel("OVN_Northbound", map[string]model.Model{ + dbModelReq, _ := model.NewClientDBModel("OVN_Northbound", map[string]model.Model{ "Logical_Switch": &MyLogicalSwitch{}, }) Finally, a client object can be created: - ovs, _ := client.Connect(context.Background(), dbModel, client.WithEndpoint("tcp:172.18.0.4:6641")) + ovs, _ := client.Connect(context.Background(), dbModelReq, client.WithEndpoint("tcp:172.18.0.4:6641")) client.MonitorAll(nil) // Only needed if you want to use the built-in cache @@ -219,7 +219,7 @@ It can be used as follows: Package name (default "ovsmodel") The result will be the definition of a Model per table defined in the ovsdb schema file. -Additionally, a function called `FullDatabaseModel()` that returns the `DBModel` is created for convenience. +Additionally, a function called `FullDatabaseModel()` that returns the `ClientDBModel` is created for convenience. Example: @@ -237,7 +237,7 @@ Run `go generate` go generate ./... -In your application, load the DBModel, connect to the server and start interacting with the database: +In your application, load the ClientDBModel, connect to the server and start interacting with the database: import ( "fmt" @@ -247,8 +247,8 @@ In your application, load the DBModel, connect to the server and start interacti ) func main() { - dbModel, _ := generated.FullDatabaseModel() - ovs, _ := client.Connect(context.Background(), dbModel, client.WithEndpoint("tcp:localhost:6641")) + dbModelReq, _ := generated.FullDatabaseModel() + ovs, _ := client.Connect(context.Background(), dbModelReq, client.WithEndpoint("tcp:localhost:6641")) ovs.MonitorAll() // Create a *LogicalRouter, as a pointer to a Model is required by the API diff --git a/cache/cache.go b/cache/cache.go index fd4d4bf4..ed9fad8e 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -89,7 +89,7 @@ func newIndex(columns ...string) index { // RowCache is a collections of Models hashed by UUID type RowCache struct { name string - schema ovsdb.TableSchema + dbModel *model.DatabaseModel dataType reflect.Type cache map[string]model.Model indexes columnToValue @@ -113,7 +113,7 @@ func (r *RowCache) RowByModel(m model.Model) model.Model { if reflect.TypeOf(m) != r.dataType { return nil } - info, _ := mapper.NewInfo(&r.schema, m) + info, _ := r.dbModel.NewModelInfo(m) uuid, err := info.FieldByColumn("_uuid") if err != nil { return nil @@ -143,11 +143,11 @@ func (r *RowCache) Create(uuid string, m model.Model, checkIndexes bool) error { if reflect.TypeOf(m) != r.dataType { return fmt.Errorf("expected data of type %s, but got %s", r.dataType.String(), reflect.TypeOf(m).String()) } - info, err := mapper.NewInfo(&r.schema, m) + info, err := r.dbModel.NewModelInfo(m) if err != nil { return err } - newIndexes := newColumnToValue(r.schema.Indexes) + newIndexes := newColumnToValue(r.dbModel.Schema().Table(r.name).Indexes) for index := range r.indexes { val, err := valueFromIndex(info, index) if err != nil { @@ -179,16 +179,17 @@ func (r *RowCache) Update(uuid string, m model.Model, checkIndexes bool) error { return NewErrCacheInconsistent(fmt.Sprintf("cannot update row %s as it does not exist in the cache", uuid)) } oldRow := model.Clone(r.cache[uuid]) - oldInfo, err := mapper.NewInfo(&r.schema, oldRow) + oldInfo, err := r.dbModel.NewModelInfo(oldRow) if err != nil { return err } - newInfo, err := mapper.NewInfo(&r.schema, m) + newInfo, err := r.dbModel.NewModelInfo(m) if err != nil { return err } - newIndexes := newColumnToValue(r.schema.Indexes) - oldIndexes := newColumnToValue(r.schema.Indexes) + indexes := r.dbModel.Schema().Table(r.name).Indexes + newIndexes := newColumnToValue(indexes) + oldIndexes := newColumnToValue(indexes) var errs []error for index := range r.indexes { var err error @@ -241,7 +242,7 @@ func (r *RowCache) Update(uuid string, m model.Model, checkIndexes bool) error { } func (r *RowCache) IndexExists(row model.Model) error { - info, err := mapper.NewInfo(&r.schema, row) + info, err := r.dbModel.NewModelInfo(row) if err != nil { return err } @@ -275,7 +276,7 @@ func (r *RowCache) Delete(uuid string) error { return NewErrCacheInconsistent(fmt.Sprintf("cannot delete row %s as it does not exist in the cache", uuid)) } oldRow := r.cache[uuid] - oldInfo, err := mapper.NewInfo(&r.schema, oldRow) + oldInfo, err := r.dbModel.NewModelInfo(oldRow) if err != nil { return err } @@ -303,6 +304,7 @@ func (r *RowCache) Rows() map[string]model.Model { func (r *RowCache) RowsByCondition(conditions []ovsdb.Condition) (map[string]model.Model, error) { results := make(map[string]model.Model) + schema := r.dbModel.Schema().Table(r.name) if len(conditions) == 0 { for uuid, row := range r.Rows() { results[uuid] = row @@ -328,7 +330,7 @@ func (r *RowCache) RowsByCondition(conditions []ovsdb.Condition) (map[string]mod } } else if index, err := r.Index(condition.Column); err != nil { for k, rowUUID := range index { - tSchema := r.schema.Columns[condition.Column] + tSchema := schema.Columns[condition.Column] nativeValue, err := ovsdb.OvsToNative(tSchema, condition.Value) if err != nil { return nil, err @@ -344,7 +346,7 @@ func (r *RowCache) RowsByCondition(conditions []ovsdb.Condition) (map[string]mod } } else { for uuid, row := range r.Rows() { - info, err := mapper.NewInfo(&r.schema, row) + info, err := r.dbModel.NewModelInfo(row) if err != nil { return nil, err } @@ -425,9 +427,7 @@ func (e *EventHandlerFuncs) OnDelete(table string, row model.Model) { type TableCache struct { cache map[string]*RowCache eventProcessor *eventProcessor - mapper *mapper.Mapper - dbModel *model.DBModel - schema *ovsdb.DatabaseSchema + dbModel *model.DatabaseModel updates chan ovsdb.TableUpdates updates2 chan ovsdb.TableUpdates2 errorChan chan error @@ -440,9 +440,9 @@ type TableCache struct { type Data map[string]map[string]model.Model // NewTableCache creates a new TableCache -func NewTableCache(schema *ovsdb.DatabaseSchema, dbModel *model.DBModel, data Data, logger *logr.Logger) (*TableCache, error) { - if schema == nil || dbModel == nil { - return nil, fmt.Errorf("tablecache without databasemodel cannot be populated") +func NewTableCache(dbModel *model.DatabaseModel, data Data, logger *logr.Logger) (*TableCache, error) { + if !dbModel.Valid() { + return nil, fmt.Errorf("tablecache without valid databasemodel cannot be populated") } if logger == nil { l := stdr.NewWithOptions(log.New(os.Stderr, "", log.LstdFlags), stdr.Options{LogCaller: stdr.All}).WithName("libovsdb/cache") @@ -454,11 +454,11 @@ func NewTableCache(schema *ovsdb.DatabaseSchema, dbModel *model.DBModel, data Da eventProcessor := newEventProcessor(bufferSize, logger) cache := make(map[string]*RowCache) tableTypes := dbModel.Types() - for name, tableSchema := range schema.Tables { - cache[name] = newRowCache(name, tableSchema, tableTypes[name]) + for name := range dbModel.Schema().Tables { + cache[name] = newRowCache(name, dbModel, tableTypes[name]) } for table, rowData := range data { - if _, ok := schema.Tables[table]; !ok { + if _, ok := dbModel.Schema().Tables[table]; !ok { return nil, fmt.Errorf("table %s is not in schema", table) } for uuid, row := range rowData { @@ -469,9 +469,7 @@ func NewTableCache(schema *ovsdb.DatabaseSchema, dbModel *model.DBModel, data Da } return &TableCache{ cache: cache, - schema: schema, eventProcessor: eventProcessor, - mapper: mapper.NewMapper(schema), dbModel: dbModel, mutex: sync.RWMutex{}, updates: make(chan ovsdb.TableUpdates, bufferSize), @@ -483,11 +481,11 @@ func NewTableCache(schema *ovsdb.DatabaseSchema, dbModel *model.DBModel, data Da // Mapper returns the mapper func (t *TableCache) Mapper() *mapper.Mapper { - return t.mapper + return t.dbModel.Mapper() } -// DBModel returns the DBModel -func (t *TableCache) DBModel() *model.DBModel { +// DatabaseModel returns the DatabaseModelRequest +func (t *TableCache) DatabaseModel() *model.DatabaseModel { return t.dbModel } @@ -671,13 +669,14 @@ func (t *TableCache) Populate2(tableUpdates ovsdb.TableUpdates2) error { } // Purge drops all data in the cache and reinitializes it using the -// provided schema -func (t *TableCache) Purge(schema *ovsdb.DatabaseSchema) { +// provided database model +func (t *TableCache) Purge(dbModel *model.DatabaseModel) { t.mutex.Lock() defer t.mutex.Unlock() + t.dbModel = dbModel tableTypes := t.dbModel.Types() - for name, tableSchema := range t.schema.Tables { - t.cache[name] = newRowCache(name, tableSchema, tableTypes[name]) + for name := range t.dbModel.Schema().Tables { + t.cache[name] = newRowCache(name, t.dbModel, tableTypes[name]) } } @@ -734,11 +733,11 @@ func (t *TableCache) processUpdates(stopCh <-chan struct{}) { // newRowCache creates a new row cache with the provided data // if the data is nil, and empty RowCache will be created -func newRowCache(name string, schema ovsdb.TableSchema, dataType reflect.Type) *RowCache { +func newRowCache(name string, dbModel *model.DatabaseModel, dataType reflect.Type) *RowCache { r := &RowCache{ name: name, - schema: schema, - indexes: newColumnToValue(schema.Indexes), + dbModel: dbModel, + indexes: newColumnToValue(dbModel.Schema().Table(name).Indexes), dataType: dataType, cache: make(map[string]model.Model), mutex: sync.RWMutex{}, @@ -846,7 +845,11 @@ func (e *eventProcessor) Run(stopCh <-chan struct{}) { // CreateModel creates a new Model instance based on the Row information func (t *TableCache) CreateModel(tableName string, row *ovsdb.Row, uuid string) (model.Model, error) { - table := t.mapper.Schema.Table(tableName) + if !t.dbModel.Valid() { + return nil, fmt.Errorf("database model not valid") + } + + table := t.dbModel.Schema().Table(tableName) if table == nil { return nil, fmt.Errorf("table %s not found", tableName) } @@ -854,18 +857,17 @@ func (t *TableCache) CreateModel(tableName string, row *ovsdb.Row, uuid string) if err != nil { return nil, err } - - err = t.mapper.GetRowData(tableName, row, model) + info, err := t.dbModel.NewModelInfo(model) + if err != nil { + return nil, err + } + err = t.dbModel.Mapper().GetRowData(row, info) if err != nil { return nil, err } if uuid != "" { - mapperInfo, err := mapper.NewInfo(table, model) - if err != nil { - return nil, err - } - if err := mapperInfo.SetField("_uuid", uuid); err != nil { + if err := info.SetField("_uuid", uuid); err != nil { return nil, err } } @@ -876,15 +878,18 @@ func (t *TableCache) CreateModel(tableName string, row *ovsdb.Row, uuid string) // ApplyModifications applies the contents of a RowUpdate2.Modify to a model // nolint: gocyclo func (t *TableCache) ApplyModifications(tableName string, base model.Model, update ovsdb.Row) error { - table := t.mapper.Schema.Table(tableName) + if !t.dbModel.Valid() { + return fmt.Errorf("database model not valid") + } + table := t.dbModel.Schema().Table(tableName) if table == nil { return fmt.Errorf("table %s not found", tableName) } - schema := t.schema.Table(tableName) + schema := t.dbModel.Schema().Table(tableName) if schema == nil { return fmt.Errorf("no schema for table %s", tableName) } - info, err := mapper.NewInfo(schema, base) + info, err := t.dbModel.NewModelInfo(base) if err != nil { return err } diff --git a/cache/cache_test.go b/cache/cache_test.go index d3624779..245cfdf7 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/go-logr/logr" - "github.com/ovn-org/libovsdb/mapper" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" "github.com/stretchr/testify/assert" @@ -82,10 +81,10 @@ func TestRowCache_Rows(t *testing.T) { func TestRowCacheCreate(t *testing.T) { var schema ovsdb.DatabaseSchema - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) require.Nil(t, err) err = json.Unmarshal([]byte(` - {"name": "TestDB", + {"name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo"]], @@ -105,7 +104,10 @@ func TestRowCacheCreate(t *testing.T) { testData := Data{ "Open_vSwitch": map[string]model.Model{"bar": &testModel{Foo: "bar"}}, } - tc, err := NewTableCache(&schema, db, testData, nil) + + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData, nil) require.Nil(t, err) tests := []struct { @@ -168,10 +170,10 @@ func TestRowCacheCreate(t *testing.T) { func TestRowCacheCreateMultiIndex(t *testing.T) { var schema ovsdb.DatabaseSchema - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) require.Nil(t, err) err = json.Unmarshal([]byte(` - {"name": "TestDB", + {"name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo", "bar"]], @@ -188,11 +190,12 @@ func TestRowCacheCreateMultiIndex(t *testing.T) { } `), &schema) require.Nil(t, err) - tSchema := schema.Table("Open_vSwitch") testData := Data{ "Open_vSwitch": map[string]model.Model{"bar": &testModel{Foo: "bar", Bar: "bar"}}, } - tc, err := NewTableCache(&schema, db, testData, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData, nil) require.Nil(t, err) tests := []struct { name string @@ -249,7 +252,7 @@ func TestRowCacheCreateMultiIndex(t *testing.T) { } } else { assert.Nil(t, err) - mapperInfo, err := mapper.NewInfo(tSchema, tt.model) + mapperInfo, err := dbModel.NewModelInfo(tt.model) require.Nil(t, err) h, err := valueFromIndex(mapperInfo, newIndex("foo", "bar")) require.Nil(t, err) @@ -261,10 +264,10 @@ func TestRowCacheCreateMultiIndex(t *testing.T) { func TestRowCacheUpdate(t *testing.T) { var schema ovsdb.DatabaseSchema - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) require.Nil(t, err) err = json.Unmarshal([]byte(` - {"name": "TestDB", + {"name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo"]], @@ -287,7 +290,9 @@ func TestRowCacheUpdate(t *testing.T) { "foobar": &testModel{Foo: "foobar"}, }, } - tc, err := NewTableCache(&schema, db, testData, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData, nil) require.Nil(t, err) tests := []struct { @@ -343,10 +348,10 @@ func TestRowCacheUpdate(t *testing.T) { func TestRowCacheUpdateMultiIndex(t *testing.T) { var schema ovsdb.DatabaseSchema - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) require.Nil(t, err) err = json.Unmarshal([]byte(` - {"name": "TestDB", + {"name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo", "bar"]], @@ -362,7 +367,6 @@ func TestRowCacheUpdateMultiIndex(t *testing.T) { } } `), &schema) - tSchema := schema.Table("Open_vSwitch") require.Nil(t, err) testData := Data{ "Open_vSwitch": map[string]model.Model{ @@ -370,7 +374,9 @@ func TestRowCacheUpdateMultiIndex(t *testing.T) { "foobar": &testModel{Foo: "foobar", Bar: "foobar"}, }, } - tc, err := NewTableCache(&schema, db, testData, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + assert.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData, nil) require.Nil(t, err) tests := []struct { @@ -413,7 +419,7 @@ func TestRowCacheUpdateMultiIndex(t *testing.T) { assert.Error(t, err) } else { assert.Nil(t, err) - mapperInfo, err := mapper.NewInfo(tSchema, tt.model) + mapperInfo, err := dbModel.NewModelInfo(tt.model) require.Nil(t, err) h, err := valueFromIndex(mapperInfo, newIndex("foo", "bar")) require.Nil(t, err) @@ -425,10 +431,10 @@ func TestRowCacheUpdateMultiIndex(t *testing.T) { func TestRowCacheDelete(t *testing.T) { var schema ovsdb.DatabaseSchema - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) require.Nil(t, err) err = json.Unmarshal([]byte(` - {"name": "TestDB", + {"name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo"]], @@ -450,7 +456,9 @@ func TestRowCacheDelete(t *testing.T) { "bar": &testModel{Foo: "bar"}, }, } - tc, err := NewTableCache(&schema, db, testData, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData, nil) require.Nil(t, err) tests := []struct { @@ -621,6 +629,29 @@ func TestEventHandlerFuncs_OnDelete(t *testing.T) { } func TestTableCacheTable(t *testing.T) { + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + assert.Nil(t, err) + var schema ovsdb.DatabaseSchema + err = json.Unmarshal([]byte(` + {"name": "Open_vSwitch", + "tables": { + "Open_vSwitch": { + "indexes": [["foo"]], + "columns": { + "foo": { + "type": "string" + }, + "bar": { + "type": "string" + } + } + } + } + } + `), &schema) + assert.Nil(t, err) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) tests := []struct { name string cache map[string]*RowCache @@ -629,15 +660,15 @@ func TestTableCacheTable(t *testing.T) { }{ { "returns nil for an empty table", - map[string]*RowCache{"bar": newRowCache("bar", ovsdb.TableSchema{}, nil)}, + map[string]*RowCache{"Open_vSwitch": newRowCache("Open_vSwitch", dbModel, nil)}, "foo", nil, }, { - "returns nil for an empty table", - map[string]*RowCache{"bar": newRowCache("bar", ovsdb.TableSchema{}, nil)}, - "bar", - newRowCache("bar", ovsdb.TableSchema{}, nil), + "returns valid row cache for valid table", + map[string]*RowCache{"Open_vSwitch": newRowCache("Open_vSwitch", dbModel, nil)}, + "Open_vSwitch", + newRowCache("Open_vSwitch", dbModel, nil), }, } for _, tt := range tests { @@ -652,6 +683,52 @@ func TestTableCacheTable(t *testing.T) { } func TestTableCacheTables(t *testing.T) { + db, err := model.NewClientDBModel("TestDB", + map[string]model.Model{ + "test1": &testModel{}, + "test2": &testModel{}, + "test3": &testModel{}}) + assert.Nil(t, err) + var schema ovsdb.DatabaseSchema + err = json.Unmarshal([]byte(` + {"name": "TestDB", + "tables": { + "test1": { + "columns": { + "foo": { + "type": "string" + }, + "bar": { + "type": "string" + } + } + }, + "test2": { + "columns": { + "foo": { + "type": "string" + }, + "bar": { + "type": "string" + } + } + }, + "test3": { + "columns": { + "foo": { + "type": "string" + }, + "bar": { + "type": "string" + } + } + } + } + } + `), &schema) + assert.Nil(t, err) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) tests := []struct { name string cache map[string]*RowCache @@ -660,9 +737,9 @@ func TestTableCacheTables(t *testing.T) { { "returns a table that exists", map[string]*RowCache{ - "test1": newRowCache("test1", ovsdb.TableSchema{}, nil), - "test2": newRowCache("test2", ovsdb.TableSchema{}, nil), - "test3": newRowCache("test3", ovsdb.TableSchema{}, nil), + "test1": newRowCache("test1", dbModel, nil), + "test2": newRowCache("test2", dbModel, nil), + "test3": newRowCache("test3", dbModel, nil), }, []string{"test1", "test2", "test3"}, }, @@ -685,11 +762,11 @@ func TestTableCacheTables(t *testing.T) { func TestTableCache_populate(t *testing.T) { t.Log("Create") - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) assert.Nil(t, err) var schema ovsdb.DatabaseSchema err = json.Unmarshal([]byte(` - {"name": "TestDB", + {"name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo"]], @@ -706,7 +783,9 @@ func TestTableCache_populate(t *testing.T) { } `), &schema) assert.Nil(t, err) - tc, err := NewTableCache(&schema, db, nil, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, nil, nil) assert.Nil(t, err) testRow := ovsdb.Row(map[string]interface{}{"_uuid": "test", "foo": "bar"}) @@ -753,11 +832,11 @@ func TestTableCache_populate(t *testing.T) { func TestTableCachePopulate(t *testing.T) { t.Log("Create") - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) assert.Nil(t, err) var schema ovsdb.DatabaseSchema err = json.Unmarshal([]byte(` - {"name": "TestDB", + {"name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo"]], @@ -774,7 +853,9 @@ func TestTableCachePopulate(t *testing.T) { } `), &schema) assert.Nil(t, err) - tc, err := NewTableCache(&schema, db, nil, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, nil, nil) assert.Nil(t, err) testRow := ovsdb.Row(map[string]interface{}{"_uuid": "test", "foo": "bar"}) @@ -820,11 +901,11 @@ func TestTableCachePopulate(t *testing.T) { } func TestTableCachePopulate2(t *testing.T) { - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) assert.Nil(t, err) var schema ovsdb.DatabaseSchema err = json.Unmarshal([]byte(` - {"name": "TestDB", + {"name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo"]], @@ -841,7 +922,9 @@ func TestTableCachePopulate2(t *testing.T) { } `), &schema) assert.Nil(t, err) - tc, err := NewTableCache(&schema, db, nil, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, nil, nil) assert.Nil(t, err) testRow := ovsdb.Row(map[string]interface{}{"_uuid": "test", "foo": "bar"}) @@ -943,11 +1026,11 @@ func TestIndex(t *testing.T) { Bar string `ovsdb:"bar"` Baz int `ovsdb:"baz"` } - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &indexTestModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &indexTestModel{}}) assert.Nil(t, err) var schema ovsdb.DatabaseSchema err = json.Unmarshal([]byte(` - {"name": "TestDB", + {"name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo"], ["bar","baz"]], @@ -967,7 +1050,9 @@ func TestIndex(t *testing.T) { } `), &schema) assert.Nil(t, err) - tc, err := NewTableCache(&schema, db, nil, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + assert.Empty(t, errs) + tc, err := NewTableCache(dbModel, nil, nil) assert.Nil(t, err) obj := &indexTestModel{ UUID: "test1", @@ -982,7 +1067,7 @@ func TestIndex(t *testing.T) { t.Run("Index by single column", func(t *testing.T) { idx, err := table.Index("foo") assert.Nil(t, err) - info, err := mapper.NewInfo(schema.Table("Open_vSwitch"), obj) + info, err := dbModel.NewModelInfo(obj) assert.Nil(t, err) v, err := valueFromIndex(info, newIndex("foo")) assert.Nil(t, err) @@ -994,7 +1079,7 @@ func TestIndex(t *testing.T) { obj2 := obj obj2.Foo = "wrong" assert.Nil(t, err) - info, err := mapper.NewInfo(schema.Table("Open_vSwitch"), obj2) + info, err := dbModel.NewModelInfo(obj2) assert.Nil(t, err) v, err := valueFromIndex(info, newIndex("foo")) assert.Nil(t, err) @@ -1012,7 +1097,7 @@ func TestIndex(t *testing.T) { t.Run("Index by multi-column", func(t *testing.T) { idx, err := table.Index("bar", "baz") assert.Nil(t, err) - info, err := mapper.NewInfo(schema.Table("Open_vSwitch"), obj) + info, err := dbModel.NewModelInfo(obj) assert.Nil(t, err) v, err := valueFromIndex(info, newIndex("bar", "baz")) assert.Nil(t, err) @@ -1023,7 +1108,7 @@ func TestIndex(t *testing.T) { assert.Nil(t, err) obj2 := obj obj2.Baz++ - info, err := mapper.NewInfo(schema.Table("Open_vSwitch"), obj) + info, err := dbModel.NewModelInfo(obj) assert.Nil(t, err) v, err := valueFromIndex(info, newIndex("bar", "baz")) assert.Nil(t, err) @@ -1038,10 +1123,10 @@ func TestIndex(t *testing.T) { func TestTableCacheRowByModelSingleIndex(t *testing.T) { var schema ovsdb.DatabaseSchema - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) require.Nil(t, err) err = json.Unmarshal([]byte(` - {"name": "TestDB", + {"name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo"]], @@ -1065,7 +1150,9 @@ func TestTableCacheRowByModelSingleIndex(t *testing.T) { "bar": &testModel{Foo: "bar", Bar: "bar"}, }, } - tc, err := NewTableCache(&schema, db, testData, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData, nil) require.NoError(t, err) t.Run("get foo by index", func(t *testing.T) { @@ -1087,10 +1174,10 @@ func TestTableCacheRowByModelSingleIndex(t *testing.T) { func TestTableCacheRowByModelTwoIndexes(t *testing.T) { var schema ovsdb.DatabaseSchema - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) require.Nil(t, err) err = json.Unmarshal([]byte(` - {"name": "TestDB", + {"name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo"], ["bar"]], @@ -1114,7 +1201,9 @@ func TestTableCacheRowByModelTwoIndexes(t *testing.T) { "bar": &testModel{Foo: "bar", Bar: "bar"}, }, } - tc, err := NewTableCache(&schema, db, testData, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData, nil) require.NoError(t, err) t.Run("get foo by Foo index", func(t *testing.T) { @@ -1138,10 +1227,10 @@ func TestTableCacheRowByModelTwoIndexes(t *testing.T) { func TestTableCacheRowByModelMultiIndex(t *testing.T) { var schema ovsdb.DatabaseSchema - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) require.Nil(t, err) err = json.Unmarshal([]byte(` - {"name": "TestDB", + {"name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo", "bar"]], @@ -1162,7 +1251,9 @@ func TestTableCacheRowByModelMultiIndex(t *testing.T) { testData := Data{ "Open_vSwitch": map[string]model.Model{"foo": myFoo, "bar": &testModel{Foo: "bar", Bar: "bar"}}, } - tc, err := NewTableCache(&schema, db, testData, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData, nil) require.NoError(t, err) t.Run("incomplete index", func(t *testing.T) { @@ -1184,6 +1275,7 @@ func TestTableCacheRowByModelMultiIndex(t *testing.T) { func TestTableCacheApplyModifications(t *testing.T) { type testDBModel struct { + UUID string `ovsdb:"_uuid"` Value string `ovsdb:"value"` Set []string `ovsdb:"set"` Map map[string]string `ovsdb:"map"` @@ -1287,12 +1379,12 @@ func TestTableCacheApplyModifications(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - db, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}}) + db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testDBModel{}}) assert.Nil(t, err) var schema ovsdb.DatabaseSchema err = json.Unmarshal([]byte(` { - "name": "TestDB", + "name": "Open_vSwitch", "tables": { "Open_vSwitch": { "indexes": [["foo"]], @@ -1308,7 +1400,9 @@ func TestTableCacheApplyModifications(t *testing.T) { } `), &schema) require.NoError(t, err) - tc, err := NewTableCache(&schema, db, nil, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, nil, nil) assert.Nil(t, err) original := model.Clone(tt.base).(*testDBModel) err = tc.ApplyModifications("Open_vSwitch", original, tt.update) diff --git a/client/api.go b/client/api.go index 079fb01a..db402594 100644 --- a/client/api.go +++ b/client/api.go @@ -8,7 +8,6 @@ import ( "github.com/go-logr/logr" "github.com/ovn-org/libovsdb/cache" - "github.com/ovn-org/libovsdb/mapper" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" ) @@ -187,13 +186,13 @@ func (a api) conditionFromModel(any bool, model model.Model, cond ...model.Condi } if len(cond) == 0 { - conditional, err = newEqualityConditional(a.cache.Mapper(), tableName, any, model) + conditional, err = newEqualityConditional(a.cache.DatabaseModel(), tableName, any, model) if err != nil { conditional = newErrorConditional(err) } } else { - conditional, err = newExplicitConditional(a.cache.Mapper(), tableName, any, model, cond...) + conditional, err = newExplicitConditional(a.cache.DatabaseModel(), tableName, any, model, cond...) if err != nil { conditional = newErrorConditional(err) } @@ -203,7 +202,7 @@ func (a api) conditionFromModel(any bool, model model.Model, cond ...model.Condi // Get is a generic Get function capable of returning (through a provided pointer) // a instance of any row in the cache. -// 'result' must be a pointer to an Model that exists in the DBModel +// 'result' must be a pointer to an Model that exists in the ClientDBModel // // The way the cache is searched depends on the fields already populated in 'result' // Any table index (including _uuid) will be used for comparison @@ -243,10 +242,8 @@ func (a api) Create(models ...model.Model) ([]ovsdb.Operation, error) { return nil, err } - table := a.cache.Mapper().Schema.Table(tableName) - // Read _uuid field, and use it as named-uuid - info, err := mapper.NewInfo(table, model) + info, err := a.cache.DatabaseModel().NewModelInfo(model) if err != nil { return nil, err } @@ -256,7 +253,7 @@ func (a api) Create(models ...model.Model) ([]ovsdb.Operation, error) { return nil, err } - row, err := a.cache.Mapper().NewRow(tableName, model) + row, err := a.cache.Mapper().NewRow(info) if err != nil { return nil, err } @@ -280,7 +277,7 @@ func (a api) Mutate(model model.Model, mutationObjs ...model.Mutation) ([]ovsdb. return nil, fmt.Errorf("at least one Mutation must be provided") } - tableName := a.cache.DBModel().FindTable(reflect.ValueOf(model).Type()) + tableName := a.cache.DatabaseModel().FindTable(reflect.ValueOf(model).Type()) if tableName == "" { return nil, fmt.Errorf("table not found for object") } @@ -294,7 +291,7 @@ func (a api) Mutate(model model.Model, mutationObjs ...model.Mutation) ([]ovsdb. return nil, err } - info, err := mapper.NewInfo(table, model) + info, err := a.cache.DatabaseModel().NewModelInfo(model) if err != nil { return nil, err } @@ -305,7 +302,7 @@ func (a api) Mutate(model model.Model, mutationObjs ...model.Mutation) ([]ovsdb. return nil, err } - mutation, err := a.cache.Mapper().NewMutation(tableName, model, col, mobj.Mutator, mobj.Value) + mutation, err := a.cache.Mapper().NewMutation(info, col, mobj.Mutator, mobj.Value) if err != nil { return nil, err } @@ -335,12 +332,12 @@ func (a api) Update(model model.Model, fields ...interface{}) ([]ovsdb.Operation return nil, err } tableSchema := a.cache.Mapper().Schema.Table(table) + info, err := a.cache.DatabaseModel().NewModelInfo(model) + if err != nil { + return nil, err + } if len(fields) > 0 { - info, err := mapper.NewInfo(tableSchema, model) - if err != nil { - return nil, err - } for _, f := range fields { colName, err := info.ColumnByPtr(f) if err != nil { @@ -357,7 +354,7 @@ func (a api) Update(model model.Model, fields ...interface{}) ([]ovsdb.Operation return nil, err } - row, err := a.cache.Mapper().NewRow(table, model, fields...) + row, err := a.cache.Mapper().NewRow(info, fields...) if err != nil { return nil, err } @@ -414,7 +411,7 @@ func (a api) getTableFromModel(m interface{}) (string, error) { if _, ok := m.(model.Model); !ok { return "", &ErrWrongType{reflect.TypeOf(m), "Type does not implement Model interface"} } - table := a.cache.DBModel().FindTable(reflect.TypeOf(m)) + table := a.cache.DatabaseModel().FindTable(reflect.TypeOf(m)) if table == "" { return "", &ErrWrongType{reflect.TypeOf(m), "Model not found in Database Model"} } @@ -439,7 +436,7 @@ func (a api) getTableFromFunc(predicate interface{}) (string, error) { fmt.Sprintf("Type %s does not implement Model interface", modelType.String())} } - table := a.cache.DBModel().FindTable(modelType) + table := a.cache.DatabaseModel().FindTable(modelType) if table == "" { return "", &ErrWrongType{predType, fmt.Sprintf("Model %s not found in Database Model", modelType.String())} diff --git a/client/api_test_model.go b/client/api_test_model.go index 776eb60b..6fafd152 100644 --- a/client/api_test_model.go +++ b/client/api_test_model.go @@ -157,9 +157,11 @@ func apiTestCache(t *testing.T, data map[string]map[string]model.Model) *cache.T var schema ovsdb.DatabaseSchema err := json.Unmarshal(apiTestSchema, &schema) assert.Nil(t, err) - db, err := model.NewDBModel("OVN_NorthBound", map[string]model.Model{"Logical_Switch": &testLogicalSwitch{}, "Logical_Switch_Port": &testLogicalSwitchPort{}}) + db, err := model.NewClientDBModel("OVN_Northbound", map[string]model.Model{"Logical_Switch": &testLogicalSwitch{}, "Logical_Switch_Port": &testLogicalSwitchPort{}}) assert.Nil(t, err) - cache, err := cache.NewTableCache(&schema, db, data, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + assert.Empty(t, errs) + cache, err := cache.NewTableCache(dbModel, data, nil) assert.Nil(t, err) return cache } diff --git a/client/client.go b/client/client.go index c323582f..65aa424f 100644 --- a/client/client.go +++ b/client/client.go @@ -16,7 +16,6 @@ import ( "github.com/cenkalti/rpc2" "github.com/cenkalti/rpc2/jsonrpc" "github.com/ovn-org/libovsdb/cache" - "github.com/ovn-org/libovsdb/mapper" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" "github.com/ovn-org/libovsdb/ovsdb/serverdb" @@ -85,8 +84,7 @@ type ovsdbClient struct { // database is everything needed to map between go types and an ovsdb Database type database struct { - model *model.DBModel - schema *ovsdb.DatabaseSchema + model *model.DatabaseModel schemaMutex sync.RWMutex cache *cache.TableCache cacheMutex sync.RWMutex @@ -102,17 +100,17 @@ type database struct { // database model. The client can be configured using one or more Option(s), // like WithTLSConfig. If no WithEndpoint option is supplied, the default of // unix:/var/run/openvswitch/ovsdb.sock is used -func NewOVSDBClient(databaseModel *model.DBModel, opts ...Option) (Client, error) { - return newOVSDBClient(databaseModel, opts...) +func NewOVSDBClient(clientDBModel *model.ClientDBModel, opts ...Option) (Client, error) { + return newOVSDBClient(clientDBModel, opts...) } // newOVSDBClient creates a new ovsdbClient -func newOVSDBClient(databaseModel *model.DBModel, opts ...Option) (*ovsdbClient, error) { +func newOVSDBClient(clientDBModel *model.ClientDBModel, opts ...Option) (*ovsdbClient, error) { ovs := &ovsdbClient{ - primaryDBName: databaseModel.Name(), + primaryDBName: clientDBModel.Name(), databases: map[string]*database{ - databaseModel.Name(): { - model: databaseModel, + clientDBModel.Name(): { + model: model.NewPartialDatabaseModel(clientDBModel), monitors: make(map[string]*Monitor), }, }, @@ -131,11 +129,11 @@ func newOVSDBClient(databaseModel *model.DBModel, opts ...Option) (*ovsdbClient, return nil, fmt.Errorf("could not initialize model _Server: %w", err) } ovs.databases[serverDB] = &database{ - model: sm, + model: model.NewPartialDatabaseModel(sm), monitors: make(map[string]*Monitor), } } - ovs.metrics.init(databaseModel.Name()) + ovs.metrics.init(clientDBModel.Name()) return ovs, nil } @@ -145,6 +143,10 @@ func newOVSDBClient(databaseModel *model.DBModel, opts ...Option) (*ovsdbClient, // The connection can be configured using one or more Option(s), like WithTLSConfig // If no WithEndpoint option is supplied, the default of unix:/var/run/openvswitch/ovsdb.sock is used func (o *ovsdbClient) Connect(ctx context.Context) error { + // add the "model" value to the structured logger + // to make it easier to tell between different DBs (e.g. ovn nbdb vs. sbdb) + l := o.options.logger.WithValues("model", o.primaryDB().model.Client().Name()) + o.options.logger = &l o.registerMetrics() if err := o.connect(ctx, false); err != nil { @@ -283,7 +285,9 @@ func (o *ovsdbClient) tryEndpoint(ctx context.Context, u *url.URL) error { return err } - errors := db.model.Validate(schema) + db.schemaMutex.Lock() + errors := db.model.SetSchema(schema) + db.schemaMutex.Unlock() if len(errors) > 0 { var combined []string for _, err := range errors { @@ -296,13 +300,9 @@ func (o *ovsdbClient) tryEndpoint(ctx context.Context, u *url.URL) error { return err } - db.schemaMutex.Lock() - db.schema = schema - db.schemaMutex.Unlock() - db.cacheMutex.Lock() if db.cache == nil { - db.cache, err = cache.NewTableCache(schema, db.model, nil, o.options.logger) + db.cache, err = cache.NewTableCache(db.model, nil, o.options.logger) if err != nil { db.cacheMutex.Unlock() o.rpcClient.Close() @@ -311,7 +311,7 @@ func (o *ovsdbClient) tryEndpoint(ctx context.Context, u *url.URL) error { } db.api = newAPI(db.cache, o.options.logger) } else { - db.cache.Purge(db.schema) + db.cache.Purge(db.model) } db.cacheMutex.Unlock() } @@ -421,7 +421,7 @@ func (o *ovsdbClient) Schema() *ovsdb.DatabaseSchema { db := o.primaryDB() db.schemaMutex.RLock() defer db.schemaMutex.RUnlock() - return db.schema + return db.model.Schema() } // Cache returns the TableCache that is populated from @@ -615,7 +615,7 @@ func (o *ovsdbClient) transact(ctx context.Context, dbName string, operation ... var reply []ovsdb.OperationResult db := o.databases[dbName] db.schemaMutex.RLock() - schema := o.databases[dbName].schema + schema := o.databases[dbName].model.Schema() db.schemaMutex.RUnlock() if schema == nil { return nil, fmt.Errorf("cannot transact to database %s: schema unknown", dbName) @@ -698,16 +698,24 @@ func (o *ovsdbClient) monitor(ctx context.Context, cookie MonitorCookie, reconne dbName := cookie.DatabaseName db := o.databases[dbName] db.schemaMutex.RLock() - mapper := mapper.NewMapper(db.schema) + mmapper := db.model.Mapper() db.schemaMutex.RUnlock() typeMap := o.databases[dbName].model.Types() requests := make(map[string]ovsdb.MonitorRequest) for _, o := range monitor.Tables { - m, ok := typeMap[o.Table] + _, ok := typeMap[o.Table] if !ok { return fmt.Errorf("type for table %s does not exist in model", o.Table) } - request, err := mapper.NewMonitorRequest(o.Table, m, o.Fields) + model, err := db.model.NewModel(o.Table) + if err != nil { + return err + } + info, err := db.model.NewModelInfo(model) + if err != nil { + return err + } + request, err := mmapper.NewMonitorRequest(info, o.Fields) if err != nil { return err } @@ -919,7 +927,7 @@ func (o *ovsdbClient) handleDisconnectNotification() { db.schemaMutex.Lock() defer db.schemaMutex.Unlock() - db.schema = nil + db.model.ClearSchema() db.monitorsMutex.Lock() defer db.monitorsMutex.Unlock() diff --git a/client/client_test.go b/client/client_test.go index 72716654..aef9cf89 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -87,7 +87,7 @@ type OpenvSwitch struct { SystemVersion *string `ovsdb:"system_version"` } -var defDB, _ = model.NewDBModel("Open_vSwitch", +var defDB, _ = model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &OpenvSwitch{}, "Bridge": &Bridge{}, @@ -491,7 +491,7 @@ func testOvsMap(t *testing.T, set interface{}) ovsdb.OvsMap { func updateBenchmark(ovs *ovsdbClient, updates []byte, b *testing.B) { for n := 0; n < b.N; n++ { - params := []json.RawMessage{[]byte(`["Open_vSwitch","v1"]`), updates} + params := []json.RawMessage{[]byte(`{"databaseName":"Open_vSwitch","id":"v1"}`), updates} var reply []interface{} err := ovs.update(params, &reply) if err != nil { @@ -559,12 +559,14 @@ func BenchmarkUpdate1(b *testing.B) { var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) require.NoError(b, err) - dbModel, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, "Open_vSwitch": &OpenvSwitch{}, }) require.NoError(b, err) - ovs.primaryDB().cache, err = cache.NewTableCache(&s, dbModel, nil, nil) + dbModel, errs := model.NewDatabaseModel(&s, clientDBModel) + require.Empty(b, errs) + ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, nil, nil) require.NoError(b, err) update := []byte(`{ "Open_vSwitch": { @@ -583,12 +585,14 @@ func BenchmarkUpdate2(b *testing.B) { var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) require.NoError(b, err) - dbModel, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, "Open_vSwitch": &OpenvSwitch{}, }) require.NoError(b, err) - ovs.primaryDB().cache, err = cache.NewTableCache(&s, dbModel, nil, nil) + dbModel, errs := model.NewDatabaseModel(&s, clientDBModel) + require.Empty(b, errs) + ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, nil, nil) require.NoError(b, err) update := []byte(`{ "Open_vSwitch": { @@ -608,12 +612,14 @@ func BenchmarkUpdate3(b *testing.B) { var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) require.NoError(b, err) - dbModel, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, "Open_vSwitch": &OpenvSwitch{}, }) require.NoError(b, err) - ovs.primaryDB().cache, err = cache.NewTableCache(&s, dbModel, nil, nil) + dbModel, errs := model.NewDatabaseModel(&s, clientDBModel) + require.Empty(b, errs) + ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, nil, nil) require.NoError(b, err) update := []byte(`{ "Open_vSwitch": { @@ -634,12 +640,14 @@ func BenchmarkUpdate5(b *testing.B) { var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) require.NoError(b, err) - dbModel, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, "Open_vSwitch": &OpenvSwitch{}, }) require.NoError(b, err) - ovs.primaryDB().cache, err = cache.NewTableCache(&s, dbModel, nil, nil) + dbModel, errs := model.NewDatabaseModel(&s, clientDBModel) + require.Empty(b, errs) + ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, nil, nil) require.NoError(b, err) update := []byte(`{ "Open_vSwitch": { @@ -662,12 +670,14 @@ func BenchmarkUpdate8(b *testing.B) { var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) require.NoError(b, err) - dbModel, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, "Open_vSwitch": &OpenvSwitch{}, }) require.NoError(b, err) - ovs.primaryDB().cache, err = cache.NewTableCache(&s, dbModel, nil, nil) + dbModel, errs := model.NewDatabaseModel(&s, clientDBModel) + require.Empty(b, errs) + ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, nil, nil) require.NoError(b, err) update := []byte(`{ "Open_vSwitch": { @@ -707,12 +717,14 @@ func TestUpdate(t *testing.T) { var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) require.NoError(t, err) - dbModel, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, "Open_vSwitch": &OpenvSwitch{}, }) require.NoError(t, err) - ovs.primaryDB().cache, err = cache.NewTableCache(&s, dbModel, nil, nil) + dbModel, errs := model.NewDatabaseModel(&s, clientDBModel) + require.Empty(t, errs) + ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, nil, nil) require.NoError(t, err) var reply []interface{} update := []byte(`{ @@ -736,7 +748,7 @@ func TestOperationWhenNotConnected(t *testing.T) { var s ovsdb.DatabaseSchema err = json.Unmarshal([]byte(schema), &s) require.NoError(t, err) - ovs.primaryDB().schema = &s + _ = ovs.primaryDB().model.SetSchema(&s) tests := []struct { name string diff --git a/client/condition.go b/client/condition.go index 99b32df2..1a745af7 100644 --- a/client/condition.go +++ b/client/condition.go @@ -26,14 +26,18 @@ type Conditional interface { // The conditions are based on the equality of the first available index. // The priority of indexes is: uuid, {schema index} type equalityConditional struct { - mapper *mapper.Mapper + dbModel *model.DatabaseModel tableName string - model model.Model + info *mapper.Info singleOp bool } func (c *equalityConditional) Matches(m model.Model) (bool, error) { - return c.mapper.EqualFields(c.tableName, c.model, m) + info, err := c.dbModel.NewModelInfo(m) + if err != nil { + return false, err + } + return c.dbModel.Mapper().EqualFields(c.info, info) } func (c *equalityConditional) Table() string { @@ -44,7 +48,7 @@ func (c *equalityConditional) Table() string { func (c *equalityConditional) Generate() ([][]ovsdb.Condition, error) { var result [][]ovsdb.Condition - conds, err := c.mapper.NewEqualityCondition(c.tableName, c.model) + conds, err := c.dbModel.Mapper().NewEqualityCondition(c.info) if err != nil { return nil, err } @@ -59,20 +63,24 @@ func (c *equalityConditional) Generate() ([][]ovsdb.Condition, error) { } // NewEqualityCondition creates a new equalityConditional -func newEqualityConditional(mapper *mapper.Mapper, table string, all bool, model model.Model, fields ...interface{}) (Conditional, error) { +func newEqualityConditional(dbModel *model.DatabaseModel, table string, all bool, model model.Model, fields ...interface{}) (Conditional, error) { + info, err := dbModel.NewModelInfo(model) + if err != nil { + return nil, err + } return &equalityConditional{ - mapper: mapper, + dbModel: dbModel, tableName: table, - model: model, + info: info, singleOp: all, }, nil } // explicitConditional generates conditions based on the provided Condition list type explicitConditional struct { - mapper *mapper.Mapper + dbModel *model.DatabaseModel tableName string - model model.Model + info *mapper.Info conditions []model.Condition singleOp bool } @@ -91,7 +99,7 @@ func (c *explicitConditional) Generate() ([][]ovsdb.Condition, error) { var conds []ovsdb.Condition for _, cond := range c.conditions { - ovsdbCond, err := c.mapper.NewCondition(c.tableName, c.model, cond.Field, cond.Function, cond.Value) + ovsdbCond, err := c.dbModel.Mapper().NewCondition(c.info, cond.Field, cond.Function, cond.Value) if err != nil { return nil, err } @@ -109,11 +117,15 @@ func (c *explicitConditional) Generate() ([][]ovsdb.Condition, error) { } // newIndexCondition creates a new equalityConditional -func newExplicitConditional(mapper *mapper.Mapper, table string, all bool, model model.Model, cond ...model.Condition) (Conditional, error) { +func newExplicitConditional(dbModel *model.DatabaseModel, table string, all bool, model model.Model, cond ...model.Condition) (Conditional, error) { + info, err := dbModel.NewModelInfo(model) + if err != nil { + return nil, err + } return &explicitConditional{ - mapper: mapper, + dbModel: dbModel, tableName: table, - model: model, + info: info, conditions: cond, singleOp: all, }, nil @@ -152,7 +164,11 @@ func (c *predicateConditional) Generate() ([][]ovsdb.Condition, error) { return nil, err } if match { - elemCond, err := c.cache.Mapper().NewEqualityCondition(c.tableName, row) + info, err := c.cache.DatabaseModel().NewModelInfo(row) + if err != nil { + return nil, err + } + elemCond, err := c.cache.Mapper().NewEqualityCondition(info) if err != nil { return nil, err } diff --git a/client/condition_test.go b/client/condition_test.go index 7ce98730..1c980a98 100644 --- a/client/condition_test.go +++ b/client/condition_test.go @@ -128,7 +128,7 @@ func TestEqualityConditional(t *testing.T) { } for _, tt := range test { t.Run(fmt.Sprintf("Equality Conditional: %s", tt.name), func(t *testing.T) { - cond, err := newEqualityConditional(tcache.Mapper(), "Logical_Switch_Port", tt.all, tt.model) + cond, err := newEqualityConditional(tcache.DatabaseModel(), "Logical_Switch_Port", tt.all, tt.model) assert.Nil(t, err) for model, shouldMatch := range tt.matches { matches, err := cond.Matches(model) @@ -431,7 +431,7 @@ func TestExplicitConditional(t *testing.T) { } for _, tt := range test { t.Run(fmt.Sprintf("Explicit Conditional: %s", tt.name), func(t *testing.T) { - cond, err := newExplicitConditional(tcache.Mapper(), "Logical_Switch_Port", tt.all, testObj, tt.args...) + cond, err := newExplicitConditional(tcache.DatabaseModel(), "Logical_Switch_Port", tt.all, testObj, tt.args...) assert.Nil(t, err) _, err = cond.Matches(testObj) assert.NotNilf(t, err, "Explicit conditions should fail to match on cache") diff --git a/client/doc.go b/client/doc.go index b5f39f30..7f7dae51 100644 --- a/client/doc.go +++ b/client/doc.go @@ -11,21 +11,21 @@ which column in the database. We refer to pointers to this structs as Models. Ex Config map[string]string `ovsdb:"other_config"` } -Based on these Models a Database Model (see DBModel type) is built to represent +Based on these Models a Database Model (see ClientDBModel type) is built to represent the entire OVSDB: - dbModel, _ := client.NewDBModel("OVN_Northbound", + clientDBModel, _ := client.NewClientDBModel("OVN_Northbound", map[string]client.Model{ "Logical_Switch": &MyLogicalSwitch{}, }) -The DBModel represents the entire Database (or the part of it we're interested in). +The ClientDBModel represents the entire Database (or the part of it we're interested in). Using it, the libovsdb.client package is able to properly encode and decode OVSDB messages and store them in Model instances. A client instance is created by simply specifying the connection information and the database model: - ovs, _ := client.Connect(context.Background(), dbModel) + ovs, _ := client.Connect(context.Background(), clientDBModel) Main API diff --git a/client/monitor.go b/client/monitor.go index d2fd7310..fe95d15f 100644 --- a/client/monitor.go +++ b/client/monitor.go @@ -72,7 +72,7 @@ func WithTable(m model.Model, fields ...interface{}) MonitorOption { return func(o *ovsdbClient, monitor *Monitor) error { tableName := o.primaryDB().model.FindTable(reflect.TypeOf(m)) if tableName == "" { - return fmt.Errorf("object of type %s is not part of the DBModel", reflect.TypeOf(m)) + return fmt.Errorf("object of type %s is not part of the ClientDBModel", reflect.TypeOf(m)) } tableMonitor := TableMonitor{ Table: tableName, @@ -87,7 +87,7 @@ func WithConditionalTable(m model.Model, condition model.Condition, fields ...in return func(o *ovsdbClient, monitor *Monitor) error { tableName := o.primaryDB().model.FindTable(reflect.TypeOf(m)) if tableName == "" { - return fmt.Errorf("object of type %s is not part of the DBModel", reflect.TypeOf(m)) + return fmt.Errorf("object of type %s is not part of the ClientDBModel", reflect.TypeOf(m)) } tableMonitor := TableMonitor{ Table: tableName, diff --git a/cmd/stress/stress.go b/cmd/stress/stress.go index bd7269fb..752c38b7 100644 --- a/cmd/stress/stress.go +++ b/cmd/stress/stress.go @@ -36,14 +36,14 @@ type ovsType struct { } var ( - cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file") - memprofile = flag.String("memoryprofile", "", "write memory profile to this file") - nins = flag.Int("inserts", 100, "the number of insertions to make to the database (per client)") - nclients = flag.Int("clients", 1, "the number of clients to use") - parallel = flag.Bool("parallel", false, "run clients in parallel") - verbose = flag.Bool("verbose", false, "Be verbose") - connection = flag.String("ovsdb", "unix:/var/run/openvswitch/db.sock", "OVSDB connection string") - dbModel *model.DBModel + cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file") + memprofile = flag.String("memoryprofile", "", "write memory profile to this file") + nins = flag.Int("inserts", 100, "the number of insertions to make to the database (per client)") + nclients = flag.Int("clients", 1, "the number of clients to use") + parallel = flag.Bool("parallel", false, "run clients in parallel") + verbose = flag.Bool("verbose", false, "Be verbose") + connection = flag.String("ovsdb", "unix:/var/run/openvswitch/db.sock", "OVSDB connection string") + clientDBModel *model.ClientDBModel ) type result struct { @@ -54,7 +54,7 @@ type result struct { } func cleanup(ctx context.Context) { - ovs, err := client.NewOVSDBClient(dbModel, client.WithEndpoint(*connection)) + ovs, err := client.NewOVSDBClient(clientDBModel, client.WithEndpoint(*connection)) if err != nil { log.Fatal(err) } @@ -96,7 +96,7 @@ func run(ctx context.Context, resultsChan chan result, wg *sync.WaitGroup) { ready := false var rootUUID string - ovs, err := client.NewOVSDBClient(dbModel, client.WithEndpoint(*connection)) + ovs, err := client.NewOVSDBClient(clientDBModel, client.WithEndpoint(*connection)) if err != nil { log.Fatal(err) } @@ -249,7 +249,7 @@ func main() { } var err error - dbModel, err = model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) + clientDBModel, err = model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) if err != nil { log.Fatal(err) } diff --git a/example/ovsdb-server/main.go b/example/ovsdb-server/main.go index b510beb0..7b4534eb 100644 --- a/example/ovsdb-server/main.go +++ b/example/ovsdb-server/main.go @@ -39,7 +39,7 @@ func main() { defer pprof.StopCPUProfile() } - dbModel, err := vswitchd.FullDatabaseModel() + clientDBModel, err := vswitchd.FullDatabaseModel() if err != nil { log.Fatal(err) } @@ -47,7 +47,7 @@ func main() { if err != nil { log.Fatal(err) } - path := filepath.Join(wd, "vswitchd", "vswitchd.ovsschema") + path := filepath.Join(wd, "vswitchd", "ovs.ovsschema") f, err := os.Open(path) if err != nil { log.Fatal(err) @@ -57,14 +57,15 @@ func main() { log.Fatal(err) } - ovsDB := server.NewInMemoryDatabase(map[string]*model.DBModel{ - schema.Name: dbModel, + ovsDB := server.NewInMemoryDatabase(map[string]*model.ClientDBModel{ + schema.Name: clientDBModel, }) - s, err := server.NewOvsdbServer(ovsDB, server.DatabaseModel{ - Model: dbModel, - Schema: schema, - }) + dbModel, errs := model.NewDatabaseModel(schema, clientDBModel) + if len(errs) > 0 { + log.Fatal(errs) + } + s, err := server.NewOvsdbServer(ovsDB, dbModel) if err != nil { log.Fatal(err) } @@ -80,7 +81,7 @@ func main() { }(s) time.Sleep(1 * time.Second) - c, err := client.NewOVSDBClient(dbModel, client.WithEndpoint(fmt.Sprintf("tcp::%d", *port))) + c, err := client.NewOVSDBClient(clientDBModel, client.WithEndpoint(fmt.Sprintf("tcp::%d", *port))) if err != nil { log.Fatal(err) } diff --git a/example/play_with_ovs/main.go b/example/play_with_ovs/main.go index 82d6c831..93a82534 100644 --- a/example/play_with_ovs/main.go +++ b/example/play_with_ovs/main.go @@ -97,13 +97,13 @@ func main() { quit = make(chan bool) update = make(chan model.Model) - dbModel, err := model.NewDBModel("Open_vSwitch", + clientDBModel, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{bridgeTable: &vswitchd.Bridge{}, ovsTable: &vswitchd.OpenvSwitch{}}) if err != nil { log.Fatal("Unable to create DB model ", err) } - ovs, err := client.NewOVSDBClient(dbModel, client.WithEndpoint(*connection)) + ovs, err := client.NewOVSDBClient(clientDBModel, client.WithEndpoint(*connection)) if err != nil { log.Fatal(err) } diff --git a/mapper/info.go b/mapper/info.go index 1ad981b6..7f620c9e 100644 --- a/mapper/info.go +++ b/mapper/info.go @@ -7,37 +7,42 @@ import ( "github.com/ovn-org/libovsdb/ovsdb" ) -// Info is a struct that handles the type map of an object -// The object must have exported tagged fields with the 'ovs' +// Info is a struct that wraps an object with its metadata type Info struct { // FieldName indexed by column - fields map[string]string - obj interface{} - table *ovsdb.TableSchema + Obj interface{} + Metadata *Metadata +} + +// Metadata represents the information needed to know how to map OVSDB columns into an objetss fields +type Metadata struct { + Fields map[string]string // Map of ColumnName -> FieldName + TableSchema *ovsdb.TableSchema // TableSchema associated + TableName string // Table name } // FieldByColumn returns the field value that corresponds to a column func (i *Info) FieldByColumn(column string) (interface{}, error) { - fieldName, ok := i.fields[column] + fieldName, ok := i.Metadata.Fields[column] if !ok { return nil, fmt.Errorf("FieldByColumn: column %s not found in orm info", column) } - return reflect.ValueOf(i.obj).Elem().FieldByName(fieldName).Interface(), nil + return reflect.ValueOf(i.Obj).Elem().FieldByName(fieldName).Interface(), nil } // FieldByColumn returns the field value that corresponds to a column func (i *Info) hasColumn(column string) bool { - _, ok := i.fields[column] + _, ok := i.Metadata.Fields[column] return ok } // SetField sets the field in the column to the specified value func (i *Info) SetField(column string, value interface{}) error { - fieldName, ok := i.fields[column] + fieldName, ok := i.Metadata.Fields[column] if !ok { return fmt.Errorf("SetField: column %s not found in orm info", column) } - fieldValue := reflect.ValueOf(i.obj).Elem().FieldByName(fieldName) + fieldValue := reflect.ValueOf(i.Obj).Elem().FieldByName(fieldName) if !fieldValue.Type().AssignableTo(reflect.TypeOf(value)) { return fmt.Errorf("column %s: native value %v (%s) is not assignable to field %s (%s)", @@ -53,12 +58,12 @@ func (i *Info) ColumnByPtr(fieldPtr interface{}) (string, error) { if fieldPtrVal.Kind() != reflect.Ptr { return "", ovsdb.NewErrWrongType("ColumnByPointer", "pointer to a field in the struct", fieldPtr) } - offset := fieldPtrVal.Pointer() - reflect.ValueOf(i.obj).Pointer() - objType := reflect.TypeOf(i.obj).Elem() + offset := fieldPtrVal.Pointer() - reflect.ValueOf(i.Obj).Pointer() + objType := reflect.TypeOf(i.Obj).Elem() for j := 0; j < objType.NumField(); j++ { if objType.Field(j).Offset == offset { column := objType.Field(j).Tag.Get("ovsdb") - if _, ok := i.fields[column]; !ok { + if _, ok := i.Metadata.Fields[column]; !ok { return "", fmt.Errorf("field does not have orm column information") } return column, nil @@ -74,7 +79,7 @@ func (i *Info) getValidIndexes() ([][]string, error) { var possibleIndexes [][]string possibleIndexes = append(possibleIndexes, []string{"_uuid"}) - possibleIndexes = append(possibleIndexes, i.table.Indexes...) + possibleIndexes = append(possibleIndexes, i.Metadata.TableSchema.Indexes...) // Iterate through indexes and validate them OUTER: @@ -83,7 +88,7 @@ OUTER: if !i.hasColumn(col) { continue OUTER } - columnSchema := i.table.Column(col) + columnSchema := i.Metadata.TableSchema.Column(col) if columnSchema == nil { continue OUTER } @@ -101,7 +106,7 @@ OUTER: } // NewInfo creates a MapperInfo structure around an object based on a given table schema -func NewInfo(table *ovsdb.TableSchema, obj interface{}) (*Info, error) { +func NewInfo(tableName string, table *ovsdb.TableSchema, obj interface{}) (*Info, error) { objPtrVal := reflect.ValueOf(obj) if objPtrVal.Type().Kind() != reflect.Ptr { return nil, ovsdb.NewErrWrongType("NewMapperInfo", "pointer to a struct", obj) @@ -146,8 +151,11 @@ func NewInfo(table *ovsdb.TableSchema, obj interface{}) (*Info, error) { } return &Info{ - fields: fields, - obj: obj, - table: table, + Obj: obj, + Metadata: &Metadata{ + Fields: fields, + TableSchema: table, + TableName: tableName, + }, }, nil } diff --git a/mapper/info_test.go b/mapper/info_test.go index b50633f3..cf5ad960 100644 --- a/mapper/info_test.go +++ b/mapper/info_test.go @@ -58,7 +58,7 @@ func TestNewMapperInfo(t *testing.T) { err := json.Unmarshal(tt.table, &table) assert.Nil(t, err) - info, err := NewInfo(&table, tt.obj) + info, err := NewInfo("Test", &table, tt.obj) if tt.err { assert.NotNil(t, err) } else { @@ -67,6 +67,7 @@ func TestNewMapperInfo(t *testing.T) { for _, col := range tt.expectedCols { assert.Truef(t, info.hasColumn(col), "Expected column should be present in Mapper Info") } + assert.Equal(t, "Test", info.Metadata.TableName) }) } @@ -141,7 +142,7 @@ func TestMapperInfoSet(t *testing.T) { err := json.Unmarshal(tt.table, &table) assert.Nil(t, err) - info, err := NewInfo(&table, tt.obj) + info, err := NewInfo("Test", &table, tt.obj) assert.Nil(t, err) err = info.SetField(tt.column, tt.field) @@ -222,7 +223,7 @@ func TestMapperInfoColByPtr(t *testing.T) { err := json.Unmarshal(tt.table, &table) assert.Nil(t, err) - info, err := NewInfo(&table, tt.obj) + info, err := NewInfo("Test", &table, tt.obj) assert.Nil(t, err) col, err := info.ColumnByPtr(tt.field) @@ -354,7 +355,7 @@ func TestOrmGetIndex(t *testing.T) { } for _, tt := range tests { t.Run(fmt.Sprintf("GetValidIndexes_%s", tt.name), func(t *testing.T) { - info, err := NewInfo(&table, tt.obj) + info, err := NewInfo("Test", &table, tt.obj) assert.Nil(t, err) indexes, err := info.getValidIndexes() diff --git a/mapper/mapper.go b/mapper/mapper.go index 2a66680c..f0677eab 100644 --- a/mapper/mapper.go +++ b/mapper/mapper.go @@ -36,21 +36,6 @@ func (e *ErrMapper) Error() string { e.objType, e.field, e.fieldType, e.fieldTag, e.reason) } -// ErrNoTable describes a error in the provided table information -type ErrNoTable struct { - table string -} - -func (e *ErrNoTable) Error() string { - return fmt.Sprintf("Table not found: %s", e.table) -} - -func newErrNoTable(table string) error { - return &ErrNoTable{ - table: table, - } -} - // NewMapper returns a new mapper func NewMapper(schema *ovsdb.DatabaseSchema) *Mapper { return &Mapper{ @@ -60,29 +45,19 @@ func NewMapper(schema *ovsdb.DatabaseSchema) *Mapper { // GetRowData transforms a Row to a struct based on its tags // The result object must be given as pointer to an object with the right tags -func (m Mapper) GetRowData(tableName string, row *ovsdb.Row, result interface{}) error { +func (m Mapper) GetRowData(row *ovsdb.Row, result *Info) error { if row == nil { return nil } - return m.getData(tableName, *row, result) + return m.getData(*row, result) } // getData transforms a map[string]interface{} containing OvS types (e.g: a ResultRow // has this format) to orm struct // The result object must be given as pointer to an object with the right tags -func (m Mapper) getData(tableName string, ovsData ovsdb.Row, result interface{}) error { - table := m.Schema.Table(tableName) - if table == nil { - return newErrNoTable(tableName) - } - - mapperInfo, err := NewInfo(table, result) - if err != nil { - return err - } - - for name, column := range table.Columns { - if !mapperInfo.hasColumn(name) { +func (m Mapper) getData(ovsData ovsdb.Row, result *Info) error { + for name, column := range result.Metadata.TableSchema.Columns { + if !result.hasColumn(name) { // If provided struct does not have a field to hold this value, skip it continue } @@ -96,10 +71,10 @@ func (m Mapper) getData(tableName string, ovsData ovsdb.Row, result interface{}) nativeElem, err := ovsdb.OvsToNative(column, ovsElem) if err != nil { return fmt.Errorf("table %s, column %s: failed to extract native element: %s", - tableName, name, err.Error()) + result.Metadata.TableName, name, err.Error()) } - if err := mapperInfo.SetField(name, nativeElem); err != nil { + if err := result.SetField(name, nativeElem); err != nil { return err } } @@ -109,24 +84,15 @@ func (m Mapper) getData(tableName string, ovsData ovsdb.Row, result interface{}) // NewRow transforms an orm struct to a map[string] interface{} that can be used as libovsdb.Row // 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(tableName string, data interface{}, fields ...interface{}) (ovsdb.Row, error) { - table := m.Schema.Table(tableName) - if table == nil { - return nil, newErrNoTable(tableName) - } - mapperInfo, err := NewInfo(table, data) - if err != nil { - return nil, err - } - +func (m Mapper) NewRow(data *Info, fields ...interface{}) (ovsdb.Row, error) { columns := make(map[string]*ovsdb.ColumnSchema) - for k, v := range table.Columns { + for k, v := range data.Metadata.TableSchema.Columns { columns[k] = v } columns["_uuid"] = &ovsdb.UUIDColumn ovsRow := make(map[string]interface{}, len(columns)) for name, column := range columns { - nativeElem, err := mapperInfo.FieldByColumn(name) + nativeElem, err := data.FieldByColumn(name) if err != nil { // If provided struct does not have a field to hold this value, skip it continue @@ -136,7 +102,7 @@ func (m Mapper) NewRow(tableName string, data interface{}, fields ...interface{} if len(fields) > 0 { found := false for _, f := range fields { - col, err := mapperInfo.ColumnByPtr(f) + col, err := data.ColumnByPtr(f) if err != nil { return nil, err } @@ -154,7 +120,7 @@ func (m Mapper) NewRow(tableName string, data interface{}, fields ...interface{} } ovsElem, err := ovsdb.NativeToOvs(column, nativeElem) if err != nil { - return nil, fmt.Errorf("table %s, column %s: failed to generate ovs element. %s", tableName, name, err.Error()) + return nil, fmt.Errorf("table %s, column %s: failed to generate ovs element. %s", data.Metadata.TableName, name, err.Error()) } ovsRow[name] = ovsElem } @@ -169,25 +135,15 @@ func (m Mapper) NewRow(tableName string, data interface{}, fields ...interface{} // object has valid data. The order in which they are traversed matches the order defined // in the schema. // By `valid data` we mean non-default data. -func (m Mapper) NewEqualityCondition(tableName string, data interface{}, fields ...interface{}) ([]ovsdb.Condition, error) { +func (m Mapper) NewEqualityCondition(data *Info, fields ...interface{}) ([]ovsdb.Condition, error) { var conditions []ovsdb.Condition var condIndex [][]string - table := m.Schema.Table(tableName) - if table == nil { - return nil, newErrNoTable(tableName) - } - - mapperInfo, err := NewInfo(table, data) - if err != nil { - return nil, err - } - // If index is provided, use it. If not, obtain the valid indexes from the mapper info if len(fields) > 0 { providedIndex := []string{} for i := range fields { - if col, err := mapperInfo.ColumnByPtr(fields[i]); err == nil { + if col, err := data.ColumnByPtr(fields[i]); err == nil { providedIndex = append(providedIndex, col) } else { return nil, err @@ -196,7 +152,7 @@ func (m Mapper) NewEqualityCondition(tableName string, data interface{}, fields condIndex = append(condIndex, providedIndex) } else { var err error - condIndex, err = mapperInfo.getValidIndexes() + condIndex, err = data.getValidIndexes() if err != nil { return nil, err } @@ -208,12 +164,12 @@ func (m Mapper) NewEqualityCondition(tableName string, data interface{}, fields // Pick the first valid index for _, col := range condIndex[0] { - field, err := mapperInfo.FieldByColumn(col) + field, err := data.FieldByColumn(col) if err != nil { return nil, err } - column := table.Column(col) + column := data.Metadata.TableSchema.Column(col) if column == nil { return nil, fmt.Errorf("column %s not found", col) } @@ -229,47 +185,27 @@ func (m Mapper) NewEqualityCondition(tableName string, data interface{}, fields // EqualFields compares two mapped objects. // The indexes to use for comparison are, the _uuid, the table indexes and the columns that correspond // to the mapped fields pointed to by 'fields'. They must be pointers to fields on the first mapped element (i.e: one) -func (m Mapper) EqualFields(tableName string, one, other interface{}, fields ...interface{}) (bool, error) { +func (m Mapper) EqualFields(one, other *Info, fields ...interface{}) (bool, error) { indexes := []string{} - - table := m.Schema.Table(tableName) - if table == nil { - return false, newErrNoTable(tableName) - } - - info, err := NewInfo(table, one) - if err != nil { - return false, err - } for _, f := range fields { - col, err := info.ColumnByPtr(f) + col, err := one.ColumnByPtr(f) if err != nil { return false, err } indexes = append(indexes, col) } - return m.equalIndexes(table, one, other, indexes...) + return m.equalIndexes(one, other, indexes...) } // NewCondition returns a ovsdb.Condition based on the model -func (m Mapper) NewCondition(tableName string, data interface{}, field interface{}, function ovsdb.ConditionFunction, value interface{}) (*ovsdb.Condition, error) { - table := m.Schema.Table(tableName) - if table == nil { - return nil, newErrNoTable(tableName) - } - - info, err := NewInfo(table, data) - if err != nil { - return nil, err - } - - column, err := info.ColumnByPtr(field) +func (m Mapper) NewCondition(data *Info, field interface{}, function ovsdb.ConditionFunction, value interface{}) (*ovsdb.Condition, error) { + column, err := data.ColumnByPtr(field) if err != nil { return nil, err } // Check that the condition is valid - columnSchema := table.Column(column) + columnSchema := data.Metadata.TableSchema.Column(column) if columnSchema == nil { return nil, fmt.Errorf("column %s not found", column) } @@ -290,23 +226,13 @@ func (m Mapper) NewCondition(tableName string, data interface{}, field interface // NewMutation creates a RFC7047 mutation object based on an ORM object and the mutation fields (in native format) // It takes care of field validation against the column type -func (m Mapper) NewMutation(tableName string, data interface{}, column string, mutator ovsdb.Mutator, value interface{}) (*ovsdb.Mutation, error) { - table := m.Schema.Table(tableName) - if table == nil { - return nil, newErrNoTable(tableName) - } - - mapperInfo, err := NewInfo(table, data) - if err != nil { - return nil, err - } - +func (m Mapper) NewMutation(data *Info, column string, mutator ovsdb.Mutator, value interface{}) (*ovsdb.Mutation, error) { // Check the column exists in the object - if !mapperInfo.hasColumn(column) { + if !data.hasColumn(column) { return nil, fmt.Errorf("mutation contains column %s that does not exist in object %v", column, data) } // Check that the mutation is valid - columnSchema := table.Column(column) + columnSchema := data.Metadata.TableSchema.Column(column) if columnSchema == nil { return nil, fmt.Errorf("column %s not found", column) } @@ -315,6 +241,7 @@ func (m Mapper) NewMutation(tableName string, data interface{}, column string, m } var ovsValue interface{} + var err error // Usually a mutation value is of the same type of the value being mutated // except for delete mutation of maps where it can also be a list of same type of // keys (rfc7047 5.1). Handle this special case here. @@ -341,24 +268,15 @@ func (m Mapper) NewMutation(tableName string, data interface{}, column string, m // For any of the indexes defined in the Table Schema, the values all of its columns are simultaneously equal // (as per RFC7047) // The values of all of the optional indexes passed as variadic parameter to this function are equal. -func (m Mapper) equalIndexes(table *ovsdb.TableSchema, one, other interface{}, indexes ...string) (bool, error) { +func (m Mapper) equalIndexes(one, other *Info, indexes ...string) (bool, error) { match := false - oneMapperInfo, err := NewInfo(table, one) - if err != nil { - return false, err - } - otherMapperInfo, err := NewInfo(table, other) - if err != nil { - return false, err - } - - oneIndexes, err := oneMapperInfo.getValidIndexes() + oneIndexes, err := one.getValidIndexes() if err != nil { return false, err } - otherIndexes, err := otherMapperInfo.getValidIndexes() + otherIndexes, err := other.getValidIndexes() if err != nil { return false, err } @@ -371,14 +289,14 @@ func (m Mapper) equalIndexes(table *ovsdb.TableSchema, one, other interface{}, i if reflect.DeepEqual(ridx, lidx) { // All columns in an index must be simultaneously equal for _, col := range lidx { - if !oneMapperInfo.hasColumn(col) || !otherMapperInfo.hasColumn(col) { + if !one.hasColumn(col) || !other.hasColumn(col) { break } - lfield, err := oneMapperInfo.FieldByColumn(col) + lfield, err := one.FieldByColumn(col) if err != nil { return false, err } - rfield, err := otherMapperInfo.FieldByColumn(col) + rfield, err := other.FieldByColumn(col) if err != nil { return false, err } @@ -401,23 +319,18 @@ func (m Mapper) equalIndexes(table *ovsdb.TableSchema, one, other interface{}, i // NewMonitorRequest returns a monitor request for the provided tableName // If fields is provided, the request will be constrained to the provided columns // If no fields are provided, all columns will be used -func (m *Mapper) NewMonitorRequest(tableName string, data interface{}, fields []interface{}) (*ovsdb.MonitorRequest, error) { +func (m *Mapper) NewMonitorRequest(data *Info, fields []interface{}) (*ovsdb.MonitorRequest, error) { var columns []string - schema := m.Schema.Tables[tableName] - info, err := NewInfo(&schema, data) - if err != nil { - return nil, err - } if len(fields) > 0 { for _, f := range fields { - column, err := info.ColumnByPtr(f) + column, err := data.ColumnByPtr(f) if err != nil { return nil, err } columns = append(columns, column) } } else { - for c := range info.table.Columns { + for c := range data.Metadata.TableSchema.Columns { columns = append(columns, c) } } diff --git a/mapper/mapper_test.go b/mapper/mapper_test.go index 5cb50e8b..7cb3aa80 100644 --- a/mapper/mapper_test.go +++ b/mapper/mapper_test.go @@ -226,7 +226,11 @@ func TestMapperGetData(t *testing.T) { test := ormTestType{ NonTagged: "something", } - err := mapper.GetRowData("TestTable", &ovsRow, &test) + testInfo, err := NewInfo("TestTable", schema.Table("TestTable"), &test) + assert.NoError(t, err) + + err = mapper.GetRowData(&ovsRow, testInfo) + assert.NoError(t, err) /*End code under test*/ if err != nil { @@ -341,7 +345,9 @@ func TestMapperNewRow(t *testing.T) { for _, test := range tests { t.Run(fmt.Sprintf("NewRow: %s", test.name), func(t *testing.T) { mapper := NewMapper(&schema) - row, err := mapper.NewRow("TestTable", test.objInput) + info, err := NewInfo("TestTable", schema.Table("TestTable"), test.objInput) + assert.NoError(t, err) + row, err := mapper.NewRow(info) if test.shoulderr { assert.NotNil(t, err) } else { @@ -432,7 +438,9 @@ func TestMapperNewRowFields(t *testing.T) { testObj.MyFloat = 0 test.prepare(&testObj) - row, err := mapper.NewRow("TestTable", &testObj, test.fields...) + info, err := NewInfo("TestTable", schema.Table("TestTable"), &testObj) + assert.NoError(t, err) + row, err := mapper.NewRow(info, test.fields...) if test.err { assert.NotNil(t, err) } else { @@ -584,7 +592,10 @@ func TestMapperCondition(t *testing.T) { for _, tt := range tests { t.Run(fmt.Sprintf("newEqualityCondition_%s", tt.name), func(t *testing.T) { tt.prepare(&testObj) - conds, err := mapper.NewEqualityCondition("TestTable", &testObj, tt.index...) + info, err := NewInfo("TestTable", schema.Table("TestTable"), &testObj) + assert.NoError(t, err) + + conds, err := mapper.NewEqualityCondition(info, tt.index...) if tt.err { if err == nil { t.Errorf("expected an error but got none") @@ -835,7 +846,11 @@ func TestMapperEqualIndexes(t *testing.T) { } for _, test := range tests { t.Run(fmt.Sprintf("Equal %s", test.name), func(t *testing.T) { - eq, err := mapper.equalIndexes(mapper.Schema.Table("TestTable"), &test.obj1, &test.obj2, test.indexes...) + info1, err := NewInfo("TestTable", schema.Table("TestTable"), &test.obj1) + assert.NoError(t, err) + info2, err := NewInfo("TestTable", schema.Table("TestTable"), &test.obj2) + assert.NoError(t, err) + eq, err := mapper.equalIndexes(info1, info2, test.indexes...) assert.Nil(t, err) assert.Equalf(t, test.expected, eq, "equal value should match expected") }) @@ -858,11 +873,15 @@ func TestMapperEqualIndexes(t *testing.T) { Int1: 42, Int2: 25, } - eq, err := mapper.EqualFields("TestTable", &obj1, &obj2, &obj1.Int1, &obj1.Int2) + info1, err := NewInfo("TestTable", schema.Table("TestTable"), &obj1) + assert.NoError(t, err) + info2, err := NewInfo("TestTable", schema.Table("TestTable"), &obj2) + assert.NoError(t, err) + eq, err := mapper.EqualFields(info1, info2, &obj1.Int1, &obj1.Int2) assert.Nil(t, err) assert.True(t, eq) // Using pointers to second value is not supported - _, err = mapper.EqualFields("TestTable", &obj1, &obj2, &obj2.Int1, &obj2.Int2) + _, err = mapper.EqualFields(info1, info2, &obj2.Int1, &obj2.Int2) assert.NotNil(t, err) } @@ -1012,7 +1031,10 @@ func TestMapperMutation(t *testing.T) { } for _, test := range tests { t.Run(fmt.Sprintf("newMutation%s", test.name), func(t *testing.T) { - mutation, err := mapper.NewMutation("TestTable", &test.obj, test.column, test.mutator, test.value) + info, err := NewInfo("TestTable", schema.Table("TestTable"), &test.obj) + assert.NoError(t, err) + + mutation, err := mapper.NewMutation(info, test.column, test.mutator, test.value) if test.err { if err == nil { t.Errorf("expected an error but got none") @@ -1097,10 +1119,12 @@ func TestNewMonitorRequest(t *testing.T) { require.NoError(t, err) mapper := NewMapper(&schema) testTable := &testType{} - mr, err := mapper.NewMonitorRequest("TestTable", testTable, nil) + info, err := NewInfo("TestTable", schema.Table("TestTable"), testTable) + assert.NoError(t, err) + mr, err := mapper.NewMonitorRequest(info, nil) require.NoError(t, err) assert.ElementsMatch(t, mr.Columns, []string{"name", "config", "composed_1", "composed_2", "int1", "int2"}) - mr2, err := mapper.NewMonitorRequest("TestTable", testTable, []interface{}{&testTable.Int1, &testTable.MyName}) + mr2, err := mapper.NewMonitorRequest(info, []interface{}{&testTable.Int1, &testTable.MyName}) require.NoError(t, err) assert.ElementsMatch(t, mr2.Columns, []string{"int1", "name"}) } diff --git a/model/client.go b/model/client.go new file mode 100644 index 00000000..80ffc669 --- /dev/null +++ b/model/client.go @@ -0,0 +1,84 @@ +package model + +import ( + "fmt" + "reflect" + + "github.com/ovn-org/libovsdb/mapper" + "github.com/ovn-org/libovsdb/ovsdb" +) + +// ClientDBModel contains the client information needed to build a DatabaseModel +type ClientDBModel struct { + name string + types map[string]reflect.Type +} + +// NewModel returns a new instance of a model from a specific string +func (db ClientDBModel) newModel(table string) (Model, error) { + mtype, ok := db.types[table] + if !ok { + return nil, fmt.Errorf("table %s not found in database model", string(table)) + } + model := reflect.New(mtype.Elem()) + return model.Interface().(Model), nil +} + +// Name returns the database name +func (db ClientDBModel) Name() string { + return db.name +} + +// Validate validates the DatabaseModel against the input schema +// Returns all the errors detected +func (db ClientDBModel) validate(schema *ovsdb.DatabaseSchema) []error { + var errors []error + if db.name != schema.Name { + errors = append(errors, fmt.Errorf("database model name (%s) does not match schema (%s)", + db.name, schema.Name)) + } + + for tableName := range db.types { + tableSchema := schema.Table(tableName) + if tableSchema == nil { + errors = append(errors, fmt.Errorf("database model contains a model for table %s that does not exist in schema", tableName)) + continue + } + model, err := db.newModel(tableName) + if err != nil { + errors = append(errors, err) + continue + } + if _, err := mapper.NewInfo(tableName, tableSchema, model); err != nil { + errors = append(errors, err) + } + } + return errors +} + +// NewClientDBModel constructs a ClientDBModel based on a database name and dictionary of models indexed by table name +func NewClientDBModel(name string, models map[string]Model) (*ClientDBModel, error) { + types := make(map[string]reflect.Type, len(models)) + for table, model := range models { + modelType := reflect.TypeOf(model) + if modelType.Kind() != reflect.Ptr || modelType.Elem().Kind() != reflect.Struct { + return nil, fmt.Errorf("model is expected to be a pointer to struct") + } + hasUUID := false + for i := 0; i < modelType.Elem().NumField(); i++ { + if field := modelType.Elem().Field(i); field.Tag.Get("ovsdb") == "_uuid" && + field.Type.Kind() == reflect.String { + hasUUID = true + } + } + if !hasUUID { + return nil, fmt.Errorf("model is expected to have a string field called uuid") + } + + types[table] = reflect.TypeOf(model) + } + return &ClientDBModel{ + types: types, + name: name, + }, nil +} diff --git a/model/database.go b/model/database.go new file mode 100644 index 00000000..6f7ee4fd --- /dev/null +++ b/model/database.go @@ -0,0 +1,157 @@ +package model + +import ( + "fmt" + "reflect" + "sync" + + "github.com/ovn-org/libovsdb/mapper" + "github.com/ovn-org/libovsdb/ovsdb" +) + +// A DatabaseModel represents libovsdb's metadata about the database. +// It's the result of combining the client's ClientDBModel and the server's Schema +type DatabaseModel struct { + client *ClientDBModel + schema *ovsdb.DatabaseSchema + mapper *mapper.Mapper + mutex sync.RWMutex + metadata map[reflect.Type]*mapper.Metadata +} + +// NewDatabaseModel returns a new DatabaseModel +func NewDatabaseModel(schema *ovsdb.DatabaseSchema, client *ClientDBModel) (*DatabaseModel, []error) { + dbModel := NewPartialDatabaseModel(client) + errs := dbModel.SetSchema(schema) + if len(errs) > 0 { + return nil, errs + } + return dbModel, nil +} + +// NewPartialDatabaseModel returns a DatabaseModel what does not have a schema yet +func NewPartialDatabaseModel(client *ClientDBModel) *DatabaseModel { + return &DatabaseModel{ + client: client, + } +} + +// Valid returns whether the DatabaseModel is fully functional +func (db *DatabaseModel) Valid() bool { + db.mutex.RLock() + defer db.mutex.RUnlock() + return db.schema != nil +} + +// SetSchema adds the Schema to the DatabaseModel making it valid if it was not before +func (db *DatabaseModel) SetSchema(schema *ovsdb.DatabaseSchema) []error { + db.mutex.Lock() + defer db.mutex.Unlock() + errors := db.client.validate(schema) + if len(errors) > 0 { + return errors + } + db.schema = schema + db.mapper = mapper.NewMapper(schema) + errs := db.generateModelInfo() + if len(errs) > 0 { + db.schema = nil + db.mapper = nil + return errs + } + return []error{} +} + +// ClearSchema removes the Schema from the DatabaseModel making it not valid +func (db *DatabaseModel) ClearSchema() { + db.mutex.Lock() + defer db.mutex.Unlock() + db.schema = nil + db.mapper = nil +} + +// Client returns the DatabaseModel's client dbModel +func (db *DatabaseModel) Client() *ClientDBModel { + return db.client +} + +// Schema returns the DatabaseModel's schema +func (db *DatabaseModel) Schema() *ovsdb.DatabaseSchema { + db.mutex.RLock() + defer db.mutex.RUnlock() + return db.schema +} + +// Mapper returns the DatabaseModel's mapper +func (db *DatabaseModel) Mapper() *mapper.Mapper { + db.mutex.RLock() + defer db.mutex.RUnlock() + return db.mapper +} + +// NewModel returns a new instance of a model from a specific string +func (db *DatabaseModel) NewModel(table string) (Model, error) { + mtype, ok := db.client.types[table] + if !ok { + return nil, fmt.Errorf("table %s not found in database model", string(table)) + } + model := reflect.New(mtype.Elem()) + return model.Interface().(Model), nil +} + +// Types returns the DatabaseModel Types +// the DatabaseModel types is a map of reflect.Types indexed by string +// The reflect.Type is a pointer to a struct that contains 'ovs' tags +// as described above. Such pointer to struct also implements the Model interface +func (db *DatabaseModel) Types() map[string]reflect.Type { + return db.client.types +} + +// FindTable returns the string associated with a reflect.Type or "" +func (db *DatabaseModel) FindTable(mType reflect.Type) string { + for table, tType := range db.client.types { + if tType == mType { + return table + } + } + return "" +} + +// generateModelMetadata creates metadata objects from all models included in the +// database and caches them for future re-use +func (db *DatabaseModel) generateModelInfo() []error { + errors := []error{} + metadata := make(map[reflect.Type]*mapper.Metadata, len(db.client.types)) + for tableName, tType := range db.client.types { + tableSchema := db.schema.Table(tableName) + if tableSchema == nil { + errors = append(errors, fmt.Errorf("Database Model contains model for table %s which is not present in schema", tableName)) + continue + } + obj, err := db.NewModel(tableName) + if err != nil { + errors = append(errors, err) + continue + } + info, err := mapper.NewInfo(tableName, tableSchema, obj) + if err != nil { + errors = append(errors, err) + continue + } + metadata[tType] = info.Metadata + } + db.metadata = metadata + return errors +} + +// NewModelInfo returns a mapper.Info object based on a provided model +func (db *DatabaseModel) NewModelInfo(obj interface{}) (*mapper.Info, error) { + meta, ok := db.metadata[reflect.TypeOf(obj)] + if !ok { + return nil, ovsdb.NewErrWrongType("NewModelInfo", "type that is part of the DatabaseModel", obj) + } + return &mapper.Info{ + Obj: obj, + Metadata: meta, + }, nil +} diff --git a/model/model.go b/model/model.go index ef77fc9d..453fd2ea 100644 --- a/model/model.go +++ b/model/model.go @@ -5,7 +5,6 @@ import ( "fmt" "reflect" - "github.com/ovn-org/libovsdb/mapper" "github.com/ovn-org/libovsdb/ovsdb" ) @@ -34,99 +33,6 @@ func Clone(a Model) Model { return b } -// DBModel is a Database model -type DBModel struct { - name string - types map[string]reflect.Type -} - -// NewModel returns a new instance of a model from a specific string -func (db DBModel) NewModel(table string) (Model, error) { - mtype, ok := db.types[table] - if !ok { - return nil, fmt.Errorf("table %s not found in database model", string(table)) - } - model := reflect.New(mtype.Elem()) - return model.Interface().(Model), nil -} - -// Types returns the DBModel Types -// the DBModel types is a map of reflect.Types indexed by string -// The reflect.Type is a pointer to a struct that contains 'ovs' tags -// as described above. Such pointer to struct also implements the Model interface -func (db DBModel) Types() map[string]reflect.Type { - return db.types -} - -// Name returns the database name -func (db DBModel) Name() string { - return db.name -} - -// FindTable returns the string associated with a reflect.Type or "" -func (db DBModel) FindTable(mType reflect.Type) string { - for table, tType := range db.types { - if tType == mType { - return table - } - } - return "" -} - -// Validate validates the DatabaseModel against the input schema -// Returns all the errors detected -func (db DBModel) Validate(schema *ovsdb.DatabaseSchema) []error { - var errors []error - if db.name != schema.Name { - errors = append(errors, fmt.Errorf("database model name (%s) does not match schema (%s)", - db.name, schema.Name)) - } - - for tableName := range db.types { - tableSchema := schema.Table(tableName) - if tableSchema == nil { - errors = append(errors, fmt.Errorf("database model contains a model for table %s that does not exist in schema", tableName)) - continue - } - model, err := db.NewModel(tableName) - if err != nil { - errors = append(errors, err) - continue - } - if _, err := mapper.NewInfo(tableSchema, model); err != nil { - errors = append(errors, err) - } - } - return errors -} - -// NewDBModel constructs a DBModel based on a database name and dictionary of models indexed by table name -func NewDBModel(name string, models map[string]Model) (*DBModel, error) { - types := make(map[string]reflect.Type, len(models)) - for table, model := range models { - modelType := reflect.TypeOf(model) - if modelType.Kind() != reflect.Ptr || modelType.Elem().Kind() != reflect.Struct { - return nil, fmt.Errorf("model is expected to be a pointer to struct") - } - hasUUID := false - for i := 0; i < modelType.Elem().NumField(); i++ { - if field := modelType.Elem().Field(i); field.Tag.Get("ovsdb") == "_uuid" && - field.Type.Kind() == reflect.String { - hasUUID = true - } - } - if !hasUUID { - return nil, fmt.Errorf("model is expected to have a string field called uuid") - } - - types[table] = reflect.TypeOf(model) - } - return &DBModel{ - types: types, - name: name, - }, nil -} - func modelSetUUID(model Model, uuid string) error { modelVal := reflect.ValueOf(model).Elem() for i := 0; i < modelVal.NumField(); i++ { diff --git a/model/model_test.go b/model/model_test.go index 6eaaee41..1e08b3b5 100644 --- a/model/model_test.go +++ b/model/model_test.go @@ -23,7 +23,7 @@ type modelInvalid struct { Foo string } -func TestDBModel(t *testing.T) { +func TestClientDBModel(t *testing.T) { type Test struct { name string obj map[string]Model @@ -50,10 +50,10 @@ func TestDBModel(t *testing.T) { } for _, tt := range tests { t.Run(fmt.Sprintf("TestNewModel_%s", tt.name), func(t *testing.T) { - db, err := NewDBModel(tt.name, tt.obj) + db, err := NewClientDBModel(tt.name, tt.obj) if tt.valid { assert.Nil(t, err) - assert.Len(t, db.Types(), len(tt.obj)) + assert.Len(t, db.types, len(tt.obj)) assert.Equal(t, tt.name, db.Name()) } else { assert.NotNil(t, err) @@ -63,11 +63,11 @@ func TestDBModel(t *testing.T) { } func TestNewModel(t *testing.T) { - db, err := NewDBModel("testTable", map[string]Model{"Test_A": &modelA{}, "Test_B": &modelB{}}) + db, err := NewClientDBModel("testTable", map[string]Model{"Test_A": &modelA{}, "Test_B": &modelB{}}) assert.Nil(t, err) - _, err = db.NewModel("Unknown") + _, err = db.newModel("Unknown") assert.NotNilf(t, err, "Creating model from unknown table should fail") - model, err := db.NewModel("Test_A") + model, err := db.newModel("Test_A") assert.Nilf(t, err, "Creating model from valid table should succeed") assert.IsTypef(t, model, &modelA{}, "model creation should return the appropriate type") } @@ -86,7 +86,7 @@ func TestSetUUID(t *testing.T) { } func TestValidate(t *testing.T) { - model, err := NewDBModel("TestDB", map[string]Model{ + model, err := NewClientDBModel("TestDB", map[string]Model{ "TestTable": &struct { aUUID string `ovsdb:"_uuid"` aString string `ovsdb:"aString"` @@ -324,7 +324,7 @@ func TestValidate(t *testing.T) { var schema ovsdb.DatabaseSchema err := json.Unmarshal(tt.schema, &schema) assert.Nil(t, err) - errors := model.Validate(&schema) + errors := model.validate(&schema) if tt.err { assert.Greater(t, len(errors), 0) } else { diff --git a/modelgen/dbmodel.go b/modelgen/dbmodel.go index bdff8a61..4ddc0858 100644 --- a/modelgen/dbmodel.go +++ b/modelgen/dbmodel.go @@ -8,7 +8,7 @@ import ( "github.com/ovn-org/libovsdb/ovsdb" ) -// NewDBTemplate return a new DBModel template +// NewDBTemplate return a new ClientDBModel template // It includes the following other templates that can be overridden to customize the generated file // "header" // "preDBDefinitions" @@ -40,8 +40,8 @@ package {{ index . "PackageName" }} {{ template "preDBDefinitions" }} // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb -func FullDatabaseModel() (*model.DBModel, error) { - return model.NewDBModel("{{ index . "DatabaseName" }}", map[string]model.Model{ +func FullDatabaseModel() (*model.ClientDBModel, error) { + return model.NewClientDBModel("{{ index . "DatabaseName" }}", map[string]model.Model{ {{ range index . "Tables" }} "{{ .TableName }}" : &{{ .StructName }}{}, {{ end }} }) diff --git a/modelgen/dbmodel_test.go b/modelgen/dbmodel_test.go index b9ca9191..8ba2fac9 100644 --- a/modelgen/dbmodel_test.go +++ b/modelgen/dbmodel_test.go @@ -59,8 +59,8 @@ import ( ) // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb -func FullDatabaseModel() (*model.DBModel, error) { - return model.NewDBModel("AtomicDB", map[string]model.Model{ +func FullDatabaseModel() (*model.ClientDBModel, error) { + return model.NewClientDBModel("AtomicDB", map[string]model.Model{ "atomicTable": &AtomicTable{}, }) } diff --git a/ovsdb/serverdb/model.go b/ovsdb/serverdb/model.go index 511112f8..45ba9da0 100644 --- a/ovsdb/serverdb/model.go +++ b/ovsdb/serverdb/model.go @@ -11,8 +11,8 @@ import ( ) // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb -func FullDatabaseModel() (*model.DBModel, error) { - return model.NewDBModel("_Server", map[string]model.Model{ +func FullDatabaseModel() (*model.ClientDBModel, error) { + return model.NewClientDBModel("_Server", map[string]model.Model{ "Database": &Database{}, }) } diff --git a/server/database.go b/server/database.go index 35b61e94..f5e5b820 100644 --- a/server/database.go +++ b/server/database.go @@ -22,11 +22,11 @@ type Database interface { type inMemoryDatabase struct { databases map[string]*cache.TableCache - models map[string]*model.DBModel + models map[string]*model.ClientDBModel mutex sync.RWMutex } -func NewInMemoryDatabase(models map[string]*model.DBModel) Database { +func NewInMemoryDatabase(models map[string]*model.ClientDBModel) Database { return &inMemoryDatabase{ databases: make(map[string]*cache.TableCache), models: models, @@ -37,12 +37,16 @@ func NewInMemoryDatabase(models map[string]*model.DBModel) Database { func (db *inMemoryDatabase) CreateDatabase(name string, schema *ovsdb.DatabaseSchema) error { db.mutex.Lock() defer db.mutex.Unlock() - var mo *model.DBModel + var mo *model.ClientDBModel var ok bool if mo, ok = db.models[schema.Name]; !ok { return fmt.Errorf("no db model provided for schema with name %s", name) } - database, err := cache.NewTableCache(schema, mo, nil, nil) + dbModel, errs := model.NewDatabaseModel(schema, mo) + if len(errs) > 0 { + return fmt.Errorf("Failed to create DatabaseModel: %#+v", errs) + } + database, err := cache.NewTableCache(dbModel, nil, nil) if err != nil { return nil } diff --git a/server/server.go b/server/server.go index 5036f04e..a0d24316 100644 --- a/server/server.go +++ b/server/server.go @@ -22,34 +22,29 @@ type OvsdbServer struct { db Database ready bool readyMutex sync.RWMutex - models map[string]DatabaseModel + models map[string]*model.DatabaseModel modelsMutex sync.RWMutex monitors map[*rpc2.Client]*connectionMonitors monitorMutex sync.RWMutex } -type DatabaseModel struct { - Model *model.DBModel - Schema *ovsdb.DatabaseSchema -} - // NewOvsdbServer returns a new OvsdbServer -func NewOvsdbServer(db Database, models ...DatabaseModel) (*OvsdbServer, error) { +func NewOvsdbServer(db Database, models ...*model.DatabaseModel) (*OvsdbServer, error) { o := &OvsdbServer{ done: make(chan struct{}, 1), db: db, - models: make(map[string]DatabaseModel), + models: make(map[string]*model.DatabaseModel), modelsMutex: sync.RWMutex{}, monitors: make(map[*rpc2.Client]*connectionMonitors), monitorMutex: sync.RWMutex{}, } o.modelsMutex.Lock() for _, model := range models { - o.models[model.Schema.Name] = model + o.models[model.Schema().Name] = model } o.modelsMutex.Unlock() for database, model := range o.models { - if err := o.db.CreateDatabase(database, model.Schema); err != nil { + if err := o.db.CreateDatabase(database, model.Schema()); err != nil { return nil, err } } @@ -113,7 +108,7 @@ func (o *OvsdbServer) ListDatabases(client *rpc2.Client, args []interface{}, rep dbs := []string{} o.modelsMutex.RLock() for _, db := range o.models { - dbs = append(dbs, db.Schema.Name) + dbs = append(dbs, db.Schema().Name) } o.modelsMutex.RUnlock() *reply = dbs @@ -132,7 +127,7 @@ func (o *OvsdbServer) GetSchema(client *rpc2.Client, args []interface{}, reply * return fmt.Errorf("database %s does not exist", db) } o.modelsMutex.RUnlock() - *reply = *model.Schema + *reply = *model.Schema() return nil } @@ -141,8 +136,8 @@ type Transaction struct { Cache *cache.TableCache } -func NewTransaction(schema *ovsdb.DatabaseSchema, model *model.DBModel) Transaction { - cache, err := cache.NewTableCache(schema, model, nil, nil) +func NewTransaction(model *model.DatabaseModel) Transaction { + cache, err := cache.NewTableCache(model, nil, nil) if err != nil { panic(err) } diff --git a/server/server_integration_test.go b/server/server_integration_test.go index 24a0a934..9c74c84e 100644 --- a/server/server_integration_test.go +++ b/server/server_integration_test.go @@ -56,7 +56,7 @@ func getSchema() (*ovsdb.DatabaseSchema, error) { } func TestClientServerEcho(t *testing.T) { - defDB, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) require.Nil(t, err) @@ -64,15 +64,14 @@ func TestClientServerEcho(t *testing.T) { schema, err := getSchema() require.Nil(t, err) - ovsDB := NewInMemoryDatabase(map[string]*model.DBModel{"Open_vSwitch": defDB}) + ovsDB := 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) - server, err := NewOvsdbServer(ovsDB, DatabaseModel{ - Model: defDB, - Schema: schema, - }) + dbModel, errs := model.NewDatabaseModel(schema, defDB) + require.Empty(t, errs) + server, err := NewOvsdbServer(ovsDB, dbModel) require.Nil(t, err) go func(t *testing.T, o *OvsdbServer) { @@ -94,7 +93,7 @@ func TestClientServerEcho(t *testing.T) { } func TestClientServerInsert(t *testing.T) { - defDB, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) require.Nil(t, err) @@ -102,14 +101,13 @@ func TestClientServerInsert(t *testing.T) { schema, err := getSchema() require.Nil(t, err) - ovsDB := NewInMemoryDatabase(map[string]*model.DBModel{"Open_vSwitch": defDB}) + ovsDB := 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) - server, err := NewOvsdbServer(ovsDB, DatabaseModel{ - Model: defDB, - Schema: schema, - }) + dbModel, errs := model.NewDatabaseModel(schema, defDB) + require.Empty(t, errs) + server, err := NewOvsdbServer(ovsDB, dbModel) assert.Nil(t, err) go func(t *testing.T, o *OvsdbServer) { @@ -162,7 +160,7 @@ func TestClientServerInsert(t *testing.T) { } func TestClientServerMonitor(t *testing.T) { - defDB, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) if err != nil { @@ -174,14 +172,13 @@ func TestClientServerMonitor(t *testing.T) { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]*model.DBModel{"Open_vSwitch": defDB}) + ovsDB := 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) - server, err := NewOvsdbServer(ovsDB, DatabaseModel{ - Model: defDB, - Schema: schema, - }) + dbModel, errs := model.NewDatabaseModel(schema, defDB) + require.Empty(t, errs) + server, err := NewOvsdbServer(ovsDB, dbModel) assert.Nil(t, err) go func(t *testing.T, o *OvsdbServer) { @@ -289,7 +286,7 @@ func TestClientServerMonitor(t *testing.T) { } func TestClientServerInsertAndDelete(t *testing.T) { - defDB, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) require.Nil(t, err) @@ -297,14 +294,13 @@ func TestClientServerInsertAndDelete(t *testing.T) { schema, err := getSchema() require.Nil(t, err) - ovsDB := NewInMemoryDatabase(map[string]*model.DBModel{"Open_vSwitch": defDB}) + ovsDB := 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) - server, err := NewOvsdbServer(ovsDB, DatabaseModel{ - Model: defDB, - Schema: schema, - }) + dbModel, errs := model.NewDatabaseModel(schema, defDB) + require.Empty(t, errs) + server, err := NewOvsdbServer(ovsDB, dbModel) assert.Nil(t, err) go func(t *testing.T, o *OvsdbServer) { @@ -355,7 +351,7 @@ func TestClientServerInsertAndDelete(t *testing.T) { } func TestClientServerInsertDuplicate(t *testing.T) { - defDB, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}, }) @@ -364,14 +360,13 @@ func TestClientServerInsertDuplicate(t *testing.T) { schema, err := getSchema() require.Nil(t, err) - ovsDB := NewInMemoryDatabase(map[string]*model.DBModel{"Open_vSwitch": defDB}) + ovsDB := 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) - server, err := NewOvsdbServer(ovsDB, DatabaseModel{ - Model: defDB, - Schema: schema, - }) + dbModel, errs := model.NewDatabaseModel(schema, defDB) + require.Empty(t, errs) + server, err := NewOvsdbServer(ovsDB, dbModel) assert.Nil(t, err) go func(t *testing.T, o *OvsdbServer) { @@ -411,7 +406,7 @@ func TestClientServerInsertDuplicate(t *testing.T) { } func TestClientServerInsertAndUpdate(t *testing.T) { - defDB, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) require.Nil(t, err) @@ -419,14 +414,13 @@ func TestClientServerInsertAndUpdate(t *testing.T) { schema, err := getSchema() require.Nil(t, err) - ovsDB := NewInMemoryDatabase(map[string]*model.DBModel{"Open_vSwitch": defDB}) + ovsDB := 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) - server, err := NewOvsdbServer(ovsDB, DatabaseModel{ - Model: defDB, - Schema: schema, - }) + dbModel, errs := model.NewDatabaseModel(schema, defDB) + require.Empty(t, errs) + server, err := NewOvsdbServer(ovsDB, dbModel) assert.Nil(t, err) go func(t *testing.T, o *OvsdbServer) { diff --git a/server/server_test.go b/server/server_test.go index 604300d0..897e3d28 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -66,7 +66,7 @@ func TestExpandNamedUUID(t *testing.T) { } func TestOvsdbServerMonitor(t *testing.T) { - defDB, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) if err != nil { @@ -76,9 +76,10 @@ func TestOvsdbServerMonitor(t *testing.T) { if err != nil { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]*model.DBModel{"Open_vSwitch": defDB}) - o, err := NewOvsdbServer(ovsDB, DatabaseModel{ - Model: defDB, Schema: schema}) + ovsDB := NewInMemoryDatabase(map[string]*model.ClientDBModel{"Open_vSwitch": defDB}) + dbModel, errs := model.NewDatabaseModel(schema, defDB) + require.Empty(t, errs) + o, err := NewOvsdbServer(ovsDB, dbModel) require.Nil(t, err) requests := make(map[string]ovsdb.MonitorRequest) for table, tableSchema := range schema.Tables { diff --git a/server/transact.go b/server/transact.go index 5fc9b598..088d8903 100644 --- a/server/transact.go +++ b/server/transact.go @@ -6,7 +6,6 @@ import ( "github.com/google/uuid" "github.com/ovn-org/libovsdb/cache" - "github.com/ovn-org/libovsdb/mapper" "github.com/ovn-org/libovsdb/ovsdb" ) @@ -75,21 +74,26 @@ func (o *OvsdbServer) Insert(database string, table string, rowUUID string, row dbModel := o.models[database] o.modelsMutex.Unlock() - m := mapper.NewMapper(dbModel.Schema) - tSchema := dbModel.Schema.Table(table) + m := dbModel.Mapper() if rowUUID == "" { rowUUID = uuid.NewString() } - model, err := dbModel.Model.NewModel(table) + model, err := dbModel.NewModel(table) if err != nil { return ovsdb.OperationResult{ Error: err.Error(), }, nil } - err = m.GetRowData(table, &row, model) + mapperInfo, err := dbModel.NewModelInfo(model) + if err != nil { + return ovsdb.OperationResult{ + Error: err.Error(), + }, nil + } + err = m.GetRowData(&row, mapperInfo) if err != nil { return ovsdb.OperationResult{ Error: err.Error(), @@ -97,12 +101,6 @@ func (o *OvsdbServer) Insert(database string, table string, rowUUID string, row } if rowUUID != "" { - mapperInfo, err := mapper.NewInfo(tSchema, model) - if err != nil { - return ovsdb.OperationResult{ - Error: err.Error(), - }, nil - } if err := mapperInfo.SetField("_uuid", rowUUID); err != nil { return ovsdb.OperationResult{ Error: err.Error(), @@ -110,7 +108,7 @@ func (o *OvsdbServer) Insert(database string, table string, rowUUID string, row } } - resultRow, err := m.NewRow(table, model) + resultRow, err := m.NewRow(mapperInfo) if err != nil { return ovsdb.OperationResult{ Error: err.Error(), @@ -155,7 +153,7 @@ func (o *OvsdbServer) Select(database string, table string, where []ovsdb.Condit dbModel := o.models[database] o.modelsMutex.Unlock() - m := mapper.NewMapper(dbModel.Schema) + m := dbModel.Mapper() var results []ovsdb.Row rows, err := o.db.List(database, table, where...) @@ -163,7 +161,11 @@ func (o *OvsdbServer) Select(database string, table string, where []ovsdb.Condit panic(err) } for _, row := range rows { - resultRow, err := m.NewRow(table, row) + info, err := dbModel.NewModelInfo(row) + if err != nil { + panic(err) + } + resultRow, err := m.NewRow(info) if err != nil { panic(err) } @@ -184,8 +186,8 @@ func (o *OvsdbServer) Update(database, table string, where []ovsdb.Condition, ro dbModel := o.models[database] o.modelsMutex.Unlock() - m := mapper.NewMapper(dbModel.Schema) - schema := dbModel.Schema.Table(table) + m := dbModel.Mapper() + schema := dbModel.Schema().Table(table) tableUpdate := make(ovsdb.TableUpdate2) rows, err := o.db.List(database, table, where...) if err != nil { @@ -194,26 +196,26 @@ func (o *OvsdbServer) Update(database, table string, where []ovsdb.Condition, ro }, nil } for _, old := range rows { - info, _ := mapper.NewInfo(schema, old) - uuid, _ := info.FieldByColumn("_uuid") + oldInfo, _ := dbModel.NewModelInfo(old) + uuid, _ := oldInfo.FieldByColumn("_uuid") - oldRow, err := m.NewRow(table, old) + oldRow, err := m.NewRow(oldInfo) if err != nil { panic(err) } - new, err := dbModel.Model.NewModel(table) + new, err := dbModel.NewModel(table) if err != nil { panic(err) } - err = m.GetRowData(table, &oldRow, new) + newInfo, err := dbModel.NewModelInfo(new) if err != nil { panic(err) } - info, err = mapper.NewInfo(schema, new) + err = m.GetRowData(&oldRow, newInfo) if err != nil { panic(err) } - err = info.SetField("_uuid", uuid) + err = newInfo.SetField("_uuid", uuid) if err != nil { panic(err) } @@ -235,7 +237,7 @@ func (o *OvsdbServer) Update(database, table string, where []ovsdb.Condition, ro Details: fmt.Sprintf("column %s is of table %s not mutable", column, table), }, nil } - old, err := info.FieldByColumn(column) + old, err := newInfo.FieldByColumn(column) if err != nil { panic(err) } @@ -254,7 +256,7 @@ func (o *OvsdbServer) Update(database, table string, where []ovsdb.Condition, ro continue } - err = info.SetField(column, native) + err = newInfo.SetField(column, native) if err != nil { panic(err) } @@ -270,7 +272,7 @@ func (o *OvsdbServer) Update(database, table string, where []ovsdb.Condition, ro } } - newRow, err := m.NewRow(table, new) + newRow, err := m.NewRow(newInfo) if err != nil { panic(err) } @@ -313,8 +315,8 @@ func (o *OvsdbServer) Mutate(database, table string, where []ovsdb.Condition, mu dbModel := o.models[database] o.modelsMutex.Unlock() - m := mapper.NewMapper(dbModel.Schema) - schema := dbModel.Schema.Table(table) + m := dbModel.Mapper() + schema := dbModel.Schema().Table(table) tableUpdate := make(ovsdb.TableUpdate2) @@ -324,24 +326,24 @@ func (o *OvsdbServer) Mutate(database, table string, where []ovsdb.Condition, mu } for _, old := range rows { - oldInfo, err := mapper.NewInfo(schema, old) + oldInfo, err := dbModel.NewModelInfo(old) if err != nil { panic(err) } uuid, _ := oldInfo.FieldByColumn("_uuid") - oldRow, err := m.NewRow(table, old) + oldRow, err := m.NewRow(oldInfo) if err != nil { panic(err) } - new, err := dbModel.Model.NewModel(table) + new, err := dbModel.NewModel(table) if err != nil { panic(err) } - err = m.GetRowData(table, &oldRow, new) + newInfo, err := dbModel.NewModelInfo(new) if err != nil { panic(err) } - newInfo, err := mapper.NewInfo(schema, new) + err = m.GetRowData(&oldRow, newInfo) if err != nil { panic(err) } @@ -424,7 +426,7 @@ func (o *OvsdbServer) Mutate(database, table string, where []ovsdb.Condition, mu }, nil } - newRow, err := m.NewRow(table, new) + newRow, err := m.NewRow(newInfo) if err != nil { panic(err) } @@ -452,17 +454,17 @@ func (o *OvsdbServer) Delete(database, table string, where []ovsdb.Condition) (o o.modelsMutex.Lock() dbModel := o.models[database] o.modelsMutex.Unlock() - m := mapper.NewMapper(dbModel.Schema) - schema := dbModel.Schema.Table(table) + m := dbModel.Mapper() + tableUpdate := make(ovsdb.TableUpdate2) rows, err := o.db.List(database, table, where...) if err != nil { panic(err) } for _, row := range rows { - info, _ := mapper.NewInfo(schema, row) + info, _ := dbModel.NewModelInfo(row) uuid, _ := info.FieldByColumn("_uuid") - oldRow, err := m.NewRow(table, row) + oldRow, err := m.NewRow(info) if err != nil { panic(err) } diff --git a/server/transact_test.go b/server/transact_test.go index 926950d7..2f0effda 100644 --- a/server/transact_test.go +++ b/server/transact_test.go @@ -12,7 +12,7 @@ import ( ) func TestMutateOp(t *testing.T) { - defDB, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) if err != nil { @@ -22,9 +22,10 @@ func TestMutateOp(t *testing.T) { if err != nil { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]*model.DBModel{"Open_vSwitch": defDB}) - o, err := NewOvsdbServer(ovsDB, DatabaseModel{ - Model: defDB, Schema: schema}) + ovsDB := NewInMemoryDatabase(map[string]*model.ClientDBModel{"Open_vSwitch": defDB}) + dbModel, errs := model.NewDatabaseModel(schema, defDB) + require.Empty(t, errs) + o, err := NewOvsdbServer(ovsDB, dbModel) require.Nil(t, err) ovsUUID := uuid.NewString() @@ -33,7 +34,9 @@ func TestMutateOp(t *testing.T) { m := mapper.NewMapper(schema) ovs := ovsType{} - ovsRow, err := m.NewRow("Open_vSwitch", &ovs) + info, err := dbModel.NewModelInfo(&ovs) + require.NoError(t, err) + ovsRow, err := m.NewRow(info) require.Nil(t, err) bridge := bridgeType{ @@ -44,7 +47,9 @@ func TestMutateOp(t *testing.T) { "waldo": "fred", }, } - bridgeRow, err := m.NewRow("Bridge", &bridge) + bridgeInfo, err := dbModel.NewModelInfo(&bridge) + require.NoError(t, err) + bridgeRow, err := m.NewRow(bridgeInfo) require.Nil(t, err) res, updates := o.Insert("Open_vSwitch", "Open_vSwitch", ovsUUID, ovsRow) @@ -204,7 +209,7 @@ 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.NewDBModel("Open_vSwitch", map[string]model.Model{ + defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) if err != nil { @@ -214,9 +219,10 @@ func TestOvsdbServerInsert(t *testing.T) { if err != nil { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]*model.DBModel{"Open_vSwitch": defDB}) - o, err := NewOvsdbServer(ovsDB, DatabaseModel{ - Model: defDB, Schema: schema}) + ovsDB := NewInMemoryDatabase(map[string]*model.ClientDBModel{"Open_vSwitch": defDB}) + dbModel, errs := model.NewDatabaseModel(schema, defDB) + require.Empty(t, errs) + o, err := NewOvsdbServer(ovsDB, dbModel) require.Nil(t, err) m := mapper.NewMapper(schema) @@ -232,7 +238,9 @@ func TestOvsdbServerInsert(t *testing.T) { }, } bridgeUUID := uuid.NewString() - bridgeRow, err := m.NewRow("Bridge", &bridge) + bridgeInfo, err := dbModel.NewModelInfo(&bridge) + require.NoError(t, err) + bridgeRow, err := m.NewRow(bridgeInfo) require.Nil(t, err) res, updates := o.Insert("Open_vSwitch", "Bridge", bridgeUUID, bridgeRow) @@ -257,7 +265,7 @@ func TestOvsdbServerInsert(t *testing.T) { } func TestOvsdbServerUpdate(t *testing.T) { - defDB, err := model.NewDBModel("Open_vSwitch", map[string]model.Model{ + defDB, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) if err != nil { @@ -267,9 +275,10 @@ func TestOvsdbServerUpdate(t *testing.T) { if err != nil { t.Fatal(err) } - ovsDB := NewInMemoryDatabase(map[string]*model.DBModel{"Open_vSwitch": defDB}) - o, err := NewOvsdbServer(ovsDB, DatabaseModel{ - Model: defDB, Schema: schema}) + ovsDB := NewInMemoryDatabase(map[string]*model.ClientDBModel{"Open_vSwitch": defDB}) + dbModel, errs := model.NewDatabaseModel(schema, defDB) + require.Empty(t, errs) + o, err := NewOvsdbServer(ovsDB, dbModel) require.Nil(t, err) m := mapper.NewMapper(schema) @@ -282,7 +291,9 @@ func TestOvsdbServerUpdate(t *testing.T) { }, } bridgeUUID := uuid.NewString() - bridgeRow, err := m.NewRow("Bridge", &bridge) + bridgeInfo, err := dbModel.NewModelInfo(&bridge) + require.NoError(t, err) + bridgeRow, err := m.NewRow(bridgeInfo) require.Nil(t, err) res, updates := o.Insert("Open_vSwitch", "Bridge", bridgeUUID, bridgeRow) diff --git a/test/ovs/ovs_integration_test.go b/test/ovs/ovs_integration_test.go index 1bf20d95..e011909c 100644 --- a/test/ovs/ovs_integration_test.go +++ b/test/ovs/ovs_integration_test.go @@ -167,7 +167,7 @@ type queueType struct { DSCP *int `ovsdb:"dscp"` } -var defDB, _ = model.NewDBModel("Open_vSwitch", map[string]model.Model{ +var defDB, _ = model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}, "IPFIX": &ipfixType{},