<p>Friendly Automation <strong>submitted</strong> this change.</p><p><a href="https://gerrit.asterisk.org/c/testsuite/+/14384">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Joshua Colp: Looks good to me, but someone else must approve
  George Joseph: Looks good to me, approved
  Friendly Automation: Approved for Submit

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">app_queue: check for leaking Stasis subscriptions after Redirect()<br><br>ASTERISK-28829<br><br>Change-Id: I90451cca574e2fa7d825b4cdc0bab03e331c0e9b<br>---<br>A lib/python/asterisk/taskprocessor_test_condition.py<br>M test-config.yaml<br>A tests/apps/queues/redirect/configs/ast1/extensions.conf<br>A tests/apps/queues/redirect/configs/ast1/pjsip.conf<br>A tests/apps/queues/redirect/configs/ast1/queues.conf<br>A tests/apps/queues/redirect/test-config.yaml<br>M tests/apps/queues/tests.yaml<br>7 files changed, 371 insertions(+), 0 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/lib/python/asterisk/taskprocessor_test_condition.py b/lib/python/asterisk/taskprocessor_test_condition.py</span><br><span>new file mode 100644</span><br><span>index 0000000..889b69b</span><br><span>--- /dev/null</span><br><span>+++ b/lib/python/asterisk/taskprocessor_test_condition.py</span><br><span>@@ -0,0 +1,151 @@</span><br><span style="color: hsl(120, 100%, 40%);">+#!/usr/bin/env python</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+import logging</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+from twisted.internet import defer</span><br><span style="color: hsl(120, 100%, 40%);">+from .test_conditions import TestCondition</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+LOGGER = logging.getLogger(__name__)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+# a list of task processors that should be ignored by this test</span><br><span style="color: hsl(120, 100%, 40%);">+IGNORED_TASKPROCESSORS = [</span><br><span style="color: hsl(120, 100%, 40%);">+    # the outsess traskprocessor will exist until 30 seconds after the call has ended</span><br><span style="color: hsl(120, 100%, 40%);">+    'pjsip/outsess/'</span><br><span style="color: hsl(120, 100%, 40%);">+]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Taskprocessor(object):</span><br><span style="color: hsl(120, 100%, 40%);">+    """A small object that tracks a task processor.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    The object parses a line from the Asterisk CLI command core show taskprocessors to</span><br><span style="color: hsl(120, 100%, 40%);">+    populate its values</span><br><span style="color: hsl(120, 100%, 40%);">+    """</span><br><span style="color: hsl(120, 100%, 40%);">+    processor = ""</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, line):</span><br><span style="color: hsl(120, 100%, 40%);">+        """Constructor</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        Keyword Arguments:</span><br><span style="color: hsl(120, 100%, 40%);">+        line A raw line received from Asterisk that describes a task processor</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        line = line.strip()</span><br><span style="color: hsl(120, 100%, 40%);">+        tokens = line.split()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if len(tokens) == 6:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.processor = tokens[0].strip()</span><br><span style="color: hsl(120, 100%, 40%);">+            self.processed = int(tokens[1])</span><br><span style="color: hsl(120, 100%, 40%);">+            self.in_queue = int(tokens[2])</span><br><span style="color: hsl(120, 100%, 40%);">+            self.max_depth = int(tokens[3])</span><br><span style="color: hsl(120, 100%, 40%);">+            self.low_water = int(tokens[4])</span><br><span style="color: hsl(120, 100%, 40%);">+            self.high_water = int(tokens[5])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class TaskprocessorTestCondition(TestCondition):</span><br><span style="color: hsl(120, 100%, 40%);">+    """Base class for the task processor pre- and post-test</span><br><span style="color: hsl(120, 100%, 40%);">+    checks. This class provides a common mechanism to get the task</span><br><span style="color: hsl(120, 100%, 40%);">+    processors from Asterisk and populate them in a dictionary for</span><br><span style="color: hsl(120, 100%, 40%);">+    tracking</span><br><span style="color: hsl(120, 100%, 40%);">+    """</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, test_config):</span><br><span style="color: hsl(120, 100%, 40%);">+        """Constructor</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        Keyword Arguments:</span><br><span style="color: hsl(120, 100%, 40%);">+        test_config The test config object</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        super(TaskprocessorTestCondition, self).__init__(test_config)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.task_processors = {}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def is_taskprocessor_ignored(self, name):</span><br><span style="color: hsl(120, 100%, 40%);">+        for ignored_taskprocessor in IGNORED_TASKPROCESSORS:</span><br><span style="color: hsl(120, 100%, 40%);">+            if name.startswith(ignored_taskprocessor):</span><br><span style="color: hsl(120, 100%, 40%);">+                return True</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def get_task_processors(self, ast):</span><br><span style="color: hsl(120, 100%, 40%);">+        """Get the task processors from some instance of Asterisk"""</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        def __show_taskprocessors_callback(result):</span><br><span style="color: hsl(120, 100%, 40%);">+            """Callback when CLI command has finished"""</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+            lines = result.output</span><br><span style="color: hsl(120, 100%, 40%);">+            if 'No such command' in lines:</span><br><span style="color: hsl(120, 100%, 40%);">+                return result</span><br><span style="color: hsl(120, 100%, 40%);">+            if 'Unable to connect to remote asterisk' in lines:</span><br><span style="color: hsl(120, 100%, 40%);">+                return result</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+            line_tokens = lines.split('\n')</span><br><span style="color: hsl(120, 100%, 40%);">+            task_processors = []</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+            for line in line_tokens:</span><br><span style="color: hsl(120, 100%, 40%);">+                task_processor = Taskprocessor(line)</span><br><span style="color: hsl(120, 100%, 40%);">+                if task_processor.processor != '' and not self.is_taskprocessor_ignored(task_processor.processor):</span><br><span style="color: hsl(120, 100%, 40%);">+                    LOGGER.debug("Tracking %s", task_processor.processor)</span><br><span style="color: hsl(120, 100%, 40%);">+                    task_processors.append(task_processor)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+            self.task_processors[result.host] = task_processors</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        return ast.cli_exec("core show taskprocessors").addCallback(__show_taskprocessors_callback)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class TaskprocessorPreTestCondition(TaskprocessorTestCondition):</span><br><span style="color: hsl(120, 100%, 40%);">+    """The Task Processor Pre-TestCondition object"""</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def evaluate(self, related_test_condition=None):</span><br><span style="color: hsl(120, 100%, 40%);">+        """Evaluate the test condition"""</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        def __raise_finished(result, finish_deferred):</span><br><span style="color: hsl(120, 100%, 40%);">+            """Called when all CLI commands have finished"""</span><br><span style="color: hsl(120, 100%, 40%);">+            finished_deferred.callback(self)</span><br><span style="color: hsl(120, 100%, 40%);">+            return result</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # Automatically pass the pre-test condition - whatever task processors</span><br><span style="color: hsl(120, 100%, 40%);">+        # are currently open are needed by Asterisk and merely expected to exist</span><br><span style="color: hsl(120, 100%, 40%);">+        # when the test is finished</span><br><span style="color: hsl(120, 100%, 40%);">+        super(TaskprocessorPreTestCondition, self).pass_check()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        finished_deferred = defer.Deferred()</span><br><span style="color: hsl(120, 100%, 40%);">+        exec_list = [super(TaskprocessorPreTestCondition, self).get_task_processors(ast)</span><br><span style="color: hsl(120, 100%, 40%);">+                     for ast in self.ast]</span><br><span style="color: hsl(120, 100%, 40%);">+        defer.DeferredList(exec_list).addCallback(__raise_finished, finished_deferred)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        return finished_deferred</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class TaskprocessorPostTestCondition(TaskprocessorTestCondition):</span><br><span style="color: hsl(120, 100%, 40%);">+    """The Task Processor Post-TestCondition object"""</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def evaluate(self, related_test_condition=None):</span><br><span style="color: hsl(120, 100%, 40%);">+        """Evaluate the test condition"""</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        def __task_processors_obtained(result, finished_deferred):</span><br><span style="color: hsl(120, 100%, 40%);">+            """Callback when all CLI commands have finished"""</span><br><span style="color: hsl(120, 100%, 40%);">+            for ast_host in related_test_condition.task_processors.keys():</span><br><span style="color: hsl(120, 100%, 40%);">+                if not ast_host in self.task_processors:</span><br><span style="color: hsl(120, 100%, 40%);">+                    super(TaskprocessorPostTestCondition, self).fail_check(</span><br><span style="color: hsl(120, 100%, 40%);">+                        "Asterisk host in pre-test check [%s]"</span><br><span style="color: hsl(120, 100%, 40%);">+                        " not found in post-test check" % ast_host)</span><br><span style="color: hsl(120, 100%, 40%);">+                else:</span><br><span style="color: hsl(120, 100%, 40%);">+                    # Find all task processors in post-check not in pre-check</span><br><span style="color: hsl(120, 100%, 40%);">+                    for task_processor in self.task_processors[ast_host]:</span><br><span style="color: hsl(120, 100%, 40%);">+                        if (len([</span><br><span style="color: hsl(120, 100%, 40%);">+                                t for t</span><br><span style="color: hsl(120, 100%, 40%);">+                                in related_test_condition.task_processors[ast_host]</span><br><span style="color: hsl(120, 100%, 40%);">+                                if task_processor.processor == t.processor]) == 0):</span><br><span style="color: hsl(120, 100%, 40%);">+                            super(TaskprocessorPostTestCondition, self).fail_check(</span><br><span style="color: hsl(120, 100%, 40%);">+                                "Failed to find task processor %s in "</span><br><span style="color: hsl(120, 100%, 40%);">+                                "pre-test check" % (task_processor.processor))</span><br><span style="color: hsl(120, 100%, 40%);">+            super(TaskprocessorPostTestCondition, self).pass_check()</span><br><span style="color: hsl(120, 100%, 40%);">+            finished_deferred.callback(self)</span><br><span style="color: hsl(120, 100%, 40%);">+            return result</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if related_test_condition is None:</span><br><span style="color: hsl(120, 100%, 40%);">+            msg = "No pre-test condition object provided"</span><br><span style="color: hsl(120, 100%, 40%);">+            super(TaskprocessorPostTestCondition, self).fail_check(msg)</span><br><span style="color: hsl(120, 100%, 40%);">+            return</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        finished_deferred = defer.Deferred()</span><br><span style="color: hsl(120, 100%, 40%);">+        exec_list = [super(TaskprocessorPostTestCondition, self).get_task_processors(ast)</span><br><span style="color: hsl(120, 100%, 40%);">+                     for ast in self.ast]</span><br><span style="color: hsl(120, 100%, 40%);">+        defer.DeferredList(exec_list).addCallback(__task_processors_obtained, finished_deferred)</span><br><span style="color: hsl(120, 100%, 40%);">+        return finished_deferred</span><br><span>diff --git a/test-config.yaml b/test-config.yaml</span><br><span>index 2e03fb3..9b6d147 100644</span><br><span>--- a/test-config.yaml</span><br><span>+++ b/test-config.yaml</span><br><span>@@ -58,6 +58,13 @@</span><br><span>                 post:</span><br><span>                     typename: 'memory_test_condition.MemoryPostTestCondition'</span><br><span>                     related-type: 'memory_test_condition.MemoryPreTestCondition'</span><br><span style="color: hsl(120, 100%, 40%);">+            -</span><br><span style="color: hsl(120, 100%, 40%);">+                name: 'taskprocessors'</span><br><span style="color: hsl(120, 100%, 40%);">+                pre:</span><br><span style="color: hsl(120, 100%, 40%);">+                    typename: 'taskprocessor_test_condition.TaskprocessorPreTestCondition'</span><br><span style="color: hsl(120, 100%, 40%);">+                post:</span><br><span style="color: hsl(120, 100%, 40%);">+                    typename: 'taskprocessor_test_condition.TaskprocessorPostTestCondition'</span><br><span style="color: hsl(120, 100%, 40%);">+                    related-type: 'taskprocessor_test_condition.TaskprocessorPreTestCondition'</span><br><span> </span><br><span> # Exclude all long-running tests (greater than one minute)</span><br><span> config-fast:</span><br><span>diff --git a/tests/apps/queues/redirect/configs/ast1/extensions.conf b/tests/apps/queues/redirect/configs/ast1/extensions.conf</span><br><span>new file mode 100644</span><br><span>index 0000000..ae535bf</span><br><span>--- /dev/null</span><br><span>+++ b/tests/apps/queues/redirect/configs/ast1/extensions.conf</span><br><span>@@ -0,0 +1,13 @@</span><br><span style="color: hsl(120, 100%, 40%);">+[default]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+exten => 101,1,Queue(queue,Rtc)</span><br><span style="color: hsl(120, 100%, 40%);">+exten => 102,1,Wait(5)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+[park]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+exten => s,1,Wait(5)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+[dialbob]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+exten => s,1,Dial(PJSIP/bob)</span><br><span style="color: hsl(120, 100%, 40%);">+    same => Hangup()</span><br><span>diff --git a/tests/apps/queues/redirect/configs/ast1/pjsip.conf b/tests/apps/queues/redirect/configs/ast1/pjsip.conf</span><br><span>new file mode 100644</span><br><span>index 0000000..c4add2f</span><br><span>--- /dev/null</span><br><span>+++ b/tests/apps/queues/redirect/configs/ast1/pjsip.conf</span><br><span>@@ -0,0 +1,33 @@</span><br><span style="color: hsl(120, 100%, 40%);">+[global]</span><br><span style="color: hsl(120, 100%, 40%);">+type=global</span><br><span style="color: hsl(120, 100%, 40%);">+debug=yes</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+[local]</span><br><span style="color: hsl(120, 100%, 40%);">+type=transport</span><br><span style="color: hsl(120, 100%, 40%);">+protocol=udp</span><br><span style="color: hsl(120, 100%, 40%);">+bind=127.0.0.1:5060</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+[endpoint_t](!)</span><br><span style="color: hsl(120, 100%, 40%);">+type=endpoint</span><br><span style="color: hsl(120, 100%, 40%);">+context=default</span><br><span style="color: hsl(120, 100%, 40%);">+direct_media=no</span><br><span style="color: hsl(120, 100%, 40%);">+disallow=all</span><br><span style="color: hsl(120, 100%, 40%);">+allow=ulaw</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+[aor_t](!)</span><br><span style="color: hsl(120, 100%, 40%);">+type=aor</span><br><span style="color: hsl(120, 100%, 40%);">+max_contacts=1</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+[alice](aor_t)</span><br><span style="color: hsl(120, 100%, 40%);">+[alice](endpoint_t)</span><br><span style="color: hsl(120, 100%, 40%);">+aors=alice</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+[bob](aor_t)</span><br><span style="color: hsl(120, 100%, 40%);">+[bob](endpoint_t)</span><br><span style="color: hsl(120, 100%, 40%);">+aors=bob</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+[carol](aor_t)</span><br><span style="color: hsl(120, 100%, 40%);">+[carol](endpoint_t)</span><br><span style="color: hsl(120, 100%, 40%);">+aors=carol</span><br><span>diff --git a/tests/apps/queues/redirect/configs/ast1/queues.conf b/tests/apps/queues/redirect/configs/ast1/queues.conf</span><br><span>new file mode 100644</span><br><span>index 0000000..cac3974</span><br><span>--- /dev/null</span><br><span>+++ b/tests/apps/queues/redirect/configs/ast1/queues.conf</span><br><span>@@ -0,0 +1,4 @@</span><br><span style="color: hsl(120, 100%, 40%);">+[general]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+[queue]</span><br><span style="color: hsl(120, 100%, 40%);">+member => Local/s@dialbob/n</span><br><span>diff --git a/tests/apps/queues/redirect/test-config.yaml b/tests/apps/queues/redirect/test-config.yaml</span><br><span>new file mode 100644</span><br><span>index 0000000..510dcc2</span><br><span>--- /dev/null</span><br><span>+++ b/tests/apps/queues/redirect/test-config.yaml</span><br><span>@@ -0,0 +1,162 @@</span><br><span style="color: hsl(120, 100%, 40%);">+testinfo:</span><br><span style="color: hsl(120, 100%, 40%);">+    summary: "Redirected queue call"</span><br><span style="color: hsl(120, 100%, 40%);">+    description: |</span><br><span style="color: hsl(120, 100%, 40%);">+        "We have Alice call the queue, which has Local/dialbob as member. Bob will</span><br><span style="color: hsl(120, 100%, 40%);">+        be called through this local channel. Upon answering, we'll have Carol call</span><br><span style="color: hsl(120, 100%, 40%);">+        the dialplan and Wait() there for further instructions.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        After Carol's call is setup, we Redirect() Alice's call to the dialplan,</span><br><span style="color: hsl(120, 100%, 40%);">+        which will break her call/bridge with Bob. We then use Bridge() to bridge</span><br><span style="color: hsl(120, 100%, 40%);">+        Alice's call with Carol's call that was still waiting in the dialplan.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        After this scenario succeeded, there should be no leaked Stasis subscriptions,</span><br><span style="color: hsl(120, 100%, 40%);">+        which we check by comparing the list of taskprocessors at the start of the test</span><br><span style="color: hsl(120, 100%, 40%);">+        with the list at the end of the test. ASTERISK-28829 will cause two subscriptions/</span><br><span style="color: hsl(120, 100%, 40%);">+        task processors to leak: stasis/p:bridge:all and stasis/p:channel:all"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+test-modules:</span><br><span style="color: hsl(120, 100%, 40%);">+    test-object:</span><br><span style="color: hsl(120, 100%, 40%);">+        config-section: test-object-config</span><br><span style="color: hsl(120, 100%, 40%);">+        typename: 'test_case.TestCaseModule'</span><br><span style="color: hsl(120, 100%, 40%);">+    modules:</span><br><span style="color: hsl(120, 100%, 40%);">+        -</span><br><span style="color: hsl(120, 100%, 40%);">+            config-section: pjsua-config</span><br><span style="color: hsl(120, 100%, 40%);">+            typename: 'phones.PjsuaPhoneController'</span><br><span style="color: hsl(120, 100%, 40%);">+        -</span><br><span style="color: hsl(120, 100%, 40%);">+            config-section: pluggable-config</span><br><span style="color: hsl(120, 100%, 40%);">+            typename: 'pluggable_modules.EventActionModule'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+test-object-config:</span><br><span style="color: hsl(120, 100%, 40%);">+    connect-ami: True</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+pjsua-config:</span><br><span style="color: hsl(120, 100%, 40%);">+    transports:</span><br><span style="color: hsl(120, 100%, 40%);">+        -</span><br><span style="color: hsl(120, 100%, 40%);">+            name: 'local-ipv4-1'</span><br><span style="color: hsl(120, 100%, 40%);">+            bind: '127.0.0.1'</span><br><span style="color: hsl(120, 100%, 40%);">+            bindport: '5061'</span><br><span style="color: hsl(120, 100%, 40%);">+        -</span><br><span style="color: hsl(120, 100%, 40%);">+            name: 'local-ipv4-2'</span><br><span style="color: hsl(120, 100%, 40%);">+            bind: '127.0.0.1'</span><br><span style="color: hsl(120, 100%, 40%);">+            bindport: '5062'</span><br><span style="color: hsl(120, 100%, 40%);">+        -</span><br><span style="color: hsl(120, 100%, 40%);">+            name: 'local-ipv4-3'</span><br><span style="color: hsl(120, 100%, 40%);">+            bind: '127.0.0.1'</span><br><span style="color: hsl(120, 100%, 40%);">+            bindport: '5063'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    accounts:</span><br><span style="color: hsl(120, 100%, 40%);">+        -</span><br><span style="color: hsl(120, 100%, 40%);">+            name: 'alice'</span><br><span style="color: hsl(120, 100%, 40%);">+            username: 'alice'</span><br><span style="color: hsl(120, 100%, 40%);">+            domain: '127.0.0.1'</span><br><span style="color: hsl(120, 100%, 40%);">+            transport: 'local-ipv4-1'</span><br><span style="color: hsl(120, 100%, 40%);">+        -</span><br><span style="color: hsl(120, 100%, 40%);">+            name: 'bob'</span><br><span style="color: hsl(120, 100%, 40%);">+            username: 'bob'</span><br><span style="color: hsl(120, 100%, 40%);">+            domain: '127.0.0.1'</span><br><span style="color: hsl(120, 100%, 40%);">+            transport: 'local-ipv4-2'</span><br><span style="color: hsl(120, 100%, 40%);">+        -</span><br><span style="color: hsl(120, 100%, 40%);">+            name: 'carol'</span><br><span style="color: hsl(120, 100%, 40%);">+            username: 'carol'</span><br><span style="color: hsl(120, 100%, 40%);">+            domain: '127.0.0.1'</span><br><span style="color: hsl(120, 100%, 40%);">+            transport: 'local-ipv4-3'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+pluggable-config:</span><br><span style="color: hsl(120, 100%, 40%);">+    -</span><br><span style="color: hsl(120, 100%, 40%);">+        ami-events:</span><br><span style="color: hsl(120, 100%, 40%);">+            conditions:</span><br><span style="color: hsl(120, 100%, 40%);">+                match:</span><br><span style="color: hsl(120, 100%, 40%);">+                    Event: 'UserEvent'</span><br><span style="color: hsl(120, 100%, 40%);">+                    UserEvent: 'PJsuaPhonesReady'</span><br><span style="color: hsl(120, 100%, 40%);">+            count: 1</span><br><span style="color: hsl(120, 100%, 40%);">+        # alice dials the queue</span><br><span style="color: hsl(120, 100%, 40%);">+        pjsua_phone:</span><br><span style="color: hsl(120, 100%, 40%);">+            action: 'call'</span><br><span style="color: hsl(120, 100%, 40%);">+            pjsua_account: 'alice'</span><br><span style="color: hsl(120, 100%, 40%);">+            call_uri: 'sip:101@127.0.0.1'</span><br><span style="color: hsl(120, 100%, 40%);">+    -</span><br><span style="color: hsl(120, 100%, 40%);">+        # queue rings bob, bob answers, alice is bridged with bob</span><br><span style="color: hsl(120, 100%, 40%);">+        ami-events:</span><br><span style="color: hsl(120, 100%, 40%);">+            conditions:</span><br><span style="color: hsl(120, 100%, 40%);">+                match:</span><br><span style="color: hsl(120, 100%, 40%);">+                    Event: 'BridgeEnter'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Channel: 'PJSIP/alice-00000000'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Context: 'default'</span><br><span style="color: hsl(120, 100%, 40%);">+            count: 1</span><br><span style="color: hsl(120, 100%, 40%);">+        # then carol starts a call that ends in the dialplan Wait()</span><br><span style="color: hsl(120, 100%, 40%);">+        pjsua_phone:</span><br><span style="color: hsl(120, 100%, 40%);">+            action: 'call'</span><br><span style="color: hsl(120, 100%, 40%);">+            pjsua_account: 'carol'</span><br><span style="color: hsl(120, 100%, 40%);">+            call_uri: 'sip:102@127.0.0.1'</span><br><span style="color: hsl(120, 100%, 40%);">+    -</span><br><span style="color: hsl(120, 100%, 40%);">+        # when we detect carol's call..</span><br><span style="color: hsl(120, 100%, 40%);">+        ami-events:</span><br><span style="color: hsl(120, 100%, 40%);">+            conditions:</span><br><span style="color: hsl(120, 100%, 40%);">+                match:</span><br><span style="color: hsl(120, 100%, 40%);">+                    Event: 'Newexten'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Channel: 'PJSIP/carol-00000002'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Exten: '102'</span><br><span style="color: hsl(120, 100%, 40%);">+            count: 1</span><br><span style="color: hsl(120, 100%, 40%);">+        # .. we redirect alice to a park extension (this will halt her call with bob)</span><br><span style="color: hsl(120, 100%, 40%);">+        ami-actions:</span><br><span style="color: hsl(120, 100%, 40%);">+            -</span><br><span style="color: hsl(120, 100%, 40%);">+                action:</span><br><span style="color: hsl(120, 100%, 40%);">+                    Action: 'Redirect'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Channel: 'PJSIP/alice-00000000'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Context: 'park'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Exten: 's'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Priority: '1'</span><br><span style="color: hsl(120, 100%, 40%);">+    -</span><br><span style="color: hsl(120, 100%, 40%);">+        # wait for alice to be parked..</span><br><span style="color: hsl(120, 100%, 40%);">+        ami-events:</span><br><span style="color: hsl(120, 100%, 40%);">+            conditions:</span><br><span style="color: hsl(120, 100%, 40%);">+                match:</span><br><span style="color: hsl(120, 100%, 40%);">+                    Event: 'Newexten'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Channel: 'PJSIP/alice-00000000'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Context: 'park'</span><br><span style="color: hsl(120, 100%, 40%);">+            count: 1</span><br><span style="color: hsl(120, 100%, 40%);">+        # .. then bridge alice with carol</span><br><span style="color: hsl(120, 100%, 40%);">+        ami-actions:</span><br><span style="color: hsl(120, 100%, 40%);">+            action:</span><br><span style="color: hsl(120, 100%, 40%);">+                Action: 'Bridge'</span><br><span style="color: hsl(120, 100%, 40%);">+                Channel1: 'PJSIP/alice-00000000'</span><br><span style="color: hsl(120, 100%, 40%);">+                Channel2: 'PJSIP/carol-00000002'</span><br><span style="color: hsl(120, 100%, 40%);">+    # once alice has entered the bridge with carol, we're done</span><br><span style="color: hsl(120, 100%, 40%);">+    -</span><br><span style="color: hsl(120, 100%, 40%);">+        ami-events:</span><br><span style="color: hsl(120, 100%, 40%);">+            conditions:</span><br><span style="color: hsl(120, 100%, 40%);">+                match:</span><br><span style="color: hsl(120, 100%, 40%);">+                    Event: 'BridgeEnter'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Channel: 'PJSIP/carol-00000002'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Context: 'default'</span><br><span style="color: hsl(120, 100%, 40%);">+            count: 1</span><br><span style="color: hsl(120, 100%, 40%);">+        ami-actions:</span><br><span style="color: hsl(120, 100%, 40%);">+            action:</span><br><span style="color: hsl(120, 100%, 40%);">+                Action: 'Hangup'</span><br><span style="color: hsl(120, 100%, 40%);">+                Channel: 'PJSIP/alice-00000000'</span><br><span style="color: hsl(120, 100%, 40%);">+    -</span><br><span style="color: hsl(120, 100%, 40%);">+        ami-events:</span><br><span style="color: hsl(120, 100%, 40%);">+            conditions:</span><br><span style="color: hsl(120, 100%, 40%);">+                match:</span><br><span style="color: hsl(120, 100%, 40%);">+                    Event: 'Hangup'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Channel: 'PJSIP/carol-00000002'</span><br><span style="color: hsl(120, 100%, 40%);">+                    Context: 'default'</span><br><span style="color: hsl(120, 100%, 40%);">+            count: 1</span><br><span style="color: hsl(120, 100%, 40%);">+        stop_test:</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+properties:</span><br><span style="color: hsl(120, 100%, 40%);">+    minversion: '13.6.0'</span><br><span style="color: hsl(120, 100%, 40%);">+    dependencies:</span><br><span style="color: hsl(120, 100%, 40%);">+        - python : twisted</span><br><span style="color: hsl(120, 100%, 40%);">+        - python : starpy</span><br><span style="color: hsl(120, 100%, 40%);">+        - python : pjsua</span><br><span style="color: hsl(120, 100%, 40%);">+        - asterisk : res_pjsip</span><br><span style="color: hsl(120, 100%, 40%);">+        - asterisk : app_queue</span><br><span style="color: hsl(120, 100%, 40%);">+    tags:</span><br><span style="color: hsl(120, 100%, 40%);">+        - pjsip</span><br><span style="color: hsl(120, 100%, 40%);">+        - queues</span><br><span style="color: hsl(120, 100%, 40%);">+        - chan_local</span><br><span style="color: hsl(120, 100%, 40%);">+    testconditions:</span><br><span style="color: hsl(120, 100%, 40%);">+        -</span><br><span style="color: hsl(120, 100%, 40%);">+            # this will check for leaking stasis subscriptions/taskprocessors at the end of the testrun</span><br><span style="color: hsl(120, 100%, 40%);">+            name: 'taskprocessors'</span><br><span>diff --git a/tests/apps/queues/tests.yaml b/tests/apps/queues/tests.yaml</span><br><span>index f618ee2..7e364e7 100644</span><br><span>--- a/tests/apps/queues/tests.yaml</span><br><span>+++ b/tests/apps/queues/tests.yaml</span><br><span>@@ -15,3 +15,4 @@</span><br><span>     - test: 'wrapup_time'</span><br><span>     - test: 'reason_pause_ami'</span><br><span>     - test: 'queue_member_forward'</span><br><span style="color: hsl(120, 100%, 40%);">+    - test: 'redirect'</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/c/testsuite/+/14384">change 14384</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/c/testsuite/+/14384"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: testsuite </div>
<div style="display:none"> Gerrit-Branch: 13 </div>
<div style="display:none"> Gerrit-Change-Id: I90451cca574e2fa7d825b4cdc0bab03e331c0e9b </div>
<div style="display:none"> Gerrit-Change-Number: 14384 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Friendly Automation </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@sangoma.com> </div>
<div style="display:none"> Gerrit-Reviewer: lvl <digium@lvlconsultancy.nl> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>