[VOL-2332] Handling multiple ONU Discovery indications

Change-Id: I04673dd1123fce36d078190f9a57af512b0a8990
diff --git a/internal/pkg/core/device_handler.go b/internal/pkg/core/device_handler.go
index 500dbf1..76f05da 100644
--- a/internal/pkg/core/device_handler.go
+++ b/internal/pkg/core/device_handler.go
@@ -99,6 +99,11 @@
 
 	deviceInfo *oop.DeviceInfo
 
+	// discOnus (map[onuSn]bool) contains a list of ONUs that have been discovered, indexed by ONU SerialNumber.
+	// if the value is true that means the OnuDiscovery indication
+	// is currently being processed and thus we can ignore concurrent requests
+	// if it's false it means the processing has completed and we shouldn't be receiving a new indication
+	// if we do it means something went wrong and we need to retry
 	discOnus                      sync.Map
 	onus                          sync.Map
 	portStats                     *OpenOltStatisticsMgr
@@ -1464,40 +1469,37 @@
 
 }
 
-func (dh *DeviceHandler) processDiscONULOSClear(ctx context.Context, onuDiscInd *oop.OnuDiscIndication, sn string) bool {
+// processDiscONULOSClear clears the LOS Alarm if it's needed
+func (dh *DeviceHandler) processDiscONULOSClear(ctx context.Context, onuDiscInd *oop.OnuDiscIndication, sn string) {
 	var alarmInd oop.OnuAlarmIndication
 	raisedTs := time.Now().Unix()
-	if _, loaded := dh.discOnus.LoadOrStore(sn, true); loaded {
 
-		/* When PON cable disconnected and connected back from OLT, it was expected OnuAlarmIndication
-		   with "los_status: off" should be raised but BAL does not raise this Alarm hence manually sending
-		   OnuLosClear event on receiving OnuDiscoveryIndication for the Onu after checking whether
-		   OnuLosRaise event sent for it */
-		dh.onus.Range(func(Onukey interface{}, onuInCache interface{}) bool {
-			if onuInCache.(*OnuDevice).serialNumber == sn && onuInCache.(*OnuDevice).losRaised {
-				if onuDiscInd.GetIntfId() != onuInCache.(*OnuDevice).intfID {
-					logger.Warnw(ctx, "onu-is-on-a-different-intf-id-now", log.Fields{
-						"previousIntfId": onuInCache.(*OnuDevice).intfID,
-						"currentIntfId":  onuDiscInd.GetIntfId()})
-					// TODO:: Should we need to ignore raising OnuLosClear event
-					// when onu connected to different PON?
-				}
-				alarmInd.IntfId = onuInCache.(*OnuDevice).intfID
-				alarmInd.OnuId = onuInCache.(*OnuDevice).onuID
-				alarmInd.LosStatus = statusCheckOff
-				go func() {
-					if err := dh.eventMgr.onuAlarmIndication(ctx, &alarmInd, onuInCache.(*OnuDevice).deviceID, raisedTs); err != nil {
-						logger.Debugw(ctx, "indication-failed", log.Fields{"err": err})
-					}
-				}()
+	/* When PON cable disconnected and connected back from OLT, it was expected OnuAlarmIndication
+	   with "los_status: off" should be raised but BAL does not raise this Alarm hence manually sending
+	   OnuLosClear event on receiving OnuDiscoveryIndication for the Onu after checking whether
+	   OnuLosRaise event sent for it */
+	dh.onus.Range(func(Onukey interface{}, onuInCache interface{}) bool {
+		if onuInCache.(*OnuDevice).serialNumber == sn && onuInCache.(*OnuDevice).losRaised {
+			if onuDiscInd.GetIntfId() != onuInCache.(*OnuDevice).intfID {
+				logger.Warnw(ctx, "onu-is-on-a-different-intf-id-now", log.Fields{
+					"previousIntfId": onuInCache.(*OnuDevice).intfID,
+					"currentIntfId":  onuDiscInd.GetIntfId()})
+				// TODO:: Should we need to ignore raising OnuLosClear event
+				// when onu connected to different PON?
 			}
-			return true
-		})
-
-		logger.Warnw(ctx, "onu-sn-is-already-being-processed", log.Fields{"sn": sn})
+			alarmInd.IntfId = onuInCache.(*OnuDevice).intfID
+			alarmInd.OnuId = onuInCache.(*OnuDevice).onuID
+			alarmInd.LosStatus = statusCheckOff
+			go func() {
+				if err := dh.eventMgr.onuAlarmIndication(ctx, &alarmInd, onuInCache.(*OnuDevice).deviceID, raisedTs); err != nil {
+					logger.Errorw(ctx, "indication-failed", log.Fields{"err": err})
+				}
+			}()
+			// stop iterating
+			return false
+		}
 		return true
-	}
-	return false
+	})
 }
 
 func (dh *DeviceHandler) onuDiscIndication(ctx context.Context, onuDiscInd *oop.OnuDiscIndication) error {
@@ -1513,11 +1515,35 @@
 	}
 	if tpInstExists {
 		//ignore the discovery if tpinstance is present.
+		logger.Debugw(ctx, "ignoring-onu-indication-as-tp-already-exists", log.Fields{"sn": sn})
 		return nil
 	}
-	if onuBeingProcessed := dh.processDiscONULOSClear(ctx, onuDiscInd, sn); onuBeingProcessed {
-		return nil
+	inProcess, existing := dh.discOnus.LoadOrStore(sn, true)
+
+	// if the ONU existed, handle the LOS Alarm
+	if existing {
+
+		if inProcess.(bool) {
+			// if we're currently processing the ONU on a different thread, do nothing
+			logger.Warnw(ctx, "onu-sn-is-being-processed", log.Fields{"sn": sn})
+			return nil
+		}
+		// if we had dealt with this ONU before, but the process didn't complete (this happens in case of errors)
+		// then continue processing it
+		logger.Debugw(ctx, "onu-processing-had-completed-but-new-indication", log.Fields{"sn": sn})
+
+		dh.processDiscONULOSClear(ctx, onuDiscInd, sn)
 	}
+
+	defer func() {
+		// once the function completes set the value to false so that
+		// we know the processing has inProcess.
+		// Note that this is done after checking if we are already processing
+		// to avoid changing the value from a different thread
+		logger.Infow(ctx, "onu-processing-completed", log.Fields{"sn": sn})
+		dh.discOnus.Store(sn, false)
+	}()
+
 	var onuID uint32
 
 	// check the ONU is already know to the OLT