testing_api_cmd_system_start.c (9983B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it 6 under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file testing/testing_api_cmd_system_start.c 21 * @brief run taler-benchmark-setup.sh command 22 * @author Christian Grothoff 23 */ 24 #include "platform.h" /* UNNECESSARY? */ 25 #include "taler/taler_json_lib.h" 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "taler/taler_signatures.h" /* UNNECESSARY? */ 28 #include "taler/taler_testing_lib.h" 29 30 31 /** 32 * State for a "system" CMD. 33 */ 34 struct SystemState 35 { 36 37 /** 38 * System process. 39 */ 40 struct GNUNET_Process *system_proc; 41 42 /** 43 * Input pipe to @e system_proc, used to keep the 44 * process alive until we are done. 45 */ 46 struct GNUNET_DISK_PipeHandle *pipe_in; 47 48 /** 49 * Output pipe to @e system_proc, used to find out 50 * when the services are ready. 51 */ 52 struct GNUNET_DISK_PipeHandle *pipe_out; 53 54 /** 55 * Task reading from @e pipe_in. 56 */ 57 struct GNUNET_SCHEDULER_Task *reader; 58 59 /** 60 * Waiting for child to die. 61 */ 62 struct GNUNET_ChildWaitHandle *cwh; 63 64 /** 65 * Our interpreter state. 66 */ 67 struct TALER_TESTING_Interpreter *is; 68 69 /** 70 * NULL-terminated array of command-line arguments. 71 */ 72 char **args; 73 74 /** 75 * Input buffer for the stdin of the test setup helper. 76 */ 77 struct GNUNET_Buffer ibuf; 78 79 /** 80 * Did we find the ready tag? 81 */ 82 bool ready; 83 84 /** 85 * Is the child process still running? 86 */ 87 bool active; 88 }; 89 90 91 /** 92 * Defines a GNUNET_ChildCompletedCallback which is sent back 93 * upon death or completion of a child process. 94 * 95 * @param cls our `struct SystemState *` 96 * @param type type of the process 97 * @param exit_code status code of the process 98 */ 99 static void 100 setup_terminated (void *cls, 101 enum GNUNET_OS_ProcessStatusType type, 102 long unsigned int exit_code) 103 { 104 struct SystemState *as = cls; 105 106 as->cwh = NULL; 107 as->active = false; 108 if (NULL != as->reader) 109 { 110 GNUNET_SCHEDULER_cancel (as->reader); 111 as->reader = NULL; 112 } 113 if (! as->ready) 114 { 115 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 116 "Launching Taler system failed: %d/%llu\n", 117 (int) type, 118 (unsigned long long) exit_code); 119 TALER_TESTING_interpreter_fail (as->is); 120 return; 121 } 122 } 123 124 125 /** 126 * Start helper to read from stdout of child. 127 * 128 * @param as our system state 129 */ 130 static void 131 start_reader (struct SystemState *as); 132 133 #define READY_MARKER "READY:" 134 135 static void 136 read_stdout (void *cls) 137 { 138 struct SystemState *as = cls; 139 const struct GNUNET_DISK_FileHandle *fh; 140 char buf[1024]; 141 ssize_t ret; 142 size_t off = 0; 143 char *testroot = NULL; 144 145 as->reader = NULL; 146 fh = GNUNET_DISK_pipe_handle (as->pipe_out, 147 GNUNET_DISK_PIPE_END_READ); 148 ret = GNUNET_DISK_file_read (fh, 149 buf, 150 sizeof (buf) - 1); 151 if (-1 == ret) 152 { 153 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 154 "read"); 155 TALER_TESTING_interpreter_fail (as->is); 156 return; 157 } 158 if (0 == ret) 159 { 160 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 161 "Child closed stdout\n"); 162 return; 163 } 164 GNUNET_buffer_write (&as->ibuf, buf, ret); 165 if ( (0 == strncmp (as->ibuf.mem, 166 READY_MARKER, 167 strlen (READY_MARKER))) && 168 (NULL != (testroot = strchr (as->ibuf.mem, 169 '\n'))) ) 170 { 171 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 172 "Got test root %s\n", 173 testroot); 174 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 175 "Taler system UP\n"); 176 as->ready = true; 177 TALER_TESTING_interpreter_next (as->is); 178 return; 179 } 180 if (NULL != strchr (as->ibuf.mem, 181 '\n') || 182 as->ibuf.position > 4096) 183 { 184 TALER_TESTING_interpreter_fail (as->is); 185 /* Only commands are allowed! */ 186 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 187 "Unexpected stdout of test setup helper: %.*s\n", 188 (int) ret, 189 &buf[off]); 190 } 191 192 start_reader (as); 193 } 194 195 196 static void 197 start_reader (struct SystemState *as) 198 { 199 const struct GNUNET_DISK_FileHandle *fh; 200 201 GNUNET_assert (NULL == as->reader); 202 fh = GNUNET_DISK_pipe_handle (as->pipe_out, 203 GNUNET_DISK_PIPE_END_READ); 204 as->reader = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, 205 fh, 206 &read_stdout, 207 as); 208 } 209 210 211 /** 212 * Run the command. Use the `taler-unified-setup.sh` program. 213 * 214 * @param cls closure. 215 * @param cmd command being run. 216 * @param is interpreter state. 217 */ 218 static void 219 system_run (void *cls, 220 const struct TALER_TESTING_Command *cmd, 221 struct TALER_TESTING_Interpreter *is) 222 { 223 struct SystemState *as = cls; 224 225 (void) cmd; 226 as->is = is; 227 as->pipe_in = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ); 228 GNUNET_assert (NULL != as->pipe_in); 229 as->pipe_out = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE); 230 GNUNET_assert (NULL != as->pipe_out); 231 as->system_proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 232 GNUNET_assert (GNUNET_OK == 233 GNUNET_process_set_options ( 234 as->system_proc, 235 GNUNET_process_option_inherit_rpipe (as->pipe_in, 236 STDIN_FILENO), 237 GNUNET_process_option_inherit_wpipe (as->pipe_out, 238 STDOUT_FILENO))); 239 if (GNUNET_OK != 240 GNUNET_process_run_command_argv (as->system_proc, 241 "taler-unified-setup.sh", 242 (const char **) as->args)) 243 { 244 GNUNET_break (0); 245 GNUNET_process_destroy (as->system_proc); 246 as->system_proc = NULL; 247 TALER_TESTING_interpreter_fail (is); 248 return; 249 } 250 as->active = true; 251 start_reader (as); 252 as->cwh = GNUNET_wait_child (as->system_proc, 253 &setup_terminated, 254 as); 255 } 256 257 258 /** 259 * Free the state of a "system" CMD, and possibly kill its 260 * process if it did not terminate correctly. 261 * 262 * @param cls closure. 263 * @param cmd the command being freed. 264 */ 265 static void 266 system_cleanup (void *cls, 267 const struct TALER_TESTING_Command *cmd) 268 { 269 struct SystemState *as = cls; 270 271 (void) cmd; 272 if (NULL != as->cwh) 273 { 274 GNUNET_wait_child_cancel (as->cwh); 275 as->cwh = NULL; 276 } 277 if (NULL != as->reader) 278 { 279 GNUNET_SCHEDULER_cancel (as->reader); 280 as->reader = NULL; 281 } 282 GNUNET_buffer_clear (&as->ibuf); 283 if (NULL != as->system_proc) 284 { 285 if (as->active) 286 { 287 GNUNET_break (GNUNET_OK == 288 GNUNET_process_kill (as->system_proc, 289 SIGTERM)); 290 GNUNET_process_wait (as->system_proc, 291 true, 292 NULL, 293 NULL); 294 } 295 GNUNET_process_destroy (as->system_proc); 296 as->system_proc = NULL; 297 } 298 if (NULL != as->pipe_in) 299 { 300 GNUNET_break (GNUNET_OK == 301 GNUNET_DISK_pipe_close (as->pipe_in)); 302 as->pipe_in = NULL; 303 } 304 if (NULL != as->pipe_out) 305 { 306 GNUNET_break (GNUNET_OK == 307 GNUNET_DISK_pipe_close (as->pipe_out)); 308 as->pipe_out = NULL; 309 } 310 311 for (unsigned int i = 0; NULL != as->args[i]; i++) 312 GNUNET_free (as->args[i]); 313 GNUNET_free (as->args); 314 GNUNET_free (as); 315 } 316 317 318 /** 319 * Offer "system" CMD internal data to other commands. 320 * 321 * @param cls closure. 322 * @param[out] ret result. 323 * @param trait name of the trait. 324 * @param index index number of the object to offer. 325 * @return #GNUNET_OK on success 326 */ 327 static enum GNUNET_GenericReturnValue 328 system_traits (void *cls, 329 const void **ret, 330 const char *trait, 331 unsigned int index) 332 { 333 struct SystemState *as = cls; 334 struct TALER_TESTING_Trait traits[] = { 335 TALER_TESTING_make_trait_process (&as->system_proc), 336 TALER_TESTING_trait_end () 337 }; 338 339 return TALER_TESTING_get_trait (traits, 340 ret, 341 trait, 342 index); 343 } 344 345 346 struct TALER_TESTING_Command 347 TALER_TESTING_cmd_system_start ( 348 const char *label, 349 const char *config_file, 350 ...) 351 { 352 struct SystemState *as; 353 va_list ap; 354 const char *arg; 355 unsigned int cnt; 356 357 as = GNUNET_new (struct SystemState); 358 cnt = 4; /* 0-2 reserved, +1 for NULL termination */ 359 va_start (ap, 360 config_file); 361 while (NULL != (arg = va_arg (ap, 362 const char *))) 363 { 364 cnt++; 365 } 366 va_end (ap); 367 as->args = GNUNET_new_array (cnt, 368 char *); 369 as->args[0] = GNUNET_strdup ("taler-unified-setup"); 370 as->args[1] = GNUNET_strdup ("-c"); 371 as->args[2] = GNUNET_strdup (config_file); 372 cnt = 3; 373 va_start (ap, 374 config_file); 375 while (NULL != (arg = va_arg (ap, 376 const char *))) 377 { 378 as->args[cnt++] = GNUNET_strdup (arg); 379 } 380 va_end (ap); 381 382 { 383 struct TALER_TESTING_Command cmd = { 384 .cls = as, 385 .label = label, 386 .run = &system_run, 387 .cleanup = &system_cleanup, 388 .traits = &system_traits 389 }; 390 391 return cmd; 392 } 393 } 394 395 396 /* end of testing_api_cmd_system_start.c */