[asterisk-commits] dlee: branch dlee/ari-bridge-tests r4007 - in /asterisk/team/dlee/ari-bridge-...
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Fri Aug 9 14:54:00 CDT 2013
Author: dlee
Date: Fri Aug 9 14:53:59 2013
New Revision: 4007
URL: http://svnview.digium.com/svn/testsuite?view=rev&rev=4007
Log:
mjordan's ARI test object patch
Added:
asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/ari.conf (with props)
Modified:
asterisk/team/dlee/ari-bridge-tests/addons/Makefile
asterisk/team/dlee/ari-bridge-tests/lib/python/asterisk/ari.py
asterisk/team/dlee/ari-bridge-tests/sample-yaml/ari-config.yaml.sample
asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/extensions.conf
asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/rest_continue.py
asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/test-config.yaml
Modified: asterisk/team/dlee/ari-bridge-tests/addons/Makefile
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/ari-bridge-tests/addons/Makefile?view=diff&rev=4007&r1=4006&r2=4007
==============================================================================
--- asterisk/team/dlee/ari-bridge-tests/addons/Makefile (original)
+++ asterisk/team/dlee/ari-bridge-tests/addons/Makefile Fri Aug 9 14:53:59 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/ari-bridge-tests/lib/python/asterisk/ari.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/ari-bridge-tests/lib/python/asterisk/ari.py?view=diff&rev=4007&r1=4006&r2=4007
==============================================================================
--- asterisk/team/dlee/ari-bridge-tests/lib/python/asterisk/ari.py (original)
+++ asterisk/team/dlee/ari-bridge-tests/lib/python/asterisk/ari.py Fri Aug 9 14:53:59 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/ari-bridge-tests/sample-yaml/ari-config.yaml.sample
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/ari-bridge-tests/sample-yaml/ari-config.yaml.sample?view=diff&rev=4007&r1=4006&r2=4007
==============================================================================
--- asterisk/team/dlee/ari-bridge-tests/sample-yaml/ari-config.yaml.sample (original)
+++ asterisk/team/dlee/ari-bridge-tests/sample-yaml/ari-config.yaml.sample Fri Aug 9 14:53:59 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' ] }
Added: asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/ari.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/ari.conf?view=auto&rev=4007
==============================================================================
--- asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/ari.conf (added)
+++ asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/ari.conf Fri Aug 9 14:53:59 2013
@@ -1,0 +1,6 @@
+[general]
+enabled = yes
+
+[testsuite]
+type=user
+password = testsuite
Propchange: asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/ari.conf
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/ari.conf
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Propchange: asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/ari.conf
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/extensions.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/extensions.conf?view=diff&rev=4007&r1=4006&r2=4007
==============================================================================
--- asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/extensions.conf (original)
+++ asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/configs/ast1/extensions.conf Fri Aug 9 14:53:59 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/ari-bridge-tests/tests/rest_api/continue/rest_continue.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/rest_continue.py?view=diff&rev=4007&r1=4006&r2=4007
==============================================================================
--- asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/rest_continue.py (original)
+++ asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/rest_continue.py Fri Aug 9 14:53:59 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/ari-bridge-tests/tests/rest_api/continue/test-config.yaml
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/test-config.yaml?view=diff&rev=4007&r1=4006&r2=4007
==============================================================================
--- asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/test-config.yaml (original)
+++ asterisk/team/dlee/ari-bridge-tests/tests/rest_api/continue/test-config.yaml Fri Aug 9 14:53:59 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