diff --git a/poller.c b/poller.c index b03cd6e2..66d8e91a 100644 --- a/poller.c +++ b/poller.c @@ -117,6 +117,265 @@ void *child(void *arg) { exit(0); } +/*! \fn void poller_item_scope(char *out, size_t len, int poller_id) + * \brief The poller_item filter for the poller this process is running as. + * + * The main poller reads every item that has not been deleted. A remote poller + * reads the items assigned to it, and ownership already excludes deleted rows. + * Kept in one place because poll_host() built the same pair of queries twice, + * once per branch, and a column added to one copy would not reach the other. + */ +void poller_item_scope(char *out, size_t len, int poller_id) { + if (out == NULL || len == 0) { + return; + } + + if (poller_id == 0) { + snprintf(out, len, " AND deleted = ''"); + } else { + snprintf(out, len, " AND poller_id = %i", poller_id); + } +} + +/*! \fn void poller_owner_scope(char *out, size_t len, int poller_id) + * \brief The ownership filter applied only by a remote poller. + * + * The main poller does not constrain these queries at all; a remote poller + * restricts them to its own rows. Returns an empty string for the main poller + * so the caller can interpolate it unconditionally. + */ +void poller_owner_scope(char *out, size_t len, int poller_id) { + if (out == NULL || len == 0) { + return; + } + + if (poller_id == 0) { + out[0] = '\0'; + } else { + snprintf(out, len, " AND poller_id = %i", poller_id); + } +} + + +/*! \fn void poll_host_build_queries(poll_host_queries_t *q, int host_id, const char *regex_col, const char *limits) + * \brief build every query poll_host() issues for one device + * + * Split out of poll_host() so the construction can be reached from a test. + * It reads only its arguments and the settings named below, which is what + * makes it checkable: given the same inputs it produces the same SQL. + * + * Reads set.poller_id, set.total_snmp_ports, set.dbonupdate, + * set.poller_interval and set.active_profiles. + */ +void poll_host_build_queries(poll_host_queries_t *q, int host_id, const char *regex_col, const char *limits) { + char item_scope[SMALL_BUFSIZE]; + char owner_scope[SMALL_BUFSIZE]; + + /* Scope every poller_item read to what this poller owns: the main poller + takes every undeleted row, a remote poller takes the rows assigned to it. + Both fragments are interpolated unconditionally, so the queries below do + not branch on poller_id and cannot drift apart. */ + poller_item_scope(item_scope, sizeof(item_scope), set.poller_id); + poller_owner_scope(owner_scope, sizeof(owner_scope), set.poller_id); + + if (set.total_snmp_ports == 1) { + snprintf(q->query1, BUFSIZE, + "SELECT SQL_NO_CACHE action, hostname, snmp_community, " + "snmp_version, snmp_username, snmp_password, " + "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " + "rrd_num, snmp_port, snmp_timeout, " + "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" + "%s" + " FROM poller_item" + " WHERE host_id = %i" + "%s %s", regex_col, host_id, item_scope, limits); + } else { + snprintf(q->query1, BUFSIZE, + "SELECT SQL_NO_CACHE action, hostname, snmp_community, " + "snmp_version, snmp_username, snmp_password, " + "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " + "rrd_num, snmp_port, snmp_timeout, " + "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" + "%s" + " FROM poller_item" + " WHERE host_id = %i" + "%s" + " ORDER BY snmp_port %s", regex_col, host_id, item_scope, limits); + } + + /* host structure for uptime checks */ + snprintf(q->query2, BIG_BUFSIZE, + "SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, " + "snmp_username, snmp_password, snmp_auth_protocol, " + "snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, " + "availability_method, ping_method, ping_port, ping_timeout, ping_retries, " + "status, status_event_count, UNIX_TIMESTAMP(status_fail_date), " + "UNIX_TIMESTAMP(status_rec_date), status_last_error, " + "min_time, max_time, cur_time, avg_time, " + "total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, " + "snmp_sysContact, snmp_sysName, snmp_sysLocation" + " FROM host" + " WHERE id = %i" + " AND deleted = ''", host_id); + + /* data query structure for reindex detection */ + snprintf(q->query4, BUFSIZE, + "SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1" + " FROM poller_reindex" + " WHERE host_id = %i", host_id); + + /* multiple polling interval query for items */ + if (set.active_profiles != 1) { + if (set.total_snmp_ports == 1) { + snprintf(q->query5, BUFSIZE, + "SELECT SQL_NO_CACHE action, hostname, snmp_community, " + "snmp_version, snmp_username, snmp_password, " + "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " + "rrd_num, snmp_port, snmp_timeout, " + "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" + "%s" + " FROM poller_item" + " WHERE host_id = %i" + " AND rrd_next_step <= 0" + "%s %s", regex_col, host_id, owner_scope, limits); + } else { + snprintf(q->query5, BUFSIZE, + "SELECT SQL_NO_CACHE action, hostname, snmp_community, " + "snmp_version, snmp_username, snmp_password, " + "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " + "rrd_num, snmp_port, snmp_timeout, " + "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" + "%s" + " FROM poller_item" + " WHERE host_id = %i" + " AND rrd_next_step <= 0" + "%s" + " ORDER BY snmp_port %s", regex_col, host_id, owner_scope, limits); + } + } else { + if (set.total_snmp_ports == 1) { + snprintf(q->query5, BUFSIZE, + "SELECT SQL_NO_CACHE action, hostname, snmp_community, " + "snmp_version, snmp_username, snmp_password, " + "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " + "rrd_num, snmp_port, snmp_timeout, " + "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" + "%s" + " FROM poller_item" + " WHERE host_id = %i" + "%s %s", regex_col, host_id, owner_scope, limits); + } else { + snprintf(q->query5, BUFSIZE, + "SELECT SQL_NO_CACHE action, hostname, snmp_community, " + "snmp_version, snmp_username, snmp_password, " + "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " + "rrd_num, snmp_port, snmp_timeout, " + "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" + "%s" + " FROM poller_item" + " WHERE host_id = %i" + "%s" + " ORDER BY snmp_port %s", regex_col, host_id, owner_scope, limits); + } + } + + /* query to setup the next polling interval in cacti */ + snprintf(q->query6, BUFSIZE, + "UPDATE poller_item" + " SET rrd_next_step = IF(rrd_step = %i, 0, IF(rrd_next_step - %i < 0, rrd_step - %i, rrd_next_step - %i))" + " WHERE host_id = %i%s", set.poller_interval, set.poller_interval, set.poller_interval, set.poller_interval, host_id, owner_scope); + + /* query to add output records to the poller output table */ + snprintf(q->query8, BUFSIZE, + "INSERT INTO poller_output" + " (local_data_id, rrd_name, time, output) VALUES"); + + /* query suffix to add rows to the poller output table */ + if (set.dbonupdate == 0) { + snprintf(q->posuffix, BUFSIZE, + " ON DUPLICATE KEY UPDATE output=VALUES(output)"); + } else { + snprintf(q->posuffix, BUFSIZE, + " AS rs ON DUPLICATE KEY UPDATE output=rs.output"); + } + + /* number of agent's count for single polling interval */ + snprintf(q->query9, BUFSIZE, + "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" + " FROM poller_item" + " WHERE host_id = %i" + "%s" + " GROUP BY snmp_port %s", host_id, owner_scope, limits); + + /* number of agent's count for multiple polling intervals */ + if (set.active_profiles != 1) { + snprintf(q->query10, BUFSIZE, + "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" + " FROM poller_item" + " WHERE host_id = %i" + " AND rrd_next_step <= 0" + "%s" + " GROUP BY snmp_port %s", host_id, owner_scope, limits); + } else { + snprintf(q->query10, BUFSIZE, + "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" + " FROM poller_item" + " WHERE host_id = %i" + "%s" + " GROUP BY snmp_port %s", host_id, owner_scope, limits); + } + + /* query to add output records to the poller output table */ + snprintf(q->query11, BUFSIZE, + "INSERT INTO poller_output_boost" + " (local_data_id, rrd_name, time, output) VALUES"); + + q->query8_len = strlen(q->query8); + q->query11_len = strlen(q->query11); + q->posuffix_len = strlen(q->posuffix); +} + +/*! \fn static void poll_host_release(host_t **host, reindex_t **reindex, ping_t **ping, char **error_string, int **buf_size, int **buf_errors, pool_t *local_cnn, pool_t *remote_cnn, int host_id, int host_thread) + * \brief release everything poll_host() owns, on every exit path + * + * poll_host() leaves through three places and each used to spell this out + * again. The copies were not in the same order and did not contain the same + * steps: the device-row-missing path never called mysql_thread_end(), which + * leaks the client library's thread-local state once per affected device per + * cycle on a thread-per-device poller. See #594. + */ +static void poll_host_release_connections(pool_t *local_cnn, pool_t *remote_cnn, int host_id, int host_thread) { + if (local_cnn != NULL) { + db_release_connection(LOCAL, local_cnn->id); + } else { + SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized local connection.", host_id, host_thread)); + } + + if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) { + if (remote_cnn != NULL) { + db_release_connection(REMOTE, remote_cnn->id); + } else { + SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized remote connection.", host_id, host_thread)); + } + } +} + +static void poll_host_release(host_t **host, reindex_t **reindex, ping_t **ping, + char **error_string, int **buf_size, int **buf_errors, + pool_t *local_cnn, pool_t *remote_cnn, int host_id, int host_thread) { + + poll_host_release_connections(local_cnn, remote_cnn, host_id, host_thread); + + SPINE_FREE(*host); + SPINE_FREE(*reindex); + SPINE_FREE(*ping); + SPINE_FREE(*error_string); + SPINE_FREE(*buf_size); + SPINE_FREE(*buf_errors); + + mysql_thread_end(); +} + /*! \fn void poll_host(int device_counter, int host_id, int host_thread, int host_threads, int host_data_ids, char *host_time, int *host_errors, double host_time_double) * \brief core Spine function that polls a host * \param host_id integer value for the host_id from the hosts table in Cacti @@ -140,22 +399,10 @@ void *child(void *arg) { * */ void poll_host(int device_counter, int host_id, int host_thread, int host_threads, int host_data_ids, char *host_time, int *host_errors, double host_time_double) { - char query1[BUFSIZE]; - char query2[BIG_BUFSIZE]; + poll_host_queries_t q; char *query3 = NULL; - char query4[BUFSIZE]; - char query5[BUFSIZE]; - char query6[BUFSIZE]; - char query8[BUFSIZE]; - char query9[BUFSIZE]; - char query10[BUFSIZE]; - char query11[BUFSIZE]; char *query12 = NULL; - char posuffix[BUFSIZE]; - int query8_len = 0; - int query11_len = 0; - int posuffix_len = 0; char sysUptime[BUFSIZE]; char result_string[RESULTS_BUFFER+SMALL_BUFSIZE]; @@ -276,302 +523,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* optional output_regex column (added in Cacti 1.3.1) */ const char *regex_col = set.has_output_regex ? ", output_regex" : ""; - /* single polling interval query for items */ - if (set.poller_id == 0) { - if (set.total_snmp_ports == 1) { - snprintf(query1, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND deleted = '' %s", regex_col, host_id, limits); - } else { - snprintf(query1, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND deleted = ''" - " ORDER BY snmp_port %s", regex_col, host_id, limits); - } - - /* host structure for uptime checks */ - snprintf(query2, BIG_BUFSIZE, - "SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, " - "snmp_username, snmp_password, snmp_auth_protocol, " - "snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, " - "availability_method, ping_method, ping_port, ping_timeout, ping_retries, " - "status, status_event_count, UNIX_TIMESTAMP(status_fail_date), " - "UNIX_TIMESTAMP(status_rec_date), status_last_error, " - "min_time, max_time, cur_time, avg_time, " - "total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, " - "snmp_sysContact, snmp_sysName, snmp_sysLocation" - " FROM host" - " WHERE id = %i" - " AND deleted = ''", host_id); - - /* data query structure for reindex detection */ - snprintf(query4, BUFSIZE, - "SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1" - " FROM poller_reindex" - " WHERE host_id = %i", host_id); - - /* multiple polling interval query for items */ - if (set.active_profiles != 1) { - if (set.total_snmp_ports == 1) { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND rrd_next_step <= 0" - " %s", regex_col, host_id, limits); - } else { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND rrd_next_step <= 0" - " ORDER BY snmp_port %s", regex_col, host_id, limits); - } - } else { - if (set.total_snmp_ports == 1) { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " %s", regex_col, host_id, limits); - } else { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " ORDER BY snmp_port %s", regex_col, host_id, limits); - } - } - - /* query to setup the next polling interval in cacti */ - snprintf(query6, BUFSIZE, - "UPDATE poller_item" - " SET rrd_next_step = IF(rrd_step = %i, 0, IF(rrd_next_step - %i < 0, rrd_step - %i, rrd_next_step - %i))" - " WHERE host_id = %i", set.poller_interval, set.poller_interval, set.poller_interval, set.poller_interval, host_id); - - /* query to add output records to the poller output table */ - snprintf(query8, BUFSIZE, - "INSERT INTO poller_output" - " (local_data_id, rrd_name, time, output) VALUES"); - - /* query suffix to add rows to the poller output table */ - if (set.dbonupdate == 0) { - snprintf(posuffix, BUFSIZE, - " ON DUPLICATE KEY UPDATE output=VALUES(output)"); - } else { - snprintf(posuffix, BUFSIZE, - " AS rs ON DUPLICATE KEY UPDATE output=rs.output"); - } - - /* number of agent's count for single polling interval */ - snprintf(query9, BUFSIZE, - "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" - " FROM poller_item" - " WHERE host_id = %i" - " GROUP BY snmp_port %s", host_id, limits); - - /* number of agent's count for multiple polling intervals */ - if (set.active_profiles != 1) { - snprintf(query10, BUFSIZE, - "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" - " FROM poller_item" - " WHERE host_id = %i" - " AND rrd_next_step <= 0" - " GROUP BY snmp_port %s", host_id, limits); - } else { - snprintf(query10, BUFSIZE, - "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" - " FROM poller_item" - " WHERE host_id = %i" - " GROUP BY snmp_port %s", host_id, limits); - } - } else { - if (set.total_snmp_ports == 1) { - snprintf(query1, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND poller_id=%i %s", regex_col, host_id, set.poller_id, limits); - } else { - snprintf(query1, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND poller_id=%i" - " ORDER BY snmp_port %s", regex_col, host_id, set.poller_id, limits); - } - - /* host structure for uptime checks */ - snprintf(query2, BIG_BUFSIZE, - "SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, " - "snmp_username, snmp_password, snmp_auth_protocol, " - "snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, " - "availability_method, ping_method, ping_port, ping_timeout, ping_retries, " - "status, status_event_count, UNIX_TIMESTAMP(status_fail_date), " - "UNIX_TIMESTAMP(status_rec_date), status_last_error, " - "min_time, max_time, cur_time, avg_time, " - "total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, " - "snmp_sysContact, snmp_sysName, snmp_sysLocation" - " FROM host" - " WHERE id = %i" - " AND deleted = ''", host_id); - - /* data query structure for reindex detection */ - snprintf(query4, BUFSIZE, - "SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1" - " FROM poller_reindex" - " WHERE host_id = %i", host_id); - - /* multiple polling interval query for items */ - if (set.active_profiles != 1) { - if (set.total_snmp_ports == 1) { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND rrd_next_step <= 0" - " AND poller_id = %i %s", regex_col, host_id, set.poller_id, limits); - } else { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND rrd_next_step <= 0" - " AND poller_id = %i" - " ORDER BY snmp_port %s", regex_col, host_id, set.poller_id, limits); - } - } else { - if (set.total_snmp_ports == 1) { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND poller_id = %i %s", regex_col, host_id, set.poller_id, limits); - } else { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND poller_id = %i" - " ORDER BY snmp_port %s", regex_col, host_id, set.poller_id, limits); - } - } - - /* query to setup the next polling interval in cacti */ - snprintf(query6, BUFSIZE, - "UPDATE poller_item" - " SET rrd_next_step = IF(rrd_step = %i, 0, IF(rrd_next_step - %i < 0, rrd_step - %i, rrd_next_step - %i))" - " WHERE host_id = %i" - " AND poller_id = %i", set.poller_interval, set.poller_interval, set.poller_interval, set.poller_interval, host_id, set.poller_id); - - /* query to add output records to the poller output table */ - snprintf(query8, BUFSIZE, - "INSERT INTO poller_output" - " (local_data_id, rrd_name, time, output) VALUES"); - - /* query suffix to add rows to the poller output table */ - snprintf(posuffix, BUFSIZE, - " ON DUPLICATE KEY UPDATE output=VALUES(output)"); - - /* number of agent's count for single polling interval */ - snprintf(query9, BUFSIZE, - "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" - " FROM poller_item" - " WHERE host_id = %i" - " AND poller_id = %i" - " GROUP BY snmp_port %s", host_id, set.poller_id, limits); - - /* number of agent's count for multiple polling intervals */ - if (set.active_profiles != 1) { - snprintf(query10, BUFSIZE, - "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" - " FROM poller_item" - " WHERE host_id = %i" - " AND rrd_next_step <= 0" - " AND poller_id = %i" - " GROUP BY snmp_port %s", host_id, set.poller_id, limits); - } else { - snprintf(query10, BUFSIZE, - "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" - " FROM poller_item" - " WHERE host_id = %i" - " AND poller_id = %i" - " GROUP BY snmp_port %s", host_id, set.poller_id, limits); - } - } - - /* query to add output records to the poller output table */ - snprintf(query11, BUFSIZE, - "INSERT INTO poller_output_boost" - " (local_data_id, rrd_name, time, output) VALUES"); - - query8_len = strlen(query8); - query11_len = strlen(query11); - posuffix_len = strlen(posuffix); + poll_host_build_queries(&q, host_id, regex_col, limits); /* initialize the ping structure variables */ snprintf(ping->ping_status, 50, "down"); @@ -582,32 +534,14 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* if the host is a real host. Note host_id=0 is not host based data source */ if (host_id) { /* get data about this host */ - if ((result = db_query(&mysql, LOCAL, query2)) != 0) { + if ((result = db_query(&mysql, LOCAL, q.query2)) != 0) { num_rows = mysql_num_rows(result); if (num_rows != 1) { db_free_result(result); - if (local_cnn != NULL) { - db_release_connection(LOCAL, local_cnn->id); - } else { - SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized local connection.", host_id, host_thread)); - } - - if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) { - if (remote_cnn != NULL) { - db_release_connection(REMOTE, remote_cnn->id); - } else { - SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized remote connection.", host_id, host_thread)); - } - } - - SPINE_FREE(host); - SPINE_FREE(reindex); - SPINE_FREE(ping); - SPINE_FREE(error_string); - SPINE_FREE(buf_size); - SPINE_FREE(buf_errors); + poll_host_release(&host, &reindex, &ping, &error_string, &buf_size, &buf_errors, + local_cnn, remote_cnn, host_id, host_thread); return; } @@ -875,35 +809,15 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread } if (set.ping_only) { - SPINE_FREE(host); - SPINE_FREE(reindex); - SPINE_FREE(ping); - SPINE_FREE(error_string); - SPINE_FREE(buf_size); - SPINE_FREE(buf_errors); - - if (local_cnn != NULL) { - db_release_connection(LOCAL, local_cnn->id); - } else { - SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized local connection.", host_id, host_thread)); - } - - if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) { - if (remote_cnn != NULL) { - db_release_connection(REMOTE, remote_cnn->id); - } else { - SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized remote connection.", host_id, host_thread)); - } - } - - mysql_thread_end(); + poll_host_release(&host, &reindex, &ping, &error_string, &buf_size, &buf_errors, + local_cnn, remote_cnn, host_id, host_thread); return; } /* do the reindex check for this host if not script based */ if ((!host->ignore_host) && (host_id)) { - if ((result = db_query(&mysql, LOCAL, query4)) != 0) { + if ((result = db_query(&mysql, LOCAL, q.query4)) != 0) { num_rows = mysql_num_rows(result); if (num_rows > 0) { @@ -1269,14 +1183,14 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread num_rows = 0; if (set.poller_interval == 0) { /* get the poller items */ - if ((result = db_query(&mysql, LOCAL, query1)) != 0) { + if ((result = db_query(&mysql, LOCAL, q.query1)) != 0) { num_rows = mysql_num_rows(result); } else { SPINE_LOG(("Device[%i] HT[%i] ERROR: Unable to Retrieve Rows due to Null Result!", host->id, host_thread)); } } else { /* get the poller items */ - if ((result = db_query(&mysql, LOCAL, query5)) != 0) { + if ((result = db_query(&mysql, LOCAL, q.query5)) != 0) { num_rows = mysql_num_rows(result); } else { SPINE_LOG(("Device[%i] HT[%i] ERROR: Unable to Retrieve Rows due to Null Result!", host->id, host_thread)); @@ -1852,7 +1766,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread memset(query3, 0, buf_length); /* append data */ - strncat(query3, query8, query8_len); + strncat(query3, q.query8, q.query8_len); out_buffer = strlen(query3); @@ -1866,7 +1780,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread memset(query12, 0, buf_length); /* append data */ - strncat(query12, query11, query11_len); + strncat(query12, q.query11, q.query11_len); } int mode; @@ -1899,7 +1813,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* if the next element to the buffer will overflow it, write to the database */ if ((out_buffer + result_length) >= MAX_MYSQL_BUF_SIZE) { /* append the suffix */ - strncat(query3, posuffix, posuffix_len); + strncat(query3, q.posuffix, q.posuffix_len); /* insert the record */ db_insert(&mysqlt, mode, query3); @@ -1907,18 +1821,18 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* re-initialize the query buffer */ memset(query3, 0, MAX_MYSQL_BUF_SIZE+RESULTS_BUFFER); - strncat(query3, query8, query8_len); + strncat(query3, q.query8, q.query8_len); /* insert the record for boost */ if (set.boost_redirect && set.boost_enabled) { /* append the suffix */ - strncat(query12, posuffix, posuffix_len); + strncat(query12, q.posuffix, q.posuffix_len); db_insert(&mysqlt, mode, query12); memset(query12, 0, MAX_MYSQL_BUF_SIZE+RESULTS_BUFFER); - strncat(query12, query11, query11_len); + strncat(query12, q.query11, q.query11_len); } /* reset the output buffer length */ @@ -1947,9 +1861,9 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread } /* perform the last insert if there is data to process */ - if (out_buffer > strlen(query8)) { + if (out_buffer > strlen(q.query8)) { /* append the suffix */ - strncat(query3, posuffix, posuffix_len); + strncat(query3, q.posuffix, q.posuffix_len); /* insert records into database */ db_insert(&mysqlt, mode, query3); @@ -1957,7 +1871,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* insert the record for boost */ if (set.boost_redirect && set.boost_enabled) { /* append the suffix */ - strncat(query12, posuffix, posuffix_len); + strncat(query12, q.posuffix, q.posuffix_len); db_insert(&mysqlt, mode, query12); } @@ -1989,7 +1903,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread if (host_thread == host_threads && set.active_profiles != 1) { SPINE_LOG_MEDIUM(("Device[%i] HT[%i] Updating Poller Items for Next Poll", host_id, host_thread)); - db_query(&mysql, LOCAL, query6); + db_query(&mysql, LOCAL, q.query6); } /* record the polling time for the device */ @@ -2007,9 +1921,9 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread details[device_counter]->complete = TRUE; poll_time = get_time_as_double(); - query1[0] = '\0'; - snprintf(query1, BUFSIZE, "UPDATE host SET polling_time = %.3f - %.3f WHERE id = %i", poll_time, host_time_double, host_id); - db_query(&mysql, LOCAL, query1); + q.query1[0] = '\0'; + snprintf(q.query1, BUFSIZE, "UPDATE host SET polling_time = %.3f - %.3f WHERE id = %i", poll_time, host_time_double, host_id); + db_query(&mysql, LOCAL, q.query1); } @@ -2034,19 +1948,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread thread_mutex_unlock(LOCK_THDET); - if (local_cnn != NULL) { - db_release_connection(LOCAL, local_cnn->id); - } else { - SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized local connection.", host_id, host_thread)); - } - - if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) { - if (remote_cnn != NULL) { - db_release_connection(REMOTE, remote_cnn->id); - } else { - SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized remote connection.", host_id, host_thread)); - } - } + poll_host_release_connections(local_cnn, remote_cnn, host_id, host_thread); mysql_thread_end(); diff --git a/spine.h b/spine.h index 7344424c..ed061ebf 100644 --- a/spine.h +++ b/spine.h @@ -626,6 +626,31 @@ typedef struct db_connection { #include "error.h" /* Globals */ +/*! Every query poll_host() issues for one device. + * + * Grouped so the construction can be built and inspected on its own; the + * three lengths are cached because the result loop appends these fragments + * once per data source. */ +typedef struct { + char query1[BUFSIZE]; /* poller_item rows this poller owns */ + char query2[BIG_BUFSIZE]; /* host row, for the uptime checks */ + char query4[BUFSIZE]; + char query5[BUFSIZE]; + char query6[BUFSIZE]; + char query8[BUFSIZE]; + char query9[BUFSIZE]; + char query10[BUFSIZE]; + char query11[BUFSIZE]; + char posuffix[BUFSIZE]; /* upsert tail for poller_output */ + int query8_len; + int query11_len; + int posuffix_len; +} poll_host_queries_t; + +extern void poll_host_build_queries(poll_host_queries_t *q, int host_id, const char *regex_col, const char *limits); +extern void poller_item_scope(char *out, size_t len, int poller_id); +extern void poller_owner_scope(char *out, size_t len, int poller_id); + extern config_t set; extern php_t *php_processes; extern char start_datetime[20]; diff --git a/tests/golden/README.md b/tests/golden/README.md new file mode 100644 index 00000000..0486c50b --- /dev/null +++ b/tests/golden/README.md @@ -0,0 +1,28 @@ +# Golden capture for poll_host() query construction + +`poll_host_queries.golden` holds every query `poll_host()` builds for one +device, across the inputs it branches on: `total_snmp_ports` 1 and 2, +`dbonupdate` 0 and 1, a main poller and a remote poller. + +`test_build_queries_matches_the_golden_capture` in `tests/unit/test_linked.c` +regenerates that output from `poll_host_build_queries()` and diffs it line by +line, so `make check` fails on any change to the SQL. It reports the first +differing line and prints both sides. + +Point it at another file with `SPINE_GOLDEN=path ./tests/unit/test_linked`. + +## Changing the queries + +A deliberate change makes the test fail. That is the point: update the fixture +in the same commit as the code, and say in the commit message what moved. Do +not regenerate it without reading the diff first, and never regenerate it to +make a red build green. + +## Extending it + +The fixture only covers what `poll_host_build_queries()` produces. The rest of +`poll_host()` still builds and mutates state inline and is not reachable from a +test. When the next piece comes out, capture what it emits before moving it, +extract, then require the diff to be empty or to contain only changes named in +advance. That is how #590 was found: the two branches of the old code disagreed +about `dbonupdate`, and the capture showed it. diff --git a/tests/golden/poll_host_queries.golden b/tests/golden/poll_host_queries.golden new file mode 100644 index 00000000..c75d0b26 --- /dev/null +++ b/tests/golden/poll_host_queries.golden @@ -0,0 +1,152 @@ +== MAIN ports=1 onupd=0 == +### main query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND deleted = '' LIMIT 0,100 +### main query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### main query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### main query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 LIMIT 0,100 +### main query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 +### main query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### main query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main posuffix + ON DUPLICATE KEY UPDATE output=VALUES(output) +== MAIN ports=1 onupd=1 == +### main query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND deleted = '' LIMIT 0,100 +### main query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### main query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### main query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 LIMIT 0,100 +### main query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 +### main query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### main query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main posuffix + AS rs ON DUPLICATE KEY UPDATE output=rs.output +== MAIN ports=2 onupd=0 == +### main query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND deleted = '' ORDER BY snmp_port LIMIT 0,100 +### main query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### main query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### main query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 ORDER BY snmp_port LIMIT 0,100 +### main query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 +### main query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### main query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main posuffix + ON DUPLICATE KEY UPDATE output=VALUES(output) +== MAIN ports=2 onupd=1 == +### main query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND deleted = '' ORDER BY snmp_port LIMIT 0,100 +### main query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### main query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### main query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 ORDER BY snmp_port LIMIT 0,100 +### main query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 +### main query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### main query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main posuffix + AS rs ON DUPLICATE KEY UPDATE output=rs.output +== REMOTE ports=1 onupd=0 pid=3 == +### remote query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 3 LIMIT 0,100 +### remote query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### remote query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### remote query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 3 LIMIT 0,100 +### remote query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 AND poller_id = 3 +### remote query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### remote query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 3 GROUP BY snmp_port LIMIT 0,100 +### remote query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 3 GROUP BY snmp_port LIMIT 0,100 +### remote posuffix + ON DUPLICATE KEY UPDATE output=VALUES(output) +== REMOTE ports=1 onupd=1 pid=7 == +### remote query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 7 LIMIT 0,100 +### remote query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### remote query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### remote query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 7 LIMIT 0,100 +### remote query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 AND poller_id = 7 +### remote query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### remote query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 7 GROUP BY snmp_port LIMIT 0,100 +### remote query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 7 GROUP BY snmp_port LIMIT 0,100 +### remote posuffix + AS rs ON DUPLICATE KEY UPDATE output=rs.output +== REMOTE ports=2 onupd=0 pid=3 == +### remote query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 3 ORDER BY snmp_port LIMIT 0,100 +### remote query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### remote query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### remote query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 3 ORDER BY snmp_port LIMIT 0,100 +### remote query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 AND poller_id = 3 +### remote query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### remote query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 3 GROUP BY snmp_port LIMIT 0,100 +### remote query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 3 GROUP BY snmp_port LIMIT 0,100 +### remote posuffix + ON DUPLICATE KEY UPDATE output=VALUES(output) +== REMOTE ports=2 onupd=1 pid=7 == +### remote query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 7 ORDER BY snmp_port LIMIT 0,100 +### remote query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### remote query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### remote query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 7 ORDER BY snmp_port LIMIT 0,100 +### remote query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 AND poller_id = 7 +### remote query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### remote query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 7 GROUP BY snmp_port LIMIT 0,100 +### remote query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 7 GROUP BY snmp_port LIMIT 0,100 +### remote posuffix + AS rs ON DUPLICATE KEY UPDATE output=rs.output diff --git a/tests/unit/test_linked.c b/tests/unit/test_linked.c index 71371452..73fada4b 100644 --- a/tests/unit/test_linked.c +++ b/tests/unit/test_linked.c @@ -457,6 +457,269 @@ static void test_is_debug_device_matches_only_listed_ids(void **state) { debug_devices = saved; } + +/* poll_host() built the same six queries twice, once for the main poller and + * once for a remote one, differing only in how each query is scoped. A column + * added to one copy would not have reached the other, and none of it was + * reachable from a test. These two helpers hold the scoping rule. + */ +static void test_poller_item_scope_filters_deleted_on_the_main_poller(void **state) { + char scope[64]; + (void) state; + + poller_item_scope(scope, sizeof scope, 0); + assert_string_equal(scope, " AND deleted = ''"); +} + +static void test_poller_item_scope_filters_by_owner_on_a_remote_poller(void **state) { + char scope[64]; + (void) state; + + poller_item_scope(scope, sizeof scope, 3); + assert_string_equal(scope, " AND poller_id = 3"); + + poller_item_scope(scope, sizeof scope, 1); + assert_string_equal(scope, " AND poller_id = 1"); +} + +/* The main poller does not constrain the ownership queries at all, so the + * fragment has to be empty rather than absent: the caller interpolates it + * unconditionally. */ +static void test_poller_owner_scope_is_empty_on_the_main_poller(void **state) { + char scope[64]; + (void) state; + + memcpy(scope, "stale", 6); + poller_owner_scope(scope, sizeof scope, 0); + assert_string_equal(scope, ""); +} + +static void test_poller_owner_scope_names_the_remote_poller(void **state) { + char scope[64]; + (void) state; + + poller_owner_scope(scope, sizeof scope, 7); + assert_string_equal(scope, " AND poller_id = 7"); +} + +/* Both helpers are handed fixed stack buffers by poll_host(), so a degenerate + * size must not write. */ +static void test_poller_scopes_refuse_a_degenerate_buffer(void **state) { + char scope[8]; + (void) state; + + memcpy(scope, "keep", 5); + poller_item_scope(scope, 0, 0); + assert_string_equal(scope, "keep"); + + poller_owner_scope(scope, 0, 4); + assert_string_equal(scope, "keep"); + + poller_item_scope(NULL, sizeof scope, 0); + poller_owner_scope(NULL, sizeof scope, 0); +} + + +/* --------------------------------------------------------------------------- + * poll_host_build_queries() + * + * poll_host() is 1,600+ lines and builds its SQL inline, so none of this was + * reachable from a test. The construction now lives in its own function, and + * these pin what it emits across every input it branches on, against the + * fixture in tests/golden/poll_host_queries.golden. + * ------------------------------------------------------------------------- */ + +static void build_with(poll_host_queries_t *q, int poller_id, int ports, int dbonupdate) { + set.poller_id = poller_id; + set.total_snmp_ports = ports; + set.dbonupdate = dbonupdate; + set.poller_interval = 60; + set.active_profiles = 1; + + memset(q, 0, sizeof(*q)); + poll_host_build_queries(q, 42, ", 'RE' AS re", "LIMIT 0,100"); +} + +static void test_build_queries_scopes_the_main_poller_by_deleted(void **state) { + poll_host_queries_t q; + + (void) state; + build_with(&q, 0, 1, 0); + + assert_non_null(strstr(q.query1, " AND deleted = ''")); + assert_null(strstr(q.query1, "poller_id")); + /* the ownership filter is absent, not defaulted to some poller */ + assert_null(strstr(q.query5, "poller_id")); + assert_null(strstr(q.query9, "poller_id")); +} + +static void test_build_queries_scopes_a_remote_poller_by_owner(void **state) { + poll_host_queries_t q; + + (void) state; + build_with(&q, 7, 1, 0); + + assert_non_null(strstr(q.query1, " AND poller_id = 7")); + assert_null(strstr(q.query1, "deleted")); + assert_non_null(strstr(q.query5, " AND poller_id = 7")); + assert_non_null(strstr(q.query9, " AND poller_id = 7")); + assert_non_null(strstr(q.query10, " AND poller_id = 7")); + + /* the host row is filtered by deleted on both, never by owner */ + assert_non_null(strstr(q.query2, " AND deleted = ''")); + assert_null(strstr(q.query2, "poller_id")); +} + +static void test_build_queries_orders_by_port_only_for_multiple_ports(void **state) { + poll_host_queries_t q; + + (void) state; + build_with(&q, 0, 1, 0); + assert_null(strstr(q.query1, "ORDER BY snmp_port")); + + build_with(&q, 0, 2, 0); + assert_non_null(strstr(q.query1, "ORDER BY snmp_port")); +} + +/* The defect the extraction exposed: the remote branch had its own copy of + this suffix and never picked up the version check. */ +static void test_build_queries_applies_dbonupdate_on_both_poller_types(void **state) { + poll_host_queries_t q; + + (void) state; + + build_with(&q, 0, 1, 0); + assert_string_equal(q.posuffix, " ON DUPLICATE KEY UPDATE output=VALUES(output)"); + build_with(&q, 0, 1, 1); + assert_string_equal(q.posuffix, " AS rs ON DUPLICATE KEY UPDATE output=rs.output"); + + build_with(&q, 7, 1, 0); + assert_string_equal(q.posuffix, " ON DUPLICATE KEY UPDATE output=VALUES(output)"); + build_with(&q, 7, 1, 1); + assert_string_equal(q.posuffix, " AS rs ON DUPLICATE KEY UPDATE output=rs.output"); +} + +static void test_build_queries_caches_the_lengths_the_result_loop_uses(void **state) { + poll_host_queries_t q; + + (void) state; + build_with(&q, 0, 1, 0); + + assert_int_equal(q.query8_len, (int) strlen(q.query8)); + assert_int_equal(q.query11_len, (int) strlen(q.query11)); + assert_int_equal(q.posuffix_len, (int) strlen(q.posuffix)); +} + +static void test_build_queries_fills_every_buffer(void **state) { + poll_host_queries_t q; + + (void) state; + build_with(&q, 0, 1, 0); + + assert_true(strlen(q.query1) > 0); + assert_true(strlen(q.query2) > 0); + assert_true(strlen(q.query4) > 0); + assert_true(strlen(q.query5) > 0); + assert_true(strlen(q.query6) > 0); + assert_true(strlen(q.query8) > 0); + assert_true(strlen(q.query9) > 0); + assert_true(strlen(q.query10) > 0); + assert_true(strlen(q.query11) > 0); + assert_true(strlen(q.posuffix) > 0); +} + +/* The golden fixture, executed rather than documented. Regenerate it with + SPINE_WRITE_GOLDEN=1 and read the diff before committing the result. */ +static void emit_one(FILE *f, const char *tag, poll_host_queries_t *q) { + fprintf(f, "### %s query1\n%s\n", tag, q->query1); + fprintf(f, "### %s query2\n%s\n", tag, q->query2); + fprintf(f, "### %s query4\n%s\n", tag, q->query4); + fprintf(f, "### %s query5\n%s\n", tag, q->query5); + fprintf(f, "### %s query6\n%s\n", tag, q->query6); + fprintf(f, "### %s query8\n%s\n", tag, q->query8); + fprintf(f, "### %s query9\n%s\n", tag, q->query9); + fprintf(f, "### %s query10\n%s\n", tag, q->query10); + fprintf(f, "### %s posuffix\n%s\n", tag, q->posuffix); +} + +static void write_all(FILE *f) { + poll_host_queries_t q; + int ports[2] = {1, 2}; + int onupd[2] = {0, 1}; + int pid[2] = {3, 7}; + int i, j; + + for (i = 0; i < 2; i++) { + for (j = 0; j < 2; j++) { + build_with(&q, 0, ports[i], onupd[j]); + fprintf(f, "== MAIN ports=%d onupd=%d ==\n", ports[i], onupd[j]); + emit_one(f, "main", &q); + } + } + for (i = 0; i < 2; i++) { + for (j = 0; j < 2; j++) { + build_with(&q, pid[j], ports[i], onupd[j]); + fprintf(f, "== REMOTE ports=%d onupd=%d pid=%d ==\n", ports[i], onupd[j], pid[j]); + emit_one(f, "remote", &q); + } + } +} + +static void test_build_queries_matches_the_golden_capture(void **state) { + const char *path = getenv("SPINE_GOLDEN"); + char actual[] = "/tmp/spine_golden_actual.XXXXXX"; + FILE *f; + FILE *g; + int fd; + int line = 0; + char a[BIG_BUFSIZE]; + char b[BIG_BUFSIZE]; + + (void) state; + + if (path == NULL) { + path = "tests/golden/poll_host_queries.golden"; + } + + g = fopen(path, "r"); + if (g == NULL) { + print_message("golden fixture %s not readable, skipping\n", path); + return; + } + + fd = mkstemp(actual); + assert_true(fd >= 0); + f = fdopen(fd, "w+"); + assert_non_null(f); + + write_all(f); + fflush(f); + rewind(f); + + while (fgets(a, sizeof(a), g) != NULL) { + line++; + if (fgets(b, sizeof(b), f) == NULL) { + fclose(g); + fclose(f); + unlink(actual); + fail_msg("golden has more lines than produced, first missing at %d", line); + } + if (strcmp(a, b) != 0) { + print_message("line %d\n golden: %s actual: %s", line, a, b); + fclose(g); + fclose(f); + unlink(actual); + fail_msg("query construction diverged from the golden capture at line %d", line); + } + } + + assert_null(fgets(b, sizeof(b), f)); + + fclose(g); + fclose(f); + unlink(actual); +} + int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_strncopy_truncates_within_the_buffer), @@ -496,6 +759,18 @@ int main(void) { cmocka_unit_test(test_get_date_format_clamps_an_out_of_range_format), cmocka_unit_test(test_get_date_format_covers_each_supported_format), cmocka_unit_test(test_is_debug_device_matches_only_listed_ids), + cmocka_unit_test(test_poller_item_scope_filters_deleted_on_the_main_poller), + cmocka_unit_test(test_poller_item_scope_filters_by_owner_on_a_remote_poller), + cmocka_unit_test(test_poller_owner_scope_is_empty_on_the_main_poller), + cmocka_unit_test(test_poller_owner_scope_names_the_remote_poller), + cmocka_unit_test(test_poller_scopes_refuse_a_degenerate_buffer), + cmocka_unit_test(test_build_queries_scopes_the_main_poller_by_deleted), + cmocka_unit_test(test_build_queries_scopes_a_remote_poller_by_owner), + cmocka_unit_test(test_build_queries_orders_by_port_only_for_multiple_ports), + cmocka_unit_test(test_build_queries_applies_dbonupdate_on_both_poller_types), + cmocka_unit_test(test_build_queries_caches_the_lengths_the_result_loop_uses), + cmocka_unit_test(test_build_queries_fills_every_buffer), + cmocka_unit_test(test_build_queries_matches_the_golden_capture), }; return cmocka_run_group_tests(tests, NULL, NULL);