[VOL-1036] Initial implementation of device lifecycle management

Change-Id: I5aa58fdcbcd852f6f5eef35d48f25f76e20c0418
diff --git a/rw_core/core/logical_device_agent.go b/rw_core/core/logical_device_agent.go
index 117c869..218478f 100644
--- a/rw_core/core/logical_device_agent.go
+++ b/rw_core/core/logical_device_agent.go
@@ -25,17 +25,18 @@
 	"github.com/opencord/voltha-go/protos/voltha"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
-	"reflect"
+	"sync"
 )
 
 type LogicalDeviceAgent struct {
-	logicalDeviceId  string
-	lastData         *voltha.LogicalDevice
-	rootDeviceId     string
-	deviceMgr        *DeviceManager
-	ldeviceMgr       *LogicalDeviceManager
-	clusterDataProxy *model.Proxy
-	exitChannel      chan int
+	logicalDeviceId   string
+	lastData          *voltha.LogicalDevice
+	rootDeviceId      string
+	deviceMgr         *DeviceManager
+	ldeviceMgr        *LogicalDeviceManager
+	clusterDataProxy  *model.Proxy
+	exitChannel       chan int
+	lockLogicalDevice sync.RWMutex
 }
 
 func NewLogicalDeviceAgent(id string, device *voltha.Device, ldeviceMgr *LogicalDeviceManager, deviceMgr *DeviceManager,
@@ -47,11 +48,12 @@
 	agent.deviceMgr = deviceMgr
 	agent.clusterDataProxy = cdProxy
 	agent.ldeviceMgr = ldeviceMgr
+	agent.lockLogicalDevice = sync.RWMutex{}
 	return &agent
 }
 
 func (agent *LogicalDeviceAgent) Start(ctx context.Context) error {
-	log.Info("starting-logical_device-agent")
+	log.Infow("starting-logical_device-agent", log.Fields{"logicaldeviceId": agent.logicalDeviceId})
 	//Build the logical device based on information retrieved from the device adapter
 	var switchCap *ca.SwitchCapability
 	var err error
@@ -68,7 +70,7 @@
 	//hence. may need to extract the port by the NNI port id defined by the adapter during device
 	//creation
 	var nniPorts *voltha.Ports
-	if nniPorts, err = agent.deviceMgr.getNNIPorts(ctx, agent.rootDeviceId); err != nil {
+	if nniPorts, err = agent.deviceMgr.getPorts(ctx, agent.rootDeviceId, voltha.Port_ETHERNET_NNI); err != nil {
 		log.Errorw("error-creating-logical-port", log.Fields{"error": err})
 	}
 	var portCap *ca.PortCapability
@@ -80,8 +82,11 @@
 		}
 
 		lp := (proto.Clone(portCap.Port)).(*voltha.LogicalPort)
+		lp.DeviceId = agent.rootDeviceId
 		ld.Ports = append(ld.Ports, lp)
 	}
+	agent.lockLogicalDevice.Lock()
+	defer agent.lockLogicalDevice.Unlock()
 	// Save the logical device
 	if added := agent.clusterDataProxy.Add("/logical_devices", ld, ""); added == nil {
 		log.Errorw("failed-to-add-logical-device", log.Fields{"logicaldeviceId": agent.logicalDeviceId})
@@ -92,8 +97,30 @@
 	return nil
 }
 
+func (agent *LogicalDeviceAgent) getLogicalDevice() (*voltha.LogicalDevice, error) {
+	log.Debug("getLogicalDevice")
+	agent.lockLogicalDevice.Lock()
+	defer agent.lockLogicalDevice.Unlock()
+	logicalDevice := agent.clusterDataProxy.Get("/logical_devices/"+agent.logicalDeviceId, 1, false, "")
+	if lDevice, ok := logicalDevice.(*voltha.LogicalDevice); ok {
+		cloned := proto.Clone(lDevice).(*voltha.LogicalDevice)
+		return cloned, nil
+	}
+	return nil, status.Errorf(codes.NotFound, "logical_device-%s", agent.logicalDeviceId)
+}
+
+func (agent *LogicalDeviceAgent) getLogicalDeviceWithoutLock() (*voltha.LogicalDevice, error) {
+	log.Debug("getLogicalDeviceWithoutLock")
+	logicalDevice := agent.clusterDataProxy.Get("/logical_devices/"+agent.logicalDeviceId, 1, false, "")
+	if lDevice, ok := logicalDevice.(*voltha.LogicalDevice); ok {
+		cloned := proto.Clone(lDevice).(*voltha.LogicalDevice)
+		return cloned, nil
+	}
+	return nil, status.Errorf(codes.NotFound, "logical_device-%s", agent.logicalDeviceId)
+}
+
 func (agent *LogicalDeviceAgent) addUNILogicalPort(ctx context.Context, childDevice *voltha.Device, portNo uint32) error {
-	log.Info("addUNILogicalPort-start")
+	log.Infow("addUNILogicalPort-start", log.Fields{"logicalDeviceId": agent.logicalDeviceId})
 	// Build the logical device based on information retrieved from the device adapter
 	var portCap *ca.PortCapability
 	var err error
@@ -101,29 +128,67 @@
 		log.Errorw("error-creating-logical-port", log.Fields{"error": err})
 		return err
 	}
+	agent.lockLogicalDevice.Lock()
+	defer agent.lockLogicalDevice.Unlock()
 	// Get stored logical device
-	if ldevice, err := agent.ldeviceMgr.getLogicalDevice(agent.logicalDeviceId); err != nil {
+	if ldevice, err := agent.getLogicalDeviceWithoutLock(); err != nil {
 		return status.Error(codes.NotFound, agent.logicalDeviceId)
 	} else {
-		cloned := reflect.ValueOf(ldevice).Elem().Interface().(voltha.LogicalDevice)
-		lp := (proto.Clone(portCap.Port)).(*voltha.LogicalPort)
+		cloned := proto.Clone(ldevice).(*voltha.LogicalDevice)
+		lp := proto.Clone(portCap.Port).(*voltha.LogicalPort)
+		lp.DeviceId = childDevice.Id
 		cloned.Ports = append(cloned.Ports, lp)
-		afterUpdate := agent.clusterDataProxy.Update("/logical_devices/"+agent.logicalDeviceId, &cloned, false, "")
-		if afterUpdate == nil {
-			return status.Errorf(codes.Internal, "failed-add-UNI-port:%s", agent.logicalDeviceId)
-		}
+		return agent.updateLogicalDeviceWithoutLock(cloned)
+	}
+}
+
+//updateLogicalDeviceWithoutLock updates the model with the logical device.  It clones the logicaldevice before saving it
+func (agent *LogicalDeviceAgent) updateLogicalDeviceWithoutLock(logicalDevice *voltha.LogicalDevice) error {
+	cloned := proto.Clone(logicalDevice).(*voltha.LogicalDevice)
+	afterUpdate := agent.clusterDataProxy.Update("/logical_devices/"+agent.logicalDeviceId, cloned, false, "")
+	if afterUpdate == nil {
+		return status.Errorf(codes.Internal, "failed-updating-logical-device:%s", agent.logicalDeviceId)
+	}
+	return nil
+}
+
+// deleteLogicalPort removes the logical port associated with a child device
+func (agent *LogicalDeviceAgent) deleteLogicalPort(device *voltha.Device) error {
+	agent.lockLogicalDevice.Lock()
+	defer agent.lockLogicalDevice.Unlock()
+	// Get the most up to date logical device
+	var logicaldevice *voltha.LogicalDevice
+	if logicaldevice, _ = agent.getLogicalDeviceWithoutLock(); logicaldevice == nil {
+		log.Debugw("no-logical-device", log.Fields{"logicalDeviceId": agent.logicalDeviceId, "deviceId": device.Id})
 		return nil
 	}
+	index := -1
+	for i, logicalPort := range logicaldevice.Ports {
+		if logicalPort.DeviceId == device.Id {
+			index = i
+			break
+		}
+	}
+	if index >= 0 {
+		copy(logicaldevice.Ports[index:], logicaldevice.Ports[index+1:])
+		logicaldevice.Ports[len(logicaldevice.Ports)-1] = nil
+		logicaldevice.Ports = logicaldevice.Ports[:len(logicaldevice.Ports)-1]
+		log.Debugw("logical-port-deleted", log.Fields{"logicalDeviceId": agent.logicalDeviceId})
+		return agent.updateLogicalDeviceWithoutLock(logicaldevice)
+	}
+	return nil
 }
 
 func (agent *LogicalDeviceAgent) Stop(ctx context.Context) {
 	log.Info("stopping-logical_device-agent")
+	agent.lockLogicalDevice.Lock()
+	defer agent.lockLogicalDevice.Unlock()
+	//Remove the logical device from the model
+	if removed := agent.clusterDataProxy.Remove("/logical_devices/"+agent.logicalDeviceId, ""); removed == nil {
+		log.Errorw("failed-to-remove-logical-device", log.Fields{"logicaldeviceId": agent.logicalDeviceId})
+	} else {
+		log.Debugw("logicaldevice-removed", log.Fields{"logicaldeviceId": agent.logicalDeviceId})
+	}
 	agent.exitChannel <- 1
 	log.Info("logical_device-agent-stopped")
 }
-
-func (agent *LogicalDeviceAgent) getLogicalDevice(ctx context.Context) *voltha.LogicalDevice {
-	log.Debug("getLogicalDevice")
-	cp := proto.Clone(agent.lastData)
-	return cp.(*voltha.LogicalDevice)
-}