From 103bd5b65c5000cd19b75da8be9ba82437e1aa5c Mon Sep 17 00:00:00 2001 From: Alexandre Savard <alexandre.savard@savoirfairelinux.com> Date: Sun, 15 Apr 2012 10:27:51 -0400 Subject: [PATCH] #9621: Implement conference functional tests --- tools/pysflphone/sflphonectrl.py | 50 ++- tools/pysflphone/sippwrap.py | 13 +- .../test_sflphone_dbus_interface.py | 307 ++++++++++++++---- ...no_cvs.xml => uac_register_no_cvs_300.xml} | 0 tools/sippxml/uac_register_no_cvs_400.xml | 110 +++++++ 5 files changed, 397 insertions(+), 83 deletions(-) rename tools/sippxml/{uac_register_no_cvs.xml => uac_register_no_cvs_300.xml} (100%) create mode 100644 tools/sippxml/uac_register_no_cvs_400.xml diff --git a/tools/pysflphone/sflphonectrl.py b/tools/pysflphone/sflphonectrl.py index 88a4fe9038..b6a17226a2 100755 --- a/tools/pysflphone/sflphonectrl.py +++ b/tools/pysflphone/sflphonectrl.py @@ -59,11 +59,15 @@ class SflPhoneCtrl(Thread): onCallCurrent_cb onCallBusy_cb onCallFailure_cb + onConferenceCreated_cb """ # list of active calls (known by the client) activeCalls = {} + # list of active conferences + activeConferences = {} + def __init__(self, name=sys.argv[0]): Thread.__init__(self) @@ -74,6 +78,7 @@ class SflPhoneCtrl(Thread): self.name = name self.currentCallId = "" + self.currentConfId = "" self.isStop = False @@ -145,6 +150,7 @@ class SflPhoneCtrl(Thread): print "Adding Incoming call method" proxy_callmgr.connect_to_signal('incomingCall', self.onIncomingCall) proxy_callmgr.connect_to_signal('callStateChanged', self.onCallStateChanged) + proxy_callmgr.connect_to_signal('conferenceCreated', self.onConferenceCreated) except dbus.DBusException, e: print e @@ -171,7 +177,7 @@ class SflPhoneCtrl(Thread): def onIncomingCall_cb(self): pass - def onCallHangup_cb(self): + def onCallHangup_cb(self, callId): pass def onCallRinging_cb(self): @@ -202,7 +208,7 @@ class SflPhoneCtrl(Thread): def onCallHangUp(self, callid): """ Remove callid from call list """ - self.onCallHangup_cb() + self.onCallHangup_cb(callid) self.currentCallId = "" del self.activeCalls[callid] @@ -274,6 +280,12 @@ class SflPhoneCtrl(Thread): else: print "unknown state" + def onConferenceCreated_cb(self): + pass + + def onConferenceCreated(self, confId): + self.currentConfId = confId + self.onConferenceCreated_cb() # # Account management @@ -545,9 +557,6 @@ class SflPhoneCtrl(Thread): for call in self.activeCalls: print "\t" + call - # - # Action - # def Call(self, dest): """Start a call and return a CallID @@ -636,12 +645,6 @@ class SflPhoneCtrl(Thread): def Hold(self, callid): """Hold a call identified by a CallID""" - # if not self.account: - # self.setFirstRegisteredAccount() - - # if not self.isAccountRegistered(): - # raise SflPhoneError("Can't hold a call without a registered account") - if callid is None or callid == "": raise SflPhoneError("Invalid callID") @@ -651,12 +654,6 @@ class SflPhoneCtrl(Thread): def UnHold(self, callid): """Unhold an incoming call identified by a CallID""" - # if not self.account: - # self.setFirstRegisteredAccount() - - # if not self.isAccountRegistered(): - # raise SflPhoneError("Can't unhold a call without a registered account") - if callid is None or callid == "": raise SflPhoneError("Invalid callID") @@ -679,13 +676,30 @@ class SflPhoneCtrl(Thread): callid = m.hexdigest() return callid + + def createConference(self, call1Id, call2Id): + """ Create a conference given the two call ids """ + + self.callmanager.joinParticipant(call1Id, call2Id) + + + def hangupConference(self, confId): + """ Hang up each call for this conference """ + + self.callmanager.hangUpConference(confId) + + def run(self): """Processing method for this thread""" - context = self.loop.get_context() + context = self.loop.get_context() while True: context.iteration(True) if self.isStop: + print "++++++++++++++++++++++++++++++++++++++++" + print "++++++++++++++++++++++++++++++++++++++++" + print "++++++++++++++++++++++++++++++++++++++++" + print "++++++++++++++++++++++++++++++++++++++++" return diff --git a/tools/pysflphone/sippwrap.py b/tools/pysflphone/sippwrap.py index b959478241..0bdc073558 100644 --- a/tools/pysflphone/sippwrap.py +++ b/tools/pysflphone/sippwrap.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# Copyright (C) 2009 by the Free Software Foundation, Inc. +# Copyright (C) 2012 by the Free Software Foundation, Inc. +# +# Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -22,7 +24,7 @@ class SippWrapper: """ Wrapper taht allow for managing sipp command line easily """ def __init__(self): - self.commandLine = "sipp" + self.commandLine = "./sipp" self.remoteServer = "" self.remotePort = "" self.localInterface = "" @@ -41,9 +43,11 @@ class SippWrapper: self.enableTraceRtt = False self.enableTraceLogs = False - def buildCommandLine(self): + def buildCommandLine(self, port): """ Fill the command line arguments based on specified parameters """ + self.localPort = str(port) + if not self.remotePort and not self.remoteServer: self.isUserAgentClient = False elif self.remotePort and not self.remoteServer: @@ -106,9 +110,8 @@ class SippWrapper: def launch(self): """ Launch the sipp instance using the specified arguments """ - self.buildCommandLine() print self.commandLine - return os.system(self.commandLine) + return os.system(self.commandLine + " 2>&1 > /dev/null") class SippScreenStatParser: diff --git a/tools/pysflphone/test_sflphone_dbus_interface.py b/tools/pysflphone/test_sflphone_dbus_interface.py index 2af61f5c55..7b796bb527 100644 --- a/tools/pysflphone/test_sflphone_dbus_interface.py +++ b/tools/pysflphone/test_sflphone_dbus_interface.py @@ -1,6 +1,8 @@ -#!/usr/bin/env python +#!/USR/BIN/ENV PYTHON # -# Copyright (C) 2009 by the Free Software Foundation, Inc. +# Copyright (C) 2012 by the Free Software Foundation, Inc. +# +# Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,7 +20,9 @@ import os import time +import yaml import logging +import multiprocessing from sippwrap import SippWrapper from sippwrap import SippScreenStatParser from sflphonectrl import SflPhoneCtrl @@ -29,10 +33,62 @@ from nose.tools import nottest ### function starting with 'test' are executed. ### -accountList = ["IP2IP", "Account:1332798167"] - SCENARIO_PATH = "../sippxml/" +class SippCtrl: + + def __init__(self): + self.remoteServer = "127.0.0.1" + self.remotePort = str(5062) + self.localInterface = "127.0.0.1" + self.localPort = str(5060) + + def initialize_sipp_registration_instance(self, instance, xmlScenario): + instance.remoteServer = self.remoteServer + instance.remotePort = self.remotePort + instance.localInterface = self.localInterface + instance.localPort = self.localPort + instance.customScenarioFile = SCENARIO_PATH + xmlScenario + instance.numberOfCall = 1 + instance.numberOfSimultaneousCall = 1 + + def initialize_sipp_call_instance(self, instance): + instance.localInterface = self.localInterface + instance.localPort = self.localPort + instance.numberOfCall = 1 + instance.numberOfSimultaneousCall = 1 + instance.enableTraceScreen = True + + def launchSippProcess(self, sippInstance, localPort): + sippInstance.buildCommandLine(localPort) + sippInstance.launch() + + def find_sipp_pid(self): + # Retreive the PID of the last + # The /proc/PID/cmdline contain the command line from + pids = [int(x) for x in os.listdir("/proc") if x.isdigit()] + sippPid = [pid for pid in pids if "sipp" in open("/proc/" + str(pid) + "/cmdline").readline()] + + return sippPid[0] + + def clean_log_directory(self): + dirlist = os.listdir("./") + files = [x for x in dirlist if "screen.log" in x] + for f in files: + os.remove(f) + + def parse_results(self): + dirlist = os.listdir("./") + logfile = [x for x in dirlist if "screen.log" in x] + + fullpath = os.path.dirname(os.path.realpath(__file__)) + "/" + + # there should be only one screen.log file (see clean_log_directory) + resultParser = SippScreenStatParser(fullpath + logfile[0]) + + assert(not resultParser.isAnyFailedCall()) + assert(resultParser.isAnySuccessfulCall()) + class TestSFLPhoneAccountConfig(SflPhoneCtrl): """ The test suite for account configuration """ @@ -46,14 +102,40 @@ class TestSFLPhoneAccountConfig(SflPhoneCtrl): self.logger.addHandler(filehdlr) self.logger.setLevel(logging.INFO) - @nottest + + def get_config(self): + """ Parsse configuration file and return a dictionary """ + config = {} + with open("sflphoned.functest.yml","r") as stream: + config = yaml.load(stream) + + return config + + + def get_account_list_from_config(self): + """ Get the accout list from config and add IP2IP """ + + config = self.get_config() + + accounts = config["preferences"]["order"] + accountList = accounts.split('/') + del accountList[len(accountList)-1] + accountList.append("IP2IP") + + return accountList + + def test_get_account_list(self): self.logger.info("Test get account list") + + accountList = self.get_account_list_from_config() + + # make sure that the intersection between the list is of same size accList = self.getAllAccounts() listIntersection = set(accList) & set(accountList) assert len(listIntersection) == len(accountList) - @nottest + def test_account_registration(self): self.logger.info("Test account registration") accList = [x for x in self.getAllAccounts() if x != "IP2IP"] @@ -72,6 +154,24 @@ class TestSFLPhoneAccountConfig(SflPhoneCtrl): assert self.isAccountRegistered(acc) + + @nottest + def test_get_account_details(self): + self.logger.info("Test account details") + + accList = [x for x in self.getAllAccounts() if x != "IP2IP"] + + config = self.get_config() + + accountDetails = {} + for acc in accList: + accountDetails[acc] = self.getAccountDetails(acc) + + accountConfDetails = {} + for accConf in config["accounts"]: + accountConfDetails[accConf["id"]] = accConf + + @nottest def test_add_remove_account(self): self.logger.info("Test add/remove account") @@ -99,11 +199,12 @@ class TestSFLPhoneAccountConfig(SflPhoneCtrl): -class TestSFLPhoneRegisteredCalls(SflPhoneCtrl): +class TestSFLPhoneRegisteredCalls(SflPhoneCtrl, SippCtrl): """ The test suite for call interaction """ def __init__(self): SflPhoneCtrl.__init__(self) + SippCtrl.__init__(self) self.logger = logging.getLogger("TestSFLPhoneRegisteredCalls") filehdlr = logging.FileHandler("/tmp/sfltestregisteredcall.log") @@ -113,8 +214,6 @@ class TestSFLPhoneRegisteredCalls(SflPhoneCtrl): self.logger.setLevel(logging.INFO) self.sippRegistrationInstance = SippWrapper() self.sippCallInstance = SippWrapper() - self.localInterface = "127.0.0.1" - self.localPort = str(5064) # Make sure the test directory is populated with most recent log files self.clean_log_directory() @@ -143,68 +242,144 @@ class TestSFLPhoneRegisteredCalls(SflPhoneCtrl): self.stopThread() - def clean_log_directory(self): - dirlist = os.listdir("./") - files = [x for x in dirlist if "screen.log" in x] - for f in files: - os.remove(f) + def test_registered_call(self): + self.logger.info("Test Registered Call") + # Launch a sipp instance for account registration on asterisk + # this account will then be used to receive call from sflphone + self.initialize_sipp_registration_instance(self.sippRegistrationInstance, "uac_register_no_cvs_300.xml") + regd = multiprocessing.Process(name='sipp1register', target=self.launchSippProcess, + args=(self.sippRegistrationInstance, 5064,)) + regd.start() - def find_sipp_pid(self): - # Retreive the PID of the last - # The /proc/PID/cmdline contain the command line from - pids = [int(x) for x in os.listdir("/proc") if x.isdigit()] - sippPid = [pid for pid in pids if "sipp" in open("/proc/" + str(pid) + "/cmdline").readline()] + # wait for the registration to complete + regd.join() - return sippPid[0] + # Launch a sipp instance waiting for a call from previously registered account + self.initialize_sipp_call_instance(self.sippCallInstance) + calld = multiprocessing.Process(name='sipp1call', target=self.launchSippProcess, + args=(self.sippCallInstance, 5064,)) + calld.start() + # Make sure every account are enabled + accList = [x for x in self.getAllAccounts() if x != "IP2IP"] + for acc in accList: + if not self.isAccountRegistered(acc): + self.setAccountEnable(acc, True) - def parse_results(self): - dirlist = os.listdir("./") - logfile = [x for x in dirlist if "screen.log" in x] - print logfile + # Make a call to the SIPP instance + self.Call("300") - fullpath = os.path.dirname(os.path.realpath(__file__)) + "/" + # Start the threaded loop to handle GLIB cllbacks + self.start() - # there should be only one screen.log file (see clean_log_directory) - resultParser = SippScreenStatParser(fullpath + logfile[0]) + # Wait for the sipp instance to dump log files + calld.join() - assert(not resultParser.isAnyFailedCall()) - assert(resultParser.isAnySuccessfulCall()) + self.stopThread() + self.parse_results() - def test_registered_call(self): - self.logger.info("Test Registered Call") +class TestSFLPhoneConferenceCalls(SflPhoneCtrl, SippCtrl): + """ Test Conference calls """ - # launch the sipp instance in background - # sipp 127.0.0.1:5060 -sf uac_register_no_cvs.xml -i 127.0.0.1 -p 5062 - self.sippRegistrationInstance.remoteServer = "127.0.0.1" - self.sippRegistrationInstance.remotePort = str(5062) - self.sippRegistrationInstance.localInterface = self.localInterface - self.sippRegistrationInstance.localPort = self.localPort - self.sippRegistrationInstance.customScenarioFile = SCENARIO_PATH + "uac_register_no_cvs.xml" - self.sippRegistrationInstance.launchInBackground = True - self.sippRegistrationInstance.numberOfCall = 1 - self.sippRegistrationInstance.numberOfSimultaneousCall = 1 + def __init__(self): + SflPhoneCtrl.__init__(self) + SippCtrl.__init__(self) - self.sippRegistrationInstance.launch() + self.logger = logging.getLogger("TestSFLPhoneRegisteredCalls") + filehdlr = logging.FileHandler("/tmp/sfltestregisteredcall.log") + formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s") + filehdlr.setFormatter(formatter) + self.logger.addHandler(filehdlr) + self.logger.setLevel(logging.INFO) + self.sippRegistrationInstanceA = SippWrapper() + self.sippRegistrationInstanceB = SippWrapper() + self.sippCallInstanceA = SippWrapper() + self.sippCallInstanceB = SippWrapper() + self.localPortCallA = str(5064) + self.localPortCallB = str(5066) + self.callCount = 0 + self.accountCalls = [] - # wait for this instance of sipp to complete registration - sippPid = self.find_sipp_pid() - while os.path.exists("/proc/" + str(sippPid)): - time.sleep(1) + # Make sure the test directory is populated with most recent log files + # self.clean_log_directory() - # sipp -sn uas -p 5062 -i 127.0.0.1 - self.sippCallInstance.localInterface = self.localInterface - self.sippCallInstance.localPort = self.localPort - self.sippCallInstance.launchInBackground = True - self.sippCallInstance.numberOfCall = 1 - self.sippCallInstance.numberOfSimultaneousCall = 1 - self.sippCallInstance.enableTraceScreen = True - self.sippCallInstance.launch() + def onCallCurrent_cb(self): + """ On incoming call, answer the call, then hangup """ - sippPid = self.find_sipp_pid() + self.callCount += 1 + + self.accountCalls.append(self.currentCallId) + print "Account List: ", str(self.accountCalls) + + if self.callCount == 2: + self.createConference(self.accountCalls[0], self.accountCalls[1]) + + + def onCallRinging_cb(self): + """ Display messages when call is ringing """ + + print "The call is ringing" + + + def onCallHangup_cb(self, callId): + """ Exit thread when all call are finished """ + + if callId in self.accountCalls: + self.accountCalls.remove(callId) + + self.callCount -= 1 + if self.callCount == 0: + self.stopThread() + + + def onCallFailure_cb(self): + """ If a failure occurs duing the call, just leave the running thread """ + + print "Stopping Thread" + self.stopThread() + + + def onConferenceCreated_cb(self): + """ Called once the conference is created """ + + print "Conference Created ", self.currentConfId + print "Conference Hangup ", self.currentConfId + + self.hangupConference(self.currentConfId) + + + def test_conference_call(self): + self.logger.info("Test Registered Call") + + # launch the sipp instance to register the first participant to astersik + self.initialize_sipp_registration_instance(self.sippRegistrationInstanceA, "uac_register_no_cvs_300.xml") + regd = multiprocessing.Process(name='sipp1register', target=self.launchSippProcess, + args=(self.sippRegistrationInstanceA, 5064,)) + regd.start() + regd.join() + + # launch the sipp instance to register the second participant to asterisk + self.initialize_sipp_registration_instance(self.sippRegistrationInstanceB, "uac_register_no_cvs_400.xml") + regd = multiprocessing.Process(name='sipp2register', target=self.launchSippProcess, + args=(self.sippRegistrationInstanceB, 5066,)) + regd.start() + regd.join() + + # launch the sipp instance waining for call as the first participant + self.initialize_sipp_call_instance(self.sippCallInstanceA) + calldA = multiprocessing.Process(name='sipp1call', target=self.launchSippProcess, + args=(self.sippCallInstanceA, 5064,)) + calldA.start() + + + # launch the sipp instance waiting for call as the second particpant + self.initialize_sipp_call_instance(self.sippCallInstanceB) + calldB = multiprocessing.Process(name='sipp2call', target=self.launchSippProcess, + args=(self.sippCallInstanceB, 5066,)) + calldB.start() # make sure every account are enabled accList = [x for x in self.getAllAccounts() if x != "IP2IP"] @@ -212,14 +387,26 @@ class TestSFLPhoneRegisteredCalls(SflPhoneCtrl): if not self.isAccountRegistered(acc): self.setAccountEnable(acc, True) - # Make a call to the SIPP instance + # make a call to the SIPP instance self.Call("300") + self.Call("400") - # Start Glib mainloop to process callbacks + # start the main loop for processing glib callbacks self.start() - # Wait the sipp instance to dump log files - while os.path.exists("/proc/" + str(sippPid)): - time.sleep(1) + print "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-" + calldA.join() + print "+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+" + calldB.join() + + print "=====================================================" + self.stopThread() self.parse_results() + + +# callInstance = TestSFLPhoneRegisteredCalls() +# callInstance.test_registered_call() + +confInstance = TestSFLPhoneConferenceCalls() +confInstance.test_conference_call() diff --git a/tools/sippxml/uac_register_no_cvs.xml b/tools/sippxml/uac_register_no_cvs_300.xml similarity index 100% rename from tools/sippxml/uac_register_no_cvs.xml rename to tools/sippxml/uac_register_no_cvs_300.xml diff --git a/tools/sippxml/uac_register_no_cvs_400.xml b/tools/sippxml/uac_register_no_cvs_400.xml new file mode 100644 index 0000000000..ccceacdf77 --- /dev/null +++ b/tools/sippxml/uac_register_no_cvs_400.xml @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="ISO-8859-2" ?> + +<!-- Use with CSV file struct like: 3000;192.168.1.106;[authentication username=3000 password=3000]; + (user part of uri, server address, auth tag in each line) +--> + +<scenario name="register_client"> + <send retrans="500"> + <![CDATA[ + + REGISTER sip:127.0.0.1 SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:300@127.0.0.1>;tag=[call_number] + To: <sip:400@127.0.0.1> + Call-ID: [call_id] + CSeq: [cseq] REGISTER + Contact: sip:300@[local_ip]:[local_port] + Max-Forwards: 10 + Expires: 120 + User-Agent: SIPp/Win32 + Content-Length: 0 + + ]]> + </send> + + <!-- asterisk --> + <recv response="200"> + <!-- + <action> + <ereg regexp=".*" search_in="hdr" header="Contact:" check_it="true" assign_to="1" /> + </action> + --> + </recv> + + <!-- + <recv request="INVITE" crlf="true"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[pid]SIPpTag01[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[pid]SIPpTag01[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK" + optional="true" + rtd="true" + crlf="true"> + </recv> + + <recv request="BYE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <timewait milliseconds="4000"/> + + + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + --> + +</scenario> -- GitLab