VOL-2868 Model simplification/removal.
Reduced the model to its most commonly used functions. (Removed unused logic & test cases.)
Reworked remaining functions to be more intuitive to use, and to more closely follow golang conventions.
Change-Id: Ibbb267ff37e039b73489b4379aa2654208614d5b
diff --git a/db/model/proxy.go b/db/model/proxy.go
index 73ea70d..997ebe4 100644
--- a/db/model/proxy.go
+++ b/db/model/proxy.go
@@ -18,495 +18,149 @@
import (
"context"
- "crypto/md5"
"errors"
"fmt"
- "reflect"
- "runtime"
- "strings"
- "sync"
-
+ "github.com/gogo/protobuf/proto"
+ "github.com/opencord/voltha-lib-go/v3/pkg/db"
"github.com/opencord/voltha-lib-go/v3/pkg/log"
+ "reflect"
)
-// OperationContext holds details on the information used during an operation
-type OperationContext struct {
- Path string
- Data interface{}
- FieldName string
- ChildKey string
-}
+// RequestTimestamp attribute used to store a timestamp in the context object
+const RequestTimestamp contextKey = "request-timestamp"
-// NewOperationContext instantiates a new OperationContext structure
-func NewOperationContext(path string, data interface{}, fieldName string, childKey string) *OperationContext {
- oc := &OperationContext{
- Path: path,
- Data: data,
- FieldName: fieldName,
- ChildKey: childKey,
- }
- return oc
-}
-
-// Update applies new data to the context structure
-func (oc *OperationContext) Update(data interface{}) *OperationContext {
- oc.Data = data
- return oc
-}
+type contextKey string
// Proxy holds the information for a specific location with the data model
type Proxy struct {
- mutex sync.RWMutex
- Root *root
- Node *node
- ParentNode *node
- Path string
- FullPath string
- Exclusive bool
- Callbacks map[CallbackType]map[string]*CallbackTuple
- operation ProxyOperation
+ kvStore *db.Backend
+ path string
}
// NewProxy instantiates a new proxy to a specific location
-func NewProxy(root *root, node *node, parentNode *node, path string, fullPath string, exclusive bool) *Proxy {
- callbacks := make(map[CallbackType]map[string]*CallbackTuple)
- if fullPath == "/" {
- fullPath = ""
- }
- p := &Proxy{
- Root: root,
- Node: node,
- ParentNode: parentNode,
- Exclusive: exclusive,
- Path: path,
- FullPath: fullPath,
- Callbacks: callbacks,
- }
- return p
-}
-
-// getRoot returns the root attribute of the proxy
-func (p *Proxy) getRoot() *root {
- return p.Root
-}
-
-// getPath returns the path attribute of the proxy
-func (p *Proxy) getPath() string {
- return p.Path
-}
-
-// getFullPath returns the full path attribute of the proxy
-func (p *Proxy) getFullPath() string {
- return p.FullPath
-}
-
-// getCallbacks returns the full list of callbacks associated to the proxy
-func (p *Proxy) getCallbacks(callbackType CallbackType) map[string]*CallbackTuple {
- p.mutex.RLock()
- defer p.mutex.RUnlock()
-
- if p != nil {
- if cb, exists := p.Callbacks[callbackType]; exists {
- return cb
- }
- } else {
- logger.Debugw("proxy-is-nil", log.Fields{"callback-type": callbackType.String()})
- }
- return nil
-}
-
-// getCallback returns a specific callback matching the type and function hash
-func (p *Proxy) getCallback(callbackType CallbackType, funcHash string) *CallbackTuple {
- p.mutex.Lock()
- defer p.mutex.Unlock()
- if tuple, exists := p.Callbacks[callbackType][funcHash]; exists {
- return tuple
- }
- return nil
-}
-
-// setCallbacks applies a callbacks list to a type
-func (p *Proxy) setCallbacks(callbackType CallbackType, callbacks map[string]*CallbackTuple) {
- p.mutex.Lock()
- defer p.mutex.Unlock()
- p.Callbacks[callbackType] = callbacks
-}
-
-// setCallback applies a callback to a type and hash value
-func (p *Proxy) setCallback(callbackType CallbackType, funcHash string, tuple *CallbackTuple) {
- p.mutex.Lock()
- defer p.mutex.Unlock()
- p.Callbacks[callbackType][funcHash] = tuple
-}
-
-// DeleteCallback removes a callback matching the type and hash
-func (p *Proxy) DeleteCallback(callbackType CallbackType, funcHash string) {
- p.mutex.Lock()
- defer p.mutex.Unlock()
- delete(p.Callbacks[callbackType], funcHash)
-}
-
-// ProxyOperation callbackType is an enumerated value to express when a callback should be executed
-type ProxyOperation uint8
-
-// Enumerated list of callback types
-const (
- ProxyNone ProxyOperation = iota
- ProxyGet
- ProxyList
- ProxyAdd
- ProxyUpdate
- ProxyRemove
- ProxyCreate
- ProxyWatch
-)
-
-var proxyOperationTypes = []string{
- "PROXY_NONE",
- "PROXY_GET",
- "PROXY_LIST",
- "PROXY_ADD",
- "PROXY_UPDATE",
- "PROXY_REMOVE",
- "PROXY_CREATE",
- "PROXY_WATCH",
-}
-
-func (t ProxyOperation) String() string {
- return proxyOperationTypes[t]
-}
-
-// GetOperation -
-func (p *Proxy) GetOperation() ProxyOperation {
- p.mutex.RLock()
- defer p.mutex.RUnlock()
- return p.operation
-}
-
-// SetOperation -
-func (p *Proxy) SetOperation(operation ProxyOperation) {
- p.mutex.Lock()
- defer p.mutex.Unlock()
- p.operation = operation
-}
-
-// List will retrieve information from the data model at the specified path location
-// A list operation will force access to persistence storage
-func (p *Proxy) List(ctx context.Context, path string, depth int, deep bool, txid string) (interface{}, error) {
- var effectivePath string
+func NewProxy(kvStore *db.Backend, path string) *Proxy {
if path == "/" {
- effectivePath = p.getFullPath()
- } else {
- effectivePath = p.getFullPath() + path
+ path = ""
}
+ return &Proxy{
+ kvStore: kvStore,
+ path: path,
+ }
+}
- p.SetOperation(ProxyList)
- defer p.SetOperation(ProxyNone)
+// List will retrieve information from the data model at the specified path location, and write it to the target slice
+// target must be a type of the form *[]<proto.Message Type> For example: *[]*voltha.Device
+func (p *Proxy) List(ctx context.Context, path string, target interface{}) error {
+ completePath := p.path + path
logger.Debugw("proxy-list", log.Fields{
- "path": path,
- "effective": effectivePath,
- "operation": p.GetOperation(),
+ "path": completePath,
})
- return p.getRoot().List(ctx, path, "", depth, deep, txid)
-}
-// Get will retrieve information from the data model at the specified path location
-func (p *Proxy) Get(ctx context.Context, path string, depth int, deep bool, txid string) (interface{}, error) {
- var effectivePath string
- if path == "/" {
- effectivePath = p.getFullPath()
- } else {
- effectivePath = p.getFullPath() + path
+ // verify type of target is *[]*<type>
+ pointerType := reflect.TypeOf(target) // *[]*<type>
+ if pointerType.Kind() != reflect.Ptr {
+ return errors.New("target is not of type *[]*<type>")
+ }
+ sliceType := pointerType.Elem() // []*type
+ if sliceType.Kind() != reflect.Slice {
+ return errors.New("target is not of type *[]*<type>")
+ }
+ elemType := sliceType.Elem() // *type
+ if sliceType.Implements(reflect.TypeOf((*proto.Message)(nil)).Elem()) {
+ return errors.New("target slice does not contain elements of type proto.Message")
+ }
+ dataType := elemType.Elem() // type
+
+ blobs, err := p.kvStore.List(ctx, completePath)
+ if err != nil {
+ return fmt.Errorf("failed to retrieve %s from kvstore: %s", path, err)
}
- p.SetOperation(ProxyGet)
- defer p.SetOperation(ProxyNone)
-
- logger.Debugw("proxy-get", log.Fields{
- "path": path,
- "effective": effectivePath,
- "operation": p.GetOperation(),
+ logger.Debugw("parsing-data-blobs", log.Fields{
+ "path": path,
+ "size": len(blobs),
})
- return p.getRoot().Get(ctx, path, "", depth, deep, txid)
+ ret := reflect.MakeSlice(sliceType, len(blobs), len(blobs))
+ i := 0
+ for _, blob := range blobs {
+ data := reflect.New(dataType)
+ if err := proto.Unmarshal(blob.Value.([]byte), data.Interface().(proto.Message)); err != nil {
+ return fmt.Errorf("failed to unmarshal %s: %s", blob.Key, err)
+ }
+ ret.Index(i).Set(data)
+ i++
+ }
+ reflect.ValueOf(target).Elem().Set(ret)
+ return nil
+}
+
+// Get will retrieve information from the data model at the specified path location, and write it to target
+func (p *Proxy) Get(ctx context.Context, path string, target proto.Message) (bool, error) {
+ completePath := p.path + path
+
+ logger.Debugw("proxy-get", log.Fields{
+ "path": completePath,
+ })
+
+ blob, err := p.kvStore.Get(ctx, completePath)
+ if err != nil {
+ return false, fmt.Errorf("failed to retrieve %s from kvstore: %s", path, err)
+ } else if blob == nil {
+ return false, nil // this blob does not exist
+ }
+
+ logger.Debugw("parsing-data-blobs", log.Fields{
+ "path": path,
+ })
+
+ if err := proto.Unmarshal(blob.Value.([]byte), target); err != nil {
+ return false, fmt.Errorf("failed to unmarshal %s: %s", blob.Key, err)
+ }
+ return true, nil
}
// Update will modify information in the data model at the specified location with the provided data
-func (p *Proxy) Update(ctx context.Context, path string, data interface{}, strict bool, txid string) (interface{}, error) {
- if !strings.HasPrefix(path, "/") {
- logger.Errorf("invalid path: %s", path)
- return nil, fmt.Errorf("invalid path: %s", path)
- }
- var fullPath string
- var effectivePath string
- if path == "/" {
- fullPath = p.getPath()
- effectivePath = p.getFullPath()
- } else {
- fullPath = p.getPath() + path
- effectivePath = p.getFullPath() + path
- }
-
- p.SetOperation(ProxyUpdate)
- defer p.SetOperation(ProxyNone)
-
- logger.Debugw("proxy-update", log.Fields{
- "path": path,
- "effective": effectivePath,
- "full": fullPath,
- "operation": p.GetOperation(),
- })
-
- result := p.getRoot().Update(ctx, fullPath, data, strict, txid, nil)
-
- if result != nil {
- return result.GetData(), nil
- }
-
- return nil, nil
+func (p *Proxy) Update(ctx context.Context, path string, data proto.Message) error {
+ return p.add(ctx, path, data)
}
// AddWithID will insert new data at specified location.
-// This method also allows the user to specify the ID of the data entry to ensure
-// that access control is active while inserting the information.
-func (p *Proxy) AddWithID(ctx context.Context, path string, id string, data interface{}, txid string) (interface{}, error) {
- if !strings.HasPrefix(path, "/") {
- logger.Errorf("invalid path: %s", path)
- return nil, fmt.Errorf("invalid path: %s", path)
- }
- var fullPath string
- var effectivePath string
- if path == "/" {
- fullPath = p.getPath()
- effectivePath = p.getFullPath()
- } else {
- fullPath = p.getPath() + path
- effectivePath = p.getFullPath() + path + "/" + id
- }
-
- p.SetOperation(ProxyAdd)
- defer p.SetOperation(ProxyNone)
-
- logger.Debugw("proxy-add-with-id", log.Fields{
- "path": path,
- "effective": effectivePath,
- "full": fullPath,
- "operation": p.GetOperation(),
- })
-
- result := p.getRoot().Add(ctx, fullPath, data, txid, nil)
-
- if result != nil {
- return result.GetData(), nil
- }
-
- return nil, nil
+// This method also allows the user to specify the ID.
+func (p *Proxy) AddWithID(ctx context.Context, path string, id string, data proto.Message) error {
+ return p.add(ctx, path+"/"+id, data)
}
-// Add will insert new data at specified location.
-func (p *Proxy) Add(ctx context.Context, path string, data interface{}, txid string) (interface{}, error) {
- if !strings.HasPrefix(path, "/") {
- logger.Errorf("invalid path: %s", path)
- return nil, fmt.Errorf("invalid path: %s", path)
- }
- var fullPath string
- var effectivePath string
- if path == "/" {
- fullPath = p.getPath()
- effectivePath = p.getFullPath()
- } else {
- fullPath = p.getPath() + path
- effectivePath = p.getFullPath() + path
- }
-
- p.SetOperation(ProxyAdd)
- defer p.SetOperation(ProxyNone)
+// add will insert new data at specified location.
+func (p *Proxy) add(ctx context.Context, path string, data proto.Message) error {
+ completePath := p.path + path
logger.Debugw("proxy-add", log.Fields{
- "path": path,
- "effective": effectivePath,
- "full": fullPath,
- "operation": p.GetOperation(),
+ "path": completePath,
})
- result := p.getRoot().Add(ctx, fullPath, data, txid, nil)
-
- if result != nil {
- return result.GetData(), nil
+ blob, err := proto.Marshal(data)
+ if err != nil {
+ return fmt.Errorf("unable to save to kvStore, error marshalling: %s", err)
}
- return nil, nil
+ if err := p.kvStore.Put(ctx, completePath, blob); err != nil {
+ return fmt.Errorf("unable to write to kvStore: %s", err)
+ }
+ return nil
}
// Remove will delete an entry at the specified location
-func (p *Proxy) Remove(ctx context.Context, path string, txid string) (interface{}, error) {
- if !strings.HasPrefix(path, "/") {
- logger.Errorf("invalid path: %s", path)
- return nil, fmt.Errorf("invalid path: %s", path)
- }
- var fullPath string
- var effectivePath string
- if path == "/" {
- fullPath = p.getPath()
- effectivePath = p.getFullPath()
- } else {
- fullPath = p.getPath() + path
- effectivePath = p.getFullPath() + path
- }
-
- p.SetOperation(ProxyRemove)
- defer p.SetOperation(ProxyNone)
+func (p *Proxy) Remove(ctx context.Context, path string) error {
+ completePath := p.path + path
logger.Debugw("proxy-remove", log.Fields{
- "path": path,
- "effective": effectivePath,
- "full": fullPath,
- "operation": p.GetOperation(),
+ "path": completePath,
})
- result := p.getRoot().Remove(ctx, fullPath, txid, nil)
-
- if result != nil {
- return result.GetData(), nil
+ if err := p.kvStore.Delete(ctx, completePath); err != nil {
+ return fmt.Errorf("unable to delete %s in kvStore: %s", completePath, err)
}
-
- return nil, nil
-}
-
-// CreateProxy to interact with specific path directly
-func (p *Proxy) CreateProxy(ctx context.Context, path string, exclusive bool) (*Proxy, error) {
- if !strings.HasPrefix(path, "/") {
- logger.Errorf("invalid path: %s", path)
- return nil, fmt.Errorf("invalid path: %s", path)
- }
-
- var fullPath string
- var effectivePath string
- if path == "/" {
- fullPath = p.getPath()
- effectivePath = p.getFullPath()
- } else {
- fullPath = p.getPath() + path
- effectivePath = p.getFullPath() + path
- }
-
- p.SetOperation(ProxyCreate)
- defer p.SetOperation(ProxyNone)
-
- logger.Debugw("proxy-create", log.Fields{
- "path": path,
- "effective": effectivePath,
- "full": fullPath,
- "operation": p.GetOperation(),
- })
-
- return p.getRoot().CreateProxy(ctx, fullPath, exclusive)
-}
-
-// OpenTransaction creates a new transaction branch to isolate operations made to the data model
-func (p *Proxy) OpenTransaction() *Transaction {
- txid := p.getRoot().MakeTxBranch()
- return NewTransaction(p, txid)
-}
-
-// commitTransaction will apply and merge modifications made in the transaction branch to the data model
-func (p *Proxy) commitTransaction(ctx context.Context, txid string) {
- p.getRoot().FoldTxBranch(ctx, txid)
-}
-
-// cancelTransaction will terminate a transaction branch along will all changes within it
-func (p *Proxy) cancelTransaction(txid string) {
- p.getRoot().DeleteTxBranch(txid)
-}
-
-// CallbackFunction is a type used to define callback functions
-type CallbackFunction func(ctx context.Context, args ...interface{}) interface{}
-
-// CallbackTuple holds the function and arguments details of a callback
-type CallbackTuple struct {
- callback CallbackFunction
- args []interface{}
-}
-
-// Execute will process the a callback with its provided arguments
-func (tuple *CallbackTuple) Execute(ctx context.Context, contextArgs []interface{}) interface{} {
- args := []interface{}{}
-
- args = append(args, tuple.args...)
-
- args = append(args, contextArgs...)
-
- return tuple.callback(ctx, args...)
-}
-
-// RegisterCallback associates a callback to the proxy
-func (p *Proxy) RegisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
- if p.getCallbacks(callbackType) == nil {
- p.setCallbacks(callbackType, make(map[string]*CallbackTuple))
- }
- funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
- logger.Debugf("value of function: %s", funcName)
- funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
-
- p.setCallback(callbackType, funcHash, &CallbackTuple{callback, args})
-}
-
-// UnregisterCallback removes references to a callback within a proxy
-func (p *Proxy) UnregisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
- if p.getCallbacks(callbackType) == nil {
- logger.Errorf("no such callback type - %s", callbackType.String())
- return
- }
-
- funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
- funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
-
- logger.Debugf("value of function: %s", funcName)
-
- if p.getCallback(callbackType, funcHash) == nil {
- logger.Errorf("function with hash value: '%s' not registered with callback type: '%s'", funcHash, callbackType)
- return
- }
-
- p.DeleteCallback(callbackType, funcHash)
-}
-
-func (p *Proxy) invoke(ctx context.Context, callback *CallbackTuple, context []interface{}) (result interface{}, err error) {
- defer func() {
- if r := recover(); r != nil {
- errStr := fmt.Sprintf("callback error occurred: %+v", r)
- err = errors.New(errStr)
- logger.Error(errStr)
- }
- }()
-
- result = callback.Execute(ctx, context)
-
- return result, err
-}
-
-// InvokeCallbacks executes all callbacks associated to a specific type
-func (p *Proxy) InvokeCallbacks(ctx context.Context, args ...interface{}) (result interface{}) {
- callbackType := args[0].(CallbackType)
- proceedOnError := args[1].(bool)
- context := args[2:]
-
- var err error
-
- if callbacks := p.getCallbacks(callbackType); callbacks != nil {
- p.mutex.Lock()
- for _, callback := range callbacks {
- if result, err = p.invoke(ctx, callback, context); err != nil {
- if !proceedOnError {
- logger.Info("An error occurred. Stopping callback invocation")
- break
- }
- logger.Info("An error occurred. Invoking next callback")
- }
- }
- p.mutex.Unlock()
- }
-
- return result
+ return nil
}