Support A2Billing :

provided by Star2Billing S.L.

Support A2Billing :
It is currently Fri Mar 29, 2024 4:35 am
Hosted Voice Broadcast


All times are UTC




Post new topic Reply to topic  [ 11 posts ] 
Author Message
 Post subject: How to improve callback daemon dramatically [HOW-TO]
PostPosted: Wed Jul 20, 2011 11:08 am 
Offline

Joined: Sun Aug 17, 2008 1:52 pm
Posts: 93
Hi guys,

As you might have noticed that Callback daemon is not really made to handle large number of calls,
I noticed that callback is checking queued calls every 5 or 10 seconds and read 5 rows from queues
and goes through them one by one to make call, if the first person takes time to pickup, while it is ringing
the other callback in the queue must wait which is not good.

we wanted something immediate, therefore we made some changes in database and python files to achieve
callback daemon process without any daemon and without pressing mysql with continuous queries.

with trigger method, there is no need to run the daemon service in the background anymore and you will
save all the resources daemon is taking to ask mysql for a new callback request every 5 seconds,

the callback delay can be minimized from 5 second to near zero delay.

since below is a dirty and quick patch, you might want to update me if you make any change.

what I will do next is in case the call failed, we want to try for 2 minutes to find the person,
sometime mobiles are out of coverage and some times ,

we noticed in 1.5 million callback 10% were failed and user had to request again.

we added below trigger to a2billing database:
Code:
CREATE TRIGGER `callback_after_insert` AFTER INSERT ON `cc_callback_spool`
FOR EACH ROW BEGIN
DECLARE result CHAR(255);
SET @exec_var = sys_exec(concat('/usr/bin/python /usr/share/callback_daemon/a2b_callback_daemon.py ', NEW.id , ' & >> /dev/null') );
END;



change database.py
from:
Code:
    def find_callback_request(self, c_status = 'PENDING', c_hours = 24):
       
        get_CallBack_Spool = self.CallBack_Spool_q.filter(
                                 (self.cc_callback_spool.c.status==c_status) &
                                 (self.cc_callback_spool.c.entry_time > datetime.datetime.now() - datetime.timedelta(hours=c_hours)) &
                                 ((self.cc_callback_spool.c.callback_time==None) | (self.cc_callback_spool.c.callback_time < datetime.datetime.now()))
                                 ).all()
        return get_CallBack_Spool


to:
Code:
    def find_callback_request(self, c_id = 1, c_hours = 24):
       
        get_CallBack_Spool = self.CallBack_Spool_q.filter(
                                 (self.cc_callback_spool.c.id==c_id) &
                                 (self.cc_callback_spool.c.entry_time > datetime.datetime.now() - datetime.timedelta(hours=c_hours)) &
                                 ((self.cc_callback_spool.c.callback_time==None) | (self.cc_callback_spool.c.callback_time < datetime.datetime.now()))
                                 ).all()
        return get_CallBack_Spool


we also need to install argparse module for python:
Code:
easy_install argparse

to allow mysql to run external command you need to install this module sys_exec
http://www.mysqludf.org/



then we modified the python files as below.
Code:
#!/usr/bin/env python
# vim: set expandtab shiftwidth=4:
'''

Daemon to proceed Call-Back request from the a2billing plaftorm

kill -9 `cat /var/run/a2b-callback-daemon.pid`
'''

__author__ = "Belaid Arezqui ([email protected])"
__copyright__ = "Copyright (C) Belaid Arezqui"

__revision__ = "$Id$"
__version__ = "1.00"

# Load Python modules
import argparse
import threading
import signal
import datetime
import random

import daemon
import database
import logging , logging.config
import sys, time, string
#import asterisk.manager
import manager

INTP_VER = sys.version_info[:2]
if INTP_VER < (2, 2):
    raise RuntimeError("Python v.2.2 or later needed")

# import pdb # debugger
import string


# ------------------------------ PARAMETERS ------------------------------ 

# Daemon Config File
CONFIG_FILE = '/etc/a2billing.conf'


# The next 2 parameters will define the speed of the daemon
# move this later in conf file

# amount of callback request to proceed at each loop
AMOUNT_TO_QUEUE = 10
# Amount of second the daemon will sleep after each check
DAEMON_CYCLE_TIME = 1

# Theard event for all shutdown
shutdown_all = threading.Event()


def handler(signum, frame):
    logging.debug('Signal handler called with signal %d', signum)
    logging.debug("At "+str(frame.f_code.co_name) + " in " + str(frame.f_code.co_filename) + " line "+ str(frame.f_lineno))
    shutdown_all.set()
    sys.exit()


def Init():
    """   
        Catch signal
        Initialize logging
    """ 
    signal.signal( signal.SIGTERM, handler )
    signal.signal( signal.SIGHUP, handler )
    signal.signal( signal.SIGINT, handler )
   
    setup_logger()


def setup_logger():
    # load config for logger
    logging.config.fileConfig(CONFIG_FILE)
    #create logger
    logger = logging.getLogger("callbackLogger")
   
    # test logger
    logger.debug("debug message")
    logger.info("info message")
    logger.warn("warn message")
    logger.error("error message")
    logger.critical("critical message")


#
# ------------------------------ CLASS -----------------------------
#


# Class For our callback Database
class CallBackDatabase(database.callback_database):
    config_filename = CONFIG_FILE
    section = 'database'



# Class For our callback Daemon
# Read conf and initiate Daemon behavior
class CallBackDaemon(daemon.Daemon):
   
    default_conf = CONFIG_FILE
    section = 'daemon-info'
   
    def run(self):
       
        Init()
       
        inst_cb_db = CallBackDatabase()
        run_action = CallBackAction(inst_cb_db)
        logging.info("Processing ID: " + str(db_id_row))
        logging.info("------ Starting Callback Daemon ------ \n")
        logging.info("waiting...")
        run_action.perform()
           
        # wait for few seconds before check if there's any call in 'PENDING' state
        time.sleep(DAEMON_CYCLE_TIME)
        sys.exit(0)


class CallBackAction(object):
   
    inst_cb_db = None
    inst_callback_manager = None
    num_placed = 0
   
    def __init__(self, inst_cb_db):
        self.inst_cb_db = inst_cb_db
        self.inst_callback_manager = callback_manager()
       
    def perform(self, db_id_row = '1'):
       
        perform_amount_request = 0
       
        request_list = self.inst_cb_db.find_callback_request(db_id_row, 11124)
       
        if (len(request_list) > 0) :
            logging.info(request_list)
       
        prev_id_server_group = -1
        id_server_group = None
        for current_request in request_list:
           
            #print current_request.id,' : ',current_request.channel,' : ',current_request.context,' : ',current_request.exten,' : ',current_request.priority,' : '
            try:
                get_Server_Manager = self.inst_cb_db.find_server_manager_roundrobin(current_request.id_server_group)
                #print get_Server_Manager.id,' : ',get_Server_Manager.id_group, ' :  ',get_Server_Manager.server_ip, ' : ',get_Server_Manager.manager_username
            except:
                logging.error("ERROR to find the Server Manager for the Id group : " + str(current_request.id_server_group))
                self.inst_cb_db.update_callback_request(current_request.id, 'ERROR')
                continue
           
            # Connect to Manager
            logging.info("host:" + get_Server_Manager.manager_host + " username: " + get_Server_Manager.manager_username)
            try:
                self.inst_callback_manager.connect(get_Server_Manager.manager_host, get_Server_Manager.manager_username, get_Server_Manager.manager_secret)
            except:
                # cannot connect to the manager
                self.inst_cb_db.update_callback_request(current_request.id, 'ERROR')
                continue
           
            current_channel = current_request.channel
           
            # UPDATE Callback Request to "Perform Status"
            self.inst_cb_db.update_callback_request(current_request.id, 'PROCESSING')
           
            """
            id ; uniqueid ; entry_time ; status; server_ip ; num_attempt ; last_attempt_time ; manager_result ; agi_result ; callback_time ; channel ; exten
            context ; priority ; application ; data ; timeout ; callerid ; variable ; account ; async ; actionid ; id_server ;  id_server_group
            """
            self.num_placed = self.num_placed + 1
           
            attempt = 0
            status = ""
            str_manager_res = ""
            while ((attempt < 5) and (str_manager_res <> "Success")):
          attempt = attempt + 1
          # Initiate call
          logging.info("attempt no: " + str(attempt) + " | try_originate : " + current_request.channel + " : " + current_request.exten + " : " + current_request.context + "| Account: " + current_request.account)
          try:
         res_orig = self.inst_callback_manager.try_originate (
                        current_channel,
                        current_request.exten,
                        current_request.context,
                        current_request.priority,
                        current_request.timeout,
                        current_request.callerid,
                        False,
                        current_request.account,
                        current_request.application,
                        current_request.data,
                        current_request.variable)

          except:
         # cannot connect to the manager
         self.inst_cb_db.update_callback_request(current_request.id, 'ERROR')
         continue
         
          str_manager_res = str(res_orig)
          logging.info("CallBack Status : " + str_manager_res)
           
            if (str_manager_res.find('Success') == -1):
                # Callback Failed
                self.inst_cb_db.update_callback_request_server(current_request.id, 'ERROR', get_Server_Manager.id, str_manager_res)
                status = "FAILED"
            else:
                # Callback Successful
                self.inst_cb_db.update_callback_request_server(current_request.id, 'SENT', get_Server_Manager.id, str_manager_res)
                status = "SUCCESS"
               
            logging.info("["+time.strftime("%Y/%m/%d %H:%M:%S", time.localtime())+"] Placed "+str(self.num_placed)+" calls")
           
            """
            self.inst_cb_db.update_callback_request(current_request.id, 'PENDING')
            sys.exit()
            """
           


class callback_manager(object):
    _manager = None
    _manager_host = None
    _manager_login = None
    _manager_passw = None
   
    def connect (self, host, login, password):
        if (self._manager_host != host or
            self._manager_login != login or
            self._manager_passw != password or
            self._manager == None) :
            # we have different manager parameter so let s connect
            if self._manager != None:
                self.disconnect()
            self._manager_host = host
            self._manager_login = login
            self._manager_passw = password
            return self.try_connect()
        return True
       
    def try_connect (self):
       
        self._manager = manager.Manager()
       
        try :
            self._manager.connect(self._manager_host)
            self._manager.login(self._manager_login, self._manager_passw)
           
        except manager.ManagerSocketException, (errno, reason):
            #print "Error connecting to the manager: %s" % reason
            #sys.exit(1)
            return False
        except manager.ManagerAuthException, reason:
            #print "Error logging in to the manager: %s" % reason
            #sys.exit(1)
            return False
        except manager.ManagerException, reason:
            #print "Error: %s" % reason
            #sys.exit(1)
            return False
        return True
       
    def try_originate (self, channel = None, exten = None, context = None, priority = None, timeout = None, caller_id = None, async = True, account = None, application = None, data = None, variables = None, actionid = None):
       
        response = self._manager.originate(channel, exten, context, priority, timeout, caller_id, async, account, application, data, variables, actionid)
        return response
     
    def disconnect (self):
        self._manager.close()
        self._manager = None


# ------------------------------ FUNCTION MAIN  ------------------------------
 
def main ():
   
    CallBackDaemon().main()


# ------------------------------ FUNCTION ------------------------------ 
       
def IsInt( str ):
    """ Is the given string an integer?    """
    ok = 1
    try:
        num = int(str)
    except ValueError:
        ok = 0
    except TypeError:
        ok = 0
    return ok


def readToEnd( manager, message, END_SIGNAL='--END COMMAND--' ):
    """Read until the end of the current command"""
    result = []
    while manager._running.isSet():
        current = message.data
        if current.strip() == END_SIGNAL:
            return result
        else:
            result.append( current.rstrip('\n') )
        message = manager._response_queue.get()



# ------------------------------ MAIN ------------------------------ 
Init()
inst_cb_db = CallBackDatabase()
run_action = CallBackAction(inst_cb_db)
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('integers', metavar='N', type=int, nargs='+', help='an integer for the accumulator')
db_id_row = parser.parse_args().integers[0]
logging.info("Processing ID (db_id_row): " + str(db_id_row))
logging.info("------ run_action.perform() ------ \n")
run_action.perform(str(db_id_row))
           
# wait for few seconds before check if there's any call in 'PENDING' state
#time.sleep(DAEMON_CYCLE_TIME)
sys.exit()

if __name__ == '__main__':
   
   
    #CallBackDaemon().main()
   
    #inst_cb_db = CallBackDatabase()
    #inst_cb_action = CallBackAction(inst_cb_db)
    #inst_cb_action.perform()
   
    logging.info("End of Script")
   
    sys.exit()


Top
 Profile  
 
 Post subject: Re: How to improve callback daemon dramatically [HOW-TO]
PostPosted: Fri Aug 19, 2011 2:19 pm 
Offline

Joined: Tue Oct 06, 2009 12:23 pm
Posts: 23
Location: Austria
Wow !

Great idea and thanks for this new method with trigger.

Unfortunatly it does not work properly with my installations.
Using a2b 1.9.4 asterisk 1.6 I got callback working fine with daemons, but maximum is about 30,000 minutes a day and callback delay is up to 10 minutes.

When using trigger method it, trigger only starts correct when I enter a new set in cc_callback_spool manually.
But when a2b adds a set in cc_callback_spool the query does not find any record in
Code:
def find_callback_request(self, c_id = 1, c_hours = 24):
       
        get_CallBack_Spool = self.CallBack_Spool_q.filter(
                                 (self.cc_callback_spool.c.id==c_id) &
                                 (self.cc_callback_spool.c.entry_time > datetime.datetime.now() - datetime.timedelta(hours=c_hours)) &
                                 ((self.cc_callback_spool.c.callback_time==None) | (self.cc_callback_spool.c.callback_time < datetime.datetime.now()))
                                 ).all()
        return get_CallBack_Spool


Any idea why ?

Thanks for help

alfred


Top
 Profile  
 
 Post subject: Re: How to improve callback daemon dramatically [HOW-TO]
PostPosted: Fri Aug 19, 2011 5:12 pm 
Offline

Joined: Sun Aug 17, 2008 1:52 pm
Posts: 93
if you check the code, the trigger is providing the ID on specific callback it must handle to the python code as input argument,
you also need to make sure yuor python library have necessary library installed already. I forgot , i think the line to "import argparse" was importing it and you might check your python installation

let me know if you need any help.


also make sure the trigger is being called after the insert, if it is called before, obviously there is no record in mysql yet and also make sure you callback is myisam engine


Top
 Profile  
 
 Post subject: Re: How to improve callback daemon dramatically [HOW-TO]
PostPosted: Sat Aug 20, 2011 2:01 pm 
Offline

Joined: Tue Oct 06, 2009 12:23 pm
Posts: 23
Location: Austria
Thank you very much for your help.
I checked all installed packages again and checked all log files carefully.

Finally it worked :)

Its really a great performance booster and also much more callback requests become completed billing events because of 5 times try to call back.

Thanks a lot !

www.benotos.com


Top
 Profile  
 
 Post subject: Re: How to improve callback daemon dramatically [HOW-TO]
PostPosted: Wed Oct 19, 2011 2:27 am 
Offline

Joined: Mon Jan 08, 2007 6:56 pm
Posts: 345
Step 3 with the delimiters worked on this version of Mysql. Also needed to change ownership on the log directory. Indentation issues also in the python script as a result of copy and paste, but that was resolved.

Code:
1. go into mya2billing

2.
DROP TRIGGER if exists callback_after_insert;

3.
delimiter |
CREATE TRIGGER `callback_after_insert` AFTER INSERT ON `cc_callback_spool`
FOR EACH ROW
BEGIN
DECLARE result CHAR(255);
   SET @exec_var = sys_exec(concat('/usr/bin/python2.6 /usr/lib/python2.6/site-packages/callback_daemon-1.0.prod-py2.6.egg/callback_daemon/a2b_callback_daemon.py ', NEW.id , ' & >> /dev/null') );
END;
|

4.
//++ change log ownership
chown -hR mysql:mysql  /var/log/a2billing/callback

.
.
.


Top
 Profile  
 
 Post subject: Re: How to improve callback daemon dramatically [HOW-TO]
PostPosted: Sun Oct 23, 2011 11:53 pm 
Offline

Joined: Fri Sep 09, 2011 3:02 am
Posts: 7
hello,

when i initiate a callback from console with:

Code:
/usr/bin/python /usr/share/callback/a2b_callback_daemon.py 331


everything work perfect. But whenever the callbacks get initiated by the database trigger the execution doesnt run through well.
In logs then i see:

Code:
"2011-10-24 01:43:48,826 - callbackLogger - MainThread - INFO - info message"
"2011-10-24 01:43:48,826 - callbackLogger - MainThread - WARNING - warn message"
"2011-10-24 01:43:48,826 - callbackLogger - MainThread - ERROR - error message"
"2011-10-24 01:43:48,826 - callbackLogger - MainThread - CRITICAL - critical message"
"2011-10-24 01:43:48,891 - root - MainThread - INFO - Processing ID (db_id_row): 331"
"2011-10-24 01:43:48,891 - root - MainThread - INFO - ------ run_action.perform() ------
"


And thats it. Any idea? It should show in next line

Code:
"2011-10-24 01:29:11,327 - root - MainThread - INFO - [<database.CallBack_Spool object at 0xXXXXXXX>]"


but nothing more happens.

Regards

Chris


Top
 Profile  
 
 Post subject: Re: How to improve callback daemon dramatically [HOW-TO]
PostPosted: Mon Oct 24, 2011 6:45 pm 
Offline

Joined: Mon Jan 08, 2007 6:56 pm
Posts: 345
Code:
0. install callback based on a2billing install instructions and test

   export LOAD_LOC=/usr/local/src/Star2Billing194
   yum -y install python-setuptools.noarch
   yum -y install python-setuptools-devel.noarch   #this too fedora
   yum -y install MySQL-python

   yum -y install python-psycopg2
   yum -y install python-sqlalchemy


   cd $LOAD_LOC/CallBack/callback-daemon-py/
   /usr/bin/easy_install sqlalchemy
   /usr/bin/python setup.py build    **ignore not found message
   /usr/bin/python setup.py bdist_egg
   /usr/bin/easy_install dist/callback_daemon-1.0.prod-py2.6.egg

test:
   . create a callback from test customer account and get the id from the database eg; 51
   . path to callback program from commandline
/usr/bin/python2.6       /usr/lib/python2.6/site-packages/callback_daemon-1.0.prod-py2.6.egg/callback_daemon/a2b_callback_daemon.py 51





1. go into mya2billing

2.
DROP TRIGGER if exists callback_after_insert;

3.
delimiter |
CREATE TRIGGER `callback_after_insert` AFTER INSERT ON `cc_callback_spool`
FOR EACH ROW
BEGIN
DECLARE result CHAR(255);
   SET @exec_var = sys_exec(concat('/usr/bin/python2.6 /usr/lib/python2.6/site-packages/callback_daemon-1.0.prod-py2.6.egg/callback_daemon/a2b_callback_daemon.py ', NEW.id , ' & >> /dev/null') );
END;
|


4.
//++ change log ownership
chown -hR mysql:mysql  /var/log/a2billing/callback

5. edit database.py with modifications from the first post

6. from command line
easy_install argparse

7.
to allow mysql to run external command you need to install this module sys_exec
http://www.mysqludf.org/

8. edit a2b_callback_daemon.py with modifications from the first post

   recompile after changes
   /usr/bin/python setup.py build    **ignore not found message
   /usr/bin/python setup.py bdist_egg
   /usr/bin/easy_install dist/callback_daemon-1.0.prod-py2.6.egg




Top
 Profile  
 
 Post subject: Re: How to improve callback daemon dramatically [HOW-TO]
PostPosted: Wed Oct 26, 2011 6:12 pm 
Offline

Joined: Fri Sep 09, 2011 3:02 am
Posts: 7
its frustating...

1. Callback gets initiated.
2. in cc_callback_spool a row is added with ID x
3. the trigger shoots the python script with ID x in arguments
4. the python scripts executes, loggs the ID x
5. It tries to get the row from cc_callback_spool and FAILS!!!

the same what alfred37 describes.
When executing the python script from console everything works. So parsing arguments works and mysql-access also works.
I even executed the python script from console as user mysql and it worked.

Any suggestions ?


Top
 Profile  
 
 Post subject: Re: How to improve callback daemon dramatically [HOW-TO]
PostPosted: Thu Oct 27, 2011 2:00 pm 
Offline

Joined: Mon Jan 08, 2007 6:56 pm
Posts: 345
Check mysql and messages log files for clues and double check python/mysql requirements.

Did you get any indentation error messages when you reinstalled script? Set your a2b mysql user to global (%) access on the database. Double check settings in /etc/a2billing.conf.


Top
 Profile  
 
 Post subject: Re: How to improve callback daemon dramatically [HOW-TO]
PostPosted: Thu Oct 27, 2011 9:06 pm 
Offline

Joined: Fri Sep 09, 2011 3:02 am
Posts: 7
Hello,

there is some error within this query, but i didnt have the time yet to figure out why its working from console and not from the mysql-sys_exec.

Code:
def find_callback_request(self, c_id = 1, c_hours = 24):
       
        get_CallBack_Spool = self.CallBack_Spool_q.filter(
                                 (self.cc_callback_spool.c.id==c_id) &
                                 (self.cc_callback_spool.c.entry_time > datetime.datetime.now() - datetime.timedelta(hours=c_hours)) &
                                 ((self.cc_callback_spool.c.callback_time==None) | (self.cc_callback_spool.c.callback_time < datetime.datetime.now()))
                                 ).all()
        return get_CallBack_Spool


So I changed it to


Code:
    def find_callback_request(self, c_id = 1, c_hours = 24):

        return self.CallBack_Spool_q.filter((self.cc_callback_spool.c.status=='PENDING')).all()


This is working, even its not the right since this way all PENDING calls will get a callback, and if 2 people call in the same time, then 2 python-scripts will initiate 2 callbacks =)


Top
 Profile  
 
 Post subject: Re: How to improve callback daemon dramatically [HOW-TO]
PostPosted: Thu Feb 16, 2012 2:34 pm 
Offline

Joined: Thu May 27, 2010 11:37 pm
Posts: 6
Just now have finished testing of my realtime callback.
Settings added - there are timeouts and number attempts for various reason of attempts for each customer card personally.
Example to run daemon under daemontools is at addons/daemontools.
Base code is CallBack/callback-daemon-php/callback_daemon.php :
Code:
#!/usr/bin/php -q
<?php

$id_server_group=1;

declare(ticks = 1);
if (function_exists('pcntl_signal'))
{
    pcntl_signal(SIGHUP, SIG_IGN);
}

set_time_limit(0);
error_reporting(E_ALL ^ (E_NOTICE | E_WARNING));


include ("lib/admin.defines.php");
include ("lib/Class.RateEngine.php");
include ("lib/ProcessHandler.php");
include ("lib/phpagi/phpagi-asmanager.php");

function originateresponse($e, $parameters, $server, $port, $actionid) {

    if ($parameters['ActionID'] == $actionid)   return $parameters['Reason'];
    return false;
}

function callback_engine(&$A2B, $server, $username, $secret, $AmiVars, $destination, $tariff) {

    $A2B -> cardnumber = $AmiVars[4];

    if ($A2B -> callingcard_ivr_authenticate_light ($error_msg))
    {
        $RateEngine = new RateEngine();
        $RateEngine -> webui = 0;

//      LOOKUP RATE : FIND A RATE FOR THIS DESTINATION
        $A2B -> agiconfig['accountcode'] = $A2B -> cardnumber;
        $A2B -> agiconfig['use_dnid'] = 1;
        $A2B -> agiconfig['say_timetocall'] = 0;
        $A2B -> extension = $A2B -> dnid = $A2B -> destination = $destination;

        $resfindrate = $RateEngine->rate_engine_findrates($A2B, $destination, $tariff);

//      IF FIND RATE
        if ($resfindrate!=0)
        {
            $res_all_calcultimeout = $RateEngine->rate_engine_all_calcultimeout($A2B, $A2B->credit);
            if ($res_all_calcultimeout)
            {
                $ast = new AGI_AsteriskManager();
                $res = $ast -> connect($server, $username, $secret);
                if (!$res) return -4;
//              MAKE THE CALL
                $res = $RateEngine->rate_engine_performcall(false, $destination, $A2B, 8, $AmiVars, $ast);
                $ast -> disconnect();
                if ($res !== false) return $res;
                else return -2; // not enough free trunk for make call
            }
            else return -3; // not have enough credit to call you back
        }
        else return -1; // no route to call back your phonenumber
    }
    else return -1; // ERROR MESSAGE IS CONFIGURE BY THE callingcard_ivr_authenticate_light
}


if (!defined('PID'))
    define("PID", "/var/run/a2billing/a2b-callback-daemon.pid");

// CHECK IF THE DAEMON IS ALREADY RUNNING
if (ProcessHandler :: isActive())
    die("Already running!");
else
    ProcessHandler :: activate();

$FG_DEBUG = 0;
$verbose_level = 1;

$A2B = new A2Billing();
$A2B->load_conf($agi);

write_log(LOGFILE_API_CALLBACK, basename(__FILE__) . ' line:' . __LINE__ . "[#### CALLBACK BEGIN ####]");

if (!$A2B->DbConnect()) {
    echo "[Cannot connect to the database]\n";
    write_log(LOGFILE_API_CALLBACK, basename(__FILE__) . ' line:' . __LINE__ . "[Cannot connect to the database]");
    exit;
}

if ($A2B->config["database"]['dbtype'] == "postgres")
    $UNIX_TIMESTAMP = "date_part('epoch',";
else
    $UNIX_TIMESTAMP = "UNIX_TIMESTAMP(";

$instance_table = new Table();
$A2B -> set_instance_table ($instance_table);

$query="SELECT `id`,`manager_host`,`manager_username`,`manager_secret` FROM `cc_server_manager` WHERE `id_group`=$id_server_group LIMIT 1";
$result=$instance_table->SQLExec($A2B->DBHandle, $query);
if (!(is_array($result) && count($result)>0)) {
    print("id_server_group $id_server_group does not exist\n");
    exit(1);
}
list($manager_id,$manager_host,$manager_username,$manager_secret)=$result[0];
$query="UPDATE `cc_server_manager` SET `lasttime_used`=now()";
if (!$A2B->DBHandle->Execute($query)) die("Can't execute query '$query'\n");

while(true)
{
    pcntl_wait($status, WNOHANG);
    $query="SELECT `id`,`status`,`exten_leg_a`,`account`,`callerid`,`exten`,`context`,`priority`,`variable`,`timeout`,`reason`,`num_attempts_unavailable`,`num_attempts_busy`,`num_attempts_noanswer`,TIMEDIFF(now(),`callback_time`)";
    $query.=" FROM `cc_callback_spool` WHERE `id_server_group`=$id_server_group AND `status`='PENDING' AND (`next_attempt_time`<=now() OR ISNULL(`next_attempt_time`))";
    $result=$instance_table->SQLExec($A2B->DBHandle, $query);
    foreach ($result as $value) {
        list($cc_id,$cc_status,$cc_exten_leg_a,$cc_account,$cc_callerid,$cc_exten,$cc_context,$cc_priority,$cc_variable,$cc_timeout,$cc_reason,$cc_num_attempts_unavailable,$cc_num_attempts_busy,$cc_num_attempts_noanswer,$cc_timediff)=$value;
        $query2="SELECT `tariff`,`cbtimeoutunavailable`,`cbattemptunavailable`,`cbtimeoutbusy`,`cbattemptbusy`,`cbtimeoutnoanswer`,`cbattemptnoanswer`,`cbtimeoutmax`,TIME_TO_SEC(TIMEDIFF(`cbtimeoutmax`,'$cc_timediff')) FROM `cc_card` WHERE `username`='$cc_account' L
        $result2=$instance_table->SQLExec($A2B->DBHandle, $query2);
        if (!(is_array($result2) && count($result2)>0)) die("Can't execute query '$query2'\n");
        list($acc_tariff,$acc_to_unav,$acc_max_unav,$acc_to_busy,$acc_max_busy,$acc_to_noansw,$acc_max_noansw,$acc_max_timeout,$acc_timeout_res)=$result2[0];
        if ($acc_timeout_res < 0)
        {
            $query3="UPDATE `cc_callback_spool` SET `status`='ERROR_TIMEOUT',`id_server`='$manager_id',`id_server_group`='$id_server_group' WHERE `id`=$cc_id";
            if (!$A2B->DBHandle->Execute($query3)) die("Can't execute query '$query3'\n");
        }
        elseif($acc_max_unav<=$cc_num_attempts_unavailable)
        {
            $query3="UPDATE `cc_callback_spool` SET `status`='ERROR_UNAVAILABLE',`id_server`='$manager_id',`id_server_group`='$id_server_group' WHERE `id`=$cc_id";
            if (!$A2B->DBHandle->Execute($query3)) die("Can't execute query '$query3'\n");
        }
        elseif($acc_max_busy<=$cc_num_attempts_busy)
        {
            $query3="UPDATE `cc_callback_spool` SET `status`='ERROR_BUSY',`id_server`='$manager_id',`id_server_group`='$id_server_group' WHERE `id`=$cc_id";
            if (!$A2B->DBHandle->Execute($query3)) die("Can't execute query '$query3'\n");
        }
        elseif($acc_max_noansw<=$cc_num_attempts_noanswer)
        {
            $query3="UPDATE `cc_callback_spool` SET `status`='ERROR_NO-ANSWER',`id_server`='$manager_id',`id_server_group`='$id_server_group' WHERE `id`=$cc_id";
            if (!$A2B->DBHandle->Execute($query3)) die("Can't execute query '$query3'\n");
        }
        else {
            $A2B->DbDisconnect();
            $pid=pcntl_fork();
            if($pid==-1) {
                print("Can't fork!\n");
                exit(2);
            }
            elseif($pid) {
                pcntl_wait($status, WNOHANG);
                $A2B -> DbConnect($agi);
                $A2B -> set_instance_table ($instance_table);
            }
            else {
                ob_start();
                register_shutdown_function(create_function('$pars', 'ob_end_clean();posix_kill(getmypid(), SIGKILL);'), array());

                $A2B -> DbConnect($agi);
                $A2B -> set_instance_table ($instance_table);
                $query3="UPDATE `cc_callback_spool` SET `status`='PROCESSING',`num_attempt`=`num_attempt`+1,`last_attempt_time`=now(),`id_server`='$manager_id',`id_server_group`='$id_server_group' WHERE `id`=$cc_id";
                if (!$A2B->DBHandle->Execute($query3)) die("Can't execute query '$query3'\n");
                $return=callback_engine($A2B, $manager_host.":5038", $manager_username, $manager_secret, array($cc_exten,$cc_priority,$cc_callerid,$cc_variable,$cc_account,$cc_id), $cc_exten_leg_a, $acc_tariff);
                $timeout=-1;
                $fatal=0;
                switch($return)
                {
                    case -4: $last_status="ERROR_AMI";$fatal=1;break; // AMI not have connecting
                    case -3: $last_status="ERROR_NO-MONEY";$fatal=1;break; // not have enough credit to call you back
                    case -2: $last_status="ERROR_CHANNEL-UNAVAILABLE";$timeout=$acc_to_unav;break; // not enough free trunk for make call
                    case -1: $last_status="ERROR_NO-RATE-AVAILABLE";$fatal=1;break; // no route to call back your phonenumber or other fatal errors
                    case  0: $last_status="ERROR_CHANNEL-UNAVAILABLE";$timeout=$acc_to_unav;break;
                    case  1: $last_status="BUSY";$timeout=$acc_to_busy;break;
                    case  3: $last_status="NO-ANSWER";$timeout=$acc_to_noansw;break;
                    case  4: $last_status="SENT";$fatal=1;break;
                    case  5: $last_status="ERROR_CONGESTION";$fatal=1;break;
                    case  8: $last_status="ERROR_CONGESTION_OR_CHANNEL-UNAVAILABLE";$fatal=0;$timeout=$acc_to_unav;break;
                    default: $last_status="ERROR_UNKNOWN (#$return)";$fatal=1;break;
                }
                if($fatal) $status=$last_status;
                    else $status='PENDING';
                $query3="UPDATE `cc_callback_spool` SET `status`='$status',`last_status`='$last_status',`manager_result`='$last_status',`id_server`='$manager_id',`id_server_group`='$id_server_group'";
                if($return==-2 || $return==0 || $return==8) $query3.=",`num_attempts_unavailable`=`num_attempts_unavailable`+1";
                if($return==1) $query3.=",`num_attempts_busy`=`num_attempts_busy`+1";
                if($return==3) $query3.=",`num_attempts_noanswer`=`num_attempts_noanswer`+1";
                if($timeout>=0) $query3.=",`next_attempt_time`=ADDTIME(now(),SEC_TO_TIME($timeout))";
                $query3.=" WHERE `id`=$cc_id";
                if (!$A2B->DBHandle->Execute($query3)) die("Can't execute query '$query3'\n");
                $A2B->DbDisconnect();
                exit(0);
            }
        }
    }
    sleep(1);
}
?>

Old python callback daemon must be stopped and disabled from startup.
For func new features some adds for database needed... just run DataBase/mysql-5.x/UPDATE-a2billing-nixonch.sh
All code is fully compatible with main trunk of a2b project.
That daemon not work properly without full update to my fork.
Sourcecode you can download from github site by home link into my profile.

______
https://github.com/nixonch/a2billing Image


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 11 posts ] 
Hosted Voice Broadcast


All times are UTC


Who is online

Users browsing this forum: No registered users and 11 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group