[VOL-5561] Handle Multiple ONU Indications for a device at ONU Adapter

Change-Id: I5ecbd46b2e186c76357d82171f8bf01de2729fc2
Signed-off-by: bseeniva <balaji.seenivasan@radisys.com>
diff --git a/internal/pkg/core/device_handler.go b/internal/pkg/core/device_handler.go
index 6b12134..adaa761 100755
--- a/internal/pkg/core/device_handler.go
+++ b/internal/pkg/core/device_handler.go
@@ -25,6 +25,7 @@
 	"sync"
 	"time"
 
+	"github.com/golang/protobuf/ptypes/empty"
 	"github.com/opencord/voltha-openonu-adapter-go/internal/pkg/config"
 
 	"github.com/gogo/protobuf/proto"
@@ -77,19 +78,16 @@
 
 const (
 	// events of Device FSM
-	devEvDeviceInit       = "devEvDeviceInit"
-	devEvGrpcConnected    = "devEvGrpcConnected"
-	devEvGrpcDisconnected = "devEvGrpcDisconnected"
-	devEvDeviceUpInd      = "devEvDeviceUpInd"
-	devEvDeviceDownInd    = "devEvDeviceDownInd"
+	devEvDeviceInit    = "devEvDeviceInit"
+	devEvDeviceUpInd   = "devEvDeviceUpInd"
+	devEvDeviceDownInd = "devEvDeviceDownInd"
 )
 const (
 	// states of Device FSM
-	devStNull      = "devStNull"
-	devStDown      = "devStDown"
-	devStInit      = "devStInit"
-	devStConnected = "devStConnected"
-	devStUp        = "devStUp"
+	devStNull = "devStNull"
+	devStDown = "devStDown"
+	devStInit = "devStInit"
+	devStUp   = "devStUp"
 )
 
 // Event category and subcategory definitions - same as defiend for OLT in eventmgr.go  - should be done more centrally
@@ -301,20 +299,15 @@
 		devStNull,
 		fsm.Events{
 			{Name: devEvDeviceInit, Src: []string{devStNull, devStDown}, Dst: devStInit},
-			{Name: devEvGrpcConnected, Src: []string{devStInit}, Dst: devStConnected},
-			{Name: devEvGrpcDisconnected, Src: []string{devStConnected, devStDown}, Dst: devStInit},
-			{Name: devEvDeviceUpInd, Src: []string{devStConnected, devStDown}, Dst: devStUp},
+			{Name: devEvDeviceUpInd, Src: []string{devStInit, devStDown}, Dst: devStUp},
 			{Name: devEvDeviceDownInd, Src: []string{devStUp}, Dst: devStDown},
 		},
 		fsm.Callbacks{
-			"before_event":                      func(e *fsm.Event) { dh.logStateChange(ctx, e) },
-			("before_" + devEvDeviceInit):       func(e *fsm.Event) { dh.doStateInit(ctx, e) },
-			("after_" + devEvDeviceInit):        func(e *fsm.Event) { dh.postInit(ctx, e) },
-			("before_" + devEvGrpcConnected):    func(e *fsm.Event) { dh.doStateConnected(ctx, e) },
-			("before_" + devEvGrpcDisconnected): func(e *fsm.Event) { dh.doStateInit(ctx, e) },
-			("after_" + devEvGrpcDisconnected):  func(e *fsm.Event) { dh.postInit(ctx, e) },
-			("before_" + devEvDeviceUpInd):      func(e *fsm.Event) { dh.doStateUp(ctx, e) },
-			("before_" + devEvDeviceDownInd):    func(e *fsm.Event) { dh.doStateDown(ctx, e) },
+			"before_event":                   func(e *fsm.Event) { dh.logStateChange(ctx, e) },
+			("before_" + devEvDeviceInit):    func(e *fsm.Event) { dh.doStateInit(ctx, e) },
+			("after_" + devEvDeviceInit):     func(e *fsm.Event) { dh.postInit(ctx, e) },
+			("before_" + devEvDeviceUpInd):   func(e *fsm.Event) { dh.doStateUp(ctx, e) },
+			("before_" + devEvDeviceDownInd): func(e *fsm.Event) { dh.doStateDown(ctx, e) },
 		},
 	)
 
@@ -914,7 +907,13 @@
 		}
 	}
 	if onuIndication.OperState == "up" {
-		if err := dh.createInterface(ctx, &onuIndication); err != nil {
+		if !dh.pDeviceStateFsm.Can(devEvDeviceUpInd) {
+			logger.Errorw(ctx, "invalid state transition for event device up indication", log.Fields{"currentState": dh.pDeviceStateFsm.Current()})
+			dh.stopReconciling(ctx, false, cWaitReconcileFlowNoActivity)
+			return
+		}
+		err := dh.pDeviceStateFsm.Event(devEvDeviceUpInd, &onuIndication)
+		if err != nil {
 			logger.Errorw(ctx, "failed to handle device up indication", log.Fields{"device-id": dh.DeviceID, "error": err})
 			dh.stopReconciling(ctx, false, cWaitReconcileFlowNoActivity)
 			return
@@ -2172,25 +2171,15 @@
 	logger.Debugw(ctx, "postInit-done", log.Fields{"device-id": dh.DeviceID})
 }
 
-// doStateConnected get the device info and update to voltha core
-// for comparison of the original method (not that easy to uncomment): compare here:
-//
-//	voltha-openolt-adapter/adaptercore/device_handler.go
-//	-> this one obviously initiates all communication interfaces of the device ...?
-func (dh *deviceHandler) doStateConnected(ctx context.Context, e *fsm.Event) {
-
-	logger.Debugw(ctx, "doStateConnected-started", log.Fields{"device-id": dh.DeviceID})
-	err := errors.New("device FSM: function not implemented yet")
-	e.Cancel(err)
-	logger.Debugw(ctx, "doStateConnected-done", log.Fields{"device-id": dh.DeviceID})
-}
-
 // doStateUp handle the onu up indication and update to voltha core
 func (dh *deviceHandler) doStateUp(ctx context.Context, e *fsm.Event) {
 
 	logger.Debugw(ctx, "doStateUp-started", log.Fields{"device-id": dh.DeviceID})
-	err := errors.New("device FSM: function not implemented yet")
-	e.Cancel(err)
+	onuIndication := e.Args[0].(*oop.OnuIndication)
+	if err := dh.createInterface(ctx, onuIndication); err != nil {
+		logger.Errorw(ctx, "failed to create interface", log.Fields{"device-id": dh.DeviceID, "error": err})
+		e.Cancel(err)
+	}
 	logger.Debugw(ctx, "doStateUp-done", log.Fields{"device-id": dh.DeviceID})
 
 	/*
@@ -2219,7 +2208,12 @@
 	}
 
 	cloned := proto.Clone(device).(*voltha.Device)
+
 	logger.Debugw(ctx, "do-state-down", log.Fields{"ClonedDeviceID": cloned.Id})
+	if err := dh.UpdateInterface(ctx); err != nil {
+		logger.Errorw(ctx, "failed to update interface", log.Fields{"device-id": dh.DeviceID, "error": err})
+		e.Cancel(err)
+	}
 	/*
 		// Update the all ports state on that device to disable
 		if er := dh.coreProxy.PortsStateUpdate(ctx, cloned.Id, voltha.OperStatus_UNKNOWN); er != nil {
@@ -2262,8 +2256,6 @@
 		logger.Debugw("do-state-down-end", log.Fields{"device-id": device.Id})
 		return nil
 	*/
-	err = errors.New("device FSM: function not implemented yet")
-	e.Cancel(err)
 	logger.Debugw(ctx, "doStateDown-done", log.Fields{"device-id": dh.DeviceID})
 }
 
@@ -2343,12 +2335,7 @@
 		"OnuIntfId": onuind.GetIntfId(), "OnuSerialNumber": onuind.GetSerialNumber()})
 
 	dh.pOnuIndication = onuind // let's revise if storing the pointer is sufficient...
-
-	pDevEntry := dh.GetOnuDeviceEntry(ctx, true)
-	if pDevEntry == nil {
-		logger.Errorw(ctx, "No valid OnuDevice - aborting", log.Fields{"device-id": dh.DeviceID})
-		return fmt.Errorf("no valid OnuDevice: %s", dh.DeviceID)
-	}
+	pDevEntry := dh.pOnuOmciDevice
 
 	if !dh.IsReconciling() {
 		logger.Debugw(ctx, "call DeviceStateUpdate upon create interface", log.Fields{"ConnectStatus": voltha.ConnectStatus_REACHABLE,
@@ -5338,6 +5325,55 @@
 	return dh.deviceDeleteCommChan
 }
 
+func (dh *deviceHandler) processOnuIndication(ctx context.Context, onuInd *ia.OnuIndicationMessage) (*empty.Empty, error) {
+
+	onuIndication := onuInd.OnuIndication
+	onuOperstate := onuIndication.GetOperState()
+
+	if dh.GetDeletionInProgress() {
+		logger.Warnw(ctx, "device deletion in progress, ignoring the ONU Indication", log.Fields{"device-id": dh.DeviceID})
+		return nil, fmt.Errorf("device deletion in progress for device-id: %s", dh.DeviceID)
+	}
+
+	logger.Infow(ctx, "onu-ind-request", log.Fields{"device-id": dh.DeviceID,
+		"OnuId":      onuIndication.GetOnuId(),
+		"AdminState": onuIndication.GetAdminState(), "OperState": onuOperstate,
+		"SNR": onuIndication.GetSerialNumber()})
+
+	pDevEntry := dh.GetOnuDeviceEntry(ctx, true)
+	if pDevEntry == nil {
+		logger.Errorw(ctx, "No valid OnuDevice - aborting", log.Fields{"device-id": dh.DeviceID})
+		return nil, fmt.Errorf("no valid OnuDevice: %s", dh.DeviceID)
+	}
+
+	var event string
+
+	switch onuOperstate {
+	case "up":
+		if dh.pDeviceStateFsm.Current() == devStUp {
+			logger.Errorw(ctx, "invalid state transition", log.Fields{"expectedState": devStDown, "currentState": dh.pDeviceStateFsm.Current()})
+			return nil, fmt.Errorf("invalid state transition: expected %s, got %s", devStDown, dh.pDeviceStateFsm.Current())
+		}
+		event = devEvDeviceUpInd
+	case "down", "unreachable":
+		if dh.pDeviceStateFsm.Current() == devStDown {
+			logger.Errorw(ctx, "invalid state transition", log.Fields{"expectedState": devStUp, "currentState": dh.pDeviceStateFsm.Current()})
+			return nil, fmt.Errorf("invalid state transition: expected %s, got %s", devStUp, dh.pDeviceStateFsm.Current())
+		}
+		event = devEvDeviceDownInd
+	default:
+		logger.Errorw(ctx, "unknown-onu-ind-request operState", log.Fields{"OnuId": onuIndication.GetOnuId()})
+		return nil, fmt.Errorf("invalidOperState: %s, %s", onuOperstate, dh.DeviceID)
+	}
+
+	err := dh.pDeviceStateFsm.Event(event, onuIndication)
+	if err != nil {
+		return nil, err
+	}
+
+	return &empty.Empty{}, nil
+}
+
 // PrepareForGarbageCollection - remove references to prepare for garbage collection
 func (dh *deviceHandler) PrepareForGarbageCollection(ctx context.Context, aDeviceID string) {
 	logger.Debugw(ctx, "prepare for garbage collection", log.Fields{"device-id": aDeviceID})