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