Пример #1
0
class Test_Manager(unittest.TestCase):
    """ Test the asterisk management interface.
    """

    default_events = AsteriskEmu.default_events

    def close(self):
        if self.manager:
            self.manager.close()
            self.manager = None
        self.astemu.close()

    def setUp(self):
        self.manager = None
        self.childpid = None
        self.events = []
        self.evcount = 0
        self.queue = Queue()

    def tearDown(self):
        self.close()

    def handler(self, event, manager):
        self.events.append(event)
        self.queue.put(self.evcount)
        self.evcount += 1

    def run_manager(self, chatscript):
        self.astemu = AsteriskEmu(chatscript)
        self.port = self.astemu.port
        self.manager = Manager()
        self.manager.connect('localhost', port=self.port)
        self.manager.register_event('*', self.handler)

    def compare_result(self, r_event, event):
        for k, v in event.iteritems():
            if k == 'CONTENT':
                self.assertEqual(r_event.data, v)
            elif isinstance(v, str):
                self.assertEqual(r_event[k], v)
            else:
                self.assertEqual(r_event[k], v[-1])
                self.assertEqual(sorted(r_event.multiheaders[k]),
                                 sorted(list(v)))

    def test_login(self):
        self.run_manager({})
        r = self.manager.login('account', 'geheim')
        self.compare_result(r, self.default_events['Login'][0])
        self.close()
        self.assertEqual(self.events, [])

    def test_command(self):
        d = dict
        events = dict \
            ( Command =
                ( Event
                    ( Response  = ('Follows',)
                    , Privilege = ('Command',)
                    , CONTENT   =
                      """Channel              Location             State   Application(Data)
lcr/556              s@attendoparse:9     Up Read(dtmf,,30,noanswer,,2)    
1 active channel
1 active call
372 calls processed
--END COMMAND--\r
"""
                    )
                ,
                )
            )
        self.run_manager(events)
        r = self.manager.command('core show channels')
        self.assertEqual(self.events, [])
        self.compare_result(r, events['Command'][0])

    def test_redirect(self):
        d = dict
        events = dict \
            ( Redirect =
                ( Event
                    ( Response  = ('Success',)
                    , Message   = ('Redirect successful',)
                    )
                ,
                )
            )
        self.run_manager(events)
        r = self.manager.redirect \
            ('lcr/556', 'generic', 'Bye', context='attendo')
        self.assertEqual(self.events, [])
        self.compare_result(r, events['Redirect'][0])

    def test_originate(self):
        d = dict
        events = dict \
            ( Originate =
                ( Event
                    ( Response  = ('Success',)
                    , Message   = ('Originate successfully queued',)
                    )
                , Event
                    ( Event            = ('Newchannel',)
                    , Privilege        = ('call,all',)
                    , Channel          = ('lcr/557',)
                    , ChannelState     = ('1',)
                    , ChannelStateDesc = ('Rsrvd',)
                    , CallerIDNum      = ('',)
                    , CallerIDName     = ('',)
                    , AccountCode      = ('',)
                    , Exten            = ('',)
                    , Context          = ('',)
                    , Uniqueid         = ('1332366541.558',)
                    )
                , Event
                    ( Event            = ('NewAccountCode',)
                    , Privilege        = ('call,all',)
                    , Channel          = ('lcr/557',)
                    , Uniqueid         = ('1332366541.558',)
                    , AccountCode      = ('4019946397',)
                    , OldAccountCode   = ('',)
                    )
                , Event
                    ({ 'Event'           : ('NewCallerid',)
                     , 'Privilege'       : ('call,all',)
                     , 'Channel'         : ('lcr/557',)
                     , 'CallerIDNum'     : ('',)
                     , 'CallerIDName'    : ('',)
                     , 'Uniqueid'        : ('1332366541.558',)
                     , 'CID-CallingPres' :
                        ('0 (Presentation Allowed, Not Screened)',)
                    })
                , Event
                    ( Event            = ('Newchannel',)
                    , Privilege        = ('call,all',)
                    , Channel          = ('lcr/558',)
                    , ChannelState     = ('1',)
                    , ChannelStateDesc = ('Rsrvd',)
                    , CallerIDNum      = ('',)
                    , CallerIDName     = ('',)
                    , AccountCode      = ('',)
                    , Exten            = ('',)
                    , Context          = ('',)
                    , Uniqueid         = ('1332366541.559',)
                    )
                , Event
                    ( Event            = ('Newstate',)
                    , Privilege        = ('call,all',)
                    , Channel          = ('lcr/558',)
                    , ChannelState     = ('4',)
                    , ChannelStateDesc = ('Ring',)
                    , CallerIDNum      = ('0000000000',)
                    , CallerIDName     = ('',)
                    , Uniqueid         = ('1332366541.559',)
                    )
                , Event
                    ( Event            = ('Newstate',)
                    , Privilege        = ('call,all',)
                    , Channel          = ('lcr/558',)
                    , ChannelState     = ('7',)
                    , ChannelStateDesc = ('Busy',)
                    , CallerIDNum      = ('0000000000',)
                    , CallerIDName     = ('',)
                    , Uniqueid         = ('1332366541.559',)
                    )
                , Event
                    ({ 'Event'         : ('Hangup',)
                     , 'Privilege'     : ('call,all',)
                     , 'Channel'       : ('lcr/558',)
                     , 'Uniqueid'      : ('1332366541.559',)
                     , 'CallerIDNum'   : ('0000000000',)
                     , 'CallerIDName'  : ('<unknown>',)
                     , 'Cause'         : ('16',)
                     , 'Cause-txt'     : ('Normal Clearing',)
                    })
                , Event
                    ({ 'Event'         : ('Hangup',)
                     , 'Privilege'     : ('call,all',)
                     , 'Channel'       : ('lcr/557',)
                     , 'Uniqueid'      : ('1332366541.558',)
                     , 'CallerIDNum'   : ('<unknown>',)
                     , 'CallerIDName'  : ('<unknown>',)
                     , 'Cause'         : ('17',)
                     , 'Cause-txt'     : ('User busy',)
                    })
                , Event
                    ( Event            = ('OriginateResponse',)
                    , Privilege        = ('call,all',)
                    , Response         = ('Failure',)
                    , Channel          = ('LCR/Ext1/0000000000',)
                    , Context          = ('linecheck',)
                    , Exten            = ('1',)
                    , Reason           = ('1',)
                    , Uniqueid         = ('<null>',)
                    , CallerIDNum      = ('<unknown>',)
                    , CallerIDName     = ('<unknown>',)
                    )
                )
            )
        self.run_manager(events)
        r = self.manager.originate \
            ('LCR/Ext1/0000000000', '1'
            , context   = 'linecheck'
            , priority  = '1'
            , account   = '4019946397'
            , variables = {'CALL_DELAY' : '1', 'SOUND' : 'abandon-all-hope'}
            )
        self.compare_result(r, events['Originate'][0])
        for k in events['Originate'][1:]:
            n = self.queue.get()
            self.compare_result(self.events[n], events['Originate'][n + 1])

    def test_misc_events(self):
        d = dict
        # Events from SF bug 3470641
        # http://sourceforge.net/tracker/
        # ?func=detail&aid=3470641&group_id=76162&atid=546272
        # But we fail to reproduce the bug.
        events = dict \
            ( Login =
                ( self.default_events['Login'][0]
                , Event
                    ({ 'AppData'    : '0?begin2'
                     , 'Extension'  : 'zap2dahdi'
                     , 'Uniqueid'   : '1325950970.698'
                     , 'Priority'   : '9'
                     , 'Application': 'GotoIf'
                     , 'Context'    : 'macro-dial-one'
                     , 'Privilege'  : 'dialplan,all'
                     , 'Event'      : 'Newexten'
                     , 'Channel'    : 'Local/102@from-queue-a8ca;2'
                    })
                , Event
                    ({ 'Value'     : '2'
                     , 'Variable'  : 'MACRO_DEPTH'
                     , 'Uniqueid'  : '1325950970.698'
                     , 'Privilege' : 'dialplan,all'
                     , 'Event'     : 'VarSet'
                     , 'Channel'   : 'Local/102@from-queue-a8ca;2'
                    })
                , Event
                    ({'Privilege': 'dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 9\r\n'
                      'Application: GotoIf\r\n'
                      'AppData: 0?begin2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 10\r\n'
                      'Application: Set\r\n'
                      'AppData: THISDIAL=SIP/102\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: THISDIAL\r\n'
                      'Value: SIP/102\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 11\r\n'
                      'Application: Return\r\n'
                      'AppData: \r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: GOSUB_RETVAL\r\n'
                      'Value: \r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: dstring\r\n'
                      'Priority: 9\r\n'
                      'Application: Set\r\n'
                      'AppData: DSTRING=SIP/102&\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: DSTRING\r\n'
                      'Value: SIP/102&\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: dstring\r\n'
                      'Priority: 10\r\n'
                      'Application: Set\r\n'
                      'AppData: ITER=2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 6\r\n'
                      'Application: ExecIf\r\n'
                      'AppData: 0?Set(THISPART2=DAHDI/101)\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: ITER\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 7\r\n'
                      'Application: Set\r\n'
                      'AppData: NEWDIAL=SIP/101&\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: NEWDIAL\r\n'
                      'Value: SIP/101&\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 8\r\n'
                      'Application: Set\r\n'
                      'AppData: ITER2=2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: ITER2\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 9\r\n'
                      'Application: GotoIf\r\n'
                      'AppData: 0?begin2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 10\r\n'
                      'Application: Set\r\n'
                      'AppData: THISDIAL=SIP/101\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: THISDIAL\r\n'
                      'Value: SIP/101\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2'
                    , 'Variable': 'MACRO_DEPTH'
                    , 'Event': 'VarSet'
                    , 'Value': '2'
                    , 'Uniqueid': '1325950970.696'
                   })
                )
            )
        self.run_manager(events)
        r = self.manager.login('account', 'geheim')
        self.compare_result(r, events['Login'][0])
        evnames = []
        for s in events['Login'][3]['Privilege'].split('\r\n'):
            if s.startswith('Event:'):
                evnames.append(s.split(':')[1].strip())
        for k in xrange(30):
            n = self.queue.get()
            e = self.events[n]
            if n < 2:
                self.compare_result(e, events['Login'][n + 1])
            elif n == 2:
                self.assertEqual(e['Event'], 'VarSet')
            else:
                self.assertEqual(e['Event'], evnames[n - 3])
        self.assertEqual(len(self.events), 30)

    def test_agent_event(self):
        d = dict
        # Events from SF bug 3470641
        # http://sourceforge.net/tracker/
        # ?func=detail&aid=3470641&group_id=76162&atid=546272
        # But we fail to reproduce the bug.
        events = dict \
            ( Login =
                ( self.default_events['Login'][0]
                , Event
                    ( Event              = ('AgentCalled',)
                    , Privilege          = ('agent,all',)
                    , Queue              = ('test',)
                    , AgentCalled        = ('SIP/s394000',)
                    , AgentName          = ('910567',)
                    , ChannelCalling     = ('SIP/multifon-00000006',)
                    , DestinationChannel = ('SIP/s394000-00000007',)
                    , CallerIDNum        = ('394000',)
                    , CallerIDName       = ('Agent',)
                    , Context            = ('from-multifon',)
                    , Extension          = ('7930456789',)
                    , Priority           = ('3',)
                    , Uniqueid           = ('1302010429.6',)
                    , Variable           = ('data1=456789', 'data2=test')
                    )
                )
            )
        self.run_manager(events)
        r = self.manager.login('account', 'geheim')
        self.compare_result(r, events['Login'][0])
        for k in events['Login'][1:]:
            n = self.queue.get()
            self.compare_result(self.events[n], events['Login'][n + 1])
Пример #2
0
class Manager(object):
    def __init__(self):
        self._sock = None     # our socket
        self.title = None     # set by received greeting
        self._connected = threading.Event()
        self._running = threading.Event()
        
        # our hostname
        self.hostname = socket.gethostname()
        # pid -- used for unique naming of ActionID
        self.pid      = os.getpid ()

        # our queues
        self._message_queue = Queue()
        self._response_queue = Queue()
        self._event_queue = Queue()

        # callbacks for events
        self._event_callbacks = {}

        self._reswaiting = []  # who is waiting for a response

        # sequence stuff
        self._seqlock = threading.Lock()
        self._seq = 0
       
        # some threads
        self.message_thread = threading.Thread(target=self.message_loop)
        self.event_dispatch_thread = threading.Thread(target=self.event_dispatch)
        
        self.message_thread.setDaemon(True)
        self.event_dispatch_thread.setDaemon(True)


    def __del__(self):
        self.close()

    def connected(self):
        """
        Check if we are connected or not.
        """
        return self._connected.isSet()

    def next_seq(self):
        """Return the next number in the sequence, this is used for ActionID"""
        self._seqlock.acquire()
        try:
            return self._seq
        finally:
            self._seq += 1
            self._seqlock.release()
        
    def send_action(self, cdict={}, **kwargs):
        """
        Send a command to the manager
        
        If a list is passed to the cdict argument, each item in the list will
        be sent to asterisk under the same header in the following manner:

        cdict = {"Action": "Originate",
                 "Variable": ["var1=value", "var2=value"]}
        send_action(cdict)

        ...

        Action: Originate
        Variable: var1=value
        Variable: var2=value
        """

        if not self._connected.isSet():
            raise ManagerException("Not connected")
        
        # fill in our args
        cdict.update(kwargs)

        # set the action id
        if 'ActionID' not in cdict:
            cdict['ActionID'] = '%s-%04s-%08x' % (self.hostname,
                self.pid, self.next_seq())
        clist = []

        # generate the command
        for key, value in cdict.items():
            if isinstance(value, list):
               for item in value:
                  item = tuple([key, item])
                  clist.append('%s: %s' % item)
            else:
               item = tuple([key, value])
               clist.append('%s: %s' % item)
        clist.append(EOL)
        command = EOL.join(clist)

        # lock the socket and send our command
        try:
            self._sock.write(command.encode('utf-8'))
            self._sock.flush()
        except socket.error as err:
            errno, reason = err
            raise ManagerSocketException(errno, reason)

        self._reswaiting.insert(0,1)
        response = self._response_queue.get()
        self._reswaiting.pop(0)

        if not response:
            raise ManagerSocketException(0, 'Connection Terminated')

        return response

    def _receive_data(self):
        """
        Read the response from a command.
        """

        multiline = False
        wait_for_marker = False
        eolcount = 0
        # loop while we are sill running and connected
        while self._running.isSet() and self._connected.isSet():
            try:
                lines = []
                for line in self._sock :
                    line = line.decode('utf-8')
                    # check to see if this is the greeting line
                    if not self.title and '/' in line and not ':' in line:
                        # store the title of the manager we are connecting to:
                        self.title = line.split('/')[0].strip()
                        # store the version of the manager we are connecting to:
                        self.version = line.split('/')[1].strip()
                        # fake message header
                        lines.append ('Response: Generated Header\r\n')
                        lines.append (line)
                        break
                    # If the line is EOL marker we have a complete message.
                    # Some commands are broken and contain a \n\r\n
                    # sequence, in the case wait_for_marker is set, we
                    # have such a command where the data ends with the
                    # marker --END COMMAND--, so we ignore embedded
                    # newlines until we see that marker
                    if line == EOL and not wait_for_marker :
                        multiline = False
                        if lines or not self._connected.isSet():
                            break
                        # ignore empty lines at start
                        continue
                    lines.append(line)
                    # line not ending in \r\n or without ':' isn't a
                    # valid header and starts multiline response
                    if not line.endswith('\r\n') or ':' not in line:
                        multiline = True
                    # Response: Follows indicates we should wait for end
                    # marker --END COMMAND--
                    if not multiline and line.startswith('Response') and \
                        line.split(':', 1)[1].strip() == 'Follows':
                        wait_for_marker = True
                    # same when seeing end of multiline response
                    if multiline and line.startswith('--END COMMAND--'):
                        wait_for_marker = False
                        multiline = False
                    if not self._connected.isSet():
                        break
                else:
                    # EOF during reading
                    self._sock.close()
                    self._connected.clear()
                # if we have a message append it to our queue
                if lines and self._connected.isSet():
                    self._message_queue.put(lines)
                else:
                    self._message_queue.put(None)
            except socket.error:
                self._sock.close()
                self._connected.clear()
                self._message_queue.put(None)

    
    def register_event(self, event, function):
        """
        Register a callback for the specfied event.
        If a callback function returns True, no more callbacks for that
        event will be executed.
        """

        # get the current value, or an empty list
        # then add our new callback
        current_callbacks = self._event_callbacks.get(event, [])
        current_callbacks.append(function)
        self._event_callbacks[event] = current_callbacks

    def unregister_event(self, event, function):
        """
        Unregister a callback for the specified event.
        """
        current_callbacks = self._event_callbacks.get(event, [])
        current_callbacks.remove(function)
        self._event_callbacks[event] = current_callbacks

    def message_loop(self):
        """
        The method for the event thread.
        This actually recieves all types of messages and places them
        in the proper queues.
        """

        # start a thread to receive data
        t = threading.Thread(target=self._receive_data)
        t.setDaemon(True)
        t.start()

        try:
            # loop getting messages from the queue
            while self._running.isSet():
                # get/wait for messages
                data = self._message_queue.get()

                # if we got None as our message we are done
                if not data:
                    # notify the other queues
                    self._event_queue.put(None)
                    for waiter in self._reswaiting:
                        self._response_queue.put(None)
                    break

                # parse the data
                message = ManagerMsg(data)

                # check if this is an event message
                if message.has_header('Event'):
                    self._event_queue.put(Event(message))
                # check if this is a response
                elif message.has_header('Response'):
                    self._response_queue.put(message)
                else:
                    self._response_queue.put(None)
                    print ('No clue what we got\n%s' % message.data)
        finally:
            # wait for our data receiving thread to exit
            t.join()
                            

    def event_dispatch(self):
        """This thread is responsible for dispatching events"""

        # loop dispatching events
        while self._running.isSet():
            # get/wait for an event
            ev = self._event_queue.get()

            # if we got None as an event, we are finished
            if not ev:
                break
            
            # dispatch our events

            # first build a list of the functions to execute
            callbacks = (self._event_callbacks.get(ev.name, [])
                      +  self._event_callbacks.get('*', []))

            # now execute the functions  
            for callback in callbacks:
               if callback(ev, self):
                  break

    def connect(self, host, port=5038):
        """Connect to the manager interface"""

        if self._connected.isSet():
            raise ManagerException('Already connected to manager')

        # make sure host is a string
        assert isinstance (host, string_types)

        port = int(port)  # make sure port is an int

        # create our socket and connect
        try:
            _sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            _sock.connect((host,port))
            self._sock = _sock.makefile(mode='rwb')
            _sock.close()
        except socket.error as err:
            errno, reason = err
            raise ManagerSocketException(errno, reason)

        # we are connected and running
        self._connected.set()
        self._running.set()

        # start the event thread
        self.message_thread.start()

        # start the event dispatching thread
        self.event_dispatch_thread.start()

        # get our initial connection response
        return self._response_queue.get()

    def close(self):
        """Shutdown the connection to the manager"""
        
        # if we are still running, logout
        if self._running.isSet() and self._connected.isSet():
            self.logoff()
         
        if self._running.isSet():
            # put None in the message_queue to kill our threads
            self._message_queue.put(None)

            # wait for the event thread to exit
            self.message_thread.join()

            # make sure we do not join our self (when close is called from event handlers)
            if threading.currentThread() != self.event_dispatch_thread:
                # wait for the dispatch thread to exit
                self.event_dispatch_thread.join()
            
        self._running.clear()

# Manager actions

    def login(self, username, secret):
        """Login to the manager, throws ManagerAuthException when login falis"""
           
        cdict = {'Action':'Login'}
        cdict['Username'] = username
        cdict['Secret'] = secret
        response = self.send_action(cdict)
        
        if response.get_header('Response') == 'Error':
           raise ManagerAuthException(response.get_header('Message'))
        
        return response

    def ping(self):
        """Send a ping action to the manager"""
        cdict = {'Action':'Ping'}
        response = self.send_action(cdict)
        return response

    def logoff(self):
        """Logoff from the manager"""

        cdict = {'Action':'Logoff'}
        response = self.send_action(cdict)
        
        return response

    def hangup(self, channel):
        """Hangup the specified channel"""
    
        cdict = {'Action':'Hangup'}
        cdict['Channel'] = channel
        response = self.send_action(cdict)
        
        return response

    def status(self, channel = ''):
        """Get a status message from asterisk"""

        cdict = {'Action':'Status'}
        cdict['Channel'] = channel
        response = self.send_action(cdict)
        
        return response

    def redirect(self, channel, exten, priority='1', extra_channel='', context=''):
        """Redirect a channel"""
    
        cdict = {'Action':'Redirect'}
        cdict['Channel'] = channel
        cdict['Exten'] = exten
        cdict['Priority'] = priority
        if context:   cdict['Context']  = context
        if extra_channel: cdict['ExtraChannel'] = extra_channel
        response = self.send_action(cdict)
        
        return response

    def originate(self, channel, exten, context='', priority='', timeout='', caller_id='', async=False, account='', variables={}):
Пример #3
0
class Manager(object):
    def __init__(self):
        self._sock = None  # our socket
        self.title = None  # set by received greeting
        self._connected = threading.Event()
        self._running = threading.Event()

        # our hostname
        self.hostname = socket.gethostname()
        # pid -- used for unique naming of ActionID
        self.pid = os.getpid()

        # our queues
        self._message_queue = Queue()
        self._response_queue = Queue()
        self._event_queue = Queue()

        # callbacks for events
        self._event_callbacks = {}

        self._reswaiting = []  # who is waiting for a response

        # sequence stuff
        self._seqlock = threading.Lock()
        self._seq = 0

        # some threads
        self.message_thread = threading.Thread(target=self.message_loop)
        self.event_dispatch_thread = threading.Thread(
            target=self.event_dispatch)

        self.message_thread.setDaemon(True)
        self.event_dispatch_thread.setDaemon(True)

    def __del__(self):
        self.close()

    def connected(self):
        """
        Check if we are connected or not.
        """
        return self._connected.isSet()

    def next_seq(self):
        """Return the next number in the sequence, this is used for ActionID"""
        self._seqlock.acquire()
        try:
            return self._seq
        finally:
            self._seq += 1
            self._seqlock.release()

    def send_action(self, cdict={}, **kwargs):
        """
        Send a command to the manager
        
        If a list is passed to the cdict argument, each item in the list will
        be sent to asterisk under the same header in the following manner:

        cdict = {"Action": "Originate",
                 "Variable": ["var1=value", "var2=value"]}
        send_action(cdict)

        ...

        Action: Originate
        Variable: var1=value
        Variable: var2=value
        """

        if not self._connected.isSet():
            raise ManagerException("Not connected")

        # fill in our args
        cdict.update(kwargs)

        # set the action id
        if 'ActionID' not in cdict:
            cdict['ActionID'] = '%s-%04s-%08x' % (self.hostname, self.pid,
                                                  self.next_seq())
        clist = []

        # generate the command
        for key, value in cdict.items():
            if isinstance(value, list):
                for item in value:
                    item = tuple([key, item])
                    clist.append('%s: %s' % item)
            else:
                item = tuple([key, value])
                clist.append('%s: %s' % item)
        clist.append(EOL)
        command = EOL.join(clist)

        # lock the socket and send our command
        try:
            self._sock.write(command.encode('utf-8'))
            self._sock.flush()
        except socket.error as err:
            errno, reason = err
            raise ManagerSocketException(errno, reason)

        self._reswaiting.insert(0, 1)
        response = self._response_queue.get()
        self._reswaiting.pop(0)

        if not response:
            raise ManagerSocketException(0, 'Connection Terminated')

        return response

    def _receive_data(self):
        """
        Read the response from a command.
        """

        multiline = False
        wait_for_marker = False
        eolcount = 0
        # loop while we are sill running and connected
        while self._running.isSet() and self._connected.isSet():
            try:
                lines = []
                for line in self._sock:
                    line = line.decode('utf-8')
                    # check to see if this is the greeting line
                    if not self.title and '/' in line and not ':' in line:
                        # store the title of the manager we are connecting to:
                        self.title = line.split('/')[0].strip()
                        # store the version of the manager we are connecting to:
                        self.version = line.split('/')[1].strip()
                        # fake message header
                        lines.append('Response: Generated Header\r\n')
                        lines.append(line)
                        break
                    # If the line is EOL marker we have a complete message.
                    # Some commands are broken and contain a \n\r\n
                    # sequence, in the case wait_for_marker is set, we
                    # have such a command where the data ends with the
                    # marker --END COMMAND--, so we ignore embedded
                    # newlines until we see that marker
                    if line == EOL and not wait_for_marker:
                        multiline = False
                        if lines or not self._connected.isSet():
                            break
                        # ignore empty lines at start
                        continue
                    lines.append(line)
                    # line not ending in \r\n or without ':' isn't a
                    # valid header and starts multiline response
                    if not line.endswith('\r\n') or ':' not in line:
                        multiline = True
                    # Response: Follows indicates we should wait for end
                    # marker --END COMMAND--
                    if not multiline and line.startswith('Response') and \
                        line.split(':', 1)[1].strip() == 'Follows':
                        wait_for_marker = True
                    # same when seeing end of multiline response
                    if multiline and line.startswith('--END COMMAND--'):
                        wait_for_marker = False
                        multiline = False
                    if not self._connected.isSet():
                        break
                else:
                    # EOF during reading
                    self._sock.close()
                    self._connected.clear()
                # if we have a message append it to our queue
                if lines and self._connected.isSet():
                    self._message_queue.put(lines)
                else:
                    self._message_queue.put(None)
            except socket.error:
                self._sock.close()
                self._connected.clear()
                self._message_queue.put(None)

    def register_event(self, event, function):
        """
        Register a callback for the specfied event.
        If a callback function returns True, no more callbacks for that
        event will be executed.
        """

        # get the current value, or an empty list
        # then add our new callback
        current_callbacks = self._event_callbacks.get(event, [])
        current_callbacks.append(function)
        self._event_callbacks[event] = current_callbacks

    def unregister_event(self, event, function):
        """
        Unregister a callback for the specified event.
        """
        current_callbacks = self._event_callbacks.get(event, [])
        current_callbacks.remove(function)
        self._event_callbacks[event] = current_callbacks

    def message_loop(self):
        """
        The method for the event thread.
        This actually recieves all types of messages and places them
        in the proper queues.
        """

        # start a thread to receive data
        t = threading.Thread(target=self._receive_data)
        t.setDaemon(True)
        t.start()

        try:
            # loop getting messages from the queue
            while self._running.isSet():
                # get/wait for messages
                data = self._message_queue.get()

                # if we got None as our message we are done
                if not data:
                    # notify the other queues
                    self._event_queue.put(None)
                    for waiter in self._reswaiting:
                        self._response_queue.put(None)
                    break

                # parse the data
                message = ManagerMsg(data)

                # check if this is an event message
                if message.has_header('Event'):
                    self._event_queue.put(Event(message))
                # check if this is a response
                elif message.has_header('Response'):
                    self._response_queue.put(message)
                else:
                    self._response_queue.put(None)
                    print('No clue what we got\n%s' % message.data)
        finally:
            # wait for our data receiving thread to exit
            t.join()

    def event_dispatch(self):
        """This thread is responsible for dispatching events"""

        # loop dispatching events
        while self._running.isSet():
            # get/wait for an event
            ev = self._event_queue.get()

            # if we got None as an event, we are finished
            if not ev:
                break

            # dispatch our events

            # first build a list of the functions to execute
            callbacks = (self._event_callbacks.get(ev.name, []) +
                         self._event_callbacks.get('*', []))

            # now execute the functions
            for callback in callbacks:
                if callback(ev, self):
                    break

    def connect(self, host, port=5038):
        """Connect to the manager interface"""

        if self._connected.isSet():
            raise ManagerException('Already connected to manager')

        # make sure host is a string
        assert isinstance(host, string_types)

        port = int(port)  # make sure port is an int

        # create our socket and connect
        try:
            _sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            _sock.connect((host, port))
            self._sock = _sock.makefile(mode='rwb')
            _sock.close()
        except socket.error as err:
            errno, reason = err
            raise ManagerSocketException(errno, reason)

        # we are connected and running
        self._connected.set()
        self._running.set()

        # start the event thread
        self.message_thread.start()

        # start the event dispatching thread
        self.event_dispatch_thread.start()

        # get our initial connection response
        return self._response_queue.get()

    def close(self):
        """Shutdown the connection to the manager"""

        # if we are still running, logout
        if self._running.isSet() and self._connected.isSet():
            self.logoff()

        if self._running.isSet():
            # put None in the message_queue to kill our threads
            self._message_queue.put(None)

            # wait for the event thread to exit
            self.message_thread.join()

            # make sure we do not join our self (when close is called from event handlers)
            if threading.currentThread() != self.event_dispatch_thread:
                # wait for the dispatch thread to exit
                self.event_dispatch_thread.join()

        self._running.clear()


# Manager actions

    def login(self, username, secret):
        """Login to the manager, throws ManagerAuthException when login falis"""

        cdict = {'Action': 'Login'}
        cdict['Username'] = username
        cdict['Secret'] = secret
        response = self.send_action(cdict)

        if response.get_header('Response') == 'Error':
            raise ManagerAuthException(response.get_header('Message'))

        return response

    def ping(self):
        """Send a ping action to the manager"""
        cdict = {'Action': 'Ping'}
        response = self.send_action(cdict)
        return response

    def logoff(self):
        """Logoff from the manager"""

        cdict = {'Action': 'Logoff'}
        response = self.send_action(cdict)

        return response

    def hangup(self, channel):
        """Hangup the specified channel"""

        cdict = {'Action': 'Hangup'}
        cdict['Channel'] = channel
        response = self.send_action(cdict)

        return response

    def status(self, channel=''):
        """Get a status message from asterisk"""

        cdict = {'Action': 'Status'}
        cdict['Channel'] = channel
        response = self.send_action(cdict)

        return response

    def redirect(self,
                 channel,
                 exten,
                 priority='1',
                 extra_channel='',
                 context=''):
        """Redirect a channel"""

        cdict = {'Action': 'Redirect'}
        cdict['Channel'] = channel
        cdict['Exten'] = exten
        cdict['Priority'] = priority
        if context: cdict['Context'] = context
        if extra_channel: cdict['ExtraChannel'] = extra_channel
        response = self.send_action(cdict)

        return response

    def originate(self,
                  channel,
                  exten,
                  context='',
                  priority='',
                  timeout='',
                  caller_id='',
                  async=False,
                  account='',
                  variables={}):
Пример #4
0
class Test_Manager(unittest.TestCase):
    """ Test the asterisk management interface.
    """

    default_events = AsteriskEmu.default_events

    def close(self):
        if self.manager:
            self.manager.close()
            self.manager = None
        self.astemu.close()

    def setUp(self):
        self.manager  = None
        self.childpid = None
        self.events   = []
        self.evcount  = 0
        self.queue    = Queue()

    def tearDown(self):
        self.close()

    def handler(self, event, manager):
        self.events.append(event)
        self.queue.put(self.evcount)
        self.evcount += 1

    def run_manager(self, chatscript):
        self.astemu = AsteriskEmu (chatscript)
        self.port = self.astemu.port
        self.manager = Manager()
        self.manager.connect('localhost', port = self.port)
        self.manager.register_event ('*', self.handler)

    def compare_result(self, r_event, event):
        for k, v in event.iteritems():
            if k == 'CONTENT':
                self.assertEqual(r_event.data, v)
            elif isinstance(v, str):
                self.assertEqual(r_event[k], v)
            else:
                self.assertEqual(r_event[k], v[-1])
                self.assertEqual(sorted(r_event.multiheaders[k]),
                    sorted(list(v)))

    def test_login(self):
        self.run_manager({})
        r = self.manager.login('account', 'geheim')
        self.compare_result(r, self.default_events['Login'][0])
        self.close()
        self.assertEqual(self.events, [])

    def test_command(self):
        d = dict
        events = dict \
            ( Command =
                ( Event
                    ( Response  = ('Follows',)
                    , Privilege = ('Command',)
                    , CONTENT   = 
"""Channel              Location             State   Application(Data)
lcr/556              s@attendoparse:9     Up Read(dtmf,,30,noanswer,,2)    
1 active channel
1 active call
372 calls processed
--END COMMAND--\r
"""
                    )
                ,
                )
            )
        self.run_manager(events)
        r = self.manager.command ('core show channels')
        self.assertEqual(self.events, [])
        self.compare_result(r, events['Command'][0])

    def test_redirect(self):
        d = dict
        events = dict \
            ( Redirect =
                ( Event
                    ( Response  = ('Success',)
                    , Message   = ('Redirect successful',)
                    )
                ,
                )
            )
        self.run_manager(events)
        r = self.manager.redirect \
            ('lcr/556', 'generic', 'Bye', context='attendo')
        self.assertEqual(self.events, [])
        self.compare_result(r, events['Redirect'][0])

    def test_originate(self):
        d = dict
        events = dict \
            ( Originate =
                ( Event
                    ( Response  = ('Success',)
                    , Message   = ('Originate successfully queued',)
                    )
                , Event
                    ( Event            = ('Newchannel',)
                    , Privilege        = ('call,all',)
                    , Channel          = ('lcr/557',)
                    , ChannelState     = ('1',)
                    , ChannelStateDesc = ('Rsrvd',)
                    , CallerIDNum      = ('',)
                    , CallerIDName     = ('',)
                    , AccountCode      = ('',)
                    , Exten            = ('',)
                    , Context          = ('',)
                    , Uniqueid         = ('1332366541.558',)
                    )
                , Event
                    ( Event            = ('NewAccountCode',)
                    , Privilege        = ('call,all',)
                    , Channel          = ('lcr/557',)
                    , Uniqueid         = ('1332366541.558',)
                    , AccountCode      = ('4019946397',)
                    , OldAccountCode   = ('',)
                    )
                , Event
                    ({ 'Event'           : ('NewCallerid',)
                     , 'Privilege'       : ('call,all',)
                     , 'Channel'         : ('lcr/557',)
                     , 'CallerIDNum'     : ('',)
                     , 'CallerIDName'    : ('',)
                     , 'Uniqueid'        : ('1332366541.558',)
                     , 'CID-CallingPres' :
                        ('0 (Presentation Allowed, Not Screened)',)
                    })
                , Event
                    ( Event            = ('Newchannel',)
                    , Privilege        = ('call,all',)
                    , Channel          = ('lcr/558',)
                    , ChannelState     = ('1',)
                    , ChannelStateDesc = ('Rsrvd',)
                    , CallerIDNum      = ('',)
                    , CallerIDName     = ('',)
                    , AccountCode      = ('',)
                    , Exten            = ('',)
                    , Context          = ('',)
                    , Uniqueid         = ('1332366541.559',)
                    )
                , Event
                    ( Event            = ('Newstate',)
                    , Privilege        = ('call,all',)
                    , Channel          = ('lcr/558',)
                    , ChannelState     = ('4',)
                    , ChannelStateDesc = ('Ring',)
                    , CallerIDNum      = ('0000000000',)
                    , CallerIDName     = ('',)
                    , Uniqueid         = ('1332366541.559',)
                    )
                , Event
                    ( Event            = ('Newstate',)
                    , Privilege        = ('call,all',)
                    , Channel          = ('lcr/558',)
                    , ChannelState     = ('7',)
                    , ChannelStateDesc = ('Busy',)
                    , CallerIDNum      = ('0000000000',)
                    , CallerIDName     = ('',)
                    , Uniqueid         = ('1332366541.559',)
                    )
                , Event
                    ({ 'Event'         : ('Hangup',)
                     , 'Privilege'     : ('call,all',)
                     , 'Channel'       : ('lcr/558',)
                     , 'Uniqueid'      : ('1332366541.559',)
                     , 'CallerIDNum'   : ('0000000000',)
                     , 'CallerIDName'  : ('<unknown>',)
                     , 'Cause'         : ('16',)
                     , 'Cause-txt'     : ('Normal Clearing',)
                    })
                , Event
                    ({ 'Event'         : ('Hangup',)
                     , 'Privilege'     : ('call,all',)
                     , 'Channel'       : ('lcr/557',)
                     , 'Uniqueid'      : ('1332366541.558',)
                     , 'CallerIDNum'   : ('<unknown>',)
                     , 'CallerIDName'  : ('<unknown>',)
                     , 'Cause'         : ('17',)
                     , 'Cause-txt'     : ('User busy',)
                    })
                , Event
                    ( Event            = ('OriginateResponse',)
                    , Privilege        = ('call,all',)
                    , Response         = ('Failure',)
                    , Channel          = ('LCR/Ext1/0000000000',)
                    , Context          = ('linecheck',)
                    , Exten            = ('1',)
                    , Reason           = ('1',)
                    , Uniqueid         = ('<null>',)
                    , CallerIDNum      = ('<unknown>',)
                    , CallerIDName     = ('<unknown>',)
                    )
                )
            )
        self.run_manager(events)
        r = self.manager.originate \
            ('LCR/Ext1/0000000000', '1'
            , context   = 'linecheck'
            , priority  = '1'
            , account   = '4019946397'
            , variables = {'CALL_DELAY' : '1', 'SOUND' : 'abandon-all-hope'}
            )
        self.compare_result(r, events['Originate'][0])
        for k in events['Originate'][1:]:
            n = self.queue.get()
            self.compare_result(self.events[n], events['Originate'][n+1])

    def test_misc_events(self):
        d = dict
        # Events from SF bug 3470641 
        # http://sourceforge.net/tracker/
        # ?func=detail&aid=3470641&group_id=76162&atid=546272
        # But we fail to reproduce the bug.
        events = dict \
            ( Login =
                ( self.default_events['Login'][0]
                , Event
                    ({ 'AppData'    : '0?begin2'
                     , 'Extension'  : 'zap2dahdi'
                     , 'Uniqueid'   : '1325950970.698'
                     , 'Priority'   : '9'
                     , 'Application': 'GotoIf'
                     , 'Context'    : 'macro-dial-one'
                     , 'Privilege'  : 'dialplan,all'
                     , 'Event'      : 'Newexten'
                     , 'Channel'    : 'Local/102@from-queue-a8ca;2'
                    })
                , Event
                    ({ 'Value'     : '2'
                     , 'Variable'  : 'MACRO_DEPTH'
                     , 'Uniqueid'  : '1325950970.698'
                     , 'Privilege' : 'dialplan,all'
                     , 'Event'     : 'VarSet'
                     , 'Channel'   : 'Local/102@from-queue-a8ca;2'
                    })
                , Event
                    ({'Privilege': 'dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 9\r\n'
                      'Application: GotoIf\r\n'
                      'AppData: 0?begin2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 10\r\n'
                      'Application: Set\r\n'
                      'AppData: THISDIAL=SIP/102\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: THISDIAL\r\n'
                      'Value: SIP/102\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 11\r\n'
                      'Application: Return\r\n'
                      'AppData: \r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: GOSUB_RETVAL\r\n'
                      'Value: \r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: dstring\r\n'
                      'Priority: 9\r\n'
                      'Application: Set\r\n'
                      'AppData: DSTRING=SIP/102&\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: DSTRING\r\n'
                      'Value: SIP/102&\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: dstring\r\n'
                      'Priority: 10\r\n'
                      'Application: Set\r\n'
                      'AppData: ITER=2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 6\r\n'
                      'Application: ExecIf\r\n'
                      'AppData: 0?Set(THISPART2=DAHDI/101)\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: ITER\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/102@from-queue-a8ca;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.698\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 7\r\n'
                      'Application: Set\r\n'
                      'AppData: NEWDIAL=SIP/101&\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: NEWDIAL\r\n'
                      'Value: SIP/101&\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 8\r\n'
                      'Application: Set\r\n'
                      'AppData: ITER2=2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: ITER2\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 9\r\n'
                      'Application: GotoIf\r\n'
                      'AppData: 0?begin2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: MACRO_DEPTH\r\n'
                      'Value: 2\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: Newexten\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Context: macro-dial-one\r\n'
                      'Extension: zap2dahdi\r\n'
                      'Priority: 10\r\n'
                      'Application: Set\r\n'
                      'AppData: THISDIAL=SIP/101\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2\r\n'
                      'Variable: THISDIAL\r\n'
                      'Value: SIP/101\r\n'
                      'Uniqueid: 1325950970.696\r\n'
                      '\r\n'
                      'Event: VarSet\r\n'
                      'Privilege: dialplan,all\r\n'
                      'Channel: Local/101@from-queue-4406;2'
                    , 'Variable': 'MACRO_DEPTH'
                    , 'Event': 'VarSet'
                    , 'Value': '2'
                    , 'Uniqueid': '1325950970.696'
                   })
                )
            )
        self.run_manager(events)
        r = self.manager.login('account', 'geheim')
        self.compare_result(r, events['Login'][0])
        evnames = []
        for s in events['Login'][3]['Privilege'].split('\r\n'):
            if s.startswith('Event:'):
                evnames.append(s.split(':')[1].strip())
        for k in xrange(30):
            n = self.queue.get()
            e = self.events[n]
            if n < 2:
                self.compare_result(e, events['Login'][n+1])
            elif n == 2:
                self.assertEqual(e['Event'], 'VarSet')
            else:
                self.assertEqual(e['Event'], evnames[n-3])
        self.assertEqual(len(self.events), 30)

    def test_agent_event(self):
        d = dict
        # Events from SF bug 3470641 
        # http://sourceforge.net/tracker/
        # ?func=detail&aid=3470641&group_id=76162&atid=546272
        # But we fail to reproduce the bug.
        events = dict \
            ( Login =
                ( self.default_events['Login'][0]
                , Event
                    ( Event              = ('AgentCalled',)
                    , Privilege          = ('agent,all',)
                    , Queue              = ('test',)
                    , AgentCalled        = ('SIP/s394000',)
                    , AgentName          = ('910567',)
                    , ChannelCalling     = ('SIP/multifon-00000006',)
                    , DestinationChannel = ('SIP/s394000-00000007',)
                    , CallerIDNum        = ('394000',)
                    , CallerIDName       = ('Agent',)
                    , Context            = ('from-multifon',)
                    , Extension          = ('7930456789',)
                    , Priority           = ('3',)
                    , Uniqueid           = ('1302010429.6',)
                    , Variable           = ('data1=456789', 'data2=test')
                    )
                )
            )
        self.run_manager(events)
        r = self.manager.login('account', 'geheim')
        self.compare_result(r, events['Login'][0])
        for k in events['Login'][1:]:
            n = self.queue.get()
            self.compare_result(self.events[n], events['Login'][n+1])