SEBA-237 Add simulate_alarm CLI command and plumb through

Change-Id: I3e146bf2d2d4a4b2ee7e491fb62cb762e884e233
diff --git a/cli/device.py b/cli/device.py
index 3a6345c..3f02dcd 100644
--- a/cli/device.py
+++ b/cli/device.py
@@ -510,6 +510,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/voltha/adapters/openolt/openolt.py b/voltha/adapters/openolt/openolt.py
index 6dde34a..1179dd5 100644
--- a/voltha/adapters/openolt/openolt.py
+++ b/voltha/adapters/openolt/openolt.py
@@ -26,6 +26,7 @@
 from voltha.protos.adapter_pb2 import Adapter
 from voltha.protos.adapter_pb2 import AdapterConfig
 from voltha.protos.common_pb2 import LogLevel
+from voltha.protos.common_pb2 import OperationResp
 from voltha.protos.device_pb2 import DeviceType, DeviceTypes
 from voltha.registry import registry
 
@@ -363,3 +364,17 @@
             log.error('Could not find matching handler',
                       looking_for_device_id=device_id,
                       available_handlers=self.devices.keys())
+
+    def simulate_alarm(self, device, request):
+        log.info('simulate_alarm', device=device, request=request)
+
+        if device.id not in self.devices:
+            log.error("Device does not exist", device_id=device.id)
+            return OperationResp(code=OperationResp.OPERATION_FAILURE,
+                                      additional_info="Device %s does not exist" % device.id)
+
+        handler = self.devices[device.id]
+
+        handler.simulate_alarm(request)
+
+        return OperationResp(code=OperationResp.OPERATION_SUCCESS)
diff --git a/voltha/adapters/openolt/openolt_alarms.py b/voltha/adapters/openolt/openolt_alarms.py
index 5ebf20b..6eb0702 100644
--- a/voltha/adapters/openolt/openolt_alarms.py
+++ b/voltha/adapters/openolt/openolt_alarms.py
@@ -31,6 +31,8 @@
 from voltha.extensions.alarms.onu.onu_window_drift_alarm import OnuWindowDriftAlarm
 from voltha.extensions.alarms.onu.onu_activation_fail_alarm import OnuActivationFailAlarm
 
+from voltha.extensions.alarms.onu.onu_discovery_alarm import OnuDiscoveryAlarm
+
 import protos.openolt_pb2 as openolt_pb2
 import voltha.protos.device_pb2 as device_pb2
 
@@ -101,6 +103,50 @@
             self.log.error('sorting of alarm went wrong', error=e,
                            alarm=alarm_ind)
 
+    def simulate_alarm(self, alarm):
+        if alarm.indicator == "los":
+            alarm_obj = OltLosAlarm(self.alarms, intf_id=alarm.intf_id, port_type_name=alarm.port_type_name)
+        elif alarm.indicator == "dying_gasp":
+            alarm_obj = OnuDyingGaspAlarm(self.alarms, alarm.intf_id, alarm.onu_device_id)
+        elif alarm.indicator == "onu_los":
+            alarm_obj = OnuLosAlarm(self.alarms, onu_id=alarm.onu_device_id, intf_id=alarm.intf_id)
+        elif alarm.indicator == "onu_lopc_miss":
+            alarm_obj = OnuLopcMissAlarm(self.alarms, onu_id=alarm.onu_device_id, intf_id=alarm.intf_id)
+        elif alarm.indicator == "onu_lopc_mic":
+            alarm_obj = OnuLopcMicErrorAlarm(self.alarms, onu_id=alarm.onu_device_id, intf_id=alarm.intf_id)
+        elif alarm.indicator == "onu_lob":
+            alarm_obj = OnuLobAlarm(self.alarms, onu_id=alarm.onu_device_id, intf_id=alarm.intf_id)
+        elif alarm.indicator == "onu_startup":
+            alarm_obj = OnuStartupAlarm(self.alarms, intf_id=alarm.intf_id, onu_id=alarm.onu_device_id)
+        elif alarm.indicator == "onu_signal_degrade":
+            alarm_obj = OnuSignalDegradeAlarm(self.alarms, intf_id=alarm.intf_id, onu_id=alarm.onu_device_id,
+                                  inverse_bit_error_rate=alarm.inverse_bit_error_rate)
+        elif alarm.indicator == "onu_drift_of_window":
+            alarm_obj = OnuWindowDriftAlarm(self.alarms, intf_id=alarm.intf_id,
+                                onu_id=alarm.onu_device_id,
+                                drift=alarm.drift,
+                                new_eqd=alarm.new_eqd)
+        elif alarm.indicator == "onu_signal_fail":
+            alarm_obj = OnuSignalFailAlarm(self.alarms, intf_id=alarm.intf_id,
+                               onu_id=alarm.onu_device_id,
+                               inverse_bit_error_rate=alarm.inverse_bit_error_rate)
+        elif alarm.indicator == "onu_activation":
+            alarm_obj = OnuActivationFailAlarm(self.alarms, intf_id=alarm.intf_id,
+                                   onu_id=alarm.onu_device_id)
+        elif alarm.indicator == "onu_discovery":
+            alarm_obj = OnuDiscoveryAlarm(self.alarms, pon_id=alarm.intf_id,
+                                   serial_number=alarm.onu_serial_number)
+        else:
+            raise Exception("Unknown alarm indicator %s" % alarm.indicator)
+
+        if alarm.operation == alarm.RAISE:
+            alarm_obj.raise_alarm()
+        elif alarm.operation == alarm.CLEAR:
+            alarm_obj.clear_alarm()
+        else:
+            # This shouldn't happen
+            raise Exception("Unknown alarm operation")
+
     def los_indication(self, los_ind):
 
         try:
diff --git a/voltha/adapters/openolt/openolt_device.py b/voltha/adapters/openolt/openolt_device.py
index db68bca..087179c 100644
--- a/voltha/adapters/openolt/openolt_device.py
+++ b/voltha/adapters/openolt/openolt_device.py
@@ -1078,3 +1078,6 @@
                            error=e)
         else:
             self.log.info('statistics requested')
+
+    def simulate_alarm(self, alarm):
+        self.alarm_mgr.simulate_alarm(alarm)
diff --git a/voltha/core/adapter_agent.py b/voltha/core/adapter_agent.py
index a063533..bf048d0 100644
--- a/voltha/core/adapter_agent.py
+++ b/voltha/core/adapter_agent.py
@@ -271,6 +271,9 @@
     def remove_multicast_distribution_set(self, device, data):
         return self.adapter.remove_multicast_distribution_set(device, data)
 
+    def simulate_alarm(self, device, request):
+        return self.adapter.simulate_alarm(device, request)
+
     # ~~~~~~~~~~~~~~~~~~~ Adapter-Facing Service ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
     def get_device(self, device_id):
diff --git a/voltha/core/device_agent.py b/voltha/core/device_agent.py
index 5d7a8a2..33f7008 100644
--- a/voltha/core/device_agent.py
+++ b/voltha/core/device_agent.py
@@ -379,6 +379,14 @@
         if not dry_run:
             yield self.adapter_agent.delete_device(device)
 
+    def simulate_alarm(self, device, alarm):
+        try:
+            self.log.debug('simulate_alarm', alarm=alarm)
+            return self.adapter_agent.simulate_alarm(device, alarm)
+        except Exception as e:
+            self.log.exception(e.message)
+            return OperationResp(code=OperationResp.OPERATION_FAILURE)
+
     admin_state_fsm = {
 
         # Missing entries yield no-op
diff --git a/voltha/core/global_handler.py b/voltha/core/global_handler.py
index 54c2d28..098e145 100644
--- a/voltha/core/global_handler.py
+++ b/voltha/core/global_handler.py
@@ -1778,4 +1778,26 @@
             returnValue(AlarmDeviceData())
         else:
             log.debug('grpc-success-response', response=response)
+            returnValue(response)
+
+    @twisted_async
+    @inlineCallbacks
+    def SimulateAlarm(self, request, context):
+        try:
+            log.debug('grpc-request', request=request)
+            response = yield self.dispatcher.dispatch('SimulateAlarm',
+                                                      request,
+                                                      context,
+                                                      id=request.id)
+            log.debug('grpc-response', response=response)
+        except Exception as e:
+            log.exception('grpc-exception', e=e)
+
+        if isinstance(response, DispatchError):
+            log.warn('grpc-error-response', error=response.error_code)
+            context.set_details('Device \'{}\' error'.format(request.id))
+            context.set_code(response.error_code)
+            returnValue(OperationResp(code=OperationResp.OPERATION_FAILURE))
+        else:
+            log.debug('grpc-success-response', response=response)
             returnValue(response)
\ No newline at end of file
diff --git a/voltha/core/local_handler.py b/voltha/core/local_handler.py
index 8520a42..2a2df05 100644
--- a/voltha/core/local_handler.py
+++ b/voltha/core/local_handler.py
@@ -1372,4 +1372,33 @@
             context.set_details(
                 'OMCI ALARM for Device \'{}\' not found'.format(request.id))
             context.set_code(StatusCode.NOT_FOUND)
-            return AlarmDeviceData()
\ No newline at end of file
+            return AlarmDeviceData()
+
+    @twisted_async
+    def SimulateAlarm(self, request, context):
+        log.debug('grpc-request', request=request)
+
+        if '/' in request.id:
+            context.set_details(
+                'Malformed device id \'{}\''.format(request.id))
+            context.set_code(StatusCode.INVALID_ARGUMENT)
+            response = OperationResp(code=OperationResp.OPERATION_FAILURE)
+            return response
+
+        try:
+            path = '/devices/{}'.format(request.id)
+            device = self.root.get(path)
+            agent = self.core.get_device_agent(device.id)
+            response = agent.simulate_alarm(device, request)
+            return response
+
+        except KeyError:
+            context.set_details(
+                'Device \'{}\' not found'.format(request.id))
+            context.set_code(StatusCode.NOT_FOUND)
+            response = OperationResp(code=OperationResp.OPERATION_FAILURE)
+            return response
+        except Exception as e:
+            log.exception(e.message)
+            response = OperationResp(code=OperationResp.OPERATION_FAILURE)
+            return response
\ No newline at end of file
diff --git a/voltha/protos/device.proto b/voltha/protos/device.proto
index 58594ed..46ed270 100644
--- a/voltha/protos/device.proto
+++ b/voltha/protos/device.proto
@@ -306,3 +306,21 @@
 message Devices {
     repeated Device items = 1;
 }
+
+message SimulateAlarmRequest {
+    enum OperationType {
+        RAISE = 0;
+        CLEAR = 1;
+    }
+    // Device Identifier
+    string id = 1;
+    string indicator = 2;
+    string intf_id = 3;
+    string port_type_name = 4;
+    string onu_device_id = 5;
+    int32 inverse_bit_error_rate = 6;
+    int32 drift = 7;
+    int32 new_eqd = 8;
+    string onu_serial_number = 9;
+    OperationType operation = 10;
+}
\ No newline at end of file
diff --git a/voltha/protos/voltha.proto b/voltha/protos/voltha.proto
index 3d62878..4736973 100644
--- a/voltha/protos/voltha.proto
+++ b/voltha/protos/voltha.proto
@@ -972,6 +972,14 @@
             get: "/api/v1/openomci/{id}/alarm"
         };
     }
+
+    // Simulate an Alarm
+    rpc SimulateAlarm(SimulateAlarmRequest) returns(OperationResp) {
+        option (google.api.http) = {
+            post: "/api/v1/devices/{id}/simulate_larm"
+            body: "*"
+        };
+    }
 }
 
 /*
@@ -1714,4 +1722,12 @@
             get: "/api/v1/openomci/{id}/alarm"
         };
     }
+
+    // Simulate an Alarm
+    rpc SimulateAlarm(SimulateAlarmRequest) returns(OperationResp) {
+        option (google.api.http) = {
+            post: "/api/v1/devices/{id}/simulate_alarm"
+            body: "*"
+        };
+    }
 }