[VOL-3331] Implement incremental ONU traffic flow setup request with according OMCI VLAN configuration,
now already merged with git merged patch for [VOL-3051] Create MIB template from first ONU + correction TechProfile channel processing

Signed-off-by: mpagenko <michael.pagenkopf@adtran.com>
Change-Id: Iabbf4e1bc16da9c115e8e4002fd328a4c6bf33fb
diff --git a/internal/pkg/onuadaptercore/device_handler.go b/internal/pkg/onuadaptercore/device_handler.go
index 93dbb6c..395b369 100644
--- a/internal/pkg/onuadaptercore/device_handler.go
+++ b/internal/pkg/onuadaptercore/device_handler.go
@@ -32,9 +32,13 @@
 	me "github.com/opencord/omci-lib-go/generated"
 	"github.com/opencord/voltha-lib-go/v3/pkg/adapters/adapterif"
 	"github.com/opencord/voltha-lib-go/v3/pkg/db"
+	flow "github.com/opencord/voltha-lib-go/v3/pkg/flows"
 	"github.com/opencord/voltha-lib-go/v3/pkg/log"
 	vc "github.com/opencord/voltha-protos/v3/go/common"
 	ic "github.com/opencord/voltha-protos/v3/go/inter_container"
+	"github.com/opencord/voltha-protos/v3/go/openflow_13"
+	of "github.com/opencord/voltha-protos/v3/go/openflow_13"
+	ofp "github.com/opencord/voltha-protos/v3/go/openflow_13"
 	oop "github.com/opencord/voltha-protos/v3/go/openolt"
 	"github.com/opencord/voltha-protos/v3/go/voltha"
 )
@@ -123,11 +127,12 @@
 	//onus     sync.Map
 	//portStats          *OpenOltStatisticsMgr
 	//metrics            *pmmetrics.PmMetrics
-	stopCollector      chan bool
-	stopHeartbeatCheck chan bool
-	activePorts        sync.Map
-	uniEntityMap       map[uint32]*OnuUniPort
-	reconciling        bool
+	stopCollector       chan bool
+	stopHeartbeatCheck  chan bool
+	activePorts         sync.Map
+	uniEntityMap        map[uint32]*OnuUniPort
+	UniVlanConfigFsmMap map[uint8]*UniVlanConfigFsm
+	reconciling         bool
 }
 
 //NewDeviceHandler creates a new device handler
@@ -151,6 +156,7 @@
 	dh.activePorts = sync.Map{}
 	//TODO initialize the support classes.
 	dh.uniEntityMap = make(map[uint32]*OnuUniPort)
+	dh.UniVlanConfigFsmMap = make(map[uint8]*UniVlanConfigFsm)
 	dh.reconciling = false
 
 	// Device related state machine
@@ -315,7 +321,7 @@
 				var wg sync.WaitGroup
 				wg.Add(2) // for the 2 go routines to finish
 				// attention: deadline completion check and wg.Done is to be done in both routines
-				go dh.pOnuTP.configureUniTp(dctx, techProfMsg.UniId, techProfMsg.Path, &wg)
+				go dh.pOnuTP.configureUniTp(dctx, uint8(techProfMsg.UniId), techProfMsg.Path, &wg)
 				go dh.pOnuTP.updateOnuTpPathKvStore(dctx, &wg)
 				//the wait.. function is responsible for tpProcMutex.Unlock()
 				err := dh.pOnuTP.waitForTpCompletion(cancel, &wg) //wait for background process to finish and collect their result
@@ -408,6 +414,58 @@
 	return nil
 }
 
+//FlowUpdateIncremental removes and/or adds the flow changes on a given device
+func (dh *DeviceHandler) FlowUpdateIncremental(apOfFlowChanges *openflow_13.FlowChanges,
+	apOfGroupChanges *openflow_13.FlowGroupChanges, apFlowMetaData *voltha.FlowMetadata) error {
+
+	//Remove flows
+	if apOfFlowChanges.ToRemove != nil {
+		for _, flowItem := range apOfFlowChanges.ToRemove.Items {
+			logger.Debugw("incremental flow item remove", log.Fields{"deviceId": dh.deviceID,
+				"Item": flowItem})
+		}
+	}
+	if apOfFlowChanges.ToAdd != nil {
+		for _, flowItem := range apOfFlowChanges.ToAdd.Items {
+			if flowItem.GetCookie() == 0 {
+				logger.Debugw("incremental flow add - no cookie - ignore", log.Fields{
+					"deviceId": dh.deviceID})
+				continue
+			}
+			flowInPort := flow.GetInPort(flowItem)
+			if flowInPort == uint32(of.OfpPortNo_OFPP_INVALID) {
+				logger.Errorw("flow inPort invalid", log.Fields{"deviceID": dh.deviceID})
+				return errors.New("flow inPort invalid")
+			} else if flowInPort == dh.ponPortNumber {
+				//this is some downstream flow
+				logger.Debugw("incremental flow ignore downstream", log.Fields{
+					"deviceId": dh.deviceID, "inPort": flowInPort})
+				continue
+			} else {
+				// this is the relevant upstream flow
+				var loUniPort *OnuUniPort
+				if uniPort, exist := dh.uniEntityMap[flowInPort]; exist {
+					loUniPort = uniPort
+				} else {
+					logger.Errorw("flow inPort not found in UniPorts",
+						log.Fields{"deviceID": dh.deviceID, "inPort": flowInPort})
+					return fmt.Errorf("flow-parameter inPort %d not found in internal UniPorts", flowInPort)
+				}
+				flowOutPort := flow.GetOutPort(flowItem)
+				logger.Debugw("incremental flow-add port indications", log.Fields{
+					"deviceId": dh.deviceID, "inPort": flowInPort, "outPort": flowOutPort,
+					"uniPortName": loUniPort.name})
+				err := dh.addFlowItemToUniPort(flowItem, loUniPort)
+				//abort processing in error case
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+	return nil
+}
+
 //DisableDevice locks the ONU and its UNI/VEIP ports (admin lock via OMCI)
 // TODO!!! Clarify usage of this method, it is for sure not used within ONOS (OLT) device disable
 //         maybe it is obsolete by now
@@ -509,7 +567,7 @@
 		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)
+		go dh.pOnuTP.configureUniTp(dctx, uint8(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
@@ -1080,6 +1138,18 @@
 				if dh.pOnuTP.pAniConfigFsm != nil {
 					dh.pOnuTP.pAniConfigFsm.pAdaptFsm.pFsm.Event(aniEvReset)
 				}
+				for _, uniPort := range dh.uniEntityMap {
+					//reset the TechProfileConfig Done state for all (active) UNI's
+					dh.pOnuTP.setConfigDone(uniPort.uniId, false)
+					// reset tjhe possibly existing VlanConfigFsm
+					if pVlanFilterFsm, exist := dh.UniVlanConfigFsmMap[uniPort.uniId]; exist {
+						//VlanFilterFsm exists and was already started
+						pVlanFilterStatemachine := pVlanFilterFsm.pAdaptFsm.pFsm
+						if pVlanFilterStatemachine != nil {
+							pVlanFilterStatemachine.Event(vlanEvReset)
+						}
+					}
+				}
 			}
 			//TODO!!! care about PM/Alarm processing once started
 		}
@@ -1275,8 +1345,6 @@
 	case OmciAniConfigDone:
 		{
 			logger.Debugw("OmciAniConfigDone event received", log.Fields{"device-id": dh.deviceID})
-			//TODO!: it might be needed to check some 'cached' pending flow configuration (vlan setting)
-			//  - to consider with outstanding flow implementation
 			// attention: the device reason update is done based on ONU-UNI-Port related activity
 			//  - which may cause some inconsistency
 			if dh.deviceReason != "tech-profile-config-download-success" {
@@ -1298,6 +1366,28 @@
 				dh.deviceReason = "tech-profile-config-download-success"
 			}
 		}
+	case OmciVlanFilterDone:
+		{
+			logger.Debugw("OmciVlanFilterDone 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
+			//			yield self.core_proxy.device_reason_update(self.device_id, 'omci-flows-pushed')
+
+			if dh.deviceReason != "omci-flows-pushed" {
+				// 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
+				if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "omci-flows-pushed"); err != nil {
+					logger.Errorw("error-DeviceReasonUpdate to 'omci-flows-pushed'",
+						log.Fields{"device-id": dh.deviceID, "error": err})
+				} else {
+					logger.Infow("updated dev reason to ''omci-flows-pushed'",
+						log.Fields{"device-id": dh.deviceID})
+				}
+				//set internal state anyway - as it was done
+				dh.deviceReason = "omci-flows-pushed"
+			}
+		}
 	default:
 		{
 			logger.Warnw("unhandled-device-event", log.Fields{"device-id": dh.deviceID, "event": dev_Event})
@@ -1415,7 +1505,7 @@
 		log.Fields{"device-id": a_deviceID, "with-EventName": de.DeviceEventName})
 }
 
-// createUniLockFsm initialises and runs the UniLock FSM to transfer teh OMCi related commands for port lock/unlock
+// createUniLockFsm initialises and runs the UniLock FSM to transfer the OMCI related commands for port lock/unlock
 func (dh *DeviceHandler) createUniLockFsm(aAdminState bool, devEvent OnuDeviceEvent) {
 	chLSFsm := make(chan Message, 2048)
 	var sFsmName string
@@ -1503,3 +1593,257 @@
 
 	return kvbackend
 }
+
+//addFlowItemToUniPort parses the actual flow item to add it to the UniPort
+func (dh *DeviceHandler) addFlowItemToUniPort(apFlowItem *ofp.OfpFlowStats, apUniPort *OnuUniPort) error {
+	var loSetVlan uint16 = uint16(of.OfpVlanId_OFPVID_NONE)      //noValidEntry
+	var loMatchVlan uint16 = uint16(of.OfpVlanId_OFPVID_PRESENT) //reserved VLANID entry
+	var loAddPcp, loSetPcp uint8
+	/* the TechProfileId is part of the flow Metadata - compare also comment within
+	 * OLT-Adapter:openolt_flowmgr.go
+	 *     Metadata 8 bytes:
+	 *	   Most Significant 2 Bytes = Inner VLAN
+	 *	   Next 2 Bytes = Tech Profile ID(TPID)
+	 *	   Least Significant 4 Bytes = Port ID
+	 *     Flow Metadata carries Tech-Profile (TP) ID and is mandatory in all
+	 *     subscriber related flows.
+	 */
+
+	metadata := flow.GetMetadataFromWriteMetadataAction(apFlowItem)
+	if metadata == 0 {
+		logger.Debugw("FlowAdd invalid metadata - abort",
+			log.Fields{"device-id": dh.deviceID})
+		return errors.New("FlowAdd invalid metadata")
+	}
+	loTpID := flow.GetTechProfileIDFromWriteMetaData(metadata)
+	logger.Debugw("FlowAdd TechProfileId", log.Fields{"device-id": dh.deviceID, "TP-Id": loTpID})
+	for _, field := range flow.GetOfbFields(apFlowItem) {
+		switch field.Type {
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_ETH_TYPE:
+			{
+				logger.Debugw("FlowAdd type EthType", log.Fields{"device-id": dh.deviceID,
+					"EthType": strconv.FormatInt(int64(field.GetEthType()), 16)})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_IP_PROTO:
+			{
+				loIPProto := field.GetIpProto()
+				logger.Debugw("FlowAdd type IpProto", log.Fields{"device-id": dh.deviceID,
+					"IpProto": strconv.FormatInt(int64(loIPProto), 16)})
+				if loIPProto == 2 {
+					// some workaround for TT workflow at proto == 2 (IGMP trap) -> ignore the flow
+					// avoids installing invalid EVTOCD rule
+					logger.Debugw("FlowAdd type IpProto 2: TT workaround: ignore flow",
+						log.Fields{"device-id": dh.deviceID,
+							"IpProto": strconv.FormatInt(int64(loIPProto), 16)})
+					return nil
+				}
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_VID:
+			{
+				loMatchVlan = uint16(field.GetVlanVid())
+				loMatchVlanMask := uint16(field.GetVlanVidMask())
+				if !(loMatchVlan == uint16(of.OfpVlanId_OFPVID_PRESENT) &&
+					loMatchVlanMask == uint16(of.OfpVlanId_OFPVID_PRESENT)) {
+					loMatchVlan = loMatchVlan & 0xFFF // not transparent: copy only ID bits
+				}
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"VID": strconv.FormatInt(int64(loMatchVlan), 16)})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_PCP:
+			{
+				loAddPcp = uint8(field.GetVlanPcp())
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"PCP": loAddPcp})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_UDP_DST:
+			{
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"UDP-DST": strconv.FormatInt(int64(field.GetUdpDst()), 16)})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_UDP_SRC:
+			{
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"UDP-SRC": strconv.FormatInt(int64(field.GetUdpSrc()), 16)})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_IPV4_DST:
+			{
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"IPv4-DST": field.GetIpv4Dst()})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_IPV4_SRC:
+			{
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"IPv4-SRC": field.GetIpv4Src()})
+			}
+		case of.OxmOfbFieldTypes_OFPXMT_OFB_METADATA:
+			{
+				logger.Debugw("FlowAdd field type", log.Fields{"device-id": dh.deviceID,
+					"Metadata": field.GetTableMetadata()})
+			}
+			/*
+				default:
+					{
+						//all other entires ignored
+					}
+			*/
+		}
+	} //for all OfbFields
+
+	for _, action := range flow.GetActions(apFlowItem) {
+		switch action.Type {
+		/* not used:
+		case of.OfpActionType_OFPAT_OUTPUT:
+			{
+				logger.Debugw("FlowAdd action type", log.Fields{"device-id": dh.deviceID,
+					"Output": action.GetOutput()})
+			}
+		*/
+		case of.OfpActionType_OFPAT_PUSH_VLAN:
+			{
+				logger.Debugw("FlowAdd action type", log.Fields{"device-id": dh.deviceID,
+					"PushEthType": strconv.FormatInt(int64(action.GetPush().Ethertype), 16)})
+			}
+		case of.OfpActionType_OFPAT_SET_FIELD:
+			{
+				pActionSetField := action.GetSetField()
+				if pActionSetField.Field.OxmClass != of.OfpOxmClass_OFPXMC_OPENFLOW_BASIC {
+					logger.Warnw("FlowAdd action SetField invalid OxmClass (ignored)", log.Fields{"device-id": dh.deviceID,
+						"OxcmClass": pActionSetField.Field.OxmClass})
+				}
+				if pActionSetField.Field.GetOfbField().Type == of.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_VID {
+					loSetVlan = uint16(pActionSetField.Field.GetOfbField().GetVlanVid())
+					logger.Debugw("FlowAdd Set VLAN from SetField action", log.Fields{"device-id": dh.deviceID,
+						"SetVlan": strconv.FormatInt(int64(loSetVlan), 16)})
+				} else if pActionSetField.Field.GetOfbField().Type == of.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_PCP {
+					loSetPcp = uint8(pActionSetField.Field.GetOfbField().GetVlanPcp())
+					logger.Debugw("FlowAdd Set PCP from SetField action", log.Fields{"device-id": dh.deviceID,
+						"SetPcp": loSetPcp})
+				} else {
+					logger.Warnw("FlowAdd action SetField invalid FieldType", log.Fields{"device-id": dh.deviceID,
+						"Type": pActionSetField.Field.GetOfbField().Type})
+				}
+			}
+			/*
+				default:
+					{
+						//all other entires ignored
+					}
+			*/
+		}
+	} //for all Actions
+
+	if loSetVlan == uint16(of.OfpVlanId_OFPVID_NONE) && loMatchVlan != uint16(of.OfpVlanId_OFPVID_PRESENT) {
+		logger.Errorw("FlowAdd aborted - SetVlanId undefined, but MatchVid set", log.Fields{
+			"device-id": dh.deviceID, "UniPort": apUniPort.portNo,
+			"set_vid":   strconv.FormatInt(int64(loSetVlan), 16),
+			"match_vid": strconv.FormatInt(int64(loMatchVlan), 16)})
+		//TODO!!: Use DeviceId within the error response to rwCore
+		//  likewise also in other error response cases to calling components as requested in [VOL-3458]
+		return errors.New("FlowAdd Set/Match VlanId inconsistent")
+	}
+	if loSetVlan == uint16(of.OfpVlanId_OFPVID_NONE) && loMatchVlan == uint16(of.OfpVlanId_OFPVID_PRESENT) {
+		logger.Debugw("FlowAdd vlan-any/copy", log.Fields{"device-id": dh.deviceID})
+		loSetVlan = loMatchVlan //both 'transparent' (copy any)
+	} else {
+		//looks like OMCI value 4097 (copyFromOuter - for Uni double tagged) is not supported here
+		if loSetVlan != uint16(of.OfpVlanId_OFPVID_PRESENT) {
+			// not set to transparent
+			loSetVlan &= 0x0FFF //mask VID bits as prerequiste for vlanConfigFsm
+		}
+		logger.Debugw("FlowAdd vlan-set", log.Fields{"device-id": dh.deviceID})
+	}
+	//TODO!!: further FlowAdd requests may be valid even in case the FSM is already running,
+	//  e.g. for multi-step flow configuration, error treatment must be redefined in this context as requested in [VOL-3441]
+	if _, exist := dh.UniVlanConfigFsmMap[apUniPort.uniId]; exist {
+		logger.Errorw("FlowAdd aborted - FSM already running", log.Fields{
+			"device-id": dh.deviceID, "UniPort": apUniPort.portNo})
+		return errors.New("FlowAdd FSM already running")
+	}
+	return dh.createVlanFilterFsm(apUniPort,
+		loTpID, loMatchVlan, loSetVlan, loSetPcp, OmciVlanFilterDone)
+}
+
+// createVlanFilterFsm initialises and runs the VlanFilter FSM to transfer OMCI related VLAN config
+func (dh *DeviceHandler) createVlanFilterFsm(apUniPort *OnuUniPort,
+	aTpID uint16, aMatchVlan uint16, aSetVlan uint16, aSetPcp uint8, aDevEvent OnuDeviceEvent) error {
+	chVlanFilterFsm := make(chan Message, 2048)
+
+	pDevEntry := dh.GetOnuDeviceEntry(true)
+	if pDevEntry == nil {
+		logger.Errorw("No valid OnuDevice -aborting", log.Fields{"device-id": dh.deviceID})
+		return fmt.Errorf("No valid OnuDevice for device-id %x - aborting", dh.deviceID)
+	}
+
+	pVlanFilterFsm := NewUniVlanConfigFsm(dh, pDevEntry.PDevOmciCC, apUniPort, dh.pOnuTP,
+		pDevEntry.pOnuDB, aTpID, aDevEvent, "UniVlanConfigFsm", dh.deviceID, chVlanFilterFsm,
+		dh.pOpenOnuAc.AcceptIncrementalEvto, aMatchVlan, aSetVlan, aSetPcp)
+	if pVlanFilterFsm != nil {
+		dh.UniVlanConfigFsmMap[apUniPort.uniId] = pVlanFilterFsm
+		pVlanFilterStatemachine := pVlanFilterFsm.pAdaptFsm.pFsm
+		if pVlanFilterStatemachine != nil {
+			if pVlanFilterStatemachine.Is(vlanStDisabled) {
+				if err := pVlanFilterStatemachine.Event(vlanEvStart); err != nil {
+					logger.Warnw("UniVlanConfigFsm: can't start", log.Fields{"err": err})
+					return fmt.Errorf("Can't start UniVlanConfigFsm for device-id %x", dh.deviceID)
+				} else {
+					/***** UniVlanConfigFsm started */
+					logger.Debugw("UniVlanConfigFsm started", log.Fields{
+						"state": pVlanFilterStatemachine.Current(), "device-id": dh.deviceID,
+						"UniPort": apUniPort.portNo})
+				}
+			} else {
+				logger.Warnw("wrong state of UniVlanConfigFsm - want: disabled", log.Fields{
+					"have": pVlanFilterStatemachine.Current(), "device-id": dh.deviceID})
+				return fmt.Errorf("UniVlanConfigFsm not in expected disabled state for device-id %x", dh.deviceID)
+			}
+		} else {
+			logger.Errorw("UniVlanConfigFsm StateMachine invalid - cannot be executed!!", log.Fields{
+				"device-id": dh.deviceID})
+			return fmt.Errorf("UniVlanConfigFsm invalid for device-id %x", dh.deviceID)
+		}
+	} else {
+		logger.Errorw("UniVlanConfigFsm could not be created - abort!!", log.Fields{
+			"device-id": dh.deviceID, "UniPort": apUniPort.portNo})
+		return fmt.Errorf("UniVlanConfigFsm could not be created for device-id %x", dh.deviceID)
+	}
+	return nil
+}
+
+//verifyUniVlanConfigRequest checks on existence of flow configuration and starts it accordingly
+func (dh *DeviceHandler) verifyUniVlanConfigRequest(apUniPort *OnuUniPort) {
+	//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
+	if pVlanFilterFsm, exist := dh.UniVlanConfigFsmMap[apUniPort.uniId]; exist {
+		//VlanFilterFsm exists and was already started (assumed to wait for TechProfile execution here)
+		pVlanFilterStatemachine := pVlanFilterFsm.pAdaptFsm.pFsm
+		if pVlanFilterStatemachine != nil {
+			if pVlanFilterStatemachine.Is(vlanStWaitingTechProf) {
+				if err := pVlanFilterStatemachine.Event(vlanEvContinueConfig); err != nil {
+					logger.Warnw("UniVlanConfigFsm: can't continue processing", log.Fields{"err": err})
+				} else {
+					/***** UniVlanConfigFsm continued */
+					logger.Debugw("UniVlanConfigFsm continued", log.Fields{
+						"state": pVlanFilterStatemachine.Current(), "device-id": dh.deviceID,
+						"UniPort": apUniPort.portNo})
+				}
+			} else {
+				logger.Debugw("no state of UniVlanConfigFsm to be continued", log.Fields{
+					"have": pVlanFilterStatemachine.Current(), "device-id": dh.deviceID})
+			}
+		} else {
+			logger.Debugw("UniVlanConfigFsm StateMachine does not exist, no flow processing", log.Fields{
+				"device-id": dh.deviceID})
+		}
+
+	} // else: nothing to do
+}
+
+//RemoveVlanFilterFsm deletes the stored pointer to the VlanConfigFsm
+// intention is to provide this method to be called from VlanConfigFsm itself, when resources (and methods!) are cleaned up
+func (dh *DeviceHandler) RemoveVlanFilterFsm(apUniPort *OnuUniPort) {
+	logger.Debugw("remove UniVlanConfigFsm StateMachine", log.Fields{
+		"device-id": dh.deviceID, "uniPort": apUniPort.portNo})
+	//save to do, even if entry dows not exist
+	delete(dh.UniVlanConfigFsmMap, apUniPort.uniId)
+}