Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement test server referential integrity #375

Merged
merged 13 commits into from
Jan 25, 2024
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ This package is divided into several sub-packages. Documentation for each sub-pa
* **cache**: model-based cache [![godoc for libovsdb/cache][cachebadge]][cachedoc]
* **modelgen**: common code-generator functions [![godoc for libovsdb/modelgen][genbadge]][gendoc]
* **server**: ovsdb test server [![godoc for libovsdb/server][serverbadge]][serverdoc]
* **database**: in-memory database for the server [![godoc for libovsdb/database][dbbadge]][dbdoc]
* **database**: database related types, interfaces and implementations [![godoc for libovsdb/database][dbbadge]][dbdoc]
* **updates**: common code to handle model updates [![godoc for libovsdb/updates][updatesbadge]][updatesdoc]

[doc]: https://pkg.go.dev/
Expand Down
6 changes: 3 additions & 3 deletions client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ type API interface {
Get(context.Context, model.Model) error

// Create returns the operation needed to add the model(s) to the Database
// Only fields with non-default values will be added to the transaction
// If the field associated with column "_uuid" has some content, it will be
// treated as named-uuid
// Only fields with non-default values will be added to the transaction. If
// the field associated with column "_uuid" has some content other than a
// UUID, it will be treated as named-uuid
Create(...model.Model) ([]ovsdb.Operation, error)
}

Expand Down
4 changes: 2 additions & 2 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"github.com/go-logr/stdr"
"github.com/google/uuid"
"github.com/ovn-org/libovsdb/cache"
db "github.com/ovn-org/libovsdb/database"
"github.com/ovn-org/libovsdb/database/inmemory"
"github.com/ovn-org/libovsdb/mapper"
"github.com/ovn-org/libovsdb/model"
"github.com/ovn-org/libovsdb/ovsdb"
Expand Down Expand Up @@ -956,7 +956,7 @@ func newOVSDBServer(t *testing.T, dbModel model.ClientDBModel, schema ovsdb.Data
require.NoError(t, err)
serverSchema := serverdb.Schema()

db := db.NewInMemoryDatabase(map[string]model.ClientDBModel{
db := inmemory.NewDatabase(map[string]model.ClientDBModel{
schema.Name: dbModel,
serverSchema.Name: serverDBModel,
})
Expand Down
111 changes: 11 additions & 100 deletions database/database.go
Original file line number Diff line number Diff line change
@@ -1,122 +1,33 @@
package database

import (
"fmt"
"sync"

"github.com/google/uuid"
"github.com/ovn-org/libovsdb/cache"
"github.com/ovn-org/libovsdb/model"
"github.com/ovn-org/libovsdb/ovsdb"
)

// Database abstracts database operations from ovsdb
// Database abstracts a database that a server can use to store and transact data
type Database interface {
CreateDatabase(database string, model ovsdb.DatabaseSchema) error
Exists(database string) bool
NewTransaction(database string) Transaction
Commit(database string, id uuid.UUID, update Update) error
CheckIndexes(database string, table string, m model.Model) error
List(database, table string, conditions ...ovsdb.Condition) (map[string]model.Model, error)
Get(database, table string, uuid string) (model.Model, error)
GetReferences(database, table, row string) (References, error)
}

// Transaction abstracts a database transaction that can generate database
// updates
type Transaction interface {
Transact(operations ...ovsdb.Operation) ([]*ovsdb.OperationResult, Update)
}

// Update abstacts a database update in both ovsdb and model notation
// Update abstracts an update that can be committed to a database
type Update interface {
GetUpdatedTables() []string
ForEachModelUpdate(table string, do func(uuid string, old, new model.Model) error) error
ForEachRowUpdate(table string, do func(uuid string, row ovsdb.RowUpdate2) error) error
}

type inMemoryDatabase struct {
databases map[string]*cache.TableCache
models map[string]model.ClientDBModel
mutex sync.RWMutex
}

func NewInMemoryDatabase(models map[string]model.ClientDBModel) Database {
return &inMemoryDatabase{
databases: make(map[string]*cache.TableCache),
models: models,
mutex: sync.RWMutex{},
}
}

func (db *inMemoryDatabase) CreateDatabase(name string, schema ovsdb.DatabaseSchema) error {
db.mutex.Lock()
defer db.mutex.Unlock()
var mo model.ClientDBModel
var ok bool
if mo, ok = db.models[schema.Name]; !ok {
return fmt.Errorf("no db model provided for schema with name %s", name)
}
dbModel, errs := model.NewDatabaseModel(schema, mo)
if len(errs) > 0 {
return fmt.Errorf("failed to create DatabaseModel: %#+v", errs)
}
database, err := cache.NewTableCache(dbModel, nil, nil)
if err != nil {
return err
}
db.databases[name] = database
return nil
}

func (db *inMemoryDatabase) Exists(name string) bool {
db.mutex.RLock()
defer db.mutex.RUnlock()
_, ok := db.databases[name]
return ok
}

func (db *inMemoryDatabase) Commit(database string, id uuid.UUID, update Update) error {
if !db.Exists(database) {
return fmt.Errorf("db does not exist")
}
db.mutex.RLock()
targetDb := db.databases[database]
db.mutex.RUnlock()

return targetDb.ApplyCacheUpdate(update)
}

func (db *inMemoryDatabase) CheckIndexes(database string, table string, m model.Model) error {
if !db.Exists(database) {
return nil
}
db.mutex.RLock()
targetDb := db.databases[database]
db.mutex.RUnlock()
targetTable := targetDb.Table(table)
return targetTable.IndexExists(m)
}

func (db *inMemoryDatabase) List(database, table string, conditions ...ovsdb.Condition) (map[string]model.Model, error) {
if !db.Exists(database) {
return nil, fmt.Errorf("db does not exist")
}
db.mutex.RLock()
targetDb := db.databases[database]
db.mutex.RUnlock()

targetTable := targetDb.Table(table)
if targetTable == nil {
return nil, fmt.Errorf("table does not exist")
}

return targetTable.RowsByCondition(conditions)
}

func (db *inMemoryDatabase) Get(database, table string, uuid string) (model.Model, error) {
if !db.Exists(database) {
return nil, fmt.Errorf("db does not exist")
}
db.mutex.RLock()
targetDb := db.databases[database]
db.mutex.RUnlock()

targetTable := targetDb.Table(table)
if targetTable == nil {
return nil, fmt.Errorf("table does not exist")
}
return targetTable.Row(uuid), nil
ForReferenceUpdates(do func(references References) error) error
}
3 changes: 2 additions & 1 deletion database/doc.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/*
Package database provides an in-memory database implementation.
Package database collects database related types, interfaces and
implementations.
*/
package database
4 changes: 4 additions & 0 deletions database/inmemory/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*
Package inmemory provides a in-memory database implementation
*/
package inmemory
145 changes: 145 additions & 0 deletions database/inmemory/inmemory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package inmemory

import (
"fmt"
"log"
"os"
"sync"

"github.com/go-logr/logr"
"github.com/go-logr/stdr"
"github.com/google/uuid"
"github.com/ovn-org/libovsdb/cache"
dbase "github.com/ovn-org/libovsdb/database"
"github.com/ovn-org/libovsdb/database/transaction"
"github.com/ovn-org/libovsdb/model"
"github.com/ovn-org/libovsdb/ovsdb"
)

type inMemoryDatabase struct {
databases map[string]*cache.TableCache
models map[string]model.ClientDBModel
references map[string]dbase.References
logger *logr.Logger
mutex sync.RWMutex
}

func NewDatabase(models map[string]model.ClientDBModel) dbase.Database {
logger := stdr.NewWithOptions(log.New(os.Stderr, "", log.LstdFlags), stdr.Options{LogCaller: stdr.All}).WithName("database")
return &inMemoryDatabase{
databases: make(map[string]*cache.TableCache),
models: models,
references: make(map[string]dbase.References),
mutex: sync.RWMutex{},
logger: &logger,
}
}

func (db *inMemoryDatabase) NewTransaction(dbName string) dbase.Transaction {
db.mutex.Lock()
defer db.mutex.Unlock()
var model model.DatabaseModel
if database, ok := db.databases[dbName]; ok {
model = database.DatabaseModel()
}
transaction := transaction.NewTransaction(model, dbName, db, db.logger)
return &transaction
}

func (db *inMemoryDatabase) CreateDatabase(name string, schema ovsdb.DatabaseSchema) error {
db.mutex.Lock()
defer db.mutex.Unlock()
var mo model.ClientDBModel
var ok bool
if mo, ok = db.models[schema.Name]; !ok {
return fmt.Errorf("no db model provided for schema with name %s", name)
}
dbModel, errs := model.NewDatabaseModel(schema, mo)
if len(errs) > 0 {
return fmt.Errorf("failed to create DatabaseModel: %#+v", errs)
}
database, err := cache.NewTableCache(dbModel, nil, nil)
if err != nil {
return err
}
db.databases[name] = database
db.references[name] = make(dbase.References)
return nil
}

func (db *inMemoryDatabase) Exists(name string) bool {
db.mutex.RLock()
defer db.mutex.RUnlock()
_, ok := db.databases[name]
return ok
}

func (db *inMemoryDatabase) Commit(database string, id uuid.UUID, update dbase.Update) error {
if !db.Exists(database) {
return fmt.Errorf("db does not exist")
}
db.mutex.RLock()
targetDb := db.databases[database]
db.mutex.RUnlock()

err := targetDb.ApplyCacheUpdate(update)
if err != nil {
return err
}

return update.ForReferenceUpdates(func(references dbase.References) error {
db.references[database].UpdateReferences(references)
return nil
})
}

func (db *inMemoryDatabase) CheckIndexes(database string, table string, m model.Model) error {
if !db.Exists(database) {
return nil
}
db.mutex.RLock()
targetDb := db.databases[database]
db.mutex.RUnlock()
targetTable := targetDb.Table(table)
return targetTable.IndexExists(m)
}

func (db *inMemoryDatabase) List(database, table string, conditions ...ovsdb.Condition) (map[string]model.Model, error) {
if !db.Exists(database) {
return nil, fmt.Errorf("db does not exist")
}
db.mutex.RLock()
targetDb := db.databases[database]
db.mutex.RUnlock()

targetTable := targetDb.Table(table)
if targetTable == nil {
return nil, fmt.Errorf("table does not exist")
}

return targetTable.RowsByCondition(conditions)
}

func (db *inMemoryDatabase) Get(database, table string, uuid string) (model.Model, error) {
if !db.Exists(database) {
return nil, fmt.Errorf("db does not exist")
}
db.mutex.RLock()
targetDb := db.databases[database]
db.mutex.RUnlock()

targetTable := targetDb.Table(table)
if targetTable == nil {
return nil, fmt.Errorf("table does not exist")
}
return targetTable.Row(uuid), nil
}

func (db *inMemoryDatabase) GetReferences(database, table, row string) (dbase.References, error) {
if !db.Exists(database) {
return nil, fmt.Errorf("db does not exist")
}
db.mutex.RLock()
defer db.mutex.RUnlock()
return db.references[database].GetReferences(table, row), nil
}
Loading
Loading