VOL-1556 Add alarm simulation to voltha-go core

Change-Id: I23dcd720909a3e23cb203fd1ae32eada5fc4e34e
diff --git a/python/cli/device.py b/python/cli/device.py
old mode 100644
new mode 100755
index 484185f..25ad72b
--- a/python/cli/device.py
+++ b/python/cli/device.py
@@ -508,6 +508,116 @@
         self.poutput('response: {}'.format(name))
         self.poutput('{}'.format(response))
 
+    def help_simulate_alarm(self):
+        self.poutput(
+'''
+simulate_alarm <alarm_name> [-b <bit rate>] [-c] [-d <drift>] [-e <eqd>]
+            [-i <interface id>] [-o <onu device id>] [-p <port type name>]
+
+<name> is the name of the alarm to raise. Other rguments are alarm specific
+and only have meaning in the context of a particular alarm. Below is a list
+of the alarms that may be raised:
+
+simulate_alarm los -i <interface_id> -p <port_type_name>
+simulate_alarm dying_gasp -i <interface_id> -o <onu_device_id>
+simulate_alarm onu_los -i <interface_id> -o <onu_device_id>
+simulate_alarm onu_lopc_miss -i <interface_id> -o <onu_device_id>
+simulate_alarm onu_lopc_mic -i <interface_id> -o <onu_device_id>
+simulate_alarm onu_lob -i <interface_id> -o <onu_device_id>
+simulate_alarm onu_signal_degrade -i <interface_id> -o <onu_device_id>
+               -b <bit_rate>
+simulate_alarm onu_drift_of_window -i <interface_id>
+               -o <onu_device_id> -d <drift> -e <eqd>
+simulate_alarm onu_signal_fail -i <interface_id> -o <onu_device_id>
+               -b <bit_rate>
+simulate_alarm onu_activation -i <interface_id> -o <onu_device_id>
+simulate_alarm onu_startup -i <interface_id> -o <onu_device_id>
+simulate_alarm onu_discovery -i <interface_id> -s <onu_serial_number>
+
+If the -c option is specified then the alarm will be cleared. By default,
+it will be raised. Note that only some alarms can be cleared.
+'''
+        )
+
+    @options([
+        make_option('-c', '--clear', action='store_true', default=False,
+                    help="Clear alarm instead of raising"),
+        make_option('-b', '--inverse_bit_error_rate', action='store', dest='inverse_bit_error_rate',
+                    help="Inverse bit error rate", default=0, type="int"),
+        make_option('-d', '--drift', action='store', dest='drift',
+                    help="Drift", default=0, type="int"),
+        make_option('-e', '--new_eqd', action='store', dest='new_eqd',
+                    help="New EQD", default=0, type="int"),
+        make_option('-i', '--intf_id', action='store', dest='intf_id',
+                    help="Interface ID", default=""),
+        make_option('-o', '--onu_device_id', action='store', dest='onu_device_id',
+                    help="ONU device ID", default=""),
+        make_option('-p', '--port_type_name', action='store', dest='port_type_name',
+                    help="Port type name", default=""),
+        make_option('-s', '--onu_serial_number', action='store', dest='onu_serial_number',
+                    help="ONU Serial Number", default=""),
+    ])
+    def do_simulate_alarm(self, line, opts):
+        indicator = line
+        device = self.get_device(depth=-1)
+        device_id = device.id
+
+        alarm_args = {"los": ["intf_id", "port_type_name"],
+                      "dying_gasp": ["intf_id", "onu_device_id"],
+                      "onu_los": ["intf_id", "onu_device_id"],
+                      "onu_lopc_miss": ["intf_id", "onu_device_id"],
+                      "onu_lopc_mic": ["intf_id", "onu_device_id"],
+                      "onu_lob": ["intf_id", "onu_device_id"],
+                      "onu_signal_degrade": ["intf_id", "onu_device_id", "inverse_bit_error_rate"],
+                      "onu_drift_of_window": ["intf_id", "onu_device_id", "drift", "new_eqd"],
+                      "onu_signal_fail": ["intf_id", "onu_device_id", "inverse_bit_error_rate"],
+                      "onu_activation": ["intf_id", "onu_device_id"],
+                      "onu_startup": ["intf_id", "onu_device_id"],
+                      "onu_discovery": ["intf_id", "onu_serial_number"]
+                      }
+        try:
+            if indicator not in alarm_args:
+                self.poutput("Unknown alarm indicator %s. Valid choices are %s." % (indicator,
+                                                                                    ", ".join(alarm_args.keys())))
+                raise Exception("Unknown alarm indicator %s" % indicator)
+
+            for arg_name in alarm_args[indicator]:
+                if not getattr(opts, arg_name):
+                    self.poutput("Option %s is required for alarm %s. See help." % (arg_name, indicator))
+                    raise Exception("Option %s is required for alarm %s" % (arg_name, indicator))
+
+            # TODO: check for required arguments
+            kw = dict(id=device_id)
+
+            kw["indicator"] = indicator
+            kw["intf_id"] = opts.intf_id
+            kw["onu_device_id"] = opts.onu_device_id
+            kw["port_type_name"] = opts.port_type_name
+            kw["inverse_bit_error_rate"] = opts.inverse_bit_error_rate
+            kw["drift"] = opts.drift
+            kw["new_eqd"] = opts.new_eqd
+            kw["onu_serial_number"] = opts.onu_serial_number
+
+            if opts.clear:
+                kw["operation"] = voltha_pb2.SimulateAlarmRequest.CLEAR
+            else:
+                kw["operation"] = voltha_pb2.SimulateAlarmRequest.RAISE
+        except Exception as e:
+            self.poutput('Error simulate alarm {}. Error:{}'.format(device_id, e))
+            return
+        response = None
+        try:
+            simulate_alarm = voltha_pb2.SimulateAlarmRequest(**kw)
+            stub = self.get_stub()
+            response = stub.SimulateAlarm(simulate_alarm)
+        except Exception as e:
+            self.poutput('Error simulate alarm {}. Error:{}'.format(kw['id'], e))
+            return
+        name = enum2name(common_pb2.OperationResp,
+                        'OperationReturnCode', response.code)
+        self.poutput('response: {}'.format(name))
+        self.poutput('{}'.format(response))
+
     @options([
         make_option('-n', '--name', action='store', dest='name',
                     help="Image name"),
diff --git a/python/cli/omci.py b/python/cli/omci.py
old mode 100644
new mode 100755
index 95768c8..e1ed4b1
--- a/python/cli/omci.py
+++ b/python/cli/omci.py
@@ -26,6 +26,8 @@
 from voltha_protos import voltha_pb2
 from voltha_protos.omci_mib_db_pb2 import MibDeviceData, MibClassData, \
     MibInstanceData
+from voltha_protos.omci_alarm_db_pb2 import AlarmDeviceData, AlarmClassData, \
+    AlarmInstanceData
 from os import linesep
 
 
@@ -209,8 +211,8 @@
         return ' '.join([v[0].upper() + v[1:] for v in attr.split('_')])
 
     def _instance_to_dict(self, instance):
-        if not isinstance(instance, MibInstanceData):
-            raise TypeError('{} is not of type MibInstanceData'.format(type(instance)))
+        if not isinstance(instance, (MibInstanceData, AlarmInstanceData)):
+            raise TypeError('{} is not of type MIB/Alarm Instance Data'.format(type(instance)))
 
         data = {
             OmciCli.INSTANCE_ID_KEY: instance.instance_id,
@@ -224,8 +226,8 @@
         return data
 
     def _class_to_dict(self, val):
-        if not isinstance(val, MibClassData):
-            raise TypeError('{} is not of type MibClassData'.format(type(val)))
+        if not isinstance(val, (MibClassData, AlarmClassData)):
+            raise TypeError('{} is not of type MIB/Alarm Class Data'.format(type(val)))
 
         data = {
             OmciCli.CLASS_ID_KEY: val.class_id,
diff --git a/rw_core/core/adapter_proxy.go b/rw_core/core/adapter_proxy.go
old mode 100644
new mode 100755
index 4ed1a71..541b502
--- a/rw_core/core/adapter_proxy.go
+++ b/rw_core/core/adapter_proxy.go
@@ -29,10 +29,10 @@
 )
 
 type AdapterProxy struct {
-	TestMode     bool
+	TestMode              bool
 	deviceTopicRegistered bool
-	coreTopic *kafka.Topic
-	kafkaICProxy *kafka.InterContainerProxy
+	coreTopic             *kafka.Topic
+	kafkaICProxy          *kafka.InterContainerProxy
 }
 
 func NewAdapterProxy(kafkaProxy *kafka.InterContainerProxy) *AdapterProxy {
@@ -62,18 +62,17 @@
 	ap.coreTopic = coreTopic
 }
 
-func (ap *AdapterProxy) getCoreTopic() kafka.Topic{
+func (ap *AdapterProxy) getCoreTopic() kafka.Topic {
 	if ap.coreTopic != nil {
 		return *ap.coreTopic
 	}
-	return kafka.Topic{Name:ap.kafkaICProxy.DefaultTopic.Name}
+	return kafka.Topic{Name: ap.kafkaICProxy.DefaultTopic.Name}
 }
 
-func (ap *AdapterProxy) getAdapterTopic(adapterName string) kafka.Topic{
+func (ap *AdapterProxy) getAdapterTopic(adapterName string) kafka.Topic {
 	return kafka.Topic{Name: adapterName}
 }
 
-
 func (ap *AdapterProxy) AdoptDevice(ctx context.Context, device *voltha.Device) error {
 	log.Debugw("AdoptDevice", log.Fields{"device": device})
 	rpc := "adopt_device"
@@ -484,3 +483,25 @@
 	log.Debug("UnSuppressAlarm")
 	return nil
 }
+
+func (ap *AdapterProxy) SimulateAlarm(ctx context.Context, device *voltha.Device, simulatereq *voltha.SimulateAlarmRequest) error {
+	log.Debugw("SimulateAlarm", log.Fields{"id": simulatereq.Id})
+	rpc := "simulate_alarm"
+	toTopic := ap.getAdapterTopic(device.Adapter)
+	args := make([]*kafka.KVArg, 2)
+	args[0] = &kafka.KVArg{
+		Key:   "device",
+		Value: device,
+	}
+	args[1] = &kafka.KVArg{
+		Key:   "request",
+		Value: simulatereq,
+	}
+
+	// Use a device topic for the response as we are the only core handling requests for this device
+	replyToTopic := ap.getCoreTopic()
+	ap.deviceTopicRegistered = true
+	success, result := ap.kafkaICProxy.InvokeRPC(ctx, rpc, &toTopic, &replyToTopic, true, device.Id, args...)
+	log.Debugw("SimulateAlarm-response", log.Fields{"replyTopic": replyToTopic, "deviceid": device.Id, "success": success})
+	return unPackResponse(rpc, device.Id, success, result)
+}
diff --git a/rw_core/core/device_agent.go b/rw_core/core/device_agent.go
old mode 100644
new mode 100755
index d00375f..9704fff
--- a/rw_core/core/device_agent.go
+++ b/rw_core/core/device_agent.go
@@ -21,10 +21,10 @@
 	"github.com/gogo/protobuf/proto"
 	"github.com/opencord/voltha-go/common/log"
 	"github.com/opencord/voltha-go/db/model"
+	fu "github.com/opencord/voltha-go/rw_core/utils"
 	ic "github.com/opencord/voltha-protos/go/inter_container"
 	ofp "github.com/opencord/voltha-protos/go/openflow_13"
 	"github.com/opencord/voltha-protos/go/voltha"
-	fu "github.com/opencord/voltha-go/rw_core/utils"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 	"reflect"
@@ -36,7 +36,7 @@
 	deviceType       string
 	lastData         *voltha.Device
 	adapterProxy     *AdapterProxy
-	adapterMgr *AdapterManager
+	adapterMgr       *AdapterManager
 	deviceMgr        *DeviceManager
 	clusterDataProxy *model.Proxy
 	deviceProxy      *model.Proxy
@@ -410,7 +410,7 @@
 		}
 	}
 	return &voltha.OperationResp{Code: voltha.OperationResp_OPERATION_SUCCESS}, nil
-	}
+}
 
 func (agent *DeviceAgent) activateImage(ctx context.Context, img *voltha.ImageDownload) (*voltha.OperationResp, error) {
 	agent.lockDevice.Lock()
@@ -448,8 +448,8 @@
 		// The status of the AdminState will be changed following the update_download_status response from the adapter
 		// The image name will also be removed from the device list
 	}
-	return &voltha.OperationResp{Code: voltha.OperationResp_OPERATION_SUCCESS}, nil}
-
+	return &voltha.OperationResp{Code: voltha.OperationResp_OPERATION_SUCCESS}, nil
+}
 
 func (agent *DeviceAgent) revertImage(ctx context.Context, img *voltha.ImageDownload) (*voltha.OperationResp, error) {
 	agent.lockDevice.Lock()
@@ -484,8 +484,7 @@
 		}
 	}
 	return &voltha.OperationResp{Code: voltha.OperationResp_OPERATION_SUCCESS}, nil
-	}
-
+}
 
 func (agent *DeviceAgent) getImageDownloadStatus(ctx context.Context, img *voltha.ImageDownload) (*voltha.ImageDownload, error) {
 	agent.lockDevice.Lock()
@@ -504,7 +503,7 @@
 	}
 }
 
-func (agent *DeviceAgent) updateImageDownload(img *voltha.ImageDownload) error{
+func (agent *DeviceAgent) updateImageDownload(img *voltha.ImageDownload) error {
 	agent.lockDevice.Lock()
 	defer agent.lockDevice.Unlock()
 	log.Debugw("updateImageDownload", log.Fields{"id": agent.deviceId})
@@ -526,7 +525,7 @@
 		// Set the Admin state to enabled if required
 		if (img.DownloadState != voltha.ImageDownload_DOWNLOAD_REQUESTED &&
 			img.DownloadState != voltha.ImageDownload_DOWNLOAD_STARTED) ||
-			(img.ImageState != voltha.ImageDownload_IMAGE_ACTIVATING){
+			(img.ImageState != voltha.ImageDownload_IMAGE_ACTIVATING) {
 			cloned.AdminState = voltha.AdminState_ENABLED
 		}
 
@@ -562,7 +561,7 @@
 	if device, err := agent.getDeviceWithoutLock(); err != nil {
 		return nil, status.Errorf(codes.NotFound, "%s", agent.deviceId)
 	} else {
-		return &voltha.ImageDownloads{Items:device.ImageDownloads}, nil
+		return &voltha.ImageDownloads{Items: device.ImageDownloads}, nil
 	}
 }
 
@@ -1016,3 +1015,20 @@
 	}
 	return
 }
+
+func (agent *DeviceAgent) simulateAlarm(ctx context.Context, simulatereq *voltha.SimulateAlarmRequest) error {
+	agent.lockDevice.Lock()
+	defer agent.lockDevice.Unlock()
+	log.Debugw("simulateAlarm", log.Fields{"id": agent.deviceId})
+	// Get the most up to date the device info
+	if device, err := agent.getDeviceWithoutLock(); err != nil {
+		return status.Errorf(codes.NotFound, "%s", agent.deviceId)
+	} else {
+		// First send the request to an Adapter and wait for a response
+		if err := agent.adapterProxy.SimulateAlarm(ctx, device, simulatereq); err != nil {
+			log.Debugw("simulateAlarm-error", log.Fields{"id": agent.lastData.Id, "error": err})
+			return err
+		}
+	}
+	return nil
+}
diff --git a/rw_core/core/device_manager.go b/rw_core/core/device_manager.go
old mode 100644
new mode 100755
index f37244f..f9da623
--- a/rw_core/core/device_manager.go
+++ b/rw_core/core/device_manager.go
@@ -496,7 +496,7 @@
 		}
 		// Notify the logical device manager to setup a logical port if needed
 		if port.Type == voltha.Port_ETHERNET_NNI || port.Type == voltha.Port_ETHERNET_UNI {
-			if device , err := dMgr.GetDevice(deviceId); err == nil {
+			if device, err := dMgr.GetDevice(deviceId); err == nil {
 				go dMgr.logicalDeviceMgr.addLogicalPort(device, port)
 			} else {
 				log.Errorw("failed-to-retrieve-device", log.Fields{"deviceId": deviceId})
@@ -992,3 +992,16 @@
 	}
 	return nil
 }
+
+func (dMgr *DeviceManager) simulateAlarm(ctx context.Context, simulatereq *voltha.SimulateAlarmRequest, ch chan interface{}) {
+	log.Debugw("simulateAlarm", log.Fields{"id": simulatereq.Id, "Indicator": simulatereq.Indicator, "IntfId": simulatereq.IntfId,
+		"PortTypeName": simulatereq.PortTypeName, "OnuDeviceId": simulatereq.OnuDeviceId, "InverseBitErrorRate": simulatereq.InverseBitErrorRate,
+		"Drift": simulatereq.Drift, "NewEqd": simulatereq.NewEqd, "OnuSerialNumber": simulatereq.OnuSerialNumber, "Operation": simulatereq.Operation})
+	var res interface{}
+	if agent := dMgr.getDeviceAgent(simulatereq.Id); agent != nil {
+		res = agent.simulateAlarm(ctx, simulatereq)
+		log.Debugw("SimulateAlarm-result", log.Fields{"result": res})
+	}
+	//TODO CLI always get successful response
+	sendResponse(ctx, ch, res)
+}
diff --git a/rw_core/core/grpc_nbi_api_handler.go b/rw_core/core/grpc_nbi_api_handler.go
old mode 100644
new mode 100755
index 7ed756c..6532b6e
--- a/rw_core/core/grpc_nbi_api_handler.go
+++ b/rw_core/core/grpc_nbi_api_handler.go
@@ -835,13 +835,29 @@
 	return nil, nil
 }
 
-//@TODO useless stub, what should this actually do?
 func (handler *APIHandler) SimulateAlarm(
 	ctx context.Context,
 	in *voltha.SimulateAlarmRequest,
 ) (*common.OperationResp, error) {
-	log.Debug("SimulateAlarm-stub")
-	return nil, nil
+	log.Debugw("SimulateAlarm-request", log.Fields{"id": in.Id})
+	successResp := &common.OperationResp{Code: common.OperationResp_OPERATION_SUCCESS}
+	if isTestMode(ctx) {
+		return successResp, nil
+	}
+
+	if handler.competeForTransaction() {
+		if txn, err := handler.takeRequestOwnership(ctx, &utils.DeviceID{Id:in.Id}, handler.longRunningRequestTimeout); err != nil {
+			failedresponse := &common.OperationResp{Code:voltha.OperationResp_OPERATION_FAILURE}
+			return failedresponse, err
+		} else {
+			defer txn.Close()
+		}
+	}
+
+	ch := make(chan interface{})
+	defer close(ch)
+	go handler.deviceMgr.simulateAlarm(ctx, in, ch)
+	return successResp, nil
 }
 
 //@TODO useless stub, what should this actually do?