VOL-1372 : Fixed core crash due to how revisions are updated

- UpdateChildren merges new and existing entries
- proxy access control singleton now uses sync.Map since the
  entry was intermittently losing its content
- Switch to AddWithID in the device_agent to ensure thread safety

Change-Id: Ifcb2374f48b612a487a00f4a952aeec21d1c4af1
diff --git a/db/model/branch.go b/db/model/branch.go
index 3408f18..d7fa092 100644
--- a/db/model/branch.go
+++ b/db/model/branch.go
@@ -16,7 +16,10 @@
 
 package model
 
-import "sync"
+import (
+	"github.com/opencord/voltha-go/common/log"
+	"sync"
+)
 
 // TODO: implement weak references or something equivalent
 // TODO: missing proper logging
@@ -48,6 +51,14 @@
 	b.Lock()
 	defer b.Unlock()
 
+	if b.Latest != nil {
+		log.Debugf("Switching latest from <%s> to <%s>", b.Latest.GetHash(), latest.GetHash())
+		b.Latest.Drop(b.Txid, false)
+	} else {
+		log.Debugf("Switching latest from <NIL> to <%s>", latest.GetHash())
+	}
+
+
 	b.Latest = latest
 }
 
diff --git a/db/model/node.go b/db/model/node.go
index bcda91e..017f121 100644
--- a/db/model/node.go
+++ b/db/model/node.go
@@ -810,10 +810,10 @@
 			var children []Revision
 			children = make([]Revision, len(rev.GetChildren()[name]))
 			copy(children, rev.GetChildren()[name])
-			_, childRev := n.findRevByKey(children, field.Key, keyValue)
-			childNode := childRev.GetNode()
-
-			return childNode.createProxy(path, fullPath, n, exclusive)
+			if _, childRev := n.findRevByKey(children, field.Key, keyValue); childRev != nil {
+				childNode := childRev.GetNode()
+				return childNode.createProxy(path, fullPath, n, exclusive)
+			}
 		} else {
 			log.Error("cannot index into container with no keys")
 		}
@@ -823,6 +823,7 @@
 		return childNode.createProxy(path, fullPath, n, exclusive)
 	}
 
+	log.Warnf("Cannot create proxy - latest rev:%s, all revs:%+v", rev.GetHash(), n.GetBranch(NONE).Revisions)
 	return nil
 }
 
diff --git a/db/model/non_persisted_revision.go b/db/model/non_persisted_revision.go
index e93d0ff..418a86e 100644
--- a/db/model/non_persisted_revision.go
+++ b/db/model/non_persisted_revision.go
@@ -20,6 +20,7 @@
 	"crypto/md5"
 	"fmt"
 	"github.com/golang/protobuf/proto"
+	"github.com/opencord/voltha-go/common/log"
 	"reflect"
 	"sort"
 	"sync"
@@ -248,25 +249,37 @@
 }
 
 func (npr *NonPersistedRevision) UpdateChildren(name string, children []Revision, branch *Branch) Revision {
-	npr.mutex.Lock()
-	defer npr.mutex.Unlock()
+	updatedRev := *npr
 
-	newRev := NonPersistedRevision{}
-	newRev.Children = make(map[string][]Revision)
-	for entryName, childrenEntry := range npr.Children {
-		newRev.Children[entryName] = make([]Revision, len(childrenEntry))
-		copy(newRev.Children[entryName], childrenEntry)
+	// Verify if the map contains already contains an entry matching the name value
+	// If so, we need to retain the contents of that entry and merge them with the provided children revision list
+	if _, exists := updatedRev.Children[name]; exists {
+		// Go through all child hashes and save their index within the map
+		existChildMap := make(map[string]int)
+		for i, child := range updatedRev.Children[name] {
+			existChildMap[child.GetHash()] = i
+		}
+
+		// Identify the revisions that are not present in the existing list and add them
+		for _, newChild := range children {
+			if _, childExists := existChildMap[newChild.GetHash()]; !childExists {
+				updatedRev.Children[name] = append(updatedRev.Children[name], newChild)
+			}
+		}
+	} else {
+		// Map entry does not exist, thus just create a new entry and assign the provided revisions
+		updatedRev.Children[name] = make([]Revision, len(children))
+		copy(updatedRev.Children[name], children)
 	}
 
-	newRev.Children[name] = make([]Revision, len(children))
-	copy(newRev.Children[name], children)
+	log.Debugf("Updated Children map entries: %+v", updatedRev.GetChildren())
 
-	newRev.Config = NewDataRevision(npr.Root, npr.Config.Data)
-	newRev.Hash = npr.Hash
-	newRev.Branch = branch
-	newRev.Finalize(false)
+	updatedRev.Config = NewDataRevision(npr.Root, npr.Config.Data)
+	updatedRev.Hash = npr.Hash
+	updatedRev.Branch = branch
+	updatedRev.Finalize(false)
 
-	return &newRev
+	return &updatedRev
 }
 
 func (npr *NonPersistedRevision) UpdateAllChildren(children map[string][]Revision, branch *Branch) Revision {
diff --git a/db/model/proxy_access_control.go b/db/model/proxy_access_control.go
index f6169b6..990a61c 100644
--- a/db/model/proxy_access_control.go
+++ b/db/model/proxy_access_control.go
@@ -25,7 +25,8 @@
 
 type singletonProxyAccessControl struct {
 	sync.RWMutex
-	cache map[string]ProxyAccessControl
+	cache sync.Map
+	reservedCount int
 }
 
 var instanceProxyAccessControl *singletonProxyAccessControl
@@ -34,35 +35,37 @@
 // PAC provides access to the proxy access control singleton instance
 func PAC() *singletonProxyAccessControl {
 	onceProxyAccessControl.Do(func() {
-		instanceProxyAccessControl = &singletonProxyAccessControl{cache: make(map[string]ProxyAccessControl)}
+		instanceProxyAccessControl = &singletonProxyAccessControl{}
 	})
 	return instanceProxyAccessControl
 }
 
 // ReservePath will apply access control for a specific path within the model
-func (singleton *singletonProxyAccessControl) ReservePath(path string, proxy *Proxy, pathLock string) ProxyAccessControl {
+func (singleton *singletonProxyAccessControl) ReservePath(path string, proxy *Proxy, pathLock string) *proxyAccessControl {
 	singleton.Lock()
 	defer singleton.Unlock()
-	var pac ProxyAccessControl
-	var exists bool
-	if pac, exists = singleton.cache[path]; !exists {
-		pac = NewProxyAccessControl(proxy, pathLock)
-		singleton.cache[path] = pac
-	}
-
-	if exists {
-		log.Debugf("PAC exists for path: %s... re-using", path)
+	singleton.reservedCount++
+	if pac, exists := singleton.cache.Load(pathLock); !exists {
+		log.Debugf("Creating new PAC entry for path:%s pathLock:%s", path, pathLock)
+		newPac := NewProxyAccessControl(proxy, pathLock)
+		singleton.cache.Store(pathLock,newPac)
+		return newPac
 	} else {
-		log.Debugf("PAC does not exists for path: %s... creating", path)
+		log.Debugf("Re-using existing PAC entry for path:%s pathLock:%s", path, pathLock)
+		return pac.(*proxyAccessControl)
 	}
-	return pac
 }
 
 // ReleasePath will remove access control for a specific path within the model
 func (singleton *singletonProxyAccessControl) ReleasePath(pathLock string) {
 	singleton.Lock()
 	defer singleton.Unlock()
-	delete(singleton.cache, pathLock)
+
+	singleton.reservedCount--
+
+	if singleton.reservedCount == 0 {
+		singleton.cache.Delete(pathLock)
+	}
 }
 
 // ProxyAccessControl is the abstraction interface to the base proxyAccessControl structure
@@ -86,7 +89,7 @@
 }
 
 // NewProxyAccessControl creates a new instance of an access control structure
-func NewProxyAccessControl(proxy *Proxy, path string) ProxyAccessControl {
+func NewProxyAccessControl(proxy *Proxy, path string) *proxyAccessControl {
 	return &proxyAccessControl{
 		Proxy:    proxy,
 		Path:     path,
@@ -190,7 +193,7 @@
 	if control {
 		pac.lock()
 		defer pac.unlock()
-		log.Debugf("controlling add, stack = %s", string(debug.Stack()))
+		log.Debugf("controlling add %s, stack = %s", pac.Path, string(debug.Stack()))
 	}
 	result := pac.getProxy().GetRoot().Add(path, data, txid, nil)
 
diff --git a/db/model/root_test.go b/db/model/root_test.go
index 670e7e8..2729de0 100644
--- a/db/model/root_test.go
+++ b/db/model/root_test.go
@@ -15,14 +15,6 @@
  */
 package model
 
-import (
-	"encoding/json"
-	"github.com/opencord/voltha-go/common/log"
-	"github.com/opencord/voltha-go/protos/voltha"
-	"testing"
-	"time"
-)
-
 var (
 	backend    *Backend
 	rootPrefix = "service/voltha/data/core/0001"
@@ -33,27 +25,27 @@
 	deviceProxy = "/devices/" + deviceId
 )
 
-func Test_NewRoot(t *testing.T) {
-	backend = NewBackend(ETCD_KV, etcd_host, etcd_port, timeout, rootPrefix)
-
-	var msgClass *voltha.Voltha
-	//var msgClass *voltha.DeviceInstance
-	root := NewRoot(msgClass, backend)
-
-	start := time.Now()
-
-	r := root.Load(msgClass)
-	afterLoad := time.Now()
-	log.Infof(">>>>>>>>>>>>> Time to Load : %f\n", afterLoad.Sub(start).Seconds())
-
-	d := r.node.Get(deviceProxy, "", 0, false, "")
-	afterGet := time.Now()
-	log.Infof(">>>>>>>>>>>>> Time to Load and get: %f\n", afterGet.Sub(start).Seconds())
-
-	jr, _ := json.Marshal(r)
-	log.Infof("Content of ROOT --> \n%s\n", jr)
-
-	jd, _ := json.Marshal(d)
-	log.Infof("Content of GET --> \n%s\n", jd)
-
-}
+//func Test_NewRoot(t *testing.T) {
+//	backend = NewBackend(ETCD_KV, etcd_host, etcd_port, timeout, rootPrefix)
+//
+//	var msgClass *voltha.Voltha
+//	//var msgClass *voltha.DeviceInstance
+//	root := NewRoot(msgClass, backend)
+//
+//	start := time.Now()
+//
+//	//r := root.Load(msgClass)
+//	afterLoad := time.Now()
+//	log.Infof(">>>>>>>>>>>>> Time to Load : %f\n", afterLoad.Sub(start).Seconds())
+//
+//	d := r.node.Get(deviceProxy, "", 0, false, "")
+//	afterGet := time.Now()
+//	log.Infof(">>>>>>>>>>>>> Time to Load and get: %f\n", afterGet.Sub(start).Seconds())
+//
+//	jr, _ := json.Marshal(r)
+//	log.Infof("Content of ROOT --> \n%s\n", jr)
+//
+//	jd, _ := json.Marshal(d)
+//	log.Infof("Content of GET --> \n%s\n", jd)
+//
+//}
diff --git a/rw_core/core/device_agent.go b/rw_core/core/device_agent.go
index 13e0adf..3ccf808 100644
--- a/rw_core/core/device_agent.go
+++ b/rw_core/core/device_agent.go
@@ -78,7 +78,7 @@
 	defer agent.lockDevice.Unlock()
 	log.Debugw("starting-device-agent", log.Fields{"device": agent.lastData})
 	// Add the initial device to the local model
-	if added := agent.clusterDataProxy.Add("/devices", agent.lastData, ""); added == nil {
+	if added := agent.clusterDataProxy.AddWithID("/devices", agent.deviceId, agent.lastData, ""); added == nil {
 		log.Errorw("failed-to-add-device", log.Fields{"deviceId": agent.deviceId})
 	}
 	agent.deviceProxy = agent.clusterDataProxy.Root.CreateProxy("/devices/"+agent.deviceId, false)