[VOL-3834] Avoid ONU service disruption on adapter restart

Change-Id: I21d15b09bdbfe2900edccefac9a72e498d2da9d7
diff --git a/VERSION b/VERSION
index 6cfec74..edbe966 100755
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.2.5-dev173
+1.2.5-dev174
diff --git a/internal/pkg/onuadaptercore/alarm_manager.go b/internal/pkg/onuadaptercore/alarm_manager.go
index 1efc819..fdb17b3 100644
--- a/internal/pkg/onuadaptercore/alarm_manager.go
+++ b/internal/pkg/onuadaptercore/alarm_manager.go
@@ -279,7 +279,7 @@
 	context := make(map[string]string)
 	intfID := am.getIntfIDAlarm(ctx, classID, instanceID)
 	onuID := am.pDeviceHandler.deviceID
-	serialNo := am.pDeviceHandler.pOnuOmciDevice.serialNumber
+	serialNo := am.pDeviceHandler.pOnuOmciDevice.sOnuPersistentData.PersSerialNumber
 	if intfID == nil {
 		logger.Warn(ctx, "intf-id-for-alarm-not-found", log.Fields{"alarm-no": alarm, "class-id": classID})
 		return
diff --git a/internal/pkg/onuadaptercore/device_handler.go b/internal/pkg/onuadaptercore/device_handler.go
index 6c4d5ce..3422c43 100644
--- a/internal/pkg/onuadaptercore/device_handler.go
+++ b/internal/pkg/onuadaptercore/device_handler.go
@@ -153,6 +153,12 @@
 	drTechProfileConfigDeleteSuccess:   "tech-profile-config-delete-success",
 }
 
+const (
+	cNoReconciling = iota
+	cOnuConfigReconciling
+	cSkipOnuConfigReconciling
+)
+
 //deviceHandler will interact with the ONU ? device.
 type deviceHandler struct {
 	deviceID         string
@@ -174,17 +180,18 @@
 	pOpenOnuAc      *OpenONUAC
 	pDeviceStateFsm *fsm.FSM
 	//pPonPort        *voltha.Port
-	deviceEntrySet  chan bool //channel for DeviceEntry set event
-	pOnuOmciDevice  *OnuDeviceEntry
-	pOnuTP          *onuUniTechProf
-	pOnuMetricsMgr  *onuMetricsManager
-	pAlarmMgr       *onuAlarmManager
-	exitChannel     chan int
-	lockDevice      sync.RWMutex
-	pOnuIndication  *oop.OnuIndication
-	deviceReason    uint8
-	pLockStateFsm   *lockStateFsm
-	pUnlockStateFsm *lockStateFsm
+	deviceEntrySet    chan bool //channel for DeviceEntry set event
+	pOnuOmciDevice    *OnuDeviceEntry
+	pOnuTP            *onuUniTechProf
+	pOnuMetricsMgr    *onuMetricsManager
+	pAlarmMgr         *onuAlarmManager
+	exitChannel       chan int
+	lockDevice        sync.RWMutex
+	pOnuIndication    *oop.OnuIndication
+	deviceReason      uint8
+	mutexDeviceReason sync.RWMutex
+	pLockStateFsm     *lockStateFsm
+	pUnlockStateFsm   *lockStateFsm
 
 	//flowMgr       *OpenOltFlowMgr
 	//eventMgr      *OpenOltEventMgr
@@ -206,7 +213,7 @@
 	UniVlanConfigFsmMap        map[uint8]*UniVlanConfigFsm
 	lockUpgradeFsm             sync.RWMutex
 	pOnuUpradeFsm              *OnuUpgradeFsm
-	reconciling                bool
+	reconciling                uint8
 	mutexReconcilingFlag       sync.RWMutex
 	chReconcilingFinished      chan bool //channel to indicate that reconciling has been finished
 	ReadyForSpecificOmciConfig bool
@@ -238,7 +245,7 @@
 	dh.lockVlanConfig = sync.RWMutex{}
 	dh.lockUpgradeFsm = sync.RWMutex{}
 	dh.UniVlanConfigFsmMap = make(map[uint8]*UniVlanConfigFsm)
-	dh.reconciling = false
+	dh.reconciling = cNoReconciling
 	dh.chReconcilingFinished = make(chan bool)
 	dh.ReadyForSpecificOmciConfig = false
 
@@ -359,8 +366,8 @@
 	}
 	if !dh.ReadyForSpecificOmciConfig {
 		logger.Errorw(ctx, "TechProf-set rejected: improper device state", log.Fields{"device-id": dh.deviceID,
-			"device-state": deviceReasonMap[dh.deviceReason]})
-		return fmt.Errorf("improper device state %s on device %s", deviceReasonMap[dh.deviceReason], dh.deviceID)
+			"device-state": dh.getDeviceReasonString()})
+		return fmt.Errorf("improper device state %s on device %s", dh.getDeviceReasonString(), dh.deviceID)
 	}
 	//previous state test here was just this one, now extended for more states to reject the SetRequest:
 	// at least 'mib-downloaded' should be reached for processing of this specific ONU configuration
@@ -680,7 +687,7 @@
 				// also abort for the other still possible flows here
 				if !dh.ReadyForSpecificOmciConfig {
 					logger.Errorw(ctx, "flow-add rejected: improper device state", log.Fields{"device-id": dh.deviceID,
-						"last device-reason": deviceReasonMap[dh.deviceReason]})
+						"last device-reason": dh.getDeviceReasonString()})
 					return fmt.Errorf("improper device state on device %s", dh.deviceID)
 				}
 
@@ -716,7 +723,7 @@
 	//note that disableDevice sequences in some 'ONU active' state may yield also
 	// "tech...delete-success" or "omci-flow-deleted" according to further received requests in the end
 	// - inblock state checking to prevent possibly unneeded processing (on command repitition)
-	if dh.deviceReason != drOmciAdminLock {
+	if dh.getDeviceReason() != drOmciAdminLock {
 		//disable-device shall be just a UNi/ONU-G related admin state setting
 		//all other configurations/FSM's shall not be impacted and shall execute as required by the system
 
@@ -800,6 +807,7 @@
 	pDevEntry := dh.getOnuDeviceEntry(ctx, true)
 	if pDevEntry == nil {
 		logger.Errorw(ctx, "No valid OnuDevice - aborting", log.Fields{"device-id": dh.deviceID})
+		dh.stopReconciling(ctx)
 		return
 	}
 	dh.pOnuTP.lockTpProcMutex()
@@ -813,14 +821,16 @@
 		dh.stopReconciling(ctx)
 		return
 	}
+	techProfsFound := false
+	flowsFound := false
 	for _, uniData := range pDevEntry.sOnuPersistentData.PersUniConfig {
 		//TODO: check for uni-port specific reconcilement in case of multi-uni-port-per-onu-support
 		if len(uniData.PersTpPathMap) == 0 {
-			logger.Debugw(ctx, "reconciling - no TPs have been stored before adapter restart - terminate reconcilement",
+			logger.Debugw(ctx, "reconciling - no TPs stored for uniID",
 				log.Fields{"uni-id": uniData.PersUniID, "device-id": dh.deviceID})
-			dh.stopReconciling(ctx)
-			return
+			continue
 		}
+		techProfsFound = true
 		for tpID := range uniData.PersTpPathMap {
 			// 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
@@ -836,12 +846,24 @@
 				logger.Errorw(ctx, err.Error(), log.Fields{"device-id": dh.deviceID})
 			}
 		}
-		if len(uniData.PersFlowParams) == 0 {
-			logger.Debugw(ctx, "reconciling - no flows have been stored before adapter restart - terminate reconcilement",
-				log.Fields{"uni-id": uniData.PersUniID, "device-id": dh.deviceID})
-			dh.stopReconciling(ctx)
+		if len(uniData.PersFlowParams) != 0 {
+			flowsFound = true
 		}
 	}
+	if !techProfsFound {
+		logger.Debugw(ctx, "reconciling - no TPs have been stored before adapter restart - terminate reconcilement",
+			log.Fields{"device-id": dh.deviceID})
+		dh.stopReconciling(ctx)
+		return
+	}
+	if dh.isSkipOnuConfigReconciling() {
+		dh.setDeviceReason(drTechProfileConfigDownloadSuccess)
+	}
+	if !flowsFound {
+		logger.Debugw(ctx, "reconciling - no flows have been stored before adapter restart - terminate reconcilement",
+			log.Fields{"device-id": dh.deviceID})
+		dh.stopReconciling(ctx)
+	}
 }
 
 func (dh *deviceHandler) reconcileDeviceFlowConfig(ctx context.Context) {
@@ -850,33 +872,43 @@
 	pDevEntry := dh.getOnuDeviceEntry(ctx, true)
 	if pDevEntry == nil {
 		logger.Errorw(ctx, "No valid OnuDevice - aborting", log.Fields{"device-id": dh.deviceID})
+		dh.stopReconciling(ctx)
 		return
 	}
 	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})
 		dh.stopReconciling(ctx)
 		return
 	}
+	flowsFound := false
 	for _, uniData := range pDevEntry.sOnuPersistentData.PersUniConfig {
 		//TODO: check for uni-port specific reconcilement in case of multi-uni-port-per-onu-support
 		if len(uniData.PersFlowParams) == 0 {
-			logger.Debugw(ctx, "reconciling - no flows have been stored before adapter restart - terminate reconcilement",
+			logger.Debugw(ctx, "reconciling - no flows stored for uniID",
 				log.Fields{"uni-id": uniData.PersUniID, "device-id": dh.deviceID})
-			dh.stopReconciling(ctx)
-			return
+			continue
+		}
+		if len(uniData.PersTpPathMap) == 0 {
+			logger.Warnw(ctx, "reconciling - flows but no TPs stored for uniID",
+				log.Fields{"uni-id": uniData.PersUniID, "device-id": dh.deviceID})
 		}
 		var uniPort *onuUniPort
 		var exist bool
 		uniNo := mkUniPortNum(ctx, dh.pOnuIndication.GetIntfId(), dh.pOnuIndication.GetOnuId(), uint32(uniData.PersUniID))
 		if uniPort, exist = dh.uniEntityMap[uniNo]; !exist {
-			logger.Errorw(ctx, "onuUniPort data not found!", log.Fields{"uniNo": uniNo, "device-id": dh.deviceID})
+			logger.Errorw(ctx, "reconciling - onuUniPort data not found  - terminate reconcilement",
+				log.Fields{"uniNo": uniNo, "device-id": dh.deviceID})
+			dh.stopReconciling(ctx)
 			return
 		}
+		flowsFound = true
+		flowsProcessed := 0
 		for _, flowData := range uniData.PersFlowParams {
-			logger.Debugw(ctx, "add flow with cookie slice", log.Fields{"device-id": dh.deviceID, "cookies": flowData.CookieSlice})
+			logger.Debugw(ctx, "reconciling - 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 {
@@ -894,12 +926,20 @@
 					logger.Errorw(ctx, err.Error(), log.Fields{"device-id": dh.deviceID})
 				}
 			}
+			flowsProcessed++
 		}
-		if len(uniData.PersTpPathMap) == 0 {
-			logger.Debugw(ctx, "reconciling - no TPs have been stored before adapter restart - terminate reconcilement",
-				log.Fields{"uni-id": uniData.PersUniID, "device-id": dh.deviceID})
-			dh.stopReconciling(ctx)
-		}
+		logger.Debugw(ctx, "reconciling - flows processed", log.Fields{"device-id": dh.deviceID, "flowsProcessed": flowsProcessed,
+			"numUniFlows":       dh.UniVlanConfigFsmMap[uniData.PersUniID].numUniFlows,
+			"configuredUniFlow": dh.UniVlanConfigFsmMap[uniData.PersUniID].configuredUniFlow})
+	}
+	if !flowsFound {
+		logger.Debugw(ctx, "reconciling - no flows have been stored before adapter restart - terminate reconcilement",
+			log.Fields{"device-id": dh.deviceID})
+		dh.stopReconciling(ctx)
+		return
+	}
+	if dh.isSkipOnuConfigReconciling() {
+		dh.setDeviceReason(drOmciFlowsPushed)
 	}
 }
 
@@ -1051,7 +1091,7 @@
 	dh.device.Vendor = "OpenONU"
 	dh.device.Model = "go"
 	dh.device.Reason = deviceReasonMap[drActivatingOnu]
-	dh.deviceReason = drActivatingOnu
+	dh.setDeviceReason(drActivatingOnu)
 
 	dh.logicalDeviceID = dh.deviceID // really needed - what for ??? //TODO!!!
 
@@ -1533,7 +1573,7 @@
 func (dh *deviceHandler) updateInterface(ctx context.Context, onuind *oop.OnuIndication) error {
 	//state checking to prevent unneeded processing (eg. on ONU 'unreachable' and 'down')
 	// (but note that the deviceReason may also have changed to e.g. TechProf*Delete_Success in between)
-	if dh.deviceReason != drStoppingOpenomci {
+	if dh.getDeviceReason() != drStoppingOpenomci {
 		logger.Debugw(ctx, "updateInterface-started - stopping-device", log.Fields{"device-id": dh.deviceID})
 
 		//stop all running FSM processing - make use of the DH-state as mirrored in the deviceReason
@@ -1674,38 +1714,14 @@
 func (dh *deviceHandler) processMibDatabaseSyncEvent(ctx context.Context, devEvent OnuDeviceEvent) {
 	logger.Debugw(ctx, "MibInSync event received, adding uni ports and locking the ONU interfaces", log.Fields{"device-id": dh.deviceID})
 
+	// store persistent data collected during MIB upload processing
+	if err := dh.storePersistentData(ctx); err != nil {
+		logger.Warnw(ctx, "store persistent data error - continue as there will be additional write attempts",
+			log.Fields{"device-id": dh.deviceID, "err": err})
+	}
 	_ = dh.deviceReasonUpdate(ctx, drDiscoveryMibsyncComplete, !dh.isReconciling())
-	pDevEntry := dh.getOnuDeviceEntry(ctx, false)
-	if pDevEntry == nil {
-		logger.Errorw(ctx, "No valid OnuDevice - aborting", log.Fields{"device-id": dh.deviceID})
-		return
-	}
-	i := uint8(0) //UNI Port limit: see MaxUnisPerOnu (by now 16) (OMCI supports max 255 p.b.)
-	if pptpInstKeys := pDevEntry.pOnuDB.getSortedInstKeys(
-		ctx, me.PhysicalPathTerminationPointEthernetUniClassID); len(pptpInstKeys) > 0 {
-		for _, mgmtEntityID := range pptpInstKeys {
-			logger.Debugw(ctx, "Add PPTPEthUni port for MIB-stored instance:", log.Fields{
-				"device-id": dh.deviceID, "PPTPEthUni EntityID": mgmtEntityID})
-			dh.addUniPort(ctx, mgmtEntityID, i, uniPPTP)
-			i++
-		}
-	} else {
-		logger.Debugw(ctx, "No UniG instances found", log.Fields{"device-id": dh.deviceID})
-	}
-	if veipInstKeys := pDevEntry.pOnuDB.getSortedInstKeys(
-		ctx, me.VirtualEthernetInterfacePointClassID); len(veipInstKeys) > 0 {
-		for _, mgmtEntityID := range veipInstKeys {
-			logger.Debugw(ctx, "Add VEIP for MIB-stored instance:", log.Fields{
-				"device-id": dh.deviceID, "VEIP EntityID": mgmtEntityID})
-			dh.addUniPort(ctx, mgmtEntityID, i, uniVEIP)
-			i++
-		}
-	} else {
-		logger.Debugw(ctx, "No VEIP instances found", log.Fields{"device-id": dh.deviceID})
-	}
-	if i == 0 {
-		logger.Warnw(ctx, "No PPTP instances found", log.Fields{"device-id": dh.deviceID})
-	}
+	dh.addAllUniPorts(ctx)
+
 	/* 200605: lock processing after initial MIBUpload removed now as the ONU should be in the lock state per default here */
 	/* 201117: build_dt-berlin-pod-openonugo_1T8GEM_voltha_DT_openonugo_master_test runs into error TC
 	 *    'Test Disable ONUs and OLT Then Delete ONUs and OLT for DT' with Sercom ONU, which obviously needs
@@ -1733,7 +1749,7 @@
 	 */
 	pDevEntry := dh.getOnuDeviceEntry(ctx, false)
 	if pDevEntry == nil {
-		logger.Errorw(ctx, "No valid OnuDevice -aborting", log.Fields{"device-id": dh.deviceID})
+		logger.Errorw(ctx, "No valid OnuDevice - aborting", log.Fields{"device-id": dh.deviceID})
 		return
 	}
 	pMibDlFsm := pDevEntry.pMibDownloadFsm.pFsm
@@ -1781,17 +1797,6 @@
 			logger.Debugw(ctx, "dev state updated to 'Oper.Active'", log.Fields{"device-id": dh.deviceID})
 		}
 	} else {
-		pDevEntry := dh.getOnuDeviceEntry(ctx, false)
-		if pDevEntry == nil {
-			logger.Errorw(ctx, "No valid OnuDevice - aborting", log.Fields{"device-id": dh.deviceID})
-			return
-		}
-		if pDevEntry.sOnuPersistentData.PersUniDisableDone {
-			logger.Debugw(ctx, "reconciling - uni-ports were disabled by admin before adapter restart - keep the ports locked and wait for re-enabling",
-				log.Fields{"device-id": dh.deviceID})
-			dh.stopReconciling(ctx)
-			return
-		}
 		logger.Debugw(ctx, "reconciling - don't notify core about DeviceStateUpdate to ACTIVE",
 			log.Fields{"device-id": dh.deviceID})
 	}
@@ -1805,12 +1810,25 @@
 	}
 
 	dh.ReadyForSpecificOmciConfig = true
-	// *** should generate UniUnlockStateDone event *****
-	if dh.pUnlockStateFsm == nil {
-		dh.createUniLockFsm(ctx, false, UniUnlockStateDone)
-	} else { //UnlockStateFSM already init
-		dh.pUnlockStateFsm.setSuccessEvent(UniUnlockStateDone)
-		dh.runUniLockFsm(ctx, false)
+
+	pDevEntry := dh.getOnuDeviceEntry(ctx, false)
+	if pDevEntry == nil {
+		logger.Errorw(ctx, "No valid OnuDevice - aborting", log.Fields{"device-id": dh.deviceID})
+		return
+	}
+	if dh.isReconciling() && pDevEntry.sOnuPersistentData.PersUniDisableDone {
+		logger.Debugw(ctx, "reconciling - uni-ports were disabled by admin before adapter restart - keep the ports locked",
+			log.Fields{"device-id": dh.deviceID})
+		go dh.reconcileDeviceTechProf(ctx)
+		// reconcilement will be continued after ani config is done
+	} else {
+		// *** should generate UniUnlockStateDone event *****
+		if dh.pUnlockStateFsm == nil {
+			dh.createUniLockFsm(ctx, false, UniUnlockStateDone)
+		} else { //UnlockStateFSM already init
+			dh.pUnlockStateFsm.setSuccessEvent(UniUnlockStateDone)
+			dh.runUniLockFsm(ctx, false)
+		}
 	}
 }
 
@@ -1901,7 +1919,7 @@
 		logger.Debugw(ctx, "OmciAniConfigDone event received", log.Fields{"device-id": dh.deviceID})
 		// attention: the device reason update is done based on ONU-UNI-Port related activity
 		//  - which may cause some inconsistency
-		if dh.deviceReason != drTechProfileConfigDownloadSuccess {
+		if dh.getDeviceReason() != drTechProfileConfigDownloadSuccess {
 			// which may be the case from some previous actvity even on this UNI Port (but also other UNI ports)
 			_ = dh.deviceReasonUpdate(ctx, drTechProfileConfigDownloadSuccess, !dh.isReconciling())
 		}
@@ -1912,7 +1930,7 @@
 		logger.Debugw(ctx, "OmciAniResourceRemoved event received", log.Fields{"device-id": dh.deviceID})
 		// attention: the device reason update is done based on ONU-UNI-Port related activity
 		//  - which may cause some inconsistency
-		if dh.deviceReason != drTechProfileConfigDeleteSuccess {
+		if dh.getDeviceReason() != drTechProfileConfigDeleteSuccess {
 			// which may be the case from some previous actvity even on this ONU port (but also other UNI ports)
 			_ = dh.deviceReasonUpdate(ctx, drTechProfileConfigDeleteSuccess, true)
 		}
@@ -1926,7 +1944,7 @@
 	//  - which may cause some inconsistency
 
 	if aDevEvent == OmciVlanFilterAddDone || aDevEvent == OmciVlanFilterAddDoneNoKvStore {
-		if dh.deviceReason != drOmciFlowsPushed {
+		if dh.getDeviceReason() != 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
 			_ = dh.deviceReasonUpdate(ctx, drOmciFlowsPushed, !dh.isReconciling())
@@ -1935,7 +1953,7 @@
 			}
 		}
 	} else {
-		if dh.deviceReason != drOmciFlowsDeleted {
+		if dh.getDeviceReason() != drOmciFlowsDeleted {
 			//not relevant for reconcile
 			_ = dh.deviceReasonUpdate(ctx, drOmciFlowsDeleted, true)
 		}
@@ -2021,6 +2039,40 @@
 	}
 }
 
+func (dh *deviceHandler) addAllUniPorts(ctx context.Context) {
+	pDevEntry := dh.getOnuDeviceEntry(ctx, false)
+	if pDevEntry == nil {
+		logger.Errorw(ctx, "No valid OnuDevice - aborting", log.Fields{"device-id": dh.deviceID})
+		return
+	}
+	i := uint8(0) //UNI Port limit: see MaxUnisPerOnu (by now 16) (OMCI supports max 255 p.b.)
+	if pptpInstKeys := pDevEntry.pOnuDB.getSortedInstKeys(
+		ctx, me.PhysicalPathTerminationPointEthernetUniClassID); len(pptpInstKeys) > 0 {
+		for _, mgmtEntityID := range pptpInstKeys {
+			logger.Debugw(ctx, "Add PPTPEthUni port for MIB-stored instance:", log.Fields{
+				"device-id": dh.deviceID, "PPTPEthUni EntityID": mgmtEntityID})
+			dh.addUniPort(ctx, mgmtEntityID, i, uniPPTP)
+			i++
+		}
+	} else {
+		logger.Debugw(ctx, "No PPTP instances found", log.Fields{"device-id": dh.deviceID})
+	}
+	if veipInstKeys := pDevEntry.pOnuDB.getSortedInstKeys(
+		ctx, me.VirtualEthernetInterfacePointClassID); len(veipInstKeys) > 0 {
+		for _, mgmtEntityID := range veipInstKeys {
+			logger.Debugw(ctx, "Add VEIP for MIB-stored instance:", log.Fields{
+				"device-id": dh.deviceID, "VEIP EntityID": mgmtEntityID})
+			dh.addUniPort(ctx, mgmtEntityID, i, uniVEIP)
+			i++
+		}
+	} else {
+		logger.Debugw(ctx, "No VEIP instances found", log.Fields{"device-id": dh.deviceID})
+	}
+	if i == 0 {
+		logger.Warnw(ctx, "No UniG instances found", log.Fields{"device-id": dh.deviceID})
+	}
+}
+
 // enableUniPortStateUpdate enables UniPortState and update core port state accordingly
 func (dh *deviceHandler) enableUniPortStateUpdate(ctx context.Context) {
 	//  py code was updated 2003xx to activate the real ONU UNI ports per OMCI (VEIP or PPTP)
@@ -2029,7 +2081,7 @@
 	//       # TODO: for now only support the first UNI given no requirement for multiple uni yet. Also needed to reduce flow
 	//       #  load on the core
 
-	// lock_ports(false) as done in py code here is shifted to separate call from devicevent processing
+	// lock_ports(false) as done in py code here is shifted to separate call from device event processing
 
 	for uniNo, uniPort := range dh.uniEntityMap {
 		// only if this port is validated for operState transfer
@@ -2040,7 +2092,6 @@
 				//maybe also use getter functions on uniPort - perhaps later ...
 				go dh.coreProxy.PortStateUpdate(log.WithSpanFromContext(context.TODO(), ctx), dh.deviceID, voltha.Port_ETHERNET_UNI, uniPort.portNo, uniPort.operState)
 			} else {
-				//TODO there is no retry mechanism, return error
 				logger.Debugw(ctx, "reconciling - don't notify core about PortStateUpdate", log.Fields{"device-id": dh.deviceID})
 			}
 		}
@@ -2056,8 +2107,13 @@
 		if (1<<uniPort.uniID)&activeUniPortStateUpdateMask == (1 << uniPort.uniID) {
 			logger.Infow(ctx, "onuUniPort-forced-OperState-UNKNOWN", log.Fields{"for PortNo": uniNo})
 			uniPort.setOperState(vc.OperStatus_UNKNOWN)
-			//maybe also use getter functions on uniPort - perhaps later ...
-			go dh.coreProxy.PortStateUpdate(log.WithSpanFromContext(context.TODO(), ctx), dh.deviceID, voltha.Port_ETHERNET_UNI, uniPort.portNo, uniPort.operState)
+			if !dh.isReconciling() {
+				//maybe also use getter functions on uniPort - perhaps later ...
+				go dh.coreProxy.PortStateUpdate(log.WithSpanFromContext(context.TODO(), ctx), dh.deviceID, voltha.Port_ETHERNET_UNI, uniPort.portNo, uniPort.operState)
+			} else {
+				logger.Debugw(ctx, "reconciling - don't notify core about PortStateUpdate", log.Fields{"device-id": dh.deviceID})
+			}
+
 		}
 	}
 }
@@ -2737,7 +2793,7 @@
 
 func (dh *deviceHandler) deviceReasonUpdate(ctx context.Context, deviceReason uint8, notifyCore bool) error {
 
-	dh.deviceReason = deviceReason
+	dh.setDeviceReason(deviceReason)
 	if notifyCore {
 		//TODO with VOL-3045/VOL-3046: return the error and stop further processing at calling position
 		if err := dh.coreProxy.DeviceReasonUpdate(log.WithSpanFromContext(context.TODO(), ctx), dh.deviceID, deviceReasonMap[deviceReason]); err != nil {
@@ -3070,7 +3126,7 @@
 		go dh.startAlarmManager(ctx)
 	}
 	dh.uniEntityMap = make(map[uint32]*onuUniPort)
-	dh.startReconciling(ctx)
+	dh.startReconciling(ctx, false)
 }
 
 func (dh *deviceHandler) setCollectorIsRunning(flagValue bool) {
@@ -3113,10 +3169,13 @@
 	}
 }
 
-func (dh *deviceHandler) startReconciling(ctx context.Context) {
-	logger.Debugw(ctx, "start reconciling", log.Fields{"timeout": dh.pOpenOnuAc.maxTimeoutReconciling, "device-id": dh.deviceID})
+func (dh *deviceHandler) startReconciling(ctx context.Context, skipOnuConfig bool) {
+	logger.Debugw(ctx, "start reconciling", log.Fields{"withOnuConfig": skipOnuConfig, "device-id": dh.deviceID})
+
 	if !dh.isReconciling() {
 		go func() {
+			logger.Debugw(ctx, "wait for channel signal or timeout",
+				log.Fields{"timeout": dh.pOpenOnuAc.maxTimeoutReconciling, "device-id": dh.deviceID})
 			select {
 			case <-dh.chReconcilingFinished:
 				logger.Debugw(ctx, "reconciling has been finished in time",
@@ -3126,15 +3185,17 @@
 					log.Fields{"device-id": dh.deviceID})
 			}
 			dh.mutexReconcilingFlag.Lock()
-			dh.reconciling = false
+			dh.reconciling = cNoReconciling
 			dh.mutexReconcilingFlag.Unlock()
 		}()
-		dh.mutexReconcilingFlag.Lock()
-		dh.reconciling = true
-		dh.mutexReconcilingFlag.Unlock()
-	} else {
-		logger.Warnw(ctx, "reconciling is already running", log.Fields{"device-id": dh.deviceID})
 	}
+	dh.mutexReconcilingFlag.Lock()
+	if skipOnuConfig {
+		dh.reconciling = cSkipOnuConfigReconciling
+	} else {
+		dh.reconciling = cOnuConfigReconciling
+	}
+	dh.mutexReconcilingFlag.Unlock()
 }
 
 func (dh *deviceHandler) stopReconciling(ctx context.Context) {
@@ -3148,7 +3209,29 @@
 
 func (dh *deviceHandler) isReconciling() bool {
 	dh.mutexReconcilingFlag.RLock()
-	value := dh.reconciling
-	dh.mutexReconcilingFlag.RUnlock()
+	defer dh.mutexReconcilingFlag.RUnlock()
+	return dh.reconciling != cNoReconciling
+}
+
+func (dh *deviceHandler) isSkipOnuConfigReconciling() bool {
+	dh.mutexReconcilingFlag.RLock()
+	defer dh.mutexReconcilingFlag.RUnlock()
+	return dh.reconciling == cSkipOnuConfigReconciling
+}
+
+func (dh *deviceHandler) setDeviceReason(value uint8) {
+	dh.mutexDeviceReason.Lock()
+	dh.deviceReason = value
+	dh.mutexDeviceReason.Unlock()
+}
+
+func (dh *deviceHandler) getDeviceReason() uint8 {
+	dh.mutexDeviceReason.RLock()
+	value := dh.deviceReason
+	dh.mutexDeviceReason.RUnlock()
 	return value
 }
+
+func (dh *deviceHandler) getDeviceReasonString() string {
+	return deviceReasonMap[dh.getDeviceReason()]
+}
diff --git a/internal/pkg/onuadaptercore/mib_sync.go b/internal/pkg/onuadaptercore/mib_sync.go
index 0c71848..9880cb5 100644
--- a/internal/pkg/onuadaptercore/mib_sync.go
+++ b/internal/pkg/onuadaptercore/mib_sync.go
@@ -128,71 +128,15 @@
 	oo.PDevOmciCC.pLastTxMeInstance = meInstance
 }
 
-func (oo *OnuDeviceEntry) enterGettingMibTemplate(ctx context.Context, e *fsm.Event) {
+func (oo *OnuDeviceEntry) enterGettingMibTemplateState(ctx context.Context, e *fsm.Event) {
 
 	if oo.onuSwImageIndications.activeEntityEntry.valid {
-		oo.activeSwVersion = oo.onuSwImageIndications.activeEntityEntry.version
+		oo.sOnuPersistentData.PersActiveSwVersion = oo.onuSwImageIndications.activeEntityEntry.version
 	} else {
 		logger.Errorw(ctx, "get-mib-template: no active SW version found, working with empty SW version, which might be untrustworthy",
 			log.Fields{"device-id": oo.deviceID})
 	}
-
-	meStoredFromTemplate := false
-	oo.mibTemplatePath = fmt.Sprintf(cSuffixMibTemplateKvStore, oo.vendorID, oo.equipmentID, oo.activeSwVersion)
-	logger.Debugw(ctx, "MibSync FSM - MibTemplate - etcd search string", log.Fields{"path": fmt.Sprintf("%s/%s", cBasePathMibTemplateKvStore, oo.mibTemplatePath)})
-	Value, err := oo.mibTemplateKVStore.Get(log.WithSpanFromContext(context.TODO(), ctx), oo.mibTemplatePath)
-	if err == nil {
-		if Value != nil {
-			logger.Debugf(ctx, "MibSync FSM - MibTemplate read: Key: %s, Value: %s  %s", Value.Key, Value.Value)
-
-			// swap out tokens with specific data
-			mibTmpString, _ := kvstore.ToString(Value.Value)
-			mibTmpString2 := strings.Replace(mibTmpString, "%SERIAL_NUMBER%", oo.serialNumber, -1)
-			mibTmpString = strings.Replace(mibTmpString2, "%MAC_ADDRESS%", oo.macAddress, -1)
-			mibTmpBytes := []byte(mibTmpString)
-			logger.Debugf(ctx, "MibSync FSM - MibTemplate tokens swapped out: %s", mibTmpBytes)
-
-			var firstLevelMap map[string]interface{}
-			if err = json.Unmarshal(mibTmpBytes, &firstLevelMap); err != nil {
-				logger.Errorw(ctx, "MibSync FSM - Failed to unmarshal template", log.Fields{"error": err, "device-id": oo.deviceID})
-			} else {
-				for firstLevelKey, firstLevelValue := range firstLevelMap {
-					//logger.Debugw(ctx, "MibSync FSM - firstLevelKey", log.Fields{"firstLevelKey": firstLevelKey})
-					if uint16ValidNumber, err := strconv.ParseUint(firstLevelKey, 10, 16); err == nil {
-						meClassID := me.ClassID(uint16ValidNumber)
-						//logger.Debugw(ctx, "MibSync FSM - firstLevelKey is a number in uint16-range", log.Fields{"uint16ValidNumber": uint16ValidNumber})
-						if isSupportedClassID(meClassID) {
-							//logger.Debugw(ctx, "MibSync FSM - firstLevelKey is a supported classID", log.Fields{"meClassID": meClassID})
-							secondLevelMap := firstLevelValue.(map[string]interface{})
-							for secondLevelKey, secondLevelValue := range secondLevelMap {
-								//logger.Debugw(ctx, "MibSync FSM - secondLevelKey", log.Fields{"secondLevelKey": secondLevelKey})
-								if uint16ValidNumber, err := strconv.ParseUint(secondLevelKey, 10, 16); err == nil {
-									meEntityID := uint16(uint16ValidNumber)
-									//logger.Debugw(ctx, "MibSync FSM - secondLevelKey is a number and a valid EntityId", log.Fields{"meEntityID": meEntityID})
-									thirdLevelMap := secondLevelValue.(map[string]interface{})
-									for thirdLevelKey, thirdLevelValue := range thirdLevelMap {
-										if thirdLevelKey == "Attributes" {
-											//logger.Debugw(ctx, "MibSync FSM - thirdLevelKey refers to attributes", log.Fields{"thirdLevelKey": thirdLevelKey})
-											attributesMap := thirdLevelValue.(map[string]interface{})
-											//logger.Debugw(ctx, "MibSync FSM - attributesMap", log.Fields{"attributesMap": attributesMap})
-											oo.pOnuDB.PutMe(ctx, meClassID, meEntityID, attributesMap)
-											meStoredFromTemplate = true
-										}
-									}
-								}
-							}
-						}
-					}
-				}
-			}
-		} else {
-			logger.Debugw(ctx, "No MIB template found", log.Fields{"path": oo.mibTemplatePath, "device-id": oo.deviceID})
-		}
-	} else {
-		logger.Errorf(ctx, "Get from kvstore operation failed for path",
-			log.Fields{"path": oo.mibTemplatePath, "device-id": oo.deviceID})
-	}
-	if meStoredFromTemplate {
+	if oo.getMibFromTemplate(ctx) {
 		logger.Debug(ctx, "MibSync FSM - valid MEs stored from template")
 		oo.pOnuDB.logMeDb(ctx)
 		fsmMsg = LoadMibTemplateOk
@@ -248,14 +192,7 @@
 
 func (oo *OnuDeviceEntry) enterExaminingMdsState(ctx context.Context, e *fsm.Event) {
 	logger.Debugw(ctx, "MibSync FSM", log.Fields{"Start GetMds processing in State": e.FSM.Current(), "device-id": oo.deviceID})
-	// TODO: As long as story VOL-3834 "Avoid ONU service distruption on adapter restart" is not finished,
-	// we need a full configuration cycle of the ONU to reconcile all local FSM data.
-	// Therefore we simulate a failed MDS check here to trigger this config
-	//oo.requestMdsValue(ctx)
-	logger.Debugw(ctx, "MibSync FSM - MDS examination failed - new provisioning", log.Fields{"device-id": oo.deviceID})
-	go func() {
-		_ = oo.pMibUploadFsm.pFsm.Event(ulEvMismatch)
-	}()
+	oo.requestMdsValue(ctx)
 }
 
 func (oo *OnuDeviceEntry) enterResynchronizingState(ctx context.Context, e *fsm.Event) {
@@ -268,6 +205,43 @@
 	// VOL-3793 - ONU-reconcile handling after adapter restart based on mib resync
 }
 
+func (oo *OnuDeviceEntry) enterExaminingMdsSuccessState(ctx context.Context, e *fsm.Event) {
+	logger.Debugw(ctx, "MibSync FSM",
+		log.Fields{"Start processing on examining MDS success in State": e.FSM.Current(), "device-id": oo.deviceID})
+
+	if oo.getMibFromTemplate(ctx) {
+		oo.baseDeviceHandler.startReconciling(ctx, true)
+		oo.baseDeviceHandler.addAllUniPorts(ctx)
+		oo.baseDeviceHandler.setDeviceReason(drInitialMibDownloaded)
+		oo.baseDeviceHandler.ReadyForSpecificOmciConfig = true
+		// no need to reconcile additional data for MibDownloadFsm, LockStateFsm, or UnlockStateFsm
+
+		oo.baseDeviceHandler.reconcileDeviceTechProf(ctx)
+		if oo.baseDeviceHandler.isReconciling() {
+			oo.baseDeviceHandler.reconcileDeviceFlowConfig(ctx)
+		}
+		// set admin state independent of reconciling state after tp/flow reconcilement
+		if oo.sOnuPersistentData.PersUniDisableDone {
+			oo.baseDeviceHandler.disableUniPortStateUpdate(ctx)
+			oo.baseDeviceHandler.setDeviceReason(drOmciAdminLock)
+		} else {
+			oo.baseDeviceHandler.enableUniPortStateUpdate(ctx)
+		}
+		oo.baseDeviceHandler.stopReconciling(ctx)
+		go func() {
+			_ = oo.pMibUploadFsm.pFsm.Event(ulEvSuccess)
+		}()
+
+	} else {
+		logger.Debugw(ctx, "MibSync FSM",
+			log.Fields{"Getting MIB from template not successful": e.FSM.Current(), "device-id": oo.deviceID})
+		go func() {
+			//switch to reconciling with OMCI config
+			_ = oo.pMibUploadFsm.pFsm.Event(ulEvMismatch)
+		}()
+	}
+}
+
 func (oo *OnuDeviceEntry) enterAuditingState(ctx context.Context, e *fsm.Event) {
 	logger.Debugw(ctx, "MibSync FSM", log.Fields{"Start MibAudit processing in State": e.FSM.Current(), "device-id": oo.deviceID})
 	if oo.baseDeviceHandler.checkAuditStartCondition(ctx, cUploadFsm) {
@@ -455,25 +429,25 @@
 			logger.Debugf(ctx, "MibSync FSM - GetResponse Data for %s", log.Fields{"device-id": oo.deviceID, "data-fields": msgObj}, meInstance)
 			switch meInstance {
 			case "OnuG":
-				oo.vendorID = trimStringFromInterface(meAttributes["VendorId"])
+				oo.sOnuPersistentData.PersVendorID = trimStringFromInterface(meAttributes["VendorId"])
 				snBytes, _ := me.InterfaceToOctets(meAttributes["SerialNumber"])
 				if onugSerialNumberLen == len(snBytes) {
 					snVendorPart := fmt.Sprintf("%s", snBytes[:4])
 					snNumberPart := hex.EncodeToString(snBytes[4:])
-					oo.serialNumber = snVendorPart + snNumberPart
+					oo.sOnuPersistentData.PersSerialNumber = snVendorPart + snNumberPart
 					logger.Debugw(ctx, "MibSync FSM - GetResponse Data for Onu-G - VendorId/SerialNumber", log.Fields{"device-id": oo.deviceID,
-						"onuDeviceEntry.vendorID": oo.vendorID, "onuDeviceEntry.serialNumber": oo.serialNumber})
+						"onuDeviceEntry.vendorID": oo.sOnuPersistentData.PersVendorID, "onuDeviceEntry.serialNumber": oo.sOnuPersistentData.PersSerialNumber})
 				} else {
 					logger.Infow(ctx, "MibSync FSM - SerialNumber has wrong length - fill serialNumber with zeros", log.Fields{"device-id": oo.deviceID, "length": len(snBytes)})
-					oo.serialNumber = cEmptySerialNumberString
+					oo.sOnuPersistentData.PersSerialNumber = cEmptySerialNumberString
 				}
 				// trigger retrieval of EquipmentId
 				_ = oo.pMibUploadFsm.pFsm.Event(ulEvGetEquipmentID)
 				return nil
 			case "Onu2G":
-				oo.equipmentID = trimStringFromInterface(meAttributes["EquipmentId"])
+				oo.sOnuPersistentData.PersEquipmentID = trimStringFromInterface(meAttributes["EquipmentId"])
 				logger.Debugw(ctx, "MibSync FSM - GetResponse Data for Onu2-G - EquipmentId", log.Fields{"device-id": oo.deviceID,
-					"onuDeviceEntry.equipmentID": oo.equipmentID})
+					"onuDeviceEntry.equipmentID": oo.sOnuPersistentData.PersEquipmentID})
 				// trigger retrieval of 1st SW-image info
 				_ = oo.pMibUploadFsm.pFsm.Event(ulEvGetFirstSwVersion)
 				return nil
@@ -490,12 +464,12 @@
 			case "IpHostConfigData":
 				macBytes, _ := me.InterfaceToOctets(meAttributes["MacAddress"])
 				if omciMacAddressLen == len(macBytes) {
-					oo.macAddress = hex.EncodeToString(macBytes[:])
+					oo.sOnuPersistentData.PersMacAddress = hex.EncodeToString(macBytes[:])
 					logger.Debugw(ctx, "MibSync FSM - GetResponse Data for IpHostConfigData - MacAddress", log.Fields{"device-id": oo.deviceID,
-						"onuDeviceEntry.macAddress": oo.macAddress})
+						"macAddress": oo.sOnuPersistentData.PersMacAddress})
 				} else {
 					logger.Infow(ctx, "MibSync FSM - MacAddress wrong length - fill macAddress with zeros", log.Fields{"device-id": oo.deviceID, "length": len(macBytes)})
-					oo.macAddress = cEmptyMacAddrString
+					oo.sOnuPersistentData.PersMacAddress = cEmptyMacAddrString
 				}
 				// trigger retrieval of mib template
 				_ = oo.pMibUploadFsm.pFsm.Event(ulEvGetMibTemplate)
@@ -523,7 +497,7 @@
 	imageVersion := trimStringFromInterface(meAttributes["Version"])
 	logger.Infow(ctx, "MibSync FSM - GetResponse Data for SoftwareImage",
 		log.Fields{"device-id": oo.deviceID, "entityID": entityID,
-			"version": imageVersion, "isActive": imageIsActive, "isCommitted": imageIsCommitted, "SNR": oo.serialNumber})
+			"version": imageVersion, "isActive": imageIsActive, "isCommitted": imageIsCommitted, "SNR": oo.sOnuPersistentData.PersSerialNumber})
 	if firstSwImageMeID == entityID {
 		//always accept the state of the first image (2nd image info should not yet be available)
 		if imageIsActive == swIsActive {
@@ -614,7 +588,7 @@
 			case "IpHostConfigData":
 				logger.Debugw(ctx, "MibSync FSM - erroneous result for IpHostConfigData received - ONU doesn't support ME - fill macAddress with zeros",
 					log.Fields{"device-id": oo.deviceID, "data-fields": msgObj})
-				oo.macAddress = cEmptyMacAddrString
+				oo.sOnuPersistentData.PersMacAddress = cEmptyMacAddrString
 				// trigger retrieval of mib template
 				_ = oo.pMibUploadFsm.pFsm.Event(ulEvGetMibTemplate)
 				return nil
@@ -743,9 +717,9 @@
 	logger.Debugw(ctx, "MibSync FSM - GetResponse Data for Onu-Data - MibDataSync", log.Fields{"device-id": oo.deviceID,
 		"mibDataSyncOnu": mibDataSyncOnu, "PersMibDataSyncAdpt": oo.sOnuPersistentData.PersMibDataSyncAdpt})
 
-	mdsCheckOk := oo.sOnuPersistentData.PersMibDataSyncAdpt == mibDataSyncOnu
+	mdsValuesAreEqual := oo.sOnuPersistentData.PersMibDataSyncAdpt == mibDataSyncOnu
 	if oo.pMibUploadFsm.pFsm.Is(ulStAuditing) {
-		if mdsCheckOk {
+		if mdsValuesAreEqual {
 			logger.Debugw(ctx, "MibSync FSM - mib audit - MDS check ok", log.Fields{"device-id": oo.deviceID})
 			_ = oo.pMibUploadFsm.pFsm.Event(ulEvSuccess)
 		} else {
@@ -753,7 +727,7 @@
 			_ = oo.pMibUploadFsm.pFsm.Event(ulEvMismatch)
 		}
 	} else if oo.pMibUploadFsm.pFsm.Is(ulStReAuditing) {
-		if mdsCheckOk {
+		if mdsValuesAreEqual {
 			logger.Debugw(ctx, "MibSync FSM - mib reaudit - MDS check ok", log.Fields{"device-id": oo.deviceID})
 			_ = oo.pMibUploadFsm.pFsm.Event(ulEvSuccess)
 		} else {
@@ -762,7 +736,7 @@
 			_ = oo.pMibUploadFsm.pFsm.Event(ulEvMismatch)
 		}
 	} else if oo.pMibUploadFsm.pFsm.Is(ulStExaminingMds) {
-		if mdsCheckOk {
+		if mdsValuesAreEqual && mibDataSyncOnu != 0 {
 			logger.Debugw(ctx, "MibSync FSM - MDS examination ok", log.Fields{"device-id": oo.deviceID})
 			_ = oo.pMibUploadFsm.pFsm.Event(ulEvSuccess)
 		} else {
@@ -801,3 +775,63 @@
 	}
 	return false //all other case are treated as 'nothing to commit
 }
+func (oo *OnuDeviceEntry) getMibFromTemplate(ctx context.Context) bool {
+
+	oo.mibTemplatePath = oo.buildMibTemplatePath()
+	logger.Debugw(ctx, "MibSync FSM - get Mib from template", log.Fields{"path": fmt.Sprintf("%s/%s", cBasePathMibTemplateKvStore, oo.mibTemplatePath)})
+
+	restoredFromMibTemplate := false
+	Value, err := oo.mibTemplateKVStore.Get(log.WithSpanFromContext(context.TODO(), ctx), oo.mibTemplatePath)
+	if err == nil {
+		if Value != nil {
+			logger.Debugf(ctx, "MibSync FSM - Mib template read: Key: %s, Value: %s  %s", Value.Key, Value.Value)
+
+			// swap out tokens with specific data
+			mibTmpString, _ := kvstore.ToString(Value.Value)
+			mibTmpString2 := strings.Replace(mibTmpString, "%SERIAL_NUMBER%", oo.sOnuPersistentData.PersSerialNumber, -1)
+			mibTmpString = strings.Replace(mibTmpString2, "%MAC_ADDRESS%", oo.sOnuPersistentData.PersMacAddress, -1)
+			mibTmpBytes := []byte(mibTmpString)
+			logger.Debugf(ctx, "MibSync FSM - Mib template tokens swapped out: %s", mibTmpBytes)
+
+			var firstLevelMap map[string]interface{}
+			if err = json.Unmarshal(mibTmpBytes, &firstLevelMap); err != nil {
+				logger.Errorw(ctx, "MibSync FSM - Failed to unmarshal template", log.Fields{"error": err, "device-id": oo.deviceID})
+			} else {
+				for firstLevelKey, firstLevelValue := range firstLevelMap {
+					//logger.Debugw(ctx, "MibSync FSM - firstLevelKey", log.Fields{"firstLevelKey": firstLevelKey})
+					if uint16ValidNumber, err := strconv.ParseUint(firstLevelKey, 10, 16); err == nil {
+						meClassID := me.ClassID(uint16ValidNumber)
+						//logger.Debugw(ctx, "MibSync FSM - firstLevelKey is a number in uint16-range", log.Fields{"uint16ValidNumber": uint16ValidNumber})
+						if isSupportedClassID(meClassID) {
+							//logger.Debugw(ctx, "MibSync FSM - firstLevelKey is a supported classID", log.Fields{"meClassID": meClassID})
+							secondLevelMap := firstLevelValue.(map[string]interface{})
+							for secondLevelKey, secondLevelValue := range secondLevelMap {
+								//logger.Debugw(ctx, "MibSync FSM - secondLevelKey", log.Fields{"secondLevelKey": secondLevelKey})
+								if uint16ValidNumber, err := strconv.ParseUint(secondLevelKey, 10, 16); err == nil {
+									meEntityID := uint16(uint16ValidNumber)
+									//logger.Debugw(ctx, "MibSync FSM - secondLevelKey is a number and a valid EntityId", log.Fields{"meEntityID": meEntityID})
+									thirdLevelMap := secondLevelValue.(map[string]interface{})
+									for thirdLevelKey, thirdLevelValue := range thirdLevelMap {
+										if thirdLevelKey == "Attributes" {
+											//logger.Debugw(ctx, "MibSync FSM - thirdLevelKey refers to attributes", log.Fields{"thirdLevelKey": thirdLevelKey})
+											attributesMap := thirdLevelValue.(map[string]interface{})
+											//logger.Debugw(ctx, "MibSync FSM - attributesMap", log.Fields{"attributesMap": attributesMap})
+											oo.pOnuDB.PutMe(ctx, meClassID, meEntityID, attributesMap)
+											restoredFromMibTemplate = true
+										}
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		} else {
+			logger.Debugw(ctx, "No MIB template found", log.Fields{"path": oo.mibTemplatePath, "device-id": oo.deviceID})
+		}
+	} else {
+		logger.Errorf(ctx, "Get from kvstore operation failed for path",
+			log.Fields{"path": oo.mibTemplatePath, "device-id": oo.deviceID})
+	}
+	return restoredFromMibTemplate
+}
diff --git a/internal/pkg/onuadaptercore/omci_ani_config.go b/internal/pkg/onuadaptercore/omci_ani_config.go
index 756b3f5..742a0ab 100644
--- a/internal/pkg/onuadaptercore/omci_ani_config.go
+++ b/internal/pkg/onuadaptercore/omci_ani_config.go
@@ -423,7 +423,7 @@
 	//ensure internal slices are empty (which might be set from previous run) - release memory
 	oFsm.gemPortAttribsSlice = nil
 
-	// start go routine for processing of LockState messages
+	// start go routine for processing of ANI config messages
 	go oFsm.processOmciAniMessages(ctx)
 
 	//let the state machine run forward from here directly
diff --git a/internal/pkg/onuadaptercore/omci_vlan_config.go b/internal/pkg/onuadaptercore/omci_vlan_config.go
index 3a33a0f..d541ffd 100644
--- a/internal/pkg/onuadaptercore/omci_vlan_config.go
+++ b/internal/pkg/onuadaptercore/omci_vlan_config.go
@@ -102,8 +102,10 @@
 	vlanEvFlowDataRemoved         = "vlanEvFlowDataRemoved"
 	//vlanEvTimeoutSimple  = "vlanEvTimeoutSimple"
 	//vlanEvTimeoutMids    = "vlanEvTimeoutMids"
-	vlanEvReset   = "vlanEvReset"
-	vlanEvRestart = "vlanEvRestart"
+	vlanEvReset             = "vlanEvReset"
+	vlanEvRestart           = "vlanEvRestart"
+	vlanEvSkipOmciConfig    = "vlanEvSkipOmciConfig"
+	vlanEvSkipIncFlowConfig = "vlanEvSkipIncFlowConfig"
 )
 
 const (
@@ -231,6 +233,10 @@
 				Dst: vlanStResetting},
 			// the only way to get to resource-cleared disabled state again is via "resseting"
 			{Name: vlanEvRestart, Src: []string{vlanStResetting}, Dst: vlanStDisabled},
+			// transitions for reconcile handling according to VOL-3834
+			{Name: vlanEvSkipOmciConfig, Src: []string{vlanStStarting}, Dst: vlanStConfigDone},
+			{Name: vlanEvSkipOmciConfig, Src: []string{vlanStConfigDone}, Dst: vlanStConfigIncrFlow},
+			{Name: vlanEvSkipIncFlowConfig, Src: []string{vlanStConfigIncrFlow}, Dst: vlanStConfigDone},
 		},
 		fsm.Callbacks{
 			"enter_state":                   func(e *fsm.Event) { instFsm.pAdaptFsm.logFsmStateChange(ctx, e) },
@@ -449,12 +455,24 @@
 				"device-id": oFsm.deviceID})
 
 			oFsm.numUniFlows++
+			pConfigVlanStateBaseFsm := oFsm.pAdaptFsm.pFsm
+
+			if oFsm.pDeviceHandler.isSkipOnuConfigReconciling() {
+				logger.Debugw(ctx, "reconciling - skip omci-config of additional vlan rule",
+					log.Fields{"fsmState": oFsm.pAdaptFsm.pFsm.Current(), "device-id": oFsm.deviceID})
+				oFsm.mutexFlowParams.Unlock()
+				if pConfigVlanStateBaseFsm.Is(vlanStConfigDone) {
+					go func(a_pBaseFsm *fsm.FSM) {
+						_ = a_pBaseFsm.Event(vlanEvSkipOmciConfig)
+					}(pConfigVlanStateBaseFsm)
+				}
+				return nil
+			}
 			// note: theoretical it would be possible to clear the same rule from the remove slice
 			//  (for entries that have not yet been started with removal)
 			//  but that is getting quite complicated - maybe a future optimization in case it should prove reasonable
 			// anyway the precondition here is that the FSM checks for rules to delete first and adds new rules afterwards
 
-			pConfigVlanStateBaseFsm := oFsm.pAdaptFsm.pFsm
 			if pConfigVlanStateBaseFsm.Is(vlanStConfigDone) {
 				//have to re-trigger the FSM to proceed with outstanding incremental flow configuration
 				if oFsm.configuredUniFlow == 0 {
@@ -801,8 +819,16 @@
 	//let the state machine run forward from here directly
 	pConfigVlanStateAFsm := oFsm.pAdaptFsm
 	if pConfigVlanStateAFsm != nil {
-		oFsm.mutexFlowParams.Lock()
 
+		if oFsm.pDeviceHandler.isSkipOnuConfigReconciling() {
+			logger.Debugw(ctx, "reconciling - skip omci-config of vlan rule",
+				log.Fields{"fsmState": oFsm.pAdaptFsm.pFsm.Current(), "device-id": oFsm.deviceID})
+			go func(a_pAFsm *AdapterFsm) {
+				_ = a_pAFsm.pFsm.Event(vlanEvSkipOmciConfig)
+			}(pConfigVlanStateAFsm)
+			return
+		}
+		oFsm.mutexFlowParams.Lock()
 		//possibly the entry is not valid anymore based on intermediate delete requests
 		//just a basic protection ...
 		if len(oFsm.uniVlanFlowParamsSlice) == 0 {
@@ -815,7 +841,6 @@
 			}(pConfigVlanStateAFsm)
 			return
 		}
-
 		//access to uniVlanFlowParamsSlice is done on first element only here per definition
 		//store the actual rule that shall be worked upon in the following transient states
 		oFsm.actualUniVlanConfigRule = oFsm.uniVlanFlowParamsSlice[0].VlanRuleParams
@@ -926,6 +951,7 @@
 }
 
 func (oFsm *UniVlanConfigFsm) enterVlanConfigDone(ctx context.Context, e *fsm.Event) {
+
 	oFsm.mutexFlowParams.RLock()
 	defer oFsm.mutexFlowParams.RUnlock()
 
@@ -953,6 +979,12 @@
 		return
 	}
 	if oFsm.numUniFlows > oFsm.configuredUniFlow {
+		if oFsm.pDeviceHandler.isSkipOnuConfigReconciling() {
+			oFsm.configuredUniFlow = oFsm.numUniFlows
+			logger.Debugw(ctx, "reconciling - skip enterVlanConfigDone processing",
+				log.Fields{"numUniFlows": oFsm.numUniFlows, "configuredUniFlow": oFsm.configuredUniFlow, "device-id": oFsm.deviceID})
+			return
+		}
 		if oFsm.configuredUniFlow == 0 {
 			// this is a restart with a complete new flow, we can re-use the initial flow config control
 			// including the check, if the related techProfile is (still) available (probably also removed in between)
@@ -997,6 +1029,15 @@
 }
 
 func (oFsm *UniVlanConfigFsm) enterConfigIncrFlow(ctx context.Context, e *fsm.Event) {
+
+	if oFsm.pDeviceHandler.isSkipOnuConfigReconciling() {
+		logger.Debugw(ctx, "reconciling - skip further processing for incremental flow",
+			log.Fields{"fsmState": oFsm.pAdaptFsm.pFsm.Current(), "device-id": oFsm.deviceID})
+		go func(a_pBaseFsm *fsm.FSM) {
+			_ = a_pBaseFsm.Event(vlanEvSkipIncFlowConfig)
+		}(oFsm.pAdaptFsm.pFsm)
+		return
+	}
 	oFsm.mutexFlowParams.Lock()
 	logger.Debugw(ctx, "UniVlanConfigFsm - start config further incremental flow", log.Fields{
 		"in state": e.FSM.Current(), "recent flow-number": oFsm.configuredUniFlow,
@@ -1159,7 +1200,7 @@
 				oFsm.pLastTxMeInstance = meInstance
 			} else {
 				logger.Debugw(ctx, "UniVlanConfigFsm delete VTFD OMCI handling skipped based on device state", log.Fields{
-					"device-id": oFsm.deviceID, "device-state": deviceReasonMap[oFsm.pDeviceHandler.deviceReason]})
+					"device-id": oFsm.deviceID, "device-state": oFsm.pDeviceHandler.getDeviceReasonString()})
 			}
 		} else {
 			//many VTFD already should exists - find and remove the one concerned by the actual remove rule
@@ -1197,7 +1238,7 @@
 					oFsm.pLastTxMeInstance = meInstance
 				} else {
 					logger.Debugw(ctx, "UniVlanConfigFsm set VTFD OMCI handling skipped based on device state", log.Fields{
-						"device-id": oFsm.deviceID, "device-state": deviceReasonMap[oFsm.pDeviceHandler.deviceReason]})
+						"device-id": oFsm.deviceID, "device-state": oFsm.pDeviceHandler.getDeviceReasonString()})
 				}
 			} else {
 				logger.Warnw(ctx, "UniVlanConfigFsm: requested VLAN for removal not found in list - ignore and continue (no VTFD set)",
diff --git a/internal/pkg/onuadaptercore/onu_device_entry.go b/internal/pkg/onuadaptercore/onu_device_entry.go
index 25ffaa3..fa21e68 100644
--- a/internal/pkg/onuadaptercore/onu_device_entry.go
+++ b/internal/pkg/onuadaptercore/onu_device_entry.go
@@ -79,6 +79,7 @@
 	ulStInSync                 = "ulStInSync"
 	ulStExaminingMds           = "ulStExaminingMds"
 	ulStResynchronizing        = "ulStResynchronizing"
+	ulStExaminingMdsSuccess    = "ulStExaminingMdsSuccess"
 	ulStAuditing               = "ulStAuditing"
 	ulStReAuditing             = "ulStReAuditing"
 	ulStOutOfSync              = "ulStOutOfSync"
@@ -237,7 +238,11 @@
 type onuPersistentData struct {
 	PersOnuID            uint32          `json:"onu_id"`
 	PersIntfID           uint32          `json:"intf_id"`
-	PersSnr              string          `json:"serial_number"`
+	PersSerialNumber     string          `json:"serial_number"`
+	PersMacAddress       string          `json:"mac_address"`
+	PersVendorID         string          `json:"vendor_id"`
+	PersEquipmentID      string          `json:"equipment_id"`
+	PersActiveSwVersion  string          `json:"active_sw_version"`
 	PersAdminState       string          `json:"admin_state"`
 	PersOperState        string          `json:"oper_state"`
 	PersUniUnlockDone    bool            `json:"uni_unlock_done"`
@@ -266,12 +271,7 @@
 	onuKVStorePath        string
 	onuKVStoreprocResult  error //error indication of processing
 	chOnuKvProcessingStep chan uint8
-	vendorID              string
-	serialNumber          string
-	equipmentID           string
 	onuSwImageIndications sSwImageIndications
-	activeSwVersion       string
-	macAddress            string
 	//lockDeviceEntries           sync.RWMutex
 	mibDbClass    func(context.Context) error
 	supportedFsms OmciDeviceFsms
@@ -373,12 +373,16 @@
 			{Name: ulEvSuccess, Src: []string{ulStUploading}, Dst: ulStUploadDone},
 
 			{Name: ulEvSuccess, Src: []string{ulStUploadDone}, Dst: ulStInSync},
-			{Name: ulEvSuccess, Src: []string{ulStExaminingMds}, Dst: ulStInSync},
+			//{Name: ulEvSuccess, Src: []string{ulStExaminingMds}, Dst: ulStInSync},
+			{Name: ulEvSuccess, Src: []string{ulStExaminingMds}, Dst: ulStExaminingMdsSuccess},
 			// TODO: As long as mib-resynchronizing is not implemented, failed MDS-examination triggers
 			// mib-reset and new provisioning at this point
 			//{Name: ulEvMismatch, Src: []string{ulStExaminingMds}, Dst: ulStResynchronizing},
 			{Name: ulEvMismatch, Src: []string{ulStExaminingMds}, Dst: ulStResettingMib},
 
+			{Name: ulEvSuccess, Src: []string{ulStExaminingMdsSuccess}, Dst: ulStInSync},
+			{Name: ulEvMismatch, Src: []string{ulStExaminingMdsSuccess}, Dst: ulStResettingMib},
+
 			{Name: ulEvAuditMib, Src: []string{ulStInSync}, Dst: ulStAuditing},
 
 			{Name: ulEvSuccess, Src: []string{ulStOutOfSync}, Dst: ulStInSync},
@@ -412,11 +416,12 @@
 			"enter_" + ulStGettingFirstSwVersion:  func(e *fsm.Event) { onuDeviceEntry.enterGettingFirstSwVersionState(ctx, e) },
 			"enter_" + ulStGettingSecondSwVersion: func(e *fsm.Event) { onuDeviceEntry.enterGettingSecondSwVersionState(ctx, e) },
 			"enter_" + ulStGettingMacAddress:      func(e *fsm.Event) { onuDeviceEntry.enterGettingMacAddressState(ctx, e) },
-			"enter_" + ulStGettingMibTemplate:     func(e *fsm.Event) { onuDeviceEntry.enterGettingMibTemplate(ctx, e) },
+			"enter_" + ulStGettingMibTemplate:     func(e *fsm.Event) { onuDeviceEntry.enterGettingMibTemplateState(ctx, e) },
 			"enter_" + ulStUploading:              func(e *fsm.Event) { onuDeviceEntry.enterUploadingState(ctx, e) },
 			"enter_" + ulStUploadDone:             func(e *fsm.Event) { onuDeviceEntry.enterUploadDoneState(ctx, e) },
 			"enter_" + ulStExaminingMds:           func(e *fsm.Event) { onuDeviceEntry.enterExaminingMdsState(ctx, e) },
 			"enter_" + ulStResynchronizing:        func(e *fsm.Event) { onuDeviceEntry.enterResynchronizingState(ctx, e) },
+			"enter_" + ulStExaminingMdsSuccess:    func(e *fsm.Event) { onuDeviceEntry.enterExaminingMdsSuccessState(ctx, e) },
 			"enter_" + ulStAuditing:               func(e *fsm.Event) { onuDeviceEntry.enterAuditingState(ctx, e) },
 			"enter_" + ulStReAuditing:             func(e *fsm.Event) { onuDeviceEntry.enterReAuditingState(ctx, e) },
 			"enter_" + ulStOutOfSync:              func(e *fsm.Event) { onuDeviceEntry.enterOutOfSyncState(ctx, e) },
@@ -586,7 +591,8 @@
 	}
 	oo.persUniConfigMutex.Lock()
 	defer oo.persUniConfigMutex.Unlock()
-	oo.sOnuPersistentData = onuPersistentData{0, 0, "", "", "", false, false, oo.mibAuditInterval, 0, 0, make([]uniPersConfig, 0)}
+	oo.sOnuPersistentData =
+		onuPersistentData{0, 0, "", "", "", "", "", "", "", false, false, oo.mibAuditInterval, 0, 0, make([]uniPersConfig, 0)}
 	oo.onuKVStoreMutex.RLock()
 	Value, err := oo.onuKVStore.Get(ctx, oo.onuKVStorePath)
 	oo.onuKVStoreMutex.RUnlock()
@@ -638,9 +644,9 @@
 	oo.persUniConfigMutex.Lock()
 	defer oo.persUniConfigMutex.Unlock()
 
-	oo.sOnuPersistentData.PersUniConfig = nil                                                                                      //releasing all UniConfig entries to garbage collector
-	oo.sOnuPersistentData = onuPersistentData{0, 0, "", "", "", false, false, oo.mibAuditInterval, 0, 0, make([]uniPersConfig, 0)} //default entry
-
+	oo.sOnuPersistentData.PersUniConfig = nil //releasing all UniConfig entries to garbage collector default entry
+	oo.sOnuPersistentData =
+		onuPersistentData{0, 0, "", "", "", "", "", "", "", false, false, oo.mibAuditInterval, 0, 0, make([]uniPersConfig, 0)}
 	logger.Debugw(ctx, "delete ONU-data from KVStore", log.Fields{"device-id": oo.deviceID})
 	oo.onuKVStoreMutex.Lock()
 	err := oo.onuKVStore.Delete(ctx, oo.onuKVStorePath)
@@ -676,7 +682,6 @@
 	//assign values which are not already present when newOnuDeviceEntry() is called
 	oo.sOnuPersistentData.PersOnuID = oo.baseDeviceHandler.pOnuIndication.OnuId
 	oo.sOnuPersistentData.PersIntfID = oo.baseDeviceHandler.pOnuIndication.IntfId
-	oo.sOnuPersistentData.PersSnr = oo.baseDeviceHandler.pOnuOmciDevice.serialNumber
 	//TODO: verify usage of these values during restart UC
 	oo.sOnuPersistentData.PersAdminState = oo.baseDeviceHandler.pOnuIndication.AdminState
 	oo.sOnuPersistentData.PersOperState = oo.baseDeviceHandler.pOnuIndication.OperState
@@ -828,3 +833,7 @@
 	}
 	logger.Debugf(ctx, "mibDataSync updated - mds: %d - device-id: %s", oo.sOnuPersistentData.PersMibDataSyncAdpt, oo.deviceID)
 }
+
+func (oo *OnuDeviceEntry) buildMibTemplatePath() string {
+	return fmt.Sprintf(cSuffixMibTemplateKvStore, oo.sOnuPersistentData.PersVendorID, oo.sOnuPersistentData.PersEquipmentID, oo.sOnuPersistentData.PersActiveSwVersion)
+}
diff --git a/internal/pkg/onuadaptercore/onu_uni_tp.go b/internal/pkg/onuadaptercore/onu_uni_tp.go
index 0ab368e..3fdbe19 100644
--- a/internal/pkg/onuadaptercore/onu_uni_tp.go
+++ b/internal/pkg/onuadaptercore/onu_uni_tp.go
@@ -227,6 +227,14 @@
 		return
 	}
 
+	if onuTP.baseDeviceHandler.isSkipOnuConfigReconciling() {
+		logger.Debugw(ctx, "reconciling - skip omci-config of ANI side ", log.Fields{"uni-id": aUniID, "device-id": onuTP.deviceID})
+		if _, existTP := onuTP.mapUniTpIndication[uniTpKey]; existTP {
+			onuTP.mapUniTpIndication[uniTpKey].techProfileConfigDone = true
+		}
+		return
+	}
+
 	processingStep++
 
 	valuePA, existPA := onuTP.mapPonAniConfig[uniTpKey]
@@ -633,7 +641,7 @@
 			}
 		} else {
 			logger.Debugw(ctx, "uniPonAniConfigFsm delete Gem on OMCI skipped based on device state", log.Fields{
-				"device-id": onuTP.deviceID, "device-state": onuTP.baseDeviceHandler.deviceReason})
+				"device-id": onuTP.deviceID, "device-state": onuTP.baseDeviceHandler.getDeviceReasonString()})
 		}
 		// remove GemPort from config DB
 		delete(onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams, onuTP.mapRemoveGemEntry[uniTPKey].removeGemID)
@@ -713,7 +721,7 @@
 			}
 		} else {
 			logger.Debugw(ctx, "uniPonAniConfigFsm TCont cleanup on OMCI skipped based on device state", log.Fields{
-				"device-id": onuTP.deviceID, "device-state": onuTP.baseDeviceHandler.deviceReason})
+				"device-id": onuTP.deviceID, "device-state": onuTP.baseDeviceHandler.getDeviceReasonString()})
 		}
 		//clear the internal store profile data
 		onuTP.clearAniSideConfig(ctx, aUniID, aTpID)
@@ -744,7 +752,7 @@
 	}
 }
 
-// createUniLockFsm initializes and runs the AniConfig FSM to transfer the OMCI related commands for ANI side configuration
+// createAniConfigFsm initializes and runs the AniConfig FSM to transfer the OMCI related commands for ANI side configuration
 func (onuTP *onuUniTechProf) createAniConfigFsm(ctx context.Context, aUniID uint8, aTpID uint8,
 	apCurrentUniPort *onuUniPort, devEvent OnuDeviceEvent, aProcessingStep uint8) error {
 	logger.Debugw(ctx, "createAniConfigFsm", log.Fields{"device-id": onuTP.deviceID})
diff --git a/internal/pkg/onuadaptercore/openonu.go b/internal/pkg/onuadaptercore/openonu.go
index eb1aaba..3f98031 100644
--- a/internal/pkg/onuadaptercore/openonu.go
+++ b/internal/pkg/onuadaptercore/openonu.go
@@ -322,7 +322,7 @@
 		handler := newDeviceHandler(ctx, oo.coreProxy, oo.adapterProxy, oo.eventProxy, device, oo)
 		oo.addDeviceHandlerToMap(ctx, handler)
 		handler.device = device
-		handler.startReconciling(ctx)
+		handler.startReconciling(ctx, false)
 		go handler.adoptOrReconcileDevice(ctx, handler.device)
 		// reconcilement will be continued after onu-device entry is added
 	} else {