[Asterisk-code-review] Add a nominal caller initiated remote attended transfer test. (testsuite[master])

John Bigelow asteriskteam at digium.com
Tue Jun 2 12:33:06 CDT 2015


John Bigelow has uploaded a new change for review.

  https://gerrit.asterisk.org/565

Change subject: Add a nominal caller initiated remote attended transfer test.
......................................................................

Add a nominal caller initiated remote attended transfer test.

This uses two Asterisk instances and a PJSUA instance. The PJSUA
instance makes a call to both Asterisk instances and then performs an
attended transfer. This ensures that the caller initiated remote
attended transfer properly occurs.

The pjsua_mod.py module had to be modified to allow the creation of
PJSUA accounts that do not register. Otherwise PJSUA will only send
calls to the registrar that it is registered to even if the IP of the
dialed URI differs. Thus the test configuration option 'register' was
created to allow disabling registration for PJSUA accounts. If
registration is disabled, the callback module for user code is called
after all accounts have been created.

The phones.py module was updated to override pjsua_mod.acct_success()
that has been added. This is so the AMI PJsuaPhonesReady user event is
generated when registration is disabled and all accounts have been
created.

Change-Id: I5aaf2b4a72d3334e320c7056a4eed8176b71ce6e
---
M lib/python/asterisk/phones.py
M lib/python/asterisk/pjsua_mod.py
A tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast1/extensions.conf
A tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast1/pjsip.conf
A tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast2/extensions.conf
A tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast2/pjsip.conf
A tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/test-config.yaml
M tests/channels/pjsip/transfers/attended_transfer/nominal/tests.yaml
8 files changed, 361 insertions(+), 13 deletions(-)


  git pull ssh://gerrit.asterisk.org:29418/testsuite refs/changes/65/565/1

diff --git a/lib/python/asterisk/phones.py b/lib/python/asterisk/phones.py
index 5ad1c36..c4822eb 100755
--- a/lib/python/asterisk/phones.py
+++ b/lib/python/asterisk/phones.py
@@ -48,10 +48,29 @@
             PjsuaPhoneController.__singleton_instance = self
         LOGGER.info("Pluggable module initialized.")
 
+    def acct_success(self):
+        """Override of parent method.
+
+        If accounts will not be registering and all accounts have been created,
+        instantiate a phone for each.
+        """
+        self.num_accts_created += 1
+        if (self.num_accts_created != self.num_accts or
+                self.config.get('register', True)):
+            return
+
+        for account in self.config['accounts']:
+            pjsua_phone = PjsuaPhone(self, account)
+            self.__pjsua_phones[account['name']] = pjsua_phone
+
+        LOGGER.info("PJSUA Accounts Created.")
+        self.__setup_pjsua_acc_cb()
+
     def reg_success(self):
         """Override of parent callback method.
 
-        Callback for when all PJSUA accounts have registered to Asterisk.
+        Callback for when all PJSUA accounts have registered to Asterisk. This
+        will instantiate a phone for each.
         """
         self.num_regs += 1
         if self.num_regs != self.num_accts:
diff --git a/lib/python/asterisk/pjsua_mod.py b/lib/python/asterisk/pjsua_mod.py
index 6e526dd..63add6a 100644
--- a/lib/python/asterisk/pjsua_mod.py
+++ b/lib/python/asterisk/pjsua_mod.py
@@ -32,9 +32,6 @@
     called when the registration state of an account changes. When all
     configured accounts have registered, then the configured callback method
     for the test is called into.
-
-    This means that as written, all PJSUA tests require registration to be
-    performed.
     """
     def __init__(self, test_plugin):
         self.test_plugin = test_plugin
@@ -100,6 +97,12 @@
     This class will initiate PJLIB, create any configured accounts, and wait
     for the accounts to register. Once registered, this will call into user
     code so that manipulation of the endpoints may be performed if specified.
+
+    This class will initiate PJLIB and create any configured accounts. Accounts
+    can be configured to register or not register on an overall basis (not per
+    account). If configured to register (the default), this will call into user
+    code once all accounts have registered. If configured not to register, this
+    will call into user code once all accounts have been created.
     """
 
     def __init__(self, instance_config, test_object):
@@ -114,6 +117,7 @@
         self.lib = None
         self.num_regs = 0
         self.num_accts = 0
+        self.num_accts_created = 0
         self.ami_connected = 0
         self.callback_module = instance_config.get('callback_module')
         self.callback_method = instance_config.get('callback_method')
@@ -218,12 +222,21 @@
 
     def __create_account(self, acct_cfg):
         """Create a PJSuaAccount from configuration"""
+        account_cb = None
         name = acct_cfg['name']
         username = acct_cfg.get('username', name)
         domain = acct_cfg.get('domain', '127.0.0.1')
         password = acct_cfg.get('password', '')
 
-        pj_acct_cfg = pj.AccountConfig(domain, username, password, name)
+        # If account is not to register to a server then create the config
+        # without specifying a domain and set the ID using the domain ourself.
+        if not self.config.get('register', True):
+            pj_acct_cfg = pj.AccountConfig()
+            pj_acct_cfg.id = "%s <sip:%s@%s>" % (name, username, domain)
+        else:
+            account_cb = RegDetector(self)
+            pj_acct_cfg = pj.AccountConfig(domain, username, password, name)
+
         if acct_cfg.get('mwi-subscribe'):
             pj_acct_cfg.mwi_enabled = 1
         if acct_cfg.get('transport'):
@@ -233,9 +246,8 @@
                 pj_acct_cfg.transport_id = transport_id
 
         LOGGER.info("Creating PJSUA account %s@%s" % (username, domain))
-        account = PJsuaAccount(
-            self.lib.create_account(pj_acct_cfg, False, RegDetector(self)),
-            self.lib)
+        account = PJsuaAccount(self.lib.create_account(pj_acct_cfg, False,
+                                                       account_cb), self.lib)
         account.add_buddies(acct_cfg.get('buddies', []))
         return account
 
@@ -258,14 +270,42 @@
                 LOGGER.error("Account configuration has no name")
                 self.test_object.stop_reactor()
             self.pj_accounts[name] = self.__create_account(acct)
+            self.acct_success()
+
+    def acct_success(self):
+        """Count & check number of created PJSUA accounts.
+
+        If accounts will not be registering and all accounts have been created,
+        call the configured callback module/method.
+        """
+        self.verify_callback_config()
+        self.num_accts_created += 1
+        # Only execute callback when accounts won't be registering. The
+        # callback will be executed else where if accounts will be registering.
+        if (self.num_accts_created == self.num_accts and
+                not self.config.get('register', True)):
+            self.do_callback()
 
     def reg_success(self):
+        """Count & check number of registered PJSUA accounts.
+
+        If all accounts have registered, call the configured callback
+        module/method.
+        """
+        self.verify_callback_config()
+        self.num_regs += 1
+        if self.num_regs == self.num_accts:
+            self.do_callback()
+
+    def verify_callback_config(self):
+        """Stop the reactor if no callback module or method is configured"""
         if self.callback_module is None or self.callback_method is None:
             LOGGER.error("No callback configured.")
             self.test_object.stop_reactor()
             return
-        self.num_regs += 1
-        if self.num_regs == self.num_accts:
-            callback_module = __import__(self.callback_module)
-            callback_method = getattr(callback_module, self.callback_method)
-            callback_method(self.test_object, self.pj_accounts)
+
+    def do_callback(self):
+        """Call the configured callback module/method"""
+        callback_module = __import__(self.callback_module)
+        callback_method = getattr(callback_module, self.callback_method)
+        callback_method(self.test_object, self.pj_accounts)
diff --git a/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast1/extensions.conf b/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast1/extensions.conf
new file mode 100644
index 0000000..ec4347c
--- /dev/null
+++ b/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast1/extensions.conf
@@ -0,0 +1,9 @@
+[default]
+
+exten => 101,1,Dial(Local/echo at default)
+
+exten => echo,1,Answer()
+same => n,Echo()
+
+exten => external_replaces,1,NoOp()
+same => n,Dial(PJSIP/asterisk2/${SIPREFERTOHDR})
diff --git a/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast1/pjsip.conf b/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast1/pjsip.conf
new file mode 100644
index 0000000..6631d2b
--- /dev/null
+++ b/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast1/pjsip.conf
@@ -0,0 +1,24 @@
+[globals]
+type = global
+debug = yes
+
+[local]
+type = transport
+protocol = udp
+bind = 127.0.0.1:5060
+
+[alice]
+type = endpoint
+context = default
+allow = ulaw
+rewrite_contact = yes
+aors = alice
+
+[alice]
+type = aor
+max_contacts=1
+
+[asterisk2]
+type = endpoint
+allow = ulaw
+from_user = asterisk1
diff --git a/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast2/extensions.conf b/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast2/extensions.conf
new file mode 100644
index 0000000..3282ba7
--- /dev/null
+++ b/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast2/extensions.conf
@@ -0,0 +1,6 @@
+[default]
+
+exten => 102,1,Dial(Local/echo2 at default)
+
+exten => echo2,1,Answer()
+same => n,Echo()
diff --git a/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast2/pjsip.conf b/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast2/pjsip.conf
new file mode 100644
index 0000000..f53606a
--- /dev/null
+++ b/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/configs/ast2/pjsip.conf
@@ -0,0 +1,24 @@
+[global]
+type = global
+debug = yes
+
+[local]
+type = transport
+protocol = udp
+bind = 127.0.0.2:5060
+
+[alice]
+type = endpoint
+context = default
+allow = ulaw
+rewrite_contact = yes
+aors = alice
+
+[alice]
+type = aor
+max_contacts = 1
+
+[asterisk1]
+type = endpoint
+allow = ulaw
+from_user = asterisk2
diff --git a/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/test-config.yaml b/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/test-config.yaml
new file mode 100644
index 0000000..1a60d1f
--- /dev/null
+++ b/tests/channels/pjsip/transfers/attended_transfer/nominal/caller_remote/test-config.yaml
@@ -0,0 +1,225 @@
+testinfo:
+    summary: "Nominal caller initiated remote attended transfer."
+    description: |
+        "This test uses two Asterisk and a PJSUA instance to perform a remote
+        attended transfer. For PJSUA to properly perform the transfer, it does
+        not register to either Asterisk instance.
+
+        Alice (PJSUA) first calls Asterisk 1 which goes into the dialplan and
+        dials a Local channel that executes Echo(). Once it is confirmed that
+        Alice and Asterisk 1 are bridged, Alice places the call on hold. Alice
+        then calls Asterisk 2 which goes into the dialplan and dials a Local
+        channel that executes Echo(). Once it is confirmed that Alice and
+        Asterisk 2 are bridged, Alice performs an attended transfer.
+
+        The attended transfer results in Alice sending a REFER request to
+        Asterisk 1. Since the referenced dialog in the Refer-To header of the
+        REFER is not known by Asterisk 1, Asterisk 1 must perform a remote
+        attended transfer. The end result should be that Asterisk 1 and
+        Asterisk 2 are connected via SIP, and both of Alice's calls are hung
+        up.
+
+        From the perspective of the Asterisk server performing the transfer, a
+        remote attended transfer appears to be a blind transfer. Therefore a
+        BlindTransfer AMI event is emitted.
+
+        This test ensures that Asterisk 1 properly handles a REFER that refers
+        to a remote dialog and that Asterisk 2 properly handles receiving an
+        INVITE with Replaces."
+
+test-modules:
+    test-object:
+        config-section: test-object-config
+        typename: test_case.TestCaseModule
+    modules:
+        -
+            config-section: ami-config
+            typename: 'pluggable_modules.EventActionModule'
+        -
+            config-section: pjsua-config
+            typename: 'phones.PjsuaPhoneController'
+        -
+            config-section: hangup-config
+            typename: 'pluggable_modules.HangupMonitor'
+
+test-object-config:
+    connect-ami: True
+    asterisk-instances: 2
+
+hangup-config:
+    ids: [ '0', '1' ]
+
+pjsua-config:
+    # For this test to properly work, the PJSUA account must *not* register.
+    register: False
+    transports:
+        -
+            name: 'local-ipv4'
+            bind: '127.0.0.3'
+            bindport: '5060'
+    accounts:
+        -
+            name: 'alice'
+            username: 'alice'
+            domain: '127.0.0.3'
+            transport: 'local-ipv4'
+
+ami-config:
+    # Ensure our pjsua phones are ready. Then alice calls exten 101 which
+    # creates a Local channel that enters Echo().
+    -
+        ami-events:
+            id: '0'
+            conditions:
+                match:
+                    Event: 'UserEvent'
+                    UserEvent: 'PJsuaPhonesReady'
+            count: 1
+        pjsua_phone:
+            action: 'call'
+            pjsua_account: 'alice'
+            call_uri: 'sip:101 at 127.0.0.1'
+    # Ensure first alice channel and the Local echo channel are bridged. Then
+    # alice places the call on hold.
+    -
+        ami-events:
+            id: '0'
+            conditions:
+                match:
+                    Event: 'BridgeEnter'
+                    Channel: '(PJSIP/alice-.*|Local/echo at default-.*)'
+                    BridgeNumChannels: '2'
+                nomatch:
+                    ConnectedLineNum: 'external_replaces'
+            count: 1
+        pjsua_phone:
+            action: 'hold'
+            pjsua_account: 'alice'
+    # Ensure MOH starts on Local/echo at default. Then alice places another call,
+    # this time to Asterisk instance 2.
+    -
+        ami-events:
+            id: '0'
+            conditions:
+                match:
+                    Event: 'MusicOnHoldStart'
+                    Channel: 'Local/echo at default-.*'
+            count: 1
+        pjsua_phone:
+            action: 'call'
+            pjsua_account: 'alice'
+            call_uri: 'sip:102 at 127.0.0.2'
+    # Ensure second alice channel and the Local echo2 channel are bridged on
+    # Asterisk instance 2. Then alice transfers the first call (bridged
+    # to Local/echo at default on Asterisk instance 1) to the second call (bridged
+    # to Local/echo2 at default on Asterisk instance 2).
+    -
+        ami-events:
+            id: '1'
+            conditions:
+                match:
+                    Event: 'BridgeEnter'
+                    Channel: '(PJSIP/alice-.*|Local/echo2 at default-.*)'
+                    BridgeNumChannels: '2'
+            count: 1
+        pjsua_phone:
+            action: 'transfer'
+            pjsua_account: 'alice'
+            transfer_type: 'attended'
+    # Ensure MOH stops on Local/echo at default.
+    -
+        ami-events:
+            id: '0'
+            conditions:
+                match:
+                    Event: 'MusicOnHoldStop'
+                    Channel: 'Local/echo at default-.*'
+            count: 1
+    # Ensure channel variable is properly set.
+    -
+        ami-events:
+            id: '0'
+            conditions:
+                match:
+                    Event: 'VarSet'
+                    Channel: 'Local/echo at default-.*'
+                    Variable: '__SIPREFERREDBYHDR'
+                    Value: '.*sip:alice at 127.0.0.3.*'
+            count: 1
+    # Ensure channel variable is properly set.
+    -
+        ami-events:
+            id: '0'
+            conditions:
+                match:
+                    Event: 'VarSet'
+                    Channel: 'Local/echo at default-.*'
+                    Variable: 'SIPREFERTOHDR'
+                    Value: 'sip:102 at 127.0.0.2'
+            count: 1
+    # Ensure blind transfer occurs properly.
+    -
+        ami-events:
+            id: '0'
+            conditions:
+                match:
+                    Event: 'BlindTransfer'
+            requirements:
+                match:
+                    TransfererChannel: 'PJSIP/alice-.*'
+                    TransfereeChannel: 'Local/echo at default-00000000;1'
+                    Context: 'default'
+                    Extension: 'external_replaces'
+                    Result: 'Success'
+    # Ensure asterisk1 channel and the Local echo channel are bridged on
+    # Asterisk instance 2 (due to PJSIP/asterisk1 being swapped with
+    # PJSIP/alice).
+    -
+        ami-events:
+            id: '1'
+            conditions:
+                match:
+                    Event: 'BridgeEnter'
+                    Channel: 'PJSIP/asterisk1-.*'
+                    BridgeNumChannels: '2'
+            count: 1
+    # Ensure asterisk2 channel and the Local echo channel are bridged on
+    # Asterisk instance 1.
+    -
+        ami-events:
+            id: '0'
+            conditions:
+                match:
+                    Event: 'BridgeEnter'
+                    Channel: '(PJSIP/asterisk2-.*|Local/echo at default-.*)'
+                    BridgeNumChannels: '2'
+                nomatch:
+                    Exten: '101'
+                    ConnectedLineNum: 'alice'
+            count: 1
+    # Hang up the PJSIP channel to tear down all channels and end the test.
+    -
+        ami-events:
+            id: '0'
+            conditions:
+                match:
+                    Event: 'VarSet'
+                    Channel: 'PJSIP/asterisk2-.*'
+                    Variable: 'BRIDGEPEER'
+                    Value: 'Local/echo at default-.*'
+            count: '>0'
+        ami-actions:
+            action:
+                Action: 'Hangup'
+                Channel: '/^PJSIP/asterisk2-.*/'
+
+properties:
+    minversion: '13.2.0'
+    dependencies:
+        - python: 'twisted'
+        - python: 'starpy'
+        - python: 'pjsua'
+        - asterisk: 'res_pjsip'
+        - asterisk: 'res_pjsip_session'
+        - asterisk: 'res_pjsip_refer'
+
diff --git a/tests/channels/pjsip/transfers/attended_transfer/nominal/tests.yaml b/tests/channels/pjsip/transfers/attended_transfer/nominal/tests.yaml
index d46199b..73008fb 100644
--- a/tests/channels/pjsip/transfers/attended_transfer/nominal/tests.yaml
+++ b/tests/channels/pjsip/transfers/attended_transfer/nominal/tests.yaml
@@ -2,6 +2,7 @@
 tests:
     - test: 'caller_local'
     - test: 'callee_local'
+    - test: 'caller_remote'
     - test: 'callee_remote'
     - test: 'caller_local_app'
     - test: 'callee_local_app'

-- 
To view, visit https://gerrit.asterisk.org/565
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I5aaf2b4a72d3334e320c7056a4eed8176b71ce6e
Gerrit-PatchSet: 1
Gerrit-Project: testsuite
Gerrit-Branch: master
Gerrit-Owner: John Bigelow <jbigelow at digium.com>



More information about the asterisk-code-review mailing list