app_queue: Add QueueWithdrawCaller AMI action

This adds a new AMI action called QueueWithdrawCaller.
This AMI action makes it possible to withdraw a caller from a queue,
in a safe and a generic manner.
This can be useful for retrieving a specific call and
dispatching it to a specific extension.
It works by signaling the caller to exit the queue application
whenever it can. Therefore, it is not guaranteed
that the call will leave the queue.

ASTERISK-29909 #close

Change-Id: Ic15aa238e23b2884abdcaadff2fda7679e29b7ec
This commit is contained in:
Kfir Itzhak 2022-02-09 12:28:29 +02:00 committed by Friendly Automation
parent 8666455bd8
commit 3959d20ba5
2 changed files with 159 additions and 1 deletions

View File

@ -290,6 +290,7 @@
<value name="JOINUNAVAIL" />
<value name="LEAVEUNAVAIL" />
<value name="CONTINUE" />
<value name="WITHDRAW" />
</variable>
<variable name="ABANDONED">
<para>If the call was not answered by an agent this variable will be TRUE.</para>
@ -298,6 +299,9 @@
<variable name="DIALEDPEERNUMBER">
<para>Resource of the agent that was dialed set on the outbound channel.</para>
</variable>
<variable name="QUEUE_WITHDRAW_INFO">
<para>If the call was successfully withdrawn from the queue, and the withdraw request was provided with optional withdraw info, the withdraw info will be stored in this variable.</para>
</variable>
</variablelist>
</description>
<see-also>
@ -1057,6 +1061,25 @@
<description>
</description>
</manager>
<manager name="QueueWithdrawCaller" language="en_US">
<synopsis>
Request to withdraw a caller from the queue back to the dialplan.
</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Queue" required="true">
<para>The name of the queue to take action on.</para>
</parameter>
<parameter name="Caller" required="true">
<para>The caller (channel) to withdraw from the queue.</para>
</parameter>
<parameter name="WithdrawInfo" required="false">
<para>Optional info to store. If the call is successfully withdrawn from the queue, this information will be available in the QUEUE_WITHDRAW_INFO variable.</para>
</parameter>
</syntax>
<description>
</description>
</manager>
<managerEvent language="en_US" name="QueueParams">
<managerEventInstance class="EVENT_FLAG_AGENT">
@ -1602,6 +1625,7 @@ enum queue_result {
QUEUE_LEAVEUNAVAIL = 5,
QUEUE_FULL = 6,
QUEUE_CONTINUE = 7,
QUEUE_WITHDRAW = 8,
};
static const struct {
@ -1616,6 +1640,7 @@ static const struct {
{ QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" },
{ QUEUE_FULL, "FULL" },
{ QUEUE_CONTINUE, "CONTINUE" },
{ QUEUE_WITHDRAW, "WITHDRAW" },
};
enum queue_timeout_priority {
@ -1684,6 +1709,8 @@ struct queue_ent {
time_t start; /*!< When we started holding */
time_t expire; /*!< When this entry should expire (time out of queue) */
int cancel_answered_elsewhere; /*!< Whether we should force the CAE flag on this call (C) option*/
unsigned int withdraw:1; /*!< Should this call exit the queue at its next iteration? Used for QueueWithdrawCaller */
char *withdraw_info; /*!< Optional info passed by the caller of QueueWithdrawCaller */
struct ast_channel *chan; /*!< Our channel */
AST_LIST_HEAD_NOLOCK(,penalty_rule) qe_rules; /*!< Local copy of the queue's penalty rules */
struct penalty_rule *pr; /*!< Pointer to the next penalty rule to implement */
@ -5802,6 +5829,13 @@ static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *r
/* This is the holding pen for callers 2 through maxlen */
for (;;) {
/* A request to withdraw this call from the queue arrived */
if (qe->withdraw) {
*reason = QUEUE_WITHDRAW;
res = 1;
break;
}
if (is_our_turn(qe)) {
break;
}
@ -7622,6 +7656,51 @@ static int change_priority_caller_on_queue(const char *queuename, const char *ca
}
/*! \brief Request to withdraw a caller from a queue
* \retval RES_NOSUCHQUEUE queue does not exist
* \retval RES_OKAY withdraw request sent
* \retval RES_NOT_CALLER queue exists but no caller
* \retval RES_EXISTS a withdraw request was already sent for this caller (channel) and queue
*/
static int request_withdraw_caller_from_queue(const char *queuename, const char *caller, const char *withdraw_info)
{
struct call_queue *q;
struct queue_ent *qe;
int res = RES_NOSUCHQUEUE;
/*! \note Ensure the appropriate realtime queue is loaded. Note that this
* short-circuits if the queue is already in memory. */
if (!(q = find_load_queue_rt_friendly(queuename))) {
return res;
}
ao2_lock(q);
res = RES_NOT_CALLER;
for (qe = q->head; qe; qe = qe->next) {
if (!strcmp(ast_channel_name(qe->chan), caller)) {
if (qe->withdraw) {
ast_debug(1, "Ignoring duplicate withdraw request of caller %s from queue %s\n", caller, queuename);
res = RES_EXISTS;
} else {
ast_debug(1, "Requested withdraw of caller %s from queue %s\n", caller, queuename);
/* It is not possible to change the withdraw info by further withdraw requests for this caller (channel)
in this queue, so we do not need to worry about a memory leak here. */
if (withdraw_info) {
qe->withdraw_info = ast_strdup(withdraw_info);
}
qe->withdraw = 1;
res = RES_OKAY;
}
break;
}
}
ao2_unlock(q);
queue_unref(q);
return res;
}
static int publish_queue_member_pause(struct call_queue *q, struct member *member)
{
struct ast_json *json_blob = queue_member_blob_create(q, member);
@ -8569,6 +8648,13 @@ check_turns:
/* they may dial a digit from the queue context; */
/* or, they may timeout. */
/* A request to withdraw this call from the queue arrived */
if (qe.withdraw) {
reason = QUEUE_WITHDRAW;
res = 1;
break;
}
/* Leave if we have exceeded our queuetimeout */
if (qe.expire && (time(NULL) >= qe.expire)) {
record_abandoned(&qe);
@ -8596,6 +8682,13 @@ check_turns:
}
}
/* A request to withdraw this call from the queue arrived */
if (qe.withdraw) {
reason = QUEUE_WITHDRAW;
res = 1;
break;
}
/* Leave if we have exceeded our queuetimeout */
if (qe.expire && (time(NULL) >= qe.expire)) {
record_abandoned(&qe);
@ -8670,7 +8763,14 @@ check_turns:
stop:
if (res) {
if (res < 0) {
if (reason == QUEUE_WITHDRAW) {
record_abandoned(&qe);
ast_queue_log(qe.parent->name, ast_channel_uniqueid(qe.chan), "NONE", "WITHDRAW", "%d|%d|%ld|%.40s", qe.pos, qe.opos, (long) (time(NULL) - qe.start), qe.withdraw_info ? qe.withdraw_info : "");
if (qe.withdraw_info) {
pbx_builtin_setvar_helper(qe.chan, "QUEUE_WITHDRAW_INFO", qe.withdraw_info);
}
res = 0;
} else if (res < 0) {
if (!qe.handled) {
record_abandoned(&qe);
ast_queue_log(args.queuename, ast_channel_uniqueid(chan), "NONE", "ABANDON",
@ -8690,6 +8790,13 @@ stop:
}
}
/* Free the optional withdraw info if present */
/* This is done here to catch all cases. e.g. if the call eventually wasn't withdrawn, e.g. answered */
if (qe.withdraw_info) {
ast_free(qe.withdraw_info);
qe.withdraw_info = NULL;
}
/* Don't allow return code > 0 */
if (res >= 0) {
res = 0;
@ -10743,6 +10850,41 @@ static int manager_change_priority_caller_on_queue(struct mansession *s, const s
return 0;
}
static int manager_request_withdraw_caller_from_queue(struct mansession *s, const struct message *m)
{
const char *queuename, *caller, *withdraw_info;
queuename = astman_get_header(m, "Queue");
caller = astman_get_header(m, "Caller");
withdraw_info = astman_get_header(m, "WithdrawInfo");
if (ast_strlen_zero(queuename)) {
astman_send_error(s, m, "'Queue' not specified.");
return 0;
}
if (ast_strlen_zero(caller)) {
astman_send_error(s, m, "'Caller' not specified.");
return 0;
}
switch (request_withdraw_caller_from_queue(queuename, caller, withdraw_info)) {
case RES_OKAY:
astman_send_ack(s, m, "Withdraw requested successfully");
break;
case RES_NOSUCHQUEUE:
astman_send_error(s, m, "Unable to request withdraw from queue: No such queue");
break;
case RES_NOT_CALLER:
astman_send_error(s, m, "Unable to request withdraw from queue: No such caller");
break;
case RES_EXISTS:
astman_send_error(s, m, "Unable to request withdraw from queue: Already requested");
break;
}
return 0;
}
static char *handle_queue_add_member(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
@ -11472,6 +11614,7 @@ static int unload_module(void)
ast_manager_unregister("QueueReset");
ast_manager_unregister("QueueMemberRingInUse");
ast_manager_unregister("QueueChangePriorityCaller");
ast_manager_unregister("QueueWithdrawCaller");
ast_unregister_application(app_aqm);
ast_unregister_application(app_rqm);
ast_unregister_application(app_pqm);
@ -11585,6 +11728,7 @@ static int load_module(void)
err |= ast_manager_register_xml("QueueReload", 0, manager_queue_reload);
err |= ast_manager_register_xml("QueueReset", 0, manager_queue_reset);
err |= ast_manager_register_xml("QueueChangePriorityCaller", 0, manager_change_priority_caller_on_queue);
err |= ast_manager_register_xml("QueueWithdrawCaller", 0, manager_request_withdraw_caller_from_queue);
err |= ast_custom_function_register(&queuevar_function);
err |= ast_custom_function_register(&queueexists_function);
err |= ast_custom_function_register(&queuemembercount_function);

View File

@ -0,0 +1,14 @@
Subject: app_queue
Added a new AMI action: QueueWithdrawCaller
This AMI action makes it possible to withdraw a caller from a queue
back to the dialplan. The call will be signaled to leave the queue
whenever it can, hence, it not guaranteed that the call will leave
the queue.
Optional custom data can be passed in the request, in the WithdrawInfo
parameter. If the call successfully withdrawn the queue,
it can be retrieved using the QUEUE_WITHDRAW_INFO variable.
This can be useful for certain uses, such as dispatching the call
to a specific extension.