52
52
#include "asterisk/strings.h"
53
53
#include "asterisk/musiconhold.h"
54
54
#include "asterisk/format_cache.h"
55
+ #include "asterisk/devicestate.h"
55
56
56
57
/*** DOCUMENTATION
57
58
<application name="CCSA" language="en_US">
135
136
<value name="NO_ROUTES">
136
137
No routes available for CCSA destination
137
138
</value>
139
+ <value name="UNROUTABLE">
140
+ Destination cannot be reached using any routes
141
+ </value>
138
142
<value name="UNAUTHORIZED">
139
143
Not authorized for the available CCSA routes
140
144
</value>
276
280
<synopsis>Threshold of queued priority 3 calls at which it is unlikely an additional call would be able to queue successfully at priority 3 without timing out.</synopsis>
277
281
<description>
278
282
<para>If this threshold of queued priority 3 calls is exceeded, additional calls will not be allowed to Off Hook Queue until queued priority 3 calls drop below this threshold again.</para>
283
+ <para>To completely disable queuing against this route, specify a negative value (e.g. <literal>-1</literal>). This is useful when dealing with virtual routes against which queuing does not make sense, such as IP-based (e.g. SIP) trunks.</para>
279
284
</description>
280
285
</configOption>
281
286
<configOption name="limit" default="0">
312
317
<para>By default, when set to no, BUSY is treated as a successful call outcome, and alternate routing will stop at this point (appropriate for IP trunking). For analog trunking, you will probably want to set this to yes, so that if BUSY is received, alternate routing will continue using other routes.</para>
313
318
</description>
314
319
</configOption>
320
+ <configOption name="devstate">
321
+ <synopsis>Aggregate device state for trunk group</synopsis>
322
+ <description>
323
+ <para>If specified, a list of devices whose aggregate device state will be used to determine the current status of the trunk group (whether idle or in use).</para>
324
+ </description>
325
+ </configOption>
315
326
<configOption name="time">
316
327
<synopsis>Time restrictions for route</synopsis>
317
328
<description>
@@ -468,6 +479,7 @@ struct route {
468
479
char facility [AST_MAX_CONTEXT ]; /*!< Facility Name */
469
480
enum facility_type factype ; /*!< Facility Type */
470
481
char dialstr [PATH_MAX ]; /*!< Dial string */
482
+ char * devstate ; /*!< Device state */
471
483
unsigned int threshold ; /*!< Threshold at which facility is "saturated" */
472
484
unsigned int limit ; /*!< Concurrent call limit */
473
485
unsigned int frl :3 ; /*!< Minimum Facility Restriction Level required */
@@ -956,6 +968,22 @@ static int route_num_priority3_calls(const char *route)
956
968
return pri3calls ;
957
969
}
958
970
971
+ static int route_has_any_calls (const char * route )
972
+ {
973
+ struct ccsa_call * call ;
974
+
975
+ AST_RWLIST_RDLOCK (& calls );
976
+ AST_LIST_TRAVERSE (& calls , call , entry ) {
977
+ if (!strcmp (route , call -> route )) {
978
+ AST_RWLIST_UNLOCK (& calls );
979
+ return 1 ;
980
+ }
981
+ }
982
+ AST_RWLIST_UNLOCK (& calls );
983
+
984
+ return 0 ;
985
+ }
986
+
959
987
static int cbq_calls_pending (const char * caller )
960
988
{
961
989
struct ccsa_call * call ;
@@ -1079,35 +1107,138 @@ static int store_dtmf(struct ast_channel *chan)
1079
1107
return 0 ;
1080
1108
}
1081
1109
1082
- static int route_permits_ohq ( const char * route )
1110
+ static int route_permits_ohq_locked ( struct route * f , time_t elapsed , int is_simulation )
1083
1111
{
1084
- int threshold = 0 ;
1085
- struct route * f ;
1086
1112
/* Nortel 553-2751-101 4.00, Off-Hook Queuing availability:
1087
1113
* Each trunk route has a threshold value that indicates the maximum number of priority 3 calls
1088
1114
* that can be queued against it before OHQ timeout becomes a high probability.
1089
1115
* Before a call is placed in the OHQ, the current queue count is compared with the
1090
1116
* threshold value for each eligible trunk route in the initial set of routes.
1091
1117
* If at least one of the trunk routes has a count less than or equal to the threshold value,
1092
1118
* the call is allowed to perform OHQ against all OHQ eligible routes. */
1093
- int pri3calls = route_num_priority3_calls (route );
1094
- AST_RWLIST_RDLOCK (& routes );
1095
- f = find_route (route , 0 );
1096
- threshold = f ? f -> threshold : 0 ;
1097
- if (!ast_strlen_zero (f -> time ) && time_matches (f -> time )) {
1098
- f = NULL ; /* Route time restrictions not satisfied, so OHQ not permitted either */
1119
+ int pri3calls ;
1120
+
1121
+ /* XXX If the route currently has no calls, it may not make sense to queue.
1122
+ * For example, if we tried route 0 and the call failed, but route 0 was
1123
+ * using a trunk group with 0 calls, then there is no point in off-hook
1124
+ * queuing on route 0, since it wasn't in use in the first place,
1125
+ * so off-hooking queuing on it won't accomplish anything.
1126
+ *
1127
+ * Note that routes do not correspond 1:1 to trunk groups,
1128
+ * so to determine if the underlying trunk group is currently saturated or not,
1129
+ * we need to be able to determine the "device state" of the trunk group. */
1130
+ if (!f -> limit ) {
1131
+ /* If there is no limit, then this is a "virtual" trunk group, essentially,
1132
+ * i.e. there is no hard cap on allowed calls. Common for enterprise SIP trunking. */
1133
+ ast_debug (6 , "Facility %s does not have a defined capacity, won't queue against it\n" , f -> name );
1134
+ return 0 ;
1099
1135
}
1100
- AST_RWLIST_UNLOCK (& routes );
1101
- /* Would've already thrown a warning about this route not existing if it didn't, so don't do it again. */
1102
- if (!f ) {
1136
+
1137
+ if (f -> threshold < 0 ) {
1138
+ /* Queuing explicitly disabled */
1139
+ ast_debug (6 , "Facility %s queuing disabled\n" , f -> name );
1103
1140
return 0 ;
1104
1141
}
1105
- if (pri3calls > threshold ) {
1142
+
1143
+ if (!ast_strlen_zero (f -> time ) && time_matches (f -> time )) {
1144
+ ast_debug (6 , "Facility %s skipped due to time restrictions\n" , f -> name );
1145
+ return 0 ; /* Route time restrictions not satisfied, so OHQ not permitted either */
1146
+ }
1147
+
1148
+ pri3calls = route_num_priority3_calls (f -> name );
1149
+ if (pri3calls > f -> threshold ) {
1150
+ ast_debug (6 , "Facility %s skipped (too many priority 3 calls queued: %d)\n" , f -> name , pri3calls );
1106
1151
return 0 ; /* So many queued calls already that it's unlikely OHQ call will succeed in queue before timing out */
1107
1152
}
1153
+
1154
+ /* Only check current call status if not in simulation */
1155
+ if (!is_simulation ) {
1156
+ if (f -> devstate ) {
1157
+ /* Even if there is a call limit, like for analog trunk groups with a fixed number of circuits,
1158
+ * if the entire trunk group is idle, it is still inappropriate to queue against it,
1159
+ * since the trunk group isn't currently saturated.
1160
+ *
1161
+ * However, there is no way to automatically determine ourselves if the trunk group is completely
1162
+ * saturated in this case. Multiple routes could use certain circuits, and those circuits could
1163
+ * also be used outside of this module, so we can't just calculate the device state.
1164
+ * In this case, it would make sense to lookup an "aggregate device state" for the entire trunk group,
1165
+ * and only if that comes back as "all trunks in the trunk group are busy" would queuing make sense.
1166
+ * Conversely, if no trunks are busy, then queuing also doesn't make sense. */
1167
+ enum ast_device_state devstate = ast_device_state (f -> devstate );
1168
+ switch (devstate ) {
1169
+ case AST_DEVICE_NOT_INUSE :
1170
+ /* The entire trunk group is currently "idle",
1171
+ * so if a previous attempt to use this route just now failed,
1172
+ * why try it again?
1173
+ * It's important that we bound this based on the time to do the
1174
+ * first pass of all the routes. Otherwise, a facility might have
1175
+ * been busy on the first attempt but might be free now.
1176
+ *
1177
+ * XXX A more reliable way to do this would be keep track
1178
+ * of the device state associated with each route as we attempt using
1179
+ * it the first time. If it was NOTINUSE, then that means we should
1180
+ * NOT try it the second pass, no matter what.
1181
+ * The logic here just assumes if not very much time has elapsed since
1182
+ * the first pass, the state now will be whatever it was a moment ago,
1183
+ * but technically it's the older state that we want here.
1184
+ * Because some calls could technically still clear in the same second,
1185
+ * there is a slight chance some calls could've queued that will now be excluded.
1186
+ * Beyond 1 second, the chance of a race condition is too great to do this.*/
1187
+ if (elapsed < 1 ) {
1188
+ ast_debug (6 , "Facility %s skipped (trunk group isn't in use now and probably wasn't on last attempt)\n" , f -> name );
1189
+ return 0 ;
1190
+ }
1191
+ /* Fall through */
1192
+ default :
1193
+ /* Okay to queue against, or at least, these don't preclude queuing */
1194
+ break ;
1195
+ }
1196
+ }
1197
+
1198
+ if (!route_has_any_calls (f -> name )) {
1199
+ /* The only way that off-hook queuing will end successfully is if there is a call currently
1200
+ * in the queue that ends, which will write to the alertpipe and wake up a call
1201
+ * to go ahead and try again.
1202
+ * If there aren't any calls, even if the trunk group has capacity, there isn't anything
1203
+ * to wake us up, so it won't work anyways, so don't queue for no reason.
1204
+ *
1205
+ * XXX In the future, the option could be added to "poll" the device state periodically to retry
1206
+ * the call attempt, which would better handle facilities used outside of this application. */
1207
+ ast_debug (6 , "Facility %s skipped (doesn't have any calls currently, so nothing to wait for)\n" , f -> name );
1208
+ return 0 ;
1209
+ }
1210
+ }
1211
+
1108
1212
return 1 ;
1109
1213
}
1110
1214
1215
+ static int route_permits_ohq (const char * route , time_t elapsed , int is_simulation )
1216
+ {
1217
+ struct route * f ;
1218
+ int can_queue ;
1219
+
1220
+ AST_RWLIST_RDLOCK (& routes );
1221
+ f = find_route (route , 0 );
1222
+ if (!f ) {
1223
+ /* Would've already thrown a warning about this route not existing if it didn't, so don't do it again. */
1224
+ can_queue = 0 ;
1225
+ } else {
1226
+ can_queue = route_permits_ohq_locked (f , elapsed , is_simulation );
1227
+ }
1228
+ AST_RWLIST_UNLOCK (& routes );
1229
+
1230
+ return can_queue ;
1231
+ }
1232
+
1233
+ static int route_permits_cbq (const char * route )
1234
+ {
1235
+ int permitted ;
1236
+ AST_RWLIST_RDLOCK (& routes );
1237
+ permitted = route_has_any_calls (route );
1238
+ AST_RWLIST_UNLOCK (& routes );
1239
+ return permitted ;
1240
+ }
1241
+
1111
1242
static int off_hook_queue (struct ast_channel * chan , struct ccsa_call * call , int ohq )
1112
1243
{
1113
1244
int timeout , ms , remaining_time ;
@@ -1313,13 +1444,9 @@ static void *call_back_queue(void *data)
1313
1444
int advanced = 0 ;
1314
1445
1315
1446
newroute = ast_strdup (call -> nextroute );
1316
- if (!newroute ) {
1317
- ast_log (LOG_WARNING , "strdup failed\n" );
1318
- } else {
1447
+ if (newroute ) {
1319
1448
newfacility = ast_strdup (next_facility );
1320
- if (!newfacility ) {
1321
- ast_log (LOG_WARNING , "strdup failed\n" );
1322
- } else {
1449
+ if (newfacility ) {
1323
1450
AST_RWLIST_WRLOCK (& calls );
1324
1451
/* Make sure we're still in the list, too. */
1325
1452
AST_LIST_TRAVERSE (& calls , ecall , entry ) {
@@ -1740,6 +1867,7 @@ static int ccsa_run(struct ast_channel *chan, int fd, const char *exten, const c
1740
1867
char * auth_sub_context = NULL ;
1741
1868
char * callback_caller_context = NULL , * callback_dest_context = NULL ;
1742
1869
unsigned int queue_promo_timer , route_advance_timer ;
1870
+ time_t start ;
1743
1871
1744
1872
AST_RWLIST_RDLOCK (& ccsas );
1745
1873
c = find_ccsa (ccsa , 0 );
@@ -1831,6 +1959,7 @@ static int ccsa_run(struct ast_channel *chan, int fd, const char *exten, const c
1831
1959
1832
1960
ast_assert (chan || fd >= 0 );
1833
1961
ccsa_log (chan , fd , "Beginning CCSA call\n" );
1962
+ start = time (NULL );
1834
1963
1835
1964
routes = ast_strdupa (faclist );
1836
1965
while ((route = strsep (& routes , "|" ))) {
@@ -1890,7 +2019,6 @@ static int ccsa_run(struct ast_channel *chan, int fd, const char *exten, const c
1890
2019
ast_log (LOG_WARNING , "Invalid MLPP preemption capability: %c\n" , isprint (preempt ) ? preempt : '?' );
1891
2020
} else {
1892
2021
/* Now, try preempting an existing call before we queue. */
1893
-
1894
2022
try_preempt = preempt ;
1895
2023
ccsa_log (chan , fd , "Trying to preempt calls < '%c'\n" , preempt );
1896
2024
routes = ast_strdupa (faclist );
@@ -1948,6 +2076,7 @@ static int ccsa_run(struct ast_channel *chan, int fd, const char *exten, const c
1948
2076
1949
2077
if (ohq ) { /* Off Hook Queue takes precedence over Call Back Queue, if permitted: anyone can OHQ, unlike CBQ */
1950
2078
int eligible = 0 ;
2079
+ time_t elapsed ;
1951
2080
/* Start off by trying to off hook queue on non-MER routes */
1952
2081
/* To be eligible for OHQ, at least 1 trunk route must have a call count <= threshold,
1953
2082
* then all routes are eligible for OHQ. */
@@ -1956,8 +2085,9 @@ static int ccsa_run(struct ast_channel *chan, int fd, const char *exten, const c
1956
2085
have_mer = 0 ;
1957
2086
1958
2087
routes = ast_strdupa (faclist );
2088
+ elapsed = time (NULL ) - start ;
1959
2089
while ((route = strsep (& routes , "|" ))) {
1960
- if (route_permits_ohq (route )) {
2090
+ if (route_permits_ohq (route , elapsed , fd != -1 )) {
1961
2091
eligible = 1 ;
1962
2092
break ; /* No need to check any more */
1963
2093
}
@@ -2101,6 +2231,21 @@ static int ccsa_run(struct ast_channel *chan, int fd, const char *exten, const c
2101
2231
* Our motto is "the BSPs are always right" :)
2102
2232
*/
2103
2233
2234
+ if (fd == -1 ) {
2235
+ /* If not simulation, ensure we can actually CBQ */
2236
+ routes = ast_strdupa (faclist );
2237
+ while ((route = strsep (& routes , "|" ))) {
2238
+ if (route_permits_cbq (route )) {
2239
+ break ; /* No need to check any more */
2240
+ }
2241
+ }
2242
+ if (!route ) {
2243
+ ast_debug (3 , "Call ineligible for Call Back Queue\n" );
2244
+ ccsa_set_result_val (chan , "UNROUTABLE" );
2245
+ return 0 ;
2246
+ }
2247
+ }
2248
+
2104
2249
if (!chan ) {
2105
2250
ccsa_log (chan , fd , "Call Back Queue treatment\n" );
2106
2251
return 0 ;
@@ -2888,6 +3033,8 @@ static int ccsa_reload(int reload)
2888
3033
f -> threshold = atoi (var -> value );
2889
3034
} else if (!strcasecmp (var -> name , "limit" ) && !ast_strlen_zero (var -> value )) {
2890
3035
f -> limit = atoi (var -> value );
3036
+ } else if (!strcasecmp (var -> name , "devstate" )) {
3037
+ f -> devstate = ast_strdup (var -> value );
2891
3038
} else {
2892
3039
ast_log (LOG_WARNING , "Unknown keyword in profile '%s': %s at line %d of %s\n" , var -> name , var -> name , var -> lineno , CONFIG_FILE );
2893
3040
}
@@ -3003,6 +3150,9 @@ static int unload_module(void)
3003
3150
3004
3151
AST_RWLIST_WRLOCK (& routes );
3005
3152
while ((f = AST_RWLIST_REMOVE_HEAD (& routes , entry ))) {
3153
+ if (f -> devstate ) {
3154
+ ast_free (f -> devstate );
3155
+ }
3006
3156
ast_free (f );
3007
3157
}
3008
3158
AST_RWLIST_UNLOCK (& routes );
0 commit comments