void * SWITCH_THREAD_FUNC mod_amqp_logging_thread(switch_thread_t *thread, void *data) { mod_amqp_message_t *msg = NULL; switch_status_t status = SWITCH_STATUS_SUCCESS; mod_amqp_logging_profile_t *profile = (mod_amqp_logging_profile_t *)data; amqp_boolean_t passive = 0; amqp_boolean_t durable = 1; while (profile->running) { if (!profile->conn_active) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Amqp no connection- reconnecting...\n"); status = mod_amqp_connection_open(profile->conn_root, &(profile->conn_active), profile->name, profile->custom_attr); if ( status == SWITCH_STATUS_SUCCESS ) { // Ensure that the exchange exists, and is of the correct type amqp_exchange_declare(profile->conn_active->state, 1, amqp_cstring_bytes(profile->exchange), amqp_cstring_bytes(profile->exchange_type), passive, durable, amqp_empty_table); if (!mod_amqp_log_if_amqp_error(amqp_get_rpc_reply(profile->conn_active->state), "Declaring exchange")) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Amqp reconnect successful- connected\n"); continue; } } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Profile[%s] failed to connect with code(%d), sleeping for %dms\n", profile->name, status, profile->reconnect_interval_ms); switch_sleep(profile->reconnect_interval_ms * 1000); continue; } if (!msg && switch_queue_pop_timeout(profile->send_queue, (void**)&msg, 1000000) != SWITCH_STATUS_SUCCESS) { continue; } if (msg) { switch (mod_amqp_logging_send(profile, msg)) { case SWITCH_STATUS_SUCCESS: /* Success: prepare for next message */ mod_amqp_util_msg_destroy(&msg); break; case SWITCH_STATUS_NOT_INITALIZED: switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Send failed with 'not initialised'\n"); break; case SWITCH_STATUS_SOCKERR: switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Send failed with 'socket error'\n"); break; default: switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Send failed with a generic error\n"); /* Send failed and closed the connection; reconnect will happen at the beginning of the loop * NB: do we need a delay here to prevent a fast reconnect-send-fail loop? */ break; } } } /* Abort the current message */ mod_amqp_util_msg_destroy(&msg); // Terminate the thread switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Event sender thread stopped\n"); switch_thread_exit(thread, SWITCH_STATUS_SUCCESS); return NULL; }
void * SWITCH_THREAD_FUNC mod_amqp_command_thread(switch_thread_t *thread, void *data) { mod_amqp_command_profile_t *profile = (mod_amqp_command_profile_t *) data; while (profile->running) { amqp_queue_declare_ok_t *recv_queue; amqp_bytes_t queueName = { 0, NULL }; /* Ensure we have an AMQP connection */ if (!profile->conn_active) { switch_status_t status; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Amqp no connection- reconnecting...\n"); status = mod_amqp_connection_open(profile->conn_root, &(profile->conn_active), profile->name, profile->custom_attr); if ( status != SWITCH_STATUS_SUCCESS ) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Profile[%s] failed to connect with code(%d), sleeping for %dms\n", profile->name, status, profile->reconnect_interval_ms); switch_sleep(profile->reconnect_interval_ms * 1000); continue; } /* Check if exchange already exists */ amqp_exchange_declare(profile->conn_active->state, 1, amqp_cstring_bytes(profile->exchange), amqp_cstring_bytes("topic"), 0, /* passive */ 1, /* durable */ amqp_empty_table); if (mod_amqp_log_if_amqp_error(amqp_get_rpc_reply(profile->conn_active->state), "Checking for command exchange")) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Profile[%s] failed to create missing command exchange", profile->name); continue; } /* Ensure we have a queue */ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Creating command queue"); recv_queue = amqp_queue_declare(profile->conn_active->state, // state 1, // channel profile->queue ? amqp_cstring_bytes(profile->queue) : amqp_empty_bytes, // queue name 0, 0, // passive, durable 0, 1, // exclusive, auto-delete amqp_empty_table); // args if (mod_amqp_log_if_amqp_error(amqp_get_rpc_reply(profile->conn_active->state), "Declaring queue")) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Profile[%s] failed to connect with code(%d), sleeping for %dms\n", profile->name, status, profile->reconnect_interval_ms); switch_sleep(profile->reconnect_interval_ms * 1000); continue; } if (queueName.bytes) { amqp_bytes_free(queueName); } queueName = amqp_bytes_malloc_dup(recv_queue->queue); if (!queueName.bytes) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Out of memory while copying queue name"); break; } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Created command queue %.*s", (int)queueName.len, (char *)queueName.bytes); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Binding command queue to exchange %s", profile->exchange); /* Bind the queue to the exchange */ amqp_queue_bind(profile->conn_active->state, // state 1, // channel queueName, // queue amqp_cstring_bytes(profile->exchange), // exchange amqp_cstring_bytes(profile->binding_key), // routing key amqp_empty_table); // args if (mod_amqp_log_if_amqp_error(amqp_get_rpc_reply(profile->conn_active->state), "Binding queue")) { mod_amqp_connection_close(profile->conn_active); profile->conn_active = NULL; switch_sleep(profile->reconnect_interval_ms * 1000); continue; } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Amqp reconnect successful- connected\n"); continue; } // Start a command amqp_basic_consume(profile->conn_active->state, // state 1, // channel queueName, // queue amqp_empty_bytes, // command tag 0, 1, 0, // no_local, no_ack, exclusive amqp_empty_table); // args if (mod_amqp_log_if_amqp_error(amqp_get_rpc_reply(profile->conn_active->state), "Creating a command")) { mod_amqp_connection_close(profile->conn_active); profile->conn_active = NULL; switch_sleep(profile->reconnect_interval_ms * 1000); continue; } while (profile->running && profile->conn_active) { amqp_rpc_reply_t res; amqp_envelope_t envelope; struct timeval timeout = {0}; char command[1024]; enum ECommandFormat { COMMAND_FORMAT_UNKNOWN, COMMAND_FORMAT_PLAINTEXT } commandFormat = COMMAND_FORMAT_PLAINTEXT; char *fs_resp_exchange = NULL, *fs_resp_key = NULL; amqp_maybe_release_buffers(profile->conn_active->state); timeout.tv_usec = 500 * 1000; res = amqp_consume_message(profile->conn_active->state, &envelope, &timeout, 0); if (res.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION) { if (res.library_error == AMQP_STATUS_UNEXPECTED_STATE) { /* Unexpected frame. Discard it then continue */ amqp_frame_t decoded_frame; amqp_simple_wait_frame(profile->conn_active->state, &decoded_frame); } if (res.library_error == AMQP_STATUS_SOCKET_ERROR) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "A socket error occurred. Tearing down and reconnecting\n"); break; } if (res.library_error == AMQP_STATUS_CONNECTION_CLOSED) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "AMQP connection was closed. Tearing down and reconnecting\n"); break; } if (res.library_error == AMQP_STATUS_TCP_ERROR) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "A TCP error occurred. Tearing down and reconnecting\n"); break; } if (res.library_error == AMQP_STATUS_TIMEOUT) { // nop } /* Try consuming again */ continue; } if (res.reply_type != AMQP_RESPONSE_NORMAL) { break; } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Delivery:%u, exchange:%.*s routingkey:%.*s\n", (unsigned) envelope.delivery_tag, (int) envelope.exchange.len, (char *) envelope.exchange.bytes, (int) envelope.routing_key.len, (char *) envelope.routing_key.bytes); if (envelope.message.properties._flags & AMQP_BASIC_CONTENT_TYPE_FLAG) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Content-type: %.*s\n", (int) envelope.message.properties.content_type.len, (char *) envelope.message.properties.content_type.bytes); if (strncasecmp("text/plain", envelope.message.properties.content_type.bytes, strlen("text/plain")) == 0) { commandFormat = COMMAND_FORMAT_PLAINTEXT; } else { commandFormat = COMMAND_FORMAT_UNKNOWN; } } if (envelope.message.properties.headers.num_entries) { int x = 0; for ( x = 0; x < envelope.message.properties.headers.num_entries; x++) { char *header_key = (char *)envelope.message.properties.headers.entries[x].key.bytes; char *header_value = (char *)envelope.message.properties.headers.entries[x].value.value.bytes.bytes; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "AMQP message custom header key[%s] value[%s]\n", header_key, header_value); if ( !strncmp(header_key, "x-fs-api-resp-exchange", 22)) { fs_resp_exchange = header_value; } else if (!strncmp(header_key, "x-fs-api-resp-key", 17)) { fs_resp_key = header_value; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Ignoring unrecognized event header [%s]\n", header_key); } } } if (commandFormat == COMMAND_FORMAT_PLAINTEXT) { switch_stream_handle_t stream = { 0 }; /* Collects the command output */ /* Convert amqp bytes to c-string */ snprintf(command, sizeof(command), "%.*s", (int) envelope.message.body.len, (char *) envelope.message.body.bytes); /* Execute the command */ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Executing: %s\n", command); SWITCH_STANDARD_STREAM(stream); if ( fs_resp_exchange && fs_resp_key ) { switch_status_t status = switch_console_execute(command, 0, &stream); mod_amqp_command_response(profile, command, stream, fs_resp_exchange, fs_resp_key, status); } else { if (switch_console_execute(command, 0, &stream) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Remote command failed:\n%s\n", (char *) stream.data); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Remote command succeeded:\n%s\n", (char *) stream.data); } } switch_safe_free(stream.data); } /* Tidy up */ amqp_destroy_envelope(&envelope); } amqp_bytes_free(queueName); queueName.bytes = NULL; mod_amqp_connection_close(profile->conn_active); profile->conn_active = NULL; if (profile->running) { /* We'll reconnect, but sleep to avoid hammering resources */ switch_sleep(500); } } /* Terminate the thread */ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Command listener thread stopped\n"); switch_thread_exit(thread, SWITCH_STATUS_SUCCESS); return NULL; }
switch_status_t mod_amqp_logging_create(char *name, switch_xml_t cfg) { mod_amqp_logging_profile_t *profile = NULL; switch_xml_t params, param, connections, connection; switch_threadattr_t *thd_attr = NULL; char *exchange = NULL, *exchange_type = NULL; int exchange_durable = 1; /* durable */ switch_memory_pool_t *pool; if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { goto err; } profile = switch_core_alloc(pool, sizeof(mod_amqp_logging_profile_t)); profile->pool = pool; profile->name = switch_core_strdup(profile->pool, name); profile->running = 1; profile->conn_root = NULL; profile->conn_active = NULL; profile->log_level_mask = 0; profile->send_queue_size = 5000; if ((params = switch_xml_child(cfg, "params")) != NULL) { for (param = switch_xml_child(params, "param"); param; param = param->next) { char *var = (char *) switch_xml_attr_soft(param, "name"); char *val = (char *) switch_xml_attr_soft(param, "value"); if (!var) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Profile[%s] param missing 'name' attribute\n", profile->name); continue; } if (!val) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Profile[%s] param[%s] missing 'value' attribute\n", profile->name, var); continue; } if (!strncmp(var, "reconnect_interval_ms", 21)) { int interval = atoi(val); if ( interval && interval > 0 ) { profile->reconnect_interval_ms = interval; } } else if (!strncmp(var, "send_queue_size", 15)) { int interval = atoi(val); if ( interval && interval > 0 ) { profile->send_queue_size = interval; } } else if (!strncmp(var, "exchange-type", 13)) { exchange_type = switch_core_strdup(profile->pool, val); } else if (!strncmp(var, "exchange-name", 13)) { exchange = switch_core_strdup(profile->pool, val); } else if (!strncmp(var, "exchange-durable", 16)) { exchange_durable = switch_true(val); } else if (!strncmp(var, "log-levels", 10)) { profile->log_level_mask = switch_log_str2mask(val); } } /* params for loop */ } /* Handle defaults of string types */ profile->exchange = exchange ? exchange : switch_core_strdup(profile->pool, "TAP.Events"); profile->exchange_type = exchange_type ? exchange_type : switch_core_strdup(profile->pool, "topic"); profile->exchange_durable = exchange_durable; if ((connections = switch_xml_child(cfg, "connections")) != NULL) { for (connection = switch_xml_child(connections, "connection"); connection; connection = connection->next) { if ( ! profile->conn_root ) { /* Handle first root node */ if (mod_amqp_connection_create(&(profile->conn_root), connection, profile->pool) != SWITCH_STATUS_SUCCESS) { /* Handle connection create failure */ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Profile[%s] failed to create connection\n", profile->name); continue; } profile->conn_active = profile->conn_root; } else { if (mod_amqp_connection_create(&(profile->conn_active->next), connection, profile->pool) != SWITCH_STATUS_SUCCESS) { /* Handle connection create failure */ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Profile[%s] failed to create connection\n", profile->name); continue; } profile->conn_active = profile->conn_active->next; } } } profile->conn_active = NULL; if ( mod_amqp_connection_open(profile->conn_root, &(profile->conn_active), profile->name, profile->custom_attr) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Profile[%s] was unable to connect to any connection\n", profile->name); goto err; } amqp_exchange_declare(profile->conn_active->state, 1, amqp_cstring_bytes(profile->exchange), amqp_cstring_bytes(profile->exchange_type), 0, /* passive */ profile->exchange_durable, amqp_empty_table); if (mod_amqp_log_if_amqp_error(amqp_get_rpc_reply(profile->conn_active->state), "Declaring exchange")) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Profile[%s] failed to create exchange\n", profile->name); goto err; } /* Create a bounded FIFO queue for sending messages */ if (switch_queue_create(&(profile->send_queue), profile->send_queue_size, profile->pool) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot create send queue of size %d!\n", profile->send_queue_size); goto err; } /* Start the event send thread. This will set up the initial connection */ switch_threadattr_create(&thd_attr, profile->pool); switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); if (switch_thread_create(&profile->logging_thread, thd_attr, mod_amqp_logging_thread, profile, profile->pool)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot create 'amqp event sender' thread!\n"); goto err; } if ( switch_core_hash_insert(globals.logging_hash, name, (void *) profile) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to insert new profile [%s] into mod_amqp profile hash\n", name); goto err; } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Profile[%s] Successfully started\n", profile->name); return SWITCH_STATUS_SUCCESS; err: /* Cleanup */ mod_amqp_logging_destroy(&profile); return SWITCH_STATUS_GENERR; }