[asterisk-commits] dlee: branch dlee/matts-ari-patch r4053 - in /asterisk/team/dlee/matts-ari-pa...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Mon Aug 19 14:48:29 CDT 2013


Author: dlee
Date: Mon Aug 19 14:48:27 2013
New Revision: 4053

URL: http://svnview.digium.com/svn/testsuite?view=rev&rev=4053
Log:
mjordan's ARI test object patch

Added:
    asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/configs/ast1/ari.conf
      - copied unchanged from r4007, asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/ari.conf
Modified:
    asterisk/team/dlee/matts-ari-patch/addons/Makefile
    asterisk/team/dlee/matts-ari-patch/lib/python/asterisk/ari.py
    asterisk/team/dlee/matts-ari-patch/sample-yaml/ari-config.yaml.sample
    asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/configs/ast1/extensions.conf
    asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/rest_continue.py
    asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/test-config.yaml

Modified: asterisk/team/dlee/matts-ari-patch/addons/Makefile
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/matts-ari-patch/addons/Makefile?view=diff&rev=4053&r1=4052&r2=4053
==============================================================================
--- asterisk/team/dlee/matts-ari-patch/addons/Makefile (original)
+++ asterisk/team/dlee/matts-ari-patch/addons/Makefile Mon Aug 19 14:48:27 2013
@@ -6,12 +6,7 @@
 # the GNU General Public License Version 2.
 #
 
-all:
-	@if [ ! -d starpy ]; then \
-		echo "starpy/ does not exist.  Run \`make update\` to checkout via git."; \
-	else \
-		echo "Run \`make install\`."; \
-	fi
+all: starpy-update
 
 clean: _clean
 
@@ -19,16 +14,41 @@
 
 dist-clean: distclean
 
-distclean: _clean
+distclean: _clean starpy-clean autobahn-uninstall
+
+install: starpy-install autobahn-install
+
+uninstall: starpy-uninstall autobahn-uninstall
+
+update: starpy-update autobahn-update
+
+# autobahn targets
+
+autobahn-update:
+	pip install --upgrade autobahn
+
+autobahn-install:
+	pip install autobahn
+
+autobahn-uninstall:
+	pip uninstall autobahn
+
+# starpy targets
+
+starpy-install:
+	@if [ ! -d starpy ]; then \
+		echo "starpy/ does not exist.  Run \`make update\` to checkout via git."; \
+	else \
+		(cd starpy; python setup.py install --prefix=~/.local --record install.record) \
+	fi
+
+starpy-clean:
 	rm -rf starpy/install.record
 
-install:
-	(cd starpy; python setup.py install --prefix=~/.local --record install.record)
-
-uninstall:
+starpy-uninstall:
 	rm -rf $$(cat starpy/install.record)
 
-update:
+starpy-update:
 	@if [ -d starpy ]; then \
 		cd starpy; \
 		if [ `git config --get remote.origin.url` = git://github.com/asterisk-org/starpy.git ]; then \
@@ -38,3 +58,10 @@
 	else \
 		git clone git://github.com/asterisk/starpy.git; \
 	fi
+	@echo "+--------- StarPY Downloaded ---------+"
+	@echo "+ StarPY has been successfully cloned +"
+	@echo "+ from its git repo. It can be        +"
+	@echo "+ installed by running:               +"
+	@echo "+                                     +"
+	@echo "+          make install               +"
+	@echo "+-------------------------------------+"

Modified: asterisk/team/dlee/matts-ari-patch/lib/python/asterisk/ari.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/matts-ari-patch/lib/python/asterisk/ari.py?view=diff&rev=4053&r1=4052&r2=4053
==============================================================================
--- asterisk/team/dlee/matts-ari-patch/lib/python/asterisk/ari.py (original)
+++ asterisk/team/dlee/matts-ari-patch/lib/python/asterisk/ari.py Mon Aug 19 14:48:27 2013
@@ -11,22 +11,21 @@
 import logging
 import re
 import requests
-import time
 import traceback
 import urllib
 
+from TestCase import TestCase
 from twisted.internet import reactor
 from autobahn.websocket import WebSocketClientFactory, \
     WebSocketClientProtocol, connectWS
 
-logger = logging.getLogger(__name__)
+LOGGER = logging.getLogger(__name__)
 
 DEFAULT_PORT = 8088
-
 
 #: Default matcher to ensure we don't have any validation failures on the
 #  WebSocket
-ValidationMatcher = {
+VALIDATION_MATCHER = {
     'conditions': {
         'match': {
             'error': "^InvalidMessage$"
@@ -36,6 +35,144 @@
 }
 
 
+class AriTestObject(TestCase):
+    ''' Class that acts as a Test Object in the pluggable module framework '''
+
+    def __init__(self, test_path='', test_config=None):
+        ''' Constructor for a test object
+
+        Keyword Arguments:
+        test_path The full path to the test location
+        test_config The YAML test configuration
+        '''
+        super(AriTestObject, self).__init__(test_path, test_config)
+
+        if test_config is None:
+            # Meh, just use defaults
+            test_config = {}
+
+        self.apps = test_config.get('apps') or 'testsuite'
+        if isinstance(self.apps, list):
+            self.apps = ','.join(self.apps)
+        host = test_config.get('host') or '127.0.0.1'
+        port = test_config.get('port') or DEFAULT_PORT
+        userpass = (test_config.get('username') or 'testsuite',
+            test_config.get('password') or 'testsuite')
+
+        # Create the REST interface and the WebSocket Factory
+        self.ari = ARI(host, port=port, userpass=userpass)
+        self.ari_factory = AriClientFactory(receiver=self, host=host, port=port,
+                                        apps=self.apps,
+                                        userpass=userpass)
+        self.iterations = test_config.get('test-iterations')
+
+        self.test_iteration = 0
+        self.channels = []
+        self._ws_event_handlers = []
+
+        if self.iterations is None:
+            self.iterations = []
+            self.iterations.append({'channel': 'Local/s at default',
+                                    'application': 'Echo'})
+
+        self.create_asterisk(count=1)
+
+    def run(self):
+        ''' Override of TestCase run
+
+        Called when reactor starts and after all instances of Asterisk have started
+        '''
+        super(AriTestObject, self).run()
+        self.ari_factory.connect()
+
+    def register_ws_event_handler(self, callback):
+        ''' Register a callback for when an event is received over the WS
+
+        :param callback The method to call when an event is received. This
+        will have a single parameter (the event) passed to it
+        '''
+        self._ws_event_handlers.append(callback)
+
+    def on_ws_event(self, message):
+        ''' Handler for WebSocket events
+
+        :param message The WS event payload
+        '''
+        for handler in self._ws_event_handlers:
+            handler(message)
+
+    def on_ws_open(self, protocol):
+        ''' Handler for WebSocket Client Protocol opened
+
+        :param protocol The WS Client protocol object
+        '''
+        reactor.callLater(0, self._create_ami_connection)
+
+    def on_ws_closed(self, protocol):
+        ''' Handler for WebSocket Client Protocol closed
+
+        :param protocol The WS Client protocol object
+        '''
+        LOGGER.debug('WebSocket connection closed...')
+
+    def _create_ami_connection(self):
+        ''' Create the AMI connection '''
+        self.create_ami_factory(count=1)
+
+    def ami_connect(self, ami):
+        ''' Override of TestCase ami_connect
+        Called when an AMI connection is made
+
+        :param ami The AMI factory
+        '''
+        ami.registerEvent('Newchannel', self._new_channel_handler)
+        ami.registerEvent('Hangup', self._hangup_handler)
+        self.execute_test()
+
+    def _new_channel_handler(self, ami, event):
+        ''' Handler for new channels
+
+        :param ami The AMI instance
+        :param event The Newchannl event
+        '''
+        LOGGER.debug('Tracking channel %s' % event['channel'])
+        self.channels.append(event['channel'])
+
+    def _hangup_handler(self, ami, event):
+        ''' Handler for channel hangup
+
+        :param ami The AMI instance
+        :param event Hangup event
+        '''
+        LOGGER.debug('Removing tracking for %s' % event['channel'])
+        self.channels.remove(event['channel'])
+        if len(self.channels) == 0:
+            self.test_iteration += 1
+            self.execute_test()
+
+    def execute_test(self):
+        ''' Execute the current iteration of the test '''
+
+        if (self.test_iteration == len(self.iterations)):
+            LOGGER.info('All iterations executed; stopping')
+            self.stop_reactor()
+            return
+
+        iteration = self.iterations[self.test_iteration]
+        if isinstance(iteration, list):
+            for channel in iteration:
+                self._spawn_channel(channel)
+        else:
+            self._spawn_channel(iteration)
+
+    def _spawn_channel(self, channel_def):
+        ''' Create a new channel '''
+
+        # There's only one Asterisk instance, so just use the first AMI factory
+        LOGGER.info('Creating channel %s' % channel_def['channel'])
+        self.ami[0].originate(**channel_def).addErrback(self.handleOriginateFailure)
+
+
 class WebSocketEventModule(object):
     '''Module for capturing events from the ARI WebSocket
     '''
@@ -46,35 +183,20 @@
         :param module_config: Configuration dict parse from test-config.yaml.
         :param test_object: Test control object.
         '''
-        logger.debug("WebSocketEventModule(%r)", test_object)
-        self.host = '127.0.0.1'
-        self.port = DEFAULT_PORT
-        self.test_object = test_object
-        username = module_config.get('username') or 'testsuite'
-        password = module_config.get('password') or 'testsuite'
-        userpass = (username, password)
-        #: ARI interface object
-        self.ari = ARI(self.host, port=self.port, userpass=userpass)
-        #: Matchers for incoming events
+        self.ari = test_object.ari
         self.event_matchers = [
             EventMatcher(self.ari, e, test_object)
             for e in module_config['events']]
-        self.event_matchers.append(EventMatcher(self.ari, ValidationMatcher,
+        self.event_matchers.append(EventMatcher(self.ari, VALIDATION_MATCHER,
                                                 test_object))
-        apps = module_config.get('apps') or 'testsuite'
-        if isinstance(apps, list):
-            apps = ','.join(apps)
-        #: Twisted protocol factory for ARI WebSockets
-        self.factory = AriClientFactory(host=self.host, port=self.port,
-                                        apps=apps, on_event=self.on_event,
-                                        userpass=userpass)
+        test_object.register_ws_event_handler(self.on_event)
 
     def on_event(self, event):
         '''Handle incoming events from the WebSocket.
 
         :param event: Dictionary parsed from incoming JSON event.
         '''
-        logger.error('%r' % event)
+        LOGGER.debug('Received event: %r' % event.get('type'))
         for matcher in self.event_matchers:
             matcher.on_event(event)
 
@@ -82,42 +204,39 @@
 class AriClientFactory(WebSocketClientFactory):
     '''Twisted protocol factory for building ARI WebSocket clients.
     '''
-    def __init__(self, host, apps, on_event, userpass, port=DEFAULT_PORT,
-                 timeout_secs=60):
+    def __init__(self, receiver, host, apps, userpass, port=DEFAULT_PORT,
+        timeout_secs=60):
         '''Constructor
 
+        :param receiver The object that will receive events from the protocol
         :param host: Hostname of Asterisk.
         :param apps: App names to subscribe to.
-        :param on_event: Callback to invoke for all received events.
         :param port: Port of Asterisk web server.
         :param timeout_secs: Maximum time to try to connect to Asterisk.
         '''
         url = "ws://%s:%d/ari/events?%s" % \
               (host, port,
                urllib.urlencode({'app': apps, 'api_key': '%s:%s' % userpass}))
-        logger.info("WebSocketClientFactory(url=%s)" % url)
-        WebSocketClientFactory.__init__(self, url, debug = True, protocols=['ari'])
-        self.on_event = on_event
+        LOGGER.info("WebSocketClientFactory(url=%s)" % url)
+        WebSocketClientFactory.__init__(self, url, debug = True,
+            protocols=['ari'])
         self.timeout_secs = timeout_secs
-        self.protocol = self.__build_protocol
         self.attempts = 0
         self.start = None
-
+        self.receiver = receiver
+
+    def buildProtocol(self, addr):
+        ''' Make the protocol '''
+        return AriClientProtocol(self.receiver, self)
+
+    def clientConnectionFailed(self, connector, reason):
+        ''' Doh, connection lost '''
+        LOGGER.debug('Connection lost; attempting again in 1 second')
+        reactor.callLater(1, self.reconnect)
+
+    def connect(self):
+        ''' Start the connection '''
         self.reconnect()
-
-    def __build_protocol(self):
-        '''Build a client protocol instance
-        '''
-        return AriClientProtocol(self.on_event)
-
-    def clientConnectionFailed(self, connector, reason):
-        '''Callback when client connection failed to connect.
-
-        :param connector: Twisted connector.
-        :param reason: Failure reason.
-        '''
-        logger.info("clientConnectionFailed(%s)" % (reason))
-        reactor.callLater(1, self.reconnect)
 
     def reconnect(self):
         '''Attempt to reconnect the ARI WebSocket.
@@ -125,13 +244,13 @@
         This call will give up after timeout_secs has been exceeded.
         '''
         self.attempts += 1
-        logger.debug("WebSocket attempt #%d" % self.attempts)
+        LOGGER.debug("WebSocket attempt #%d" % self.attempts)
         if not self.start:
             self.start = datetime.datetime.now()
         runtime = (datetime.datetime.now() - self.start).seconds
         if runtime >= self.timeout_secs:
-            logger.error("  Giving up after %d seconds" % self.timeout_secs)
-            return
+            LOGGER.error("  Giving up after %d seconds" % self.timeout_secs)
+            raise Exception("Failed to connect after %d seconds" % self.timeout_secs)
 
         connectWS(self)
 
@@ -139,31 +258,35 @@
 class AriClientProtocol(WebSocketClientProtocol):
     '''Twisted protocol for handling a ARI WebSocket connection.
     '''
-    def __init__(self, on_event):
+    def __init__(self, receiver, factory):
         '''Constructor.
 
-        :param on_event: Callback to invoke with each parsed event.
-        '''
-        self.on_event = on_event
+        :param receiver The event receiver
+        '''
+        LOGGER.debug('Made me a client protocol!')
+        self.receiver = receiver
+        self.factory = factory
 
     def onOpen(self):
         '''Called back when connection is open.
         '''
-        logger.debug("onOpen()")
+        LOGGER.debug('WebSocket Open')
+        self.receiver.on_ws_open(self)
 
     def onClose(self, wasClean, code, reason):
         '''Called back when connection is closed.
         '''
-        logger.debug("onClose(%r, %d, %s)" % (wasClean, code, reason))
-        reactor.callLater(1, self.factory.reconnect)
+        LOGGER.debug("WebSocket closed(%r, %d, %s)" % (wasClean, code, reason))
+        self.receiver.on_ws_closed(self)
 
     def onMessage(self, msg, binary):
         '''Called back when message is received.
 
         :param msg: Received text message.
         '''
-        logger.info("rxed: %s" % msg)
-        self.on_event(json.loads(msg))
+        LOGGER.debug("rxed: %s" % msg)
+        msg = json.loads(msg)
+        self.receiver.on_ws_event(msg)
 
 
 class ARI(object):
@@ -200,7 +323,7 @@
         :throws: requests.exceptions.HTTPError
         '''
         url = self.build_url(*args)
-        logger.info("GET %s %r" % (url, kwargs))
+        LOGGER.info("GET %s %r" % (url, kwargs))
         return raise_on_err(requests.get(url, params=kwargs,
                                          auth=self.userpass))
 
@@ -213,7 +336,7 @@
         :throws: requests.exceptions.HTTPError
         '''
         url = self.build_url(*args, **kwargs)
-        logger.info("POST %s %r" % (url, kwargs))
+        LOGGER.info("POST %s %r" % (url, kwargs))
         return raise_on_err(requests.post(url, params=kwargs,
                                           auth=self.userpass))
 
@@ -226,7 +349,7 @@
         :throws: requests.exceptions.HTTPError
         '''
         url = self.build_url(*args, **kwargs)
-        logger.info("DELETE %s %r" % (url, kwargs))
+        LOGGER.info("DELETE %s %r" % (url, kwargs))
         return raise_on_err(requests.delete(url, params=kwargs,
                                             auth=self.userpass))
 
@@ -273,11 +396,11 @@
             try:
                 res = self.callback(self.ari, message)
                 if not res:
-                    logger.error("Callback failed: %r" %
+                    LOGGER.error("Callback failed: %r" %
                                  self.instance_config)
                     self.passed = False
             except:
-                logger.error("Exception in callback: %s" %
+                LOGGER.error("Exception in callback: %s" %
                              traceback.format_exc())
                 self.passed = False
 
@@ -287,7 +410,7 @@
         :param args: Ignored arguments.
         '''
         if not self.count_range.contains(self.count):
-            logger.error("Expected %d <= count <= %d; was %d (%r)",
+            LOGGER.error("Expected %d <= count <= %d; was %d (%r)",
                          self.count_range.min, self.count_range.max,
                          self.count, self.conditions)
             self.passed = False
@@ -316,9 +439,9 @@
     :param message: Message to compare.
     :returns: True if message matches pattern; False otherwise.
     '''
-    #logger.debug("%r ?= %r" % (pattern, message))
-    #logger.debug("  %r" % type(pattern))
-    #logger.debug("  %r" % type(message))
+    #LOGGER.debug("%r ?= %r" % (pattern, message))
+    #LOGGER.debug("  %r" % type(pattern))
+    #LOGGER.debug("  %r" % type(message))
     if pattern is None:
         # Empty pattern always matches
         return True
@@ -345,7 +468,7 @@
         # Integers are literal matches
         return pattern == message
     else:
-        logger.error("Unhandled pattern type %s" % type(pattern)).__name__
+        LOGGER.error("Unhandled pattern type %s" % type(pattern)).__name__
 
 
 class Range(object):

Modified: asterisk/team/dlee/matts-ari-patch/sample-yaml/ari-config.yaml.sample
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/matts-ari-patch/sample-yaml/ari-config.yaml.sample?view=diff&rev=4053&r1=4052&r2=4053
==============================================================================
--- asterisk/team/dlee/matts-ari-patch/sample-yaml/ari-config.yaml.sample (original)
+++ asterisk/team/dlee/matts-ari-patch/sample-yaml/ari-config.yaml.sample Mon Aug 19 14:48:27 2013
@@ -1,10 +1,39 @@
 # -*- yaml -*-
 # Configuration sample for the Generic ARI module
 
-ari-config:
+# Configuration for ari.AriTestObject
+#
+# A test object that creates an ARI connection and an AMI connection. The test object
+# creates a number of channels and, when all channels have been created/hungup, stops
+# the test.
+test-object-config:
+    # The host to connect to. Default is 127.0.0.1
+    host: 127.0.0.1
+
+    # The port to connect to. Default is 8088.
+    port: 8088
+
+    # The username to use for the connection. Default is testsuite
+    username: testsuite
+
+    # The password to use for the connection. Default is testsuite
+    password: testsuite
+
     # ARI always attempts to connect to the /ari/events WebSocket.
     # This is a comma seperated list of applications to connect for.
+    # If not provided, default is 'testsuite'
     apps: foo-test
+
+    # Define the channels to create for the test. If not specified, a single Local
+    # channel will be created to s at default, tied to the Echo application. Channels
+    # can be created in parallel for a single iteration if a list is provided.
+    test-iterations:
+        - { channel: Local/foo at bar, context: default, exten: yackity, priority: 1}
+        - [ { channel: Local/foo at bar, application: Echo, async: True},
+            { channel: Local/yackity at schmackity, application: Echo, async: True} ]
+
+# Pluggable module that listens for events over the ARI WebSocket
+ari-config:
     # List of events to monitor for. Every event received on the WebSocket
     # is compared against the 'conditions' clause. If the conditions match,
     # then the further specified processing is evaluated.
@@ -34,7 +63,6 @@
                         # block sequences can also be used for arrays
                         args:
                             - 'barman.*'
-
 
             # The above example would match the following:
             #  { 'application': 'foo-test', 'args': [ 'bar' ] }

Modified: asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/configs/ast1/extensions.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/configs/ast1/extensions.conf?view=diff&rev=4053&r1=4052&r2=4053
==============================================================================
--- asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/configs/ast1/extensions.conf (original)
+++ asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/configs/ast1/extensions.conf Mon Aug 19 14:48:27 2013
@@ -2,6 +2,6 @@
 
 exten => s,1,NoOp()
 	same => n,Answer()
-	same => n,Stasis(continue-test)
-	same => n,Stasis(continue-test,fin)
+	same => n,Stasis(testsuite)
+	same => n,Stasis(testsuite,fin)
 	same => n,Hangup()

Modified: asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/rest_continue.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/rest_continue.py?view=diff&rev=4053&r1=4052&r2=4053
==============================================================================
--- asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/rest_continue.py (original)
+++ asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/rest_continue.py Mon Aug 19 14:48:27 2013
@@ -16,7 +16,7 @@
 def on_start(ari, event):
     logger.debug("on_start(%r)" % event)
     global id
-    id = event['channel']['uniqueid']
+    id = event['channel']['id']
     ari.post('channels', id, 'continue')
     return True
 
@@ -24,11 +24,11 @@
 def on_end(ari, event):
     global id
     logger.debug("on_end(%r)" % event)
-    return id == event['channel']['uniqueid']
+    return id == event['channel']['id']
 
 
 def on_second_start(ari, event):
     global id
     logger.debug("on_second_start(%r)" % event)
     ari.delete('channels', id)
-    return id == event['channel']['uniqueid']
+    return id == event['channel']['id']

Modified: asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/test-config.yaml
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/test-config.yaml?view=diff&rev=4053&r1=4052&r2=4053
==============================================================================
--- asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/test-config.yaml (original)
+++ asterisk/team/dlee/matts-ari-patch/tests/rest_api/continue/test-config.yaml Mon Aug 19 14:48:27 2013
@@ -8,16 +8,13 @@
     add-test-to-search-path: True
     test-object:
         config-section: test-object-config
-        typename: SimpleTestCase.SimpleTestCase
+        typename: ari.AriTestObject
     modules:
         -   config-section: ari-config
             typename: ari.WebSocketEventModule
 
 test-object-config:
-    spawn-after-hangup: True
-    test-iterations:
-        -   channel: Local/s at default
-            application: Echo
+
 
 ari-config:
     apps: continue-test
@@ -25,7 +22,7 @@
         -   conditions:
                 match:
                     type: StasisStart
-                    application: continue-test
+                    application: testsuite
                     args: []
             count: 1
             callback:
@@ -34,7 +31,7 @@
         -   conditions:
                 match:
                     type: StasisEnd
-                    application: continue-test
+                    application: testsuite
             count: 2
             callback:
                 module: rest_continue
@@ -42,7 +39,7 @@
         -   conditions:
                 match:
                     type: StasisStart
-                    application: continue-test
+                    application: testsuite
                     args: [fin]
             count: 1
             callback:




More information about the asterisk-commits mailing list