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 GHashTableIter iter;
448 char *key = NULL;
449 char *value = NULL;
450
451 if (options.params != NULL) {
452 return pcmk_rc_ok; // User specified parameters explicitly
453 }
454
455 // Retrieve and update CIB
456 rc = cib__signon_query(NULL, NULL, &cib_xml_copy);
457 if (rc != pcmk_rc_ok) {
458 return rc;
459 }
460 rc = pcmk__update_configured_schema(&cib_xml_copy, false);
461 if (rc != pcmk_rc_ok) {
462 return rc;
463 }
464
465 // Calculate cluster status
466 scheduler = pcmk_new_scheduler();
467 if (scheduler == NULL) {
468 pcmk__crit("Could not allocate scheduler data");
469 return ENOMEM;
470 }
471 pcmk__set_scheduler_flags(scheduler, pcmk__sched_no_counts);
472 scheduler->input = cib_xml_copy;
473 scheduler->priv->now = crm_time_new(NULL);
474 cluster_status(scheduler);
475
476 // Find resource in CIB
477 rsc = pe_find_resource_with_flags(scheduler->priv->resources,
478 options.rsc_id,
479 pcmk_rsc_match_history
480 |pcmk_rsc_match_basename);
481 if (rsc == NULL) {
482 pcmk__err("Resource does not exist in config");
483 pcmk_free_scheduler(scheduler);
484 return EINVAL;
485 }
486
487 // Add resource instance parameters to options.params
488 params = pe_rsc_params(rsc, NULL, scheduler);
489 if (params != NULL) {
490 g_hash_table_iter_init(&iter, params);
491 while (g_hash_table_iter_next(&iter, (gpointer *) &key,
492 (gpointer *) &value)) {
493 options.params = lrmd_key_value_add(options.params, key, value);
494 }
495 }
496
497 // Add resource meta-attributes to options.params
498 g_hash_table_iter_init(&iter, rsc->priv->meta);
499 while (g_hash_table_iter_next(&iter, (gpointer *) &key,
500 (gpointer *) &value)) {
501 char *crm_name = crm_meta_name(key);
502
503 options.params = lrmd_key_value_add(options.params, crm_name, value);
504 free(crm_name);
505 }
506
507 pcmk_free_scheduler(scheduler);
508 return rc;
509 }
510
511 static GOptionContext *
512 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
513 GOptionContext *context = NULL;
514
515 context = pcmk__build_arg_context(args, NULL, group, NULL);
516
517 pcmk__add_main_args(context, basic_entries);
518 pcmk__add_arg_group(context, "api-call", "API Call Options:",
519 "Parameters for api-call option", api_call_entries);
520
521 return context;
522 }
523
524 int
525 main(int argc, char **argv)
526 {
527 GError *error = NULL;
528 crm_exit_t exit_code = CRM_EX_OK;
529 crm_trigger_t *trig = NULL;
530
531 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
532 /* Typically we'd pass all the single character options that take an argument
533 * as the second parameter here (and there's a bunch of those in this tool).
534 * However, we control how this program is called so we can just not call it
535 * in a way where the preprocessing ever matters.
536 */
537 gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
538 GOptionContext *context = build_arg_context(args, NULL);
539
540 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
541 exit_code = CRM_EX_USAGE;
542 goto done;
543 }
544
545 /* We have to use crm_log_init here to set up the logging because there's
546 * different handling for daemons vs. command line programs, and
547 * pcmk__cli_init_logging is set up to only handle the latter.
548 */
549 crm_log_init(NULL, LOG_INFO, TRUE, (args->verbosity? TRUE : FALSE), argc,
550 argv, FALSE);
551
552 for (int i = 0; i < args->verbosity; i++) {
553 crm_bump_log_level(argc, argv);
554 }
555
556 if (!options.listen && pcmk__strcase_any_of(options.api_call, "metadata", "list_agents",
557 "list_standards", "list_ocf_providers", NULL)) {
558 options.no_connect = TRUE;
559 }
560
561 if (options.is_running) {
562 int rc = pcmk_rc_ok;
563
564 if (options.rsc_id == NULL) {
565 exit_code = CRM_EX_USAGE;
566 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
567 "--is-running requires --rsc-id");
568 goto done;
569 }
570
571 options.interval_ms = 0;
572 if (options.timeout == 0) {
573 options.timeout = 30000;
574 }
575
576 rc = generate_params();
577 if (rc != pcmk_rc_ok) {
578 exit_code = pcmk_rc2exitc(rc);
579 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
580 "Can not determine resource status: "
581 "unable to get parameters from CIB");
582 goto done;
583 }
584 options.api_call = "exec";
585 options.action = PCMK_ACTION_MONITOR;
586 options.exec_call_opts = lrmd_opt_notify_orig_only;
587 }
588
589 if (!options.api_call && !options.listen) {
590 exit_code = CRM_EX_USAGE;
591 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
592 "Must specify at least one of --api-call, --listen, "
593 "or --is-running");
594 goto done;
595 }
596
597 if (options.use_tls) {
598 lrmd_conn = lrmd_remote_api_new(NULL, "localhost", 0);
599 } else {
600 lrmd_conn = lrmd_api_new();
601 }
602 trig = mainloop_add_trigger(G_PRIORITY_HIGH, start_test, NULL);
603 mainloop_set_trigger(trig);
604 mainloop_add_signal(SIGTERM, test_shutdown);
605
606 pcmk__info("Starting");
607 mainloop = g_main_loop_new(NULL, FALSE);
608 g_main_loop_run(mainloop);
609
610 done:
611 g_strfreev(processed_args);
612 pcmk__free_arg_context(context);
613
614 free(key);
615 free(val);
616
617 pcmk__output_and_clear_error(&error, NULL);
618 return test_exit(exit_code);
619 }
620