diff --git a/README.md b/README.md index 491ad5ec..7d8e7391 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 DatabaseModelRequest which is a created by assigning table names to pointers to these structs: - dbModel, _ := model.NewDBModel("OVN_Northbound", map[string]model.Model{ + dbModelReq, _ := model.NewDatabaseModelRequest("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 `DatabaseModelRequest` 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 DatabaseModelRequest, 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 b8101f8b..21b7208e 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -66,7 +66,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 @@ -90,7 +90,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 @@ -120,11 +120,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 { @@ -156,16 +156,17 @@ func (r *RowCache) Update(uuid string, m model.Model, checkIndexes bool) error { return fmt.Errorf("row %s does not exist", 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 @@ -218,7 +219,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 } @@ -252,7 +253,7 @@ func (r *RowCache) Delete(uuid string) error { return fmt.Errorf("row %s does not exist", uuid) } oldRow := r.cache[uuid] - oldInfo, err := mapper.NewInfo(&r.schema, oldRow) + oldInfo, err := r.dbModel.NewModelInfo(oldRow) if err != nil { return err } @@ -280,6 +281,7 @@ func (r *RowCache) Rows() []string { func (r *RowCache) RowsByCondition(conditions []ovsdb.Condition) ([]model.Model, error) { var results []model.Model + schema := r.dbModel.Schema().Table(r.name) if len(conditions) == 0 { uuids := r.Rows() for _, uuid := range uuids { @@ -308,7 +310,7 @@ func (r *RowCache) RowsByCondition(conditions []ovsdb.Condition) ([]model.Model, } } else if index, err := r.Index(condition.Column); err != nil { for k, v := 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 @@ -325,7 +327,7 @@ func (r *RowCache) RowsByCondition(conditions []ovsdb.Condition) ([]model.Model, } else { for _, uuid := range r.Rows() { row := r.Row(uuid) - info, err := mapper.NewInfo(&r.schema, row) + info, err := r.dbModel.NewModelInfo(row) if err != nil { return nil, err } @@ -406,9 +408,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 ovsdb.NotificationHandler mutex sync.RWMutex } @@ -417,18 +417,18 @@ 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) (*TableCache, error) { - if schema == nil || dbModel == nil { - return nil, fmt.Errorf("tablecache without databasemodel cannot be populated") +func NewTableCache(dbModel *model.DatabaseModel, data Data) (*TableCache, error) { + if !dbModel.Valid() { + return nil, fmt.Errorf("tablecache without valid databasemodel cannot be populated") } eventProcessor := newEventProcessor(bufferSize) 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 { @@ -439,9 +439,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{}, }, nil @@ -449,11 +447,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 { +// DatabaseModelRequest returns the DatabaseModelRequest +func (t *TableCache) DatabaseModel() *model.DatabaseModel { return t.dbModel } @@ -624,13 +622,14 @@ func (t *TableCache) Populate2(tableUpdates ovsdb.TableUpdates2) { } // 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]) } } @@ -646,11 +645,11 @@ func (t *TableCache) Run(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{}, @@ -756,7 +755,7 @@ 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) + table := t.dbModel.Schema().Table(tableName) if table == nil { return nil, fmt.Errorf("table %s not found", tableName) } @@ -764,18 +763,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 } } @@ -786,20 +784,20 @@ 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) + 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 } for k, v := range update { - if k == "_uuid" { + if k == "_uuid" || !t.dbModel.HasColumn(tableName, k) { continue } diff --git a/cache/cache_test.go b/cache/cache_test.go index 411aa785..55a342e7 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -5,7 +5,6 @@ import ( "reflect" "testing" - "github.com/ovn-org/libovsdb/mapper" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" "github.com/stretchr/testify/assert" @@ -84,10 +83,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.NewDatabaseModelRequest("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"]], @@ -107,7 +106,9 @@ func TestRowCacheCreate(t *testing.T) { testData := Data{ "Open_vSwitch": map[string]model.Model{"bar": &testModel{Foo: "bar"}}, } - tc, err := NewTableCache(&schema, db, testData) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData) require.Nil(t, err) tests := []struct { @@ -170,10 +171,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.NewDatabaseModelRequest("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"]], @@ -190,11 +191,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) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData) require.Nil(t, err) tests := []struct { name string @@ -251,7 +253,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) @@ -263,10 +265,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.NewDatabaseModelRequest("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"]], @@ -289,7 +291,9 @@ func TestRowCacheUpdate(t *testing.T) { "foobar": &testModel{Foo: "foobar"}, }, } - tc, err := NewTableCache(&schema, db, testData) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData) require.Nil(t, err) tests := []struct { @@ -345,10 +349,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.NewDatabaseModelRequest("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"]], @@ -364,7 +368,6 @@ func TestRowCacheUpdateMultiIndex(t *testing.T) { } } `), &schema) - tSchema := schema.Table("Open_vSwitch") require.Nil(t, err) testData := Data{ "Open_vSwitch": map[string]model.Model{ @@ -372,7 +375,9 @@ func TestRowCacheUpdateMultiIndex(t *testing.T) { "foobar": &testModel{Foo: "foobar", Bar: "foobar"}, }, } - tc, err := NewTableCache(&schema, db, testData) + dbModel, errs := model.NewDatabaseModel(&schema, db) + assert.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData) require.Nil(t, err) tests := []struct { @@ -415,7 +420,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) @@ -427,10 +432,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.NewDatabaseModelRequest("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"]], @@ -452,7 +457,9 @@ func TestRowCacheDelete(t *testing.T) { "bar": &testModel{Foo: "bar"}, }, } - tc, err := NewTableCache(&schema, db, testData) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData) require.Nil(t, err) tests := []struct { @@ -623,6 +630,29 @@ func TestEventHandlerFuncs_OnDelete(t *testing.T) { } func TestTableCacheTable(t *testing.T) { + db, err := model.NewDatabaseModelRequest("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 @@ -631,15 +661,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 { @@ -654,6 +684,52 @@ func TestTableCacheTable(t *testing.T) { } func TestTableCacheTables(t *testing.T) { + db, err := model.NewDatabaseModelRequest("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 @@ -662,9 +738,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"}, }, @@ -687,11 +763,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.NewDatabaseModelRequest("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"]], @@ -708,7 +784,9 @@ func TestTableCache_populate(t *testing.T) { } `), &schema) assert.Nil(t, err) - tc, err := NewTableCache(&schema, db, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, nil) assert.Nil(t, err) testRow := ovsdb.Row(map[string]interface{}{"_uuid": "test", "foo": "bar"}) @@ -752,11 +830,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.NewDatabaseModelRequest("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"]], @@ -773,7 +851,9 @@ func TestTableCachePopulate(t *testing.T) { } `), &schema) assert.Nil(t, err) - tc, err := NewTableCache(&schema, db, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, nil) assert.Nil(t, err) testRow := ovsdb.Row(map[string]interface{}{"_uuid": "test", "foo": "bar"}) @@ -816,11 +896,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.NewDatabaseModelRequest("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"]], @@ -837,7 +917,9 @@ func TestTableCachePopulate2(t *testing.T) { } `), &schema) assert.Nil(t, err) - tc, err := NewTableCache(&schema, db, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, nil) assert.Nil(t, err) testRow := ovsdb.Row(map[string]interface{}{"_uuid": "test", "foo": "bar"}) @@ -934,11 +1016,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.NewDatabaseModelRequest("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"]], @@ -958,7 +1040,9 @@ func TestIndex(t *testing.T) { } `), &schema) assert.Nil(t, err) - tc, err := NewTableCache(&schema, db, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + assert.Empty(t, errs) + tc, err := NewTableCache(dbModel, nil) assert.Nil(t, err) obj := &indexTestModel{ UUID: "test1", @@ -973,7 +1057,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) @@ -985,7 +1069,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) @@ -1003,7 +1087,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) @@ -1014,7 +1098,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) @@ -1029,10 +1113,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.NewDatabaseModelRequest("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"]], @@ -1056,7 +1140,9 @@ func TestTableCacheRowByModelSingleIndex(t *testing.T) { "bar": &testModel{Foo: "bar", Bar: "bar"}, }, } - tc, err := NewTableCache(&schema, db, testData) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData) require.NoError(t, err) t.Run("get foo by index", func(t *testing.T) { @@ -1078,10 +1164,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.NewDatabaseModelRequest("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"]], @@ -1105,7 +1191,9 @@ func TestTableCacheRowByModelTwoIndexes(t *testing.T) { "bar": &testModel{Foo: "bar", Bar: "bar"}, }, } - tc, err := NewTableCache(&schema, db, testData) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData) require.NoError(t, err) t.Run("get foo by Foo index", func(t *testing.T) { @@ -1129,10 +1217,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.NewDatabaseModelRequest("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"]], @@ -1153,7 +1241,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) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, testData) require.NoError(t, err) t.Run("incomplete index", func(t *testing.T) { @@ -1175,6 +1265,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"` @@ -1278,12 +1369,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.NewDatabaseModelRequest("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"]], @@ -1299,7 +1390,9 @@ func TestTableCacheApplyModifications(t *testing.T) { } `), &schema) require.NoError(t, err) - tc, err := NewTableCache(&schema, db, nil) + dbModel, errs := model.NewDatabaseModel(&schema, db) + require.Empty(t, errs) + tc, err := NewTableCache(dbModel, 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 04fd5c47..cd5d53eb 100644 --- a/client/api.go +++ b/client/api.go @@ -8,7 +8,6 @@ import ( "reflect" "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 DatabaseModelRequest // // 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 b90541ac..468c2f8e 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.NewDatabaseModelRequest("OVN_Northbound", map[string]model.Model{"Logical_Switch": &testLogicalSwitch{}, "Logical_Switch_Port": &testLogicalSwitchPort{}}) assert.Nil(t, err) - cache, err := cache.NewTableCache(&schema, db, data) + dbModel, errs := model.NewDatabaseModel(&schema, db) + assert.Empty(t, errs) + cache, err := cache.NewTableCache(dbModel, data) assert.Nil(t, err) return cache } diff --git a/client/client.go b/client/client.go index fd911b92..7fd824e2 100644 --- a/client/client.go +++ b/client/client.go @@ -17,7 +17,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" @@ -86,8 +85,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 @@ -103,17 +101,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(databaseModelRequest *model.DatabaseModelRequest, opts ...Option) (Client, error) { + return newOVSDBClient(databaseModelRequest, opts...) } // newOVSDBClient creates a new ovsdbClient -func newOVSDBClient(databaseModel *model.DBModel, opts ...Option) (*ovsdbClient, error) { +func newOVSDBClient(databaseModelRequest *model.DatabaseModelRequest, opts ...Option) (*ovsdbClient, error) { ovs := &ovsdbClient{ - primaryDBName: databaseModel.Name(), + primaryDBName: databaseModelRequest.Name(), databases: map[string]*database{ - databaseModel.Name(): { - model: databaseModel, + databaseModelRequest.Name(): { + model: model.NewPartialDatabaseModel(databaseModelRequest), monitors: make(map[string]*Monitor), }, }, @@ -132,11 +130,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(databaseModelRequest.Name()) return ovs, nil } @@ -148,7 +146,7 @@ func newOVSDBClient(databaseModel *model.DBModel, opts ...Option) (*ovsdbClient, 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.Name()) + l := o.options.logger.WithValues("model", o.primaryDB().model.Request().Name()) o.options.logger = &l o.registerMetrics() @@ -287,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 { @@ -300,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) + db.cache, err = cache.NewTableCache(db.model, nil) if err != nil { db.cacheMutex.Unlock() o.rpcClient.Close() @@ -315,7 +311,7 @@ func (o *ovsdbClient) tryEndpoint(ctx context.Context, u *url.URL) error { } db.api = newAPI(db.cache) } else { - db.cache.Purge(db.schema) + db.cache.Purge(db.model) } db.cacheMutex.Unlock() } @@ -425,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 @@ -619,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) @@ -705,16 +701,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 } @@ -908,7 +912,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 173bab29..32157057 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.NewDatabaseModelRequest("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{ + dbModelReq, err := model.NewDatabaseModelRequest("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, "Open_vSwitch": &OpenvSwitch{}, }) require.NoError(b, err) - ovs.primaryDB().cache, err = cache.NewTableCache(&s, dbModel, nil) + dbModel, errs := model.NewDatabaseModel(&s, dbModelReq) + require.Empty(b, errs) + ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, 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{ + dbModelReq, err := model.NewDatabaseModelRequest("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, "Open_vSwitch": &OpenvSwitch{}, }) require.NoError(b, err) - ovs.primaryDB().cache, err = cache.NewTableCache(&s, dbModel, nil) + dbModel, errs := model.NewDatabaseModel(&s, dbModelReq) + require.Empty(b, errs) + ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, 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{ + dbModelReq, err := model.NewDatabaseModelRequest("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, "Open_vSwitch": &OpenvSwitch{}, }) require.NoError(b, err) - ovs.primaryDB().cache, err = cache.NewTableCache(&s, dbModel, nil) + dbModel, errs := model.NewDatabaseModel(&s, dbModelReq) + require.Empty(b, errs) + ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, 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{ + dbModelReq, err := model.NewDatabaseModelRequest("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, "Open_vSwitch": &OpenvSwitch{}, }) require.NoError(b, err) - ovs.primaryDB().cache, err = cache.NewTableCache(&s, dbModel, nil) + dbModel, errs := model.NewDatabaseModel(&s, dbModelReq) + require.Empty(b, errs) + ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, 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{ + dbModelReq, err := model.NewDatabaseModelRequest("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, "Open_vSwitch": &OpenvSwitch{}, }) require.NoError(b, err) - ovs.primaryDB().cache, err = cache.NewTableCache(&s, dbModel, nil) + dbModel, errs := model.NewDatabaseModel(&s, dbModelReq) + require.Empty(b, errs) + ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, 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{ + dbModelReq, err := model.NewDatabaseModelRequest("Open_vSwitch", map[string]model.Model{ "Bridge": &Bridge{}, "Open_vSwitch": &OpenvSwitch{}, }) require.NoError(t, err) - ovs.primaryDB().cache, err = cache.NewTableCache(&s, dbModel, nil) + dbModel, errs := model.NewDatabaseModel(&s, dbModelReq) + require.Empty(t, errs) + ovs.primaryDB().cache, err = cache.NewTableCache(dbModel, 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 cba7a7f7..a7a48426 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 @@ -153,7 +165,11 @@ func (c *predicateConditional) Generate() ([][]ovsdb.Condition, error) { return nil, err } if match { - elemCond, err := c.cache.Mapper().NewEqualityCondition(c.tableName, elem) + info, err := c.cache.DatabaseModel().NewModelInfo(elem) + 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..9d10ef6b 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 DatabaseModelRequest type) is built to represent the entire OVSDB: - dbModel, _ := client.NewDBModel("OVN_Northbound", + dbModelReq, _ := client.NewDatabaseModelRequest("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 DatabaseModelRequest 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(), dbModelReq) Main API diff --git a/client/monitor.go b/client/monitor.go index d2fd7310..9bb36099 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 DatabaseModelRequest", 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 DatabaseModelRequest", reflect.TypeOf(m)) } tableMonitor := TableMonitor{ Table: tableName, diff --git a/cmd/stress/stress.go b/cmd/stress/stress.go index 511bb724..476f570f 100644 --- a/cmd/stress/stress.go +++ b/cmd/stress/stress.go @@ -43,7 +43,7 @@ var ( 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 + dbModelReq *model.DatabaseModelRequest ) 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(dbModelReq, 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(dbModelReq, client.WithEndpoint(*connection)) if err != nil { log.Fatal(err) } @@ -249,7 +249,8 @@ func main() { } var err error - dbModel, err = model.NewDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) + dbModelReq, err = model.NewDatabaseModelRequest("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}}) + dbModelReq.SetCompatibility(true) if err != nil { log.Fatal(err) } diff --git a/example/ovsdb-server/main.go b/example/ovsdb-server/main.go index b510beb0..ad728986 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() + dbModelReq, 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,16 @@ func main() { log.Fatal(err) } - ovsDB := server.NewInMemoryDatabase(map[string]*model.DBModel{ - schema.Name: dbModel, + ovsDB := server.NewInMemoryDatabase(map[string]*model.DatabaseModelRequest{ + schema.Name: dbModelReq, }) - s, err := server.NewOvsdbServer(ovsDB, server.DatabaseModel{ - Model: dbModel, - Schema: schema, - }) + dbModel, errs := model.NewDatabaseModel(schema, dbModelReq) + if len(errs) > 0 { + log.Fatal(errs) + } + + s, err := server.NewOvsdbServer(ovsDB, *dbModel) if err != nil { log.Fatal(err) } @@ -80,7 +82,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(dbModelReq, 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 b4e1bd33..deda060c 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", + dbModelReq, err := model.NewDatabaseModelRequest("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(dbModelReq, client.WithEndpoint(*connection)) if err != nil { log.Fatal(err) } diff --git a/mapper/info.go b/mapper/info.go index 1ad981b6..c7fbd499 100644 --- a/mapper/info.go +++ b/mapper/info.go @@ -2,42 +2,48 @@ package mapper import ( "fmt" + "log" "reflect" "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 objet's 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 nil, fmt.Errorf("FieldByColumn: column %s not found in mapper 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] +// HasColumn returns whether the column exists in the Metadata fields +func (i *Info) HasColumn(column string) bool { + _, 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) + return fmt.Errorf("SetField: column %s not found in mapper 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,18 +59,18 @@ 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 { - return "", fmt.Errorf("field does not have orm column information") + if _, ok := i.Metadata.Fields[column]; !ok { + return "", fmt.Errorf("field does not have mapper column information") } return column, nil } } - return "", fmt.Errorf("field pointer does not correspond to orm struct") + return "", fmt.Errorf("field pointer does not correspond to mapper struct") } // getValidIndexes inspects the object and returns the a list of indexes (set of columns) for witch @@ -74,16 +80,16 @@ 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: for _, idx := range possibleIndexes { for _, col := range idx { - if !i.hasColumn(col) { + if !i.HasColumn(col) { continue OUTER } - columnSchema := i.table.Column(col) + columnSchema := i.Metadata.TableSchema.Column(col) if columnSchema == nil { continue OUTER } @@ -101,7 +107,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{}, compat bool) (*Info, error) { objPtrVal := reflect.ValueOf(obj) if objPtrVal.Type().Kind() != reflect.Ptr { return nil, ovsdb.NewErrWrongType("NewMapperInfo", "pointer to a struct", obj) @@ -122,32 +128,45 @@ func NewInfo(table *ovsdb.TableSchema, obj interface{}) (*Info, error) { } column := table.Column(colName) if column == nil { - return nil, &ErrMapper{ + err := &ErrMapper{ objType: objType.String(), field: field.Name, fieldType: field.Type.String(), fieldTag: colName, reason: "Column does not exist in schema", } + if compat { + log.Printf("Warning: %s. Skipping", err.Error()) + continue + } + return nil, err } // Perform schema-based type checking expType := ovsdb.NativeType(column) if expType != field.Type { - return nil, &ErrMapper{ + err := &ErrMapper{ objType: objType.String(), field: field.Name, fieldType: field.Type.String(), fieldTag: colName, reason: fmt.Sprintf("Wrong type, column expects %s", expType), } + if compat { + log.Printf("Warning: %s. Skipping", err.Error()) + continue + } + return nil, err } fields[colName] = field.Name } 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..7771c473 100644 --- a/mapper/info_test.go +++ b/mapper/info_test.go @@ -39,17 +39,77 @@ func TestNewMapperInfo(t *testing.T) { table []byte obj interface{} expectedCols []string + compat bool err bool } tests := []test{ { - name: "no_orm", + name: "Info from object without tags should return no columns", table: sampleTable, obj: &struct { foo string bar int }{}, - err: false, + expectedCols: []string{}, + compat: false, + err: false, + }, + { + name: "Valid model should contain columns", + table: sampleTable, + obj: &struct { + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + }{}, + expectedCols: []string{"aString", "aInteger"}, + compat: true, + err: false, + }, + { + name: "Extra column no compat should error", + table: sampleTable, + obj: &struct { + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz int `ovsdb:"aNonExistingCol"` + }{}, + expectedCols: []string{"aString", "aInteger"}, + compat: false, + err: true, + }, + { + name: "Extra column compat should not error", + table: sampleTable, + obj: &struct { + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz int `ovsdb:"aNonExistingCol"` + }{}, + expectedCols: []string{"aString", "aInteger"}, + compat: true, + err: false, + }, + { + name: "Different column typ no compat should error", + table: sampleTable, + obj: &struct { + Foo string `ovsdb:"aString"` + Bar string `ovsdb:"aInt"` + }{}, + expectedCols: []string{"aString", "aInt"}, + compat: false, + err: true, + }, + { + name: "Different column type compat should not error", + table: sampleTable, + obj: &struct { + Foo string `ovsdb:"aString"` + Bar string `ovsdb:"aInt"` + }{}, + expectedCols: []string{"aString"}, + compat: true, + err: false, }, } for _, tt := range tests { @@ -58,14 +118,15 @@ 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, tt.compat) if tt.err { assert.NotNil(t, err) } else { assert.Nil(t, err) - } - for _, col := range tt.expectedCols { - assert.Truef(t, info.hasColumn(col), "Expected column should be present in Mapper Info") + for _, col := range tt.expectedCols { + assert.Truef(t, info.HasColumn(col), "Expected column %s should be present in Mapper Info", col) + } + assert.Equal(t, "Test", info.Metadata.TableName) } }) @@ -141,7 +202,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, false) assert.Nil(t, err) err = info.SetField(tt.column, tt.field) @@ -222,7 +283,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, false) assert.Nil(t, err) col, err := info.ColumnByPtr(tt.field) @@ -354,7 +415,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, false) assert.Nil(t, err) indexes, err := info.getValidIndexes() diff --git a/mapper/mapper.go b/mapper/mapper.go index 2a66680c..97fc017f 100644 --- a/mapper/mapper.go +++ b/mapper/mapper.go @@ -36,20 +36,20 @@ 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 -} +//// 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 (e *ErrNoTable) Error() string { - return fmt.Sprintf("Table not found: %s", e.table) -} - -func newErrNoTable(table string) error { - return &ErrNoTable{ - table: table, - } -} +//func newErrNoTable(table string) error { +// return &ErrNoTable{ +// table: table, +// } +//} // NewMapper returns a new mapper func NewMapper(schema *ovsdb.DatabaseSchema) *Mapper { @@ -60,29 +60,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 +86,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 +99,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 +117,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 +135,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 +150,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 +167,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 +179,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 +200,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 +241,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 +256,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 +283,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 +304,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 +334,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..9e304060 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, false) + 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, false) + 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, false) + 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, false) + 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, false) + assert.NoError(t, err) + info2, err := NewInfo("TestTable", schema.Table("TestTable"), &test.obj2, false) + 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, false) + assert.NoError(t, err) + info2, err := NewInfo("TestTable", schema.Table("TestTable"), &obj2, false) + 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, false) + 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, false) + 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/database.go b/model/database.go new file mode 100644 index 00000000..7e15ffd3 --- /dev/null +++ b/model/database.go @@ -0,0 +1,155 @@ +package model + +import ( + "fmt" + "reflect" + + "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 DatabaseModelRequest and the server's Schema +type DatabaseModel struct { + valid bool + request *DatabaseModelRequest + schema *ovsdb.DatabaseSchema + mapper *mapper.Mapper + metadata map[string]*mapper.Metadata +} + +// NewDatabaseModel returns a new DatabaseModel +func NewDatabaseModel(schema *ovsdb.DatabaseSchema, request *DatabaseModelRequest) (*DatabaseModel, []error) { + dbModel := NewPartialDatabaseModel(request) + 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(request *DatabaseModelRequest) *DatabaseModel { + return &DatabaseModel{ + valid: false, + request: request, + } +} + +// Valid returns whether the DatabaseModel is fully functional +func (db *DatabaseModel) Valid() bool { + return db.valid +} + +// SetSchema adds the Schema to the DatabaseModel making it valid if it was not before +func (db *DatabaseModel) SetSchema(schema *ovsdb.DatabaseSchema) []error { + errors := db.request.validate(schema) + if len(errors) > 0 { + return errors + } + db.schema = schema + db.mapper = mapper.NewMapper(schema) + errs := db.generateModelInfo() + if len(errs) > 0 { + return errs + } + db.valid = true + return []error{} +} + +// ClearSchema removes the Schema from the DatabaseModel making it not valid +func (db *DatabaseModel) ClearSchema() { + db.schema = nil + db.mapper = nil + db.valid = false +} + +// Request returns the DatabaseModel's request +func (db *DatabaseModel) Request() *DatabaseModelRequest { + return db.request +} + +// Schema returns the DatabaseModel's schema +func (db *DatabaseModel) Schema() *ovsdb.DatabaseSchema { + return db.schema +} + +// Mapper returns the DatabaseModel's mapper +func (db *DatabaseModel) Mapper() *mapper.Mapper { + 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.request.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.request.types +} + +// FindTable returns the string associated with a reflect.Type or "" +func (db *DatabaseModel) FindTable(mType reflect.Type) string { + for table, tType := range db.request.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[string]*mapper.Metadata, len(db.request.types)) + for tableName := range db.request.types { + tableSchema := db.schema.Table(tableName) + if tableSchema == nil { + continue + } + obj, err := db.NewModel(tableName) + if err != nil { + errors = append(errors, err) + continue + } + info, err := mapper.NewInfo(tableName, tableSchema, obj, db.request.compat) + if err != nil { + errors = append(errors, err) + continue + } + metadata[tableName] = 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[db.FindTable(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 +} + +func (db *DatabaseModel) HasColumn(tableName, column string) bool { + meta, ok := db.metadata[tableName] + if !ok { + return false + } + _, ok = meta.Fields[column] + return ok +} diff --git a/model/database_test.go b/model/database_test.go new file mode 100644 index 00000000..8f377146 --- /dev/null +++ b/model/database_test.go @@ -0,0 +1,288 @@ +package model + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/ovn-org/libovsdb/ovsdb" + "github.com/stretchr/testify/require" +) + +var tableA = ` + "TableA": { + "columns": { + "aString": { + "type": "string" + }, + "aInteger": { + "type": "integer" + }, + "aSet": { + "type": { + "key": "string", + "max": "unlimited", + "min": 0 + } + } + } +}` +var tableB = ` + "TableB": { + "columns": { + "aString": { + "type": "string" + }, + "aInteger": { + "type": "integer" + }, + "aSet": { + "type": { + "key": "string", + "max": "unlimited", + "min": 0 + } + } + } +}` + +var schema = ` { + "cksum": "223619766 22548", + "name": "TestSchema", + "tables": {` + tableA + "," + tableB + ` + } + } +` + +func TestNewDatabaseModel(t *testing.T) { + + tests := []struct { + name string + schema string + requestTypes map[string]Model + compat bool + expectedCols map[string][]string + expectedNotCols map[string][]string + err bool + }{ + { + name: "Fully matching model should succeed", + schema: schema, + requestTypes: map[string]Model{ + "TableA": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + }{}, + "TableB": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + }{}, + }, + expectedCols: map[string][]string{ + "TableA": {"aString", "aInteger", "aSet"}, + "TableB": {"aString", "aInteger", "aSet"}, + }, + compat: false, + err: false, + }, + { + name: "Model with less tables should succeed", + schema: schema, + requestTypes: map[string]Model{ + "TableA": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + }{}, + }, + expectedCols: map[string][]string{ + "TableA": {"aString", "aInteger", "aSet"}, + }, + compat: false, + err: false, + }, + { + name: "Model with less tables should succeed", + schema: schema, + requestTypes: map[string]Model{ + "TableA": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + }{}, + }, + expectedCols: map[string][]string{ + "TableA": {"aString", "aInteger", "aSet"}, + }, + compat: false, + err: false, + }, + { + name: "Model more tables should fail", + schema: schema, + requestTypes: map[string]Model{ + "TableA": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + }{}, + "TableB": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + }{}, + "TableC": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + }{}, + }, + compat: false, + err: true, + }, + { + name: "Model with more tables (compat) should succeed", + schema: schema, + requestTypes: map[string]Model{ + "TableA": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + }{}, + "TableB": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + }{}, + "TableC": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + }{}, + }, + expectedCols: map[string][]string{ + "TableA": {"aString", "aInteger", "aSet"}, + "TableB": {"aString", "aInteger", "aSet"}, + }, + expectedNotCols: map[string][]string{ + "TableC": {"aString", "aInteger", "aSet"}, + }, + compat: true, + err: false, + }, + { + name: "Model with more columns should fail", + schema: schema, + requestTypes: map[string]Model{ + "TableA": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + FooBar []string `ovsdb:"aSecondSet"` + }{}, + "TableB": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + }{}, + }, + compat: false, + err: true, + }, + { + name: "Model with more columns (compat) should succeed", + schema: schema, + requestTypes: map[string]Model{ + "TableA": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + FooBar []string `ovsdb:"aSecondSet"` + }{}, + "TableB": &struct { + UUID string `ovsdb:"_uuid"` + Foo string `ovsdb:"aString"` + Bar int `ovsdb:"aInteger"` + Baz []string `ovsdb:"aSet"` + }{}, + }, + expectedCols: map[string][]string{ + "TableA": {"aString", "aInteger", "aSet"}, + "TableB": {"aString", "aInteger", "aSet"}, + }, + expectedNotCols: map[string][]string{ + "TableA": {"aSecondSet"}, + }, + compat: false, + err: true, + }, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("NewDatabaseModel%s", tt.name), func(t *testing.T) { + var schema ovsdb.DatabaseSchema + err := json.Unmarshal([]byte(tt.schema), &schema) + require.NoError(t, err) + req, err := NewDatabaseModelRequest("TestSchema", tt.requestTypes) + if tt.compat { + req.SetCompatibility(true) + } + require.NoError(t, err) + + // Test single step creation + db, errs := NewDatabaseModel(&schema, req) + + if tt.err { + require.NotEmpty(t, errs) + } else { + require.Empty(t, errs) + for table, cols := range tt.expectedCols { + for _, col := range cols { + require.Truef(t, db.HasColumn(table, col), "table %s column %s should be present in model", table, col) + } + } + for table, cols := range tt.expectedNotCols { + for _, col := range cols { + require.Falsef(t, db.HasColumn(table, col), "table %s column %s should not be present in model", table, col) + } + } + } + + // Test 2-step step creation + db = NewPartialDatabaseModel(req) + errs = db.SetSchema(&schema) + + if tt.err { + require.NotEmpty(t, errs) + } else { + require.Empty(t, errs) + for table, cols := range tt.expectedCols { + for _, col := range cols { + require.Truef(t, db.HasColumn(table, col), "table %s column %s should be present in model", table, col) + } + } + for table, cols := range tt.expectedNotCols { + for _, col := range cols { + require.Falsef(t, db.HasColumn(table, col), "table %s column %s should not be present in model", table, col) + } + } + } + + }) + } + +} 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..84d1b765 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 TestDatabaseModelRequest(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 := NewDatabaseModelRequest(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 := NewDatabaseModelRequest("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 := NewDatabaseModelRequest("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/model/request.go b/model/request.go new file mode 100644 index 00000000..c62fb74d --- /dev/null +++ b/model/request.go @@ -0,0 +1,100 @@ +package model + +import ( + "fmt" + "log" + "reflect" + + "github.com/ovn-org/libovsdb/mapper" + "github.com/ovn-org/libovsdb/ovsdb" +) + +// DatabaseModelRequest contains the information needed to build a DatabaseModel +type DatabaseModelRequest struct { + name string + types map[string]reflect.Type + compat bool +} + +// NewModel returns a new instance of a model from a specific string +func (db *DatabaseModelRequest) 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 +} + +// SetCompatible requests the DatabaseModel to try to be compatible with the server schema. +// On this mode, the model will try to fill in fields if they exist on the database but will not fail +// if additional columns or tables are declared in the model. +func (db *DatabaseModelRequest) SetCompatibility(compat bool) { + db.compat = compat +} + +// Name returns the database name +func (db *DatabaseModelRequest) Name() string { + return db.name +} + +// Validate validates the DatabaseModel against the input schema +// Returns all the errors detected +func (db *DatabaseModelRequest) 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 { + err := fmt.Errorf("database model contains a model for table %s that does not exist in schema", tableName) + if db.compat { + log.Printf("Warning: %s. Skipping", err.Error()) + } else { + errors = append(errors, err) + } + continue + } + model, err := db.newModel(tableName) + if err != nil { + errors = append(errors, err) + continue + } + _, err = mapper.NewInfo(tableName, tableSchema, model, db.compat) + if err != nil { + errors = append(errors, err) + } + } + return errors +} + +// NewDatabaseModelRequest constructs a DatabaseModelRequest based on a database name and dictionary of models indexed by table name +func NewDatabaseModelRequest(name string, models map[string]Model) (*DatabaseModelRequest, 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 &DatabaseModelRequest{ + types: types, + name: name, + compat: false, + }, nil +} diff --git a/modelgen/dbmodel.go b/modelgen/dbmodel.go index bdff8a61..35faa9e1 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 DatabaseModelRequest 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.DatabaseModelRequest, error) { + return model.NewDatabaseModelRequest("{{ 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..8b3d6093 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.DatabaseModelRequest, error) { + return model.NewDatabaseModelRequest("AtomicDB", map[string]model.Model{ "atomicTable": &AtomicTable{}, }) } diff --git a/ovsdb/serverdb/model.go b/ovsdb/serverdb/model.go index 511112f8..7a8dc9a3 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.DatabaseModelRequest, error) { + return model.NewDatabaseModelRequest("_Server", map[string]model.Model{ "Database": &Database{}, }) } diff --git a/server/database.go b/server/database.go index ffb7c0ea..46acd7a5 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.DatabaseModelRequest mutex sync.RWMutex } -func NewInMemoryDatabase(models map[string]*model.DBModel) Database { +func NewInMemoryDatabase(models map[string]*model.DatabaseModelRequest) 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.DatabaseModelRequest 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) + 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) if err != nil { return nil } diff --git a/server/server.go b/server/server.go index 3450a8ff..6cf292df 100644 --- a/server/server.go +++ b/server/server.go @@ -22,34 +22,34 @@ 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 -} +//type DatabaseModel struct { +// Model *model.DatabaseModelRequest +// 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 +113,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 +132,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 +141,8 @@ type Transaction struct { Cache *cache.TableCache } -func NewTransaction(schema *ovsdb.DatabaseSchema, model *model.DBModel) Transaction { - cache, err := cache.NewTableCache(schema, model, nil) +func NewTransaction(schema *ovsdb.DatabaseSchema, model *model.DatabaseModel) Transaction { + cache, err := cache.NewTableCache(model, nil) if err != nil { panic(err) } diff --git a/server/server_integration_test.go b/server/server_integration_test.go index 24a0a934..6da49181 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.NewDatabaseModelRequest("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.DatabaseModelRequest{"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.NewDatabaseModelRequest("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.DatabaseModelRequest{"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.NewDatabaseModelRequest("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.DatabaseModelRequest{"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.NewDatabaseModelRequest("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.DatabaseModelRequest{"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.NewDatabaseModelRequest("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.DatabaseModelRequest{"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.NewDatabaseModelRequest("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.DatabaseModelRequest{"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..5e93a2a1 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.NewDatabaseModelRequest("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.DatabaseModelRequest{"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..d7c6b870 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.NewDatabaseModelRequest("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.DatabaseModelRequest{"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.NewDatabaseModelRequest("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.DatabaseModelRequest{"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.NewDatabaseModelRequest("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.DatabaseModelRequest{"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..5246295e 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.NewDatabaseModelRequest("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}, "IPFIX": &ipfixType{},