[asterisk-commits] jbigelow: testsuite/asterisk/trunk r3783 - in /asterisk/trunk: configs/ lib/p...
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Fri May 17 15:53:19 CDT 2013
Author: jbigelow
Date: Fri May 17 15:53:14 2013
New Revision: 3783
URL: http://svnview.digium.com/svn/testsuite?view=rev&rev=3783
Log:
Add pluggable CEL verification module
This uses AMI to allow verification of CEL messages. It can be configured to
allow CEL messages to come before and/or after others if they are not always
in the same order due to channels operating on their own threads of execution.
(issue ASTERISK-21422)
(closes issue ASTERISK-21422)
Review: https://reviewboard.asterisk.org/r/2498/
Modified:
asterisk/trunk/configs/cel.conf
asterisk/trunk/lib/python/asterisk/ami.py
asterisk/trunk/sample-yaml/ami-config.yaml.sample
Modified: asterisk/trunk/configs/cel.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/configs/cel.conf?view=diff&rev=3783&r1=3782&r2=3783
==============================================================================
--- asterisk/trunk/configs/cel.conf (original)
+++ asterisk/trunk/configs/cel.conf Fri May 17 15:53:14 2013
@@ -2,3 +2,6 @@
enable=yes
apps=dial,park,queue
events=ALL
+
+[manager]
+enabled=yes
Modified: asterisk/trunk/lib/python/asterisk/ami.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/lib/python/asterisk/ami.py?view=diff&rev=3783&r1=3782&r2=3783
==============================================================================
--- asterisk/trunk/lib/python/asterisk/ami.py (original)
+++ asterisk/trunk/lib/python/asterisk/ami.py Fri May 17 15:53:14 2013
@@ -43,6 +43,16 @@
self.count_max = float("inf")
self.event_count = 0
+
+ if instance_config['type'] == 'cel':
+ # If the type is 'cel' and no condition matches are defined in the
+ # test's yaml then create the dict with setting the Event to 'CEL'.
+ # Otherwise set Event to 'CEL' since it's the only Event we want.
+ if instance_config['conditions']['match'] is None:
+ instance_config['conditions']['match'] = {'Event' : 'CEL'}
+ self.match_conditions = instance_config['conditions']['match']
+ else:
+ instance_config['conditions']['match']['Event'] = 'CEL'
if 'Event' not in self.match_conditions:
logger.error("No event specified to match on. Aborting test")
@@ -231,6 +241,390 @@
self.test_object.set_passed(self.passed)
return callback_param
+class AMICel(AMIEventInstance):
+ '''
+ A subclass of AMIEventInstance that operates by matching headers of
+ AMI CEL events to expected values. This is similar to
+ AMIOrderedHeaderMatchInstance but differs in that it's specifically for
+ checking CEL events and that a partial order may be specified to allow some
+ events to be out of order.
+ '''
+ def __init__(self, instance_config, test_object):
+ super(AMICel, self).__init__(instance_config, test_object)
+ self.match_index = 0
+ self.submatch_index = None
+ self.cel_ids = []
+ self.match_requirements = []
+ self.partial_order_requirements = []
+ self.cel_eventnames = []
+ self.matched_events = []
+ self.marked_failed = False
+
+ for instance in instance_config['requirements']:
+ if 'id' not in instance:
+ logger.error("No 'id' keyword was found for a cel requirement "
+ "in the test's yaml file.")
+ raise Exception
+
+ if not instance['id']:
+ logger.error("No value found for the 'id' keyword of a cel "
+ "requirement in the test's yaml file.")
+ raise Exception
+
+ if instance['id'] in self.cel_ids:
+ logger.error("The cel id '%s' is a duplicate. ID's must be "
+ "unique." % instance['id'])
+ raise Exception
+
+ self.cel_ids.append(instance['id'])
+
+ self.match_requirements.append(
+ instance.get('match', {}))
+
+ if 'partialorder' not in instance:
+ self.partial_order_requirements.append(
+ instance.get('partialorder',
+ {'after' : None, 'before' : None}))
+ else:
+ tmp = {}
+ origpartorder = instance['partialorder']
+ for k,v in origpartorder.items():
+ # Don't change an int of 0 to None. This allows it to be
+ # caught in the regex later in case the single quotes were
+ # forgotten in the config.
+ if not v and v != 0 and v is not None:
+ tmp[k] = None
+ else:
+ tmp[k] = v
+
+ if 'after' not in tmp:
+ tmp['after'] = None
+ if 'before' not in tmp:
+ tmp['before'] = None
+ self.partial_order_requirements.append(tmp)
+
+ self.__verify_match_requirements(self.match_requirements)
+
+ # Remove any duplicates from our list of CEL EventName's
+ self.cel_eventnames = list(set(self.cel_eventnames))
+
+ def __verify_match_requirements(self, match_requirements):
+ cel_cnt = 0
+
+ for item in range(0, len(match_requirements)):
+ # Fixup empty strings defined in the test's yaml file.
+ for k, v in match_requirements[item].items():
+ if not v:
+ logger.debug("No expected header for '%s', using '^$'" % k)
+ match_requirements[item][k] = '^$'
+ # Build list of CEL EventName's from test's yaml file. Used later on to
+ # ignore any received event where the EventName is not found in this list.
+ if "EventName" not in match_requirements[item]:
+ logger.error("No EventName specified. Aborting test.")
+ raise Exception
+ eventname = match_requirements[item].get('EventName')
+ if eventname is None:
+ logger.error("No value found for 'EventName' or it's not "
+ "defined in the test's yaml file. Aborting test.")
+ raise Exception
+
+ self.cel_eventnames.append(eventname)
+
+ # Check if 'after' is found in the test's yaml file for this event
+ # requirement and if it's a string of one or more digits.
+ after = self.partial_order_requirements[item].get('after')
+ if after is not None:
+ try:
+ if not re.match('^[a-zA-Z0-9_-]*$', after):
+ logger.error("CEL ID '%s': The value of 'after' must "
+ "be a string. Aborting test." %
+ self.cel_ids[cel_cnt])
+ raise Exception
+ except TypeError:
+ logger.error("CEL ID '%s': The value of 'after' must be a "
+ "string. Aborting test." %
+ self.cel_ids[cel_cnt])
+ raise Exception
+
+ # Check if 'before' is found in the test's yaml file for this event
+ # requirement and if it's a string of one or more digits.
+ before = self.partial_order_requirements[item].get('before')
+ if before is not None:
+ try:
+ if not re.match('^[a-zA-Z0-9_-]*$', before):
+ logger.error("CEL id '%s': The value of 'before' must "
+ "be a string. Aborting test." %
+ self.cel_ids[cel_cnt])
+ raise Exception
+ except TypeError:
+ logger.error("CEL ID '%s': The value of 'before' must be "
+ "a string. Aborting test." %
+ self.cel_ids[cel_cnt])
+ raise Exception
+
+ # Get dict from list for whatever index we are on:
+ d = match_requirements[item]
+
+ # Convert the dict keys to lowercase but leave the values as is for our
+ # expected event.
+ tmp = [(k.lower(),v) for k,v in d.items()]
+ match_requirements.append(dict(tmp))
+ cel_cnt += 1
+
+ # Remove the orignal items leaving only those whose keys are now lower
+ # case
+ for x in range(0, cel_cnt):
+ match_requirements.pop(0)
+
+ return match_requirements
+
+ def event_callback(self, ami, event):
+ # Ignore any received CEL events where the EventName is not a
+ # requirement listed in the test's yaml.
+ eventname = event.get('eventname')
+ if eventname not in self.cel_eventnames:
+ logger.debug("Ignoring CEL EventName '%s'" % eventname)
+ return
+
+ # Check if we already made a match for this expected event. If so then
+ # skip it.
+ for m in range(self.match_index, len(self.match_requirements)):
+ if self.cel_ids[self.match_index] in self.matched_events:
+ logger.info("Skipping requirement ID '%s' since we already "
+ "matched it." % self.cel_ids[self.match_index])
+ self.match_index += 1
+
+ logger.info("Expecting requirement ID '%s'" %
+ self.cel_ids[self.match_index])
+ # Get dict from list for whatever index we are on:
+ expected = self.match_requirements[self.match_index]
+
+ for k,v in expected.items():
+ if self.marked_failed: return
+ if k not in event.keys():
+ self.mark_failed('nokey', self.cel_ids[self.match_index], k, event)
+ return
+ if not re.match(v, event.get(k)):
+ match_found = False
+ submatch_found = False
+ logger.debug("A requirement 'match' was NOT met against "
+ "requirement ID '%s'" % self.cel_ids[self.match_index])
+ # Check partial order to see if being out of order is allowed
+ self.check_partorder_exists(self.match_index, event,
+ match_found)
+ if self.marked_failed: return
+ # See if this received event matches any other requirements.
+ submatch_found = self.find_requirement_match(event)
+ if self.marked_failed: return
+ # Now lets see if this requirement has a partial order specified
+ self.check_partorder_exists(self.submatch_index, event,
+ match_found, submatch_found)
+ else:
+ match_found = True
+ logger.debug("A requirement 'match' was met against "
+ "requirement ID '%s'" % self.cel_ids[self.match_index])
+ self.check_partorder_exists(self.match_index, event,
+ match_found)
+
+ if self.submatch_index is not None:
+ logger.info("All criteria has been met for requirement ID '%s'" %
+ self.cel_ids[self.submatch_index])
+ self.matched_events.append(self.cel_ids[self.submatch_index])
+ self.submatch_index = None
+ # Not incrementing self.match_index here since we're still
+ # expecting the requirement that we haven't successfully matched to
+ else:
+ logger.info("All criteria has been met for requirement ID '%s'" %
+ self.cel_ids[self.match_index])
+ self.matched_events.append(self.cel_ids[self.match_index])
+ # Increment so we expect a new requirement since we matched the
+ # one we were on.
+ self.match_index += 1
+
+ logger.debug("Matched requirements so far: %s" % self.matched_events)
+ return (ami, event)
+
+ def check_partorder_exists(self, index, event, match_found,
+ submatch_found = None):
+ after = self.partial_order_requirements[index].get('after')
+ before = self.partial_order_requirements[index].get('before')
+ # If a before/after partial order isn't defined for this expected event
+ # then we must fail it as the expected event did not match the
+ # received event.
+ logger.debug("Checking if partial order exists on requirement ID "
+ "'%s'" % self.cel_ids[index])
+
+ if match_found:
+ # if no order specified then we want this to pass
+ if after is None and before is None:
+ return
+ else:
+ logger.debug("Partial order found on requirement ID '%s'. "
+ "Strict ordering NOT enforced." %
+ self.cel_ids[index])
+ # check before and after order
+ self.check_order(index, event, match_found)
+ return
+ if not match_found and not submatch_found:
+ if after is None and before is None:
+ logger.debug("No partial order found on requirement ID "
+ "'%s'. Strict ordering enforced." %
+ self.cel_ids[index])
+ self.mark_failed('partial_order', self.cel_ids[index], event)
+ return
+ else:
+ logger.debug("Found partial order on requirement ID '%s'. "
+ "Strict ordering NOT enforce for this expected event." %
+ self.cel_ids[index])
+ return
+ if not match_found and submatch_found:
+ if after is None and before is None:
+ logger.debug("No partial order found on requirement ID "
+ "'%s'. Strict ordering enforced on this sub match." %
+ self.cel_ids[index])
+ self.mark_failed('partial_order', self.cel_ids[index], event)
+ return
+ else:
+ logger.debug("Found partial order on requirement ID '%s'. "
+ "Strict ordering NOT enforce for this expected event "
+ "sub match." % self.cel_ids[index])
+ # check before and after order
+ self.check_order(index, event, match_found)
+ return
+
+ def find_requirement_match(self, event):
+ logger.debug("Trying to find a requirement that matches")
+ submatch_found = False
+ # Search from our current index+1(since we know it doesn't match our
+ # current index) to the last
+ for i in range(self.match_index + 1, len(self.match_requirements)):
+ # Get dict from list for whatever index we are on:
+ expected = self.match_requirements[i]
+
+ hdrmatchcnt = 0
+ numkeys = 0
+ for k,v in expected.items():
+ numkeys += 1
+ if re.match(v, event.get(k)):
+ hdrmatchcnt += 1
+ if hdrmatchcnt == numkeys:
+ self.submatch_index = i
+ submatch_found = True
+ break
+
+ if submatch_found:
+ logger.debug("Found a sub requirement that matches at ID '%s'" %
+ self.cel_ids[self.submatch_index])
+ else:
+ self.mark_failed('nomatches', None, None, event)
+
+ return submatch_found
+
+ def check_order(self, index, event, match_found):
+ # Check if the 'after' partial order requirement is met.
+ matched_after = self.check_after_order(index)
+
+ # Check if the 'before' partial order requirement is met.
+ matched_before = self.check_before_order(index)
+
+ if not matched_after or not matched_before:
+ if match_found:
+ logger.debug("A match was found at requirement ID '%s' "
+ "but the partial order requirement failed!" %
+ self.cel_ids[index])
+ else:
+ logger.debug("A match was found at sub requirement ID "
+ "'%s' but the partial order requirement failed!" %
+ self.cel_ids[index])
+ logger.warning("Received event does *NOT* match expected "
+ "event")
+ self.mark_failed('partial_order', self.cel_ids[index])
+
+ def check_after_order(self, index):
+ # Lets see if we have a 'after' partial order specified for this
+ # expected event we found to match the received event.
+ logger.debug("Checking the 'after' partial order requirements for expected "
+ "event at requirement ID '%s'" % self.cel_ids[index])
+ # We know that either 'after' or 'before' has a value since
+ # check_partorder_exists() already told us that at least one of them does.
+ # If 'after' is None then it's NOT a problem and therefore we set our
+ # var to True.
+ if self.partial_order_requirements[index].get('after') is None:
+ after_range_matched = True
+ else:
+ after = self.partial_order_requirements[index].get('after')
+ # Check the 'after' order specified on the expected event that was
+ # matched to see if the expected event corresponding to the value of
+ # 'after' was already matched or not.
+ if after in self.matched_events:
+ logger.debug("The expected match for requirement ID '%s' did "
+ "occur after the requirement ID '%s'" %
+ (self.cel_ids[index], after))
+ after_range_matched = True
+ else:
+ logger.debug("The expected match for requirement ID '%s' did "
+ "*NOT* occur after the requirement ID '%s'" %
+ (self.cel_ids[index], after))
+ after_range_matched = False
+
+ return after_range_matched
+
+ def check_before_order(self, index):
+ logger.debug("Checking the 'before' partial order requirements for expected "
+ "event at requirement ID '%s'" % self.cel_ids[index])
+ # We know that either 'after' or 'before' has a value since
+ # check_partorder_exists() already told us that at least one of them does.
+ # If 'before' is None then it's NOT a problem and therefore we set our
+ # var to True.
+ if self.partial_order_requirements[index].get('before') is None:
+ before_range_matched = True
+ else:
+ before = self.partial_order_requirements[index].get('before')
+ # Check the 'before' order specified on the expected event that was
+ # matched to see if the expected event corresponding to the value of
+ # 'before' was already matched or not.
+ if before not in self.matched_events:
+ logger.debug("The expected match for requirement ID '%s' did "
+ "occur before the requirement ID '%s'" %
+ (self.cel_ids[index], before))
+ before_range_matched = True
+ else:
+ logger.debug("The expected match for requirement ID '%s' did "
+ "*NOT* occur before the requirement ID '%s'" %
+ (self.cel_ids[index], before))
+ before_range_matched = False
+
+ return before_range_matched
+
+ def mark_failed(self, item_failed, cel_id, expected = None, received = None):
+ self.passed = False
+ self.marked_failed = True
+ if item_failed == "partial_order":
+ logger.error("The partial order failed or doesn't exist for "
+ "requirement ID '%s'" % cel_id)
+ if item_failed == "match":
+ logger.error("The match failed for requirement ID '%s'" % cel_id)
+ logger.error("=== Event expected ===")
+ logger.error(expected)
+ logger.error("=== Event received ===")
+ logger.error(received)
+ if item_failed == "nomatches":
+ logger.error("No requirement could be matched for the received "
+ "event:")
+ logger.error("%s" % received)
+ if item_failed == "nokey":
+ logger.error("Required CEL key '%s' not found in received "
+ "event" % expected)
+ logger.error("=== Event received ===")
+ logger.error(received)
+
+ logger.debug("Marking test as failed!")
+ return
+
+ def check_result(self, callback_param):
+ self.test_object.set_passed(self.passed)
+ return callback_param
+
class AMICallbackInstance(AMIEventInstance):
'''
Subclass of AMIEventInstance that operates by calling a user-defined
@@ -267,6 +661,9 @@
elif instance_type == "orderedheadermatch":
logger.debug("instance type is 'orderedheadermatch'")
return AMIOrderedHeaderMatchInstance(instance_config, test_object)
+ elif instance_type == "cel":
+ logger.debug("instance type is 'cel'")
+ return AMICel(instance_config, test_object)
elif instance_type == "callback":
logger.debug("instance type is 'callback'")
return AMICallbackInstance(instance_config, test_object)
Modified: asterisk/trunk/sample-yaml/ami-config.yaml.sample
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/sample-yaml/ami-config.yaml.sample?view=diff&rev=3783&r1=3782&r2=3783
==============================================================================
--- asterisk/trunk/sample-yaml/ami-config.yaml.sample (original)
+++ asterisk/trunk/sample-yaml/ami-config.yaml.sample Fri May 17 15:53:14 2013
@@ -87,6 +87,143 @@
# above.
count: '2'
-
+ # The "cel" type allows for checking CEL events via AMI.
+ # This is based on the "orderedheadermatch" type but allows for partial
+ # ordering of the events as they are not always generated in the order
+ # expected. A condition match of "Event: 'CEL'" is always used for this
+ # type and can not be overridden. Like the "orderedheadermatch" type,
+ # when the condition match is made, the first requirement in the list
+ # is expected. A requirement consists of 'match' criteria and
+ # optionally a 'partialorder'. Here is a break down:
+ #
+ # * If the event does successfully meet the 'match' critera of the
+ # requirement expected and...
+ # * a 'partialorder' is not specified, then this requirement
+ # passes. Therefore the second requirement in the
+ # requirements list is then expected.
+ # * a 'partialorder' is specified and...
+ # * the criteria is also met, then the requirement
+ # passes. Therefore the second requirement in the
+ # requirements list is then expected.
+ # * the criteria is not met, the test is failed.
+ # * If it does not meet the 'match' criteria of the requirement
+ # expected and...
+ # * a partial order is not specified, the test is failed.
+ # * a partial order is specified, it will remember the
+ # requirement it is on and check if the 'match' criteria of
+ # the next requirement in the list is met. It will continue
+ # with each requirement in the order listed (where all the
+ # criteria hasn't previously been met) until the 'match'
+ # criteria is met or the end of the requirements list has
+ # been reached at which point the test is failed.
+ # * If the 'match' critera is successfully met and a...
+ # * partial order is not specified, the test is
+ # failed as the event arrived to early.
+ # * partial order is specified and it's criteria
+ # is not met, the test is failed.
+ # * partial order is specified and it's criteria
+ # is met, it is considered a successful match. It will
+ # then move on and expect the next requirement in the
+ # list from the point of the remembered requirement.
+ #
+ # A simple example is shown below.
+
+ type: 'cel'
+
+ # Conditions are handled exactly the same as for "headermatch" types.
+ conditions:
+ match:
+ Channel: 'SIP/alice-.*|SIP/bob-.*'
+
+ # If an event is received with an EventName that is not in the list of
+ # requirements, it is ignored. A unique 'id' string must be set for
+ # each requirement. If a partial order is used then it would reference
+ # the id's of other requirements.
+ requirements:
+ -
+ # The 'match' criteria of this requirement must be met
+ # since a partial order is not specified. If it's not
+ # met then the test fails.
+ id: 'zero'
+ match:
+ EventName: 'CHAN_START'
+ Channel: 'SIP/alice-.*'
+ -
+ # This requirement is then expected and the 'match' criteria
+ # must be met as a partial order isn't specified here either.
+ # If it's not met then the test fails.
+ id: 'one'
+ match:
+ EventName: 'CHAN_START'
+ Channel: 'SIP/bob-.*'
+ -
+ # This requirement is then expected. If the 'match' criteria is
+ # not met it will check if the 'match' critia of the next
+ # requirement in the list is met since a 'partialorder' is
+ # specified. If the 'match' criteria is met but the
+ # 'partialorder' criteria is not met, the test is failed. If
+ # both the 'match' and 'partialorder' criteria is met, the next
+ # requirement in the list is expected.
+ id: 'two'
+ # Here we allow for a partial match. As an example, it may
+ # not be known if the HANGUP CEL event for SIP/alice-.*
+ # will be received before or after the HANGUP CEL event for
+ # SIP/bob-.*. If all that is cared about is that it's been
+ # received for both channels then a partial order should be
+ # specified. If the 'match' criteria is met then the
+ # 'after' and 'before' partial order criteria will be
+ # checked. If both partialorder criteria is met, then this
+ # requirement passes and the next requirement in the list
+ # is expected. If any of the partialorder criteria fails,
+ # then the test fails. If the 'match' criteria is not met
+ # it will check if the 'match' critia of the next
+ # requirement in the list is met since a 'partialorder' is
+ # specified.
+ partialorder:
+ # Here we specify that this requirement must arrive
+ # after ID 'zero' and before ID 'four'. If the
+ # EventName is 'HANGUP' and the Channel is bob's
+ # instead of alice's, the next requirement is checked
+ # and all the criteria will be met there in this case.
+ # This requirement will still be expected upon the next
+ # CEL event. So if the HANGUP arrives for alice's
+ # channel it will meet all the criteria for this
+ # requirement.
+ after: 'zero'
+ before: 'four'
+ match:
+ EventName: 'HANGUP'
+ Channel: 'SIP/alice-.*'
+ -
+ # The same as the previous requirement but with a slightly
+ # different partialorder.
+ id: 'three'
+ partialorder:
+ # Here we specify that this requirement must arrive
+ # after ID 'one' and before ID 'four'. The criteria for
+ # bob's channel will be met here.
+ after: 'one'
+ before: 'four'
+ match:
+ EventName: 'HANGUP'
+ Channel: 'SIP/bob-.*'
+ -
+ # This is the last requirement that must be met for the test to
+ # be able to pass.
+ id: 'four'
+ partialorder:
+ # We specify that this requirement can only be met if
+ # the requirement for ID 'three' has already been met
+ # (of course the 'match' criteria for this requirement
+ # must also have been met).
+ after: 'three'
+ match:
+ # We specify that either alice's or bob's channel can
+ # match here as it will depend on the order in which
+ # they were hung up.
+ EventName: 'LINKEDID_END'
+ Channel: 'SIP/alice-.*|SIP/bob-.*'
+ -
# The "callback" type indicates that when event conditions are fulfilled
# A callback should be called into. This is useful if pass/fail conditions
# depend on more than just having specific headers match what is expected.
More information about the asterisk-commits
mailing list