SPON-3043 [WIP] Added Reboot of device admin based on external (northbound) config requests

Change-Id: I0af1e3aa7fd73ddc537327e94894d4050683aa3a
diff --git a/internal/pkg/onuadaptercore/device_handler.go b/internal/pkg/onuadaptercore/device_handler.go
index d1cb6f3..023a1fa 100644
--- a/internal/pkg/onuadaptercore/device_handler.go
+++ b/internal/pkg/onuadaptercore/device_handler.go
@@ -493,6 +493,74 @@
 	return nil
 }
 
+func (dh *DeviceHandler) RebootDevice(device *voltha.Device) error {
+	logger.Debugw("reboot-device", log.Fields{"DeviceId": device.Id, "SerialNumber": device.SerialNumber})
+	if device.ConnectStatus != voltha.ConnectStatus_REACHABLE {
+		logger.Errorw("device-unreachable", log.Fields{"DeviceId": device.Id, "SerialNumber": device.SerialNumber})
+		return errors.New("device-unreachable")
+	}
+	dh.pOnuOmciDevice.Reboot(context.TODO())
+	if err := dh.coreProxy.DeviceStateUpdate(context.TODO(), dh.deviceID, voltha.ConnectStatus_UNREACHABLE,
+		voltha.OperStatus_DISCOVERED); err != nil {
+		logger.Errorw("error-updating-device-state", log.Fields{"deviceID": dh.deviceID, "error": err})
+		return err
+	}
+	if err := dh.coreProxy.DeviceReasonUpdate(context.TODO(), dh.deviceID, "rebooting-onu"); err != nil {
+		logger.Errorw("error-updating-reason-state", log.Fields{"deviceID": dh.deviceID, "error": err})
+		return err
+	}
+	dh.deviceReason = "rebooting-onu"
+	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 #########
 // #####################################################################################
 
diff --git a/internal/pkg/onuadaptercore/omci_cc.go b/internal/pkg/onuadaptercore/omci_cc.go
index 5b2b33e..04d8a39 100644
--- a/internal/pkg/onuadaptercore/omci_cc.go
+++ b/internal/pkg/onuadaptercore/omci_cc.go
@@ -25,7 +25,6 @@
 	"errors"
 	"strconv"
 	"sync"
-
 	//"time"
 
 	"github.com/google/gopacket"
@@ -581,6 +580,40 @@
 	return oo.Send(ctx, pkt, timeout, 0, highPrio, omciRxCallbackPair)
 }
 
+func (oo *OmciCC) sendReboot(ctx context.Context, timeout int, highPrio bool, responseChannel chan Message) error {
+	logger.Debugw("send Reboot-msg to:", log.Fields{"deviceId": oo.deviceID})
+	request := &omci.RebootRequest{
+		MeBasePacket: omci.MeBasePacket{
+			EntityClass: me.OnuGClassID,
+		},
+	}
+	tid := oo.GetNextTid(highPrio)
+	pkt, err := serialize(omci.RebootRequestType, request, tid)
+	if err != nil {
+		logger.Errorw("Cannot serialize RebootRequest", log.Fields{
+			"Err": err, "deviceId": oo.deviceID})
+		return err
+	}
+	omciRxCallbackPair := CallbackPair{
+		cbKey:   tid,
+		cbEntry: CallbackPairEntry{oo.pOnuDeviceEntry.omciRebootMessageReceivedChannel, oo.receiveOmciResponse},
+	}
+
+	err = oo.Send(ctx, pkt, timeout, 0, highPrio, omciRxCallbackPair)
+	if err != nil {
+		logger.Errorw("Cannot send RebootRequest", log.Fields{
+			"Err": err, "deviceId": oo.deviceID})
+		return err
+	}
+	err = oo.pOnuDeviceEntry.waitForRebootResponse(responseChannel)
+	if err != nil {
+		logger.Error("aborting ONU Reboot!")
+		oo.pOnuDeviceEntry.pMibDownloadFsm.pFsm.Event("reset")
+		return err
+	}
+	return nil
+}
+
 func (oo *OmciCC) sendMibUpload(ctx context.Context, timeout int, highPrio bool) error {
 	logger.Debugw("send MibUpload-msg to:", log.Fields{"deviceId": oo.deviceID})
 	request := &omci.MibUploadRequest{
diff --git a/internal/pkg/onuadaptercore/onu_device_entry.go b/internal/pkg/onuadaptercore/onu_device_entry.go
index b623bf9..53101be 100644
--- a/internal/pkg/onuadaptercore/onu_device_entry.go
+++ b/internal/pkg/onuadaptercore/onu_device_entry.go
@@ -20,6 +20,9 @@
 import (
 	"context"
 	"errors"
+	"github.com/opencord/omci-lib-go"
+	me "github.com/opencord/omci-lib-go/generated"
+	"time"
 
 	//"sync"
 	//"time"
@@ -195,7 +198,8 @@
 	pMibDownloadFsm *AdapterFsm //could be handled dynamically and more general as pAdapterFsm - perhaps later
 	//remark: general usage of pAdapterFsm would require generalization of commChan  usage and internal event setting
 	//  within the FSM event procedures
-	omciMessageReceived chan bool //seperate channel needed by DownloadFsm
+	omciMessageReceived              chan bool    //seperate channel needed by DownloadFsm
+	omciRebootMessageReceivedChannel chan Message // channel needed by Reboot request
 }
 
 //OnuDeviceEntry returns a new instance of a OnuDeviceEntry
@@ -211,6 +215,7 @@
 	onuDeviceEntry.coreProxy = core_proxy
 	onuDeviceEntry.adapterProxy = adapter_proxy
 	onuDeviceEntry.devState = DeviceStatusInit
+	onuDeviceEntry.omciRebootMessageReceivedChannel = make(chan Message, 2048)
 	//openomciagent.lockDeviceHandlersMap = sync.RWMutex{}
 	//OMCI related databases are on a per-agent basis. State machines and tasks
 	//are per ONU Vendor
@@ -389,6 +394,47 @@
 	return nil
 }
 
+func (oo *OnuDeviceEntry) Reboot(ctx context.Context) error {
+	logger.Info("reboot-OnuDeviceEntry")
+	if err := oo.PDevOmciCC.sendReboot(context.TODO(), ConstDefaultOmciTimeout, true, oo.omciRebootMessageReceivedChannel); err != nil {
+		logger.Errorw("onu didn't reboot", log.Fields{"for device": oo.deviceID})
+		return err
+	}
+	logger.Info("OnuDeviceEntry-reboot")
+	return nil
+}
+
+func (oo *OnuDeviceEntry) waitForRebootResponse(responseChannel chan Message) error {
+	select {
+	case <-time.After(3 * time.Second): //3s was detected to be to less in 8*8 bbsim test with debug Info/Debug
+		logger.Warnw("Reboot timeout", log.Fields{"for device-id": oo.deviceID})
+		return errors.New("RebootTimeout")
+	case data := <-responseChannel:
+		switch data.Data.(OmciMessage).OmciMsg.MessageType {
+		case omci.RebootResponseType:
+			{
+				msgLayer := (*data.Data.(OmciMessage).OmciPacket).Layer(omci.LayerTypeRebootResponse)
+				if msgLayer == nil {
+					return errors.New("Omci Msg layer could not be detected for RebootResponseType")
+				}
+				msgObj, msgOk := msgLayer.(*omci.GetResponse)
+				if !msgOk {
+					return errors.New("Omci Msg layer could not be assigned for RebootResponseType")
+				}
+				logger.Debugw("CreateResponse Data", log.Fields{"deviceId": oo.deviceID, "data-fields": msgObj})
+				if msgObj.Result != me.Success {
+					logger.Errorw("Omci RebootResponseType Error ", log.Fields{"Error": msgObj.Result})
+					// possibly force FSM into abort or ignore some errors for some messages? store error for mgmt display?
+					return errors.New("Omci RebootResponse Result Error indication")
+				}
+				return nil
+			}
+		}
+		logger.Warnw("Reboot response error", log.Fields{"for device-id": oo.deviceID})
+		return errors.New("Unexpected OmciResponse type received")
+	}
+}
+
 //Relay the InSync message via Handler to Rw core - Status update
 func (oo *OnuDeviceEntry) transferSystemEvent(dev_Event OnuDeviceEvent) error {
 	logger.Debugw("relaying system-event", log.Fields{"Event": dev_Event})
diff --git a/internal/pkg/onuadaptercore/openonu.go b/internal/pkg/onuadaptercore/openonu.go
index 06790bc..7250e18 100644
--- a/internal/pkg/onuadaptercore/openonu.go
+++ b/internal/pkg/onuadaptercore/openonu.go
@@ -258,7 +258,13 @@
 
 //Reboot_device reboots the given device
 func (oo *OpenONUAC) Reboot_device(device *voltha.Device) error {
-	return errors.New("unImplemented")
+	logger.Debugw("Reboot-device", log.Fields{"deviceId": device.Id})
+	if handler := oo.getDeviceHandler(device.Id); handler != nil {
+		go handler.RebootDevice(device)
+		return nil
+	}
+	logger.Warnw("no handler found for device-reboot", log.Fields{"deviceId": device.Id})
+	return fmt.Errorf(fmt.Sprintf("handler-not-found-#{device.Id}"))
 }
 
 //Self_test_device unimplemented