master
c 550 lines 17.2 KB
Raw
1 #include "libnetdata/libnetdata.h"
2
3 #define ENV_VAR_KEY "SPAWN_TESTER"
4 #define ENV_VAR_VALUE "1234567890"
5
6 size_t warnings = 0;
7
8 void child_check_environment(void) {
9 const char *s = getenv(ENV_VAR_KEY);
10 if(!s || !*s || strcmp(s, ENV_VAR_VALUE) != 0) {
11 nd_log(NDLS_COLLECTORS, NDLP_ERR,
12 "Wrong environment. Variable '%s' should have value '%s' but it has '%s'",
13 ENV_VAR_KEY, ENV_VAR_VALUE, s ? s : "(unset)");
14
15 exit(1);
16 }
17 }
18
19 static bool is_valid_fd(int fd) {
20 errno_clear();
21 return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
22 }
23
24 void child_check_fds(void) {
25 for(int fd = 0; fd < 3; fd++) {
26 if(!is_valid_fd(fd)) {
27 nd_log(NDLS_COLLECTORS, NDLP_ERR,
28 "fd No %d should be a valid file descriptor - but it isn't.", fd);
29
30 exit(1);
31 }
32 }
33
34 for(int fd = 3; fd < /* os_get_fd_open_max() */ 1024; fd++) {
35 if(is_valid_fd(fd)) {
36 nd_log(NDLS_COLLECTORS, NDLP_ERR,
37 "fd No %d is a valid file descriptor - it shouldn't.", fd);
38
39 exit(1);
40 }
41 }
42
43 errno_clear();
44 }
45
46 // --------------------------------------------------------------------------------------------------------------------
47
48 static void test_int_fds_echo_loop(SPAWN_INSTANCE *si, const char *msg, size_t iterations) {
49 if(!msg || !*msg) return;
50
51 const size_t max_msg_len = (size_t)(SSIZE_MAX / 2);
52 size_t ulen = strnlen(msg, max_msg_len + 1);
53 if(unlikely(ulen > max_msg_len))
54 return;
55
56 ssize_t len = (ssize_t)ulen;
57 size_t buffer_size = ulen * 2;
58 CLEAN_CHAR_P *buffer = mallocz(buffer_size);
59
60 for(size_t j = 0; j < iterations; j++) {
61 fprintf(stderr, "-");
62 memset(buffer, 0, buffer_size);
63
64 ssize_t rc = write(spawn_server_instance_write_fd(si), msg, len);
65 if (rc != len) {
66 nd_log(NDLS_COLLECTORS, NDLP_ERR,
67 "Cannot write to plugin. Expected to write %zd bytes, wrote %zd bytes",
68 len, rc);
69 exit(1);
70 }
71
72 rc = read(spawn_server_instance_read_fd(si), buffer, buffer_size);
73 if (rc != len) {
74 nd_log(NDLS_COLLECTORS, NDLP_ERR,
75 "Cannot read from plugin. Expected to read %zd bytes, read %zd bytes",
76 len, rc);
77 exit(1);
78 }
79
80 if (memcmp(msg, buffer, len) != 0) {
81 nd_log(NDLS_COLLECTORS, NDLP_ERR,
82 "Read corrupted data. Expected '%s', Read '%s'",
83 msg, buffer);
84 exit(1);
85 }
86 }
87 fprintf(stderr, "\n");
88 }
89
90 static void test_popen_echo_loop(POPEN_INSTANCE *pi, const char *msg, size_t iterations) {
91 if(!msg || !*msg) return;
92
93 const size_t max_msg_len =
94 ((size_t)(INT_MAX / 2) < (size_t)(SSIZE_MAX / 2)) ? (size_t)(INT_MAX / 2) : (size_t)(SSIZE_MAX / 2);
95 size_t len = strnlen(msg, max_msg_len + 1);
96 if(unlikely(len > max_msg_len))
97 return;
98
99 size_t buffer_size = len * 2;
100 CLEAN_CHAR_P *buffer = mallocz(buffer_size);
101
102 for(size_t j = 0; j < iterations; j++) {
103 fprintf(stderr, "-");
104 memset(buffer, 0, buffer_size);
105
106 size_t rc = fwrite(msg, 1, len, spawn_popen_stdin(pi));
107 if (rc != len) {
108 nd_log(NDLS_COLLECTORS, NDLP_ERR,
109 "Cannot write to plugin. Expected to write %zu bytes, wrote %zu bytes",
110 len, rc);
111 exit(1);
112 }
113 fflush(spawn_popen_stdin(pi));
114
115 char *s = fgets(buffer, (int)buffer_size, spawn_popen_stdout(pi));
116 if (!s || strlen(s) != len) {
117 nd_log(NDLS_COLLECTORS, NDLP_ERR,
118 "Cannot read from plugin. Expected to read %zu bytes, read %zu bytes",
119 len, (size_t)(s ? strlen(s) : 0));
120 exit(1);
121 }
122 if (memcmp(msg, buffer, len) != 0) {
123 nd_log(NDLS_COLLECTORS, NDLP_ERR,
124 "Read corrupted data. Expected '%s', Read '%s'",
125 msg, buffer);
126 exit(1);
127 }
128 }
129 fprintf(stderr, "\n");
130 }
131
132 // --------------------------------------------------------------------------------------------------------------------
133 // kill to stop
134
135 int plugin_kill_to_stop() {
136 child_check_fds();
137 child_check_environment();
138
139 char buffer[1024];
140 while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
141 fprintf(stderr, "+");
142 printf("%s", buffer);
143 fflush(stdout);
144 }
145
146 return 0;
147 }
148
149 void test_int_fds_plugin_kill_to_stop(SPAWN_SERVER *server, const char *argv0) {
150 const char *params[] = {
151 argv0,
152 "plugin-kill-to-stop",
153 NULL,
154 };
155
156 SPAWN_INSTANCE *si = spawn_server_exec(server, STDERR_FILENO, 0, params, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
157 if(!si) {
158 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (spawn)");
159 exit(1);
160 }
161
162 test_int_fds_echo_loop(si, "Hello World!\n", 30);
163
164 int code = spawn_server_exec_kill(server, si, 0);
165
166 nd_log(NDLS_COLLECTORS, NDLP_ERR,
167 "child exited with code %d",
168 code);
169
170 if(code != 15 && code != 0) {
171 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0 or 15, but exited with code %d", code);
172 warnings++;
173 }
174 }
175
176 void test_popen_plugin_kill_to_stop(const char *argv0) {
177 char cmd[FILENAME_MAX + 100];
178 snprintfz(cmd, sizeof(cmd), "exec %s plugin-kill-to-stop", argv0);
179 POPEN_INSTANCE *pi = spawn_popen_run(cmd);
180 if(!pi) {
181 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
182 exit(1);
183 }
184
185 test_popen_echo_loop(pi, "Hello World!\n", 30);
186
187 int code = spawn_popen_kill(pi, 0);
188
189 nd_log(NDLS_COLLECTORS, NDLP_ERR,
190 "child exited with code %d",
191 code);
192
193 if(code != 0) {
194 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0, but exited with code %d", code);
195 warnings++;
196 }
197 }
198
199 // --------------------------------------------------------------------------------------------------------------------
200 // close to stop
201
202 int plugin_close_to_stop() {
203 child_check_fds();
204 child_check_environment();
205
206 char buffer[1024];
207 while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
208 fprintf(stderr, "+");
209 printf("%s", buffer);
210 fflush(stdout);
211 }
212
213 nd_log(NDLS_COLLECTORS, NDLP_ERR, "child detected a closed pipe.");
214 exit(1);
215 }
216
217 void test_int_fds_plugin_close_to_stop(SPAWN_SERVER *server, const char *argv0) {
218 const char *params[] = {
219 argv0,
220 "plugin-close-to-stop",
221 NULL,
222 };
223
224 SPAWN_INSTANCE *si = spawn_server_exec(server, STDERR_FILENO, 0, params, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
225 if(!si) {
226 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (spawn)");
227 exit(1);
228 }
229
230 test_int_fds_echo_loop(si, "Hello World!\n", 1);
231
232 int code = spawn_server_exec_wait(server, si);
233
234 nd_log(NDLS_COLLECTORS, NDLP_ERR,
235 "child exited with code %d",
236 code);
237
238 if(!WIFEXITED(code) || WEXITSTATUS(code) != 1) {
239 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 1, but exited with code %d", code);
240 warnings++;
241 }
242 }
243
244 void test_popen_plugin_close_to_stop(const char *argv0) {
245 char cmd[FILENAME_MAX + 100];
246 snprintfz(cmd, sizeof(cmd), "exec %s plugin-close-to-stop", argv0);
247 POPEN_INSTANCE *pi = spawn_popen_run(cmd);
248 if(!pi) {
249 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
250 exit(1);
251 }
252
253 test_popen_echo_loop(pi, "Hello World!\n", 1);
254
255 int code = spawn_popen_wait(pi);
256
257 nd_log(NDLS_COLLECTORS, NDLP_ERR,
258 "child exited with code %d",
259 code);
260
261 if(code != 1) {
262 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 1, but exited with code %d", code);
263 warnings++;
264 }
265 }
266
267 // --------------------------------------------------------------------------------------------------------------------
268 // echo and exit
269
270 #define ECHO_AND_EXIT_MSG "GOODBYE\n"
271
272 int plugin_echo_and_exit() {
273 child_check_fds();
274 child_check_environment();
275
276 printf(ECHO_AND_EXIT_MSG);
277 exit(0);
278 }
279
280 void test_int_fds_plugin_echo_and_exit(SPAWN_SERVER *server, const char *argv0) {
281 const char *params[] = {
282 argv0,
283 "plugin-echo-and-exit",
284 NULL,
285 };
286
287 SPAWN_INSTANCE *si = spawn_server_exec(server, STDERR_FILENO, 0, params, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
288 if(!si) {
289 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (spawn)");
290 exit(1);
291 }
292
293 char buffer[1024];
294 size_t reads = 0;
295
296 for(size_t j = 0; j < 30 ;j++) {
297 fprintf(stderr, "-");
298 memset(buffer, 0, sizeof(buffer));
299
300 ssize_t rc = read(spawn_server_instance_read_fd(si), buffer, sizeof(buffer));
301 if(rc <= 0)
302 break;
303
304 reads++;
305
306 if (rc != strlen(ECHO_AND_EXIT_MSG)) {
307 nd_log(NDLS_COLLECTORS, NDLP_ERR,
308 "Cannot read from plugin. Expected to read %zu bytes, read %zd bytes",
309 strlen(ECHO_AND_EXIT_MSG), rc);
310 exit(1);
311 }
312 if (memcmp(ECHO_AND_EXIT_MSG, buffer, strlen(ECHO_AND_EXIT_MSG)) != 0) {
313 nd_log(NDLS_COLLECTORS, NDLP_ERR,
314 "Read corrupted data. Expected '%s', Read '%s'",
315 ECHO_AND_EXIT_MSG, buffer);
316 exit(1);
317 }
318 }
319 fprintf(stderr, "\n");
320
321 if(reads != 1) {
322 nd_log(NDLS_COLLECTORS, NDLP_ERR,
323 "Cannot read from plugin. Expected to read %d times, but read %zu",
324 1, reads);
325 exit(1);
326 }
327
328 int code = spawn_server_exec_wait(server, si);
329
330 nd_log(NDLS_COLLECTORS, NDLP_ERR,
331 "child exited with code %d",
332 code);
333
334 if(code != 0) {
335 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0, but exited with code %d", code);
336 warnings++;
337 }
338 }
339
340 void test_popen_plugin_echo_and_exit(const char *argv0) {
341 char cmd[FILENAME_MAX + 100];
342 snprintfz(cmd, sizeof(cmd), "exec %s plugin-echo-and-exit", argv0);
343 POPEN_INSTANCE *pi = spawn_popen_run(cmd);
344 if(!pi) {
345 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
346 exit(1);
347 }
348
349 char buffer[1024];
350 size_t reads = 0;
351 for(size_t j = 0; j < 30 ;j++) {
352 fprintf(stderr, "-");
353 memset(buffer, 0, sizeof(buffer));
354
355 char *s = fgets(buffer, sizeof(buffer), spawn_popen_stdout(pi));
356 if(!s) break;
357 reads++;
358 if (strlen(s) != strlen(ECHO_AND_EXIT_MSG)) {
359 nd_log(NDLS_COLLECTORS, NDLP_ERR,
360 "Cannot read from plugin. Expected to read %zu bytes, read %zu bytes",
361 strlen(ECHO_AND_EXIT_MSG), (size_t)(s ? strlen(s) : 0));
362 exit(1);
363 }
364 if (memcmp(ECHO_AND_EXIT_MSG, buffer, strlen(ECHO_AND_EXIT_MSG)) != 0) {
365 nd_log(NDLS_COLLECTORS, NDLP_ERR,
366 "Read corrupted data. Expected '%s', Read '%s'",
367 ECHO_AND_EXIT_MSG, buffer);
368 exit(1);
369 }
370 }
371 fprintf(stderr, "\n");
372
373 if(reads != 1) {
374 nd_log(NDLS_COLLECTORS, NDLP_ERR,
375 "Cannot read from plugin. Expected to read %d times, but read %zu",
376 1, reads);
377 exit(1);
378 }
379
380 int code = spawn_popen_wait(pi);
381
382 nd_log(NDLS_COLLECTORS, NDLP_ERR,
383 "child exited with code %d",
384 code);
385
386 if(code != 0) {
387 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0, but exited with code %d", code);
388 warnings++;
389 }
390 }
391
392 // --------------------------------------------------------------------------------------------------------------------
393 // timed wait
394
395 int plugin_sleep_to_stop(void) {
396 child_check_fds();
397 child_check_environment();
398
399 // ignore the pipes - only a kill can stop us within the test's lifetime
400 sleep_usec(3600 * USEC_PER_SEC);
401 return 0;
402 }
403
404 void test_popen_plugin_timedwait_exits(const char *argv0) {
405 // a child that exits on its own must be reaped by spawn_popen_timedwait()
406 char cmd[FILENAME_MAX + 100];
407 snprintfz(cmd, sizeof(cmd), "exec %s plugin-echo-and-exit", argv0);
408 POPEN_INSTANCE *pi = spawn_popen_run(cmd);
409 if(!pi) {
410 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
411 exit(1);
412 }
413
414 int code = -1;
415 size_t slices = 0;
416 for(;;) {
417 SPAWN_TIMEDWAIT_RESULT r = spawn_popen_timedwait(pi, 100, &code);
418 if(r == SPAWN_TIMEDWAIT_EXITED)
419 break;
420
421 if(r == SPAWN_TIMEDWAIT_ERROR) {
422 // ERROR must never be looped over; for a cleanly-exiting child it should not happen at all.
423 // pi is still owned on ERROR, so reclaim it before bailing out.
424 nd_log(NDLS_COLLECTORS, NDLP_ERR, "spawn_popen_timedwait() returned ERROR for a child that should exit cleanly");
425 spawn_popen_kill(pi, 0);
426 exit(1);
427 }
428
429 // SPAWN_TIMEDWAIT_RUNNING - pi is still owned
430 if(++slices > 100) {
431 nd_log(NDLS_COLLECTORS, NDLP_ERR, "spawn_popen_timedwait() did not reap a child that exits immediately");
432 spawn_popen_kill(pi, 0);
433 exit(1);
434 }
435 }
436
437 nd_log(NDLS_COLLECTORS, NDLP_ERR,
438 "child exited with code %d (after %zu timedwait slices)",
439 code, slices);
440
441 if(code != 0) {
442 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0, but exited with code %d", code);
443 warnings++;
444 }
445 }
446
447 void test_popen_plugin_timedwait_kill(const char *argv0) {
448 // a child that never exits must be reported still-running on every slice, then killed
449 char cmd[FILENAME_MAX + 100];
450 snprintfz(cmd, sizeof(cmd), "exec %s plugin-sleep-to-stop", argv0);
451 POPEN_INSTANCE *pi = spawn_popen_run(cmd);
452 if(!pi) {
453 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
454 exit(1);
455 }
456
457 int code = 0;
458 for(size_t i = 0; i < 5; i++) {
459 SPAWN_TIMEDWAIT_RESULT r = spawn_popen_timedwait(pi, 200, &code);
460 if(r != SPAWN_TIMEDWAIT_RUNNING) {
461 nd_log(NDLS_COLLECTORS, NDLP_ERR,
462 "spawn_popen_timedwait() did not report RUNNING for a sleeping child");
463 // on ERROR we still own pi and must reclaim it; on EXITED it was already freed
464 if(r == SPAWN_TIMEDWAIT_ERROR) spawn_popen_kill(pi, 0);
465 exit(1);
466 }
467 }
468
469 code = spawn_popen_kill(pi, 0);
470
471 nd_log(NDLS_COLLECTORS, NDLP_ERR,
472 "child killed, exited with code %d",
473 code);
474
475 if(code != 0) {
476 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "killed child should report code 0, but reported code %d", code);
477 warnings++;
478 }
479 }
480
481 // --------------------------------------------------------------------------------------------------------------------
482
483 int main(int argc, const char **argv) {
484 if(argc > 1 && strcmp(argv[1], "plugin-kill-to-stop") == 0)
485 return plugin_kill_to_stop();
486
487 if(argc > 1 && strcmp(argv[1], "plugin-sleep-to-stop") == 0)
488 return plugin_sleep_to_stop();
489
490 if(argc > 1 && strcmp(argv[1], "plugin-echo-and-exit") == 0)
491 return plugin_echo_and_exit();
492
493 if(argc > 1 && strcmp(argv[1], "plugin-close-to-stop") == 0)
494 return plugin_close_to_stop();
495
496 if(argc <= 1 || strcmp(argv[1], "test") != 0) {
497 fprintf(stderr, "Run me with 'test' parameter!\n");
498 exit(1);
499 }
500
501 nd_setenv(ENV_VAR_KEY, ENV_VAR_VALUE, 1);
502
503 fprintf(stderr, "\n\nTESTING fds\n\n");
504 SPAWN_SERVER *server = spawn_server_create(SPAWN_SERVER_OPTION_EXEC, "test", NULL, argc, argv);
505 if(!server) {
506 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot create spawn server");
507 exit(1);
508 }
509 for(size_t i = 0; i < 5; i++) {
510 fprintf(stderr, "\n\nTESTING fds No %zu (kill to stop)\n\n", i + 1);
511 test_int_fds_plugin_kill_to_stop(server, argv[0]);
512 }
513 for(size_t i = 0; i < 5; i++) {
514 fprintf(stderr, "\n\nTESTING fds No %zu (echo and exit)\n\n", i + 1);
515 test_int_fds_plugin_echo_and_exit(server, argv[0]);
516 }
517 for(size_t i = 0; i < 5; i++) {
518 fprintf(stderr, "\n\nTESTING fds No %zu (close to stop)\n\n", i + 1);
519 test_int_fds_plugin_close_to_stop(server, argv[0]);
520 }
521 spawn_server_destroy(server);
522
523 fprintf(stderr, "\n\nTESTING popen\n\n");
524 netdata_main_spawn_server_init("test", argc, argv);
525 for(size_t i = 0; i < 5; i++) {
526 fprintf(stderr, "\n\nTESTING popen No %zu (kill to stop)\n\n", i + 1);
527 test_popen_plugin_kill_to_stop(argv[0]);
528 }
529 for(size_t i = 0; i < 5; i++) {
530 fprintf(stderr, "\n\nTESTING popen No %zu (echo and exit)\n\n", i + 1);
531 test_popen_plugin_echo_and_exit(argv[0]);
532 }
533 for(size_t i = 0; i < 5; i++) {
534 fprintf(stderr, "\n\nTESTING popen No %zu (close to stop)\n\n", i + 1);
535 test_popen_plugin_close_to_stop(argv[0]);
536 }
537 for(size_t i = 0; i < 5; i++) {
538 fprintf(stderr, "\n\nTESTING popen No %zu (timedwait exits)\n\n", i + 1);
539 test_popen_plugin_timedwait_exits(argv[0]);
540 }
541 for(size_t i = 0; i < 5; i++) {
542 fprintf(stderr, "\n\nTESTING popen No %zu (timedwait kill)\n\n", i + 1);
543 test_popen_plugin_timedwait_kill(argv[0]);
544 }
545 netdata_main_spawn_server_cleanup();
546
547 fprintf(stderr, "\n\nTests passed! (%zu warnings)\n\n", warnings);
548
549 exit(0);
550 }