From f4e69acdf3eb8390a392d48e63cb87fa76d2c19c Mon Sep 17 00:00:00 2001 From: irroot Date: Wed, 29 Jun 2011 06:39:26 +0000 Subject: [PATCH] Commit "distrotech" app_queue changes to Trunk * Added general option negative_penalty_invalid default off. when set members are seen as invalid/logged out when there penalty is negative. for realtime members when set remove from queue will set penalty to -1. * Added queue option autopausedelay when autopause is enabled it will be delayed for this number of seconds since last successful call if there was no prior call the agent will be autopaused immediately. * Added member option ignorebusy this when set and ringinuse is not will allow per member control of multiple calls as ringinuse does for the Queue. - Mark QUEUE_MEMBER_PENALTY Deprecated it never worked for realtime members - QUEUE_MEMBER is now R/W supporting setting paused, ignorebusy and penalty. (closes issue ASTERISK-17421) (closes issue ASTERISK-17391) Reported by: irroot Tested by: irroot, jrose Review: https://reviewboard.asterisk.org/r/1119/ git-svn-id: http://svn.digium.com/svn/asterisk/trunk@325483 f38db490-d61c-443f-a65b-d21fe96a405b --- CHANGES | 9 + UPGRADE.txt | 4 + apps/app_queue.c | 360 +++++++++++++++++++++++++++---------- configs/queues.conf.sample | 11 ++ 4 files changed, 290 insertions(+), 94 deletions(-) diff --git a/CHANGES b/CHANGES index 1dfadcbf7..2dcc9b5e2 100644 --- a/CHANGES +++ b/CHANGES @@ -439,6 +439,15 @@ Queue changes supports sending the event arguments to 5 individual fields, although it will fallback to the previous data definition, if the new table layout is not found. + * Added general option negative_penalty_invalid default off. when set + members are seen as invalid/logged out when there penalty is negative. + for realtime members when set remove from queue will set penalty to -1. + * Added queue option autopausedelay when autopause is enabled it will be + delayed for this number of seconds since last successful call if there + was no prior call the agent will be autopaused immediately. + * Added member option ignorebusy this when set and ringinuse is not + will allow per member control of multiple calls as ringinuse does for + the Queue. mISDN channel driver (chan_misdn) changes ---------------------------------------- diff --git a/UPGRADE.txt b/UPGRADE.txt index 3525621d3..dfa85684e 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -50,5 +50,9 @@ pbx_lua: - the autoservice now defaults to being on by default - autoservice_start() and autoservice_start() no longer return a value. +Queue: + - Mark QUEUE_MEMBER_PENALTY Deprecated it never worked for realtime members + - QUEUE_MEMBER is now R/W supporting setting paused, ignorebusy and penalty. + =========================================================== =========================================================== diff --git a/apps/app_queue.c b/apps/app_queue.c index be929c1dc..b9beafdd5 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -521,11 +521,25 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") Returns the total number of members for the specified queue. + + Gets or sets queue member penalty. + + + Gets or sets queue member paused status. + + + Gets or sets queue member ignorebusy. + + - Returns the number of members currently associated with the specified queuename. + Allows access to queue counts [R] and member information [R/W]. + + queuename is required for all operations + interface is required for all member operations. + Queue @@ -658,6 +672,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") Gets or sets queue members penalty. + This function has been deprecated in favor of the QUEUE_MEMBER() function Queue @@ -934,6 +949,9 @@ static struct ast_event_sub *device_state_sub; /*! \brief queues.conf [general] option */ static int update_cdr = 0; +/*! \brief queues.conf [general] option */ +static int negative_penalty_invalid = 0; + enum queue_result { QUEUE_UNKNOWN = 0, QUEUE_TIMEOUT = 1, @@ -1043,6 +1061,7 @@ struct member { unsigned int dead:1; /*!< Used to detect members deleted in realtime */ unsigned int delme:1; /*!< Flag to delete entry on reload */ char rt_uniqueid[80]; /*!< Unique id of realtime member entry */ + unsigned int ignorebusy:1; /*!< Flag to ignore member if the status is not available */ }; enum empty_conditions { @@ -1160,6 +1179,7 @@ struct call_queue { int timeout; /*!< How long to wait for an answer */ int weight; /*!< Respective weight */ int autopause; /*!< Auto pause queue members if they fail to answer */ + int autopausedelay; /*!< Delay auto pause for autopausedelay seconds since last call */ int timeoutpriority; /*!< Do we allow a fraction of the timeout to occur for a ring? */ /* Queue strategy things */ @@ -1190,9 +1210,10 @@ static AST_LIST_HEAD_STATIC(rule_lists, rule_list); static struct ao2_container *queues; static void update_realtime_members(struct call_queue *q); +static struct member *interface_exists(struct call_queue *q, const char *interface); static int set_member_paused(const char *queuename, const char *interface, const char *reason, int paused); -static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan); +static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan); /*! \brief sets the QUEUESTATUS channel variable */ static void set_queue_result(struct ast_channel *chan, enum queue_result res) { @@ -1698,6 +1719,7 @@ static void init_queue(struct call_queue *q) q->numperiodicannounce = 0; q->autopause = QUEUE_AUTOPAUSE_OFF; q->timeoutpriority = TIMEOUT_PRIORITY_APP; + q->autopausedelay = 0; if (!q->members) { if (q->strategy == QUEUE_STRATEGY_LINEAR || q->strategy == QUEUE_STRATEGY_RRORDERED) /* linear strategy depends on order, so we have to place all members in a single bucket */ @@ -2003,6 +2025,8 @@ static void queue_set_param(struct call_queue *q, const char *param, const char q->montype = 1; } else if (!strcasecmp(param, "autopause")) { q->autopause = autopause2int(val); + } else if (!strcasecmp(param, "autopausedelay")) { + q->autopausedelay = atoi(val); } else if (!strcasecmp(param, "maxlen")) { q->maxlen = atoi(val); if (q->maxlen < 0) @@ -2081,7 +2105,9 @@ static void rt_handle_member_record(struct call_queue *q, char *interface, struc int penalty = 0; int paused = 0; int found = 0; + int ignorebusy = 0; + const char *config_val; const char *rt_uniqueid = ast_variable_retrieve(member_config, interface, "uniqueid"); const char *membername = S_OR(ast_variable_retrieve(member_config, interface, "membername"), interface); const char *state_interface = S_OR(ast_variable_retrieve(member_config, interface, "state_interface"), interface); @@ -2095,8 +2121,11 @@ static void rt_handle_member_record(struct call_queue *q, char *interface, struc if (penalty_str) { penalty = atoi(penalty_str); - if (penalty < 0) + if ((penalty < 0) && negative_penalty_invalid) { + return; + } else if (penalty < 0) { penalty = 0; + } } if (paused_str) { @@ -2105,31 +2134,39 @@ static void rt_handle_member_record(struct call_queue *q, char *interface, struc paused = 0; } - /* Find member by realtime uniqueid and update */ - mem_iter = ao2_iterator_init(q->members, 0); - while ((m = ao2_iterator_next(&mem_iter))) { - if (!strcasecmp(m->rt_uniqueid, rt_uniqueid)) { - m->dead = 0; /* Do not delete this one. */ - ast_copy_string(m->rt_uniqueid, rt_uniqueid, sizeof(m->rt_uniqueid)); - if (paused_str) - m->paused = paused; - if (strcasecmp(state_interface, m->state_interface)) { - ast_copy_string(m->state_interface, state_interface, sizeof(m->state_interface)); - } - m->penalty = penalty; - found = 1; - ao2_ref(m, -1); - break; - } - ao2_ref(m, -1); - } + if ((config_val = ast_variable_retrieve(member_config, interface, "ignorebusy"))) { + ignorebusy = ast_true(config_val); + } else { + ignorebusy = 1; + } + + /* Find member by realtime uniqueid and update */ + mem_iter = ao2_iterator_init(q->members, 0); + while ((m = ao2_iterator_next(&mem_iter))) { + if (!strcasecmp(m->rt_uniqueid, rt_uniqueid)) { + m->dead = 0; /* Do not delete this one. */ + ast_copy_string(m->rt_uniqueid, rt_uniqueid, sizeof(m->rt_uniqueid)); + if (paused_str) + m->paused = paused; + if (strcasecmp(state_interface, m->state_interface)) { + ast_copy_string(m->state_interface, state_interface, sizeof(m->state_interface)); + } + m->penalty = penalty; + m->ignorebusy = ignorebusy; + found = 1; + ao2_ref(m, -1); + break; + } + ao2_ref(m, -1); + } ao2_iterator_destroy(&mem_iter); - /* Create a new member */ - if (!found) { + /* Create a new member */ + if (!found) { if ((m = create_queue_member(interface, membername, penalty, paused, state_interface))) { m->dead = 0; m->realtime = 1; + m->ignorebusy = ignorebusy; ast_copy_string(m->rt_uniqueid, rt_uniqueid, sizeof(m->rt_uniqueid)); ast_queue_log(q->name, "REALTIME", m->interface, "ADDMEMBER", "%s", ""); ao2_link(q->members, m); @@ -2873,16 +2910,24 @@ static int num_available_members(struct call_queue *q) mem_iter = ao2_iterator_init(q->members, 0); while ((mem = ao2_iterator_next(&mem_iter))) { switch (mem->status) { - case AST_DEVICE_INUSE: - if (!q->ringinuse) + case AST_DEVICE_INVALID: + case AST_DEVICE_UNAVAILABLE: + break; + case AST_DEVICE_INUSE: + case AST_DEVICE_BUSY: + case AST_DEVICE_RINGING: + case AST_DEVICE_RINGINUSE: + case AST_DEVICE_ONHOLD: + if ((!q->ringinuse) || (!mem->ignorebusy)) { + break; + } + /* else fall through */ + case AST_DEVICE_NOT_INUSE: + case AST_DEVICE_UNKNOWN: + if (!mem->paused) { + avl++; + } break; - /* else fall through */ - case AST_DEVICE_NOT_INUSE: - case AST_DEVICE_UNKNOWN: - if (!mem->paused) { - avl++; - } - break; } ao2_ref(mem, -1); @@ -3010,38 +3055,54 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies char tech[256]; char *location; const char *macrocontext, *macroexten; + enum ast_device_state newstate; /* on entry here, we know that tmp->chan == NULL */ + if (tmp->member->paused) { + ast_debug(1, "%s paused, can't receive call\n", tmp->interface); + if (qe->chan->cdr) { + ast_cdr_busy(qe->chan->cdr); + } + tmp->stillgoing = 0; + return 0; + } + if ((tmp->lastqueue && tmp->lastqueue->wrapuptime && (time(NULL) - tmp->lastcall < tmp->lastqueue->wrapuptime)) || (!tmp->lastqueue && qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime))) { - ast_debug(1, "Wrapuptime not yet expired on queue %s for %s\n", + ast_debug(1, "Wrapuptime not yet expired on queue %s for %s\n", (tmp->lastqueue ? tmp->lastqueue->name : qe->parent->name), tmp->interface); - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_cdr_busy(qe->chan->cdr); + } tmp->stillgoing = 0; (*busies)++; return 0; } - if (!qe->parent->ringinuse && (tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) { - ast_debug(1, "%s in use, can't receive call\n", tmp->interface); - if (qe->chan->cdr) - ast_cdr_busy(qe->chan->cdr); - tmp->stillgoing = 0; - return 0; + if (!qe->parent->ringinuse || !tmp->member->ignorebusy) { + if ((tmp->member->status == AST_DEVICE_UNKNOWN) || (tmp->member->status == AST_DEVICE_NOT_INUSE)) { + newstate = ast_parse_device_state(tmp->member->interface); + if (newstate != tmp->member->status) { + ast_log(LOG_ERROR, "Found a channel matching iterface %s while status was %i changed to %i\n", + tmp->member->interface, tmp->member->status, newstate); + update_status(qe->parent, tmp->member, newstate); + } + } + if ((tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) { + ast_debug(1, "%s in use, can't receive call\n", tmp->interface); + if (qe->chan->cdr) { + ast_cdr_busy(qe->chan->cdr); + } + tmp->stillgoing = 0; + return 0; + } } - if (tmp->member->paused) { - ast_debug(1, "%s paused, can't receive call\n", tmp->interface); - if (qe->chan->cdr) - ast_cdr_busy(qe->chan->cdr); - tmp->stillgoing = 0; - return 0; - } if (use_weight && compare_weight(qe->parent,tmp->member)) { ast_debug(1, "Priority queue delaying call to %s:%s\n", qe->parent->name, tmp->interface); - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_cdr_busy(qe->chan->cdr); + } tmp->stillgoing = 0; (*busies)++; return 0; @@ -3056,8 +3117,9 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies /* Request the peer */ tmp->chan = ast_request(tech, qe->chan->nativeformats, qe->chan, location, &status); if (!tmp->chan) { /* If we can't, just go on to the next call */ - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_cdr_busy(qe->chan->cdr); + } tmp->stillgoing = 0; ao2_lock(qe->parent); @@ -3396,6 +3458,18 @@ static void rna(int rnatime, struct queue_ent *qe, char *interface, char *member } ast_queue_log(qe->parent->name, qe->chan->uniqueid, membername, "RINGNOANSWER", "%d", rnatime); if (qe->parent->autopause != QUEUE_AUTOPAUSE_OFF && pause) { + if (qe->parent->autopausedelay > 0) { + struct member *mem; + ao2_lock(qe->parent); + if ((mem = interface_exists(qe->parent, interface))) { + time_t idletime = time(&idletime)-mem->lastcall; + if ((mem->lastcall != 0) && (qe->parent->autopausedelay > idletime)) { + ao2_unlock(qe->parent); + return; + } + } + ao2_unlock(qe->parent); + } if (qe->parent->autopause == QUEUE_AUTOPAUSE_ON) { if (!set_member_paused(qe->parent->name, interface, "Auto-Pause", 1)) { ast_verb(3, "Auto-Pausing Queue Member %s in queue %s since they failed to answer.\n", @@ -4707,8 +4781,9 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce else ast_moh_stop(qe->chan); /* If appropriate, log that we have a destination channel */ - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_cdr_setdestchan(qe->chan->cdr, peer->name); + } /* Make sure channels are compatible */ res = ast_channel_make_compatible(qe->chan, peer); if (res < 0) { @@ -4788,10 +4863,11 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce if (mixmonapp) { ast_debug(1, "Starting MixMonitor as requested.\n"); if (!monitorfilename) { - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_copy_string(tmpid, qe->chan->cdr->uniqueid, sizeof(tmpid)); - else + } else { snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random()); + } } else { const char *m = monitorfilename; for (p = tmpid2; p < tmpid2 + sizeof(tmpid2) - 1; p++, m++) { @@ -4858,12 +4934,13 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce ast_debug(1, "Arguments being passed to MixMonitor: %s\n", mixmonargs); /* We purposely lock the CDR so that pbx_exec does not update the application data */ - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_set_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED); + } pbx_exec(qe->chan, mixmonapp, mixmonargs); - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_clear_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED); - + } } else { ast_log(LOG_WARNING, "Asked to run MixMonitor on this call, but cannot find the MixMonitor app!\n"); } @@ -5180,7 +5257,10 @@ static int remove_from_queue(const char *queuename, const char *interface) ao2_lock(q); if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) { /* XXX future changes should beware of this assumption!! */ - if (!mem->dynamic) { + /*Change Penalty on realtime users*/ + if (mem->realtime && !ast_strlen_zero(mem->rt_uniqueid) && negative_penalty_invalid) { + update_realtime_member_field(mem, q->name, "penalty", "-1"); + } else if (!mem->dynamic) { ao2_ref(mem, -1); ao2_unlock(q); queue_t_unref(q, "Interface wasn't dynamic, expiring temporary reference"); @@ -5355,35 +5435,34 @@ static int set_member_penalty(const char *queuename, const char *interface, int int foundinterface = 0, foundqueue = 0; struct call_queue *q; struct member *mem; - struct ao2_iterator queue_iter; + char rtpenalty[80]; - if (penalty < 0) { + if (penalty < 0 && !negative_penalty_invalid) { ast_log(LOG_ERROR, "Invalid penalty (%d)\n", penalty); return RESULT_FAILURE; } - queue_iter = ao2_iterator_init(queues, 0); - while ((q = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) { + if ((q = load_realtime_queue(queuename))) { + foundqueue++; ao2_lock(q); - if (ast_strlen_zero(queuename) || !strcasecmp(q->name, queuename)) { - foundqueue++; - if ((mem = interface_exists(q, interface))) { - foundinterface++; + if ((mem = interface_exists(q, interface))) { + foundinterface++; + if (!mem->realtime) { mem->penalty = penalty; - - ast_queue_log(q->name, "NONE", interface, "PENALTY", "%d", penalty); - manager_event(EVENT_FLAG_AGENT, "QueueMemberPenalty", - "Queue: %s\r\n" - "Location: %s\r\n" - "Penalty: %d\r\n", - q->name, mem->interface, penalty); - ao2_ref(mem, -1); + } else { + sprintf(rtpenalty,"%i", penalty); + update_realtime_member_field(mem, q->name, "penalty", rtpenalty); } + ast_queue_log(q->name, "NONE", interface, "PENALTY", "%d", penalty); + manager_event(EVENT_FLAG_AGENT, "QueueMemberPenalty", + "Queue: %s\r\n" + "Location: %s\r\n" + "Penalty: %d\r\n", + q->name, mem->interface, penalty); + ao2_ref(mem, -1); } ao2_unlock(q); - queue_t_unref(q, "Done with iterator"); } - ao2_iterator_destroy(&queue_iter); if (foundinterface) { return RESULT_SUCCESS; @@ -6157,31 +6236,37 @@ static int queue_function_exists(struct ast_channel *chan, const char *cmd, char return 0; } -/*! +/*! * \brief Get number either busy / free / ready or total members of a specific queue - * \retval number of members (busy / free / ready / total) + * \brief Get or set member properties penalty / paused / ignorebusy + * \retval number of members (busy / free / ready / total) or member info (penalty / paused / ignorebusy) * \retval -1 on error */ -static int queue_function_qac(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +static int queue_function_mem_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) { int count = 0; struct member *m; struct ao2_iterator mem_iter; struct call_queue *q; - char *option; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(option); + AST_APP_ARG(interface); + ); + /* Make sure the returned value on error is zero length string. */ + buf[0] = '\0'; if (ast_strlen_zero(data)) { ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd); return -1; } - if ((option = strchr(data, ','))) - *option++ = '\0'; - else - option = "logged"; - if ((q = load_realtime_queue(data))) { + AST_STANDARD_APP_ARGS(args, data); + + if ((q = load_realtime_queue(args.queuename))) { ao2_lock(q); - if (!strcasecmp(option, "logged")) { + if (!strcasecmp(args.option, "logged")) { mem_iter = ao2_iterator_init(q->members, 0); while ((m = ao2_iterator_next(&mem_iter))) { /* Count the agents who are logged in and presently answering calls */ @@ -6191,7 +6276,7 @@ static int queue_function_qac(struct ast_channel *chan, const char *cmd, char *d ao2_ref(m, -1); } ao2_iterator_destroy(&mem_iter); - } else if (!strcasecmp(option, "free")) { + } else if (!strcasecmp(args.option, "free")) { mem_iter = ao2_iterator_init(q->members, 0); while ((m = ao2_iterator_next(&mem_iter))) { /* Count the agents who are logged in and presently answering calls */ @@ -6201,7 +6286,7 @@ static int queue_function_qac(struct ast_channel *chan, const char *cmd, char *d ao2_ref(m, -1); } ao2_iterator_destroy(&mem_iter); - } else if (!strcasecmp(option, "ready")) { + } else if (!strcasecmp(args.option, "ready")) { time_t now; time(&now); mem_iter = ao2_iterator_init(q->members, 0); @@ -6214,22 +6299,104 @@ static int queue_function_qac(struct ast_channel *chan, const char *cmd, char *d ao2_ref(m, -1); } ao2_iterator_destroy(&mem_iter); - } else /* must be "count" */ + } else if (!strcasecmp(args.option, "count") || ast_strlen_zero(args.option)) { count = q->membercount; + } else if (!strcasecmp(args.option, "penalty") && !ast_strlen_zero(args.interface) && + ((m = interface_exists(q, args.interface)))) { + count = m->penalty; + } else if (!strcasecmp(args.option, "paused") && !ast_strlen_zero(args.interface) && + ((m = interface_exists(q, args.interface)))) { + count = m->paused; + } else if (!strcasecmp(args.option, "ignorebusy") && !ast_strlen_zero(args.interface) && + ((m = interface_exists(q, args.interface)))) { + count = m->ignorebusy; + } ao2_unlock(q); queue_t_unref(q, "Done with temporary reference in QUEUE_MEMBER()"); - } else - ast_log(LOG_WARNING, "queue %s was not found\n", data); + } else { + ast_log(LOG_WARNING, "queue %s was not found\n", args.queuename); + } snprintf(buf, len, "%d", count); return 0; } -/*! +/*! \brief Dialplan function QUEUE_MEMBER() Sets the members penalty / paused / ignorebusy. */ +static int queue_function_mem_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) +{ + int memvalue; + struct call_queue *q; + struct member *m; + char rtvalue[80]; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(option); + AST_APP_ARG(interface); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Missing argument. QUEUE_MEMBER(,