VOL-1027 : Initial commit of voltha 2.0 data model
Change-Id: Ib8006de1af2166281ccf1c9d7c2b9156991bf4e4
diff --git a/db/model/node.go b/db/model/node.go
new file mode 100644
index 0000000..936a79a
--- /dev/null
+++ b/db/model/node.go
@@ -0,0 +1,456 @@
+package model
+
+import (
+ "fmt"
+ "github.com/golang/protobuf/proto"
+ "reflect"
+ "strings"
+)
+
+const (
+ NONE string = "none"
+)
+
+type Node struct {
+ root *Root
+ Type interface{}
+ Branches map[string]*Branch
+ Tags map[string]*Revision
+ Proxy *Proxy
+ EventBus *EventBus
+ AutoPrune bool
+}
+
+func NewNode(root *Root, initialData interface{}, autoPrune bool, txid string) *Node {
+ cn := &Node{}
+
+ cn.root = root
+ cn.Branches = make(map[string]*Branch)
+ cn.Tags = make(map[string]*Revision)
+ cn.Proxy = nil
+ cn.EventBus = nil
+ cn.AutoPrune = autoPrune
+
+ if IsProtoMessage(initialData) {
+ cn.Type = reflect.ValueOf(initialData).Interface()
+ dataCopy := proto.Clone(initialData.(proto.Message))
+ cn.initialize(dataCopy, txid)
+ } else if reflect.ValueOf(initialData).IsValid() {
+ cn.Type = reflect.ValueOf(initialData).Interface()
+ } else {
+ // not implemented error
+ fmt.Errorf("cannot process initial data - %+v", initialData)
+ }
+
+ return cn
+}
+
+func (cn *Node) makeNode(data interface{}, txid string) *Node {
+ return NewNode(cn.root, data, true, txid)
+}
+
+func (cn *Node) makeRevision(branch *Branch, data interface{}, children map[string][]*Revision) *Revision {
+ return cn.root.makeRevision(branch, data, children)
+}
+
+func (cn *Node) makeLatest(branch *Branch, revision *Revision, changeAnnouncement map[string]interface{}) {
+ if _, ok := branch.revisions[revision.Hash]; !ok {
+ branch.revisions[revision.Hash] = revision
+ }
+
+ if branch.Latest == nil || revision.Hash != branch.Latest.Hash {
+ branch.Latest = revision
+ }
+
+ if changeAnnouncement != nil && branch.Txid == "" {
+ if cn.Proxy != nil {
+ for changeType, data := range changeAnnouncement {
+ // TODO: Invoke callback
+ fmt.Printf("invoking callback - changeType: %+v, data:%+v\n", changeType, data)
+ }
+ }
+
+ for changeType, data := range changeAnnouncement {
+ // TODO: send notifications
+ fmt.Printf("sending notification - changeType: %+v, data:%+v\n", changeType, data)
+ }
+ }
+}
+
+func (cn *Node) Latest() *Revision {
+ if branch, exists := cn.Branches[NONE]; exists {
+ return branch.Latest
+ }
+ return nil
+}
+
+func (cn *Node) GetHash(hash string) *Revision {
+ return cn.Branches[NONE].revisions[hash]
+}
+
+func (cn *Node) initialize(data interface{}, txid string) {
+ var children map[string][]*Revision
+ children = make(map[string][]*Revision)
+ for fieldName, field := range ChildrenFields(cn.Type) {
+ fieldValue := GetAttributeValue(data, fieldName, 0)
+
+ if fieldValue.IsValid() {
+ if field.IsContainer {
+ if field.Key != "" {
+ var keysSeen []string
+
+ for i := 0; i < fieldValue.Len(); i++ {
+ v := fieldValue.Index(i)
+ rev := cn.makeNode(v.Interface(), txid).Latest()
+ key := GetAttributeValue(v.Interface(), field.Key, 0)
+ for _, k := range keysSeen {
+ if k == key.String() {
+ fmt.Errorf("duplicate key - %s", k)
+ }
+ }
+ children[fieldName] = append(children[fieldName], rev)
+ keysSeen = append(keysSeen, key.String())
+ }
+
+ } else {
+ for i := 0; i < fieldValue.Len(); i++ {
+ v := fieldValue.Index(i)
+ children[fieldName] = append(children[fieldName], cn.makeNode(v.Interface(), txid).Latest())
+ }
+ }
+ } else {
+ children[fieldName] = append(children[fieldName], cn.makeNode(fieldValue.Interface(), txid).Latest())
+ }
+ } else {
+ fmt.Errorf("field is invalid - %+v", fieldValue)
+ }
+ }
+ // FIXME: ClearField??? No such method in go protos. Reset?
+ //data.ClearField(field_name)
+ branch := NewBranch(cn, "", nil, cn.AutoPrune)
+ rev := cn.makeRevision(branch, data, children)
+ cn.makeLatest(branch, rev, nil)
+ cn.Branches[txid] = branch
+}
+
+func (cn *Node) makeTxBranch(txid string) *Branch {
+ branchPoint := cn.Branches[NONE].Latest
+ branch := NewBranch(cn, txid, branchPoint, true)
+ cn.Branches[txid] = branch
+ return branch
+}
+
+func (cn *Node) deleteTxBranch(txid string) {
+ delete(cn.Branches, txid)
+}
+
+type t_makeBranch func(*Node) *Branch
+
+//
+// Get operation
+//
+func (cn *Node) Get(path string, hash string, depth int, deep bool, txid string) interface{} {
+ if deep {
+ depth = -1
+ }
+
+ for strings.HasPrefix(path, "/") {
+ path = path[1:]
+ }
+
+ var branch *Branch
+ var rev *Revision
+
+ // FIXME: should empty txid be cleaned up?
+ if branch = cn.Branches[txid]; txid == "" || branch == nil {
+ branch = cn.Branches[NONE]
+ }
+
+ if hash != "" {
+ rev = branch.revisions[hash]
+ } else {
+ rev = branch.Latest
+ }
+
+ return cn.get(rev, path, depth)
+}
+
+func (cn *Node) findRevByKey(revs []*Revision, keyName string, value string) (int, *Revision) {
+ for i, rev := range revs {
+ dataValue := reflect.ValueOf(rev.Config.Data)
+ dataStruct := GetAttributeStructure(rev.Config.Data, keyName, 0)
+
+ fieldValue := dataValue.Elem().FieldByName(dataStruct.Name)
+
+ if fieldValue.Interface().(string) == value {
+ return i, rev
+ }
+ }
+
+ fmt.Errorf("key %s=%s not found", keyName, value)
+
+ return -1, nil
+}
+
+func (cn *Node) get(rev *Revision, path string, depth int) interface{} {
+ if path == "" {
+ return cn.doGet(rev, depth)
+ }
+
+ partition := strings.SplitN(path, "/", 2)
+ name := partition[0]
+ path = partition[1]
+
+ field := ChildrenFields(cn.Type)[name]
+
+ if field.IsContainer {
+ if field.Key != "" {
+ children := rev.Children[name]
+ if path != "" {
+ partition = strings.SplitN(path, "/", 2)
+ key := partition[0]
+ path = ""
+ key = field.KeyFromStr(key).(string)
+ _, childRev := cn.findRevByKey(children, field.Key, key)
+ childNode := childRev.getNode()
+ return childNode.get(childRev, path, depth)
+ } else {
+ var response []interface{}
+ for _, childRev := range children {
+ childNode := childRev.getNode()
+ value := childNode.doGet(childRev, depth)
+ response = append(response, value)
+ }
+ return response
+ }
+ } else {
+ childRev := rev.Children[name][0]
+ childNode := childRev.getNode()
+ return childNode.get(childRev, path, depth)
+ }
+ }
+ return nil
+}
+
+func (cn *Node) doGet(rev *Revision, depth int) interface{} {
+ msg := rev.Get(depth)
+
+ if cn.Proxy != nil {
+ // TODO: invoke GET callback
+ fmt.Println("invoking proxy GET Callbacks")
+ }
+ return msg
+}
+
+//
+// Update operation
+//
+func (n *Node) Update(path string, data interface{}, strict bool, txid string, makeBranch t_makeBranch) *Revision {
+ // FIXME: is this required ... a bit overkill to take out a "/"
+ for strings.HasPrefix(path, "/") {
+ path = path[1:]
+ }
+
+ var branch *Branch
+ var ok bool
+ if branch, ok = n.Branches[txid]; !ok {
+ branch = makeBranch(n)
+ }
+
+ if path == "" {
+ return n.doUpdate(branch, data, strict)
+ }
+ return &Revision{}
+}
+
+func (n *Node) doUpdate(branch *Branch, data interface{}, strict bool) *Revision {
+ if reflect.TypeOf(data) != n.Type {
+ // TODO raise error
+ fmt.Errorf("data does not match type: %+v", n.Type)
+ }
+
+ // TODO: noChildren?
+
+ if n.Proxy != nil {
+ // TODO: n.proxy.InvokeCallbacks(CallbackType.PRE_UPDATE, data)
+ fmt.Println("invoking proxy PRE_UPDATE Callbacks")
+ }
+ if branch.Latest.getData() != data {
+ if strict {
+ // TODO: checkAccessViolations(data, branch.GetLatest.data)
+ fmt.Println("checking access violations")
+ }
+ rev := branch.Latest.UpdateData(data, branch)
+ n.makeLatest(branch, rev, nil) // TODO -> changeAnnouncement needs to be a tuple (CallbackType.POST_UPDATE, rev.data)
+ return rev
+ } else {
+ return branch.Latest
+ }
+}
+
+// TODO: the python implementation has a method to check if the data has no children
+//func (n *SomeNode) noChildren(data interface{}) bool {
+// for fieldName, field := range ChildrenFields(n.Type) {
+// fieldValue := GetAttributeValue(data, fieldName)
+//
+// if fieldValue.IsValid() {
+// if field.IsContainer {
+// if len(fieldValue) > 0 {
+//
+// }
+// } else {
+//
+// }
+//
+// }
+// }
+//}
+
+//
+// Add operation
+//
+func (n *Node) Add(path string, data interface{}, txid string, makeBranch t_makeBranch) *Revision {
+ for strings.HasPrefix(path, "/") {
+ path = path[1:]
+ }
+ if path == "" {
+ // TODO raise error
+ fmt.Errorf("cannot add for non-container mode\n")
+ }
+
+ var branch *Branch
+ var ok bool
+ if branch, ok = n.Branches[txid]; !ok {
+ branch = makeBranch(n)
+ }
+
+ rev := branch.Latest
+
+ partition := strings.SplitN(path, "/", 2)
+ name := partition[0]
+ path = partition[1]
+
+ field := ChildrenFields(n.Type)[name]
+ var children []*Revision
+
+ if field.IsContainer {
+ if path == "" {
+ if field.Key != "" {
+ if n.Proxy != nil {
+ // TODO -> n.proxy.InvokeCallbacks(PRE_ADD, data)
+ fmt.Println("invoking proxy PRE_ADD Callbacks")
+ }
+
+ copy(children, rev.Children[name])
+ key := GetAttributeValue(data, field.Key, 0)
+ if _, rev := n.findRevByKey(children, field.Key, key.String()); rev != nil {
+ // TODO raise error
+ fmt.Errorf("duplicate key found: %s", key.String())
+ }
+
+ childRev := n.makeNode(data, "").Latest()
+ children = append(children, childRev)
+ rev := rev.UpdateChildren(name, children, branch)
+ n.makeLatest(branch, rev, nil) // TODO -> changeAnnouncement needs to be a tuple (CallbackType.POST_ADD, rev.data)
+ return rev
+ } else {
+ fmt.Errorf("cannot add to non-keyed container\n")
+ }
+ } else if field.Key != "" {
+ partition := strings.SplitN(path, "/", 2)
+ key := partition[0]
+ path = partition[1]
+ key = field.KeyFromStr(key).(string)
+ copy(children, rev.Children[name])
+ idx, childRev := n.findRevByKey(children, field.Key, key)
+ childNode := childRev.getNode()
+ newChildRev := childNode.Add(path, data, txid, makeBranch)
+ children[idx] = newChildRev
+ rev := rev.UpdateChildren(name, children, branch)
+ n.makeLatest(branch, rev, nil)
+ return rev
+ } else {
+ fmt.Errorf("cannot add to non-keyed container\n")
+ }
+ } else {
+ fmt.Errorf("cannot add to non-container field\n")
+ }
+ return nil
+}
+
+//
+// Remove operation
+//
+func (n *Node) Remove(path string, txid string, makeBranch t_makeBranch) *Revision {
+ for strings.HasPrefix(path, "/") {
+ path = path[1:]
+ }
+ if path == "" {
+ // TODO raise error
+ fmt.Errorf("cannot remove for non-container mode\n")
+ }
+ var branch *Branch
+ var ok bool
+ if branch, ok = n.Branches[txid]; !ok {
+ branch = makeBranch(n)
+ }
+
+ rev := branch.Latest
+
+ partition := strings.SplitN(path, "/", 2)
+ name := partition[0]
+ path = partition[1]
+
+ field := ChildrenFields(n.Type)[name]
+ var children []*Revision
+ post_anno := make(map[string]interface{})
+
+ if field.IsContainer {
+ if path == "" {
+ fmt.Errorf("cannot remove without a key\n")
+ } else if field.Key != "" {
+ partition := strings.SplitN(path, "/", 2)
+ key := partition[0]
+ path = partition[1]
+ key = field.KeyFromStr(key).(string)
+ if path != "" {
+ copy(children, rev.Children[name])
+ idx, childRev := n.findRevByKey(children, field.Key, key)
+ childNode := childRev.getNode()
+ newChildRev := childNode.Remove(path, txid, makeBranch)
+ children[idx] = newChildRev
+ rev := rev.UpdateChildren(name, children, branch)
+ n.makeLatest(branch, rev, nil)
+ return rev
+ } else {
+ copy(children, rev.Children[name])
+ idx, childRev := n.findRevByKey(children, field.Key, key)
+ if n.Proxy != nil {
+ data := childRev.getData()
+ fmt.Println("invoking proxy PRE_REMOVE Callbacks")
+ fmt.Printf("setting POST_REMOVE Callbacks : %+v\n", data)
+ } else {
+ fmt.Println("setting POST_REMOVE Callbacks")
+ }
+ children = append(children[:idx], children[idx+1:]...)
+ rev := rev.UpdateChildren(name, children, branch)
+ n.makeLatest(branch, rev, post_anno)
+ return rev
+ }
+ } else {
+ fmt.Errorf("cannot add to non-keyed container\n")
+ }
+ } else {
+ fmt.Errorf("cannot add to non-container field\n")
+ }
+
+ return nil
+}
+
+func (n *Node) LoadLatest(kvStore *Backend, hash string) {
+ branch := NewBranch(n, "", nil, n.AutoPrune)
+ pr := &PersistedRevision{}
+ rev := pr.load(branch, kvStore, n.Type, hash)
+ n.makeLatest(branch, rev.Revision, nil)
+ n.Branches[NONE] = branch
+}