Skip to content

Commit

Permalink
Merge pull request #375 from jcaamano/references
Browse files Browse the repository at this point in the history
Implement test server referential integrity
  • Loading branch information
jcaamano authored Jan 25, 2024
2 parents c71cb09 + 690361f commit 03f787b
Show file tree
Hide file tree
Showing 28 changed files with 4,642 additions and 411 deletions.
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

0 comments on commit 03f787b

Please sign in to comment.