1 /*
2 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3 * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
4 *
5 * Version: MPL 1.1
6 *
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
11 *
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
16 *
17 * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
18 *
19 * The Initial Developer of the Original Code is
20 * Anthony Minessale II <anthm@freeswitch.org>
21 * Portions created by the Initial Developer are Copyright (C)
22 * the Initial Developer. All Rights Reserved.
23 *
24 * Contributor(s):
25 * Daniel Swarbrick <daniel.swarbrick@gmail.com>
26 *
27 * mod_cdr_mongodb.c -- MongoDB CDR Module
28 *
29 * Derived from:
30 * mod_xml_cdr.c -- XML CDR Module to files or curl
31 *
32 */
33 #include <switch.h>
34 #include <mongo.h>
35
36 #define MONGO_REPLSET_MAX_MEMBERS 12
37
38 static struct {
39 switch_memory_pool_t *pool;
40 int shutdown;
41 char *mongo_host;
42 int mongo_port;
43 char *mongo_namespace;
44 char *mongo_replset_name;
45 char *mongo_username;
46 char *mongo_password;
47 mongo mongo_conn[1];
48 switch_mutex_t *mongo_mutex;
49 switch_bool_t log_b;
50 } globals;
51
52 static switch_xml_config_item_t config_settings[] = {
53 /* key, flags, ptr, default_value, syntax, helptext */
54 SWITCH_CONFIG_ITEM_STRING_STRDUP("host", CONFIG_REQUIRED, &globals.mongo_host, "127.0.0.1", NULL, "MongoDB server host address"),
55 SWITCH_CONFIG_ITEM_STRING_STRDUP("namespace", CONFIG_REQUIRED, &globals.mongo_namespace, NULL, "database.collection", "MongoDB namespace"),
56 SWITCH_CONFIG_ITEM_STRING_STRDUP("replica_set_name", CONFIG_RELOADABLE, &globals.mongo_replset_name, "cdr_mongodb", NULL, "MongoDB replica set name"),
57 SWITCH_CONFIG_ITEM_STRING_STRDUP("username", CONFIG_RELOADABLE, &globals.mongo_username, NULL, NULL, "MongoDB username"),
58 SWITCH_CONFIG_ITEM_STRING_STRDUP("password", CONFIG_RELOADABLE, &globals.mongo_password, NULL, NULL, "MongoDB password"),
59
60 /* key, type, flags, ptr, default_value, data, syntax, helptext */
61 SWITCH_CONFIG_ITEM("port", SWITCH_CONFIG_INT, CONFIG_REQUIRED, &globals.mongo_port, MONGO_DEFAULT_PORT, NULL, NULL, "MongoDB server TCP port"),
62 SWITCH_CONFIG_ITEM("log-b-leg", SWITCH_CONFIG_BOOL, CONFIG_RELOADABLE, &globals.log_b, SWITCH_TRUE, NULL, NULL, "Log B-leg in addition to A-leg"),
63
64 SWITCH_CONFIG_ITEM_END()
65 };
66
67
68 SWITCH_MODULE_LOAD_FUNCTION(mod_cdr_mongodb_load);
69 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_cdr_mongodb_shutdown);
70 SWITCH_MODULE_DEFINITION(mod_cdr_mongodb, mod_cdr_mongodb_load, mod_cdr_mongodb_shutdown, NULL);
71
bson_append_value(bson * cdr,char * name,char * val)72 static void bson_append_value(bson *cdr, char *name, char *val)
73 {
74 //Check the variable and insert it as int, long int or string depending on it's value
75 char* endptr;
76 long int lintValue = strtol(val, &endptr, 10);
77
78 if (!*endptr){
79 int intValue = lintValue;
80 if(intValue == lintValue){
81 bson_append_int(cdr, name, intValue);
82 }else{
83 bson_append_long(cdr, name, lintValue);
84 }
85 } else {
86 bson_append_string(cdr, name, val);
87 }
88
89 }
90
91
set_bson_profile_data(bson * b,switch_caller_profile_t * caller_profile)92 static void set_bson_profile_data(bson *b, switch_caller_profile_t *caller_profile)
93 {
94 bson_append_string(b, "username", caller_profile->username);
95 bson_append_string(b, "dialplan", caller_profile->dialplan);
96 bson_append_string(b, "caller_id_name", caller_profile->caller_id_name);
97 bson_append_string(b, "ani", caller_profile->ani);
98 bson_append_string(b, "aniii", caller_profile->aniii);
99 bson_append_string(b, "caller_id_number", caller_profile->caller_id_number);
100 bson_append_string(b, "network_addr", caller_profile->network_addr);
101 bson_append_string(b, "rdnis", caller_profile->rdnis);
102 bson_append_string(b, "destination_number", caller_profile->destination_number);
103 bson_append_string(b, "uuid", caller_profile->uuid);
104 bson_append_string(b, "source", caller_profile->source);
105 bson_append_string(b, "context", caller_profile->context);
106 bson_append_string(b, "chan_name", caller_profile->chan_name);
107 }
108
109
cdr_mongo_authenticate()110 static switch_status_t cdr_mongo_authenticate() {
111 switch_status_t status = SWITCH_STATUS_SUCCESS;
112 mongo_error_t db_status;
113 char *ns_tmp, *ns_split[2];
114
115 /* Split namespace db.collection into separate vars */
116 switch_strdup(ns_tmp, globals.mongo_namespace);
117 switch_separate_string(ns_tmp, '.', ns_split, 2);
118
119 db_status = mongo_cmd_authenticate(globals.mongo_conn, ns_split[0], globals.mongo_username, globals.mongo_password);
120
121 if (db_status != MONGO_OK) {
122 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_cmd_authenticate: authentication failed\n");
123 status = SWITCH_STATUS_FALSE;
124 } else {
125 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Successfully authenticated %s@%s\n", globals.mongo_username, ns_split[0]);
126 }
127
128 switch_safe_free(ns_tmp);
129 return status;
130 }
131
132
my_on_reporting(switch_core_session_t * session)133 static switch_status_t my_on_reporting(switch_core_session_t *session)
134 {
135 switch_status_t status = SWITCH_STATUS_SUCCESS;
136 switch_channel_t *channel = switch_core_session_get_channel(session);
137 switch_event_header_t *hi;
138 switch_caller_profile_t *caller_profile;
139 switch_hold_record_t *hold_record;
140 switch_app_log_t *app_log;
141 bson cdr;
142 int is_b;
143 int bson_idx, callflow_idx;
144 char idx_buffer[12];
145 char *tmp;
146
147 if (globals.shutdown) {
148 return SWITCH_STATUS_SUCCESS;
149 }
150
151 is_b = channel && switch_channel_get_originator_caller_profile(channel);
152 if (!globals.log_b && is_b) {
153 const char *force_cdr = switch_channel_get_variable(channel, SWITCH_FORCE_PROCESS_CDR_VARIABLE);
154 if (!switch_true(force_cdr)) {
155 return SWITCH_STATUS_SUCCESS;
156 }
157 }
158
159 bson_init(&cdr);
160
161 /* Channel data */
162 bson_append_start_object(&cdr, "channel_data");
163 bson_append_string(&cdr, "state", switch_channel_state_name(switch_channel_get_state(channel)));
164 bson_append_string(&cdr, "direction", switch_channel_direction(channel) == SWITCH_CALL_DIRECTION_OUTBOUND ? "outbound" : "inbound");
165 bson_append_int(&cdr, "state_number", switch_channel_get_state(channel));
166
167 if ((tmp = switch_channel_get_flag_string(channel))) {
168 bson_append_string(&cdr, "flags", tmp);
169 free(tmp);
170 }
171
172 if ((tmp = switch_channel_get_cap_string(channel))) {
173 bson_append_string(&cdr, "caps", tmp);
174 free(tmp);
175 }
176 bson_append_finish_object(&cdr); /* channel_data */
177
178
179 /* Channel variables */
180 bson_append_start_object(&cdr, "variables");
181
182 if ((hi = switch_channel_variable_first(channel))) {
183 for (; hi; hi = hi->next) {
184 if (!zstr(hi->name) && !zstr(hi->value)) {
185 bson_append_value(&cdr, hi->name, hi->value);
186 }
187 }
188 switch_channel_variable_last(channel);
189 }
190
191 bson_append_finish_object(&cdr); /* variables */
192
193
194 /* App log */
195 if ((app_log = switch_core_session_get_app_log(session))) {
196 switch_app_log_t *ap;
197
198 bson_append_start_array(&cdr, "app_log");
199
200 for (ap = app_log, bson_idx = 0; ap; ap = ap->next, bson_idx++) {
201 switch_snprintf(idx_buffer, sizeof(idx_buffer), "%d", bson_idx);
202 bson_append_start_object(&cdr, idx_buffer);
203 bson_append_string(&cdr, "app_name", ap->app);
204 bson_append_string(&cdr, "app_data", switch_str_nil(ap->arg));
205 bson_append_long(&cdr, "app_stamp", ap->stamp);
206 bson_append_finish_object(&cdr); /* application */
207 }
208
209 bson_append_finish_array(&cdr); /* app_log */
210 }
211
212
213 /* Hold */
214 if ((hold_record = switch_channel_get_hold_record(channel))) {
215 switch_hold_record_t *hr;
216
217 bson_append_start_array(&cdr, "hold_record");
218
219 for (hr = hold_record, bson_idx = 0; hr; hr = hr->next, bson_idx++) {
220 switch_snprintf(idx_buffer, sizeof(idx_buffer), "%d", bson_idx);
221 bson_append_start_object(&cdr, idx_buffer);
222 bson_append_long(&cdr, "on", hr->on);
223 bson_append_long(&cdr, "off", hr->off);
224 if (hr->uuid) {
225 bson_append_string(&cdr, "bridged_to", hr->uuid);
226 }
227 bson_append_finish_object(&cdr);
228 }
229
230 bson_append_finish_array(&cdr); /* hold_record */
231 }
232
233
234 /* Callflow */
235 caller_profile = switch_channel_get_caller_profile(channel);
236
237 /* Start callflow array */
238 bson_append_start_array(&cdr, "callflow");
239 callflow_idx = 0;
240
241 while (caller_profile) {
242 snprintf(idx_buffer, sizeof(idx_buffer), "%d", callflow_idx);
243 bson_append_start_object(&cdr, idx_buffer);
244
245 if (!zstr(caller_profile->dialplan)) {
246 bson_append_string(&cdr, "dialplan", caller_profile->dialplan);
247 }
248
249 if (!zstr(caller_profile->profile_index)) {
250 bson_append_string(&cdr, "profile_index", caller_profile->profile_index);
251 }
252
253 if (caller_profile->caller_extension) {
254 switch_caller_application_t *ap;
255
256 bson_append_start_object(&cdr, "extension");
257
258 bson_append_string(&cdr, "name", switch_str_nil(caller_profile->caller_extension->extension_name));
259 bson_append_string(&cdr, "number", switch_str_nil(caller_profile->caller_extension->extension_number));
260
261 if (caller_profile->caller_extension->current_application) {
262 bson_append_string(&cdr, "current_app", caller_profile->caller_extension->current_application->application_name);
263 }
264
265 for (ap = caller_profile->caller_extension->applications; ap; ap = ap->next) {
266 bson_append_start_object(&cdr, "application");
267 if (ap == caller_profile->caller_extension->current_application) {
268 bson_append_bool(&cdr, "last_executed", 1);
269 }
270 bson_append_string(&cdr, "app_name", ap->application_name);
271 bson_append_string(&cdr, "app_data", switch_str_nil(ap->application_data));
272 bson_append_finish_object(&cdr);
273 }
274
275 if (caller_profile->caller_extension->children) {
276 switch_caller_profile_t *cp = NULL;
277
278 for (cp = caller_profile->caller_extension->children; cp; cp = cp->next) {
279
280 if (!cp->caller_extension) {
281 continue;
282 }
283
284 bson_append_start_object(&cdr, "sub_extensions");
285 bson_append_start_object(&cdr, "extension");
286
287 bson_append_string(&cdr, "name", cp->caller_extension->extension_name);
288 bson_append_string(&cdr, "number", cp->caller_extension->extension_number);
289 bson_append_string(&cdr, "dialplan", cp->dialplan);
290 if (cp->caller_extension->current_application) {
291 bson_append_string(&cdr, "current_app", cp->caller_extension->current_application->application_name);
292 }
293
294 for (ap = cp->caller_extension->applications; ap; ap = ap->next) {
295 bson_append_start_object(&cdr, "application");
296 if (ap == cp->caller_extension->current_application) {
297 bson_append_bool(&cdr, "last_executed", 1);
298 }
299 bson_append_string(&cdr, "app_name", ap->application_name);
300 bson_append_string(&cdr, "app_data", switch_str_nil(ap->application_data));
301 bson_append_finish_object(&cdr);
302 }
303
304 bson_append_finish_object(&cdr); /* extension */
305 bson_append_finish_object(&cdr); /* sub_extensions */
306 }
307 }
308
309 bson_append_finish_object(&cdr); /* extension */
310 }
311
312 bson_append_start_object(&cdr, "caller_profile");
313 set_bson_profile_data(&cdr, caller_profile);
314
315 if (caller_profile->origination_caller_profile) {
316 switch_caller_profile_t *cp = NULL;
317
318 /* Start origination array */
319 bson_append_start_array(&cdr, "origination");
320 for (cp = caller_profile->origination_caller_profile, bson_idx = 0; cp; cp = cp->next, bson_idx++) {
321 snprintf(idx_buffer, sizeof(idx_buffer), "%d", bson_idx);
322 bson_append_start_object(&cdr, idx_buffer);
323 set_bson_profile_data(&cdr, cp);
324 bson_append_finish_object(&cdr);
325 }
326 bson_append_finish_object(&cdr); /* origination */
327 }
328
329 if (caller_profile->originator_caller_profile) {
330 switch_caller_profile_t *cp = NULL;
331
332 /* Start originator array */
333 bson_append_start_array(&cdr, "originator");
334 for (cp = caller_profile->originator_caller_profile, bson_idx = 0; cp; cp = cp->next, bson_idx++) {
335 snprintf(idx_buffer, sizeof(idx_buffer), "%d", bson_idx);
336 bson_append_start_object(&cdr, idx_buffer);
337 set_bson_profile_data(&cdr, cp);
338 bson_append_finish_object(&cdr);
339 }
340 bson_append_finish_object(&cdr); /* originator */
341 }
342
343 if (caller_profile->originatee_caller_profile) {
344 switch_caller_profile_t *cp = NULL;
345
346 /* Start originatee array */
347 bson_append_start_array(&cdr, "originatee");
348 for (cp = caller_profile->originatee_caller_profile, bson_idx = 0; cp; cp = cp->next, bson_idx++) {
349 snprintf(idx_buffer, sizeof(idx_buffer), "%d", bson_idx);
350 bson_append_start_object(&cdr, idx_buffer);
351 set_bson_profile_data(&cdr, cp);
352 bson_append_finish_object(&cdr);
353 }
354 bson_append_finish_object(&cdr); /* originatee */
355 }
356
357 bson_append_finish_object(&cdr); /* caller_profile */
358
359 /* Timestamps */
360 if (caller_profile->times) {
361 bson_append_start_object(&cdr, "times");
362
363 /* Insert timestamps as long ints (microseconds) to preserve accuracy */
364 bson_append_long(&cdr, "created_time", caller_profile->times->created);
365 bson_append_long(&cdr, "profile_created_time", caller_profile->times->profile_created);
366 bson_append_long(&cdr, "progress_time", caller_profile->times->progress);
367 bson_append_long(&cdr, "progress_media_time", caller_profile->times->progress_media);
368 bson_append_long(&cdr, "answered_time", caller_profile->times->answered);
369 bson_append_long(&cdr, "bridged_time", caller_profile->times->bridged);
370 bson_append_long(&cdr, "last_hold_time", caller_profile->times->last_hold);
371 bson_append_long(&cdr, "hold_accum_time", caller_profile->times->hold_accum);
372 bson_append_long(&cdr, "hangup_time", caller_profile->times->hungup);
373 bson_append_long(&cdr, "resurrect_time", caller_profile->times->resurrected);
374 bson_append_long(&cdr, "transfer_time", caller_profile->times->transferred);
375 bson_append_finish_object(&cdr); /* times */
376 }
377
378 bson_append_finish_object(&cdr); /* callflow */
379 caller_profile = caller_profile->next;
380 callflow_idx++;
381 }
382
383 bson_append_finish_array(&cdr);
384
385 bson_finish(&cdr);
386
387 switch_mutex_lock(globals.mongo_mutex);
388
389 if (mongo_insert(globals.mongo_conn, globals.mongo_namespace, &cdr, NULL) != MONGO_OK) {
390 if (globals.mongo_conn->err == MONGO_IO_ERROR) {
391 mongo_error_t db_status;
392 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "MongoDB connection failed; attempting reconnect...\n");
393 db_status = mongo_reconnect(globals.mongo_conn);
394
395 if (db_status != MONGO_OK) {
396 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "MongoDB reconnect failed with error code %d\n", db_status);
397 status = SWITCH_STATUS_FALSE;
398 } else {
399 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "MongoDB connection re-established.\n");
400
401 /* Re-authentication is necessary after a reconnect */
402 if (globals.mongo_username && globals.mongo_password) {
403 status = cdr_mongo_authenticate();
404 }
405
406 if (mongo_insert(globals.mongo_conn, globals.mongo_namespace, &cdr, NULL) != MONGO_OK) {
407 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_insert: %s (error code %d)\n", globals.mongo_conn->errstr, globals.mongo_conn->err);
408 status = SWITCH_STATUS_FALSE;
409 }
410 }
411
412 } else {
413 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_insert: %s (error code %d)\n", globals.mongo_conn->errstr, globals.mongo_conn->err);
414 status = SWITCH_STATUS_FALSE;
415 }
416 }
417
418 switch_mutex_unlock(globals.mongo_mutex);
419 bson_destroy(&cdr);
420
421 return status;
422 }
423
424
425 static switch_state_handler_table_t state_handlers = {
426 /*.on_init */ NULL,
427 /*.on_routing */ NULL,
428 /*.on_execute */ NULL,
429 /*.on_hangup */ NULL,
430 /*.on_exchange_media */ NULL,
431 /*.on_soft_execute */ NULL,
432 /*.on_consume_media */ NULL,
433 /*.on_hibernate */ NULL,
434 /*.on_reset */ NULL,
435 /*.on_park */ NULL,
436 /*.on_reporting */ my_on_reporting
437 };
438
439
load_config(switch_memory_pool_t * pool)440 static switch_status_t load_config(switch_memory_pool_t *pool)
441 {
442 switch_status_t status = SWITCH_STATUS_SUCCESS;
443
444 if (switch_xml_config_parse_module_settings("cdr_mongodb.conf", SWITCH_FALSE, config_settings) != SWITCH_STATUS_SUCCESS) {
445 return SWITCH_STATUS_FALSE;
446 }
447
448 return status;
449 }
450
451
SWITCH_MODULE_LOAD_FUNCTION(mod_cdr_mongodb_load)452 SWITCH_MODULE_LOAD_FUNCTION(mod_cdr_mongodb_load)
453 {
454 switch_status_t status = SWITCH_STATUS_SUCCESS;
455 mongo_error_t db_status;
456 char *repl_hosts[MONGO_REPLSET_MAX_MEMBERS];
457 char *mongo_host[2];
458 int num_hosts, mongo_port;
459
460 memset(&globals, 0, sizeof(globals));
461 globals.pool = pool;
462 if (load_config(pool) != SWITCH_STATUS_SUCCESS) {
463 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to load or parse config!\n");
464 return SWITCH_STATUS_FALSE;
465 }
466
467 num_hosts = switch_separate_string(globals.mongo_host, ',', repl_hosts, MONGO_REPLSET_MAX_MEMBERS);
468
469 if (num_hosts > 1) {
470 int i;
471
472 mongo_replset_init(globals.mongo_conn, globals.mongo_replset_name);
473
474 for (i = 0; i < num_hosts; i++) {
475 switch_separate_string(repl_hosts[i], ':', mongo_host, 2);
476 mongo_port = mongo_host[1] ? atoi(mongo_host[1]) : globals.mongo_port;
477 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Adding MongoDB server %s:%d to replica set\n", mongo_host[0], mongo_port);
478 mongo_replset_add_seed(globals.mongo_conn, mongo_host[0], mongo_port);
479 }
480
481 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Connecting to MongoDB replica set %s\n", globals.mongo_replset_name);
482 db_status = mongo_replset_connect(globals.mongo_conn);
483 } else {
484 switch_separate_string(globals.mongo_host, ':', mongo_host, 2);
485
486 if (mongo_host[1]) {
487 globals.mongo_port = atoi(mongo_host[1]);
488 }
489
490 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Connecting to MongoDB server %s:%d\n", globals.mongo_host, globals.mongo_port);
491 db_status = mongo_connect(globals.mongo_conn, globals.mongo_host, globals.mongo_port);
492 }
493
494 if (db_status != MONGO_OK) {
495 switch (globals.mongo_conn->err) {
496 case MONGO_CONN_NO_SOCKET:
497 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_connect: no socket\n");
498 break;
499 case MONGO_CONN_FAIL:
500 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_connect: connection failed\n");
501 break;
502 case MONGO_CONN_ADDR_FAIL:
503 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_connect: hostname lookup failed\n");
504 break;
505 case MONGO_CONN_NOT_MASTER:
506 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_connect: not master\n");
507 break;
508 case MONGO_CONN_BAD_SET_NAME:
509 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_replset_connect: configured replica set name does not match\n");
510 break;
511 case MONGO_CONN_NO_PRIMARY:
512 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_replset_connect: cannot find replica set primary member\n");
513 break;
514 default:
515 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mongo_connect: unknown error: status code %d, error code %d\n", db_status, globals.mongo_conn->err);
516 }
517 return SWITCH_STATUS_FALSE;
518 } else {
519 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Connection established\n");
520 }
521
522 if (globals.mongo_username && globals.mongo_password) {
523 if (cdr_mongo_authenticate() != SWITCH_STATUS_SUCCESS) {
524 return SWITCH_STATUS_FALSE;
525 }
526 }
527
528 switch_mutex_init(&globals.mongo_mutex, SWITCH_MUTEX_NESTED, pool);
529
530 switch_core_add_state_handler(&state_handlers);
531 *module_interface = switch_loadable_module_create_module_interface(pool, modname);
532
533 return status;
534 }
535
536
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_cdr_mongodb_shutdown)537 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_cdr_mongodb_shutdown)
538 {
539 globals.shutdown = 1;
540 switch_core_remove_state_handler(&state_handlers);
541 switch_mutex_destroy(globals.mongo_mutex);
542
543 mongo_destroy(globals.mongo_conn);
544
545 return SWITCH_STATUS_SUCCESS;
546 }
547
548
549
550 /* For Emacs:
551 * Local Variables:
552 * mode:c
553 * indent-tabs-mode:t
554 * tab-width:4
555 * c-basic-offset:4
556 * End:
557 * For VIM:
558 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
559 */
560