[VOL-4472] BBSim receives two "EndSoftwareDownloadRequest" when you abort the download

Signed-off-by: mpagenko <michael.pagenkopf@adtran.com>
Change-Id: Id213af2e68c6a8f381d398f8be00b6d8b0a67b8e
diff --git a/VERSION b/VERSION
index 5fca835..1111a33 100755
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.1.2-dev254
+2.1.2-dev256
diff --git a/internal/pkg/swupg/omci_onu_upgrade.go b/internal/pkg/swupg/omci_onu_upgrade.go
index fe4aac2..c0660b8 100755
--- a/internal/pkg/swupg/omci_onu_upgrade.go
+++ b/internal/pkg/swupg/omci_onu_upgrade.go
@@ -173,6 +173,7 @@
 	imageIdentifier                  string       //name of the image as used in the adapter
 	mutexIsAwaitingAdapterDlResponse sync.RWMutex
 	chAdapterDlReady                 chan bool
+	chAbortDelayEndSwDl              chan struct{}
 	isWaitingForAdapterDlResponse    bool
 	chOnuDlReady                     chan bool
 	activateImage                    bool
@@ -214,6 +215,7 @@
 	}
 	instFsm.chReceiveExpectedResponse = make(chan bool)
 	instFsm.chAdapterDlReady = make(chan bool)
+	instFsm.chAbortDelayEndSwDl = make(chan struct{})
 	instFsm.chOnuDlReady = make(chan bool)
 	instFsm.chReceiveAbortEndSwDlResponse = make(chan tEndSwDlResponseResult)
 
@@ -550,6 +552,14 @@
 	} else {
 		oFsm.mutexIsAwaitingAdapterDlResponse.RUnlock()
 	}
+
+	//abort a possible delaying of sending EndSwDl
+	//use asynchronous channel sending to avoid blocking here in case no receiver is waiting
+	select {
+	case oFsm.chAbortDelayEndSwDl <- struct{}{}:
+	default:
+	}
+
 	//chOnuDlReady is cleared as part of the FSM reset processing (from enterResetting())
 
 	// in any case (even if it might be automatically requested by above cancellation of waiting) ensure resetting the FSM
@@ -703,7 +713,7 @@
 	logger.Debugw(ctx, "OnuUpgradeFsm start downloading sections", log.Fields{
 		"device-id": oFsm.deviceID, "absolute window": oFsm.nextDownloadWindow})
 	//use a background routine to send the multiple download sections frames in a loop
-	//  in order to avoid blocking on synchrounous event calls for the entire (long) processing time
+	//  in order to avoid blocking on synchronous event calls for the entire (long) processing time
 	go oFsm.runSwDlSectionWindow(ctx)
 }
 
@@ -816,13 +826,30 @@
 func (oFsm *OnuUpgradeFsm) enterFinalizeDL(ctx context.Context, e *fsm.Event) {
 	logger.Infow(ctx, "OnuUpgradeFsm finalize DL", log.Fields{
 		"device-id": oFsm.deviceID, "crc": strconv.FormatInt(int64(oFsm.imageCRC), 16), "delay": oFsm.delayEndSwDl})
+	//use a background routine to wait EndSwDlDelay and then send the EndSwDl request
+	//  in order to avoid blocking on synchronous event calls for the complete wait time
+	//  and to allow for state transition before sending the EndSwDl request
+	go oFsm.delayAndSendEndSwDl(ctx)
+}
 
+//delayAndSendEndSwDl ensures a delay before sending the EndSwDl request
+//  may also be aborted by parallel channel reception on chAbortEndSwDl
+func (oFsm *OnuUpgradeFsm) delayAndSendEndSwDl(ctx context.Context) {
 	oFsm.mutexUpgradeParams.RLock()
 	if oFsm.delayEndSwDl {
 		oFsm.mutexUpgradeParams.RUnlock()
-		//give the ONU some time for image evaluation (hoping it does not base that on first EndSwDl itself)
-		// should not be set in case this state is used for real download abort (not yet implemented)
-		time.Sleep(cOmciEndSwDlDelaySeconds * time.Second)
+		//give the ONU some time for image evaluation (hoping it does not only start this evaluation on first EndSwDl itself)
+		logger.Debugw(ctx, "OnuUpgradeFsm delay EndSwDl", log.Fields{"device-id": oFsm.deviceID,
+			"duration_s": cOmciEndSwDlDelaySeconds})
+		select {
+		case <-time.After(cOmciEndSwDlDelaySeconds * time.Second):
+			logger.Warnw(ctx, "OnuUpgradeFsm start sending EndSwDl", log.Fields{
+				"for device-id": oFsm.deviceID, "image-id": oFsm.imageIdentifier})
+		case <-oFsm.chAbortDelayEndSwDl:
+			logger.Debugw(ctx, "OnuUpgradeFsm abort request to send EndSwDl", log.Fields{"device-id": oFsm.deviceID})
+			//according further state transition is ensured by the entity that sent the abort
+			return
+		}
 	} else {
 		oFsm.mutexUpgradeParams.RUnlock()
 	}
@@ -834,9 +861,7 @@
 		oFsm.volthaDownloadReason = voltha.ImageState_UNKNOWN_ERROR
 		oFsm.mutexUpgradeParams.Unlock()
 		// Can't call FSM Event directly, decoupling it
-		go func(a_pAFsm *cmn.AdapterFsm) {
-			_ = a_pAFsm.PFsm.Event(UpgradeEvAbort)
-		}(pBaseFsm)
+		_ = pBaseFsm.PFsm.Event(UpgradeEvAbort)
 		return
 	}
 	err := oFsm.pOmciCC.SendEndSoftwareDownload(log.WithSpanFromContext(context.Background(), ctx), oFsm.pDeviceHandler.GetOmciTimeout(), false,
@@ -848,12 +873,13 @@
 		oFsm.abortOnOmciError(ctx, true)
 		return
 	}
-	// go waiting for the EndSwDLResponse and check, if the ONU is ready for activation
-	// Can't call FSM Event directly, decoupling it
-	go func(a_pAFsm *cmn.AdapterFsm) {
-		_ = a_pAFsm.PFsm.Event(UpgradeEvWaitEndDownload)
-	}(pBaseFsm)
-}
+	//from here on sending of EndSwDl(Abort) is not needed anymore (even though response is not yet received)
+	//  this avoids sending of both EndSwDl(Success) and EndSwDl(Abort) when cancellation falls just into this window
+	//  the ONU must theoretically be prepared to receive none of them (in case of OMCI transfer issues) e.g. by timeout
+	oFsm.isEndSwDlOpen = false
+	// wait for the EndSwDLResponse and check, if the ONU is ready for activation
+	_ = pBaseFsm.PFsm.Event(UpgradeEvWaitEndDownload)
+} //delayAndSendEndSwDl
 
 func (oFsm *OnuUpgradeFsm) enterWaitEndDL(ctx context.Context, e *fsm.Event) {
 	logger.Infow(ctx, "OnuUpgradeFsm WaitEndDl", log.Fields{
@@ -868,7 +894,6 @@
 			return
 		}
 		oFsm.mutexUpgradeParams.Lock()
-		oFsm.isEndSwDlOpen = false                                         //no need to additionally request abort of download (already finished)
 		oFsm.volthaDownloadReason = voltha.ImageState_IMAGE_REFUSED_BY_ONU //something like 'END_DOWNLOAD_TIMEOUT' would be better (proto)
 		oFsm.mutexUpgradeParams.Unlock()
 		go func(a_pAFsm *cmn.AdapterFsm) {