From 1e33aaa2d30526f2889539aec3e557ac0c5890d5 Mon Sep 17 00:00:00 2001
From: mfenjiro <mohamed.fenjiro@savoirfairelinux.com>
Date: Thu, 11 Apr 2019 17:05:30 -0400
Subject: [PATCH] Monitor Jami Reliability

Change-Id: I719a83badfb811ab9bb11dc01d29ce9363829573
---
 tools/dringctrl/controller.py |  68 ++++++++++++-------
 tools/dringctrl/dringctrl.py  |   2 +-
 tools/dringctrl/jami_test.py  | 119 ++++++++++++++++++++++++++++++++++
 3 files changed, 166 insertions(+), 23 deletions(-)
 create mode 100755 tools/dringctrl/jami_test.py

diff --git a/tools/dringctrl/controller.py b/tools/dringctrl/controller.py
index baaa6cb824..d00b3f7d47 100644
--- a/tools/dringctrl/controller.py
+++ b/tools/dringctrl/controller.py
@@ -1,3 +1,4 @@
+#! /usr/bin/env python3
 #
 #  Copyright (C) 2015-2019 Savoir-faire Linux Inc. Inc
 #
@@ -28,22 +29,14 @@ import hashlib
 
 from threading import Thread
 from functools import partial
-
-try:
-    from gi.repository import GObject
-except ImportError as e:
-    import gobject as GObject
-except Exception as e:
-    print(str(e))
-    exit(1)
-
-from errors import *
+from errorsDring import DRingCtrlAccountError, DRingCtrlError, DRingCtrlDBusError, DRingCtrlDeamonError
+from gi.repository import GLib
 
 try:
     import dbus
     from dbus.mainloop.glib import DBusGMainLoop
 except ImportError as e:
-    raise DRingCtrlError("No python-dbus module found")
+    raise DRingCtrlError(str(e))
 
 
 DBUS_DEAMON_OBJECT = 'cx.ring.Ring'
@@ -69,9 +62,7 @@ class DRingCtrl(Thread):
         self.isStop = False
 
         # Glib MainLoop for processing callbacks
-        self.loop = GObject.MainLoop()
-
-        GObject.threads_init()
+        self.loop = GLib.MainLoop()
 
         # client registered to sflphoned ?
         self.registered = False
@@ -94,7 +85,7 @@ class DRingCtrl(Thread):
             bus = dbus.SessionBus()
 
         except dbus.DBusException as e:
-            raise DRingCtrlDBusError("Unable to connect DBUS session bus")
+            raise DRingCtrlDBusError(str(e))
 
         if not bus.name_has_owner(DBUS_DEAMON_OBJECT) :
             raise DRingCtrlDBusError(("Unable to find %s in DBUS." % DBUS_DEAMON_OBJECT)
@@ -167,12 +158,18 @@ class DRingCtrl(Thread):
     def onCallHangup_cb(self, callId):
         pass
 
-    def onCallRinging_cb(self):
+    def onCallConnecting_cb(self, callId):
+        pass
+
+    def onCallRinging_cb(self, callId):
         pass
 
     def onCallHold_cb(self):
         pass
 
+    def onCallInactive_cb(self):
+        pass
+
     def onCallCurrent_cb(self):
         pass
 
@@ -182,6 +179,9 @@ class DRingCtrl(Thread):
     def onCallFailure_cb(self):
         pass
 
+    def onCallOver_cb(self):
+        pass
+
     def onIncomingCall(self, account, callid, to):
         """ On incoming call event, add the call to the list of active calls """
 
@@ -192,13 +192,18 @@ class DRingCtrl(Thread):
         self.onIncomingCall_cb(callid)
 
 
-    def onCallHangUp(self, callid):
+    def onCallHangUp(self, callid, state):
         """ Remove callid from call list """
 
+        self.activeCalls[callid]['State'] = state
         self.onCallHangup_cb(callid)
         self.currentCallId = ""
-        del self.activeCalls[callid]
 
+    def onCallConnecting(self, callid, state):
+        """ Update state for this call to Ringing """
+
+        self.activeCalls[callid]['State'] = state
+        self.onCallConnecting_cb(callid)
 
     def onCallRinging(self, callid, state):
         """ Update state for this call to Ringing """
@@ -220,6 +225,11 @@ class DRingCtrl(Thread):
         self.activeCalls[callid]['State'] = state
         self.onCallCurrent_cb()
 
+    def onCallInactive(self, callid, state):
+        """ Update state for this call to current """
+
+        self.activeCalls[callid]['State'] = state
+        self.onCallInactive_cb()
 
     def onCallBusy(self, callid, state):
         """ Update state for this call to busy """
@@ -231,9 +241,14 @@ class DRingCtrl(Thread):
     def onCallFailure(self, callid, state):
         """ Handle call failure """
 
+        self.activeCalls[callid]['State'] = state
         self.onCallFailure_cb()
-        del self.activeCalls[callid]
 
+    def onCallOver(self, callid):
+        """ Handle call failure """
+
+        self.onCallOver_cb()
+        del self.activeCalls[callid]
 
     def onCallStateChanged(self, callid, state, code):
         """ On call state changed event, set the values for new calls,
@@ -252,7 +267,9 @@ class DRingCtrl(Thread):
         self.currentCallId = callid
 
         if state == "HUNGUP":
-            self.onCallHangUp(callid)
+            self.onCallHangUp(callid, state)
+        elif state == "CONNECTING":
+            self.onCallConnecting(callid, state)
         elif state == "RINGING":
             self.onCallRinging(callid, state)
         elif state == "CURRENT":
@@ -263,6 +280,10 @@ class DRingCtrl(Thread):
             self.onCallBusy(callid, state)
         elif state == "FAILURE":
             self.onCallFailure(callid, state)
+        elif state == "OVER":
+            self.onCallOver(callid)
+        elif state == "INACTIVE":
+            self.onCallInactive(callid,state)
         else:
             print("unknown state:" + str(state))
 
@@ -498,7 +519,7 @@ class DRingCtrl(Thread):
         for call in self.activeCalls:
             print("\t" + call)
 
-    def Call(self, dest):
+    def Call(self, dest, account=None):
         """Start a call and return a CallID
 
         Use the current account previously set using setAccount().
@@ -508,7 +529,7 @@ class DRingCtrl(Thread):
         """
 
         if dest is None or dest == "":
-            raise SflPhoneError("Invalid call destination")
+            raise DRingCtrlError("Invalid call destination")
 
         # Set the account to be used for this call
         if not self.account:
@@ -638,6 +659,9 @@ class DRingCtrl(Thread):
     def sendFile(self, *args, **kwds):
         return self.configurationmanager.sendFile(*args, **kwds)
 
+    def sendTextMessage(self, account, to, message):
+        return self.configurationmanager.sendTextMessage(account, to, { 'text/plain': message })
+
     def run(self):
         """Processing method for this thread"""
 
diff --git a/tools/dringctrl/dringctrl.py b/tools/dringctrl/dringctrl.py
index b09df07bed..e5d842bc69 100755
--- a/tools/dringctrl/dringctrl.py
+++ b/tools/dringctrl/dringctrl.py
@@ -32,7 +32,7 @@ except Exception as e:
     print(str(e))
     exit(1)
 
-from errors import *
+
 from controller import DRingCtrl
 from tester import DRingTester
 
diff --git a/tools/dringctrl/jami_test.py b/tools/dringctrl/jami_test.py
new file mode 100755
index 0000000000..f848121335
--- /dev/null
+++ b/tools/dringctrl/jami_test.py
@@ -0,0 +1,119 @@
+#! /usr/bin/env python3
+#
+#  Copyright (C) 2019 Savoir-faire Linux Inc. Inc
+#
+# Author: Mohamed Fenjiro <mohamed.fenjiro@savoirfairelinux.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+#
+
+import signal
+import sys
+import os
+import time
+import argparse
+
+from gi.repository import GLib
+from errorsDring import DRingCtrlError
+from controller import DRingCtrl
+
+class JamiTest(DRingCtrl):
+    def __init__(self, name, args):
+        super(JamiTest, self).__init__(name, False)
+        self.args = args
+        self.testCalls = set()
+        self.iterator = 0
+        self.failureCount = 0
+        self.failureRate = 0
+        self.callsCompleted = 0
+        ringAccounts = self.getAllAccounts('RING')
+        self.setAccount(ringAccounts[0])
+
+        if not args.peer:
+            peer = ringAccounts[1]
+            details = self.getAccountDetails(peer)
+            self.peer = details['Account.username']
+        else:
+            self.peer = args.peer
+
+        print("Using local test account: ", self.account)
+        print("Using test peer: ", self.peer)
+        if self.testCall():
+            GLib.timeout_add_seconds(args.interval, self.testCall)
+
+    def keepGoing(self):
+        return self.args.calls == 0 or self.iterator < self.args.calls
+
+    def testCall(self):
+        print("**[BEGIN] Call Test")
+        if self.keepGoing():
+            self.iterator += 1
+            callId = self.Call(self.peer)
+            self.testCalls.add(callId)
+            GLib.timeout_add_seconds(self.args.duration, lambda: self.checkCall(callId))
+        return self.keepGoing()
+
+    def checkCall(self, callId):
+        self.HangUp(callId)
+        if callId in self.testCalls:
+            self.testFailed(callId)
+        return False
+
+    def onCallStateChanged(self, callId, state, statecode):
+        super().onCallStateChanged(callId, state, statecode)
+        if callId in self.testCalls:
+            if state == "RINGING":
+                self.testSucceeded(callId)
+                self.HangUp(callId)
+
+    def testEnded(self, callId):
+        self.testCalls.remove(callId)
+        self.callsCompleted += 1
+        self.failureRate = (self.failureCount / float(self.callsCompleted))
+        print("Failure rate: ", self.failureRate)
+        if not self.keepGoing():
+            print("ENDING")
+            self.stopThread()
+            self.unregister()
+
+    def testFailed(self, callId):
+        self.failureCount += 1
+        self.testEnded(callId)
+
+    def testSucceeded(self, callId):
+        self.testEnded(callId)
+    
+    def run(self):
+        super().run()
+        if self.failureCount == 0:
+            sys.exit(0)
+        elif self.failureRate < .5:
+            sys.exit(1)
+        else:
+            sys.exit(2)
+
+if __name__ == "__main__":
+
+    parser = argparse.ArgumentParser(description='Monitor Jami reliabilty by mesuring failure rate for making Calls/Messages and receiving them.')
+    parser.add_argument('--messages', help='Number of messages sent', type=int)
+    parser.add_argument('--calls', help='Number of calls made', default=0, type=int)
+    parser.add_argument('--duration', help='Specify the duration of the test (seconds)', default=10, type=int)
+    parser.add_argument('--interval', help='Specify the test interval (seconds)', default=0, type=int)
+    parser.add_argument('--peer', help='Specify the peer account id')
+
+    args = parser.parse_args()
+
+    test = JamiTest("test", args)
+    test.run()
\ No newline at end of file
-- 
GitLab