[VOL-3414] Reconcile onu-data/states in case of onu-adapter restart

Signed-off-by: Holger Hildebrandt <holger.hildebrandt@adtran.com>
Change-Id: I28493aa8487c5150139bd222c62ecec902c4afb1
diff --git a/internal/pkg/onuadaptercore/device_handler.go b/internal/pkg/onuadaptercore/device_handler.go
index 4643e91..93dbb6c 100644
--- a/internal/pkg/onuadaptercore/device_handler.go
+++ b/internal/pkg/onuadaptercore/device_handler.go
@@ -127,6 +127,7 @@
 	stopHeartbeatCheck chan bool
 	activePorts        sync.Map
 	uniEntityMap       map[uint32]*OnuUniPort
+	reconciling        bool
 }
 
 //NewDeviceHandler creates a new device handler
@@ -150,6 +151,7 @@
 	dh.activePorts = sync.Map{}
 	//TODO initialize the support classes.
 	dh.uniEntityMap = make(map[uint32]*OnuUniPort)
+	dh.reconciling = false
 
 	// Device related state machine
 	dh.pDeviceStateFsm = fsm.NewFSM(
@@ -192,9 +194,9 @@
 // ##########################################################################################
 // DeviceHandler methods that implement the adapters interface requests ##### begin #########
 
-//AdoptDevice adopts the OLT device
-func (dh *DeviceHandler) AdoptDevice(ctx context.Context, device *voltha.Device) {
-	logger.Debugw("Adopt_device", log.Fields{"device-id": device.Id, "Address": device.GetHostAndPort()})
+//AdoptOrReconcileDevice adopts the OLT device
+func (dh *DeviceHandler) AdoptOrReconcileDevice(ctx context.Context, device *voltha.Device) {
+	logger.Debugw("Adopt_or_reconcile_device", log.Fields{"device-id": device.Id, "Address": device.GetHostAndPort()})
 
 	logger.Debugw("Device FSM: ", log.Fields{"state": string(dh.pDeviceStateFsm.Current())})
 	if dh.pDeviceStateFsm.Is(devStNull) {
@@ -203,7 +205,7 @@
 		}
 		logger.Debugw("Device FSM: ", log.Fields{"state": string(dh.pDeviceStateFsm.Current())})
 	} else {
-		logger.Debug("AdoptDevice: Agent/device init already done")
+		logger.Debugw("AdoptOrReconcileDevice: Agent/device init already done", log.Fields{"device-id": device.Id})
 	}
 
 }
@@ -426,12 +428,14 @@
 		}
 
 		if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "omci-admin-lock"); err != nil {
+			//TODO with VOL-3045/VOL-3046: return the error and stop further processing
 			logger.Errorw("error-updating-reason-state", log.Fields{"device-id": dh.deviceID, "error": err})
 		}
 		dh.deviceReason = "omci-admin-lock"
 		//200604: ConnState improved to 'unreachable' (was not set in python-code), OperState 'unknown' seems to be best choice
 		if err := dh.coreProxy.DeviceStateUpdate(context.TODO(), dh.deviceID, voltha.ConnectStatus_UNREACHABLE,
 			voltha.OperStatus_UNKNOWN); err != nil {
+			//TODO with VOL-3045/VOL-3046: return the error and stop further processing
 			logger.Errorw("error-updating-device-state", log.Fields{"device-id": dh.deviceID, "error": err})
 		}
 	}
@@ -446,11 +450,13 @@
 	// TODO!!! ConnectStatus and OperStatus to be set here could be more accurate, for now just ...(like python code)
 	if err := dh.coreProxy.DeviceStateUpdate(context.TODO(), dh.deviceID, voltha.ConnectStatus_REACHABLE,
 		voltha.OperStatus_ACTIVE); err != nil {
+		//TODO with VOL-3045/VOL-3046: return the error and stop further processing
 		logger.Errorw("error-updating-device-state", log.Fields{"device-id": dh.deviceID, "error": err})
 	}
 
 	// TODO!!! DeviceReason to be set here could be more accurate, for now just ...(like python code)
 	if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "initial-mib-downloaded"); err != nil {
+		//TODO with VOL-3045/VOL-3046: return the error and stop further processing
 		logger.Errorw("error-updating-reason-state", log.Fields{"device-id": dh.deviceID, "error": err})
 	}
 	dh.deviceReason = "initial-mib-downloaded"
@@ -466,13 +472,51 @@
 	}
 }
 
-func (dh *DeviceHandler) ReconcileDevice(device *voltha.Device) error {
-	logger.Debugw("reconcile-device", log.Fields{"device-id": device.Id, "SerialNumber": device.SerialNumber})
+func (dh *DeviceHandler) ReconcileDeviceOnuInd() {
+	logger.Debugw("reconciling - simulate onu indication", log.Fields{"device-id": dh.deviceID})
+
 	if err := dh.pOnuTP.restoreFromOnuTpPathKvStore(context.TODO()); err != nil {
-		return err
+		logger.Errorw("reconciling - restoring OnuTp-data failed - abort", log.Fields{"err": err, "device-id": dh.deviceID})
+		dh.reconciling = false
+		return
 	}
-	// TODO: further actions - init PON, metrics, reload DB ...
-	return nil
+	var onu_indication oop.OnuIndication
+	onu_indication.IntfId = dh.pOnuTP.sOnuPersistentData.PersIntfID
+	onu_indication.OnuId = dh.pOnuTP.sOnuPersistentData.PersOnuID
+	onu_indication.OperState = dh.pOnuTP.sOnuPersistentData.PersOperState
+	onu_indication.AdminState = dh.pOnuTP.sOnuPersistentData.PersAdminState
+	dh.create_interface(&onu_indication)
+}
+
+func (dh *DeviceHandler) ReconcileDeviceTechProf() {
+	logger.Debugw("reconciling - trigger tech profile config", log.Fields{"device-id": dh.deviceID})
+
+	dh.pOnuTP.lockTpProcMutex()
+	// lock hangs as long as below decoupled or other related TechProfile processing is active
+	for _, uniData := range dh.pOnuTP.sOnuPersistentData.PersUniTpPath {
+		//In order to allow concurrent calls to other dh instances we do not wait for execution here
+		//but doing so we can not indicate problems to the caller (who does what with that then?)
+		//by now we just assume straightforward successful execution
+		//TODO!!! Generally: In this scheme it would be good to have some means to indicate
+		//  possible problems to the caller later autonomously
+
+		// 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(30 * time.Second) //allowed run time to finish before execution
+		dctx, cancel := context.WithDeadline(context.Background(), deadline)
+
+		dh.pOnuTP.resetProcessingErrorIndication()
+		var wg sync.WaitGroup
+		wg.Add(1) // for the 1 go routines to finish
+		// attention: deadline completion check and wg.Done is to be done in both routines
+		go dh.pOnuTP.configureUniTp(dctx, uniData.PersUniId, uniData.PersTpPath, &wg)
+		//the wait.. function is responsible for tpProcMutex.Unlock()
+		dh.pOnuTP.waitForTpCompletion(cancel, &wg) //wait for background process to finish and collect their result
+		return
+	}
+	dh.pOnuTP.unlockTpProcMutex()
+	//TODO: reset of reconciling-flag has always to be done in the last ReconcileDevice*() function
+	dh.reconciling = false
 }
 
 func (dh *DeviceHandler) DeleteDevice(device *voltha.Device) error {
@@ -493,10 +537,12 @@
 	dh.pOnuOmciDevice.Reboot(context.TODO())
 	if err := dh.coreProxy.DeviceStateUpdate(context.TODO(), dh.deviceID, voltha.ConnectStatus_UNREACHABLE,
 		voltha.OperStatus_DISCOVERED); err != nil {
+		//TODO with VOL-3045/VOL-3046: return the error and stop further processing
 		logger.Errorw("error-updating-device-state", log.Fields{"device-id": dh.deviceID, "error": err})
 		return err
 	}
 	if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "rebooting-onu"); err != nil {
+		//TODO with VOL-3045/VOL-3046: return the error and stop further processing
 		logger.Errorw("error-updating-reason-state", log.Fields{"device-id": dh.deviceID, "error": err})
 		return err
 	}
@@ -504,54 +550,6 @@
 	return nil
 }
 
-//GetOfpPortInfo returns the Voltha PortCapabilty with the logical port
-//func (dh *DeviceHandler) GetOfpPortInfo(device *voltha.Device,
-//	portNo int64) (*ic.PortCapability, error) {
-//	logger.Debugw("GetOfpPortInfo start", log.Fields{"deviceID": device.Id, "portNo": portNo})
-
-//function body as per OLTAdapter handler code
-// adapted with values from py dapter code
-//	if pUniPort, exist := dh.uniEntityMap[uint32(portNo)]; exist {
-//		var macOctets [6]uint8
-//		macOctets[5] = 0x08
-//		macOctets[4] = uint8(dh.ponPortNumber >> 8)
-//		macOctets[3] = uint8(dh.ponPortNumber)
-//		macOctets[2] = uint8(portNo >> 16)
-//		macOctets[1] = uint8(portNo >> 8)
-//		macOctets[0] = uint8(portNo)
-//		hwAddr := genMacFromOctets(macOctets)
-//		capacity := uint32(of.OfpPortFeatures_OFPPF_1GB_FD | of.OfpPortFeatures_OFPPF_FIBER)
-//		name := device.SerialNumber + "-" + strconv.FormatUint(uint64(pUniPort.macBpNo), 10)
-//		ofUniPortState := of.OfpPortState_OFPPS_LINK_DOWN
-//		if pUniPort.operState == vc.OperStatus_ACTIVE {
-//			ofUniPortState = of.OfpPortState_OFPPS_LIVE
-//		}
-//		logger.Debugw("setting LogicalPort", log.Fields{"with-name": name,
-//			"withUniPort": pUniPort.name, "withMacBase": hwAddr, "OperState": ofUniPortState})
-
-//		return &ic.PortCapability{
-//			Port: &voltha.LogicalPort{
-//				OfpPort: &of.OfpPort{
-//					Name: name,
-//					//HwAddr:     macAddressToUint32Array(dh.device.MacAddress),
-//					HwAddr:     macAddressToUint32Array(hwAddr),
-//					Config:     0,
-//					State:      uint32(ofUniPortState),
-//					Curr:       capacity,
-//					Advertised: capacity,
-//					Peer:       capacity,
-//					CurrSpeed:  uint32(of.OfpPortFeatures_OFPPF_1GB_FD),
-//					MaxSpeed:   uint32(of.OfpPortFeatures_OFPPF_1GB_FD),
-//				},
-//				DeviceId:     device.Id,
-//				DevicePortNo: uint32(portNo),
-//			},
-//		}, nil
-//	}
-//	logger.Warnw("No UniPort found - abort", log.Fields{"for PortNo": uint32(portNo)})
-//	return nil, errors.New("UniPort not found")
-//}
-
 //  DeviceHandler methods that implement the adapters interface requests## end #########
 // #####################################################################################
 
@@ -576,7 +574,13 @@
 	dh.deviceReason = "activating-onu"
 
 	dh.logicalDeviceID = dh.deviceID // really needed - what for ??? //TODO!!!
-	dh.coreProxy.DeviceUpdate(context.TODO(), dh.device)
+
+	if !dh.reconciling {
+		dh.coreProxy.DeviceUpdate(context.TODO(), dh.device)
+	} else {
+		logger.Debugw("reconciling - don't notify core about DeviceUpdate",
+			log.Fields{"device-id": dh.deviceID})
+	}
 
 	dh.parentId = dh.device.ParentId
 	dh.ponPortNumber = dh.device.ParentPortNo
@@ -597,24 +601,28 @@
 				   oper_status=self._pon.get_port().oper_status,
 				   )
 	*/
-	logger.Debug("adding-pon-port")
-	var ponPortNo uint32 = 1
-	if dh.ponPortNumber != 0 {
-		ponPortNo = dh.ponPortNumber
-	}
+	if !dh.reconciling {
+		logger.Debugw("adding-pon-port", log.Fields{"deviceID": dh.deviceID, "ponPortNo": dh.ponPortNumber})
+		var ponPortNo uint32 = 1
+		if dh.ponPortNumber != 0 {
+			ponPortNo = dh.ponPortNumber
+		}
 
-	pPonPort := &voltha.Port{
-		PortNo:     ponPortNo,
-		Label:      fmt.Sprintf("pon-%d", ponPortNo),
-		Type:       voltha.Port_PON_ONU,
-		OperStatus: voltha.OperStatus_ACTIVE,
-		Peers: []*voltha.Port_PeerPort{{DeviceId: dh.parentId, // Peer device  is OLT
-			PortNo: ponPortNo}}, // Peer port is parent's port number
-	}
-	if err = dh.coreProxy.PortCreated(context.TODO(), dh.deviceID, pPonPort); err != nil {
-		logger.Fatalf("Device FSM: PortCreated-failed-%s", err)
-		e.Cancel(err)
-		return
+		pPonPort := &voltha.Port{
+			PortNo:     ponPortNo,
+			Label:      fmt.Sprintf("pon-%d", ponPortNo),
+			Type:       voltha.Port_PON_ONU,
+			OperStatus: voltha.OperStatus_ACTIVE,
+			Peers: []*voltha.Port_PeerPort{{DeviceId: dh.parentId, // Peer device  is OLT
+				PortNo: ponPortNo}}, // Peer port is parent's port number
+		}
+		if err = dh.coreProxy.PortCreated(context.TODO(), dh.deviceID, pPonPort); err != nil {
+			logger.Fatalf("Device FSM: PortCreated-failed-%s", err)
+			e.Cancel(err)
+			return
+		}
+	} else {
+		logger.Debugw("reconciling - pon-port already added", log.Fields{"device-id": dh.deviceID})
 	}
 	logger.Debug("doStateInit-done")
 }
@@ -635,6 +643,10 @@
 		return
 	}
 
+	if dh.reconciling {
+		go dh.ReconcileDeviceOnuInd()
+		// reconcilement will be continued after mib download is done
+	}
 	/*
 			############################################################################
 			# Setup Alarm handler
@@ -854,9 +866,15 @@
 
 	dh.pOnuIndication = onuind // let's revise if storing the pointer is sufficient...
 
-	if err := dh.coreProxy.DeviceStateUpdate(context.TODO(), dh.deviceID,
-		voltha.ConnectStatus_REACHABLE, voltha.OperStatus_ACTIVATING); err != nil {
-		logger.Errorw("error-updating-device-state", log.Fields{"device-id": dh.deviceID, "error": err})
+	if !dh.reconciling {
+		if err := dh.coreProxy.DeviceStateUpdate(context.TODO(), dh.deviceID,
+			voltha.ConnectStatus_REACHABLE, voltha.OperStatus_ACTIVATING); err != nil {
+			//TODO with VOL-3045/VOL-3046: return the error and stop further processing
+			logger.Errorw("error-updating-device-state", log.Fields{"device-id": dh.deviceID, "error": err})
+		}
+	} else {
+		logger.Debugw("reconciling - don't notify core about DeviceStateUpdate to ACTIVATING",
+			log.Fields{"device-id": dh.deviceID})
 	}
 	// It does not look to me as if makes sense to work with the real core device here, (not the stored clone)?
 	// in this code the GetDevice would just make a check if the DeviceID's Device still exists in core
@@ -879,8 +897,14 @@
 		logger.Errorw("No valid OnuDevice -aborting", log.Fields{"device-id": dh.deviceID})
 		return errors.New("No valid OnuDevice")
 	}
-	if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "starting-openomci"); err != nil {
-		logger.Errorw("error-DeviceReasonUpdate to starting-openomci", log.Fields{"device-id": dh.deviceID, "error": err})
+	if !dh.reconciling {
+		if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "starting-openomci"); err != nil {
+			//TODO with VOL-3045/VOL-3046: return the error and stop further processing
+			logger.Errorw("error-DeviceReasonUpdate to starting-openomci", log.Fields{"device-id": dh.deviceID, "error": err})
+		}
+	} else {
+		logger.Debugw("reconciling - don't notify core about DeviceReasonUpdate to starting-openomci",
+			log.Fields{"device-id": dh.deviceID})
 	}
 	dh.deviceReason = "starting-openomci"
 
@@ -1080,6 +1104,7 @@
 		dh.disableUniPortStateUpdate()
 
 		if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "stopping-openomci"); err != nil {
+			//TODO with VOL-3045/VOL-3046: return the error and stop further processing
 			logger.Errorw("error-DeviceReasonUpdate to 'stopping-openomci'",
 				log.Fields{"device-id": dh.deviceID, "error": err})
 			// abort: system behavior is just unstable ...
@@ -1089,6 +1114,7 @@
 
 		if err := dh.coreProxy.DeviceStateUpdate(context.TODO(), dh.deviceID,
 			voltha.ConnectStatus_UNREACHABLE, voltha.OperStatus_DISCOVERED); err != nil {
+			//TODO with VOL-3045/VOL-3046: return the error and stop further processing
 			logger.Errorw("error-updating-device-state unreachable-discovered",
 				log.Fields{"device-id": dh.deviceID, "error": err})
 			// abort: system behavior is just unstable ...
@@ -1106,12 +1132,18 @@
 	case MibDatabaseSync:
 		{
 			logger.Debugw("MibInSync event received", log.Fields{"device-id": dh.deviceID})
-			//initiate DevStateUpdate
-			if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "discovery-mibsync-complete"); err != nil {
-				logger.Errorw("error-DeviceReasonUpdate to 'mibsync-complete'", log.Fields{
-					"device-id": dh.deviceID, "error": err})
+			if !dh.reconciling {
+				//initiate DevStateUpdate
+				if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "discovery-mibsync-complete"); err != nil {
+					//TODO with VOL-3045/VOL-3046: return the error and stop further processing
+					logger.Errorw("error-DeviceReasonUpdate to 'mibsync-complete'", log.Fields{
+						"device-id": dh.deviceID, "error": err})
+				} else {
+					logger.Infow("dev reason updated to 'MibSync complete'", log.Fields{"deviceID": dh.deviceID})
+				}
 			} else {
-				logger.Infow("dev reason updated to 'MibSync complete'", log.Fields{"device-id": dh.deviceID})
+				logger.Debugw("reconciling - don't notify core about DeviceReasonUpdate to mibsync-complete",
+					log.Fields{"device-id": dh.deviceID})
 			}
 			//set internal state anyway - as it was done
 			dh.deviceReason = "discovery-mibsync-complete"
@@ -1191,18 +1223,29 @@
 		{
 			logger.Debugw("MibDownloadDone event received", log.Fields{"device-id": dh.deviceID})
 			//initiate DevStateUpdate
-			if err := dh.coreProxy.DeviceStateUpdate(context.TODO(), dh.deviceID,
-				voltha.ConnectStatus_REACHABLE, voltha.OperStatus_ACTIVE); err != nil {
-				logger.Errorw("error-updating-device-state", log.Fields{"device-id": dh.deviceID, "error": err})
+			if !dh.reconciling {
+				if err := dh.coreProxy.DeviceStateUpdate(context.TODO(), dh.deviceID,
+					voltha.ConnectStatus_REACHABLE, voltha.OperStatus_ACTIVE); err != nil {
+					//TODO with VOL-3045/VOL-3046: return the error and stop further processing
+					logger.Errorw("error-updating-device-state", log.Fields{"device-id": dh.deviceID, "error": err})
+				} else {
+					logger.Debugw("dev state updated to 'Oper.Active'", log.Fields{"device-id": dh.deviceID})
+				}
 			} else {
-				logger.Debugw("dev state updated to 'Oper.Active'", log.Fields{"device-id": dh.deviceID})
+				logger.Debugw("reconciling - don't notify core about DeviceStateUpdate to ACTIVE",
+					log.Fields{"device-id": dh.deviceID})
 			}
-
-			if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "initial-mib-downloaded"); err != nil {
-				logger.Errorw("error-DeviceReasonUpdate to 'initial-mib-downloaded'",
-					log.Fields{"device-id": dh.deviceID, "error": err})
+			if !dh.reconciling {
+				if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "initial-mib-downloaded"); err != nil {
+					//TODO with VOL-3045/VOL-3046: return the error and stop further processing
+					logger.Errorw("error-DeviceReasonUpdate to 'initial-mib-downloaded'",
+						log.Fields{"device-id": dh.deviceID, "error": err})
+				} else {
+					logger.Infow("dev reason updated to 'initial-mib-downloaded'", log.Fields{"device-id": dh.deviceID})
+				}
 			} else {
-				logger.Infow("dev reason updated to 'initial-mib-downloaded'", log.Fields{"device-id": dh.deviceID})
+				logger.Debugw("reconciling - don't notify core about DeviceReasonUpdate to initial-mib-downloaded",
+					log.Fields{"device-id": dh.deviceID})
 			}
 			//set internal state anyway - as it was done
 			dh.deviceReason = "initial-mib-downloaded"
@@ -1218,9 +1261,16 @@
 		{
 			go dh.enableUniPortStateUpdate() //cmp python yield self.enable_ports()
 
-			logger.Infow("UniUnlockStateDone event: Sending OnuUp event", log.Fields{"device-id": dh.deviceID})
-			raisedTs := time.Now().UnixNano()
-			go dh.sendOnuOperStateEvent(voltha.OperStatus_ACTIVE, dh.deviceID, raisedTs) //cmp python onu_active_event
+			if !dh.reconciling {
+				logger.Infow("UniUnlockStateDone event: Sending OnuUp event", log.Fields{"device-id": dh.deviceID})
+				raisedTs := time.Now().UnixNano()
+				go dh.sendOnuOperStateEvent(voltha.OperStatus_ACTIVE, dh.deviceID, raisedTs) //cmp python onu_active_event
+			} else {
+				logger.Debugw("reconciling - don't notify core that onu went to active but trigger tech profile config",
+					log.Fields{"device-id": dh.deviceID})
+				go dh.ReconcileDeviceTechProf()
+				//TODO: further actions e.g. restore flows, metrics, ...
+			}
 		}
 	case OmciAniConfigDone:
 		{
@@ -1231,11 +1281,17 @@
 			//  - which may cause some inconsistency
 			if dh.deviceReason != "tech-profile-config-download-success" {
 				// which may be the case from some previous actvity on another UNI Port of the ONU
-				if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "tech-profile-config-download-success"); err != nil {
-					logger.Errorw("error-DeviceReasonUpdate to 'tech-profile-config-download-success'",
-						log.Fields{"device-id": dh.deviceID, "error": err})
+				if !dh.reconciling {
+					if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "tech-profile-config-download-success"); err != nil {
+						//TODO with VOL-3045/VOL-3046: return the error and stop further processing
+						logger.Errorw("error-DeviceReasonUpdate to 'tech-profile-config-download-success'",
+							log.Fields{"device-id": dh.deviceID, "error": err})
+					} else {
+						logger.Infow("update dev reason to 'tech-profile-config-download-success'",
+							log.Fields{"device-id": dh.deviceID})
+					}
 				} else {
-					logger.Infow("update dev reason to 'tech-profile-config-download-success'",
+					logger.Debugw("reconciling - don't notify core about DeviceReasonUpdate to tech-profile-config-download-success",
 						log.Fields{"device-id": dh.deviceID})
 				}
 				//set internal state anyway - as it was done
@@ -1263,10 +1319,14 @@
 		} else {
 			//store UniPort with the System-PortNumber key
 			dh.uniEntityMap[uniNo] = pUniPort
-			// create announce the UniPort to the core as VOLTHA Port object
-			if err := pUniPort.CreateVolthaPort(dh); err == nil {
-				logger.Infow("onuUniPort-added", log.Fields{"for PortNo": uniNo})
-			} //error logging already within UniPort method
+			if !dh.reconciling {
+				// create announce the UniPort to the core as VOLTHA Port object
+				if err := pUniPort.CreateVolthaPort(dh); err == nil {
+					logger.Infow("onuUniPort-added", log.Fields{"for PortNo": uniNo})
+				} //error logging already within UniPort method
+			} else {
+				logger.Debugw("reconciling - onuUniPort already added", log.Fields{"for PortNo": uniNo, "device-id": dh.deviceID})
+			}
 		}
 	}
 }
@@ -1286,8 +1346,12 @@
 		if (1<<uniPort.uniId)&ActiveUniPortStateUpdateMask == (1 << uniPort.uniId) {
 			logger.Infow("onuUniPort-forced-OperState-ACTIVE", log.Fields{"for PortNo": uniNo})
 			uniPort.SetOperState(vc.OperStatus_ACTIVE)
-			//maybe also use getter functions on uniPort - perhaps later ...
-			go dh.coreProxy.PortStateUpdate(context.TODO(), dh.deviceID, voltha.Port_ETHERNET_UNI, uniPort.portNo, uniPort.operState)
+			if !dh.reconciling {
+				//maybe also use getter functions on uniPort - perhaps later ...
+				go dh.coreProxy.PortStateUpdate(context.TODO(), dh.deviceID, voltha.Port_ETHERNET_UNI, uniPort.portNo, uniPort.operState)
+			} else {
+				logger.Debugw("reconciling - don't notify core about PortStateUpdate", log.Fields{"device-id": dh.deviceID})
+			}
 		}
 	}
 }