Fix check_snmp state related tests

This commit is contained in:
Lorenz Kästle 2026-03-25 23:53:36 +01:00
parent 935f0ad2db
commit 49f6990359
8 changed files with 269 additions and 149 deletions

View file

@ -256,7 +256,7 @@ int main(int argc, char **argv) {
np_init((char *)progname, argc, argv);
state_key stateKey = np_enable_state(NULL, 1, progname, argc, argv);
state_key stateKey = np_enable_state(NULL, 1, progname, argc, (const char **)argv);
/* Parse extra opts if any */
argv = np_extra_opts(&argc, argv, progname);
@ -311,15 +311,16 @@ int main(int argc, char **argv) {
bool have_previous_state = false;
if (config.evaluation_params.calculate_rate) {
state_data *previous_state = np_state_read(stateKey);
if (previous_state == NULL) {
np_state_read_wrapper recovered = np_state_read(stateKey);
if (recovered.errorcode != 0) {
// failed to recover state
// or no previous state
have_previous_state = false;
} else {
// sanity check
recover_state_data_type prev_state_wrapper =
recover_state_data(previous_state->data, (idx_t)previous_state->length);
recover_state_data(recovered.data.data, (idx_t)recovered.data.length);
if (prev_state_wrapper.errorcode == OK) {
have_previous_state = true;

View file

@ -599,7 +599,7 @@ check_snmp_evaluation evaluate_single_unit(response_value response,
return result;
}
char *_np_state_generate_key(int argc, char **argv);
char *_np_state_generate_key(int argc, const char **argv);
/*
* If time=NULL, use current time. Create state file, with state format
@ -608,7 +608,11 @@ char *_np_state_generate_key(int argc, char **argv);
* two things writing to same key at same time.
* Will die with UNKNOWN if errors
*/
void np_state_write_string(state_key stateKey, time_t timestamp, char *stringToStore) {
void np_state_write_string(state_key stateKey, time_t timestamp, const char *stringToStore) {
if (stateKey._filename == NULL || strcmp(stateKey._filename, "") == 0) {
die(STATE_UNKNOWN, "%s: empty filename in state file\n", __FUNCTION__);
}
time_t current_time;
if (timestamp == 0) {
time(&current_time);
@ -691,10 +695,11 @@ void np_state_write_string(state_key stateKey, time_t timestamp, char *stringToS
if (rename(temp_file, stateKey._filename) != 0) {
unlink(temp_file);
printf("%s: %s to %s\n", _("Cannot rename state temp file"), temp_file, stateKey._filename);
if (temp_file) {
free(temp_file);
}
die(STATE_UNKNOWN, _("Cannot rename state temp file"));
die(STATE_UNKNOWN, NULL);
}
if (temp_file) {
@ -705,17 +710,25 @@ void np_state_write_string(state_key stateKey, time_t timestamp, char *stringToS
/*
* Read the state file
*/
bool _np_state_read_file(FILE *state_file, state_key stateKey) {
typedef struct {
int errorcode;
state_data data;
} check_snmp_state_read_file_wrapper;
check_snmp_state_read_file_wrapper _np_state_read_file(FILE *state_file, state_key input_state) {
time_t current_time;
time(&current_time);
check_snmp_state_read_file_wrapper result = {
.errorcode = 0,
.data = {},
};
/* Note: This introduces a limit of 8192 bytes in the string data */
char *line = (char *)calloc(1, 8192);
if (line == NULL) {
die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
}
bool status = false;
enum {
STATE_FILE_VERSION,
STATE_DATA_VERSION,
@ -724,7 +737,8 @@ bool _np_state_read_file(FILE *state_file, state_key stateKey) {
STATE_DATA_END
} expected = STATE_FILE_VERSION;
int failure = 0;
bool failure = false;
bool found_data = false; //if we reach the end but there is not data, we fail
while (!failure && (fgets(line, 8192, state_file)) != NULL) {
size_t pos = strlen(line);
if (line[pos - 1] == '\n') {
@ -739,15 +753,15 @@ bool _np_state_read_file(FILE *state_file, state_key stateKey) {
case STATE_FILE_VERSION: {
int i = atoi(line);
if (i != NP_STATE_FORMAT_VERSION) {
failure++;
failure = true;
} else {
expected = STATE_DATA_VERSION;
}
} break;
case STATE_DATA_VERSION: {
int i = atoi(line);
if (i != stateKey.data_version) {
failure++;
if (i != input_state.data_version) {
failure = true;
} else {
expected = STATE_DATA_TIME;
}
@ -756,20 +770,21 @@ bool _np_state_read_file(FILE *state_file, state_key stateKey) {
/* If time > now, error */
time_t data_time = strtoul(line, NULL, 10);
if (data_time > current_time) {
failure++;
failure = true;
} else {
stateKey.state_data->time = data_time;
result.data.time = data_time;
expected = STATE_DATA_TEXT;
}
} break;
case STATE_DATA_TEXT:
stateKey.state_data->data = strdup(line);
if (stateKey.state_data->data == NULL) {
result.data.data = strdup(line);
if (result.data.data == NULL) {
die(STATE_UNKNOWN, _("Cannot execute strdup: %s"), strerror(errno));
}
stateKey.state_data->length = strlen(line);
result.data.length = strlen(line);
expected = STATE_DATA_END;
status = true;
result.errorcode = 0;
found_data = true;
break;
case STATE_DATA_END:;
}
@ -778,7 +793,12 @@ bool _np_state_read_file(FILE *state_file, state_key stateKey) {
if (line) {
free(line);
}
return status;
if (failure || !found_data) {
result.errorcode = 1;
}
return result;
}
/*
* Will return NULL if no data is available (first run). If key currently
@ -787,32 +807,31 @@ bool _np_state_read_file(FILE *state_file, state_key stateKey) {
* If numerically lower, then return as no previous state. die with UNKNOWN
* if exceptional error.
*/
state_data *np_state_read(state_key stateKey) {
np_state_read_wrapper np_state_read(state_key stateKey) {
/* Open file. If this fails, no previous state found */
np_state_read_wrapper result = {
.data = {},
.errorcode = 0,
};
FILE *statefile = fopen(stateKey._filename, "r");
state_data *this_state_data = (state_data *)calloc(1, sizeof(state_data));
if (statefile != NULL) {
if (this_state_data == NULL) {
die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
}
this_state_data->data = NULL;
stateKey.state_data = this_state_data;
if (_np_state_read_file(statefile, stateKey)) {
this_state_data->errorcode = OK;
} else {
this_state_data->errorcode = ERROR;
}
fclose(statefile);
} else {
if (statefile == NULL) {
// Failed to open state file
this_state_data->errorcode = ERROR;
result.errorcode = 1;
return result;
}
return stateKey.state_data;
check_snmp_state_read_file_wrapper recovered = _np_state_read_file(statefile, stateKey);
if (recovered.errorcode == 0) {
result.errorcode = OK;
result.data = recovered.data;
} else {
result.errorcode = ERROR;
}
fclose(statefile);
return result;
}
/*
@ -846,7 +865,7 @@ char *_np_state_calculate_location_prefix(void) {
* UNKNOWN if exception
*/
state_key np_enable_state(char *keyname, int expected_data_version, const char *plugin_name,
int argc, char **argv) {
int argc, const char **argv) {
state_key *this_state = (state_key *)calloc(1, sizeof(state_key));
if (this_state == NULL) {
die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
@ -854,7 +873,7 @@ state_key np_enable_state(char *keyname, int expected_data_version, const char *
char *temp_keyname = NULL;
if (keyname == NULL) {
temp_keyname = _np_state_generate_key(argc, argv);
temp_keyname = _np_state_generate_key(argc, (const char **)argv);
} else {
temp_keyname = strdup(keyname);
if (temp_keyname == NULL) {
@ -873,7 +892,7 @@ state_key np_enable_state(char *keyname, int expected_data_version, const char *
this_state->name = temp_keyname;
this_state->plugin_name = (char *)plugin_name;
this_state->data_version = expected_data_version;
this_state->state_data = NULL;
this_state->state_data = (state_data){};
/* Calculate filename */
char *temp_filename = NULL;
@ -889,18 +908,17 @@ state_key np_enable_state(char *keyname, int expected_data_version, const char *
}
/*
* Returns a string to use as a keyname, based on an md5 hash of argv, thus
* Returns a string to use as a keyname, based on the sha256 hash of argv, thus
* hopefully a unique key per service/plugin invocation. Use the extra-opts
* parse of argv, so that uniqueness in parameters are reflected there.
*/
char *_np_state_generate_key(int argc, char **argv) {
char *_np_state_generate_key(int argc, const char **argv) {
unsigned char result[256];
#ifdef USE_OPENSSL
/*
* This code path is chosen if openssl is available (which should be the most common
* scenario). Alternatively, the gnulib implementation/
*
* scenario). Alternatively, the gnulib implementation is used
*/
EVP_MD_CTX *ctx = EVP_MD_CTX_new();

View file

@ -3,9 +3,9 @@
#include "./config.h"
#include <net-snmp/library/asn1.h>
check_snmp_test_unit check_snmp_test_unit_init();
check_snmp_test_unit check_snmp_test_unit_init(void);
int check_snmp_set_thresholds(const char *, check_snmp_test_unit[], size_t, bool);
check_snmp_config check_snmp_config_init();
check_snmp_config check_snmp_config_init(void);
typedef struct {
oid oid[MAX_OID_LEN];
@ -62,10 +62,14 @@ typedef struct state_key_struct {
char *plugin_name;
int data_version;
char *_filename;
state_data *state_data;
state_data state_data;
} state_key;
state_data *np_state_read(state_key stateKey);
typedef struct {
int errorcode;
state_data data;
} np_state_read_wrapper;
np_state_read_wrapper np_state_read(state_key stateKey);
state_key np_enable_state(char *keyname, int expected_data_version, const char *plugin_name,
int argc, char **argv);
void np_state_write_string(state_key stateKey, time_t timestamp, char *stringToStore);
int argc, const char **argv);
void np_state_write_string(state_key stateKey, time_t timestamp, const char *stringToStore);

View file

@ -19,39 +19,79 @@
#include "../../tap/tap.h"
#include "../../config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <libgen.h>
#include <ftw.h>
#include "utils_base.c"
#include "../check_snmp.d/check_snmp_helpers.h"
#include "states.h"
// helpers
int unlink_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
(void)sb;
(void)typeflag;
(void)ftwbuf;
int rv = remove(fpath);
if (rv) {
perror(fpath);
}
return rv;
}
int rmrf(char *path) { return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS); }
char *create_file_path(const char *prefix, const char *end_part) {
char *result = calloc(strlen(prefix) + strlen(end_part) + 1, sizeof(char));
if (result == NULL) {
die(2, "Failed to allocate memory");
}
strcpy(result, prefix);
strcat(result, end_part);
return result;
}
// declarations to make it compile
int verbose = 0;
void print_usage() {}
void print_usage(void) {}
const char *progname = "test_check_snmp";
char *_np_state_generate_key(int argc, char **argv);
char *_np_state_calculate_location_prefix(void);
//
int main(int argc, char **argv) {
void test_key_generation(int argc, char **argv) {
char *temp_string = (char *)_np_state_generate_key(argc, argv);
ok(!strcmp(temp_string, "e2d17f995fd4c020411b85e3e3d0ff7306d4147e"),
"Got hash with exe and no parameters") ||
diag("You are probably running in wrong directory. Must run as ./test_utils");
ok(!strcmp(temp_string, "8a5881f5f97e68878b738538d9b864e1c8e3e463"),
"Got hash with exe and no parameters: %s", temp_string);
if (!strcmp(temp_string, "8dd4ba3c1dcea40bd80fe2e2c73872b669e211banot")) {
diag("You are probably running in wrong directory. Must run as ./tests/test_check_snmp");
}
int fake_argc = 4;
char *fake_argv[] = {
"./test_utils",
"./tests/test_check_snmp",
"here",
"--and",
"now",
};
temp_string = (char *)_np_state_generate_key(fake_argc, fake_argv);
ok(!strcmp(temp_string, "bd72da9f78ff1419fad921ea5e43ce56508aef6c"),
"Got based on expected argv");
ok(!strcmp(temp_string, "cf15afca3c3a45d60056384f64459880ce3dedc5"),
"Got %s based on expected argv", temp_string);
}
void test_state_location_prefix(void) {
unsetenv("MP_STATE_PATH");
temp_string = (char *)_np_state_calculate_location_prefix();
char *temp_string = (char *)_np_state_calculate_location_prefix();
ok(!strcmp(temp_string, NP_STATE_DIR_PREFIX), "Got default directory");
setenv("MP_STATE_PATH", "", 1);
@ -62,111 +102,96 @@ int main(int argc, char **argv) {
temp_string = (char *)_np_state_calculate_location_prefix();
ok(!strcmp(temp_string, "/usr/local/nagios/var"), "Got default directory");
fake_argc = 1;
fake_argv[0] = "./test_utils";
unsetenv("MP_STATE_PATH");
}
void test_enable_state(void) {
const int fake_argc = 1;
const char *fake_argv[1] = {"fake_argv"};
state_key temp_state_key1 = np_enable_state(NULL, 51, "check_test", fake_argc, fake_argv);
ok(!strcmp(temp_state_key1.plugin_name, "check_test"), "Got plugin name");
ok(!strcmp(temp_state_key1.name, "e2d17f995fd4c020411b85e3e3d0ff7306d4147e"),
"Got generated filename");
ok(!strcmp(temp_state_key1.name, "8dd4ba3c1dcea40bd80fe2e2c73872b669e211ba"),
"Got generated filename: %s", temp_state_key1.name);
const char *expected_plugin_name = "check_foobar";
state_key temp_state_key2 =
np_enable_state("allowedchars_in_keyname", 77, "check_snmp", fake_argc, fake_argv);
np_enable_state("allowedchars_in_keyname", 77, expected_plugin_name, fake_argc, fake_argv);
ok(!strcmp(temp_state_key2.plugin_name, expected_plugin_name), "Got plugin name: %s",
temp_state_key2.plugin_name);
ok(!strcmp(temp_state_key2.name, "allowedchars_in_keyname"),
"Got key name with valid chars: %s", temp_state_key2.name);
}
char state_path[1024];
sprintf(state_path, "/usr/local/nagios/var/%lu/check_test/allowedchars_in_keyname",
(unsigned long)geteuid());
ok(!strcmp(temp_state_key2.plugin_name, "check_test"), "Got plugin name");
ok(!strcmp(temp_state_key2.name, "allowedchars_in_keyname"), "Got key name with valid chars");
ok(!strcmp(temp_state_key2._filename, state_path), "Got internal filename");
void test_read_state(state_key test_state, const char test_string[], const char *test_dir,
const char *example_dir) {
np_state_read_wrapper recovered_state = np_state_read(test_state);
ok(recovered_state.errorcode == 0, "Got state data now");
/* Don't do this test just yet. Will die */
/*
np_enable_state("bad^chars$in@here", 77);
temp_state_key = this_monitoring_plugin->state;
ok( !strcmp(temp_state_key->name, "bad_chars_in_here"), "Got key name with bad chars replaced"
);
*/
if (recovered_state.errorcode != 0) {
diag("Are you running in right directory? No state data could be recovered");
exit(2);
}
state_key temp_state_key3 =
np_enable_state("funnykeyname", 54, "check_snmp", fake_argc, fake_argv);
sprintf(state_path, "/usr/local/nagios/var/%lu/check_test/funnykeyname",
(unsigned long)geteuid());
ok(!strcmp(temp_state_key3.plugin_name, "check_test"), "Got plugin name");
ok(!strcmp(temp_state_key3.name, "funnykeyname"), "Got key name");
ok(recovered_state.data.time == 1234567890, "Got time: %d", recovered_state.data.time);
ok(!strcmp((char *)recovered_state.data.data, test_string), "Data as expected");
ok(!strcmp(temp_state_key3._filename, state_path), "Got internal filename");
ok(temp_state_key3.data_version == 54, "Version set");
test_state.data_version = 53;
recovered_state = np_state_read(test_state);
ok(recovered_state.errorcode != 0, "Older data version gives error");
test_state.data_version = 54;
state_data *temp_state_data = np_state_read(temp_state_key3);
ok(temp_state_data == NULL, "Got no state data as file does not exist");
char *nonexistent_file_path = create_file_path(example_dir, "/nonexistent");
test_state._filename = nonexistent_file_path;
np_state_read_wrapper non_existent = np_state_read(test_state);
ok(non_existent.errorcode != 0, "Missing file gives error");
/*
temp_fp = fopen("var/statefile", "r");
if (temp_fp==NULL)
printf("Error opening. errno=%d\n", errno);
printf("temp_fp=%s\n", temp_fp);
ok( _np_state_read_file(temp_fp) == true, "Can read state file" );
fclose(temp_fp);
*/
char *oldformat_file_path = create_file_path(example_dir, "/oldformat");
test_state._filename = oldformat_file_path;
np_state_read_wrapper old_format = np_state_read(test_state);
ok(old_format.errorcode != 0, "Old file format gives error");
temp_state_key3._filename = "var/statefile";
temp_state_data = np_state_read(temp_state_key3);
ok(temp_state_data != NULL, "Got state data now") ||
diag("Are you running in right directory? Will get coredump next if not");
ok(temp_state_data->time == 1234567890, "Got time");
ok(!strcmp((char *)temp_state_data->data, "String to read"), "Data as expected");
char *baddate_file_path = create_file_path(example_dir, "/baddate");
test_state._filename = baddate_file_path;
np_state_read_wrapper baddate = np_state_read(test_state);
ok(baddate.errorcode != 0, "Bad date gives error");
temp_state_key3.data_version = 53;
temp_state_data = np_state_read(temp_state_key3);
ok(temp_state_data == NULL, "Older data version gives NULL");
temp_state_key3.data_version = 54;
char *missingdataline_file_path = create_file_path(example_dir, "/missingdataline");
test_state._filename = missingdataline_file_path;
np_state_read_wrapper missingdataline = np_state_read(test_state);
ok(missingdataline.errorcode != 0, "Missing data line gives error");
temp_state_key3._filename = "var/nonexistent";
temp_state_data = np_state_read(temp_state_key3);
ok(temp_state_data == NULL, "Missing file gives NULL");
// generate new file to test time proceeding
char *generated_file_path = create_file_path(test_dir, "/generated");
test_state._filename = generated_file_path;
temp_state_key3._filename = "var/oldformat";
temp_state_data = np_state_read(temp_state_key3);
ok(temp_state_data == NULL, "Old file format gives NULL");
temp_state_key3._filename = "var/baddate";
temp_state_data = np_state_read(temp_state_key3);
ok(temp_state_data == NULL, "Bad date gives NULL");
temp_state_key3._filename = "var/missingdataline";
temp_state_data = np_state_read(temp_state_key3);
ok(temp_state_data == NULL, "Missing data line gives NULL");
unlink("var/generated");
temp_state_key3._filename = "var/generated";
char *example_statefile_file_path = create_file_path(example_dir, "/statefile");
time_t current_time = 1234567890;
np_state_write_string(temp_state_key3, current_time, "String to read");
ok(system("cmp var/generated var/statefile") == 0, "Generated file same as expected");
np_state_write_string(test_state, current_time, test_string);
char *cmp_execution_string = NULL;
(void) asprintf(&cmp_execution_string, "cmp %s %s", generated_file_path, example_statefile_file_path);
ok(system(cmp_execution_string) == 0, "Generated file same as expected");
unlink("var/generated_directory/statefile");
unlink("var/generated_directory");
temp_state_key3._filename = "var/generated_directory/statefile";
char *generated_dir_test_file_path =
create_file_path(test_dir, "/generated_directory/statefile");
test_state._filename = generated_dir_test_file_path;
current_time = 1234567890;
np_state_write_string(temp_state_key3, current_time, "String to read");
ok(system("cmp var/generated_directory/statefile var/statefile") == 0,
"Have created directory");
np_state_write_string(test_state, current_time, test_string);
(void) asprintf(&cmp_execution_string, "cmp %s %s", generated_dir_test_file_path,
example_statefile_file_path);
ok(system(cmp_execution_string) == 0, "Have created directory");
/* This test to check cannot write to dir - can't automate yet */
/*
unlink("var/generated_bad_dir");
mkdir("var/generated_bad_dir", S_IRUSR);
np_state_write_string(current_time, "String to read");
*/
temp_state_key3._filename = "var/generated";
time(&current_time);
np_state_write_string(temp_state_key3, 0, "String to read");
temp_state_data = np_state_read(temp_state_key3);
test_state._filename = generated_dir_test_file_path;
np_state_write_string(test_state, 0, test_string);
np_state_read_wrapper recovered_state_1 = np_state_read(test_state);
ok(recovered_state_1.errorcode == 0, "recovered state succesfully");
/* Check time is set to current_time */
ok(system("cmp var/generated var/statefile > /dev/null") != 0,
"Generated file should be different this time");
ok(temp_state_data->time - current_time <= 1, "Has time generated from current time");
(void) asprintf(&cmp_execution_string, "cmp %s %s > /dev/null", generated_dir_test_file_path,
example_statefile_file_path);
ok(system(cmp_execution_string) != 0, "Generated file should be different this time");
time(&current_time);
ok(recovered_state_1.data.time - current_time <= 1, "Has time generated from current time");
/* Don't know how to automatically test this. Need to be able to redefine die and catch the
* error */
@ -174,6 +199,59 @@ int main(int argc, char **argv) {
temp_state_key->_filename="/dev/do/not/expect/to/be/able/to/write";
np_state_write_string(0, "Bad file");
*/
}
int main(int argc, char **argv) {
// Generate test directory
char *base_path = dirname(argv[0]);
const char test_dir_name[] = "/test";
char *test_dir_path = create_file_path(base_path, test_dir_name);
// remove test directory before using it
rmrf(test_dir_path);
int result_mkdir = mkdir(test_dir_path, 0700);
ok(result_mkdir == 0, "Generated test directory: %s", test_dir_path);
if (result_mkdir != 0) {
// failed to create test directory, rest is useless
diag("Failed to generate test directory. Aborting here. mkdir result was: %s",
strerror(errno));
exit(2);
}
test_key_generation(argc, argv);
test_state_location_prefix();
int fake_argc = 1;
const char *fake_argv[1] = {"fake_argv"};
const char *expected_plugin_name = "fake_pluginname";
const char *test_state_subpath = "/test_state";
char *test_state_path = create_file_path(test_dir_path, test_state_subpath);
setenv("MP_STATE_PATH", test_state_path, 1);
state_key temp_state =
np_enable_state("funnykeyname", 54, expected_plugin_name, fake_argc, fake_argv);
const char *test_string = "String to read";
np_state_write_string(temp_state, 1234567890, test_string);
ok(!strcmp(temp_state.plugin_name, expected_plugin_name), "Got plugin name: %s",
temp_state.plugin_name);
ok(!strcmp(temp_state.name, "funnykeyname"), "Got key name");
np_state_read_wrapper recoverd_state_data = np_state_read(temp_state);
ok(recoverd_state_data.errorcode == 0, "Retrieve state data from file '%s'",
temp_state._filename);
const char *example_dir = "/var/check_snmp";
char *example_path = create_file_path(base_path, example_dir);
test_read_state(temp_state, test_string, test_dir_path, example_path);
np_cleanup();
// remove test directory after using it
// rmrf(test_dir_path);
}

View file

@ -0,0 +1,5 @@
# NP State file
1
54
2147483647
Date in future!!!!

View file

@ -0,0 +1,4 @@
# NP State file
1
54
1234567890

View file

@ -0,0 +1,5 @@
# NP State file
0
54
1234567890
String to read

View file

@ -0,0 +1,5 @@
# NP State file
1
54
1234567890
String to read