[asterisk-commits] mjordan: testsuite/asterisk/trunk r5541 - in /asterisk/trunk/tests/cdr: ./ cd...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri Sep 5 17:31:17 CDT 2014


Author: mjordan
Date: Fri Sep  5 17:31:14 2014
New Revision: 5541

URL: http://svnview.digium.com/svn/testsuite?view=rev&rev=5541
Log:
tests/cdr: Add a multi-party bridge test for CDRs

This patch adds a test for the testsuite that covers CDR generation in
multi-party bridge scenarios. It does the following:
* Makes 5 Local channels, and adds them to a multi-party bridge via ARI.
* It then takes channels 0 and 3 and removes from the bridge
* It then re-adds channels 0 and 3
* Upon having all 5 channels back in the multi-party bridge, it removes them
  all, hangs them up, and deletes the bridge.

The test verifies that the expected CDRs are generated.

Review: https://reviewboard.asterisk.org/r/3965/

ASTERISK-24241

Added:
    asterisk/trunk/tests/cdr/cdr_bridge_multi/
    asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py   (with props)
    asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/
    asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/
    asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf   (with props)
    asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml   (with props)
Modified:
    asterisk/trunk/tests/cdr/tests.yaml

Added: asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py?view=auto&rev=5541
==============================================================================
--- asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py (added)
+++ asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py Fri Sep  5 17:31:14 2014
@@ -1,0 +1,219 @@
+"""
+Test CDRs in a multi-party bridge
+
+Copyright (C) 2014, Digium, Inc.
+Matt Jordan <mjordan at digium.com>
+
+This program is free software, distributed under the terms of
+the GNU General Public License Version 2.
+"""
+
+import logging
+import re
+
+LOGGER = logging.getLogger(__name__)
+
+TEST_STATE = None
+
+# Yay for human sorting:
+# http://stackoverflow.com/questions/5967500/how-to-correctly-sort-a-string-with-a-number-inside
+def atoi(text):
+    return int(text) if text.isdigit() else text
+
+def natural_keys(text):
+    return [atoi(c) for c in re.split('(\d+)', text)]
+
+class TestState(object):
+    """Base class for the states the test transitions through"""
+
+    def __init__(self, next_state=None):
+        """Constructor
+
+        Keyword Arguments:
+        next_state Class that we should transition to next
+        """
+        self.next_state = next_state
+        self.ari = None
+
+    def _set_ari(self, ari):
+        """Set the ARI object on ourselves"""
+        if not self.ari:
+            self.ari = ari
+
+    def handle_start(self, ari, event, test_object):
+        """Handle a StasisStart event
+
+        Keyword Arguments:
+        ari         Our ARI connection back to Asterisk
+        event       The StasisStart event
+        test_obejct The one and only test object
+        """
+        self._set_ari(ari)
+        return
+
+    def handle_enter(self, ari, event, test_object):
+        """Handle a ChannelEnteredBridge event
+
+        Keyword Arguments:
+        ari         Our ARI connection back to Asterisk
+        event       The ChannelEnteredBridge event
+        test_obejct The one and only test object
+        """
+        self._set_ari(ari)
+        return
+
+    def handle_leave(self, ari, event, test_object):
+        """Handle a ChannelLeftBridge event
+
+        Keyword Arguments:
+        ari         Our ARI connection back to Asterisk
+        event       The ChannelLeftBridge event
+        test_obejct The one and only test object
+        """
+        self._set_ari(ari)
+        return
+
+    def change_state(self, test_object):
+        """Change the test state to the next object
+
+        A concrete implementation should call this when done.
+        This will transition to the next state, or end the test
+        """
+        if not self.next_state:
+            test_object.stop_reactor()
+        else:
+            global TEST_STATE
+            TEST_STATE = self.next_state(ari=self.ari)
+
+class ChannelStartState(TestState):
+    """State that manages getting the channels into the bridge.
+
+    Once all channels are in the bridge, it hands control over
+    to ChannelsInBridge
+    """
+
+    def __init__(self):
+        """Constructor"""
+        super(ChannelStartState, self).__init__(ChannelsInBridge)
+        LOGGER.debug('Test in ChannelStartState')
+
+        self.channels = []
+        self.channels_entered = 0
+
+    def handle_start(self, ari, event, test_object):
+        """Handler for StasisStart"""
+        super(ChannelStartState, self).handle_start(ari, event, test_object)
+
+        self.channels.append(event['channel']['id'])
+        if len(self.channels) < 5:
+            return
+
+        self.channels.sort(key=natural_keys)
+
+        # Make the bridge and put our channels into it. Note that we place
+        # the channels into the bridge in a somewhat haphazard order for "fun",
+        # if we don't make pairings correctly even when channels enter the bridge
+        # in a goofy way, our CDRs will be wrong!
+        ari.post('bridges', bridgeId='cdr_bridge')
+        for ind in [0, 2, 4, 3, 1]:
+            LOGGER.debug('Adding channel %s to bridge' % self.channels[ind])
+            ari.post('bridges', 'cdr_bridge', 'addChannel', channel=self.channels[ind])
+
+    def handle_enter(self, ari, event, test_object):
+        """Handler for ChannelEnteredBridge"""
+        super(ChannelStartState, self).handle_enter(ari, event, test_object)
+        self.channels_entered += 1
+        if (self.channels_entered == 5):
+            self.change_state(test_object)
+
+class ChannelsInBridge(TestState):
+    """State that handles once all the channels are in the bridge.
+
+    This state removes channel 0 and channel 3. Once removed, it
+    puts them back in. Once back in, it transitions to the next
+    state, ChannelsLeaveBridge
+    """
+
+    def __init__(self, ari):
+        """Constructor"""
+        super(ChannelsInBridge, self).__init__(ChannelsLeaveBridge)
+        LOGGER.debug('Test in ChannelsInBridge')
+
+        self.ari = ari
+        self.channels_left = 0
+        self.channels_entered = 0
+        self.channels = self.ari.get('bridges', 'cdr_bridge').json().get('channels')
+        self.channels.sort(key=natural_keys)
+
+        LOGGER.debug('Removing channels %s and %s from bridge' % (self.channels[0], self.channels[3]))
+        self.ari.post('bridges', 'cdr_bridge', 'removeChannel', channel=self.channels[0])
+        self.ari.post('bridges', 'cdr_bridge', 'removeChannel', channel=self.channels[3])
+
+    def handle_leave(self, ari, event, test_object):
+        """Handler for ChannelLeftBridge"""
+        super(ChannelsInBridge, self).handle_leave(ari, event, test_object)
+
+        self.channels_left += 1
+        if (self.channels_left == 2):
+            LOGGER.debug('Adding channels %s and %s to bridge' % (self.channels[3], self.channels[0]))
+            self.ari.post('bridges', 'cdr_bridge', 'addChannel', channel=self.channels[3])
+            self.ari.post('bridges', 'cdr_bridge', 'addChannel', channel=self.channels[0])
+
+    def handle_enter(self, ari, event, test_object):
+        """Handler for ChannelEnteredBridge"""
+        super(ChannelsInBridge, self).handle_enter(ari, event, test_object)
+        self.channels_entered += 1
+        if (self.channels_entered == 2):
+            self.change_state(test_object)
+
+class ChannelsLeaveBridge(TestState):
+    """State that handles all channels leaving the bridge
+
+    This state boots everyone out of the bridge. Once all are
+    out, it hangs them up in order and deletes the bridge. It
+    the ends the test by transitioning to the next state (None)
+    """
+
+    def __init__(self, ari):
+        """Constructor"""
+        super(ChannelsLeaveBridge, self).__init__(None)
+        LOGGER.debug('Test in ChannelsLeaveBridge')
+
+        self.ari = ari
+        self.channels = self.ari.get('bridges', 'cdr_bridge').json().get('channels')
+        self.channels.sort(key=natural_keys)
+        self.left_channels = 0
+
+        for channel in self.channels:
+            LOGGER.debug('Removing channel %s from bridge' % channel)
+            self.ari.post('bridges', 'cdr_bridge', 'removeChannel', channel=channel)
+
+    def handle_leave(self, ari, event, test_object):
+        """Handler for ChannelLeftBridge"""
+        super(ChannelsLeaveBridge, self).handle_leave(ari, event, test_object)
+
+        self.left_channels += 1
+        if (self.left_channels) == 5:
+            for chan in self.channels:
+                LOGGER.debug('Hanging up channel %s' % chan)
+                ari.delete('channels', chan)
+            ari.delete('bridges', 'cdr_bridge')
+            self.change_state(test_object)
+
+TEST_STATE = ChannelStartState()
+
+def on_start(ari, event, test_object):
+    """Handle the StasisStart event"""
+    TEST_STATE.handle_start(ari, event, test_object)
+    return True
+
+def on_enter(ari, event, test_object):
+    """Handle the ChannelEnteredBridge event"""
+    TEST_STATE.handle_enter(ari, event, test_object)
+    return True
+
+def on_leave(ari, event, test_object):
+    """Handle the ChannelLeftBridge event"""
+    TEST_STATE.handle_leave(ari, event, test_object)
+    return True
+

Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf?view=auto&rev=5541
==============================================================================
--- asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf (added)
+++ asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf Fri Sep  5 17:31:14 2014
@@ -1,0 +1,10 @@
+[default]
+
+exten => echo,1,NoOp()
+ same => n,Set(CDR_PROP(disable)=True)
+ same => n,Echo()
+
+exten => s,1,NoOp()
+ same => n,Answer()
+ same => n,Stasis(testsuite)
+ same => n,Hangup()

Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml?view=auto&rev=5541
==============================================================================
--- asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml (added)
+++ asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml Fri Sep  5 17:31:14 2014
@@ -1,0 +1,142 @@
+testinfo:
+    summary: Test CDRs generated in a multi-party bridge.
+    description: |
+        Five Local channels are put into a multi-party bridge. The oldest
+        channel (0) and the second to newest (3) are removed from the
+        bridge, then re-added. The test verifies that all expected CDRs
+        are generated between the pairs of channels - both for the original
+        matchings as well as new matchings for channels 0 and 3.
+
+test-modules:
+    add-test-to-search-path: True
+    test-object:
+        config-section: test-object-config
+        typename: ari.AriTestObject
+    modules:
+        -
+            config-section: ari-config
+            typename: ari.WebSocketEventModule
+        -
+            config-section: 'cdr-config'
+            typename: 'cdr.CDRModule'
+
+
+test-object-config:
+    test-iterations:
+        -
+            - {'channel': 'Local/s at default', 'context': 'default', 'exten': 'echo', 'priority': '1', }
+            - {'channel': 'Local/s at default', 'context': 'default', 'exten': 'echo', 'priority': '1', }
+            - {'channel': 'Local/s at default', 'context': 'default', 'exten': 'echo', 'priority': '1', }
+            - {'channel': 'Local/s at default', 'context': 'default', 'exten': 'echo', 'priority': '1', }
+            - {'channel': 'Local/s at default', 'context': 'default', 'exten': 'echo', 'priority': '1', }
+
+ari-config:
+    apps: testsuite
+    events:
+        -   conditions:
+                match:
+                    type: StasisStart
+                    application: testsuite
+                    args: []
+            callback:
+                module: cdr_bridge_multi
+                method: on_start
+        -   conditions:
+                match:
+                    type: ChannelEnteredBridge
+                    application: testsuite
+            callback:
+                module: cdr_bridge_multi
+                method: on_enter
+        -   conditions:
+                match:
+                    type: ChannelLeftBridge
+                    application: testsuite
+            callback:
+                module: cdr_bridge_multi
+                method: on_leave
+
+cdr-config:
+    -
+        file: 'Master'
+        lines:
+            # Channel 0 should have 4 matchings initially
+            -
+                channel: 'Local/s at default-.{7}0;2'
+                dchannel: 'Local/s at default-.{7}2;2'
+            -
+                channel: 'Local/s at default-.{7}0;2'
+                dchannel: 'Local/s at default-.{7}4;2'
+            -
+                channel: 'Local/s at default-.{7}0;2'
+                dchannel: 'Local/s at default-.{7}3;2'
+            -
+                channel: 'Local/s at default-.{7}0;2'
+                dchannel: 'Local/s at default-.{7}1;2'
+            # Channel 0 should have 4 matchings again when it re-joins
+            -
+                channel: 'Local/s at default-.{7}0;2'
+                dchannel: 'Local/s at default-.{7}2;2'
+            -
+                channel: 'Local/s at default-.{7}0;2'
+                dchannel: 'Local/s at default-.{7}4;2'
+            -
+                channel: 'Local/s at default-.{7}0;2'
+                dchannel: 'Local/s at default-.{7}3;2'
+            -
+                channel: 'Local/s at default-.{7}0;2'
+                dchannel: 'Local/s at default-.{7}1;2'
+            # Channel 1 should have 4 matchings: 3 initially, and
+            # one additional when channel 3 re-joins
+            -
+                channel: 'Local/s at default-.{7}1;2'
+                dchannel: 'Local/s at default-.{7}2;2'
+            -
+                channel: 'Local/s at default-.{7}1;2'
+                dchannel: 'Local/s at default-.{7}4;2'
+            -
+                channel: 'Local/s at default-.{7}1;2'
+                dchannel: 'Local/s at default-.{7}3;2'
+            -
+                channel: 'Local/s at default-.{7}1;2'
+                dchannel: 'Local/s at default-.{7}3;2'
+            # Channel 2 should have 3 matchings: 2 initially, and
+            # one additional when channel 3 re-joins
+            -
+                channel: 'Local/s at default-.{7}2;2'
+                dchannel: 'Local/s at default-.{7}4;2'
+            -
+                channel: 'Local/s at default-.{7}2;2'
+                dchannel: 'Local/s at default-.{7}3;2'
+            -
+                channel: 'Local/s at default-.{7}2;2'
+                dchannel: 'Local/s at default-.{7}3;2'
+            # Channel 3 should have 2 matchings: 1 initial matching
+            # and one additional when it re-joins
+            -
+                channel: 'Local/s at default-.{7}3;2'
+                dchannel: 'Local/s at default-.{7}4;2'
+            -
+                channel: 'Local/s at default-.{7}3;2'
+                dchannel: 'Local/s at default-.{7}4;2'
+            # And channel 4 is all by itself (the CDR it has is
+            # from its dialplan execution prior to joining the
+            # bridge)
+            -
+                channel: 'Local/s at default-.{7}4;2'
+                dchannel: ''
+
+properties:
+    minversion: '12.6.0'
+    dependencies:
+        - python: autobahn.websocket
+        - python: requests
+        - python: twisted
+        - python: starpy
+        - asterisk: res_ari_channels
+        - asterisk: res_ari_bridges
+        - asterisk: app_stasis
+        - asterisk: app_echo
+    tags:
+        - ARI
+        - CDR

Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: asterisk/trunk/tests/cdr/tests.yaml
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/cdr/tests.yaml?view=diff&rev=5541&r1=5540&r2=5541
==============================================================================
--- asterisk/trunk/tests/cdr/tests.yaml (original)
+++ asterisk/trunk/tests/cdr/tests.yaml Fri Sep  5 17:31:14 2014
@@ -4,6 +4,7 @@
     - test: 'console_dial_sip_busy'
     - test: 'console_dial_sip_congestion'
     - test: 'cdr_originate_sip_congestion_log'
+    - test: 'cdr_bridge_multi'
     - test: 'console_dial_sip_transfer'
     - test: 'cdr_unanswered_yes'
     # Temporarily disabled while failures are debugged




More information about the asterisk-commits mailing list