* auth flow does not use the "mod_auth.authentication-code" event.
*/
if (session.sf_flags & SF_ANON) {
prom_event_incr("auth", 1, "method", "anonymous", NULL);
}
metric_name = "login";
labels = prom_get_labels(cmd->tmp_pool);
prom_cmd_incr_type(cmd, metric_name, labels, PROM_METRIC_TYPE_COUNTER);
prom_cmd_decr(cmd, metric_name, labels);
pr_gettimeofday_millis(&now_ms);
prom_cmd_observe(cmd, metric_name,
(double) ((now_ms - prometheus_connected_ms) / 1000), labels);
return PR_DECLINED(cmd);
}
MODRET prom_err_login(cmd_rec *cmd) {
if (prometheus_engine == FALSE) {
return PR_DECLINED(cmd);
}
prom_cmd_incr_type(cmd, "login_error", NULL, PROM_METRIC_TYPE_COUNTER);
/* Note that we never decrement the "login" gauge here. Why not? A
* failed USER or PASS command could happen for multiple reasons (bad
* sequence, wrong password that will be followed by a correct one, etc).
* Thus the "login" gauge should only be decremented by a successful login,
* or end of connection.
*/
return PR_DECLINED(cmd);
}
MODRET prom_pre_retr(cmd_rec *cmd) {
if (prometheus_engine == FALSE) {
return PR_DECLINED(cmd);
}
prom_cmd_incr_type(cmd, "file_download", NULL, PROM_METRIC_TYPE_GAUGE);
return PR_DECLINED(cmd);
}
MODRET prom_log_retr(cmd_rec *cmd) {
const char *metric_name;
pr_table_t *labels;
if (prometheus_engine == FALSE) {
return PR_DECLINED(cmd);
}
metric_name = "file_download";
labels = prom_get_labels(cmd->tmp_pool);
prom_cmd_incr_type(cmd, metric_name, labels, PROM_METRIC_TYPE_COUNTER);
prom_cmd_decr(cmd, metric_name, labels);
prom_cmd_observe(cmd, metric_name, session.xfer.total_bytes, labels);
return PR_DECLINED(cmd);
}
MODRET prom_err_retr(cmd_rec *cmd) {
if (prometheus_engine == FALSE) {
return PR_DECLINED(cmd);
}
prom_cmd_incr_type(cmd, "file_download_error", NULL,
PROM_METRIC_TYPE_COUNTER);
prom_cmd_decr(cmd, "file_download", NULL);
return PR_DECLINED(cmd);
}
MODRET prom_pre_stor(cmd_rec *cmd) {
if (prometheus_engine == FALSE) {
return PR_DECLINED(cmd);
}
prom_cmd_incr_type(cmd, "file_upload", NULL, PROM_METRIC_TYPE_GAUGE);
return PR_DECLINED(cmd);
}
MODRET prom_log_stor(cmd_rec *cmd) {
const char *metric_name;
pr_table_t *labels;
if (prometheus_engine == FALSE) {
return PR_DECLINED(cmd);
}
metric_name = "file_upload";
labels = prom_get_labels(cmd->tmp_pool);
prom_cmd_incr_type(cmd, metric_name, labels, PROM_METRIC_TYPE_COUNTER);
prom_cmd_decr(cmd, metric_name, labels);
prom_cmd_observe(cmd, metric_name, session.xfer.total_bytes, labels);
return PR_DECLINED(cmd);
}
MODRET prom_err_stor(cmd_rec *cmd) {
if (prometheus_engine == FALSE) {
return PR_DECLINED(cmd);
}
prom_cmd_incr_type(cmd, "file_upload_error", NULL,
PROM_METRIC_TYPE_COUNTER);
prom_cmd_decr(cmd, "file_upload", NULL);
return PR_DECLINED(cmd);
}
MODRET prom_log_auth(cmd_rec *cmd) {
const char *metric_name;
const struct prom_metric *metric;
if (prometheus_engine == FALSE) {
return PR_DECLINED(cmd);
}
/* Note: we are not currently properly incrementing
* session{protocol="ftps"} for FTPS connections accepted using the
* UseImplicitSSL TLSOption.
*
* The issue is that for those connections, the protocol will be set to
* "ftps" in mod_tls' sess_init callback. But here in mod_prometheus, we
* are not guaranteed to being called AFTER mod_tls, due to module load
* ordering. Thus we do not have a good way of determining when to
* increment those counts for implicit FTPS connections.
*/
metric_name = "tls_protocol";
metric = prom_registry_get_metric(prometheus_registry, metric_name);
if (metric != NULL) {
pr_table_t *labels;
const char *tls_version;
labels = prom_get_labels(cmd->tmp_pool);
tls_version = pr_table_get(session.notes, "TLS_PROTOCOL", NULL);
if (tls_version == NULL) {
/* Try the environment. */
tls_version = pr_env_get(cmd->tmp_pool, "TLS_PROTOCOL");
}
if (tls_version != NULL) {
(void) pr_table_add_dup(labels, "version", tls_version, 0);
}
prom_metric_incr(cmd->tmp_pool, metric, 1, labels);
} else {
pr_trace_msg(trace_channel, 19, "%s: unknown '%s' metric requested",
(char *) cmd->argv[0], metric_name);
}
return PR_DECLINED(cmd);
}
/* Event listeners
*/
static void prom_auth_code_ev(const void *event_data, void *user_data) {
int auth_code;
if (prometheus_engine == FALSE) {
return;
}
auth_code = *((int *) event_data);
switch (auth_code) {
case PR_AUTH_OK_NO_PASS:
prom_event_incr("auth", 1, "method", session.rfc2228_mech, NULL);
break;
case PR_AUTH_RFC2228_OK:
prom_event_incr("auth", 1, "method", "certificate", NULL);
break;
case PR_AUTH_OK:
prom_event_incr("auth", 1, "method", "password", NULL);
break;
case PR_AUTH_NOPWD:
prom_event_incr("auth_error", 1, "reason", "unknown user", NULL);
break;
case PR_AUTH_BADPWD:
prom_event_incr("auth_error", 1, "reason", "bad password", NULL);
break;
default:
prom_event_incr("auth_error", 1, NULL);
break;
}
}
static void prom_connect_ev(const void *event_data, void *user_data) {
int flags;
struct prom_dbh *dbh;
/* Close any database handle inherited from our parent, and open a new
* one, per SQLite3 recommendation.
*
* NOTE: session.pool does NOT exist yet.
*/
(void) prom_db_close(prometheus_pool, prometheus_dbh);
prometheus_dbh = NULL;
flags = PROM_DB_OPEN_FL_VACUUM|PROM_DB_OPEN_FL_SKIP_TABLE_INIT;
dbh = prom_metric_db_init(prometheus_pool, prometheus_tables_dir, flags);
if (dbh == NULL) {
pr_trace_msg(trace_channel, 1,
"error initializing '%s' metrics db at connect time: %s",
prometheus_tables_dir, strerror(errno));
} else {
if (prom_registry_set_dbh(prometheus_registry, dbh) < 0) {
pr_trace_msg(trace_channel, 3, "error setting registry dbh: %s",
strerror(errno));
}
}
}
static void prom_exit_ev(const void *event_data, void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
switch (session.disconnect_reason) {
case PR_SESS_DISCONNECT_BANNED:
case PR_SESS_DISCONNECT_CONFIG_ACL:
case PR_SESS_DISCONNECT_MODULE_ACL:
case PR_SESS_DISCONNECT_SESSION_INIT_FAILED: {
const void *reason;
reason = pr_table_get(session.notes, "core.disconnect-details", NULL);
if (reason != NULL) {
prom_event_incr("connection_refused", 1, "reason", reason, NULL);
} else {
prom_event_incr("connection_refused", 1, NULL);
}
break;
}
case PR_SESS_DISCONNECT_SEGFAULT:
prom_event_decr("connection", 1, NULL);
prom_event_incr("segfault", 1, NULL);
break;
default: {
uint64_t now_ms = 0;
if (prometheus_saw_user_cmd == TRUE &&
session.user == NULL) {
/* Login was started, but not completed. */
prom_event_decr("login", 1, NULL);
if (prometheus_saw_pass_cmd == FALSE) {
prom_event_incr("auth_error", 1, "reason", "incomplete", NULL);
}
}
prom_event_decr("connection", 1, NULL);
pr_gettimeofday_millis(&now_ms);
prom_event_observe("connection",
(double) ((now_ms - prometheus_connected_ms) / 1000), NULL);
break;
}
}
prom_http_free();
if (prometheus_logfd >= 0) {
(void) close(prometheus_logfd);
prometheus_logfd = -1;
}
}
static void prom_log_msg_ev(const void *event_data, void *user_data) {
pool *tmp_pool;
int res;
const char *metric_name, *level_text = NULL;
const struct prom_metric *metric;
const pr_log_event_t *le;
pr_table_t *labels;
metric_name = "log_message";
metric = prom_registry_get_metric(prometheus_registry, metric_name);
if (metric == NULL) {
pr_trace_msg(trace_channel, 17, "unknown metric name '%s' requested",
metric_name);
return;
}
le = event_data;
switch (le->log_level) {
case PR_LOG_EMERG:
level_text = "emerg";
break;
case PR_LOG_ALERT:
level_text = "alert";
break;
case PR_LOG_CRIT:
level_text = "crit";
break;
case PR_LOG_ERR:
level_text = "error";
break;
case PR_LOG_WARNING:
level_text = "warn";
break;
case PR_LOG_NOTICE:
level_text = "notice";
break;
case PR_LOG_INFO:
level_text = "info";
break;
case PR_LOG_DEBUG:
level_text = "debug";
break;
default:
level_text = NULL;
break;
}
if (level_text == NULL) {
return;
}
if (session.pool != NULL) {
tmp_pool = make_sub_pool(session.pool);
} else {
tmp_pool = make_sub_pool(prometheus_pool);
}
labels = prom_get_labels(tmp_pool);
(void) pr_table_add_dup(labels, "level", level_text, 0);
res = prom_metric_incr(tmp_pool, metric, 1, labels);
if (res < 0) {
pr_trace_msg(trace_channel, 19, "error increment %s: %s", metric_name,
strerror(errno));
}
destroy_pool(tmp_pool);
}
#if defined(PR_SHARED_MODULE)
static void prom_mod_unload_ev(const void *event_data, void *user_data) {
if (strcmp((const char *) event_data, "mod_prometheus.c") != 0) {
return;
}
/* Unregister ourselves from all events. */
pr_event_unregister(&prometheus_module, NULL, NULL);
(void) prom_db_close(prometheus_pool, prometheus_dbh);
prometheus_dbh = NULL;
prometheus_exporter_http = NULL;
(void) prom_registry_free(prometheus_registry);
prometheus_registry = NULL;
prometheus_tables_dir = NULL;
destroy_pool(prometheus_pool);
prometheus_pool = NULL;
(void) close(prometheus_logfd);
prometheus_logfd = -1;
}
#endif /* PR_SHARED_MODULE */
static void create_session_metrics(pool *p, struct prom_dbh *dbh) {
int res;
struct prom_metric *metric;
/* Session metrics:
*
* auth
* auth_error
* connection
* directory_list
* directory_list_error
* file_download
* file_download_error
* file_upload
* file_upload_error
* login
* login_error
* timeout
* handshake_error
* tls_protocol
* sftp_protocol
*/
metric = prom_metric_create(prometheus_pool, "auth", dbh);
prom_metric_add_counter(metric, "total",
"Number of successful authentications");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "auth_error", dbh);
prom_metric_add_counter(metric, "total",
"Number of failed authentications");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "connection", dbh);
prom_metric_add_counter(metric, "total", "Number of connections");
prom_metric_add_gauge(metric, "count", "Current count of connections");
/* Create histogram buckets for connection duration of:
* 1s, 5s, 10s, 30s, 1m, 5m, 10m, 1h, 6h, 1d
*/
prom_metric_add_histogram(metric, "duration_seconds",
"Connection durations in seconds", 11, (double) 1, (double) 5, (double) 10,
(double) 30, (double) 60, (double) 300, (double) 600, (double) 1800,
(double) 3600, (double) 21600, (double) 86400);
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "directory_list", dbh);
prom_metric_add_counter(metric, "total",
"Number of succesful directory listings");
prom_metric_add_gauge(metric, "count", "Current count of directory listings");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "directory_list_error", dbh);
prom_metric_add_counter(metric, "total",
"Number of failed directory listings");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "file_download", dbh);
prom_metric_add_counter(metric, "total",
"Number of successful file downloads");
prom_metric_add_gauge(metric, "count", "Current count of file downloads");
/* Create histogram buckets for file download bytes of:
* 10K, 50K, 100K, 1M, 10M, 50M, 100M, 500M, 1G, 100G
*/
prom_metric_add_histogram(metric, "bytes",
"Amount of data downloaded in bytes", 10, (double) 10240, (double) 51200,
(double) 102400, (double) 1048576, (double) 10485760, (double) 52428800,
(double) 104857600, (double) 524288000, (double) 1073741824,
(double) 107374182400);
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "file_download_error", dbh);
prom_metric_add_counter(metric, "total", "Number of failed file downloads");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "file_upload", dbh);
prom_metric_add_counter(metric, "total", "Number of successful file uploads");
prom_metric_add_gauge(metric, "count", "Current count of file uploads");
/* Create histogram buckets for file upload bytes of:
* 10K, 50K, 100K, 1M, 10M, 50M, 100M, 500M, 1G, 100G
*/
prom_metric_add_histogram(metric, "bytes",
"Amount of data uploaded in bytes", 10, (double) 10240, (double) 51200,
(double) 102400, (double) 1048576, (double) 10485760, (double) 52428800,
(double) 104857600, (double) 524288000, (double) 1073741824,
(double) 107374182400);
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "file_upload_error", dbh);
prom_metric_add_counter(metric, "total", "Number of failed file uploads");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "login", dbh);
prom_metric_add_counter(metric, "total", "Number of successful logins");
prom_metric_add_gauge(metric, "count", "Current count of logins");
/* Create histogram buckets for login duration of:
* 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 2.5s, 5s, 10s, 30s
*/
prom_metric_add_histogram(metric, "delay_seconds",
"Delay before login in seconds", 11, (double) 0.01, (double) 0.025,
(double) 0.05, (double) 0.1, (double) 0.25, (double) 0.5, (double) 1.0,
(double) 2.5, (double) 5.0, (double) 10.0, (double) 30.0);
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "login_error", dbh);
prom_metric_add_counter(metric, "total", "Number of failed logins");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "timeout", dbh);
prom_metric_add_counter(metric, "total", "Number of timeouts");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "handshake_error", dbh);
prom_metric_add_counter(metric, "total",
"Number of failed SFTP/TLS handshakes");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "sftp_protocol", dbh);
prom_metric_add_counter(metric, "total",
"Number of SFTP sessions by protocol version");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "tls_protocol", dbh);
prom_metric_add_counter(metric, "total",
"Number of TLS sessions by protocol version");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
}
static void create_server_metrics(pool *p, struct prom_dbh *dbh) {
int res;
struct prom_metric *metric;
/* Server metrics:
*
* connection_refused
* log_message
* segfault
*/
metric = prom_metric_create(prometheus_pool, "connection_refused", dbh);
prom_metric_add_counter(metric, "total", "Number of refused connections");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "log_message", dbh);
prom_metric_add_counter(metric, "total", "Number of log_messages");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
metric = prom_metric_create(prometheus_pool, "segfault", dbh);
prom_metric_add_counter(metric, "total", "Number of segfaults");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
}
static void create_metrics(struct prom_dbh *dbh) {
pool *tmp_pool;
int res;
struct prom_metric *metric;
tmp_pool = make_sub_pool(prometheus_pool);
pr_pool_tag(tmp_pool, "Prometheus metrics creation pool");
metric = prom_metric_create(prometheus_pool, "build_info", dbh);
prom_metric_add_counter(metric, NULL, "ProFTPD build information");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
} else {
pr_table_t *labels;
labels = pr_table_nalloc(tmp_pool, 0, 2);
(void) pr_table_add_dup(labels, "proftpd_version", pr_version_get_str(), 0);
(void) pr_table_add_dup(labels, "mod_prometheus_version",
MOD_PROMETHEUS_VERSION, 0);
res = prom_metric_incr(tmp_pool, metric, 1, labels);
if (res < 0) {
pr_trace_msg(trace_channel, 3, "error incrementing metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
}
metric = prom_metric_create(prometheus_pool, "startup_time", dbh);
prom_metric_add_counter(metric, NULL,
"ProFTPD startup time, in unixtime seconds");
res = prom_registry_add_metric(prometheus_registry, metric);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error registering metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
} else {
time_t now;
now = time(NULL);
res = prom_metric_incr(tmp_pool, metric, now, NULL);
if (res < 0) {
pr_trace_msg(trace_channel, 3, "error incrementing metric '%s': %s",
prom_metric_get_name(metric), strerror(errno));
}
}
create_server_metrics(tmp_pool, dbh);
create_session_metrics(tmp_pool, dbh);
res = prom_registry_sort_metrics(prometheus_registry);
if (res < 0) {
pr_trace_msg(trace_channel, 3, "error sorting registry metrics: %s",
strerror(errno));
}
destroy_pool(tmp_pool);
}
static void prom_postparse_ev(const void *event_data, void *user_data) {
config_rec *c;
pr_netaddr_t *exporter_addr;
const char *exporter_username, *exporter_password;
c = find_config(main_server->conf, CONF_PARAM, "PrometheusEngine", FALSE);
if (c != NULL) {
prometheus_engine = *((int *) c->argv[0]);
}
if (prometheus_engine == FALSE) {
return;
}
prom_openlog();
c = find_config(main_server->conf, CONF_PARAM, "PrometheusOptions", FALSE);
while (c != NULL) {
unsigned long opts = 0;
pr_signals_handle();
opts = *((unsigned long *) c->argv[0]);
prometheus_opts |= opts;
c = find_config_next(c, c->next, CONF_PARAM, "PrometheusOptions", FALSE);
}
c = find_config(main_server->conf, CONF_PARAM, "PrometheusTables", FALSE);
if (c == NULL) {
/* No PrometheusTables configured, mod_prometheus cannot run. */
(void) pr_log_writefile(prometheus_logfd, MOD_PROMETHEUS_VERSION,
"no PrometheusTables configured, disabling module");
prometheus_engine = FALSE;
return;
}
prometheus_tables_dir = c->argv[0];
prometheus_dbh = prom_metric_init(prometheus_pool, prometheus_tables_dir);
if (prometheus_dbh == NULL) {
pr_log_pri(PR_LOG_WARNING, MOD_PROMETHEUS_VERSION
": unable to initialize metrics, failing to start up: %s",
strerror(errno));
pr_session_disconnect(&prometheus_module, PR_SESS_DISCONNECT_BAD_CONFIG,
"Failed metrics initialization");
}
prometheus_registry = prom_registry_init(prometheus_pool, "proftpd");
/* Create our known metrics, and register them. */
create_metrics(prometheus_dbh);
c = find_config(main_server->conf, CONF_PARAM, "PrometheusExporter", FALSE);
if (c == NULL) {
prometheus_engine = FALSE;
pr_log_debug(DEBUG0, MOD_PROMETHEUS_VERSION
": missing required PrometheusExporter directive, disabling module");
prom_metric_free(prometheus_pool, prometheus_dbh);
prometheus_dbh = NULL;
prom_registry_free(prometheus_registry);
prometheus_registry = NULL;
return;
}
if (prom_http_init(prometheus_pool) < 0) {
prom_metric_free(prometheus_pool, prometheus_dbh);
prometheus_dbh = NULL;
prom_registry_free(prometheus_registry);
prometheus_registry = NULL;
pr_log_pri(PR_LOG_ERR, MOD_PROMETHEUS_VERSION
": unable to initialize HTTP API, failing to start up: %s",
strerror(errno));
pr_session_disconnect(&prometheus_module, PR_SESS_DISCONNECT_BAD_CONFIG,
"Failed HTTP initialization");
}
exporter_addr = c->argv[0];
exporter_username = c->argv[1];
exporter_password = c->argv[2];
/* Look for the exporter credentials environment variables, too. */
if (exporter_username == NULL) {
exporter_username = pr_env_get(c->pool, "PROMETHEUS_USERNAME");
exporter_password = pr_env_get(c->pool, "PROMETHEUS_PASSWORD");
}
prometheus_exporter_pid = prom_exporter_start(prometheus_pool, exporter_addr,
exporter_username, exporter_password);
if (prometheus_exporter_pid == 0) {
prometheus_engine = FALSE;
pr_log_debug(DEBUG0, MOD_PROMETHEUS_VERSION
": failed to start exporter process, disabling module");
prom_metric_free(prometheus_pool, prometheus_dbh);
prometheus_dbh = NULL;
prom_registry_free(prometheus_registry);
prometheus_registry = NULL;
}
}
static void prom_restart_ev(const void *event_data, void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
pr_trace_msg(trace_channel, 17,
"restart event received, resetting counters");
prom_exporter_stop(prometheus_exporter_pid);
(void) prom_db_close(prometheus_pool, prometheus_dbh);
prometheus_dbh = NULL;
prometheus_exporter_http = NULL;
(void) prom_registry_free(prometheus_registry);
prometheus_registry = NULL;
prometheus_tables_dir = NULL;
/* Close the PrometheusLog file descriptor; it will be reopened in the
* postparse event listener.
*/
(void) close(prometheus_logfd);
prometheus_logfd = -1;
}
static void prom_shutdown_ev(const void *event_data, void *user_data) {
prom_exporter_stop(prometheus_exporter_pid);
(void) prom_db_close(prometheus_pool, prometheus_dbh);
prometheus_dbh = NULL;
destroy_pool(prometheus_pool);
prometheus_pool = NULL;
(void) close(prometheus_logfd);
prometheus_logfd = -1;
}
static void prom_startup_ev(const void *event_data, void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
if (ServerType == SERVER_INETD) {
pr_log_debug(DEBUG0, MOD_PROMETHEUS_VERSION
": cannot support Prometheus for ServerType inetd, disabling module");
prometheus_engine = FALSE;
return;
}
}
static void prom_timeout_idle_ev(const void *event_data, void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("timeout", 1, "reason", "TimeoutIdle", NULL);
}
static void prom_timeout_login_ev(const void *event_data, void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("timeout", 1, "reason", "TimeoutLogin", NULL);
}
static void prom_timeout_noxfer_ev(const void *event_data, void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("timeout", 1, "reason", "TimeoutNoTransfer", NULL);
}
static void prom_timeout_session_ev(const void *event_data, void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("timeout", 1, "reason", "TimeoutSession", NULL);
}
static void prom_timeout_stalled_ev(const void *event_data, void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("timeout", 1, "reason", "TimeoutStalled", NULL);
}
/* mod_tls-generated events */
static void prom_tls_ctrl_handshake_err_ev(const void *event_data,
void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
/* Note that we explicitly set the "protocol" label here to "ftps".
* Otherwise, it would show up as "ftp", since the TLS handshake did
* not actually succeed, and that "ftp" label would be surprising.
*/
prom_event_incr("handshake_error", 1, "connection", "ctrl",
"protocol", "ftps", NULL);
}
static void prom_tls_data_handshake_err_ev(const void *event_data,
void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("handshake_error", 1, "connection", "data", NULL);
}
/* mod_sftp-generated events */
static void prom_ssh2_kex_err_ev(const void *event_data, void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("handshake_error", 1, NULL);
}
static void prom_ssh2_auth_hostbased_ev(const void *event_data,
void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("auth", 1, "method", "hostbased", NULL);
}
static void prom_ssh2_auth_hostbased_err_ev(const void *event_data,
void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("auth_error", 1, "method", "hostbased", NULL);
}
static void prom_ssh2_auth_kbdint_ev(const void *event_data,
void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("auth", 1, "method", "keyboard-interactive", NULL);
}
static void prom_ssh2_auth_kbdint_err_ev(const void *event_data,
void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("auth_error", 1, "method", "keyboard-interactive", NULL);
}
static void prom_ssh2_auth_passwd_ev(const void *event_data,
void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("auth", 1, "method", "password", NULL);
}
static void prom_ssh2_auth_passwd_err_ev(const void *event_data,
void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("auth_error", 1, "method", "password", NULL);
}
static void prom_ssh2_auth_publickey_ev(const void *event_data,
void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("auth", 1, "method", "publickey", NULL);
}
static void prom_ssh2_auth_publickey_err_ev(const void *event_data,
void *user_data) {
if (prometheus_engine == FALSE) {
return;
}
prom_event_incr("auth_error", 1, "method", "publickey", NULL);
}
static void prom_ssh2_sftp_proto_version_ev(const void *event_data,
void *user_data) {
unsigned long protocol_version;
if (prometheus_engine == FALSE) {
return;
}
if (event_data == NULL) {
/* Missing required data. */
return;
}
protocol_version = *((unsigned long *) event_data);
switch (protocol_version) {
case 3:
prom_event_incr("sftp_protocol", 1, "version", "3", NULL);
break;
case 4:
prom_event_incr("sftp_protocol", 1, "version", "4", NULL);
break;
case 5:
prom_event_incr("sftp_protocol", 1, "version", "5", NULL);
break;
case 6:
prom_event_incr("sftp_protocol", 1, "version", "6", NULL);
break;
default:
(void) pr_log_writefile(prometheus_logfd, MOD_PROMETHEUS_VERSION,
"unknown SFTP protocol version %lu, ignoring", protocol_version);
}
}
/* Initialization routines
*/
static int prom_init(void) {
prometheus_pool = make_sub_pool(permanent_pool);
pr_pool_tag(prometheus_pool, MOD_PROMETHEUS_VERSION);
pr_event_register(&prometheus_module, "core.connect", prom_connect_ev, NULL);
#if defined(PR_SHARED_MODULE)
pr_event_register(&prometheus_module, "core.module-unload",
prom_mod_unload_ev, NULL);
#endif /* PR_SHARED_MODULE */
pr_event_register(&prometheus_module, "core.postparse", prom_postparse_ev,
NULL);
pr_event_register(&prometheus_module, "core.restart", prom_restart_ev, NULL);
pr_event_register(&prometheus_module, "core.shutdown", prom_shutdown_ev,
NULL);
pr_event_register(&prometheus_module, "core.startup", prom_startup_ev, NULL);
/* Normally we should register the 'core.exit' event listener in the
* sess_init callback. However, we use this listener to listen for
* refused connections, e.g. connections refused by other modules'
* sess_init callbacks. And depending on the module load order, another
* module might refuse the connection before mod_prometheus's sess_init
* callback is invoked, which would prevent mod_prometheus from registering
* its ' core.exit' event listener.
*
* Thus to work around this timing issue, we register our 'core.exit' event
* listener here, in the daemon process. It should not hurt anything.
*/
pr_event_register(&prometheus_module, "core.exit", prom_exit_ev, NULL);
return 0;
}
static int prom_sess_init(void) {
const char *metric_name;
const struct prom_metric *metric;
if (prometheus_engine == FALSE) {
return 0;
}
pr_event_register(&prometheus_module, "core.timeout-idle",
prom_timeout_idle_ev, NULL);
pr_event_register(&prometheus_module, "core.timeout-login",
prom_timeout_login_ev, NULL);
pr_event_register(&prometheus_module, "core.timeout-no-transfer",
prom_timeout_noxfer_ev, NULL);
pr_event_register(&prometheus_module, "core.timeout-session",
prom_timeout_session_ev, NULL);
pr_event_register(&prometheus_module, "core.timeout-stalled",
prom_timeout_stalled_ev, NULL);
pr_event_register(&prometheus_module, "mod_auth.authentication-code",
prom_auth_code_ev, NULL);
pr_event_register(&prometheus_module, "core.log.syslog", prom_log_msg_ev,
NULL);
pr_event_register(&prometheus_module, "core.log.systemlog", prom_log_msg_ev,
NULL);
if (pr_module_exists("mod_tls.c") == TRUE) {
/* mod_tls events */
pr_event_register(&prometheus_module, "mod_tls.ctrl-handshake-failed",
prom_tls_ctrl_handshake_err_ev, NULL);
pr_event_register(&prometheus_module, "mod_tls.data-handshake-failed",
prom_tls_data_handshake_err_ev, NULL);
}
if (pr_module_exists("mod_sftp.c") == TRUE) {
/* mod_sftp events */
pr_event_register(&prometheus_module, "mod_sftp.ssh2.kex.failed",
prom_ssh2_kex_err_ev, NULL);
pr_event_register(&prometheus_module, "mod_sftp.ssh2.auth-hostbased",
prom_ssh2_auth_hostbased_ev, NULL);
pr_event_register(&prometheus_module, "mod_sftp.ssh2.auth-hostbased.failed",
prom_ssh2_auth_hostbased_err_ev, NULL);
pr_event_register(&prometheus_module, "mod_sftp.ssh2.auth-kbdint",
prom_ssh2_auth_kbdint_ev, NULL);
pr_event_register(&prometheus_module, "mod_sftp.ssh2.auth-kbdint.failed",
prom_ssh2_auth_kbdint_err_ev, NULL);
pr_event_register(&prometheus_module, "mod_sftp.ssh2.auth-password",
prom_ssh2_auth_passwd_ev, NULL);
pr_event_register(&prometheus_module, "mod_sftp.ssh2.auth-password.failed",
prom_ssh2_auth_passwd_err_ev, NULL);
pr_event_register(&prometheus_module, "mod_sftp.ssh2.auth-publickey",
prom_ssh2_auth_publickey_ev, NULL);
pr_event_register(&prometheus_module, "mod_sftp.ssh2.auth-publickey.failed",
prom_ssh2_auth_publickey_err_ev, NULL);
pr_event_register(&prometheus_module, "mod_sftp.sftp.protocol-version",
prom_ssh2_sftp_proto_version_ev, NULL);
}
metric_name = "connection";
metric = prom_registry_get_metric(prometheus_registry, metric_name);
if (metric != NULL) {
pool *tmp_pool;
pr_table_t *labels;
pr_gettimeofday_millis(&prometheus_connected_ms);
tmp_pool = make_sub_pool(session.pool);
labels = prom_get_labels(tmp_pool);
prom_metric_incr(tmp_pool, metric, 1, labels);
destroy_pool(tmp_pool);
} else {
pr_trace_msg(trace_channel, 19, "CONNECT: unknown '%s' metric requested",
metric_name);
}
return 0;
}
/* Module API tables
*/
static conftable prometheus_conftab[] = {
{ "PrometheusEngine", set_prometheusengine, NULL },
{ "PrometheusExporter", set_prometheusexporter, NULL },
{ "PrometheusLog", set_prometheuslog, NULL },
{ "PrometheusOptions", set_prometheusoptions, NULL },
{ "PrometheusTables", set_prometheustables, NULL },
{ NULL }
};
static cmdtable prometheus_cmdtab[] = {
{ PRE_CMD, C_LIST, G_NONE, prom_pre_list, FALSE, FALSE },
{ LOG_CMD, C_LIST, G_NONE, prom_log_list, FALSE, FALSE },
{ LOG_CMD_ERR, C_LIST, G_NONE, prom_err_list, FALSE, FALSE },
{ PRE_CMD, C_MLSD, G_NONE, prom_pre_list, FALSE, FALSE },
{ LOG_CMD, C_MLSD, G_NONE, prom_log_list, FALSE, FALSE },
{ LOG_CMD_ERR, C_MLSD, G_NONE, prom_err_list, FALSE, FALSE },
{ PRE_CMD, C_MLST, G_NONE, prom_pre_list, FALSE, FALSE },
{ LOG_CMD, C_MLST, G_NONE, prom_log_list, FALSE, FALSE },
{ LOG_CMD_ERR, C_MLST, G_NONE, prom_err_list, FALSE, FALSE },
{ PRE_CMD, C_NLST, G_NONE, prom_pre_list, FALSE, FALSE },
{ LOG_CMD, C_NLST, G_NONE, prom_log_list, FALSE, FALSE },
{ LOG_CMD_ERR, C_NLST, G_NONE, prom_err_list, FALSE, FALSE },
{ PRE_CMD, C_USER, G_NONE, prom_pre_user, FALSE, FALSE },
{ LOG_CMD_ERR, C_USER, G_NONE, prom_err_login, FALSE, FALSE },
{ PRE_CMD, C_PASS, G_NONE, prom_pre_pass, FALSE, FALSE },
{ LOG_CMD, C_PASS, G_NONE, prom_log_pass, FALSE, FALSE },
{ LOG_CMD_ERR, C_PASS, G_NONE, prom_err_login, FALSE, FALSE },
{ PRE_CMD, C_RETR, G_NONE, prom_pre_retr, FALSE, FALSE },
{ LOG_CMD, C_RETR, G_NONE, prom_log_retr, FALSE, FALSE },
{ LOG_CMD_ERR, C_RETR, G_NONE, prom_err_retr, FALSE, FALSE },
{ PRE_CMD, C_STOR, G_NONE, prom_pre_stor, FALSE, FALSE },
{ LOG_CMD, C_STOR, G_NONE, prom_log_stor, FALSE, FALSE },
{ LOG_CMD_ERR, C_STOR, G_NONE, prom_err_stor, FALSE, FALSE },
/* For mod_tls */
{ LOG_CMD, C_AUTH, G_NONE, prom_log_auth, FALSE, FALSE },
{ 0, NULL }
};
module prometheus_module = {
/* Always NULL */
NULL, NULL,
/* Module API version */
0x20,
/* Module name */
"prometheus",
/* Module configuration handler table */
prometheus_conftab,
/* Module command handler table */
prometheus_cmdtab,
/* Module authentication handler table */
NULL,
/* Module initialization */
prom_init,
/* Session initialization */
prom_sess_init,
/* Module version */
MOD_PROMETHEUS_VERSION
};
proftpd-mod_prometheus-0.2/mod_prometheus.h.in 0000664 0000000 0000000 00000004121 14106223670 0021670 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_prometheus
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROMETHEUS_H
#define MOD_PROMETHEUS_H
#include "conf.h"
#include "privs.h"
/* Define if you have the microhttpd.h header. */
#undef HAVE_MICROHTTPD_H
#if !defined(HAVE_MICROHTTPD_H)
# error "libmicrohttpd library/headers required"
#endif
/* Define if you have the sqlite3.h header. */
#undef HAVE_SQLITE3_H
#if !defined(HAVE_SQLITE3_H)
# error "SQLite library/headers required"
#endif
/* Define if you have the zlib.h header. */
#undef HAVE_ZLIB_H
/* Define if you have the sqlite3_stmt_readonly() function. */
#undef HAVE_SQLITE3_STMT_READONLY
/* Define if you have the sqlite3_trace() function. */
#undef HAVE_SQLITE3_TRACE
/* Define if you have the sqlite3_trace_v2() function. */
#undef HAVE_SQLITE3_TRACE_V2
#define MOD_PROMETHEUS_VERSION "mod_prometheus/0.2"
/* Make sure the version of proftpd is as necessary. */
#if PROFTPD_VERSION_NUMBER < 0x0001030706
# error "ProFTPD 1.3.7a or later required"
#endif
/* Miscellaneous */
extern int prometheus_logfd;
extern module prometheus_module;
extern pool *prometheus_pool;
#endif /* MOD_PROMETHEUS_H */
proftpd-mod_prometheus-0.2/mod_prometheus.html 0000664 0000000 0000000 00000022247 14106223670 0022011 0 ustar 00root root 0000000 0000000
ProFTPD module mod_prometheus
ProFTPD module mod_prometheus
The purpose of the mod_prometheus
module is to provide metrics
for scraping by Prometheus.
Installation instructions are discussed here.
Note that mod_prometheus
requires ProFTPD 1.3.7a or later.
Detailed notes on best practices for using this module are
here.
The most current version of mod_prometheu
can be found at:
https://github.com/Castaglia/proftpd-mod_prometheus.git
Author
Please contact TJ Saunders <tj at castaglia.org> with any
questions, concerns, or suggestions regarding this module.
Directives
Syntax: PrometheusEngine on|off
Default: off
Context: server config
Module: mod_prometheus
Compatibility: 1.3.7a and later
The PrometheusEngine
directive controls whether the
mod_prometheus
module lists for Prometheus HTTP requests for
scraping metrics.
Syntax: PrometheusExporter address[:port] [username password]
Default: None
Context: server config
Module: mod_prometheus
Compatibility: 1.3.7a and later
The PrometheusExporter
directive configures the
mod_prometheus
module to act as an exporter for scraping by
Prometheus.
Note that the PrometheusExporter
directive is
required.
The address parameter can be an IP address or a DNS name; this
parameter configures the address/port on which mod_prometheus
will listen for Prometheus scrape requests. By default, a port of 9273 is
assumed; use address:port to specify an alternate port, e.g.:
PrometheusExporter 0.0.0.0:19273
Note that IPv6 addresses should be enclosed in square brackets, as they can
contain colons as well, e.g.:
PrometheusExporter [::]:19273
HTTP basic authentication can be required by providing the optional
username and password parameters. The following environment
variables can also be used, instead of configuring these credentials in the
config file:
PROMETHEUS_USERNAME
PROMETHEUS_PASSWORD
Syntax: PrometheusLog path|"none"
Default: None
Context: server config
Module: mod_prometheus
Compatibility: 1.3.7a and later
The PrometheusLog
directive is used to specify a log file for
mod_prometheus
's reporting. The path parameter given
must be the full path to the file to use for logging.
Note that this path must not be to a world-writable directory and,
unless AllowLogSymlinks
is explicitly set to on
(generally a bad idea), the path must not be a symbolic link.
Syntax: PrometheusTables path
Default: None
Context: server config
Module: mod_prometheus
Compatibility: 1.3.7a and later
The PrometheusTables
directive is used to specify a directory that
mod_prometheus
will use for storing its database files; these files
are used for tracking the various statistics reported via Prometheus.
Note that the PrometheusTables
directive is required.
Important Security Considerations
Do not configure mod_prometheus
to listen on a public
Internet address. The information provided via mod_prometheus
can be used by attackers to gain more information about your running
proftpd
, including being able to determine whether their logins
fail due to a wrong password (in which case, they know that that user name is
valid) or not. It is highly recommended that you configure
mod_prometheus
to only listen on internal/LAN addresses.
Furthermore, you should employ a firewall rule that rejects any TCP
connections from the public Internet to your mod_prometheus
exporter.
That said, things are a little different when running in an e.g.
Kubernetes cluster, with an isolated network. In these cases, given that
Kubernetes pods will have dynamic IP addresses, the recommended configuration
is to listen on the wildcard address, i.e. "0.0.0.0". This
works because the Kubernetes cluster network is already isolated from
the public Internet by default.
Prometheus Exporter Process
When proftpd
starts up with mod_prometheus
enabled,
the mod_prometheus
module will fork a new process that acts as
the Prometheus exporter, receiving and responding to all scrape requests.
This exporter listening process automatically switches to the privileges
configured by the User
and Group
directives, and will
also automatically chroot itself to a subdirectory of the
PrometheusTables
directory, after which all root privileges are
permanently dropped.
Example Configuration
The mod_prometheus
module uses an HTTP server for listening for
Prometheus scrape requests. Thus it does not require any separate
<VirtualHost>
sections, and does not interfere with the
normal FTP operations.
Here is an example configuration for mod_prometheus
:
<IfModule mod_prometheus.c>
PrometheusEngine on
PrometheusLog /etc/proftpd/prometheus/prometheus.log
# Configure the exporter to listen on the default port
PrometheusExporter 0.0.0.0
# Configure the directory that mod_prometheus will use for its database files
PrometheusTables /var/proftpd/prometheus
</IfModule>
Logging
The mod_prometheus
module supports different forms of logging. The
main module logging is done via the
PrometheusLog
directive. For
debugging purposes, the module also uses trace logging, via the module-specific channels:
- prometheus
- prometheus.db
- prometheus.http
- prometheus.metric
- prometheus.metric.db
- prometheus.registry
- prometheus.text
Thus for trace logging, to aid in debugging, you would use the following in
your proftpd.conf
:
TraceLog /path/to/proftpd-trace.log
Trace prometheus:20
This trace logging can generate large files; it is intended for debugging
use only, and should be removed from any production configuration.
To install mod_prometheus
, go to the third-party module area in
the proftpd source code and unpack the mod_prometheus
source
tarball:
$ cd proftpd-dir/contrib/
$ tar zxvf /path/to/mod_prometheus-version.tar.gz
after unpacking the latest proftpd-1.3.x source code. For including
mod_prometheus
as a statically linked module:
$ ./configure --with-modules=mod_prometheus:...
To build mod_prometheus
as a DSO module:
$ ./configure --enable-dso --with-shared=mod_prometheus:...
Then follow the usual steps:
$ make
$ make install
Note: mod_prometheus
uses the
SQLite
library; thus the
sqlite3
development library/headers must be installed for
building mod_prometheus
.
It is highly recommended that SQLite 3.8.5 or later be used. Problems
have been reported with mod_prometheus
when SQLite 3.6.20 is used;
these problems disappeared once SQLite was upgraded to a newer version.
Note: mod_prometheus
uses the
libmicrohttpd
library; thus the libmicrohttpd
development library/headers
must be installed for building mod_prometheus
.
© Copyright 2021 TJ Saunders
All Rights Reserved
proftpd-mod_prometheus-0.2/t/ 0000775 0000000 0000000 00000000000 14106223670 0016325 5 ustar 00root root 0000000 0000000 proftpd-mod_prometheus-0.2/t/Makefile.in 0000664 0000000 0000000 00000003551 14106223670 0020376 0 ustar 00root root 0000000 0000000 CC=@CC@
@SET_MAKE@
top_builddir=../../..
top_srcdir=../../..
module_srcdir=..
srcdir=@srcdir@
VPATH=@srcdir@
include $(top_srcdir)/Make.rules
# Necessary redefinitions
INCLUDES=-I. -I.. -I$(module_srcdir)/include -I../../.. -I../../../include @INCLUDES@
TEST_CPPFLAGS=$(ADDL_CPPFLAGS) -DHAVE_CONFIG_H $(DEFAULT_PATHS) $(PLATFORM) $(INCLUDES)
TEST_LDFLAGS=-L$(top_srcdir)/lib @LIBDIRS@
EXEEXT=@EXEEXT@
TEST_API_DEPS=\
$(top_srcdir)/lib/prbase.a \
$(top_srcdir)/src/pool.o \
$(top_srcdir)/src/privs.o \
$(top_srcdir)/src/str.o \
$(top_srcdir)/src/sets.o \
$(top_srcdir)/src/table.o \
$(top_srcdir)/src/event.o \
$(top_srcdir)/src/timers.o \
$(top_srcdir)/src/stash.o \
$(top_srcdir)/src/modules.o \
$(top_srcdir)/src/configdb.o \
$(top_srcdir)/src/parser.o \
$(top_srcdir)/src/fsio.o \
$(top_srcdir)/src/netio.o \
$(top_srcdir)/src/inet.o \
$(top_srcdir)/src/netaddr.o \
$(top_srcdir)/src/response.o \
$(top_srcdir)/src/auth.o \
$(top_srcdir)/src/env.o \
$(top_srcdir)/src/trace.o \
$(top_srcdir)/src/support.o \
$(top_srcdir)/src/error.o \
$(module_srcdir)/lib/prometheus/db.o \
$(module_srcdir)/lib/prometheus/http.o \
$(module_srcdir)/lib/prometheus/metric.o \
$(module_srcdir)/lib/prometheus/metric/db.o \
$(module_srcdir)/lib/prometheus/registry.o \
$(module_srcdir)/lib/prometheus/text.o
TEST_API_LIBS=-lcheck -lm @MODULE_LIBS@
TEST_API_OBJS=\
api/db.o \
api/metric.o \
api/metric/db.o \
api/text.o \
api/registry.o \
api/http.o \
api/stubs.o \
api/tests.o
dummy:
api/.c.o:
$(CC) $(CPPFLAGS) $(TEST_CPPFLAGS) $(CFLAGS) -c $<
api-tests$(EXEEXT): $(TEST_API_OBJS) $(TEST_API_DEPS)
$(LIBTOOL) --mode=link --tag=CC $(CC) $(LDFLAGS) $(TEST_LDFLAGS) -o $@ $(TEST_API_DEPS) $(TEST_API_OBJS) $(TEST_API_LIBS) $(LIBS)
./$@
clean:
$(LIBTOOL) --mode=clean $(RM) *.o api/*.o api/*/*.o api-tests$(EXEEXT) api-tests.log
proftpd-mod_prometheus-0.2/t/api/ 0000775 0000000 0000000 00000000000 14106223670 0017076 5 ustar 00root root 0000000 0000000 proftpd-mod_prometheus-0.2/t/api/db.c 0000664 0000000 0000000 00000072050 14106223670 0017633 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_prometheus API testsuite
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
/* Database API tests. */
#include "tests.h"
#include "prometheus/db.h"
static pool *p = NULL;
static const char *db_test_table = "/tmp/prt-mod_prometheus-db.dat";
static void set_up(void) {
(void) unlink(db_test_table);
if (p == NULL) {
p = permanent_pool = make_sub_pool(NULL);
session.c = NULL;
session.notes = NULL;
}
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("prometheus.db", 1, 20);
}
mark_point();
prom_db_init(p);
}
static void tear_down(void) {
prom_db_free();
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("prometheus.db", 0, 0);
}
if (p != NULL) {
destroy_pool(p);
p = permanent_pool = NULL;
session.c = NULL;
session.notes = NULL;
}
(void) unlink(db_test_table);
}
START_TEST (db_close_test) {
int res;
mark_point();
res = prom_db_close(NULL, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)",
strerror(errno), errno);
mark_point();
res = prom_db_close(p, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)",
strerror(errno), errno);
}
END_TEST
START_TEST (db_open_test) {
int res;
const char *table_path, *schema_name;
struct prom_dbh *dbh;
mark_point();
dbh = prom_db_open(NULL, NULL, NULL);
fail_unless(dbh == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)",
strerror(errno), errno);
mark_point();
dbh = prom_db_open(p, NULL, NULL);
fail_unless(dbh == NULL, "Failed to handle null table path");
fail_unless(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)",
strerror(errno), errno);
(void) unlink(db_test_table);
table_path = db_test_table;
schema_name = "prometheus_test";
mark_point();
dbh = prom_db_open(p, table_path, NULL);
fail_unless(dbh == NULL, "Failed to handle null schema name");
fail_unless(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)",
strerror(errno), errno);
mark_point();
dbh = prom_db_open(p, table_path, schema_name);
fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path,
strerror(errno));
mark_point();
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close table '%s': %s", table_path,
strerror(errno));
(void) unlink(db_test_table);
}
END_TEST
START_TEST (db_open_readonly_test) {
int res;
const char *table_path, *schema_name;
struct prom_dbh *dbh;
mark_point();
dbh = prom_db_open_readonly(NULL, NULL, NULL);
fail_unless(dbh == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)",
strerror(errno), errno);
mark_point();
dbh = prom_db_open_readonly(p, NULL, NULL);
fail_unless(dbh == NULL, "Failed to handle null table path");
fail_unless(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)",
strerror(errno), errno);
(void) unlink(db_test_table);
table_path = db_test_table;
schema_name = "prometheus_test";
mark_point();
dbh = prom_db_open_readonly(p, table_path, NULL);
fail_unless(dbh == NULL, "Failed to handle null schema name");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_db_open_readonly(p, table_path, schema_name);
fail_unless(dbh == NULL, "Failed to handle missing table");
fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM,
strerror(errno), errno);
mark_point();
dbh = prom_db_open(p, table_path, schema_name);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s': %s", table_path, schema_name,
strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
mark_point();
dbh = prom_db_open_readonly(p, table_path, schema_name);
fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path,
strerror(errno));
mark_point();
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close table '%s': %s", table_path,
strerror(errno));
(void) unlink(db_test_table);
}
END_TEST
START_TEST (db_open_with_version_test) {
int res, flags = 0;
struct prom_dbh *dbh;
const char *table_path, *schema_name;
unsigned int schema_version;
mark_point();
dbh = prom_db_open_with_version(NULL, NULL, NULL, 0, 0);
fail_unless(dbh == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)",
strerror(errno), errno);
(void) unlink(db_test_table);
table_path = db_test_table;
schema_name = "prometheus_test";
schema_version = 0;
mark_point();
dbh = prom_db_open_with_version(p, table_path, schema_name, schema_version,
flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
flags |= PROM_DB_OPEN_FL_INTEGRITY_CHECK;
mark_point();
dbh = prom_db_open_with_version(p, table_path, schema_name, schema_version,
flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
if (getenv("CI") == NULL &&
getenv("TRAVIS") == NULL) {
/* Enable the vacuuming for these tests. */
flags |= PROM_DB_OPEN_FL_VACUUM;
mark_point();
dbh = prom_db_open_with_version(p, table_path, schema_name, schema_version,
flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
flags &= ~PROM_DB_OPEN_FL_VACUUM;
}
flags &= ~PROM_DB_OPEN_FL_INTEGRITY_CHECK;
mark_point();
schema_version = 76;
flags |= PROM_DB_OPEN_FL_SCHEMA_VERSION_CHECK|PROM_DB_OPEN_FL_ERROR_ON_SCHEMA_VERSION_SKEW;
dbh = prom_db_open_with_version(p, table_path, schema_name, schema_version,
flags);
fail_unless(dbh == NULL, "Opened table with version skew unexpectedly");
fail_unless(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM,
strerror(errno), errno);
mark_point();
flags &= ~PROM_DB_OPEN_FL_ERROR_ON_SCHEMA_VERSION_SKEW;
dbh = prom_db_open_with_version(p, table_path, schema_name, schema_version,
flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
mark_point();
schema_version = 76;
dbh = prom_db_open_with_version(p, table_path, schema_name, schema_version,
flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close databas: %s", strerror(errno));
mark_point();
schema_version = 99;
dbh = prom_db_open_with_version(p, table_path, schema_name, schema_version,
flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
(void) unlink(db_test_table);
}
END_TEST
START_TEST (db_open_readonly_with_version_test) {
int res, flags = 0;
struct prom_dbh *dbh;
const char *table_path, *schema_name;
unsigned int schema_version;
mark_point();
dbh = prom_db_open_readonly_with_version(NULL, NULL, NULL, 0, 0);
fail_unless(dbh == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)",
strerror(errno), errno);
(void) unlink(db_test_table);
table_path = db_test_table;
schema_name = "prometheus_test";
schema_version = 0;
mark_point();
dbh = prom_db_open_with_version(p, table_path, schema_name, schema_version,
flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
mark_point();
dbh = prom_db_open_readonly_with_version(p, table_path, schema_name,
schema_version, flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
flags |= PROM_DB_OPEN_FL_INTEGRITY_CHECK;
mark_point();
dbh = prom_db_open_readonly_with_version(p, table_path, schema_name,
schema_version, flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
if (getenv("CI") == NULL &&
getenv("TRAVIS") == NULL) {
/* Enable the vacuuming for these tests. */
flags |= PROM_DB_OPEN_FL_VACUUM;
mark_point();
dbh = prom_db_open_readonly_with_version(p, table_path, schema_name,
schema_version, flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
flags &= ~PROM_DB_OPEN_FL_VACUUM;
}
flags &= ~PROM_DB_OPEN_FL_INTEGRITY_CHECK;
mark_point();
schema_version = 76;
flags |= PROM_DB_OPEN_FL_SCHEMA_VERSION_CHECK|PROM_DB_OPEN_FL_ERROR_ON_SCHEMA_VERSION_SKEW;
dbh = prom_db_open_readonly_with_version(p, table_path, schema_name,
schema_version, flags);
fail_unless(dbh == NULL, "Opened table with version skew unexpectedly");
fail_unless(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM,
strerror(errno), errno);
mark_point();
flags &= ~PROM_DB_OPEN_FL_ERROR_ON_SCHEMA_VERSION_SKEW;
dbh = prom_db_open_readonly_with_version(p, table_path, schema_name,
schema_version, flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
mark_point();
schema_version = 76;
dbh = prom_db_open_readonly_with_version(p, table_path, schema_name,
schema_version, flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close databas: %s", strerror(errno));
mark_point();
schema_version = 99;
dbh = prom_db_open_readonly_with_version(p, table_path, schema_name,
schema_version, flags);
fail_unless(dbh != NULL,
"Failed to open table '%s', schema '%s', version %u: %s", table_path,
schema_name, schema_version, strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
(void) unlink(db_test_table);
}
END_TEST
START_TEST (db_exec_stmt_test) {
int res;
const char *table_path, *schema_name, *stmt, *errstr;
struct prom_dbh *dbh;
mark_point();
res = prom_db_exec_stmt(NULL, NULL, NULL, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_db_exec_stmt(p, NULL, NULL, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
(void) unlink(db_test_table);
table_path = db_test_table;
schema_name = "prometheus_test";
mark_point();
dbh = prom_db_open(p, table_path, schema_name);
fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path,
strerror(errno));
mark_point();
res = prom_db_exec_stmt(p, dbh, NULL, NULL);
fail_unless(res < 0, "Failed to handle null statement");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
stmt = "SELECT COUNT(*) FROM foo;";
errstr = NULL;
res = prom_db_exec_stmt(p, dbh, stmt, &errstr);
fail_unless(res < 0, "Failed to execute statement '%s'", stmt);
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
(void) unlink(db_test_table);
}
END_TEST
static int create_table(pool *stmt_pool, struct prom_dbh *dbh,
const char *table_name) {
int res;
const char *stmt, *errstr = NULL;
stmt = pstrcat(stmt_pool, "CREATE TABLE ", table_name,
" (id INTEGER, name TEXT);", NULL);
res = prom_db_exec_stmt(stmt_pool, dbh, stmt, &errstr);
return res;
}
START_TEST (db_prepare_stmt_test) {
int res;
const char *table_path, *schema_name, *stmt;
struct prom_dbh *dbh;
mark_point();
res = prom_db_prepare_stmt(NULL, NULL, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_db_prepare_stmt(p, NULL, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
(void) unlink(db_test_table);
table_path = db_test_table;
schema_name = "prometheus_test";
mark_point();
dbh = prom_db_open(p, table_path, schema_name);
fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path,
strerror(errno));
mark_point();
res = prom_db_prepare_stmt(p, dbh, NULL);
fail_unless(res < 0, "Failed to handle null statement");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
stmt = "foo bar baz?";
res = prom_db_prepare_stmt(p, dbh, stmt);
fail_unless(res < 0, "Prepared invalid statement '%s' unexpectedly", stmt);
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
res = create_table(p, dbh, "foo");
fail_unless(res == 0, "Failed to create table 'foo': %s", strerror(errno));
mark_point();
stmt = "SELECT COUNT(*) FROM foo;";
res = prom_db_prepare_stmt(p, dbh, stmt);
fail_unless(res == 0, "Failed to prepare statement '%s': %s", stmt,
strerror(errno));
mark_point();
res = prom_db_finish_stmt(p, dbh, stmt);
fail_unless(res == 0, "Failed to finish statement '%s': %s", stmt,
strerror(errno));
res = create_table(p, dbh, "bar");
fail_unless(res == 0, "Failed to create table 'bar': %s", strerror(errno));
mark_point();
stmt = "SELECT COUNT(*) FROM bar;";
res = prom_db_prepare_stmt(p, dbh, stmt);
fail_unless(res == 0, "Failed to prepare statement '%s': %s", stmt,
strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
(void) unlink(db_test_table);
}
END_TEST
START_TEST (db_finish_stmt_test) {
int res;
const char *table_path, *schema_name, *stmt;
struct prom_dbh *dbh;
mark_point();
res = prom_db_finish_stmt(NULL, NULL, NULL);
fail_unless(res < 0, "Failed to handle null arguments");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_db_finish_stmt(p, NULL, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
(void) unlink(db_test_table);
table_path = db_test_table;
schema_name = "prometheus_test";
mark_point();
dbh = prom_db_open(p, table_path, schema_name);
fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path,
strerror(errno));
mark_point();
res = prom_db_finish_stmt(p, dbh, NULL);
fail_unless(res < 0, "Failed to handle null statement");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
stmt = "SELECT COUNT(*) FROM foo";
res = prom_db_finish_stmt(p, dbh, stmt);
fail_unless(res < 0, "Failed to handle unprepared statement");
fail_unless(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT,
strerror(errno), errno);
res = create_table(p, dbh, "foo");
fail_unless(res == 0, "Failed to create table 'foo': %s", strerror(errno));
mark_point();
res = prom_db_prepare_stmt(p, dbh, stmt);
fail_unless(res == 0, "Failed to prepare statement '%s': %s", stmt,
strerror(errno));
mark_point();
res = prom_db_finish_stmt(p, dbh, stmt);
fail_unless(res == 0, "Failed to finish statement '%s': %s", stmt,
strerror(errno));
mark_point();
res = prom_db_finish_stmt(p, dbh, stmt);
fail_unless(res < 0, "Failed to handle unprepared statement");
fail_unless(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT,
strerror(errno), errno);
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
(void) unlink(db_test_table);
}
END_TEST
START_TEST (db_bind_stmt_test) {
int res;
const char *table_path, *schema_name, *stmt;
struct prom_dbh *dbh;
int idx, int_val;
long long_val;
double double_val;
char *text_val;
mark_point();
res = prom_db_bind_stmt(NULL, NULL, NULL, -1, -1, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_db_bind_stmt(p, NULL, NULL, -1, -1, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
(void) unlink(db_test_table);
table_path = db_test_table;
schema_name = "prometheus_test";
mark_point();
dbh = prom_db_open(p, table_path, schema_name);
fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path,
strerror(errno));
mark_point();
res = prom_db_bind_stmt(p, dbh, NULL, -1, -1, NULL);
fail_unless(res < 0, "Failed to handle null statement");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
stmt = "SELECT COUNT(*) FROM table";
idx = -1;
res = prom_db_bind_stmt(p, dbh, stmt, idx, PROM_DB_BIND_TYPE_INT, NULL);
fail_unless(res < 0, "Failed to handle invalid index %d", idx);
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
idx = 1;
res = prom_db_bind_stmt(p, dbh, stmt, idx, PROM_DB_BIND_TYPE_INT, NULL);
fail_unless(res < 0, "Failed to handle unprepared statement");
fail_unless(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT,
strerror(errno), errno);
res = create_table(p, dbh, "foo");
fail_unless(res == 0, "Failed to create table 'foo': %s", strerror(errno));
mark_point();
stmt = "SELECT COUNT(*) FROM foo;";
res = prom_db_prepare_stmt(p, dbh, stmt);
fail_unless(res == 0, "Failed to prepare statement '%s': %s", stmt,
strerror(errno));
mark_point();
res = prom_db_bind_stmt(p, dbh, stmt, idx, PROM_DB_BIND_TYPE_INT, NULL);
fail_unless(res < 0, "Failed to handle missing INT value");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
int_val = 7;
res = prom_db_bind_stmt(p, dbh, stmt, idx, PROM_DB_BIND_TYPE_INT, &int_val);
fail_unless(res < 0, "Failed to handle invalid index value");
fail_unless(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM,
strerror(errno), errno);
mark_point();
res = prom_db_bind_stmt(p, dbh, stmt, idx, PROM_DB_BIND_TYPE_LONG, NULL);
fail_unless(res < 0, "Failed to handle missing LONG value");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
long_val = 7;
res = prom_db_bind_stmt(p, dbh, stmt, idx, PROM_DB_BIND_TYPE_LONG,
&long_val);
fail_unless(res < 0, "Failed to handle invalid index value");
fail_unless(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM,
strerror(errno), errno);
mark_point();
double_val = 7.0;
res = prom_db_bind_stmt(p, dbh, stmt, idx, PROM_DB_BIND_TYPE_DOUBLE,
&double_val);
fail_unless(res < 0, "Failed to handle invalid index value");
fail_unless(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM,
strerror(errno), errno);
mark_point();
res = prom_db_bind_stmt(p, dbh, stmt, idx, PROM_DB_BIND_TYPE_TEXT, NULL);
fail_unless(res < 0, "Failed to handle missing TEXT value");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
text_val = "testing";
res = prom_db_bind_stmt(p, dbh, stmt, idx, PROM_DB_BIND_TYPE_TEXT, text_val);
fail_unless(res < 0, "Failed to handle invalid index value");
fail_unless(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM,
strerror(errno), errno);
mark_point();
res = prom_db_bind_stmt(p, dbh, stmt, idx, PROM_DB_BIND_TYPE_NULL, NULL);
fail_unless(res < 0, "Failed to handle invalid NULL value");
fail_unless(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM,
strerror(errno), errno);
mark_point();
stmt = "SELECT COUNT(*) FROM foo WHERE id = ?;";
res = prom_db_prepare_stmt(p, dbh, stmt);
fail_unless(res == 0, "Failed to prepare statement '%s': %s", stmt,
strerror(errno));
mark_point();
int_val = 7;
res = prom_db_bind_stmt(p, dbh, stmt, idx, PROM_DB_BIND_TYPE_INT, &int_val);
fail_unless(res == 0, "Failed to bind INT value: %s", strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
(void) unlink(db_test_table);
}
END_TEST
START_TEST (db_exec_prepared_stmt_test) {
int res;
array_header *results;
const char *table_path, *schema_name, *stmt, *errstr = NULL;
struct prom_dbh *dbh;
mark_point();
results = prom_db_exec_prepared_stmt(NULL, NULL, NULL, NULL);
fail_unless(results == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
results = prom_db_exec_prepared_stmt(p, NULL, NULL, NULL);
fail_unless(results == NULL, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
(void) unlink(db_test_table);
table_path = db_test_table;
schema_name = "prometheus_test";
mark_point();
dbh = prom_db_open(p, table_path, schema_name);
fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path,
strerror(errno));
mark_point();
results = prom_db_exec_prepared_stmt(p, dbh, NULL, NULL);
fail_unless(results == NULL, "Failed to handle null statement");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
stmt = "SELECT COUNT(*) FROM foo;";
results = prom_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
fail_unless(results == NULL, "Failed to handle unprepared statement");
fail_unless(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT,
strerror(errno), errno);
res = create_table(p, dbh, "foo");
fail_unless(res == 0, "Failed to create table 'foo': %s", strerror(errno));
mark_point();
res = prom_db_prepare_stmt(p, dbh, stmt);
fail_unless(res == 0, "Failed to prepare statement '%s': %s", stmt,
strerror(errno));
mark_point();
results = prom_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
fail_unless(results != NULL,
"Failed to execute prepared statement '%s': %s (%s)", stmt, errstr,
strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
(void) unlink(db_test_table);
}
END_TEST
START_TEST (db_reindex_test) {
int res;
const char *table_path, *schema_name, *index_name, *errstr = NULL;
struct prom_dbh *dbh;
mark_point();
res = prom_db_reindex(NULL, NULL, NULL, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_db_reindex(p, NULL, NULL, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
(void) unlink(db_test_table);
table_path = db_test_table;
schema_name = "prometheus_test";
mark_point();
dbh = prom_db_open(p, table_path, schema_name);
fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path,
strerror(errno));
mark_point();
res = prom_db_reindex(p, dbh, NULL, NULL);
fail_unless(res < 0, "Failed to handle null index name");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
index_name = "test_idx";
res = prom_db_reindex(p, dbh, index_name, &errstr);
fail_unless(res < 0, "Failed to handle invalid index");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
fail_unless(errstr != NULL, "Failed to provide error string");
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
(void) unlink(db_test_table);
}
END_TEST
START_TEST (db_last_row_id_test) {
int res;
const char *table_path, *schema_name;
int64_t row_id = 0;
struct prom_dbh *dbh;
mark_point();
res = prom_db_last_row_id(NULL, NULL, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_db_last_row_id(p, NULL, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
(void) unlink(db_test_table);
table_path = db_test_table;
schema_name = "prometheus_test";
mark_point();
dbh = prom_db_open(p, table_path, schema_name);
fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path,
strerror(errno));
mark_point();
res = prom_db_last_row_id(p, dbh, NULL);
fail_unless(res < 0, "Failed to handle null row_id");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_db_last_row_id(p, dbh, &row_id);
fail_unless(res == 0, "Failed to get last row ID: %s", strerror(errno));
res = prom_db_close(p, dbh);
fail_unless(res == 0, "Failed to close database: %s", strerror(errno));
(void) unlink(db_test_table);
}
END_TEST
Suite *tests_get_db_suite(void) {
Suite *suite;
TCase *testcase;
suite = suite_create("db");
testcase = tcase_create("base");
tcase_add_checked_fixture(testcase, set_up, tear_down);
tcase_add_test(testcase, db_close_test);
tcase_add_test(testcase, db_open_test);
tcase_add_test(testcase, db_open_readonly_test);
tcase_add_test(testcase, db_open_with_version_test);
tcase_add_test(testcase, db_open_readonly_with_version_test);
tcase_add_test(testcase, db_exec_stmt_test);
tcase_add_test(testcase, db_prepare_stmt_test);
tcase_add_test(testcase, db_finish_stmt_test);
tcase_add_test(testcase, db_bind_stmt_test);
tcase_add_test(testcase, db_exec_prepared_stmt_test);
tcase_add_test(testcase, db_reindex_test);
tcase_add_test(testcase, db_last_row_id_test);
suite_add_tcase(suite, testcase);
return suite;
}
proftpd-mod_prometheus-0.2/t/api/http.c 0000664 0000000 0000000 00000011615 14106223670 0020225 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_prometheus API testsuite
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
/* HTTP API tests. */
#include "tests.h"
#include "prometheus/http.h"
#include "prometheus/registry.h"
static pool *p = NULL;
static void set_up(void) {
if (p == NULL) {
p = permanent_pool = make_sub_pool(NULL);
}
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("prometheus.http", 1, 20);
}
mark_point();
prom_http_init(p);
}
static void tear_down(void) {
prom_http_free();
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("prometheus.http", 0, 0);
}
if (p != NULL) {
destroy_pool(p);
p = permanent_pool = NULL;
}
}
START_TEST (http_init_test) {
int res;
mark_point();
res = prom_http_init(NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_http_init(p);
fail_unless(res == 0, "Failed to init HTTP API: %s", strerror(errno));
}
END_TEST
START_TEST (http_free_test) {
int res;
mark_point();
res = prom_http_free();
fail_unless(res == 0, "Failed to free HTTP API: %s", strerror(errno));
}
END_TEST
START_TEST (http_stop_test) {
int res;
mark_point();
res = prom_http_stop(NULL, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_http_stop(p, NULL);
fail_unless(res < 0, "Failed to handle null http");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
}
END_TEST
START_TEST (http_start_test) {
int res;
pr_netaddr_t *addr;
struct prom_http *http;
struct prom_registry *registry;
mark_point();
http = prom_http_start(NULL, NULL, NULL, NULL, NULL);
fail_unless(http == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
http = prom_http_start(p, NULL, NULL, NULL, NULL);
fail_unless(http == NULL, "Failed to handle null addr");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
addr = pr_netaddr_alloc(p);
pr_netaddr_set_family(addr, AF_INET);
pr_netaddr_set_sockaddr_any(addr);
pr_netaddr_set_port2(addr, 0);
http = prom_http_start(p, addr, NULL, NULL, NULL);
fail_unless(http == NULL, "Failed to handle null registry");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
/* Note: We don't need a real registry here, just a non-null pointer. */
registry = pcalloc(p, 8);
http = prom_http_start(p, addr, registry, NULL, NULL);
fail_unless(http != NULL, "Failed to start http: %s", strerror(errno));
mark_point();
res = prom_http_stop(p, http);
fail_unless(res == 0, "Failed to stop http: %s", strerror(errno));
}
END_TEST
START_TEST (http_run_loop_test) {
int res;
mark_point();
res = prom_http_run_loop(NULL, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_http_run_loop(p, NULL);
fail_unless(res < 0, "Failed to handle null http");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
}
END_TEST
Suite *tests_get_http_suite(void) {
Suite *suite;
TCase *testcase;
suite = suite_create("http");
testcase = tcase_create("base");
tcase_add_checked_fixture(testcase, set_up, tear_down);
tcase_add_test(testcase, http_init_test);
tcase_add_test(testcase, http_free_test);
tcase_add_test(testcase, http_stop_test);
tcase_add_test(testcase, http_start_test);
tcase_add_test(testcase, http_run_loop_test);
suite_add_tcase(suite, testcase);
return suite;
}
proftpd-mod_prometheus-0.2/t/api/metric.c 0000664 0000000 0000000 00000105237 14106223670 0020535 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_prometheus API testsuite
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
/* Metric API tests. */
#include "tests.h"
#include "prometheus/db.h"
#include "prometheus/metric.h"
static pool *p = NULL;
static const char *test_dir = "/tmp/prt-mod_prometheus-test-metrics";
static void set_up(void) {
if (p == NULL) {
p = permanent_pool = make_sub_pool(NULL);
}
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("prometheus.db", 1, 20);
pr_trace_set_levels("prometheus.metric", 1, 20);
pr_trace_set_levels("prometheus.metric.db", 1, 20);
}
mark_point();
prom_db_init(p);
}
static void tear_down(void) {
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("prometheus.db", 0, 0);
pr_trace_set_levels("prometheus.metric", 0, 0);
pr_trace_set_levels("prometheus.metric.db", 0, 0);
}
prom_db_free();
(void) tests_rmpath(p, test_dir);
if (p != NULL) {
destroy_pool(p);
p = permanent_pool = NULL;
}
}
START_TEST (metric_free_test) {
int res;
mark_point();
res = prom_metric_free(NULL, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_free(p, NULL);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
}
END_TEST
START_TEST (metric_init_test) {
int res;
struct prom_dbh *dbh;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
dbh = prom_metric_init(NULL, NULL);
fail_unless(dbh == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_init(p, NULL);
fail_unless(dbh == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_destroy_test) {
int res;
mark_point();
res = prom_metric_destroy(NULL, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_destroy(p, NULL);
fail_unless(res < 0, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
}
END_TEST
START_TEST (metric_create_test) {
int res;
const char *name, *expected;
struct prom_dbh *dbh;
struct prom_metric *metric;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
metric = prom_metric_create(NULL, NULL, NULL);
fail_unless(metric == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
metric = prom_metric_create(p, NULL, NULL);
fail_unless(metric == NULL, "Failed to handle null name");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
name = "test";
metric = prom_metric_create(p, name, NULL);
fail_unless(metric == NULL, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
metric = prom_metric_create(p, name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
expected = "test";
fail_unless(strcmp(prom_metric_get_name(metric), expected) == 0,
"Expected metric name '%s', got '%s'", expected,
prom_metric_get_name(metric));
mark_point();
res = prom_metric_destroy(p, metric);
fail_unless(res == 0, "Failed to destroy metric: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_add_counter_test) {
int res;
const char *name, *suffix;
struct prom_dbh *dbh;
struct prom_metric *metric;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_add_counter(NULL, NULL, NULL);
fail_unless(res < 0, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
name = "test";
metric = prom_metric_create(p, name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_metric_add_counter(metric, NULL, NULL);
fail_unless(res < 0, "Failed to handle null help");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
suffix = "total";
res = prom_metric_add_counter(metric, suffix, "testing");
fail_unless(res == 0, "Failed to add counter to metric: %s", strerror(errno));
mark_point();
res = prom_metric_destroy(p, metric);
fail_unless(res == 0, "Failed to destroy metric: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_add_gauge_test) {
int res;
const char *name, *suffix;
struct prom_dbh *dbh;
struct prom_metric *metric;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_add_gauge(NULL, NULL, NULL);
fail_unless(res < 0, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
name = "test";
metric = prom_metric_create(p, name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_metric_add_gauge(metric, NULL, NULL);
fail_unless(res < 0, "Failed to handle null help");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
suffix = "count";
res = prom_metric_add_gauge(metric, suffix, "testing");
fail_unless(res == 0, "Failed to add gauge to metric: %s", strerror(errno));
mark_point();
res = prom_metric_destroy(p, metric);
fail_unless(res == 0, "Failed to destroy metric: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_add_histogram_test) {
int res;
const char *name, *suffix;
struct prom_dbh *dbh;
struct prom_metric *metric;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_add_histogram(NULL, NULL, NULL, 0);
fail_unless(res < 0, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
name = "test";
metric = prom_metric_create(p, name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_metric_add_histogram(metric, NULL, NULL, 0);
fail_unless(res < 0, "Failed to handle null help");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
suffix = "weight";
res = prom_metric_add_histogram(metric, suffix, "testing", 0);
fail_unless(res == 0, "Failed to add histogram to metric: %s",
strerror(errno));
mark_point();
res = prom_metric_destroy(p, metric);
fail_unless(res == 0, "Failed to destroy metric: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_set_dbh_test) {
int res;
struct prom_metric *metric;
struct prom_dbh *dbh;
mark_point();
res = prom_metric_set_dbh(NULL, NULL);
fail_unless(res < 0, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
/* For purposes of testing, this does not have to be a real dbh. */
mark_point();
dbh = palloc(p, 8);
metric = prom_metric_create(p, "test", dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_metric_set_dbh(metric, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_set_dbh(metric, dbh);
fail_unless(res == 0, "Failed to set dbh: %s", strerror(errno));
prom_metric_destroy(p, metric);
}
END_TEST
START_TEST (metric_get_test) {
int res;
const char *name;
struct prom_dbh *dbh;
struct prom_metric *metric;
const array_header *results;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
results = prom_metric_get(NULL, NULL, 0, NULL, NULL);
fail_unless(results == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
results = prom_metric_get(p, NULL, 0, NULL, NULL);
fail_unless(results == NULL, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
name = "test";
metric = prom_metric_create(p, name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
results = prom_metric_get(p, metric, -1, NULL, NULL);
fail_unless(results == NULL, "Failed to handle unknown metric type");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_COUNTER, NULL, NULL);
fail_unless(results == NULL, "Failed to handle counter-less metric");
fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM,
strerror(errno), errno);
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_GAUGE, NULL, NULL);
fail_unless(results == NULL, "Failed to handle gauge-less metric");
fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM,
strerror(errno), errno);
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_HISTOGRAM, NULL, NULL);
fail_unless(results == NULL, "Failed to handle histogram-less metric");
fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM,
strerror(errno), errno);
mark_point();
res = prom_metric_destroy(p, metric);
fail_unless(res == 0, "Failed to destroy metric: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_decr_test) {
int res;
const char *name;
struct prom_dbh *dbh;
struct prom_metric *metric;
uint32_t decr_val = 32;
pr_table_t *labels;
const array_header *results;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_decr(NULL, NULL, 0, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_decr(p, NULL, 0, NULL);
fail_unless(res < 0, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
name = "test";
metric = prom_metric_create(p, name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_metric_decr(p, metric, decr_val, NULL);
fail_unless(res < 0, "Failed to handle gauge-less metric");
fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM,
strerror(errno), errno);
mark_point();
res = prom_metric_add_gauge(metric, "count", "testing");
fail_unless(res == 0, "Failed to add gauge to metric: %s", strerror(errno));
mark_point();
res = prom_metric_decr(p, metric, decr_val, NULL);
fail_unless(res == 0, "Failed to decrement metric: %s", strerror(errno));
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_GAUGE, NULL, NULL);
fail_unless(results != NULL, "Failed to get label-less gauge samples: %s",
strerror(errno));
fail_unless(results->nelts == 2, "Expected 2 results, got %d",
results->nelts);
/* Now, provide labels. */
labels = pr_table_nalloc(p, 0, 2);
(void) pr_table_add_dup(labels, "protocol", "ftp", 0);
(void) pr_table_add_dup(labels, "foo", "BAR", 0);
mark_point();
res = prom_metric_decr(p, metric, decr_val, labels);
fail_unless(res == 0, "Failed to decrement metric: %s", strerror(errno));
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_GAUGE, NULL, NULL);
fail_unless(results != NULL, "Failed to get labeled gauge samples: %s",
strerror(errno));
fail_unless(results->nelts == 4, "Expected 4 results, got %d",
results->nelts);
mark_point();
res = prom_metric_destroy(p, metric);
fail_unless(res == 0, "Failed to destroy metric: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_incr_type_test) {
int res;
const char *name;
struct prom_dbh *dbh;
struct prom_metric *metric;
uint32_t incr_val = 66;
pr_table_t *labels;
const array_header *results;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_incr_type(NULL, NULL, 0, NULL, 0);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_incr_type(p, NULL, 0, NULL, 0);
fail_unless(res < 0, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
name = "test";
metric = prom_metric_create(p, name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_metric_incr_type(p, metric, incr_val, NULL, 0);
fail_unless(res < 0, "Failed to handle unknown metric type");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_incr_type(p, metric, incr_val, NULL,
PROM_METRIC_TYPE_COUNTER);
fail_unless(res < 0, "Failed to handle counter-less metric");
fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM,
strerror(errno), errno);
mark_point();
res = prom_metric_incr_type(p, metric, incr_val, NULL,
PROM_METRIC_TYPE_GAUGE);
fail_unless(res < 0, "Failed to handle gauge-less metric");
fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM,
strerror(errno), errno);
mark_point();
res = prom_metric_add_counter(metric, "total", "testing");
fail_unless(res == 0, "Failed to add counter to metric: %s", strerror(errno));
mark_point();
res = prom_metric_incr_type(p, metric, incr_val, NULL,
PROM_METRIC_TYPE_COUNTER);
fail_unless(res == 0, "Failed to increment metric: %s", strerror(errno));
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_COUNTER, NULL, NULL);
fail_unless(results != NULL, "Failed to get label-less counter samples: %s",
strerror(errno));
fail_unless(results->nelts == 2, "Expected 2 results, got %d",
results->nelts);
/* Now, provide labels. */
labels = pr_table_nalloc(p, 0, 2);
(void) pr_table_add_dup(labels, "protocol", "ftp", 0);
(void) pr_table_add_dup(labels, "foo", "BAR", 0);
mark_point();
res = prom_metric_incr_type(p, metric, incr_val, labels,
PROM_METRIC_TYPE_COUNTER);
fail_unless(res == 0, "Failed to increment metric: %s", strerror(errno));
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_COUNTER, NULL, NULL);
fail_unless(results != NULL, "Failed to get labeled counter samples: %s",
strerror(errno));
fail_unless(results->nelts == 4, "Expected 4 results, got %d",
results->nelts);
mark_point();
res = prom_metric_destroy(p, metric);
fail_unless(res == 0, "Failed to destroy metric: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_incr_test) {
int res;
const char *name;
struct prom_dbh *dbh;
struct prom_metric *metric;
uint32_t incr_val = 66;
pr_table_t *labels;
const array_header *results;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_incr(NULL, NULL, 0, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_incr(p, NULL, 0, NULL);
fail_unless(res < 0, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
name = "test";
metric = prom_metric_create(p, name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_metric_incr(p, metric, incr_val, NULL);
fail_unless(res < 0, "Failed to handle counter-less metric");
fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM,
strerror(errno), errno);
mark_point();
res = prom_metric_add_counter(metric, "total", "testing");
fail_unless(res == 0, "Failed to add counter to metric: %s", strerror(errno));
mark_point();
res = prom_metric_incr(p, metric, incr_val, NULL);
fail_unless(res == 0, "Failed to increment metric: %s", strerror(errno));
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_COUNTER, NULL, NULL);
fail_unless(results != NULL, "Failed to get label-less counter samples: %s",
strerror(errno));
fail_unless(results->nelts == 2, "Expected 2 results, got %d",
results->nelts);
/* Now, provide labels. */
labels = pr_table_nalloc(p, 0, 2);
(void) pr_table_add_dup(labels, "protocol", "ftp", 0);
(void) pr_table_add_dup(labels, "foo", "BAR", 0);
mark_point();
res = prom_metric_incr(p, metric, incr_val, labels);
fail_unless(res == 0, "Failed to increment metric: %s", strerror(errno));
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_COUNTER, NULL, NULL);
fail_unless(results != NULL, "Failed to get labeled counter samples: %s",
strerror(errno));
fail_unless(results->nelts == 4, "Expected 4 results, got %d",
results->nelts);
mark_point();
res = prom_metric_destroy(p, metric);
fail_unless(res == 0, "Failed to destroy metric: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_incr_counter_gauge_test) {
int res;
const char *name;
struct prom_dbh *dbh;
struct prom_metric *metric;
uint32_t incr_val = 66;
pr_table_t *labels;
const array_header *results;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
name = "test";
metric = prom_metric_create(p, name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_metric_add_counter(metric, "total", "testing");
fail_unless(res == 0, "Failed to add counter to metric: %s", strerror(errno));
res = prom_metric_add_gauge(metric, "count", "testing");
fail_unless(res == 0, "Failed to add gauge to metric: %s", strerror(errno));
mark_point();
res = prom_metric_incr(p, metric, incr_val, NULL);
fail_unless(res == 0, "Failed to increment metric: %s", strerror(errno));
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_COUNTER, NULL, NULL);
fail_unless(results != NULL, "Failed to get label-less counter samples: %s",
strerror(errno));
fail_unless(results->nelts == 2, "Expected 2 results, got %d",
results->nelts);
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_GAUGE, NULL, NULL);
fail_unless(results != NULL, "Failed to get label-less gauge samples: %s",
strerror(errno));
fail_unless(results->nelts == 2, "Expected 2 results, got %d",
results->nelts);
/* Now, provide labels. */
labels = pr_table_nalloc(p, 0, 2);
(void) pr_table_add_dup(labels, "protocol", "ftp", 0);
(void) pr_table_add_dup(labels, "foo", "BAR", 0);
mark_point();
res = prom_metric_incr(p, metric, incr_val, labels);
fail_unless(res == 0, "Failed to increment metric: %s", strerror(errno));
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_COUNTER, NULL, NULL);
fail_unless(results != NULL, "Failed to get labeled counter samples: %s",
strerror(errno));
fail_unless(results->nelts == 4, "Expected 4 results, got %d",
results->nelts);
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_GAUGE, NULL, NULL);
fail_unless(results != NULL, "Failed to get labeled gauge samples: %s",
strerror(errno));
fail_unless(results->nelts == 4, "Expected 4 results, got %d",
results->nelts);
mark_point();
res = prom_metric_destroy(p, metric);
fail_unless(res == 0, "Failed to destroy metric: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_observe_test) {
int res;
const char *name;
struct prom_dbh *dbh;
struct prom_metric *metric;
double observed_val = 3.1415;
pr_table_t *labels;
const array_header *results, *counts = NULL, *sums = NULL;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_observe(NULL, NULL, 0, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_observe(p, NULL, 0, NULL);
fail_unless(res < 0, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
name = "test";
metric = prom_metric_create(p, name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_metric_observe(p, metric, observed_val, NULL);
fail_unless(res < 0, "Failed to handle histogram-less metric");
fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM,
strerror(errno), errno);
mark_point();
res = prom_metric_add_histogram(metric, "units", "testing", 0);
fail_unless(res == 0, "Failed to add histogram to metric: %s",
strerror(errno));
mark_point();
res = prom_metric_observe(p, metric, observed_val, NULL);
fail_unless(res == 0, "Failed to observe metric: %s", strerror(errno));
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_HISTOGRAM, &counts,
&sums);
fail_unless(results != NULL, "Failed get histogram results: %s",
strerror(errno));
fail_unless(results->nelts == 2, "Expected 2 bucket results, got %d",
results->nelts);
fail_unless(counts != NULL, "Failed get histogram count results: %s",
strerror(errno));
fail_unless(counts->nelts == 2, "Expected 2 count results, got %d",
counts->nelts);
fail_unless(sums != NULL, "Failed get histogram sum results: %s",
strerror(errno));
fail_unless(sums->nelts == 2, "Expected 2 sum results, got %d",
sums->nelts);
/* Now, provide labels. */
labels = pr_table_nalloc(p, 0, 2);
(void) pr_table_add_dup(labels, "protocol", "ftp", 0);
(void) pr_table_add_dup(labels, "foo", "BAR", 0);
mark_point();
res = prom_metric_observe(p, metric, observed_val, labels);
fail_unless(res == 0, "Failed to observe metric: %s", strerror(errno));
mark_point();
counts = sums = NULL;
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_HISTOGRAM, &counts,
&sums);
fail_unless(results != NULL, "Failed to get histogram results: %s",
strerror(errno));
fail_unless(results->nelts == 4, "Expected 4 bucket results, got %d",
results->nelts);
fail_unless(counts != NULL, "Failed to get histogram count results: %s",
strerror(errno));
fail_unless(counts->nelts == 4, "Expected 4 count results, got %d",
counts->nelts);
fail_unless(sums != NULL, "Failed to get histogram sum results: %s",
strerror(errno));
fail_unless(sums->nelts == 4, "Expected 4 sum results, got %d",
sums->nelts);
mark_point();
res = prom_metric_destroy(p, metric);
fail_unless(res == 0, "Failed to destroy metric: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_set_test) {
int res;
const char *name;
struct prom_dbh *dbh;
struct prom_metric *metric;
uint32_t set_val = 42;
pr_table_t *labels;
const array_header *results;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_set(NULL, NULL, 0, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_set(p, NULL, 0, NULL);
fail_unless(res < 0, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
name = "test";
metric = prom_metric_create(p, name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_metric_set(p, metric, set_val, NULL);
fail_unless(res < 0, "Failed to handle gauge-less metric");
fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM,
strerror(errno), errno);
mark_point();
res = prom_metric_add_gauge(metric, "count", "testing");
fail_unless(res == 0, "Failed to add gauge to metric: %s", strerror(errno));
mark_point();
res = prom_metric_set(p, metric, set_val, NULL);
fail_unless(res == 0, "Failed to set metric: %s", strerror(errno));
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_GAUGE, NULL, NULL);
fail_unless(results != NULL, "Failed to get label-less gauge samples: %s",
strerror(errno));
fail_unless(results->nelts == 2, "Expected 2 results, got %d",
results->nelts);
/* Now, provide labels. */
labels = pr_table_nalloc(p, 0, 2);
(void) pr_table_add_dup(labels, "protocol", "ftp", 0);
(void) pr_table_add_dup(labels, "foo", "BAR", 0);
mark_point();
res = prom_metric_set(p, metric, set_val, labels);
fail_unless(res == 0, "Failed to set metric: %s", strerror(errno));
mark_point();
results = prom_metric_get(p, metric, PROM_METRIC_TYPE_GAUGE, NULL, NULL);
fail_unless(results != NULL, "Failed to get labeled gauge samples: %s",
strerror(errno));
fail_unless(results->nelts == 4, "Expected 4 results, got %d",
results->nelts);
mark_point();
res = prom_metric_destroy(p, metric);
fail_unless(res == 0, "Failed to destroy metric: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_get_text_test) {
int res;
const char *name, *text;
size_t textlen = 0;
struct prom_dbh *dbh;
struct prom_metric *metric;
pr_table_t *labels;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
name = "test";
metric = prom_metric_create(p, name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_metric_add_counter(metric, "total", "counter testing");
fail_unless(res == 0, "Failed to add counter to metric: %s", strerror(errno));
mark_point();
res = prom_metric_add_gauge(metric, "count", "gauge testing");
fail_unless(res == 0, "Failed to add gauge to metric: %s", strerror(errno));
mark_point();
res = prom_metric_add_histogram(metric, "weight", "histogram testing", 0);
fail_unless(res == 0, "Failed to add histogram to metric: %s",
strerror(errno));
mark_point();
res = prom_metric_incr(p, metric, 6, NULL);
fail_unless(res == 0, "Failed to increment metric: %s", strerror(errno));
/* Now, provide labels. */
labels = pr_table_nalloc(p, 0, 2);
(void) pr_table_add_dup(labels, "protocol", "ftp", 0);
(void) pr_table_add_dup(labels, "foo", "BAR", 0);
mark_point();
res = prom_metric_incr(p, metric, 8, labels);
fail_unless(res == 0, "Failed to increment metric: %s", strerror(errno));
mark_point();
res = prom_metric_observe(p, metric, 76.42, labels);
fail_unless(res == 0, "Failed to observe metric: %s", strerror(errno));
mark_point();
text = prom_metric_get_text(p, metric, "prt", &textlen);
fail_unless(text != NULL, "Failed to get metric text: %s", strerror(errno));
fail_unless(textlen > 0, "Expected text data, got %lu",
(unsigned long) textlen);
/* Use strstr(3) to assert bits of text. */
fail_unless(strstr(text, "# HELP prt_test_total") != NULL,
"Expected counter HELP text");
fail_unless(strstr(text, "# TYPE prt_test_total counter") != NULL,
"Expected counter TYPE text");
fail_unless(strstr(text, "prt_test_total 6") != NULL,
"Expected label-less counter sample");
fail_unless(
strstr(text, "prt_test_total{foo=\"BAR\",protocol=\"ftp\"} 8") != NULL,
"Expected labeled counter sample");
fail_unless(strstr(text, "# HELP prt_test_count") != NULL,
"Expected gauge HELP text");
fail_unless(strstr(text, "# TYPE prt_test_count gauge") != NULL,
"Expected gauge TYPE text");
fail_unless(strstr(text, "prt_test_count 6") != NULL,
"Expected label-less gauge sample");
fail_unless(
strstr(text, "prt_test_count{foo=\"BAR\",protocol=\"ftp\"} 8") != NULL,
"Expected labeled gauge sample");
fail_unless(strstr(text, "# HELP prt_test_weight") != NULL,
"Expected histogram HELP text");
fail_unless(strstr(text, "# TYPE prt_test_weight histogram") != NULL,
"Expected histogram TYPE text");
fail_unless(
strstr(text, "prt_test_weight_bucket{foo=\"BAR\",le=\"+Inf\",protocol=\"ftp\"} 1") != NULL,
"Expected labeled histogram bucket sample");
fail_unless(
strstr(text, "prt_test_weight_count{foo=\"BAR\",protocol=\"ftp\"} 1") != NULL,
"Expected labeled histogram count sample");
fail_unless(
strstr(text, "prt_test_weight_sum{foo=\"BAR\",protocol=\"ftp\"} 76.42") != NULL,
"Expected labeled histogram sum sample");
mark_point();
res = prom_metric_destroy(p, metric);
fail_unless(res == 0, "Failed to destroy metric: %s", strerror(errno));
res = prom_metric_free(p, dbh);
fail_unless(res == 0, "Failed to free metrics: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
Suite *tests_get_metric_suite(void) {
Suite *suite;
TCase *testcase;
suite = suite_create("metric");
testcase = tcase_create("base");
tcase_add_checked_fixture(testcase, set_up, tear_down);
tcase_add_test(testcase, metric_free_test);
tcase_add_test(testcase, metric_init_test);
tcase_add_test(testcase, metric_destroy_test);
tcase_add_test(testcase, metric_create_test);
tcase_add_test(testcase, metric_add_counter_test);
tcase_add_test(testcase, metric_add_gauge_test);
tcase_add_test(testcase, metric_add_histogram_test);
tcase_add_test(testcase, metric_set_dbh_test);
tcase_add_test(testcase, metric_get_test);
tcase_add_test(testcase, metric_decr_test);
tcase_add_test(testcase, metric_incr_type_test);
tcase_add_test(testcase, metric_incr_test);
tcase_add_test(testcase, metric_incr_counter_gauge_test);
tcase_add_test(testcase, metric_observe_test);
tcase_add_test(testcase, metric_set_test);
tcase_add_test(testcase, metric_get_text_test);
suite_add_tcase(suite, testcase);
return suite;
}
proftpd-mod_prometheus-0.2/t/api/metric/ 0000775 0000000 0000000 00000000000 14106223670 0020361 5 ustar 00root root 0000000 0000000 proftpd-mod_prometheus-0.2/t/api/metric/db.c 0000664 0000000 0000000 00000040171 14106223670 0021115 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_prometheus API testsuite
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
/* Metric database API tests. */
#include "../tests.h"
#include "prometheus/db.h"
#include "prometheus/metric.h"
#include "prometheus/metric/db.h"
static pool *p = NULL;
static const char *test_dir = "/tmp/prt-mod_prometheus-test-db";
static void set_up(void) {
if (p == NULL) {
p = permanent_pool = make_sub_pool(NULL);
}
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("prometheus.db", 1, 20);
pr_trace_set_levels("prometheus.metric.db", 1, 20);
}
mark_point();
prom_db_init(p);
}
static void tear_down(void) {
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("prometheus.db", 0, 0);
pr_trace_set_levels("prometheus.metric.db", 0, 0);
}
prom_db_free();
(void) tests_rmpath(p, test_dir);
if (p != NULL) {
destroy_pool(p);
p = permanent_pool = NULL;
}
}
START_TEST (metric_db_close_test) {
int res;
mark_point();
res = prom_metric_db_close(NULL, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_db_close(p, NULL);
fail_unless(res == 0, "Failed to handle null dbh");
}
END_TEST
START_TEST (metric_db_init_test) {
int res, flags = PROM_DB_OPEN_FL_SKIP_VACUUM;
struct prom_dbh *dbh;
mark_point();
dbh = prom_metric_db_init(NULL, NULL, 0);
fail_unless(dbh == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_db_init(p, NULL, 0);
fail_unless(dbh == NULL, "Failed to handle null path");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_db_init(p, test_dir, flags);
fail_unless(dbh != NULL, "Failed to init metrics db: %s", strerror(errno));
res = prom_metric_db_close(p, dbh);
fail_unless(res == 0, "Failed to close metrics db: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_db_open_test) {
int res, flags = PROM_DB_OPEN_FL_SKIP_VACUUM;
struct prom_dbh *dbh;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
dbh = prom_metric_db_open(NULL, NULL);
fail_unless(dbh == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_db_open(p, NULL);
fail_unless(dbh == NULL, "Failed to handle null path");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_db_open(p, test_dir);
fail_unless(dbh == NULL, "Failed to handle missing database");
fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM,
strerror(errno), errno);
mark_point();
dbh = prom_metric_db_init(p, test_dir, flags);
fail_unless(dbh != NULL, "Failed to init metrics db: %s", strerror(errno));
res = prom_metric_db_close(p, dbh);
fail_unless(res == 0, "Failed to close metrics db: %s", strerror(errno));
mark_point();
dbh = prom_metric_db_open(p, test_dir);
fail_unless(dbh != NULL, "Failed to open metrics db: %s", strerror(errno));
res = prom_metric_db_close(p, dbh);
fail_unless(res == 0, "Failed to close metrics db: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_db_exists_test) {
int res, flags = PROM_DB_OPEN_FL_SKIP_VACUUM;
const char *metric_name = "test_metric";
struct prom_dbh *dbh;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_db_exists(NULL, NULL, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_db_exists(p, NULL, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_db_init(p, test_dir, flags);
fail_unless(dbh != NULL, "Failed to init metrics db: %s", strerror(errno));
mark_point();
res = prom_metric_db_exists(p, dbh, metric_name);
fail_unless(res < 0, "Failed to handle nonexistent metric");
fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT,
strerror(errno), errno);
res = prom_metric_db_close(p, dbh);
fail_unless(res == 0, "Failed to close metrics db: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_db_create_test) {
int res, flags = PROM_DB_OPEN_FL_SKIP_VACUUM, metric_type = 1;
const char *metric_name = "test_metric";
int64_t metric_id = 0;
struct prom_dbh *dbh;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_db_create(NULL, NULL, NULL, 0, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_db_create(p, NULL, NULL, 0, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_db_init(p, test_dir, flags);
fail_unless(dbh != NULL, "Failed to init metrics db: %s", strerror(errno));
mark_point();
res = prom_metric_db_create(p, dbh, NULL, 0, NULL);
fail_unless(res < 0, "Failed to handle null metric name");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_db_create(p, dbh, metric_name, metric_type, &metric_id);
fail_unless(res == 0, "Failed to add db metric '%s': %s", metric_name,
strerror(errno));
mark_point();
res = prom_metric_db_exists(p, dbh, metric_name);
fail_unless(res == 0, "Failed to detect existing metric '%s': %s",
metric_name, strerror(errno));
res = prom_metric_db_close(p, dbh);
fail_unless(res == 0, "Failed to close metrics db: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_db_sample_exists_test) {
}
END_TEST
START_TEST (metric_db_sample_get_test) {
int res, flags = PROM_DB_OPEN_FL_SKIP_VACUUM;
struct prom_dbh *dbh;
const array_header *results;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
results = prom_metric_db_sample_get(NULL, NULL, 0);
fail_unless(results == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
results = prom_metric_db_sample_get(p, NULL, 0);
fail_unless(results == NULL, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_db_init(p, test_dir, flags);
fail_unless(dbh != NULL, "Failed to init metrics db: %s", strerror(errno));
mark_point();
results = prom_metric_db_sample_get(p, dbh, 0);
fail_unless(results != NULL, "Failed to get metric samples: %s",
strerror(errno));
fail_unless(results->nelts == 0,
"Expected zero results, got %d", results->nelts);
res = prom_metric_db_close(p, dbh);
fail_unless(res == 0, "Failed to close metrics db: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_db_sample_decr_test) {
int res, flags = PROM_DB_OPEN_FL_SKIP_VACUUM;
int64_t metric_id = 24;
double decr_val = 76.24, sample_val;
struct prom_dbh *dbh;
const array_header *results;
char **elts, *ptr;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_db_sample_decr(NULL, NULL, 0, 0.0, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_db_sample_decr(p, NULL, 0, 0.0, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_db_init(p, test_dir, flags);
fail_unless(dbh != NULL, "Failed to init metrics db: %s", strerror(errno));
mark_point();
res = prom_metric_db_sample_decr(p, dbh, metric_id, decr_val, NULL);
fail_unless(res < 0, "Failed to handle null sample labels");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_db_sample_decr(p, dbh, metric_id, decr_val, "");
fail_unless(res == 0, "Failed to decrement metric ID %ld: %s",
metric_id, strerror(errno));
mark_point();
results = prom_metric_db_sample_get(p, dbh, metric_id);
fail_unless(results != NULL, "Failed to get samples for metric ID %ld: %s",
metric_id, strerror(errno));
fail_unless(results->nelts == 2, "Expected results->nelts = 2, got %d",
results->nelts);
elts = results->elts;
fail_unless(elts[0] != NULL, "Expected sample value, got NULL");
ptr = NULL;
sample_val = strtod(elts[0], &ptr);
fail_if(ptr != NULL && *ptr, "Expected double sample value, got '%s'",
elts[0]);
fail_unless((int) -sample_val == (int) decr_val,
"Expected sample value %lf, got %lf", decr_val, sample_val);
fail_unless(elts[1] != NULL, "Expected sample_labels, got null");
fail_unless(strcmp(elts[1], "") == 0,
"Expected sample labels '', got '%s'", elts[1]);
res = prom_metric_db_close(p, dbh);
fail_unless(res == 0, "Failed to close metrics db: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_db_sample_incr_test) {
int res, flags = PROM_DB_OPEN_FL_SKIP_VACUUM;
int64_t metric_id = 42;
double incr_val = 24.76, sample_val;
struct prom_dbh *dbh;
const array_header *results;
char **elts, *ptr;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_db_sample_incr(NULL, NULL, 0, 0.0, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_db_sample_incr(p, NULL, 0, 0.0, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_db_init(p, test_dir, flags);
fail_unless(dbh != NULL, "Failed to init metrics db: %s", strerror(errno));
mark_point();
res = prom_metric_db_sample_incr(p, dbh, metric_id, incr_val, NULL);
fail_unless(res < 0, "Failed to handle null sample labels");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_db_sample_incr(p, dbh, metric_id, incr_val, "");
fail_unless(res == 0, "Failed to increment metric ID %ld: %s",
metric_id, strerror(errno));
mark_point();
results = prom_metric_db_sample_get(p, dbh, metric_id);
fail_unless(results != NULL, "Failed to get samples for metric ID %ld: %s",
metric_id, strerror(errno));
fail_unless(results->nelts == 2, "Expected results->nelts = 2, got %d",
results->nelts);
elts = results->elts;
fail_unless(elts[0] != NULL, "Expected sample value, got NULL");
ptr = NULL;
sample_val = strtod(elts[0], &ptr);
fail_if(ptr != NULL && *ptr, "Expected double sample value, got '%s'",
elts[0]);
fail_unless((int) sample_val == (int) incr_val,
"Expected sample value %lf, got %lf", incr_val, sample_val);
fail_unless(elts[1] != NULL, "Expected sample_labels, got null");
fail_unless(strcmp(elts[1], "") == 0,
"Expected sample labels '', got '%s'", elts[1]);
res = prom_metric_db_close(p, dbh);
fail_unless(res == 0, "Failed to close metrics db: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (metric_db_sample_set_test) {
int res, flags = PROM_DB_OPEN_FL_SKIP_VACUUM;
int64_t metric_id = 84;
double set_val = 3.1514, sample_val;
struct prom_dbh *dbh;
const array_header *results;
char **elts, *ptr;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
res = prom_metric_db_sample_set(NULL, NULL, 0, 0.0, NULL);
fail_unless(res < 0, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_db_sample_set(p, NULL, 0, 0.0, NULL);
fail_unless(res < 0, "Failed to handle null dbh");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
dbh = prom_metric_db_init(p, test_dir, flags);
fail_unless(dbh != NULL, "Failed to init metrics db: %s", strerror(errno));
mark_point();
res = prom_metric_db_sample_set(p, dbh, metric_id, set_val, NULL);
fail_unless(res < 0, "Failed to handle null sample labels");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_metric_db_sample_set(p, dbh, metric_id, set_val, "");
fail_unless(res == 0, "Failed to set metric ID %ld: %s",
metric_id, strerror(errno));
mark_point();
results = prom_metric_db_sample_get(p, dbh, metric_id);
fail_unless(results != NULL, "Failed to get samples for metric ID %ld: %s",
metric_id, strerror(errno));
fail_unless(results->nelts == 2, "Expected results->nelts = 2, got %d",
results->nelts);
elts = results->elts;
fail_unless(elts[0] != NULL, "Expected sample value, got NULL");
ptr = NULL;
sample_val = strtod(elts[0], &ptr);
fail_if(ptr != NULL && *ptr, "Expected double sample value, got '%s'",
elts[0]);
fail_unless((int) sample_val == (int) set_val,
"Expected sample value %lf, got %lf", set_val, sample_val);
fail_unless(elts[1] != NULL, "Expected sample_labels, got null");
fail_unless(strcmp(elts[1], "") == 0,
"Expected sample labels '', got '%s'", elts[1]);
res = prom_metric_db_close(p, dbh);
fail_unless(res == 0, "Failed to close metrics db: %s", strerror(errno));
(void) tests_rmpath(p, test_dir);
}
END_TEST
Suite *tests_get_metric_db_suite(void) {
Suite *suite;
TCase *testcase;
suite = suite_create("metric.db");
testcase = tcase_create("base");
tcase_add_checked_fixture(testcase, set_up, tear_down);
tcase_add_test(testcase, metric_db_close_test);
tcase_add_test(testcase, metric_db_init_test);
tcase_add_test(testcase, metric_db_open_test);
tcase_add_test(testcase, metric_db_exists_test);
tcase_add_test(testcase, metric_db_create_test);
tcase_add_test(testcase, metric_db_sample_exists_test);
tcase_add_test(testcase, metric_db_sample_get_test);
tcase_add_test(testcase, metric_db_sample_decr_test);
tcase_add_test(testcase, metric_db_sample_incr_test);
tcase_add_test(testcase, metric_db_sample_set_test);
suite_add_tcase(suite, testcase);
return suite;
}
proftpd-mod_prometheus-0.2/t/api/registry.c 0000664 0000000 0000000 00000032733 14106223670 0021122 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_prometheus API testsuite
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
/* Registry API tests. */
#include "tests.h"
#include "prometheus/registry.h"
#include "prometheus/metric/db.h"
static pool *p = NULL;
static const char *test_dir = "/tmp/prt-mod_prometheus-test-registry";
static void set_up(void) {
if (p == NULL) {
p = permanent_pool = make_sub_pool(NULL);
}
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("prometheus.db", 1, 20);
pr_trace_set_levels("prometheus.registry", 1, 20);
}
mark_point();
prom_db_init(p);
}
static void tear_down(void) {
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("prometheus.db", 0, 0);
pr_trace_set_levels("prometheus.registry", 0, 0);
}
prom_db_free();
(void) tests_rmpath(p, test_dir);
if (p != NULL) {
destroy_pool(p);
p = permanent_pool = NULL;
}
}
START_TEST (registry_free_test) {
int res;
mark_point();
res = prom_registry_free(NULL);
fail_unless(res < 0, "Failed to handle null registry");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
}
END_TEST
START_TEST (registry_init_test) {
int res;
const char *name;
struct prom_registry *registry;
mark_point();
registry = prom_registry_init(NULL, NULL);
fail_unless(registry == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
registry = prom_registry_init(p, NULL);
fail_unless(registry == NULL, "Failed to handle null name");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
registry = prom_registry_init(p, "test");
fail_unless(registry != NULL, "Failed to create registry: %s",
strerror(errno));
mark_point();
name = prom_registry_get_name(NULL);
fail_unless(name == NULL, "Failed to handle null registry");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
name = prom_registry_get_name(registry);
fail_unless(name != NULL, "Failed to get name: %s", strerror(errno));
fail_unless(strcmp(name, "test") == 0, "Expected 'test', got '%s'",
name);
res = prom_registry_free(registry);
fail_unless(res == 0, "Failed to free registry: %s", strerror(errno));
}
END_TEST
START_TEST (registry_get_metric_test) {
struct prom_registry *registry;
const struct prom_metric *metric;
mark_point();
metric = prom_registry_get_metric(NULL, NULL);
fail_unless(metric == NULL, "Failed to handle null registry");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
registry = prom_registry_init(p, "test");
fail_unless(registry != NULL, "Failed to create registry: %s",
strerror(errno));
mark_point();
metric = prom_registry_get_metric(registry, NULL);
fail_unless(metric == NULL, "Failed to handle null metric name");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
prom_registry_free(registry);
}
END_TEST
START_TEST (registry_add_metric_test) {
int res;
struct prom_registry *registry;
char *metric_name;
struct prom_metric *metric;
struct prom_dbh *dbh;
mark_point();
res = prom_registry_add_metric(NULL, NULL);
fail_unless(res < 0, "Failed to handle null registry");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
registry = prom_registry_init(p, "test");
fail_unless(registry != NULL, "Failed to create registry: %s",
strerror(errno));
mark_point();
res = prom_registry_add_metric(registry, NULL);
fail_unless(res < 0, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
/* For purposes of testing, we don't need a real dbh here. */
mark_point();
metric_name = "metric";
dbh = palloc(p, 8);
metric = prom_metric_create(p, metric_name, dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
res = prom_registry_add_metric(registry, metric);
fail_unless(res == 0, "Failed to add metric: %s", strerror(errno));
mark_point();
metric = (struct prom_metric *) prom_registry_get_metric(registry,
metric_name);
fail_unless(metric != NULL, "Failed to get metric: %s", strerror(errno));
prom_registry_free(registry);
}
END_TEST
START_TEST (registry_sort_metrics_test) {
int res;
struct prom_registry *registry;
struct prom_metric *first_metric, *second_metric;
struct prom_dbh *dbh;
mark_point();
res = prom_registry_sort_metrics(NULL);
fail_unless(res < 0, "Failed to handle null registry");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
registry = prom_registry_init(p, "test");
fail_unless(registry != NULL, "Failed to create registry: %s",
strerror(errno));
/* For purposes of testing, we don't need a real dbh here. */
mark_point();
dbh = palloc(p, 8);
first_metric = prom_metric_create(p, "first", dbh);
fail_unless(first_metric != NULL, "Failed to create metric: %s",
strerror(errno));
res = prom_registry_add_metric(registry, first_metric);
fail_unless(res == 0, "Failed to add metric: %s", strerror(errno));
mark_point();
res = prom_registry_sort_metrics(registry);
fail_unless(res == 0, "Failed to sort metrics: %s", strerror(errno));
mark_point();
second_metric = prom_metric_create(p, "second", dbh);
fail_unless(second_metric != NULL, "Failed to create metric: %s",
strerror(errno));
res = prom_registry_add_metric(registry, second_metric);
fail_unless(res == 0, "Failed to add metric: %s", strerror(errno));
res = prom_registry_sort_metrics(registry);
fail_unless(res == 0, "Failed to sort metrics: %s", strerror(errno));
prom_registry_free(registry);
}
END_TEST
START_TEST (registry_set_dbh_test) {
int res;
struct prom_registry *registry;
struct prom_dbh *dbh;
mark_point();
res = prom_registry_set_dbh(NULL, NULL);
fail_unless(res < 0, "Failed to handle null registry");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
registry = prom_registry_init(p, "test");
fail_unless(registry != NULL, "Failed to create registry: %s",
strerror(errno));
mark_point();
res = prom_registry_set_dbh(registry, NULL);
fail_unless(res < 0, "Failed to handle null registry");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
/* For purposes of testing, we don't need a real dbh here. */
mark_point();
dbh = palloc(p, 8);
res = prom_registry_set_dbh(registry, dbh);
fail_unless(res == 0, "Failed to handle set dbh: %s", strerror(errno));
prom_registry_free(registry);
}
END_TEST
START_TEST (registry_get_text_test) {
const char *text;
struct prom_registry *registry;
mark_point();
text = prom_registry_get_text(NULL, NULL);
fail_unless(text == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
text = prom_registry_get_text(p, NULL);
fail_unless(text == NULL, "Failed to handle null registry");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
registry = prom_registry_init(p, "test");
fail_unless(registry != NULL, "Failed to create registry: %s",
strerror(errno));
mark_point();
text = prom_registry_get_text(p, registry);
fail_unless(text == NULL, "Failed to handle absent metrics");
fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT,
strerror(errno), errno);
prom_registry_free(registry);
}
END_TEST
START_TEST (registry_get_text_with_metrics_test) {
int res;
const char *text;
struct prom_registry *registry;
struct prom_metric *metric;
struct prom_dbh *dbh;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
registry = prom_registry_init(p, "test");
fail_unless(registry != NULL, "Failed to create registry: %s",
strerror(errno));
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
metric = prom_metric_create(p, "metric", dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_registry_add_metric(registry, metric);
fail_unless(res == 0, "Failed to register metric: %s", strerror(errno));
mark_point();
res = prom_metric_add_counter(metric, "total", "testing");
fail_unless(res == 0, "Failed to add counter to metric: %s", strerror(errno));
mark_point();
res = prom_metric_incr(p, metric, 1, NULL);
fail_unless(res == 0, "Failed to increment metric: %s", strerror(errno));
mark_point();
text = prom_registry_get_text(p, registry);
fail_unless(text != NULL, "Failed to get registry text: %s", strerror(errno));
/* Use strstr(3) to assert bits of text. */
fail_unless(strstr(text, "# HELP test_metric_total") != NULL,
"Expected metric help, got '%s'", text);
fail_unless(strstr(text, "# TYPE test_metric_total counter") != NULL,
"Expected metric type, got '%s'", text);
fail_unless(strstr(text, "test_metric_total 1") != NULL,
"Expected metric sample, got '%s'", text);
prom_registry_free(registry);
prom_db_close(p, dbh);
(void) tests_rmpath(p, test_dir);
}
END_TEST
START_TEST (registry_get_text_with_metrics_readonly_test) {
int res;
const char *text;
struct prom_registry *registry;
struct prom_metric *metric;
struct prom_dbh *dbh;
(void) tests_rmpath(p, test_dir);
(void) tests_mkpath(p, test_dir);
mark_point();
registry = prom_registry_init(p, "test");
fail_unless(registry != NULL, "Failed to create registry: %s",
strerror(errno));
mark_point();
dbh = prom_metric_init(p, test_dir);
fail_unless(dbh != NULL, "Failed to init metrics: %s", strerror(errno));
mark_point();
metric = prom_metric_create(p, "metric", dbh);
fail_unless(metric != NULL, "Failed to create metric: %s", strerror(errno));
mark_point();
res = prom_registry_add_metric(registry, metric);
fail_unless(res == 0, "Failed to register metric: %s", strerror(errno));
mark_point();
res = prom_metric_add_counter(metric, "total", "testing");
fail_unless(res == 0, "Failed to add counter to metric: %s", strerror(errno));
mark_point();
res = prom_metric_incr(p, metric, 1, NULL);
fail_unless(res == 0, "Failed to increment metric: %s", strerror(errno));
mark_point();
/* Now, close that dbh. Open a readonly one, set it in the registry.
* This approximates what happens with the exporter process.
*/
(void) prom_db_close(p, dbh);
dbh = prom_metric_db_open(p, test_dir);
fail_unless(dbh != NULL, "Failed to open readonly dbh: %s", strerror(errno));
mark_point();
res = prom_registry_set_dbh(registry, dbh);
fail_unless(res == 0, "Failed to set registry dbh: %s", strerror(errno));
mark_point();
text = prom_registry_get_text(p, registry);
fail_unless(text != NULL, "Failed to get registry text: %s", strerror(errno));
/* Use strstr(3) to assert bits of text. */
fail_unless(strstr(text, "# HELP test_metric_total") != NULL,
"Expected metric help, got '%s'", text);
fail_unless(strstr(text, "# TYPE test_metric_total counter") != NULL,
"Expected metric type, got '%s'", text);
fail_unless(strstr(text, "test_metric_total 1") != NULL,
"Expected metric sample, got '%s'", text);
prom_registry_free(registry);
prom_db_close(p, dbh);
(void) tests_rmpath(p, test_dir);
}
END_TEST
Suite *tests_get_registry_suite(void) {
Suite *suite;
TCase *testcase;
suite = suite_create("registry");
testcase = tcase_create("base");
tcase_add_checked_fixture(testcase, set_up, tear_down);
tcase_add_test(testcase, registry_free_test);
tcase_add_test(testcase, registry_init_test);
tcase_add_test(testcase, registry_get_metric_test);
tcase_add_test(testcase, registry_add_metric_test);
tcase_add_test(testcase, registry_sort_metrics_test);
tcase_add_test(testcase, registry_set_dbh_test);
tcase_add_test(testcase, registry_get_text_test);
tcase_add_test(testcase, registry_get_text_with_metrics_test);
tcase_add_test(testcase, registry_get_text_with_metrics_readonly_test);
suite_add_tcase(suite, testcase);
return suite;
}
proftpd-mod_prometheus-0.2/t/api/stubs.c 0000664 0000000 0000000 00000013771 14106223670 0020413 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_prometheus API testsuite
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "tests.h"
/* Stubs */
session_t session;
int ServerUseReverseDNS = FALSE;
server_rec *main_server = NULL;
pid_t mpid = 1;
unsigned char is_master = TRUE;
volatile unsigned int recvd_signal_flags = 0;
module *static_modules[] = { NULL };
module *loaded_modules = NULL;
xaset_t *server_list = NULL;
int prometheus_logfd = -1;
module prometheus_module;
pool *prometheus_pool = NULL;
int tests_mkpath(pool *p, const char *path) {
int res;
mode_t perms;
perms = 0770;
res = mkdir(path, perms);
fail_unless(res == 0, "Failed to create tmp directory '%s': %s", path,
strerror(errno));
res = chmod(path, perms);
fail_unless(res == 0, "Failed to set perms %04o on directory '%s': %s",
perms, path, strerror(errno));
return 0;
}
int tests_rmpath(pool *p, const char *path) {
DIR *dirh;
struct dirent *dent;
int res, xerrno = 0;
if (path == NULL) {
errno = EINVAL;
return -1;
}
dirh = opendir(path);
if (dirh == NULL) {
xerrno = errno;
/* Change the permissions in the directory, and try again. */
if (chmod(path, (mode_t) 0755) == 0) {
dirh = opendir(path);
}
if (dirh == NULL) {
pr_trace_msg("testsuite", 9,
"error opening '%s': %s", path, strerror(xerrno));
errno = xerrno;
return -1;
}
}
while ((dent = readdir(dirh)) != NULL) {
struct stat st;
char *file;
pr_signals_handle();
if (strncmp(dent->d_name, ".", 2) == 0 ||
strncmp(dent->d_name, "..", 3) == 0) {
continue;
}
file = pdircat(p, path, dent->d_name, NULL);
if (stat(file, &st) < 0) {
pr_trace_msg("testsuite", 9,
"unable to stat '%s': %s", file, strerror(errno));
continue;
}
if (S_ISDIR(st.st_mode)) {
res = tests_rmpath(p, file);
if (res < 0) {
pr_trace_msg("testsuite", 9,
"error removing directory '%s': %s", file, strerror(errno));
}
} else {
res = unlink(file);
if (res < 0) {
pr_trace_msg("testsuite", 9,
"error removing file '%s': %s", file, strerror(errno));
}
}
}
closedir(dirh);
res = rmdir(path);
if (res < 0) {
xerrno = errno;
pr_trace_msg("testsuite", 9,
"error removing directory '%s': %s", path, strerror(xerrno));
errno = xerrno;
}
return res;
}
int pr_config_get_server_xfer_bufsz(int direction) {
int bufsz = -1;
switch (direction) {
case PR_NETIO_IO_RD:
bufsz = PR_TUNABLE_DEFAULT_RCVBUFSZ;
break;
case PR_NETIO_IO_WR:
bufsz = PR_TUNABLE_DEFAULT_SNDBUFSZ;
break;
default:
errno = EINVAL;
return -1;
}
return bufsz;
}
void pr_log_auth(int priority, const char *fmt, ...) {
if (getenv("TEST_VERBOSE") != NULL) {
va_list msg;
fprintf(stderr, "AUTH: ");
va_start(msg, fmt);
vfprintf(stderr, fmt, msg);
va_end(msg);
fprintf(stderr, "\n");
}
}
void pr_log_debug(int level, const char *fmt, ...) {
if (getenv("TEST_VERBOSE") != NULL) {
va_list msg;
fprintf(stderr, "DEBUG%d: ", level);
va_start(msg, fmt);
vfprintf(stderr, fmt, msg);
va_end(msg);
fprintf(stderr, "\n");
}
}
int pr_log_event_generate(unsigned int log_type, int log_fd, int log_level,
const char *log_msg, size_t log_msglen) {
errno = ENOSYS;
return -1;
}
int pr_log_event_listening(unsigned int log_type) {
return FALSE;
}
int pr_log_openfile(const char *log_file, int *log_fd, mode_t log_mode) {
int res;
struct stat st;
if (log_file == NULL ||
log_fd == NULL) {
errno = EINVAL;
return -1;
}
res = stat(log_file, &st);
if (res < 0) {
if (errno != ENOENT) {
return -1;
}
} else {
if (S_ISDIR(st.st_mode)) {
errno = EISDIR;
return -1;
}
}
*log_fd = STDERR_FILENO;
return 0;
}
void pr_log_pri(int prio, const char *fmt, ...) {
if (getenv("TEST_VERBOSE") != NULL) {
va_list msg;
fprintf(stderr, "PRI%d: ", prio);
va_start(msg, fmt);
vfprintf(stderr, fmt, msg);
va_end(msg);
fprintf(stderr, "\n");
}
}
void pr_log_stacktrace(int fd, const char *name) {
}
int pr_log_writefile(int fd, const char *name, const char *fmt, ...) {
if (getenv("TEST_VERBOSE") != NULL) {
va_list msg;
fprintf(stderr, "%s: ", name);
va_start(msg, fmt);
vfprintf(stderr, fmt, msg);
va_end(msg);
fprintf(stderr, "\n");
}
return 0;
}
void pr_session_disconnect(module *m, int reason_code, const char *details) {
}
const char *pr_session_get_protocol(int flags) {
return "ftp";
}
void pr_signals_handle(void) {
}
/* Module-specific stubs */
module prometheus_module = {
/* Always NULL */
NULL, NULL,
/* Module API version */
0x20,
/* Module name */
"prometheus",
/* Module configuration handler table */
NULL,
/* Module command handler table */
NULL,
/* Module authentication handler table */
NULL,
/* Module initialization */
NULL,
/* Session initialization */
NULL,
/* Module version */
MOD_PROMETHEUS_VERSION
};
proftpd-mod_prometheus-0.2/t/api/tests.c 0000664 0000000 0000000 00000007172 14106223670 0020413 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_prometheus API testsuite
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "tests.h"
struct testsuite_info {
const char *name;
Suite *(*get_suite)(void);
};
static struct testsuite_info suites[] = {
{ "db", tests_get_db_suite },
{ "http", tests_get_http_suite },
{ "text", tests_get_text_suite },
{ "metric", tests_get_metric_suite },
{ "metric.db", tests_get_metric_db_suite },
{ "registry", tests_get_registry_suite },
{ NULL, NULL }
};
static Suite *tests_get_suite(const char *suite) {
register unsigned int i;
for (i = 0; suites[i].name != NULL; i++) {
if (strcmp(suite, suites[i].name) == 0) {
return (*suites[i].get_suite)();
}
}
errno = ENOENT;
return NULL;
}
int main(int argc, char *argv[]) {
const char *log_file = "api-tests.log";
int nfailed = 0;
SRunner *runner = NULL;
char *requested = NULL;
runner = srunner_create(NULL);
/* XXX This log name should be set outside this code, e.g. via environment
* variable or command-line option.
*/
srunner_set_log(runner, log_file);
requested = getenv("PROMETHEUS_TEST_SUITE");
if (requested != NULL) {
Suite *suite;
suite = tests_get_suite(requested);
if (suite != NULL) {
srunner_add_suite(runner, suite);
} else {
fprintf(stderr,
"No such test suite ('%s') requested via PROMETHEUS_TEST_SUITE\n",
requested);
return EXIT_FAILURE;
}
} else {
register unsigned int i;
for (i = 0; suites[i].name; i++) {
Suite *suite;
suite = (suites[i].get_suite)();
if (suite != NULL) {
srunner_add_suite(runner, suite);
}
}
}
/* Configure the Trace API to write to stderr. */
pr_trace_use_stderr(TRUE);
requested = getenv("PROMETHEUS_TEST_NOFORK");
if (requested != NULL) {
srunner_set_fork_status(runner, CK_NOFORK);
} else {
requested = getenv("CK_DEFAULT_TIMEOUT");
if (requested == NULL) {
setenv("CK_DEFAULT_TIMEOUT", "60", 1);
}
}
srunner_run_all(runner, CK_NORMAL);
nfailed = srunner_ntests_failed(runner);
if (runner != NULL) {
srunner_free(runner);
}
if (nfailed != 0) {
fprintf(stderr, "-------------------------------------------------\n");
fprintf(stderr, " FAILED %d %s\n\n", nfailed,
nfailed != 1 ? "tests" : "test");
fprintf(stderr, " Please send email to:\n\n");
fprintf(stderr, " tj@castaglia.org\n\n");
fprintf(stderr, " containing the `%s' file (in the t/ directory)\n", log_file);
fprintf(stderr, " and the output from running `proftpd -V'\n");
fprintf(stderr, "-------------------------------------------------\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
proftpd-mod_prometheus-0.2/t/api/tests.h 0000664 0000000 0000000 00000003352 14106223670 0020414 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_prometheus API testsuite
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
/* Testsuite management */
#ifndef MOD_PROMETHEUS_TESTS_H
#define MOD_PROMETHEUS_TESTS_H
#include "mod_prometheus.h"
#ifdef HAVE_CHECK_H
# include
#else
# error "Missing Check installation; necessary for ProFTPD testsuite"
#endif
int tests_mkpath(pool *p, const char *path);
int tests_rmpath(pool *p, const char *path);
Suite *tests_get_db_suite(void);
Suite *tests_get_http_suite(void);
Suite *tests_get_metric_suite(void);
Suite *tests_get_metric_db_suite(void);
Suite *tests_get_registry_suite(void);
Suite *tests_get_text_suite(void);
extern volatile unsigned int recvd_signal_flags;
extern pid_t mpid;
extern server_rec *main_server;
#endif /* MOD_PROMETHEUS_TESTS_H */
proftpd-mod_prometheus-0.2/t/api/text.c 0000664 0000000 0000000 00000016354 14106223670 0020237 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_prometheus API testsuite
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
/* Text API tests. */
#include "tests.h"
#include "prometheus/text.h"
static pool *p = NULL;
static void set_up(void) {
if (p == NULL) {
p = permanent_pool = make_sub_pool(NULL);
}
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("prometheus.text", 1, 20);
}
mark_point();
}
static void tear_down(void) {
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("prometheus.text", 0, 0);
}
if (p != NULL) {
destroy_pool(p);
p = permanent_pool = NULL;
}
}
START_TEST (text_destroy_test) {
int res;
mark_point();
res = prom_text_destroy(NULL);
fail_unless(res < 0, "Failed to handle null text");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
}
END_TEST
START_TEST (text_create_test) {
int res;
struct prom_text *text;
mark_point();
text = prom_text_create(NULL);
fail_unless(text == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
text = prom_text_create(p);
fail_unless(text != NULL, "Failed to create text: %s", strerror(errno));
res = prom_text_destroy(text);
fail_unless(res == 0, "Failed to destroy text: %s", strerror(errno));
}
END_TEST
START_TEST (text_get_str_test) {
char *res;
struct prom_text *text;
mark_point();
res = prom_text_get_str(NULL, NULL, NULL);
fail_unless(res == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_text_get_str(p, NULL, NULL);
fail_unless(res == NULL, "Failed to handle null text");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
text = prom_text_create(p);
res = prom_text_get_str(p, text, NULL);
fail_unless(res == NULL, "Failed to handle absent text");
fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT,
strerror(errno), errno);
prom_text_destroy(text);
}
END_TEST
START_TEST (text_add_byte_test) {
int res;
char *str;
size_t sz;
struct prom_text *text;
mark_point();
res = prom_text_add_byte(NULL, '"');
fail_unless(res < 0, "Failed to handle null text");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
text = prom_text_create(p);
res = prom_text_add_byte(text, '{');
fail_unless(res == 0, "Failed to add byte: %s", strerror(errno));
str = prom_text_get_str(p, text, &sz);
fail_unless(str != NULL, "Failed get text: %s", strerror(errno));
fail_unless(sz == 1, "Expected size 1, got %lu", (unsigned long) sz);
fail_unless(strcmp(str, "{") == 0, "Expected '{', got '%s'", str);
prom_text_destroy(text);
}
END_TEST
START_TEST (text_add_str_test) {
int res;
char *str, *input;
size_t sz;
struct prom_text *text;
mark_point();
res = prom_text_add_str(NULL, NULL, 0);
fail_unless(res < 0, "Failed to handle null text");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
text = prom_text_create(p);
res = prom_text_add_str(text, NULL, 0);
fail_unless(res < 0, "Failed to handle null str");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
input = "foobar";
res = prom_text_add_str(text, input, 0);
fail_unless(res == 0, "Failed to handle zero-length text: %s",
strerror(errno));
str = prom_text_get_str(p, text, NULL);
fail_unless(str == NULL, "Failed to handle absent text");
fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT,
strerror(errno), errno);
mark_point();
res = prom_text_add_str(text, input, strlen(input));
fail_unless(res == 0, "Failed to handle text: %s", strerror(errno));
str = prom_text_get_str(p, text, &sz);
fail_unless(str != NULL, "Failed get text: %s", strerror(errno));
fail_unless(sz == 6, "Expected size 7, got %lu", (unsigned long) sz);
fail_unless(strcmp(str, input) == 0,
"Expected '%s', got '%s'", input, str);
prom_text_destroy(text);
}
END_TEST
START_TEST (text_from_labels_test) {
const char *res, *expected;
struct prom_text *text;
pr_table_t *labels;
mark_point();
res = prom_text_from_labels(NULL, NULL, NULL);
fail_unless(res == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = prom_text_from_labels(p, NULL, NULL);
fail_unless(res == NULL, "Failed to handle null text");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
text = prom_text_create(p);
res = prom_text_from_labels(p, text, NULL);
fail_unless(res != NULL, "Failed to handle null labels: %s",
strerror(errno));
fail_unless(strcmp(res, "") == 0, "Expected '', got '%s'", res);
/* Now, with labels. */
mark_point();
labels = pr_table_nalloc(p, 0, 2);
res = prom_text_from_labels(p, text, labels);
fail_unless(res != NULL, "Failed to handle empty labels: %s",
strerror(errno));
fail_unless(strcmp(res, "") == 0, "Expected '', got '%s'", res);
mark_point();
(void) pr_table_add_dup(labels, "protocol", "ftp", 0);
(void) pr_table_add_dup(labels, "foo", "BAR", 0);
res = prom_text_from_labels(p, text, labels);
fail_unless(res != NULL, "Failed to handle labels: %s", strerror(errno));
expected = "{foo=\"BAR\",protocol=\"ftp\"}";
fail_unless(strcmp(res, expected) == 0,
"Expected '%s', got '%s'", expected, res);
prom_text_destroy(text);
}
END_TEST
Suite *tests_get_text_suite(void) {
Suite *suite;
TCase *testcase;
suite = suite_create("text");
testcase = tcase_create("base");
tcase_add_checked_fixture(testcase, set_up, tear_down);
tcase_add_test(testcase, text_destroy_test);
tcase_add_test(testcase, text_create_test);
tcase_add_test(testcase, text_get_str_test);
tcase_add_test(testcase, text_add_byte_test);
tcase_add_test(testcase, text_add_str_test);
tcase_add_test(testcase, text_from_labels_test);
suite_add_tcase(suite, testcase);
return suite;
}
proftpd-mod_prometheus-0.2/t/lib/ 0000775 0000000 0000000 00000000000 14106223670 0017073 5 ustar 00root root 0000000 0000000 proftpd-mod_prometheus-0.2/t/lib/ProFTPD/ 0000775 0000000 0000000 00000000000 14106223670 0020311 5 ustar 00root root 0000000 0000000 proftpd-mod_prometheus-0.2/t/lib/ProFTPD/Tests/ 0000775 0000000 0000000 00000000000 14106223670 0021413 5 ustar 00root root 0000000 0000000 proftpd-mod_prometheus-0.2/t/lib/ProFTPD/Tests/Modules/ 0000775 0000000 0000000 00000000000 14106223670 0023023 5 ustar 00root root 0000000 0000000 proftpd-mod_prometheus-0.2/t/lib/ProFTPD/Tests/Modules/mod_prometheus.pm 0000664 0000000 0000000 00000523203 14106223670 0026420 0 ustar 00root root 0000000 0000000 package ProFTPD::Tests::Modules::mod_prometheus;
use lib qw(t/lib);
use base qw(ProFTPD::TestSuite::Child);
use strict;
use Data::Dumper;
use File::Path qw(mkpath);
use File::Spec;
use IO::Handle;
use ProFTPD::TestSuite::FTP;
use ProFTPD::TestSuite::Utils qw(:auth :config :features :running :test :testsuite);
$| = 1;
my $order = 0;
my $TESTS = {
prom_start_existing_dirs => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_unacceptable_method => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_bad_uri => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_base_uri => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metrics_uri => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metrics_uri_with_gzip => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metrics_uri_with_basic_auth_success => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metrics_uri_with_basic_auth_missing_credentials => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metrics_uri_with_basic_auth_wrong_username => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metrics_uri_with_basic_auth_wrong_password => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
# Basic metrics
prom_scrape_metric_build_info => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_startup_time => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
# Server metrics
prom_scrape_metric_connection => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_connection_refused => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_log_message => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
# Session metrics
prom_scrape_metric_auth_ok => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_auth_anon_ok => {
order => ++$order,
test_class => [qw(forking prometheus rootprivs)],
},
prom_scrape_metric_auth_error_unknown_user => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_auth_error_bad_password => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_auth_error_incomplete => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_directory_list => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_directory_list_error => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_file_download => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_file_download_error => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_file_upload => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_file_upload_error => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_login_succeeded => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_login_multiple_times => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_login_user_quit => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_login_user_multiple_times => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_login_pass_multiple_times => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_login_in_progress => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_login_error_bad_user => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_login_error_bad_password => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_login_error_user_bad_pass_good_pass => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_login_error_pass_multiple_times => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_login_error_denied_acl => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_timeout_idle => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_timeout_login => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_timeout_notransfer => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_timeout_session => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_scrape_metric_timeout_stalled => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
prom_config_exporter_addr => {
order => ++$order,
test_class => [qw(forking prometheus)],
},
};
sub new {
return shift()->SUPER::new(@_);
}
sub list_tests {
# Check for the required Perl modules:
#
# LWP-UserAgent
my $required = [qw(
LWP::UserAgent
)];
foreach my $req (@$required) {
eval "use $req";
if ($@) {
print STDERR "\nWARNING:\n + Module '$req' not found, skipping all tests\n
";
if ($ENV{TEST_VERBOSE}) {
print STDERR "Unable to load $req: $@\n";
}
return qw(testsuite_empty_test);
}
}
return testsuite_get_runnable_tests($TESTS);
}
# Support routines
sub saw_expected_content {
my $lines = shift;
my $expected = shift;
my $seen = 0;
foreach my $line (@$lines) {
if ($line =~ /$expected/) {
$seen = 1;
last;
}
}
return $seen;
}
# Test cases
sub prom_start_existing_dirs {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.http:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
my $ex;
# First, start the server
server_start($setup->{config_file});
# ...then stop the server. This means mod_prometheus will have created all
# the necessary directories, etc.
sleep(2);
server_stop($setup->{pid_file});
# Now start the server again. Time time, mod_prometheus will double-check
# permissions et al on the already-existing mod_prometheus directories that it
# created the first time.
sleep(2);
server_start($setup->{config_file});
# Stop server
sleep(2);
eval { server_stop($setup->{pid_file}) };
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_unacceptable_method {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.http:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/foo/bar/baz";
my $resp = $ua->delete($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
}
my $expected = 405;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'Method Not Allowed';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $headers = $resp->headers;
my $content_type = $headers->header('Content-Type');
$expected = 'text/plain';
$self->assert($expected eq $content_type,
test_msg("Expected Content-Type '$expected', got '$content_type'"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_bad_uri {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.http:20 prometheus.http.clf:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/foo/bar/baz";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
}
my $expected = 400;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'Bad Request';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $headers = $resp->headers;
my $content_type = $headers->header('Content-Type');
$expected = 'text/plain';
$self->assert($expected eq $content_type,
test_msg("Expected Content-Type '$expected', got '$content_type'"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_base_uri {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.http:20 prometheus.http.clf:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $headers = $resp->headers;
my $content_type = $headers->header('Content-Type');
$expected = 'text/plain';
$self->assert($expected eq $content_type,
test_msg("Expected Content-Type '$expected', got '$content_type'"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metrics_uri {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $headers = $resp->headers;
my $content_type = $headers->header('Content-Type');
$expected = 'text/plain';
$self->assert($expected eq $content_type,
test_msg("Expected Content-Type '$expected', got '$content_type'"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metrics_uri_with_gzip {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
$ua->default_header('Accept-Encoding' => 'deflate,gzip,foobar');
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->decoded_content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $headers = $resp->headers;
my $content_type = $headers->header('Content-Type');
$expected = 'text/plain';
$self->assert($expected eq $content_type,
test_msg("Expected Content-Type '$expected', got '$content_type'"));
my $content_encoding = $headers->header('Content-Encoding');
$expected = 'gzip';
$self->assert($expected eq $content_encoding,
test_msg("Expected Content-Encoding '$expected', got '$content_encoding'"));
my $content = $resp->decoded_content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_build_info .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metrics_uri_with_basic_auth_success {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $exporter_realm = 'proftpd';
my $exporter_username = 'prometheus';
my $exporter_password = 'Pr0m3th3u$';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port $exporter_username $exporter_password",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
$ua->credentials("127.0.0.1:$exporter_port", $exporter_realm,
$exporter_username, $exporter_password);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $headers = $resp->headers;
my $content_type = $headers->header('Content-Type');
$expected = 'text/plain';
$self->assert($expected eq $content_type,
test_msg("Expected Content-Type '$expected', got '$content_type'"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metrics_uri_with_basic_auth_from_env {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $exporter_realm = 'proftpd';
my $exporter_username = 'prometheus';
my $exporter_password = 'Pr0m3th3u$';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
$ENV{PROMETHEUS_USERNAME} = $exporter_username;
$ENV{PROMETHEUS_PASSWORD} = $exporter_password;
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
$ua->credentials("127.0.0.1:$exporter_port", $exporter_realm,
$exporter_username, $exporter_password);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $headers = $resp->headers;
my $content_type = $headers->header('Content-Type');
$expected = 'text/plain';
$self->assert($expected eq $content_type,
test_msg("Expected Content-Type '$expected', got '$content_type'"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metrics_uri_with_basic_auth_missing_credentials {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $exporter_realm = 'proftpd';
my $exporter_username = 'prometheus';
my $exporter_password = 'Pr0m3th3u$';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port $exporter_username $exporter_password",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 401;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'Unauthorized';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $headers = $resp->headers;
my $content_type = $headers->header('Content-Type');
$expected = 'text/plain';
$self->assert($expected eq $content_type,
test_msg("Expected Content-Type '$expected', got '$content_type'"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metrics_uri_with_basic_auth_wrong_username {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $exporter_realm = 'proftpd';
my $exporter_username = 'prometheus';
my $exporter_password = 'Pr0m3th3u$';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port $exporter_username $exporter_password",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
$ua->credentials("127.0.0.1:$exporter_port", $exporter_realm, 'foo',
'bar');
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 401;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'Unauthorized';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $headers = $resp->headers;
my $content_type = $headers->header('Content-Type');
$expected = 'text/plain';
$self->assert($expected eq $content_type,
test_msg("Expected Content-Type '$expected', got '$content_type'"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metrics_uri_with_basic_auth_wrong_password {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $exporter_realm = 'proftpd';
my $exporter_username = 'prometheus';
my $exporter_password = 'Pr0m3th3u$';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port $exporter_username $exporter_password",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
$ua->credentials("127.0.0.1:$exporter_port", $exporter_realm,
$exporter_username, 'bar');
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 401;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'Unauthorized';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $headers = $resp->headers;
my $content_type = $headers->header('Content-Type');
$expected = 'text/plain';
$self->assert($expected eq $content_type,
test_msg("Expected Content-Type '$expected', got '$content_type'"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_build_info {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_build_info .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_build_info counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_build_info\{mod_prometheus_version="\S+",proftpd_version="\S+"\} 1$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_startup_time {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_startup_time .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_startup_time counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_startup_time \d+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_connection {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:30 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
sleep(2);
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_connection_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_connection_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_connection_total\{protocol="ftp"\} 1$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Gauge
$expected = '^# HELP proftpd_connection_count .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_connection_count gauge$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Race: sometimes the session has not yet finished.
$expected = '^proftpd_connection_count\{protocol="ftp"\} (0|1)$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Histogram
$expected = '^# HELP proftpd_connection_duration_seconds .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_connection_duration_seconds histogram$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_connection_duration_seconds_bucket\{le="\+Inf",protocol="ftp"\} \d+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_connection_duration_seconds_count\{protocol="ftp"\} 1$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_connection_duration_seconds_sum\{protocol="ftp"\} \d+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_connection_refused {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
Limit => {
LOGIN => {
DenyAll => '',
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
eval { ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1) };
unless ($@) {
die("Connection succeeded unexpectedly");
}
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_connection_refused_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_connection_refused_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_connection_refused_total\{protocol="ftp"\} \d+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_log_message {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'event:20 prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_log_message_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_log_message_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_log_message_total\{level="debug",protocol="ftp"\} \d+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_log_message_total\{level="debug",protocol="ftp"\} \d+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_log_message_total\{level="info",protocol="ftp"\} \d+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_auth_ok {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'auth:20 prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_auth_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_auth_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_auth_total\{method="password",protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_auth_anon_ok {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $anon_dir = File::Spec->rel2abs($tmpdir);
my ($config_user, $config_group) = config_get_identity();
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'auth:20 prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
Anonymous => {
$anon_dir => {
User => $config_user,
Group => $config_group,
UserAlias => "anonymous $config_user",
RequireValidShell => 'off',
},
},
};
my $port;
($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login('anonymous', 'nospam@ftp.org');
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_auth_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_auth_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_auth_total\{method="anonymous",protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_auth_error_unknown_user {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'auth:20 prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
eval { $client->login('foo', 'bar') };
unless ($@) {
die("Login succeeded unexpectedly");
}
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_auth_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_auth_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_auth_error_total\{protocol="ftp",reason="unknown user"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_auth_error_bad_password {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'auth:20 prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
eval { $client->login($setup->{user}, 'bar') };
unless ($@) {
die("Login succeeded unexpectedly");
}
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_auth_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_auth_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_auth_error_total\{protocol="ftp",reason="bad password"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_auth_error_incomplete {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'auth:20 prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->user('foo');
$client->quit();
# Allow time for the session to end before scraping.
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_auth_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_auth_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_auth_error_total\{protocol="ftp",reason="incomplete"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_directory_list {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
# LIST
my $conn = $client->list_raw();
unless ($conn) {
die("LIST failed: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 30);
eval { $conn->close() };
# NLST
$buf = '';
$conn = $client->nlst_raw();
unless ($conn) {
die("NLST failed: " . $client->response_code() . " " .
$client->response_msg());
}
$conn->read($buf, 8192, 30);
eval { $conn->close() };
# MLSD
$buf = '';
$conn = $client->mlsd_raw();
unless ($conn) {
die("MLSD failed: " . $client->response_code() . " " .
$client->response_msg());
}
$conn->read($buf, 8192, 30);
eval { $conn->close() };
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_directory_list_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_directory_list_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_directory_list_total\{protocol="ftp"\} 3+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Gauge
$expected = '^# HELP proftpd_directory_list_count .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_directory_list_count gauge$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_directory_list_count\{protocol="ftp"\} 0+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_directory_list_error {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
# LIST always succeeds, unfortunately.
# NLST
my $conn = $client->nlst_raw('/quxx/quzz');
if ($conn) {
die("NLST succeeded unexpectedly");
}
# MLSD
$conn = $client->mlsd_raw('/alef/bet/vet');
if ($conn) {
die("MLSD succeeded unexpectedly");
}
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_directory_list_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_directory_list_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_directory_list_error_total\{protocol="ftp"\} 2+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_file_download {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $test_file = File::Spec->rel2abs("$tmpdir/test.dat");
if (open(my $fh, "> $test_file")) {
print $fh "AbCd" x 8192;
unless (close($fh)) {
die("Can't write $test_file: $!");
}
} else {
die("Can't open $test_file: $!");
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
my ($resp_code, $resp_msg) = $client->retr($test_file);
$self->assert_transfer_ok($resp_code, $resp_msg);
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
$resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
$resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_file_download_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_file_download_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_file_download_total\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Gauge
$expected = '^# HELP proftpd_file_download_count .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_file_download_count gauge$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_file_download_count\{protocol="ftp"\} 0+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Histogram
$expected = '^# HELP proftpd_file_download_bytes .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_file_download_bytes histogram$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_file_download_bytes_bucket\{le="\+Inf",protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_file_download_bytes_count\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_file_download_bytes_sum\{protocol="ftp"\} 32768+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_file_download_error {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $test_file = File::Spec->rel2abs("$tmpdir/test.dat");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
eval { $client->retr($test_file) };
unless ($@) {
die("RETR $test_file succeeded unexpectedly");
}
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_file_download_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_file_download_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_file_download_error_total\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_file_upload {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $test_file = File::Spec->rel2abs("$tmpdir/test.dat");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
my $conn = $client->stor_raw($test_file);
unless ($conn) {
die("Failed to STOR: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf = "Hello World!\n";
$conn->write($buf, length($buf), 25);
eval { $conn->close() };
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
$self->assert_transfer_ok($resp_code, $resp_msg);
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
$resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
$resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_file_upload_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_file_upload_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_file_upload_total\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Gauge
$expected = '^# HELP proftpd_file_upload_count .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_file_upload_count gauge$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_file_upload_count\{protocol="ftp"\} 0+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Histogram
$expected = '^# HELP proftpd_file_upload_bytes .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_file_upload_bytes histogram$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_file_upload_bytes_bucket\{le="\+Inf",protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_file_upload_bytes_count\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_file_upload_bytes_sum\{protocol="ftp"\} 13+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_file_upload_error {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $test_file = File::Spec->rel2abs("$tmpdir/sub.d/test.dat");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
my $conn = $client->stor_raw($test_file);
if ($conn) {
die("STOR $test_file succeeded unexpectedly");
}
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_file_upload_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_file_upload_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_file_upload_error_total\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_login_succeeded {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_login_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_total\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Gauge
$expected = '^# HELP proftpd_login_count .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_count gauge$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_count\{protocol="ftp"\} 0+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Histogram
$expected = '^# HELP proftpd_login_delay_seconds .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_delay_seconds histogram$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_delay_seconds_bucket\{le="\+Inf",protocol="ftp"\} 1$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_delay_seconds_count\{protocol="ftp"\} 1$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_delay_seconds_sum\{protocol="ftp"\} \d+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_login_multiple_times {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
eval { $client->login($setup->{user}, $setup->{passwd}) };
eval { $client->login($setup->{user}, $setup->{passwd}) };
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_login_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_total\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Gauge
$expected = '^# HELP proftpd_login_count .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_count gauge$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_count\{protocol="ftp"\} 0+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_login_user_quit {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->user($setup->{user});
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_login_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_total 0+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Gauge
$expected = '^# HELP proftpd_login_count .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_count gauge$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Race: sometimes the session has not yet finished.
$expected = '^proftpd_login_count\{protocol="ftp"\} (0|1)$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_login_user_multiple_times {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->user($setup->{user});
eval { $client->user($setup->{user}) };
eval { $client->user($setup->{user}) };
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_login_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_total 0+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Gauge
$expected = '^# HELP proftpd_login_count .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_count gauge$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_count\{protocol="ftp"\} 0+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_login_pass_multiple_times {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
eval { $client->pass('foo') };
eval { $client->pass('foo') };
eval { $client->pass('foo') };
$client->user($setup->{user});
eval { $client->pass('foo') };
eval { $client->pass('foo') };
$client->user($setup->{user});
$client->pass($setup->{passwd});
eval { $client->pass('foo') };
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_login_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_total\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Gauge
$expected = '^# HELP proftpd_login_count .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_count gauge$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_count\{protocol="ftp"\} 0+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_login_in_progress {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->user($setup->{user});
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
# Counter
$expected = '^# HELP proftpd_login_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_total 0+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
# Gauge
$expected = '^# HELP proftpd_login_count .*?\.$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_count gauge$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_count\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_login_error_bad_user {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
eval { $client->login('foo', 'bar') };
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_login_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_error_total\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_login_error_bad_password {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
eval { $client->login($setup->{user}, 'bar') };
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_login_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_error_total\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_login_error_user_bad_pass_good_pass {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->user($setup->{user});
eval { $client->pass('foo') };
$client->user($setup->{user});
$client->pass($setup->{passwd});
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_login_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_error_total\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_login_error_pass_multiple_times {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
eval { $client->pass('foo') };
eval { $client->pass('foo') };
eval { $client->pass('foo') };
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_login_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_error_total\{protocol="ftp"\} 3+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_login_error_denied_acl {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
Limit => {
LOGIN => {
DenyUser => $setup->{user},
},
}
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
eval { $client->login($setup->{user}, $setup->{passwd}) };
unless ($@) {
die("Login succeeded unexpectedly");
}
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_login_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_login_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_login_error_total\{protocol="ftp"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_timeout_idle {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $timeout_idle = 3;
my $timeout_delay = $timeout_idle + 2;
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
TimeoutIdle => $timeout_idle,
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->noop();
# Wait for more than the TimeoutIdle period
sleep($timeout_delay);
eval { $client->noop() };
unless ($@) {
die("NOOP succeeded unexpectedly");
}
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_timeout_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_timeout_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_timeout_total\{protocol="ftp",reason="TimeoutIdle"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_timeout_login {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $timeout_login = 3;
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
TimeoutLogin => $timeout_login,
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
# Wait for 2s more than the TimeoutLogin period
sleep($timeout_login + 2);
eval { $client->user($setup->{user}) };
unless ($@) {
die("USER succeeded unexpectedly");
}
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_timeout_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_timeout_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_timeout_total\{protocol="ftp",reason="TimeoutLogin"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_timeout_notransfer {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $timeout_notransfer = 2;
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
TimeoutNoTransfer => $timeout_notransfer,
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
# Wait for 2s more than the TimeoutNoTransfer period
for (my $i = 0; $i < $timeout_notransfer; $i++) {
sleep(1);
eval { $client->noop() };
}
sleep(2);
eval { $client->noop() };
unless ($@) {
die("NOOP succeeded unexpectedly");
}
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_timeout_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_timeout_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_timeout_total\{protocol="ftp",reason="TimeoutNoTransfer"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_timeout_session {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $timeout_session = 3;
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
TimeoutSession => $timeout_session,
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
# Wait for 2s more than the TimeoutSession period
for (my $i = 0; $i < $timeout_session; $i++) {
sleep(1);
eval { $client->noop() };
}
sleep(2);
eval { $client->noop() };
unless ($@) {
die("NOOP succeeded unexpectedly");
}
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_timeout_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_timeout_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_timeout_total\{protocol="ftp",reason="TimeoutSession"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_timeout_stalled {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $test_file = File::Spec->rel2abs("$tmpdir/test.dat");
if (open(my $fh, "> $test_file")) {
print $fh "AbCd" x 8192000;
unless (close($fh)) {
die("Can't write $test_file: $!");
}
} else {
die("Can't open $test_file: $!");
}
my $timeout_stalled = 2;
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 prometheus.http.clf:10 prometheus.metric:20 prometheus.metric.db:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
TimeoutStalled => $timeout_stalled,
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
my $conn = $client->retr_raw($test_file);
unless ($conn) {
die("RETR failed: " . $client->response_code() . " " .
$client->response_msg());
}
# Wait for 2s more than the stalled period
sleep($timeout_stalled + 2);
my $buf = '';
$conn->read($buf, 8192, 30);
eval { $conn->close() };
eval { $client->noop() };
unless ($@) {
die("NOOP succeeded unexpectedly");
}
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_timeout_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_timeout_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_timeout_total\{protocol="ftp",reason="TimeoutStalled"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_config_exporter_addr {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Using export port = $exporter_port\n";
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.http:20 prometheus.http.clf:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "0.0.0.0:$exporter_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require LWP::UserAgent;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow server to start up
sleep(2);
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $headers = $resp->headers;
my $content_type = $headers->header('Content-Type');
$expected = 'text/plain';
$self->assert($expected eq $content_type,
test_msg("Expected Content-Type '$expected', got '$content_type'"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
1;
proftpd-mod_prometheus-0.2/t/lib/ProFTPD/Tests/Modules/mod_prometheus/ 0000775 0000000 0000000 00000000000 14106223670 0026055 5 ustar 00root root 0000000 0000000 proftpd-mod_prometheus-0.2/t/lib/ProFTPD/Tests/Modules/mod_prometheus/sftp.pm 0000664 0000000 0000000 00000023671 14106223670 0027400 0 ustar 00root root 0000000 0000000 package ProFTPD::Tests::Modules::mod_prometheus::sftp;
use lib qw(t/lib);
use base qw(ProFTPD::TestSuite::Child);
use strict;
use Data::Dumper;
use File::Path qw(mkpath);
use File::Spec;
use IO::Handle;
use ProFTPD::TestSuite::FTP;
use ProFTPD::TestSuite::Utils qw(:auth :config :features :running :test :testsuite);
$| = 1;
my $order = 0;
my $TESTS = {
prom_scrape_metric_handshake_error_ssh2 => {
order => ++$order,
test_class => [qw(forking mod_sftp prometheus)],
},
prom_scrape_metric_sftp_protocol => {
order => ++$order,
test_class => [qw(forking mod_sftp prometheus)],
},
};
sub new {
return shift()->SUPER::new(@_);
}
sub list_tests {
# Check for the required Perl modules:
#
# LWP-UserAgent
# Net-SSH2
# Net-SSH2-SFTP
my $required = [qw(
LWP::UserAgent
Net::SSH2
Net::SSH2::SFTP
)];
foreach my $req (@$required) {
eval "use $req";
if ($@) {
print STDERR "\nWARNING:\n + Module '$req' not found, skipping all tests\n
";
if ($ENV{TEST_VERBOSE}) {
print STDERR "Unable to load $req: $@\n";
}
return qw(testsuite_empty_test);
}
}
return testsuite_get_runnable_tests($TESTS);
}
sub set_up {
my $self = shift;
$self->SUPER::set_up(@_);
# Make sure that mod_sftp does not complain about permissions on the hostkey
# files.
my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key");
my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key");
unless (chmod(0400, $rsa_host_key, $dsa_host_key)) {
die("Can't set perms on $rsa_host_key, $dsa_host_key: $!");
}
}
# Support routines
sub saw_expected_content {
my $lines = shift;
my $expected = shift;
my $seen = 0;
foreach my $line (@$lines) {
if ($line =~ /$expected/) {
$seen = 1;
last;
}
}
return $seen;
}
# Test cases
sub prom_scrape_metric_handshake_error_ssh2 {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key");
my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 sftp:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require Net::SSH2;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $ssh2 = Net::SSH2->new();
my $cipher = 'arcfour';
$ssh2->method('crypt_cs', $cipher);
sleep(2);
if ($ssh2->connect('127.0.0.1', $port)) {
die("Connected to SSH server unexpectedly");
}
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_handshake_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_handshake_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_handshake_error_total\{protocol="ssh2"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_sftp_protocol {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key");
my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:20 prometheus.http:20 sftp:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require Net::SSH2;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $ssh2 = Net::SSH2->new();
sleep(2);
unless ($ssh2->connect('127.0.0.1', $port)) {
my ($err_code, $err_name, $err_str) = $ssh2->error();
die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
}
unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) {
my ($err_code, $err_name, $err_str) = $ssh2->error();
die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
}
my $sftp = $ssh2->sftp();
unless ($sftp) {
my ($err_code, $err_name, $err_str) = $ssh2->error();
die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
}
$sftp = undef;
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_sftp_protocol_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_sftp_protocol_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_sftp_protocol_total\{protocol="sftp",version="3"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
1;
proftpd-mod_prometheus-0.2/t/lib/ProFTPD/Tests/Modules/mod_prometheus/tls.pm 0000664 0000000 0000000 00000035522 14106223670 0027224 0 ustar 00root root 0000000 0000000 package ProFTPD::Tests::Modules::mod_prometheus::tls;
use lib qw(t/lib);
use base qw(ProFTPD::TestSuite::Child);
use strict;
use Data::Dumper;
use File::Path qw(mkpath);
use File::Spec;
use IO::Handle;
use ProFTPD::TestSuite::FTP;
use ProFTPD::TestSuite::Utils qw(:auth :config :features :running :test :testsuite);
$| = 1;
my $order = 0;
my $TESTS = {
prom_scrape_metric_handshake_error_tls_ctrl => {
order => ++$order,
test_class => [qw(forking mod_tls prometheus)],
},
prom_scrape_metric_handshake_error_tls_data => {
order => ++$order,
test_class => [qw(forking mod_tls prometheus)],
},
prom_scrape_metric_tls_protocol => {
order => ++$order,
test_class => [qw(forking mod_tls prometheus)],
},
};
sub new {
return shift()->SUPER::new(@_);
}
sub list_tests {
# Check for the required Perl modules:
#
# LWP-UserAgent
# Net-FTPSSL
my $required = [qw(
LWP::UserAgent
Net::FTPSSL
)];
foreach my $req (@$required) {
eval "use $req";
if ($@) {
print STDERR "\nWARNING:\n + Module '$req' not found, skipping all tests\n
";
if ($ENV{TEST_VERBOSE}) {
print STDERR "Unable to load $req: $@\n";
}
return qw(testsuite_empty_test);
}
}
return testsuite_get_runnable_tests($TESTS);
}
# Support routines
sub saw_expected_content {
my $lines = shift;
my $expected = shift;
my $seen = 0;
foreach my $line (@$lines) {
if ($line =~ /$expected/) {
$seen = 1;
last;
}
}
return $seen;
}
# Test cases
sub prom_scrape_metric_handshake_error_tls_ctrl {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_tls/server-cert.pem");
my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_tls/ca-cert.pem");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:30 prometheus.http:20 tls:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
'mod_tls.c' => {
TLSEngine => 'on',
TLSLog => $setup->{log_file},
TLSRequired => 'on',
TLSProtocol => 'TLSv1.2',
TLSRSACertificateFile => $cert_file,
TLSCACertificateFile => $ca_file,
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require Net::FTPSSL;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Give the server a chance to start up
sleep(2);
my $ssl_opts = {
Encryption => 'E',
Port => $port,
SSL_version => 'TLSv1',
};
if ($ENV{TEST_VERBOSE}) {
$ssl_opts->{Debug} = 1;
}
my $client = Net::FTPSSL->new('127.0.0.1', %$ssl_opts);
if ($client) {
die("TLS ctrl handshake succeeded unexpectedly");
}
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_handshake_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_handshake_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_handshake_error_total\{connection="ctrl",protocol="ftps"\} 1$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_handshake_error_tls_data {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_tls/server-cert.pem");
my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_tls/ca-cert.pem");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'event:20 prometheus:20 prometheus.db:30 prometheus.http:20 tls:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
TimeoutLinger => 1,
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
'mod_tls.c' => {
TLSEngine => 'on',
TLSLog => $setup->{log_file},
TLSRequired => 'on',
TLSRSACertificateFile => $cert_file,
TLSCACertificateFile => $ca_file,
TLSOptions => 'EnableDiags NoSessionReuseRequired',
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require Net::FTPSSL;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Give the server a chance to start up
sleep(2);
my $ssl_opts = {
Encryption => 'E',
Port => $port,
SSL_version => 'TLSv1',
};
if ($ENV{TEST_VERBOSE}) {
$ssl_opts->{Debug} = 2;
}
my $client = Net::FTPSSL->new('127.0.0.1', %$ssl_opts);
unless ($client) {
die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr());
}
my $data_opts = {
SSL_cipher_list => 'MD5',
# Explicitly disable reuse of the control session; necessary for
# our other options to be honored.
SSL_reuse_ctx => undef,
};
# Note: If we need to use different tricks here, depending on the
# OpenSSL version used by Net::SSLeay (because of _e.g._ dropping of
# SSLv3 support in OpenSSL 1.1.1x), we can use:
#
# perl -mNet::SSLeay -e 'print Net::SSLeay::OpenSSL_version('OPENSSL_VERSION'), "\n";'
# OpenSSL 1.1.1 11 Sep 2018
#
# For now, we assume that the MD5 ciphersuites are sufficiently
# disabled on a widespread enough basis to suffice for our needs.
unless ($client->set_dc_from_hash($data_opts) == 2) {
die("Can't set Net-FTPSSL data conn TLS cipher list");
}
unless ($client->login($setup->{user}, $setup->{passwd})) {
die("Can't login: " . $client->last_message());
}
my $res = $client->list('.');
if ($res) {
die("LIST succeeded unexpectedly");
}
eval { $client->quit() };
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_handshake_error_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_handshake_error_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_handshake_error_total\{connection="data",protocol="ftps"\} 1$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub prom_scrape_metric_tls_protocol {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'prometheus');
my $table_dir = File::Spec->rel2abs("$tmpdir/var/prometheus");
my $exporter_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_tls/server-cert.pem");
my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_tls/ca-cert.pem");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'prometheus:20 prometheus.db:30 prometheus.http:20 tls:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_prometheus.c' => {
PrometheusEngine => 'on',
PrometheusLog => $setup->{log_file},
PrometheusTables => $table_dir,
PrometheusExporter => "127.0.0.1:$exporter_port",
},
'mod_tls.c' => {
TLSEngine => 'on',
TLSLog => $setup->{log_file},
TLSRequired => 'on',
TLSRSACertificateFile => $cert_file,
TLSCACertificateFile => $ca_file,
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require Net::FTPSSL;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Give the server a chance to start up
sleep(2);
my $ssl_opts = {
Encryption => 'E',
Port => $port,
};
if ($ENV{TEST_VERBOSE}) {
$ssl_opts->{Debug} = 1;
}
my $client = Net::FTPSSL->new('127.0.0.1', %$ssl_opts);
unless ($client) {
die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr());
}
unless ($client->login($setup->{user}, $setup->{passwd})) {
die("Can't login: " . $client->last_message());
}
$client->quit();
my $ua = LWP::UserAgent->new();
$ua->timeout(3);
my $url = "http://127.0.0.1:$exporter_port/metrics";
my $resp = $ua->get($url);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response: ", $resp->status_line, "\n";
print STDERR "# ", $resp->content, "\n";
}
my $expected = 200;
my $resp_code = $resp->code;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'OK';
my $resp_msg = $resp->message;
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $content = $resp->content;
my $lines = [split(/\n/, $content)];
$expected = '^# HELP proftpd_tls_protocol_total .*?\.$';
my $seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^# TYPE proftpd_tls_protocol_total counter$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
$expected = '^proftpd_tls_protocol_total\{protocol="ftps",version="TLSv.*?"\} 1+$';
$seen = saw_expected_content($lines, $expected);
$self->assert($seen,
test_msg("Did not see '$expected' in '$content' as expected"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
1;
proftpd-mod_prometheus-0.2/t/modules/ 0000775 0000000 0000000 00000000000 14106223670 0017775 5 ustar 00root root 0000000 0000000 proftpd-mod_prometheus-0.2/t/modules/mod_prometheus.t 0000664 0000000 0000000 00000000272 14106223670 0023215 0 ustar 00root root 0000000 0000000 #!/usr/bin/env perl
use lib qw(t/lib);
use strict;
use Test::Unit::HarnessUnit;
$| = 1;
my $r = Test::Unit::HarnessUnit->new();
$r->start("ProFTPD::Tests::Modules::mod_prometheus");
proftpd-mod_prometheus-0.2/t/modules/mod_prometheus/ 0000775 0000000 0000000 00000000000 14106223670 0023027 5 ustar 00root root 0000000 0000000 proftpd-mod_prometheus-0.2/t/modules/mod_prometheus/sftp.t 0000664 0000000 0000000 00000000300 14106223670 0024161 0 ustar 00root root 0000000 0000000 #!/usr/bin/env perl
use lib qw(t/lib);
use strict;
use Test::Unit::HarnessUnit;
$| = 1;
my $r = Test::Unit::HarnessUnit->new();
$r->start("ProFTPD::Tests::Modules::mod_prometheus::sftp");
proftpd-mod_prometheus-0.2/t/modules/mod_prometheus/tls.t 0000664 0000000 0000000 00000000277 14106223670 0024024 0 ustar 00root root 0000000 0000000 #!/usr/bin/env perl
use lib qw(t/lib);
use strict;
use Test::Unit::HarnessUnit;
$| = 1;
my $r = Test::Unit::HarnessUnit->new();
$r->start("ProFTPD::Tests::Modules::mod_prometheus::tls");
proftpd-mod_prometheus-0.2/tests.pl 0000664 0000000 0000000 00000005312 14106223670 0017562 0 ustar 00root root 0000000 0000000 #!/usr/bin/env perl
use strict;
use Cwd qw(abs_path);
use File::Spec;
use Getopt::Long;
use Test::Harness qw(&runtests $verbose);
my $opts = {};
GetOptions($opts, 'h|help', 'C|class=s@', 'K|keep-tmpfiles', 'F|file-pattern=s',
'V|verbose');
if ($opts->{h}) {
usage();
}
if ($opts->{K}) {
$ENV{KEEP_TMPFILES} = 1;
}
$verbose = 1;
if ($opts->{V}) {
$ENV{TEST_VERBOSE} = 1;
}
# We use this, rather than use(), since use() is equivalent to a BEGIN
# block, and we want the module to be loaded at run-time.
if ($ENV{PROFTPD_TEST_DIR}) {
push(@INC, "$ENV{PROFTPD_TEST_DIR}/tests/t/lib");
}
my $test_dir = (File::Spec->splitpath(abs_path(__FILE__)))[1];
push(@INC, "$test_dir/t/lib");
require ProFTPD::TestSuite::Utils;
import ProFTPD::TestSuite::Utils qw(:testsuite);
# This is to handle the case where this tests.pl script might be
# being used to run test files other than those that ship with proftpd,
# e.g. to run the tests that come with third-party modules.
unless (defined($ENV{PROFTPD_TEST_BIN})) {
$ENV{PROFTPD_TEST_BIN} = File::Spec->catfile($test_dir, '..', 'proftpd');
}
$| = 1;
my $test_files;
if (scalar(@ARGV) > 0) {
$test_files = [@ARGV];
} else {
$test_files = [qw(
t/modules/mod_prometheus.t
)];
# Now interrogate the build to see which module/feature-specific test files
# should be added to the list.
my $order = 0;
my $FEATURE_TESTS = {
't/modules/mod_prometheus/sftp.t' => {
order => ++$order,
test_class => [qw(mod_prometheus mod_sftp)],
},
't/modules/mod_prometheus/tls.t' => {
order => ++$order,
test_class => [qw(mod_prometheus mod_tls)],
},
};
my @feature_tests = testsuite_get_runnable_tests($FEATURE_TESTS);
my $feature_ntests = scalar(@feature_tests);
if ($feature_ntests > 1 ||
($feature_ntests == 1 && $feature_tests[0] ne 'testsuite_empty_test')) {
push(@$test_files, @feature_tests);
}
}
$ENV{PROFTPD_TEST} = 1;
if (defined($opts->{C})) {
$ENV{PROFTPD_TEST_ENABLE_CLASS} = join(':', @{ $opts->{C} });
} else {
# Disable all 'inprogress' and 'slow' tests by default
$ENV{PROFTPD_TEST_DISABLE_CLASS} = 'inprogress:slow';
}
if (defined($opts->{F})) {
# Using the provided string as a regex, and run only the tests whose
# files match the pattern
my $file_pattern = $opts->{F};
my $filtered_files = [];
foreach my $test_file (@$test_files) {
if ($test_file =~ /$file_pattern/) {
push(@$filtered_files, $test_file);
}
}
$test_files = $filtered_files;
}
runtests(@$test_files) if scalar(@$test_files) > 0;
exit 0;
sub usage {
print STDOUT <