1 /*
2 * Copyright 2012-2026 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <glib.h>
13 #include <stdbool.h>
14 #include <unistd.h>
15
16 #include <crm/crm.h>
17 #include <crm/services.h>
18 #include <crm/common/mainloop.h>
19
20 #include <crm/pengine/status.h>
21 #include <crm/pengine/internal.h>
22 #include <crm/cib.h>
23 #include <crm/cib/internal.h>
24 #include <crm/lrmd.h>
25
26 #define SUMMARY "cts-exec-helper - inject commands into the Pacemaker executor and watch for events"
27
28 static int exec_call_id = 0;
29 static gboolean start_test(gpointer user_data);
30 static void try_connect(void);
31
32 static char *key = NULL;
33 static char *val = NULL;
34
35 static struct {
36 int verbose;
37 int quiet;
38 guint interval_ms;
39 int timeout;
40 int start_delay;
41 int cancel_call_id;
42 gboolean no_wait;
43 gboolean is_running;
44 gboolean no_connect;
45 int exec_call_opts;
46 const char *api_call;
47 const char *rsc_id;
48 const char *provider;
49 const char *class;
50 const char *type;
51 const char *action;
52 const char *listen;
53 gboolean use_tls;
54 lrmd_key_value_t *params;
55 } options;
56
57 static gboolean
58 interval_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
59 return pcmk_parse_interval_spec(optarg,
60 &options.interval_ms) == pcmk_rc_ok;
61 }
62
63 static gboolean
64 notify_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
65 if (pcmk__str_any_of(option_name, "--notify-orig", "-n", NULL)) {
66 options.exec_call_opts = lrmd_opt_notify_orig_only;
67 } else if (pcmk__str_any_of(option_name, "--notify-changes", "-o", NULL)) {
68 options.exec_call_opts = lrmd_opt_notify_changes_only;
69 }
70
71 return TRUE;
72 }
73
74 static gboolean
75 param_key_val_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
|
(1) Event path: |
Condition "pcmk__str_any_of(option_name, "--param-key", "-k", NULL)", taking false branch. |
76 if (pcmk__str_any_of(option_name, "--param-key", "-k", NULL)) {
77 pcmk__str_update(&key, optarg);
|
(2) Event path: |
Condition "pcmk__str_any_of(option_name, "--param-val", "-v", NULL)", taking false branch. |
78 } else if (pcmk__str_any_of(option_name, "--param-val", "-v", NULL)) {
79 pcmk__str_update(&val, optarg);
80 }
81
|
(3) Event path: |
Condition "key != NULL", taking true branch. |
|
(4) Event path: |
Condition "val != NULL", taking true branch. |
82 if (key != NULL && val != NULL) {
83 options.params = lrmd_key_value_add(options.params, key, val);
|
(5) Event path: |
Condition "_p", taking true branch. |
84 g_clear_pointer(&key, free);
|
CID (unavailable; MK=70c090167e2571910bed83b837b87b7e) (#2 of 2): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(6) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(7) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
85 g_clear_pointer(&val, free);
86 }
87
88 return TRUE;
89 }
90
91 static GOptionEntry basic_entries[] = {
92 { "api-call", 'c', 0, G_OPTION_ARG_STRING, &options.api_call,
93 "Directly relates to executor API functions",
94 NULL },
95
96 { "is-running", 'R', 0, G_OPTION_ARG_NONE, &options.is_running,
97 "Determine if a resource is registered and running",
98 NULL },
99
100 { "listen", 'l', 0, G_OPTION_ARG_STRING, &options.listen,
101 "Listen for a specific event string",
102 NULL },
103
104 { "no-wait", 'w', 0, G_OPTION_ARG_NONE, &options.no_wait,
105 "Make api call and do not wait for result",
106 NULL },
107
108 { "notify-changes", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, notify_cb,
109 "Only notify client changes to recurring operations",
110 NULL },
111
112 { "notify-orig", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, notify_cb,
113 "Only notify this client of the results of an API action",
114 NULL },
115
116 { "tls", 'S', 0, G_OPTION_ARG_NONE, &options.use_tls,
117 "Use TLS backend for local connection",
118 NULL },
119
120 { NULL }
121 };
122
123 static GOptionEntry api_call_entries[] = {
124 { "action", 'a', 0, G_OPTION_ARG_STRING, &options.action,
125 NULL, NULL },
126
127 { "cancel-call-id", 'x', 0, G_OPTION_ARG_INT, &options.cancel_call_id,
128 NULL, NULL },
129
130 { "class", 'C', 0, G_OPTION_ARG_STRING, &options.class,
131 NULL, NULL },
132
133 { "interval", 'i', 0, G_OPTION_ARG_CALLBACK, interval_cb,
134 NULL, NULL },
135
136 { "param-key", 'k', 0, G_OPTION_ARG_CALLBACK, param_key_val_cb,
137 NULL, NULL },
138
139 { "param-val", 'v', 0, G_OPTION_ARG_CALLBACK, param_key_val_cb,
140 NULL, NULL },
141
142 { "provider", 'P', 0, G_OPTION_ARG_STRING, &options.provider,
143 NULL, NULL },
144
145 { "rsc-id", 'r', 0, G_OPTION_ARG_STRING, &options.rsc_id,
146 NULL, NULL },
147
148 { "start-delay", 's', 0, G_OPTION_ARG_INT, &options.start_delay,
149 NULL, NULL },
150
151 { "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout,
152 NULL, NULL },
153
154 { "type", 'T', 0, G_OPTION_ARG_STRING, &options.type,
155 NULL, NULL },
156
157 { NULL }
158 };
159
160 static GMainLoop *mainloop = NULL;
161 static lrmd_t *lrmd_conn = NULL;
162
163 static crm_exit_t
164 test_exit(crm_exit_t exit_code)
165 {
166 lrmd_api_delete(lrmd_conn);
167 return crm_exit(exit_code);
168 }
169
170 #define print_result(fmt, args...) \
171 if (!options.quiet) { \
172 printf(fmt "\n", ##args); \
173 }
174
175 static void
176 test_shutdown(int nsig)
177 {
178 g_clear_pointer(&lrmd_conn, lrmd_api_delete);
179 }
180
181 static void
182 read_events(lrmd_event_data_t * event)
183 {
184 char buf[1024] = { '\0', };
185
186 pcmk__assert(snprintf(buf, sizeof(buf),
187 "NEW_EVENT event_type:%s rsc_id:%s action:%s rc:%s "
188 "op_status:%s",
189 lrmd_event_type2str(event->type), event->rsc_id,
190 pcmk__s(event->op_type, "none"),
191 crm_exit_str((crm_exit_t) event->rc),
192 pcmk_exec_status_str(event->op_status)) >= 0);
193 pcmk__info("%s", buf);
194
195 if (options.listen && pcmk__str_eq(options.listen, buf, pcmk__str_casei)) {
196 print_result("LISTEN EVENT SUCCESSFUL");
197 test_exit(CRM_EX_OK);
198 }
199
200 if (exec_call_id && (event->call_id == exec_call_id)) {
201 if (event->op_status == 0 && event->rc == 0) {
202 print_result("API-CALL SUCCESSFUL for 'exec'");
203 } else {
204 print_result("API-CALL FAILURE for 'exec', rc:%d lrmd_op_status:%s",
205 event->rc, pcmk_exec_status_str(event->op_status));
206 test_exit(CRM_EX_ERROR);
207 }
208
209 if (!options.listen) {
210 test_exit(CRM_EX_OK);
211 }
212 }
213 }
214
215 static gboolean
216 timeout_err(gpointer data)
217 {
218 print_result("LISTEN EVENT FAILURE - timeout occurred, never found");
219 test_exit(CRM_EX_TIMEOUT);
220 return FALSE;
221 }
222
223 static void
224 connection_events(lrmd_event_data_t * event)
225 {
226 int rc = event->connection_rc;
227
228 if (event->type != lrmd_event_connect) {
229 /* ignore */
230 return;
231 }
232
233 if (!rc) {
234 pcmk__info("Executor client connection established");
235 start_test(NULL);
236 return;
237 } else {
238 sleep(1);
239 try_connect();
240 pcmk__notice("Executor client connection failed");
241 }
242 }
243
244 static void
245 try_connect(void)
246 {
247 int tries = 10;
248 static int num_tries = 0;
249 int rc = 0;
250
251 lrmd_conn->cmds->set_callback(lrmd_conn, connection_events);
252 for (; num_tries < tries; num_tries++) {
253 rc = lrmd_conn->cmds->connect_async(lrmd_conn, crm_system_name, 3000);
254
255 if (!rc) {
256 return; /* we'll hear back in async callback */
257 }
258 sleep(1);
259 }
260
261 print_result("API CONNECTION FAILURE");
262 test_exit(CRM_EX_ERROR);
263 }
264
265 static gboolean
266 start_test(gpointer user_data)
267 {
268 int rc = 0;
269
270 if (!options.no_connect) {
271 if (!lrmd_conn->cmds->is_connected(lrmd_conn)) {
272 try_connect();
273 /* async connect -- this function will get called back into */
274 return 0;
275 }
276 }
277 lrmd_conn->cmds->set_callback(lrmd_conn, read_events);
278
279 if (options.timeout) {
280 pcmk__create_timer(options.timeout, timeout_err, NULL);
281 }
282
283 if (!options.api_call) {
284 return 0;
285 }
286
287 if (pcmk__str_eq(options.api_call, "exec", pcmk__str_casei)) {
288 rc = lrmd_conn->cmds->exec(lrmd_conn,
289 options.rsc_id,
290 options.action,
291 NULL,
292 options.interval_ms,
293 options.timeout,
294 options.start_delay,
295 options.exec_call_opts,
296 options.params);
297
298 if (rc > 0) {
299 exec_call_id = rc;
300 print_result("API-CALL 'exec' action pending, waiting on response");
301 }
302
303 } else if (pcmk__str_eq(options.api_call, "register_rsc", pcmk__str_casei)) {
304 rc = lrmd_conn->cmds->register_rsc(lrmd_conn,
305 options.rsc_id,
306 options.class, options.provider, options.type, 0);
307 } else if (pcmk__str_eq(options.api_call, "get_rsc_info", pcmk__str_casei)) {
308 lrmd_rsc_info_t *rsc_info;
309
310 rsc_info = lrmd_conn->cmds->get_rsc_info(lrmd_conn, options.rsc_id, 0);
311
312 if (rsc_info) {
313 print_result("RSC_INFO: id:%s class:%s provider:%s type:%s",
314 rsc_info->id, rsc_info->standard,
315 (rsc_info->provider? rsc_info->provider : "<none>"),
316 rsc_info->type);
317 lrmd_free_rsc_info(rsc_info);
318 rc = pcmk_ok;
319 } else {
320 rc = -1;
321 }
322 } else if (pcmk__str_eq(options.api_call, "unregister_rsc", pcmk__str_casei)) {
323 rc = lrmd_conn->cmds->unregister_rsc(lrmd_conn, options.rsc_id, 0);
324 } else if (pcmk__str_eq(options.api_call, "cancel", pcmk__str_casei)) {
325 rc = lrmd_conn->cmds->cancel(lrmd_conn, options.rsc_id, options.action,
326 options.interval_ms);
327 } else if (pcmk__str_eq(options.api_call, "metadata", pcmk__str_casei)) {
328 char *output = NULL;
329
330 rc = lrmd_conn->cmds->get_metadata(lrmd_conn,
331 options.class,
332 options.provider, options.type, &output, 0);
333 if (rc == pcmk_ok) {
334 print_result("%s", output);
335 free(output);
336 }
337 } else if (pcmk__str_eq(options.api_call, "list_agents", pcmk__str_casei)) {
338 lrmd_list_t *list = NULL;
339 lrmd_list_t *iter = NULL;
340
341 rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, options.class, options.provider);
342
343 if (rc > 0) {
344 print_result("%d agents found", rc);
345 for (iter = list; iter != NULL; iter = iter->next) {
346 print_result("%s", iter->val);
347 }
348 lrmd_list_freeall(list);
349 rc = 0;
350 } else {
351 print_result("API_CALL FAILURE - no agents found");
352 rc = -1;
353 }
354 } else if (pcmk__str_eq(options.api_call, "list_ocf_providers", pcmk__str_casei)) {
355 lrmd_list_t *list = NULL;
356 lrmd_list_t *iter = NULL;
357
358 rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, options.type, &list);
359
360 if (rc > 0) {
361 print_result("%d providers found", rc);
362 for (iter = list; iter != NULL; iter = iter->next) {
363 print_result("%s", iter->val);
364 }
365 lrmd_list_freeall(list);
366 rc = 0;
367 } else {
368 print_result("API_CALL FAILURE - no providers found");
369 rc = -1;
370 }
371
372 } else if (pcmk__str_eq(options.api_call, "list_standards", pcmk__str_casei)) {
373 lrmd_list_t *list = NULL;
374 lrmd_list_t *iter = NULL;
375
376 rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list);
377
378 if (rc > 0) {
379 print_result("%d standards found", rc);
380 for (iter = list; iter != NULL; iter = iter->next) {
381 print_result("%s", iter->val);
382 }
383 lrmd_list_freeall(list);
384 rc = 0;
385 } else {
386 print_result("API_CALL FAILURE - no providers found");
387 rc = -1;
388 }
389
390 } else if (pcmk__str_eq(options.api_call, "get_recurring_ops", pcmk__str_casei)) {
391 GList *op_list = NULL;
392 GList *op_item = NULL;
393 rc = lrmd_conn->cmds->get_recurring_ops(lrmd_conn, options.rsc_id, 0, 0,
394 &op_list);
395
396 for (op_item = op_list; op_item != NULL; op_item = op_item->next) {
397 lrmd_op_info_t *op_info = op_item->data;
398
399 print_result("RECURRING_OP: %s_%s_%s timeout=%sms",
400 op_info->rsc_id, op_info->action,
401 op_info->interval_ms_s, op_info->timeout_ms_s);
402 lrmd_free_op_info(op_info);
403 }
404 g_list_free(op_list);
405
406 } else if (options.api_call) {
407 print_result("API-CALL FAILURE unknown action '%s'", options.action);
408 test_exit(CRM_EX_ERROR);
409 }
410
411 if (rc < 0) {
412 print_result("API-CALL FAILURE for '%s' api_rc:%d",
413 options.api_call, rc);
414 test_exit(CRM_EX_ERROR);
415 }
416
417 if (options.api_call && rc == pcmk_ok) {
418 print_result("API-CALL SUCCESSFUL for '%s'", options.api_call);
419 if (!options.listen) {
420 test_exit(CRM_EX_OK);
421 }
422 }
423
424 if (options.no_wait) {
425 /* just make the call and exit regardless of anything else. */
426 test_exit(CRM_EX_OK);
427 }
428
429 return 0;
430 }
431
432 /*!
433 * \internal
434 * \brief Generate resource parameters from CIB if none explicitly given
435 *
436 * \return Standard Pacemaker return code
437 */
438 static int
439 generate_params(void)
440 {
441 int rc = pcmk_rc_ok;
442 pcmk_scheduler_t *scheduler = NULL;
443 xmlNode *cib_xml_copy = NULL;
444 pcmk_resource_t *rsc = NULL;
445 GHashTable *params = NULL;
446 GHashTableIter iter;
447 char *key = NULL;
448 char *value = NULL;
449
450 if (options.params != NULL) {
451 return pcmk_rc_ok; // User specified parameters explicitly
452 }
453
454 // Retrieve and update CIB
455 rc = cib__signon_query(NULL, NULL, &cib_xml_copy);
456 if (rc != pcmk_rc_ok) {
457 return rc;
458 }
459 rc = pcmk__update_configured_schema(&cib_xml_copy, false);
460 if (rc != pcmk_rc_ok) {
461 return rc;
462 }
463
464 // Calculate cluster status
465 scheduler = pcmk_new_scheduler();
466 if (scheduler == NULL) {
467 pcmk__crit("Could not allocate scheduler data");
468 return ENOMEM;
469 }
470 pcmk__set_scheduler_flags(scheduler, pcmk__sched_no_counts);
471 scheduler->input = cib_xml_copy;
472 scheduler->priv->now = crm_time_new(NULL);
473 cluster_status(scheduler);
474
475 // Find resource in CIB
476 rsc = pe_find_resource_with_flags(scheduler->priv->resources,
477 options.rsc_id,
478 pcmk_rsc_match_history
479 |pcmk_rsc_match_basename);
480 if (rsc == NULL) {
481 pcmk__err("Resource does not exist in config");
482 pcmk_free_scheduler(scheduler);
483 return EINVAL;
484 }
485
486 // Add resource instance parameters to options.params
487 params = pe_rsc_params(rsc, NULL, scheduler);
488 if (params != NULL) {
489 g_hash_table_iter_init(&iter, params);
490 while (g_hash_table_iter_next(&iter, (gpointer *) &key,
491 (gpointer *) &value)) {
492 options.params = lrmd_key_value_add(options.params, key, value);
493 }
494 }
495
496 // Add resource meta-attributes to options.params
497 g_hash_table_iter_init(&iter, rsc->priv->meta);
498 while (g_hash_table_iter_next(&iter, (gpointer *) &key,
499 (gpointer *) &value)) {
500 char *crm_name = crm_meta_name(key);
501
502 options.params = lrmd_key_value_add(options.params, crm_name, value);
503 free(crm_name);
504 }
505
506 pcmk_free_scheduler(scheduler);
507 return rc;
508 }
509
510 static GOptionContext *
511 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
512 GOptionContext *context = NULL;
513
514 context = pcmk__build_arg_context(args, NULL, group, NULL);
515
516 pcmk__add_main_args(context, basic_entries);
517 pcmk__add_arg_group(context, "api-call", "API Call Options:",
518 "Parameters for api-call option", api_call_entries);
519
520 return context;
521 }
522
523 int
524 main(int argc, char **argv)
525 {
526 GError *error = NULL;
527 crm_exit_t exit_code = CRM_EX_OK;
528 crm_trigger_t *trig = NULL;
529
530 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
531 /* Typically we'd pass all the single character options that take an argument
532 * as the second parameter here (and there's a bunch of those in this tool).
533 * However, we control how this program is called so we can just not call it
534 * in a way where the preprocessing ever matters.
535 */
536 gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
537 GOptionContext *context = build_arg_context(args, NULL);
538
539 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
540 exit_code = CRM_EX_USAGE;
541 goto done;
542 }
543
544 /* We have to use crm_log_init here to set up the logging because there's
545 * different handling for daemons vs. command line programs, and
546 * pcmk__cli_init_logging is set up to only handle the latter.
547 */
548 crm_log_init(NULL, LOG_INFO, TRUE, (args->verbosity? TRUE : FALSE), argc,
549 argv, FALSE);
550
551 for (int i = 0; i < args->verbosity; i++) {
552 crm_bump_log_level(argc, argv);
553 }
554
555 if (!options.listen && pcmk__strcase_any_of(options.api_call, "metadata", "list_agents",
556 "list_standards", "list_ocf_providers", NULL)) {
557 options.no_connect = TRUE;
558 }
559
560 if (options.is_running) {
561 int rc = pcmk_rc_ok;
562
563 if (options.rsc_id == NULL) {
564 exit_code = CRM_EX_USAGE;
565 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
566 "--is-running requires --rsc-id");
567 goto done;
568 }
569
570 options.interval_ms = 0;
571 if (options.timeout == 0) {
572 options.timeout = 30000;
573 }
574
575 rc = generate_params();
576 if (rc != pcmk_rc_ok) {
577 exit_code = pcmk_rc2exitc(rc);
578 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
579 "Can not determine resource status: "
580 "unable to get parameters from CIB");
581 goto done;
582 }
583 options.api_call = "exec";
584 options.action = PCMK_ACTION_MONITOR;
585 options.exec_call_opts = lrmd_opt_notify_orig_only;
586 }
587
588 if (!options.api_call && !options.listen) {
589 exit_code = CRM_EX_USAGE;
590 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
591 "Must specify at least one of --api-call, --listen, "
592 "or --is-running");
593 goto done;
594 }
595
596 if (options.use_tls) {
597 lrmd_conn = lrmd_remote_api_new(NULL, "localhost", 0);
598 } else {
599 lrmd_conn = lrmd_api_new();
600 }
601 trig = mainloop_add_trigger(G_PRIORITY_HIGH, start_test, NULL);
602 mainloop_set_trigger(trig);
603 mainloop_add_signal(SIGTERM, test_shutdown);
604
605 pcmk__info("Starting");
606 mainloop = g_main_loop_new(NULL, FALSE);
607 g_main_loop_run(mainloop);
608
609 done:
610 g_strfreev(processed_args);
611 pcmk__free_arg_context(context);
612
613 free(key);
614 free(val);
615
616 pcmk__output_and_clear_error(&error, NULL);
617 return test_exit(exit_code);
618 }
619