[VOL-1658] Address disable/delete of a device in Pre-prov state

This commit addresses the case where a user wants to disable a
preprovisioned device and also delete it.  It addresses part of the
issue raised by VOL-1658.

Change-Id: Iac8adf45070a234c5505ed800f77150d8ed85156
diff --git a/rw_core/core/device_agent.go b/rw_core/core/device_agent.go
index 4e31ff8..4a88779 100755
--- a/rw_core/core/device_agent.go
+++ b/rw_core/core/device_agent.go
@@ -116,7 +116,7 @@
 	log.Debug("stopping-device-agent")
 	//	Remove the device from the KV store
 	if removed := agent.clusterDataProxy.Remove("/devices/"+agent.deviceId, ""); removed == nil {
-		log.Errorw("failed-removing-device", log.Fields{"id": agent.deviceId})
+		log.Debugw("device-already-removed", log.Fields{"id": agent.deviceId})
 	}
 	agent.exitChannel <- 1
 	log.Debug("device-agent-stopped")
@@ -348,6 +348,12 @@
 			log.Debugw("device-already-disabled", log.Fields{"id": agent.deviceId})
 			return nil
 		}
+		if device.AdminState == voltha.AdminState_PREPROVISIONED ||
+			device.AdminState == voltha.AdminState_DELETED {
+			log.Debugw("device-not-enabled", log.Fields{"id": agent.deviceId})
+			return status.Errorf(codes.FailedPrecondition, "deviceId:%s, invalid-admin-state:%s", agent.deviceId, device.AdminState)
+		}
+
 		// First send the request to an Adapter and wait for a response
 		if err := agent.adapterProxy.DisableDevice(ctx, device); err != nil {
 			log.Debugw("disableDevice-error", log.Fields{"id": agent.lastData.Id, "error": err})
@@ -422,12 +428,13 @@
 			//TODO:  Needs customized error message
 			return status.Errorf(codes.FailedPrecondition, "deviceId:%s, expected-admin-state:%s", agent.deviceId, voltha.AdminState_DISABLED)
 		}
-		// Send the request to an Adapter and wait for a response
-		if err := agent.adapterProxy.DeleteDevice(ctx, device); err != nil {
-			log.Debugw("deleteDevice-error", log.Fields{"id": agent.lastData.Id, "error": err})
-			return err
+		if device.AdminState != voltha.AdminState_PREPROVISIONED {
+			// Send the request to an Adapter only if the device is not in poreporovision state and wait for a response
+			if err := agent.adapterProxy.DeleteDevice(ctx, device); err != nil {
+				log.Debugw("deleteDevice-error", log.Fields{"id": agent.lastData.Id, "error": err})
+				return err
+			}
 		}
-
 		//	Set the state to deleted - this will trigger some background process to clean up the device as well
 		// as its association with the logical device
 		cloned := proto.Clone(device).(*voltha.Device)
diff --git a/rw_core/core/device_ownership.go b/rw_core/core/device_ownership.go
index e746444..49d860a 100644
--- a/rw_core/core/device_ownership.go
+++ b/rw_core/core/device_ownership.go
@@ -50,6 +50,7 @@
 	deviceMapLock      *sync.RWMutex
 	deviceToKeyMap     map[string]string
 	deviceToKeyMapLock *sync.RWMutex
+	ownershipLock      *sync.RWMutex
 }
 
 func NewDeviceOwnership(id string, kvClient kvstore.Client, deviceMgr *DeviceManager, logicalDeviceMgr *LogicalDeviceManager, ownershipPrefix string, reservationTimeout int64) *DeviceOwnership {
@@ -65,6 +66,7 @@
 	deviceOwnership.deviceMapLock = &sync.RWMutex{}
 	deviceOwnership.deviceToKeyMap = make(map[string]string)
 	deviceOwnership.deviceToKeyMapLock = &sync.RWMutex{}
+	deviceOwnership.ownershipLock = &sync.RWMutex{}
 	return &deviceOwnership
 }
 
@@ -109,21 +111,23 @@
 }
 
 func (da *DeviceOwnership) MonitorOwnership(id string, chnl chan int) {
+	log.Debugw("start-device-monitoring", log.Fields{"id": id})
 	op := "starting"
 	exit := false
 	ticker := time.NewTicker(time.Duration(da.reservationTimeout) / 3 * time.Second)
 	for {
 		select {
 		case <-da.exitChannel:
-			log.Infow("closing-monitoring", log.Fields{"Id": id})
+			log.Debugw("closing-monitoring", log.Fields{"Id": id})
 			exit = true
 		case <-ticker.C:
 			log.Debugw(fmt.Sprintf("%s-reservation", op), log.Fields{"Id": id})
 		case <-chnl:
-			log.Infow("closing-device-monitoring", log.Fields{"Id": id})
+			log.Debugw("closing-device-monitoring", log.Fields{"Id": id})
 			exit = true
 		}
 		if exit {
+			log.Infow("exiting-device-monitoring", log.Fields{"Id": id})
 			ticker.Stop()
 			break
 		}
@@ -144,6 +148,7 @@
 			}
 		}
 	}
+	log.Debugw("device-monitoring-stopped", log.Fields{"id": id})
 }
 
 func (da *DeviceOwnership) getOwnership(id string) (bool, bool) {
@@ -188,6 +193,12 @@
 		da.deviceToKeyMapLock.Unlock()
 	}
 
+	// Add a lock to prevent creation of two separate monitoring routines for the same device. When a NB request for a
+	// device not in memory is received this results in this function being called in rapid succession, once when
+	// loading the device and once when handling the NB request.
+	da.ownershipLock.Lock()
+	defer da.ownershipLock.Unlock()
+
 	deviceOwned, ownedByMe := da.getOwnership(ownershipKey)
 	if deviceOwned {
 		log.Debugw("ownership", log.Fields{"Id": ownershipKey, "owned": ownedByMe})
diff --git a/rw_core/core/device_state_transitions.go b/rw_core/core/device_state_transitions.go
index 5c52a8f..906c4e7 100644
--- a/rw_core/core/device_state_transitions.go
+++ b/rw_core/core/device_state_transitions.go
@@ -163,6 +163,12 @@
 			handlers:      []TransitionHandler{dMgr.NotifyInvalidTransition}})
 	transitionMap.transitions = append(transitionMap.transitions,
 		Transition{
+			deviceType:    any,
+			previousState: DeviceState{Admin: voltha.AdminState_PREPROVISIONED, Connection: voltha.ConnectStatus_UNKNOWN, Operational: voltha.OperStatus_UNKNOWN},
+			currentState:  DeviceState{Admin: voltha.AdminState_DELETED, Connection: voltha.ConnectStatus_UNKNOWN, Operational: voltha.OperStatus_UNKNOWN},
+			handlers:      []TransitionHandler{dMgr.RunPostDeviceDelete}})
+	transitionMap.transitions = append(transitionMap.transitions,
+		Transition{
 			deviceType:    parent,
 			previousState: DeviceState{Admin: voltha.AdminState_DISABLED, Connection: voltha.ConnectStatus_UNKNOWN, Operational: voltha.OperStatus_UNKNOWN},
 			currentState:  DeviceState{Admin: voltha.AdminState_DELETED, Connection: voltha.ConnectStatus_UNKNOWN, Operational: voltha.OperStatus_UNKNOWN},