[VOL-3828] subscriber flow remove fails in ATT scenario due to adverse sequence of flow add/del, + slight changes for MDS check

Signed-off-by: mpagenko <michael.pagenkopf@adtran.com>
Change-Id: I9071896f5b6ab1f99f65847d46f94b351dec38a6
diff --git a/internal/pkg/onuadaptercore/device_handler.go b/internal/pkg/onuadaptercore/device_handler.go
index 0f86675..c55afd3 100644
--- a/internal/pkg/onuadaptercore/device_handler.go
+++ b/internal/pkg/onuadaptercore/device_handler.go
@@ -104,19 +104,19 @@
 	cL2PmFsm
 )
 
-type idleCheckStruct struct {
-	idleCheckFunc func(*deviceHandler, context.Context, string) bool
-	idleState     string
+type omciIdleCheckStruct struct {
+	omciIdleCheckFunc func(*deviceHandler, context.Context, usedOmciConfigFsms, string) bool
+	omciIdleState     string
 }
 
-var fsmIdleStateFuncMap = map[usedOmciConfigFsms]idleCheckStruct{
-	cUploadFsm:        {(*deviceHandler).mibUploadFsmInIdleState, cMibUlFsmIdleState},
-	cDownloadFsm:      {(*deviceHandler).mibDownloadFsmInIdleState, cMibDlFsmIdleState},
-	cUniLockFsm:       {(*deviceHandler).devUniLockFsmInIdleState, cUniFsmIdleState},
-	cUniUnLockFsm:     {(*deviceHandler).devUniUnlockFsmInIdleState, cUniFsmIdleState},
-	cAniConfigFsm:     {(*deviceHandler).devAniConfigFsmInIdleState, cAniFsmIdleState},
-	cUniVlanConfigFsm: {(*deviceHandler).devUniVlanConfigFsmInIdleState, cVlanFsmIdleState},
-	cL2PmFsm:          {(*deviceHandler).l2PmFsmInIdleState, cL2PmFsmIdleState},
+var fsmOmciIdleStateFuncMap = map[usedOmciConfigFsms]omciIdleCheckStruct{
+	cUploadFsm:        {(*deviceHandler).isFsmInOmciIdleStateDefault, cMibUlFsmIdleState},
+	cDownloadFsm:      {(*deviceHandler).isFsmInOmciIdleStateDefault, cMibDlFsmIdleState},
+	cUniLockFsm:       {(*deviceHandler).isFsmInOmciIdleStateDefault, cUniFsmIdleState},
+	cUniUnLockFsm:     {(*deviceHandler).isFsmInOmciIdleStateDefault, cUniFsmIdleState},
+	cAniConfigFsm:     {(*deviceHandler).isAniConfigFsmInOmciIdleState, cAniFsmIdleState},
+	cUniVlanConfigFsm: {(*deviceHandler).isUniVlanConfigFsmInOmciIdleState, cVlanFsmIdleState},
+	cL2PmFsm:          {(*deviceHandler).isFsmInOmciIdleStateDefault, cL2PmFsmIdleState},
 }
 
 const (
@@ -200,7 +200,8 @@
 	stopAlarmManager           chan bool
 	stopHeartbeatCheck         chan bool
 	uniEntityMap               map[uint32]*onuUniPort
-	lockVlanConfig             sync.Mutex
+	mutexKvStoreContext        sync.Mutex
+	lockVlanConfig             sync.RWMutex
 	UniVlanConfigFsmMap        map[uint8]*UniVlanConfigFsm
 	reconciling                bool
 	mutexReconcilingFlag       sync.RWMutex
@@ -231,7 +232,7 @@
 	//dh.metrics = pmmetrics.NewPmMetrics(cloned.Id, pmmetrics.Frequency(150), pmmetrics.FrequencyOverride(false), pmmetrics.Grouped(false), pmmetrics.Metrics(pmNames))
 	//TODO initialize the support classes.
 	dh.uniEntityMap = make(map[uint32]*onuUniPort)
-	dh.lockVlanConfig = sync.Mutex{}
+	dh.lockVlanConfig = sync.RWMutex{}
 	dh.UniVlanConfigFsmMap = make(map[uint8]*UniVlanConfigFsm)
 	dh.reconciling = false
 	dh.chReconcilingFinished = make(chan bool)
@@ -322,7 +323,6 @@
 	// with restricted output of 16(?) bytes would be ...omciMsg.Message[:16]
 	logger.Debugw(ctx, "inter-adapter-recv-omci", log.Fields{
 		"device-id": dh.deviceID, "RxOmciMessage": hex.EncodeToString(omciMsg.Message)})
-	//receive_message(omci_msg.message)
 	pDevEntry := dh.getOnuDeviceEntry(ctx, true)
 	if pDevEntry != nil {
 		if pDevEntry.PDevOmciCC != nil {
@@ -848,7 +848,6 @@
 	}
 	pDevEntry.persUniConfigMutex.RLock()
 	defer pDevEntry.persUniConfigMutex.RUnlock()
-
 	if len(pDevEntry.sOnuPersistentData.PersUniConfig) == 0 {
 		logger.Debugw(ctx, "reconciling - no uni-configs have been stored before adapter restart - terminate reconcilement",
 			log.Fields{"device-id": dh.deviceID})
@@ -873,13 +872,16 @@
 		for _, flowData := range uniData.PersFlowParams {
 			logger.Debugw(ctx, "add flow with cookie slice", log.Fields{"device-id": dh.deviceID, "cookies": flowData.CookieSlice})
 			//the slice can be passed 'by value' here, - which internally passes its reference copy
+			dh.lockVlanConfig.RLock()
 			if _, exist = dh.UniVlanConfigFsmMap[uniData.PersUniID]; exist {
 				if err := dh.UniVlanConfigFsmMap[uniData.PersUniID].SetUniFlowParams(ctx, flowData.VlanRuleParams.TpID,
 					flowData.CookieSlice, uint16(flowData.VlanRuleParams.MatchVid), uint16(flowData.VlanRuleParams.SetVid),
 					uint8(flowData.VlanRuleParams.SetPcp)); err != nil {
 					logger.Errorw(ctx, err.Error(), log.Fields{"device-id": dh.deviceID})
 				}
+				dh.lockVlanConfig.RUnlock()
 			} else {
+				dh.lockVlanConfig.RUnlock()
 				if err := dh.createVlanFilterFsm(ctx, uniPort, flowData.VlanRuleParams.TpID, flowData.CookieSlice,
 					uint16(flowData.VlanRuleParams.MatchVid), uint16(flowData.VlanRuleParams.SetVid),
 					uint8(flowData.VlanRuleParams.SetPcp), OmciVlanFilterAddDone); err != nil {
@@ -968,8 +970,6 @@
 		"device-id": dh.deviceID, "image-name": (*apImageDsc).Name})
 	//return success to comfort the core processing during integration
 	return nil
-	// TODO!!: also verify error response behavior
-	//return fmt.Errorf("onuSwUpgrade not yet implemented in deviceHandler: %s", dh.deviceID)
 }
 
 //  deviceHandler methods that implement the adapters interface requests## end #########
@@ -1576,7 +1576,9 @@
 		}
 		for _, uniPort := range dh.uniEntityMap {
 			// reset the possibly existing VlanConfigFsm
+			dh.lockVlanConfig.RLock()
 			if pVlanFilterFsm, exist := dh.UniVlanConfigFsmMap[uniPort.uniID]; exist {
+				dh.lockVlanConfig.RUnlock()
 				//VlanFilterFsm exists and was already started
 				pVlanFilterStatemachine := pVlanFilterFsm.pAdaptFsm.pFsm
 				if pVlanFilterStatemachine != nil {
@@ -1586,6 +1588,8 @@
 					//and reset the UniVlanConfig FSM
 					_ = pVlanFilterStatemachine.Event(vlanEvReset)
 				}
+			} else {
+				dh.lockVlanConfig.RUnlock()
 			}
 		}
 	}
@@ -1850,7 +1854,7 @@
 	// attention: the device reason update is done based on ONU-UNI-Port related activity
 	//  - which may cause some inconsistency
 
-	if aDevEvent == OmciVlanFilterAddDone {
+	if aDevEvent == OmciVlanFilterAddDone || aDevEvent == OmciVlanFilterAddDoneNoKvStore {
 		if dh.deviceReason != drOmciFlowsPushed {
 			// which may be the case from some previous actvity on another UNI Port of the ONU
 			// or even some previous flow add activity on the same port
@@ -1865,9 +1869,16 @@
 			_ = dh.deviceReasonUpdate(ctx, drOmciFlowsDeleted, true)
 		}
 	}
-	if err := dh.storePersistentData(ctx); err != nil {
-		logger.Warnw(ctx, "store persistent data error - continue for now as there will be additional write attempts",
-			log.Fields{"device-id": dh.deviceID, "err": err})
+
+	if aDevEvent == OmciVlanFilterAddDone || aDevEvent == OmciVlanFilterRemDone {
+		//events that request KvStore write
+		if err := dh.storePersistentData(ctx); err != nil {
+			logger.Warnw(ctx, "store persistent data error - continue for now as there will be additional write attempts",
+				log.Fields{"device-id": dh.deviceID, "err": err})
+		}
+	} else {
+		logger.Debugw(ctx, "OmciVlanFilter*Done* - write to KvStore not requested",
+			log.Fields{"device-id": dh.deviceID})
 	}
 }
 
@@ -1902,7 +1913,7 @@
 		{
 			dh.processOmciAniConfigDoneEvent(ctx, devEvent)
 		}
-	case OmciVlanFilterAddDone, OmciVlanFilterRemDone:
+	case OmciVlanFilterAddDone, OmciVlanFilterAddDoneNoKvStore, OmciVlanFilterRemDone, OmciVlanFilterRemDoneNoKvStore:
 		{
 			dh.processOmciVlanFilterDoneEvent(ctx, devEvent)
 		}
@@ -2296,13 +2307,15 @@
 	}
 
 	//mutex protection as the update_flow rpc maybe running concurrently for different flows, perhaps also activities
-	dh.lockVlanConfig.Lock()
-	defer dh.lockVlanConfig.Unlock()
+	dh.lockVlanConfig.RLock()
 	logger.Debugw(ctx, "flow-add got lock", log.Fields{"device-id": dh.deviceID})
 	if _, exist := dh.UniVlanConfigFsmMap[apUniPort.uniID]; exist {
-		return dh.UniVlanConfigFsmMap[apUniPort.uniID].SetUniFlowParams(ctx, loTpID, loCookieSlice,
+		err := dh.UniVlanConfigFsmMap[apUniPort.uniID].SetUniFlowParams(ctx, loTpID, loCookieSlice,
 			loMatchVlan, loSetVlan, loSetPcp)
+		dh.lockVlanConfig.RUnlock()
+		return err
 	}
+	dh.lockVlanConfig.RUnlock()
 	return dh.createVlanFilterFsm(ctx, apUniPort, loTpID, loCookieSlice,
 		loMatchVlan, loSetVlan, loSetPcp, OmciVlanFilterAddDone)
 }
@@ -2337,8 +2350,8 @@
 	*/
 
 	//mutex protection as the update_flow rpc maybe running concurrently for different flows, perhaps also activities
-	dh.lockVlanConfig.Lock()
-	defer dh.lockVlanConfig.Unlock()
+	dh.lockVlanConfig.RLock()
+	defer dh.lockVlanConfig.RUnlock()
 	if _, exist := dh.UniVlanConfigFsmMap[apUniPort.uniID]; exist {
 		return dh.UniVlanConfigFsmMap[apUniPort.uniID].RemoveUniFlowParams(ctx, loCookie)
 	}
@@ -2367,7 +2380,9 @@
 		pDevEntry.pOnuDB, aTpID, aDevEvent, "UniVlanConfigFsm", chVlanFilterFsm,
 		dh.pOpenOnuAc.AcceptIncrementalEvto, aCookieSlice, aMatchVlan, aSetVlan, aSetPcp)
 	if pVlanFilterFsm != nil {
+		dh.lockVlanConfig.Lock()
 		dh.UniVlanConfigFsmMap[apUniPort.uniID] = pVlanFilterFsm
+		dh.lockVlanConfig.Unlock()
 		pVlanFilterStatemachine := pVlanFilterFsm.pAdaptFsm.pFsm
 		if pVlanFilterStatemachine != nil {
 			if pVlanFilterStatemachine.Is(vlanStDisabled) {
@@ -2422,7 +2437,10 @@
 	//TODO!! verify and start pending flow configuration
 	//some pending config request my exist in case the UniVlanConfig FSM was already started - with internal data -
 	//but execution was set to 'on hold' as first the TechProfile config had to be applied
+
+	dh.lockVlanConfig.RLock()
 	if pVlanFilterFsm, exist := dh.UniVlanConfigFsmMap[apUniPort.uniID]; exist {
+		dh.lockVlanConfig.RUnlock()
 		//VlanFilterFsm exists and was already started (assumed to wait for TechProfile execution here)
 		pVlanFilterStatemachine := pVlanFilterFsm.pAdaptFsm.pFsm
 		if pVlanFilterStatemachine != nil {
@@ -2462,7 +2480,9 @@
 			logger.Debugw(ctx, "UniVlanConfigFsm StateMachine does not exist, no flow processing", log.Fields{
 				"device-id": dh.deviceID, "UniPort": apUniPort.portNo})
 		}
-	} // else: nothing to do
+	} else {
+		dh.lockVlanConfig.RUnlock()
+	}
 }
 
 //RemoveVlanFilterFsm deletes the stored pointer to the VlanConfigFsm
@@ -2471,7 +2491,9 @@
 	logger.Debugw(ctx, "remove UniVlanConfigFsm StateMachine", log.Fields{
 		"device-id": dh.deviceID, "uniPort": apUniPort.portNo})
 	//save to do, even if entry dows not exist
+	dh.lockVlanConfig.Lock()
 	delete(dh.UniVlanConfigFsmMap, apUniPort.uniID)
+	dh.lockVlanConfig.Unlock()
 }
 
 //ProcessPendingTpDelete processes any pending TP delete (if available)
@@ -2503,10 +2525,30 @@
 	}
 }
 
+//startWritingOnuDataToKvStore initiates the KVStore write of ONU persistent data
+func (dh *deviceHandler) startWritingOnuDataToKvStore(ctx context.Context, aPDevEntry *OnuDeviceEntry) error {
+	dh.mutexKvStoreContext.Lock()         //this write routine may (could) be called with the same context,
+	defer dh.mutexKvStoreContext.Unlock() //this write routine may (could) be called with the same context,
+	// obviously then parallel processing on the cancel must be avoided
+	// deadline context to ensure completion of background routines waited for
+	//20200721: 10s proved to be less in 8*8 ONU test on local vbox machine with debug, might be further adapted
+	deadline := time.Now().Add(dh.pOpenOnuAc.maxTimeoutInterAdapterComm) //allowed run time to finish before execution
+	dctx, cancel := context.WithDeadline(context.Background(), deadline)
+
+	aPDevEntry.resetKvProcessingErrorIndication()
+	var wg sync.WaitGroup
+	wg.Add(1) // for the 1 go routine to finish
+
+	go aPDevEntry.updateOnuKvStore(log.WithSpanFromContext(dctx, ctx), &wg)
+	dh.waitForCompletion(ctx, cancel, &wg, "UpdateKvStore") //wait for background process to finish
+
+	return aPDevEntry.getKvProcessingErrorIndication()
+}
+
 //storePersUniFlowConfig updates local storage of OnuUniFlowConfig and writes it into kv-store afterwards to have it
 //available for potential reconcilement
-
-func (dh *deviceHandler) storePersUniFlowConfig(ctx context.Context, aUniID uint8, aUniVlanFlowParams *[]uniVlanFlowParams) error {
+func (dh *deviceHandler) storePersUniFlowConfig(ctx context.Context, aUniID uint8,
+	aUniVlanFlowParams *[]uniVlanFlowParams, aWriteToKvStore bool) error {
 
 	if dh.isReconciling() {
 		logger.Debugw(ctx, "reconciling - don't store persistent UniFlowConfig", log.Fields{"device-id": dh.deviceID})
@@ -2521,19 +2563,10 @@
 	}
 	pDevEntry.updateOnuUniFlowConfig(aUniID, aUniVlanFlowParams)
 
-	// deadline context to ensure completion of background routines waited for
-	//20200721: 10s proved to be less in 8*8 ONU test on local vbox machine with debug, might be further adapted
-	deadline := time.Now().Add(dh.pOpenOnuAc.maxTimeoutInterAdapterComm) //allowed run time to finish before execution
-	dctx, cancel := context.WithDeadline(context.Background(), deadline)
-
-	pDevEntry.resetKvProcessingErrorIndication()
-	var wg sync.WaitGroup
-	wg.Add(1) // for the 1 go routine to finish
-
-	go pDevEntry.updateOnuKvStore(log.WithSpanFromContext(dctx, ctx), &wg)
-	dh.waitForCompletion(ctx, cancel, &wg, "UpdateKvStore") //wait for background process to finish
-
-	return pDevEntry.getKvProcessingErrorIndication()
+	if aWriteToKvStore {
+		return dh.startWritingOnuDataToKvStore(ctx, pDevEntry)
+	}
+	return nil
 }
 
 func (dh *deviceHandler) waitForCompletion(ctx context.Context, cancel context.CancelFunc, wg *sync.WaitGroup, aCallerIdent string) {
@@ -2566,21 +2599,7 @@
 		logger.Warnw(ctx, "No valid OnuDevice", log.Fields{"device-id": dh.deviceID})
 		return fmt.Errorf("no valid OnuDevice: %s", dh.deviceID)
 	}
-	deadline := time.Now().Add(dh.pOpenOnuAc.maxTimeoutInterAdapterComm) //allowed run time to finish before execution
-	dctx, cancel := context.WithDeadline(context.Background(), deadline)
-
-	pDevEntry.resetKvProcessingErrorIndication()
-	var wg sync.WaitGroup
-	wg.Add(1) // for the 1 go routine to finish
-
-	go pDevEntry.updateOnuKvStore(dctx, &wg)
-	dh.waitForCompletion(ctx, cancel, &wg, "UpdateKvStore") //wait for background process to finish
-
-	if err := pDevEntry.getKvProcessingErrorIndication(); err != nil {
-		logger.Warnw(ctx, "KV-processing error", log.Fields{"device-id": dh.deviceID, "err": err})
-		return err
-	}
-	return nil
+	return dh.startWritingOnuDataToKvStore(ctx, pDevEntry)
 }
 
 func (dh *deviceHandler) combineErrorStrings(errS ...error) error {
@@ -2779,78 +2798,95 @@
 	return portStatus.getUniPortStatus(ctx, uniInfo.UniIndex)
 }
 
-func (dh *deviceHandler) isFsmInState(ctx context.Context, pFsm *fsm.FSM, wantedState string) bool {
-	var currentState string
-	if pFsm != nil {
-		currentState = pFsm.Current()
-		if currentState == wantedState {
-			return true
-		}
-	} else {
-		logger.Warnw(ctx, "FSM not defined!", log.Fields{"wantedState": wantedState, "device-id": dh.deviceID})
+func (dh *deviceHandler) isFsmInOmciIdleState(ctx context.Context, pFsm *fsm.FSM, wantedState string) bool {
+	if pFsm == nil {
+		return true //FSM not active - so there is no activity on omci
 	}
-	return false
+	return pFsm.Current() == wantedState
 }
 
-func (dh *deviceHandler) mibUploadFsmInIdleState(ctx context.Context, idleState string) bool {
-	return dh.isFsmInState(ctx, dh.pOnuOmciDevice.pMibUploadFsm.pFsm, idleState)
-}
-
-func (dh *deviceHandler) mibDownloadFsmInIdleState(ctx context.Context, idleState string) bool {
-	return dh.isFsmInState(ctx, dh.pOnuOmciDevice.pMibDownloadFsm.pFsm, idleState)
-}
-
-func (dh *deviceHandler) devUniLockFsmInIdleState(ctx context.Context, idleState string) bool {
-	return dh.isFsmInState(ctx, dh.pLockStateFsm.pAdaptFsm.pFsm, idleState)
-}
-
-func (dh *deviceHandler) devUniUnlockFsmInIdleState(ctx context.Context, idleState string) bool {
-	return dh.isFsmInState(ctx, dh.pUnlockStateFsm.pAdaptFsm.pFsm, idleState)
-}
-
-func (dh *deviceHandler) devAniConfigFsmInIdleState(ctx context.Context, idleState string) bool {
-	if dh.pOnuTP.pAniConfigFsm != nil {
-		for _, v := range dh.pOnuTP.pAniConfigFsm {
-			if !dh.isFsmInState(ctx, v.pAdaptFsm.pFsm, idleState) {
-				return false
+func (dh *deviceHandler) isFsmInOmciIdleStateDefault(ctx context.Context, omciFsm usedOmciConfigFsms, wantedState string) bool {
+	var pFsm *fsm.FSM
+	//note/TODO!!: might be that access to all these specific FSM; pointers need a semaphore protection as well, cmp lockUpgradeFsm
+	switch omciFsm {
+	case cUploadFsm:
+		{
+			pFsm = dh.pOnuOmciDevice.pMibUploadFsm.pFsm
+		}
+	case cDownloadFsm:
+		{
+			pFsm = dh.pOnuOmciDevice.pMibDownloadFsm.pFsm
+		}
+	case cUniLockFsm:
+		{
+			pFsm = dh.pLockStateFsm.pAdaptFsm.pFsm
+		}
+	case cUniUnLockFsm:
+		{
+			pFsm = dh.pUnlockStateFsm.pAdaptFsm.pFsm
+		}
+	case cL2PmFsm:
+		{
+			if dh.pOnuMetricsMgr != nil && dh.pOnuMetricsMgr.pAdaptFsm != nil {
+				pFsm = dh.pOnuMetricsMgr.pAdaptFsm.pFsm
+			} else {
+				return true //FSM not active - so there is no activity on omci
 			}
 		}
-		return true
-	}
-	logger.Warnw(ctx, "AniConfig FSM not defined!", log.Fields{"device-id": dh.deviceID})
-	return false
-}
-
-func (dh *deviceHandler) devUniVlanConfigFsmInIdleState(ctx context.Context, idleState string) bool {
-	if dh.UniVlanConfigFsmMap != nil {
-		for _, v := range dh.UniVlanConfigFsmMap {
-			if !dh.isFsmInState(ctx, v.pAdaptFsm.pFsm, idleState) {
-				return false
-			}
+	default:
+		{
+			logger.Errorw(ctx, "invalid stateMachine selected for idle check", log.Fields{
+				"device-id": dh.deviceID, "selectedFsm number": omciFsm})
+			return false //logical error in FSM check, do not not indicate 'idle' - we can't be sure
 		}
-		return true
 	}
-	logger.Warnw(ctx, "UniVlanConfig FSM not defined!", log.Fields{"device-id": dh.deviceID})
-	return false
+	return dh.isFsmInOmciIdleState(ctx, pFsm, wantedState)
 }
 
-func (dh *deviceHandler) l2PmFsmInIdleState(ctx context.Context, idleState string) bool {
-	if dh.pOnuMetricsMgr != nil && dh.pOnuMetricsMgr.pAdaptFsm != nil && dh.pOnuMetricsMgr.pAdaptFsm.pFsm != nil {
-		return dh.isFsmInState(ctx, dh.pOnuMetricsMgr.pAdaptFsm.pFsm, idleState)
-	}
-	logger.Warnw(ctx, "L2 PM FSM not defined!", log.Fields{"device-id": dh.deviceID})
-	return false
-}
-
-func (dh *deviceHandler) allButCallingFsmInIdleState(ctx context.Context, callingFsm usedOmciConfigFsms) bool {
-	for fsmName, fsmStruct := range fsmIdleStateFuncMap {
-		if fsmName != callingFsm && !fsmStruct.idleCheckFunc(dh, ctx, fsmStruct.idleState) {
+func (dh *deviceHandler) isAniConfigFsmInOmciIdleState(ctx context.Context, omciFsm usedOmciConfigFsms, idleState string) bool {
+	for _, v := range dh.pOnuTP.pAniConfigFsm {
+		if !dh.isFsmInOmciIdleState(ctx, v.pAdaptFsm.pFsm, idleState) {
 			return false
 		}
 	}
 	return true
 }
 
+func (dh *deviceHandler) isUniVlanConfigFsmInOmciIdleState(ctx context.Context, omciFsm usedOmciConfigFsms, idleState string) bool {
+	dh.lockVlanConfig.RLock()
+	defer dh.lockVlanConfig.RUnlock()
+	for _, v := range dh.UniVlanConfigFsmMap {
+		if !dh.isFsmInOmciIdleState(ctx, v.pAdaptFsm.pFsm, idleState) {
+			return false
+		}
+	}
+	return true //FSM not active - so there is no activity on omci
+}
+
+func (dh *deviceHandler) checkUserServiceExists(ctx context.Context) bool {
+	dh.lockVlanConfig.RLock()
+	defer dh.lockVlanConfig.RUnlock()
+	for _, v := range dh.UniVlanConfigFsmMap {
+		if v.pAdaptFsm.pFsm != nil {
+			if v.pAdaptFsm.pFsm.Is(cVlanFsmConfiguredState) {
+				return true //there is at least one VLAN FSM with some active configuration
+			}
+		}
+	}
+	return false //there is no VLAN FSM with some active configuration
+}
+
+func (dh *deviceHandler) checkAuditStartCondition(ctx context.Context, callingFsm usedOmciConfigFsms) bool {
+	for fsmName, fsmStruct := range fsmOmciIdleStateFuncMap {
+		if fsmName != callingFsm && !fsmStruct.omciIdleCheckFunc(dh, ctx, fsmName, fsmStruct.omciIdleState) {
+			return false
+		}
+	}
+	// a further check is done to identify, if at least some data traffic related configuration exists
+	// so that a user of this ONU could be 'online' (otherwise it makes no sense to check the MDS [with the intention to keep the user service up])
+	return dh.checkUserServiceExists(ctx)
+}
+
 func (dh *deviceHandler) prepareReconcilingWithActiveAdapter(ctx context.Context) {
 	logger.Debugw(ctx, "prepare to reconcile the ONU with adapter using persistency data", log.Fields{"device-id": dh.device.Id})
 	if err := dh.resetFsms(ctx, false); err != nil {