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 lrmd_api_delete(lrmd_conn);
179 lrmd_conn = NULL;
180 }
181
182 static void
183 read_events(lrmd_event_data_t * event)
184 {
185 char buf[1024] = { '\0', };
186
187 pcmk__assert(snprintf(buf, sizeof(buf),
188 "NEW_EVENT event_type:%s rsc_id:%s action:%s rc:%s "
189 "op_status:%s",
190 lrmd_event_type2str(event->type), event->rsc_id,
191 pcmk__s(event->op_type, "none"),
192 crm_exit_str((crm_exit_t) event->rc),
193 pcmk_exec_status_str(event->op_status)) >= 0);
194 pcmk__info("%s", buf);
195
196 if (options.listen && pcmk__str_eq(options.listen, buf, pcmk__str_casei)) {
197 print_result("LISTEN EVENT SUCCESSFUL");
198 test_exit(CRM_EX_OK);
199 }
200
201 if (exec_call_id && (event->call_id == exec_call_id)) {
202 if (event->op_status == 0 && event->rc == 0) {
203 print_result("API-CALL SUCCESSFUL for 'exec'");
204 } else {
205 print_result("API-CALL FAILURE for 'exec', rc:%d lrmd_op_status:%s",
206 event->rc, pcmk_exec_status_str(event->op_status));
207 test_exit(CRM_EX_ERROR);
208 }
209
210 if (!options.listen) {
211 test_exit(CRM_EX_OK);
212 }
213 }
214 }
215
216 static gboolean
217 timeout_err(gpointer data)
218 {
219 print_result("LISTEN EVENT FAILURE - timeout occurred, never found");
220 test_exit(CRM_EX_TIMEOUT);
221 return FALSE;
222 }
223
224 static void
225 connection_events(lrmd_event_data_t * event)
226 {
227 int rc = event->connection_rc;
228
229 if (event->type != lrmd_event_connect) {
230 /* ignore */
231 return;
232 }
233
234 if (!rc) {
235 pcmk__info("Executor client connection established");
236 start_test(NULL);
237 return;
238 } else {
239 sleep(1);
240 try_connect();
241 pcmk__notice("Executor client connection failed");
242 }
243 }
244
245 static void
246 try_connect(void)
247 {
248 int tries = 10;
249 static int num_tries = 0;
250 int rc = 0;
251
252 lrmd_conn->cmds->set_callback(lrmd_conn, connection_events);
253 for (; num_tries < tries; num_tries++) {
254 rc = lrmd_conn->cmds->connect_async(lrmd_conn, crm_system_name, 3000);
255
256 if (!rc) {
257 return; /* we'll hear back in async callback */
258 }
259 sleep(1);
260 }
261
262 print_result("API CONNECTION FAILURE");
263 test_exit(CRM_EX_ERROR);
264 }
265
266 static gboolean
267 start_test(gpointer user_data)
268 {
269 int rc = 0;
270
271 if (!options.no_connect) {
272 if (!lrmd_conn->cmds->is_connected(lrmd_conn)) {
273 try_connect();
274 /* async connect -- this function will get called back into */
275 return 0;
276 }
277 }
278 lrmd_conn->cmds->set_callback(lrmd_conn, read_events);
279
280 if (options.timeout) {
281 pcmk__create_timer(options.timeout, timeout_err, NULL);
282 }
283
284 if (!options.api_call) {
285 return 0;
286 }
287
288 if (pcmk__str_eq(options.api_call, "exec", pcmk__str_casei)) {
289 rc = lrmd_conn->cmds->exec(lrmd_conn,
290 options.rsc_id,
291 options.action,
292 NULL,
293 options.interval_ms,
294 options.timeout,
295 options.start_delay,
296 options.exec_call_opts,
297 options.params);
298
299 if (rc > 0) {
300 exec_call_id = rc;
301 print_result("API-CALL 'exec' action pending, waiting on response");
302 }
303
304 } else if (pcmk__str_eq(options.api_call, "register_rsc", pcmk__str_casei)) {
305 rc = lrmd_conn->cmds->register_rsc(lrmd_conn,
306 options.rsc_id,
307 options.class, options.provider, options.type, 0);
308 } else if (pcmk__str_eq(options.api_call, "get_rsc_info", pcmk__str_casei)) {
309 lrmd_rsc_info_t *rsc_info;
310
311 rsc_info = lrmd_conn->cmds->get_rsc_info(lrmd_conn, options.rsc_id, 0);
312
313 if (rsc_info) {
314 print_result("RSC_INFO: id:%s class:%s provider:%s type:%s",
315 rsc_info->id, rsc_info->standard,
316 (rsc_info->provider? rsc_info->provider : "<none>"),
317 rsc_info->type);
318 lrmd_free_rsc_info(rsc_info);
319 rc = pcmk_ok;
320 } else {
321 rc = -1;
322 }
323 } else if (pcmk__str_eq(options.api_call, "unregister_rsc", pcmk__str_casei)) {
324 rc = lrmd_conn->cmds->unregister_rsc(lrmd_conn, options.rsc_id, 0);
325 } else if (pcmk__str_eq(options.api_call, "cancel", pcmk__str_casei)) {
326 rc = lrmd_conn->cmds->cancel(lrmd_conn, options.rsc_id, options.action,
327 options.interval_ms);
328 } else if (pcmk__str_eq(options.api_call, "metadata", pcmk__str_casei)) {
329 char *output = NULL;
330
331 rc = lrmd_conn->cmds->get_metadata(lrmd_conn,
332 options.class,
333 options.provider, options.type, &output, 0);
334 if (rc == pcmk_ok) {
335 print_result("%s", output);
336 free(output);
337 }
338 } else if (pcmk__str_eq(options.api_call, "list_agents", pcmk__str_casei)) {
339 lrmd_list_t *list = NULL;
340 lrmd_list_t *iter = NULL;
341
342 rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, options.class, options.provider);
343
344 if (rc > 0) {
345 print_result("%d agents found", rc);
346 for (iter = list; iter != NULL; iter = iter->next) {
347 print_result("%s", iter->val);
348 }
349 lrmd_list_freeall(list);
350 rc = 0;
351 } else {
352 print_result("API_CALL FAILURE - no agents found");
353 rc = -1;
354 }
355 } else if (pcmk__str_eq(options.api_call, "list_ocf_providers", pcmk__str_casei)) {
356 lrmd_list_t *list = NULL;
357 lrmd_list_t *iter = NULL;
358
359 rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, options.type, &list);
360
361 if (rc > 0) {
362 print_result("%d providers found", rc);
363 for (iter = list; iter != NULL; iter = iter->next) {
364 print_result("%s", iter->val);
365 }
366 lrmd_list_freeall(list);
367 rc = 0;
368 } else {
369 print_result("API_CALL FAILURE - no providers found");
370 rc = -1;
371 }
372
373 } else if (pcmk__str_eq(options.api_call, "list_standards", pcmk__str_casei)) {
374 lrmd_list_t *list = NULL;
375 lrmd_list_t *iter = NULL;
376
377 rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list);
378
379 if (rc > 0) {
380 print_result("%d standards found", rc);
381 for (iter = list; iter != NULL; iter = iter->next) {
382 print_result("%s", iter->val);
383 }
384 lrmd_list_freeall(list);
385 rc = 0;
386 } else {
387 print_result("API_CALL FAILURE - no providers found");
388 rc = -1;
389 }
390
391 } else if (pcmk__str_eq(options.api_call, "get_recurring_ops", pcmk__str_casei)) {
392 GList *op_list = NULL;
393 GList *op_item = NULL;
394 rc = lrmd_conn->cmds->get_recurring_ops(lrmd_conn, options.rsc_id, 0, 0,
395 &op_list);
396
397 for (op_item = op_list; op_item != NULL; op_item = op_item->next) {
398 lrmd_op_info_t *op_info = op_item->data;
399
400 print_result("RECURRING_OP: %s_%s_%s timeout=%sms",
401 op_info->rsc_id, op_info->action,
402 op_info->interval_ms_s, op_info->timeout_ms_s);
403 lrmd_free_op_info(op_info);
404 }
405 g_list_free(op_list);
406
407 } else if (options.api_call) {
408 print_result("API-CALL FAILURE unknown action '%s'", options.action);
409 test_exit(CRM_EX_ERROR);
410 }
411
412 if (rc < 0) {
413 print_result("API-CALL FAILURE for '%s' api_rc:%d",
414 options.api_call, rc);
415 test_exit(CRM_EX_ERROR);
416 }
417
418 if (options.api_call && rc == pcmk_ok) {
419 print_result("API-CALL SUCCESSFUL for '%s'", options.api_call);
420 if (!options.listen) {
421 test_exit(CRM_EX_OK);
422 }
423 }
424
425 if (options.no_wait) {
426 /* just make the call and exit regardless of anything else. */
427 test_exit(CRM_EX_OK);
428 }
429
430 return 0;
431 }
432
433 /*!
434 * \internal
435 * \brief Generate resource parameters from CIB if none explicitly given
436 *
437 * \return Standard Pacemaker return code
438 */
439 static int
440 generate_params(void)
441 {
442 int rc = pcmk_rc_ok;
443 pcmk_scheduler_t *scheduler = NULL;
444 xmlNode *cib_xml_copy = NULL;
445 pcmk_resource_t *rsc = NULL;
446 GHashTable *params = NULL;
447 GHashTable *meta = NULL;
448 GHashTableIter iter;
449 char *key = NULL;
450 char *value = NULL;
451
452 if (options.params != NULL) {
453 return pcmk_rc_ok; // User specified parameters explicitly
454 }
455
456 // Retrieve and update CIB
457 rc = cib__signon_query(NULL, NULL, &cib_xml_copy);
458 if (rc != pcmk_rc_ok) {
459 return rc;
460 }
461 rc = pcmk__update_configured_schema(&cib_xml_copy, false);
462 if (rc != pcmk_rc_ok) {
463 return rc;
464 }
465
466 // Calculate cluster status
467 scheduler = pcmk_new_scheduler();
468 if (scheduler == NULL) {
469 pcmk__crit("Could not allocate scheduler data");
470 return ENOMEM;
471 }
472 pcmk__set_scheduler_flags(scheduler, pcmk__sched_no_counts);
473 scheduler->input = cib_xml_copy;
474 scheduler->priv->now = crm_time_new(NULL);
475 cluster_status(scheduler);
476
477 // Find resource in CIB
478 rsc = pe_find_resource_with_flags(scheduler->priv->resources,
479 options.rsc_id,
480 pcmk_rsc_match_history
481 |pcmk_rsc_match_basename);
482 if (rsc == NULL) {
483 pcmk__err("Resource does not exist in config");
484 pcmk_free_scheduler(scheduler);
485 return EINVAL;
486 }
487
488 // Add resource instance parameters to options.params
489 params = pe_rsc_params(rsc, NULL, scheduler);
490 if (params != NULL) {
491 g_hash_table_iter_init(&iter, params);
492 while (g_hash_table_iter_next(&iter, (gpointer *) &key,
493 (gpointer *) &value)) {
494 options.params = lrmd_key_value_add(options.params, key, value);
495 }
496 }
497
498 // Add resource meta-attributes to options.params
499 meta = pcmk__strkey_table(free, free);
500 get_meta_attributes(meta, rsc, NULL, scheduler);
501 g_hash_table_iter_init(&iter, meta);
502 while (g_hash_table_iter_next(&iter, (gpointer *) &key,
503 (gpointer *) &value)) {
504 char *crm_name = crm_meta_name(key);
505
506 options.params = lrmd_key_value_add(options.params, crm_name, value);
507 free(crm_name);
508 }
509 g_hash_table_destroy(meta);
510
511 pcmk_free_scheduler(scheduler);
512 return rc;
513 }
514
515 static GOptionContext *
516 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
517 GOptionContext *context = NULL;
518
519 context = pcmk__build_arg_context(args, NULL, group, NULL);
520
521 pcmk__add_main_args(context, basic_entries);
522 pcmk__add_arg_group(context, "api-call", "API Call Options:",
523 "Parameters for api-call option", api_call_entries);
524
525 return context;
526 }
527
528 int
529 main(int argc, char **argv)
530 {
531 GError *error = NULL;
532 crm_exit_t exit_code = CRM_EX_OK;
533 crm_trigger_t *trig = NULL;
534
535 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
536 /* Typically we'd pass all the single character options that take an argument
537 * as the second parameter here (and there's a bunch of those in this tool).
538 * However, we control how this program is called so we can just not call it
539 * in a way where the preprocessing ever matters.
540 */
541 gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
542 GOptionContext *context = build_arg_context(args, NULL);
543
544 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
545 exit_code = CRM_EX_USAGE;
546 goto done;
547 }
548
549 /* We have to use crm_log_init here to set up the logging because there's
550 * different handling for daemons vs. command line programs, and
551 * pcmk__cli_init_logging is set up to only handle the latter.
552 */
553 crm_log_init(NULL, LOG_INFO, TRUE, (args->verbosity? TRUE : FALSE), argc,
554 argv, FALSE);
555
556 for (int i = 0; i < args->verbosity; i++) {
557 crm_bump_log_level(argc, argv);
558 }
559
560 if (!options.listen && pcmk__strcase_any_of(options.api_call, "metadata", "list_agents",
561 "list_standards", "list_ocf_providers", NULL)) {
562 options.no_connect = TRUE;
563 }
564
565 if (options.is_running) {
566 int rc = pcmk_rc_ok;
567
568 if (options.rsc_id == NULL) {
569 exit_code = CRM_EX_USAGE;
570 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
571 "--is-running requires --rsc-id");
572 goto done;
573 }
574
575 options.interval_ms = 0;
576 if (options.timeout == 0) {
577 options.timeout = 30000;
578 }
579
580 rc = generate_params();
581 if (rc != pcmk_rc_ok) {
582 exit_code = pcmk_rc2exitc(rc);
583 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
584 "Can not determine resource status: "
585 "unable to get parameters from CIB");
586 goto done;
587 }
588 options.api_call = "exec";
589 options.action = PCMK_ACTION_MONITOR;
590 options.exec_call_opts = lrmd_opt_notify_orig_only;
591 }
592
593 if (!options.api_call && !options.listen) {
594 exit_code = CRM_EX_USAGE;
595 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
596 "Must specify at least one of --api-call, --listen, "
597 "or --is-running");
598 goto done;
599 }
600
601 if (options.use_tls) {
602 lrmd_conn = lrmd_remote_api_new(NULL, "localhost", 0);
603 } else {
604 lrmd_conn = lrmd_api_new();
605 }
606 trig = mainloop_add_trigger(G_PRIORITY_HIGH, start_test, NULL);
607 mainloop_set_trigger(trig);
608 mainloop_add_signal(SIGTERM, test_shutdown);
609
610 pcmk__info("Starting");
611 mainloop = g_main_loop_new(NULL, FALSE);
612 g_main_loop_run(mainloop);
613
614 done:
615 g_strfreev(processed_args);
616 pcmk__free_arg_context(context);
617
618 free(key);
619 free(val);
620
621 pcmk__output_and_clear_error(&error, NULL);
622 return test_exit(exit_code);
623 }
624