This source file includes following definitions.
- all_actions_cb
- attrs_cb
- failcounts_cb
- in_place_cb
- live_check_cb
- node_down_cb
- node_fail_cb
- node_up_cb
- op_fail_cb
- op_inject_cb
- pending_cb
- process_cb
- quorum_cb
- save_dotfile_cb
- save_graph_cb
- show_scores_cb
- simulate_cb
- ticket_activate_cb
- ticket_grant_cb
- ticket_revoke_cb
- ticket_standby_cb
- utilization_cb
- watchdog_cb
- xml_file_cb
- xml_pipe_cb
- setup_input
- build_arg_context
- main
1
2
3
4
5
6
7
8
9
10 #include <crm_internal.h>
11
12 #include <stdint.h>
13 #include <stdio.h>
14 #include <unistd.h>
15 #include <stdlib.h>
16 #include <time.h>
17
18 #include <sys/stat.h>
19 #include <sys/param.h>
20 #include <sys/types.h>
21 #include <dirent.h>
22
23 #include <crm/crm.h>
24 #include <crm/cib.h>
25 #include <crm/cib/internal.h>
26 #include <crm/common/cmdline_internal.h>
27 #include <crm/common/output_internal.h>
28 #include <crm/common/output.h>
29 #include <crm/common/util.h>
30 #include <crm/common/iso8601.h>
31 #include <crm/pengine/status.h>
32 #include <crm/pengine/internal.h>
33 #include <pacemaker-internal.h>
34 #include <pacemaker.h>
35
36 #define SUMMARY "crm_simulate - simulate a Pacemaker cluster's response to events"
37
38 struct {
39 char *dot_file;
40 char *graph_file;
41 gchar *input_file;
42 pcmk_injections_t *injections;
43 unsigned int flags;
44 gchar *output_file;
45 long long repeat;
46 gboolean store;
47 gchar *test_dir;
48 char *use_date;
49 char *xml_file;
50 } options = {
51 .flags = pcmk_sim_show_pending | pcmk_sim_sanitized,
52 .repeat = 1
53 };
54
55 uint32_t section_opts = 0;
56 char *temp_shadow = NULL;
57 crm_exit_t exit_code = CRM_EX_OK;
58
59 #define INDENT " "
60
61 static pcmk__supported_format_t formats[] = {
62 PCMK__SUPPORTED_FORMAT_NONE,
63 PCMK__SUPPORTED_FORMAT_TEXT,
64 PCMK__SUPPORTED_FORMAT_XML,
65 { NULL, NULL, NULL }
66 };
67
68 static gboolean
69 all_actions_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
70 options.flags |= pcmk_sim_all_actions;
71 return TRUE;
72 }
73
74 static gboolean
75 attrs_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
76 section_opts |= pcmk_section_attributes;
77 return TRUE;
78 }
79
80 static gboolean
81 failcounts_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
82 section_opts |= pcmk_section_failcounts | pcmk_section_failures;
83 return TRUE;
84 }
85
86 static gboolean
87 in_place_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
88 options.store = TRUE;
89 options.flags |= pcmk_sim_process | pcmk_sim_simulate;
90 return TRUE;
91 }
92
93 static gboolean
94 live_check_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
95 if (options.xml_file) {
96 free(options.xml_file);
97 }
98
99 options.xml_file = NULL;
100 options.flags &= ~pcmk_sim_sanitized;
101 return TRUE;
102 }
103
104 static gboolean
105 node_down_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
106 options.injections->node_down = g_list_append(options.injections->node_down, g_strdup(optarg));
107 return TRUE;
108 }
109
110 static gboolean
111 node_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
112 options.injections->node_fail = g_list_append(options.injections->node_fail, g_strdup(optarg));
113 return TRUE;
114 }
115
116 static gboolean
117 node_up_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
118 pcmk__simulate_node_config = true;
119 options.injections->node_up = g_list_append(options.injections->node_up, g_strdup(optarg));
120 return TRUE;
121 }
122
123 static gboolean
124 op_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
125 options.flags |= pcmk_sim_process | pcmk_sim_simulate;
126 options.injections->op_fail = g_list_append(options.injections->op_fail, g_strdup(optarg));
127 return TRUE;
128 }
129
130 static gboolean
131 op_inject_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
132 options.injections->op_inject = g_list_append(options.injections->op_inject, g_strdup(optarg));
133 return TRUE;
134 }
135
136 static gboolean
137 pending_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
138 options.flags |= pcmk_sim_show_pending;
139 return TRUE;
140 }
141
142 static gboolean
143 process_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
144 options.flags |= pcmk_sim_process;
145 return TRUE;
146 }
147
148 static gboolean
149 quorum_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
150 pcmk__str_update(&options.injections->quorum, optarg);
151 return TRUE;
152 }
153
154 static gboolean
155 save_dotfile_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
156 options.flags |= pcmk_sim_process;
157 pcmk__str_update(&options.dot_file, optarg);
158 return TRUE;
159 }
160
161 static gboolean
162 save_graph_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
163 options.flags |= pcmk_sim_process;
164 pcmk__str_update(&options.graph_file, optarg);
165 return TRUE;
166 }
167
168 static gboolean
169 show_scores_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
170 options.flags |= pcmk_sim_process | pcmk_sim_show_scores;
171 return TRUE;
172 }
173
174 static gboolean
175 simulate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
176 options.flags |= pcmk_sim_process | pcmk_sim_simulate;
177 return TRUE;
178 }
179
180 static gboolean
181 ticket_activate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
182 options.injections->ticket_activate = g_list_append(options.injections->ticket_activate, g_strdup(optarg));
183 return TRUE;
184 }
185
186 static gboolean
187 ticket_grant_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
188 options.injections->ticket_grant = g_list_append(options.injections->ticket_grant, g_strdup(optarg));
189 return TRUE;
190 }
191
192 static gboolean
193 ticket_revoke_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
194 options.injections->ticket_revoke = g_list_append(options.injections->ticket_revoke, g_strdup(optarg));
195 return TRUE;
196 }
197
198 static gboolean
199 ticket_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
200 options.injections->ticket_standby = g_list_append(options.injections->ticket_standby, g_strdup(optarg));
201 return TRUE;
202 }
203
204 static gboolean
205 utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
206 options.flags |= pcmk_sim_process | pcmk_sim_show_utilization;
207 return TRUE;
208 }
209
210 static gboolean
211 watchdog_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
212 pcmk__str_update(&options.injections->watchdog, optarg);
213 return TRUE;
214 }
215
216 static gboolean
217 xml_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
218 pcmk__str_update(&options.xml_file, optarg);
219 options.flags |= pcmk_sim_sanitized;
220 return TRUE;
221 }
222
223 static gboolean
224 xml_pipe_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
225 pcmk__str_update(&options.xml_file, "-");
226 options.flags |= pcmk_sim_sanitized;
227 return TRUE;
228 }
229
230 static GOptionEntry operation_entries[] = {
231 { "run", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, process_cb,
232 "Process the supplied input and show what actions the cluster will take in response",
233 NULL },
234 { "simulate", 'S', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, simulate_cb,
235 "Like --run, but also simulate taking those actions and show the resulting new status",
236 NULL },
237 { "in-place", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, in_place_cb,
238 "Like --simulate, but also store the results back to the input file",
239 NULL },
240 { "show-attrs", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attrs_cb,
241 "Show node attributes",
242 NULL },
243 { "show-failcounts", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, failcounts_cb,
244 "Show resource fail counts",
245 NULL },
246 { "show-scores", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_scores_cb,
247 "Show allocation scores",
248 NULL },
249 { "show-utilization", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb,
250 "Show utilization information",
251 NULL },
252 { "profile", 'P', 0, G_OPTION_ARG_FILENAME, &options.test_dir,
253 "Process all the XML files in the named directory to create profiling data",
254 "DIR" },
255 { "repeat", 'N', 0, G_OPTION_ARG_INT, &options.repeat,
256 "With --profile, repeat each test N times and print timings",
257 "N" },
258
259 { "pending", 'j', G_OPTION_FLAG_NO_ARG|G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, pending_cb,
260 "Display pending state if '" PCMK_META_RECORD_PENDING "' is enabled",
261 NULL },
262
263 { NULL }
264 };
265
266 static GOptionEntry synthetic_entries[] = {
267 { "node-up", 'u', 0, G_OPTION_ARG_CALLBACK, node_up_cb,
268 "Simulate bringing a node online",
269 "NODE" },
270 { "node-down", 'd', 0, G_OPTION_ARG_CALLBACK, node_down_cb,
271 "Simulate taking a node offline",
272 "NODE" },
273 { "node-fail", 'f', 0, G_OPTION_ARG_CALLBACK, node_fail_cb,
274 "Simulate a node failing",
275 "NODE" },
276 { "op-inject", 'i', 0, G_OPTION_ARG_CALLBACK, op_inject_cb,
277 "Generate a failure for the cluster to react to in the simulation.\n"
278 INDENT "See `Operation Specification` help for more information.",
279 "OPSPEC" },
280 { "op-fail", 'F', 0, G_OPTION_ARG_CALLBACK, op_fail_cb,
281 "If the specified task occurs during the simulation, have it fail with return code ${rc}.\n"
282 INDENT "The transition will normally stop at the failed action.\n"
283 INDENT "Save the result with --save-output and re-run with --xml-file.\n"
284 INDENT "See `Operation Specification` help for more information.",
285 "OPSPEC" },
286 { "set-datetime", 't', 0, G_OPTION_ARG_STRING, &options.use_date,
287 "Set date/time (ISO 8601 format, see https://en.wikipedia.org/wiki/ISO_8601)",
288 "DATETIME" },
289 { "quorum", 'q', 0, G_OPTION_ARG_CALLBACK, quorum_cb,
290 "Set to '1' (or 'true') to indicate cluster has quorum",
291 "QUORUM" },
292 { "watchdog", 'w', 0, G_OPTION_ARG_CALLBACK, watchdog_cb,
293 "Set to '1' (or 'true') to indicate cluster has an active watchdog device",
294 "DEVICE" },
295 { "ticket-grant", 'g', 0, G_OPTION_ARG_CALLBACK, ticket_grant_cb,
296 "Simulate granting a ticket",
297 "TICKET" },
298 { "ticket-revoke", 'r', 0, G_OPTION_ARG_CALLBACK, ticket_revoke_cb,
299 "Simulate revoking a ticket",
300 "TICKET" },
301 { "ticket-standby", 'b', 0, G_OPTION_ARG_CALLBACK, ticket_standby_cb,
302 "Simulate making a ticket standby",
303 "TICKET" },
304 { "ticket-activate", 'e', 0, G_OPTION_ARG_CALLBACK, ticket_activate_cb,
305 "Simulate activating a ticket",
306 "TICKET" },
307
308 { NULL }
309 };
310
311 static GOptionEntry artifact_entries[] = {
312 { "save-input", 'I', 0, G_OPTION_ARG_FILENAME, &options.input_file,
313 "Save the input configuration to the named file",
314 "FILE" },
315 { "save-output", 'O', 0, G_OPTION_ARG_FILENAME, &options.output_file,
316 "Save the output configuration to the named file",
317 "FILE" },
318 { "save-graph", 'G', 0, G_OPTION_ARG_CALLBACK, save_graph_cb,
319 "Save the transition graph (XML format) to the named file",
320 "FILE" },
321 { "save-dotfile", 'D', 0, G_OPTION_ARG_CALLBACK, save_dotfile_cb,
322 "Save the transition graph (DOT format) to the named file",
323 "FILE" },
324 { "all-actions", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, all_actions_cb,
325 "Display all possible actions in DOT graph (even if not part of transition)",
326 NULL },
327
328 { NULL }
329 };
330
331 static GOptionEntry source_entries[] = {
332 { "live-check", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, live_check_cb,
333 "Connect to CIB manager and use the current CIB contents as input",
334 NULL },
335 { "xml-file", 'x', 0, G_OPTION_ARG_CALLBACK, xml_file_cb,
336 "Retrieve XML from the named file",
337 "FILE" },
338 { "xml-pipe", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, xml_pipe_cb,
339 "Retrieve XML from stdin",
340 NULL },
341
342 { NULL }
343 };
344
345 static int
346 setup_input(pcmk__output_t *out, const char *input, const char *output,
347 GError **error)
348 {
349 int rc = pcmk_rc_ok;
350 xmlNode *cib_object = NULL;
351 char *local_output = NULL;
352
353 if (input == NULL) {
354
355 rc = cib__signon_query(out, NULL, &cib_object);
356 if (rc != pcmk_rc_ok) {
357
358 return rc;
359 }
360
361 } else if (pcmk__str_eq(input, "-", pcmk__str_casei)) {
362 cib_object = pcmk__xml_read(NULL);
363
364 } else {
365 cib_object = pcmk__xml_read(input);
366 }
367
368 if (cib_object == NULL) {
369 rc = pcmk_rc_bad_input;
370 g_set_error(error, PCMK__EXITC_ERROR, pcmk_rc2exitc(rc),
371 "Could not read input XML: %s", pcmk_rc_str(rc));
372 return rc;
373 }
374
375 if (pcmk_find_cib_element(cib_object, PCMK_XE_STATUS) == NULL) {
376 pcmk__xe_create(cib_object, PCMK_XE_STATUS);
377 }
378
379 rc = pcmk__update_configured_schema(&cib_object, false);
380 if (rc != pcmk_rc_ok) {
381 free_xml(cib_object);
382 return rc;
383 }
384
385 if (!pcmk__validate_xml(cib_object, NULL, NULL, NULL)) {
386 free_xml(cib_object);
387 return pcmk_rc_schema_validation;
388 }
389
390 if (output == NULL) {
391 char *pid = pcmk__getpid_s();
392
393 local_output = get_shadow_file(pid);
394 temp_shadow = strdup(local_output);
395 output = local_output;
396 free(pid);
397 }
398
399 rc = pcmk__xml_write_file(cib_object, output, false, NULL);
400 if (rc != pcmk_rc_ok) {
401 g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_CANTCREAT,
402 "Could not create '%s': %s", output, pcmk_rc_str(rc));
403 } else {
404 setenv("CIB_file", output, 1);
405 }
406
407 free_xml(cib_object);
408 free(local_output);
409 return rc;
410 }
411
412 static GOptionContext *
413 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
414 GOptionContext *context = NULL;
415
416 GOptionEntry extra_prog_entries[] = {
417 { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
418 "Display only essential output",
419 NULL },
420
421 { NULL }
422 };
423
424 const char *description = "Operation Specification:\n\n"
425 "The OPSPEC in any command line option is of the form\n"
426 "${resource}_${task}_${interval_in_ms}@${node}=${rc}\n"
427 "(memcached_monitor_20000@bart.example.com=7, for example).\n"
428 "${rc} is an OCF return code. For more information on these\n"
429 "return codes, refer to https://clusterlabs.org/pacemaker/doc/2.1/Pacemaker_Administration/html/agents.html#ocf-return-codes\n\n"
430 "Examples:\n\n"
431 "Pretend a recurring monitor action found memcached stopped on node\n"
432 "fred.example.com and, during recovery, that the memcached stop\n"
433 "action failed:\n\n"
434 "\tcrm_simulate -LS --op-inject memcached:0_monitor_20000@bart.example.com=7 "
435 "--op-fail memcached:0_stop_0@fred.example.com=1 --save-output /tmp/memcached-test.xml\n\n"
436 "Now see what the reaction to the stop failed would be:\n\n"
437 "\tcrm_simulate -S --xml-file /tmp/memcached-test.xml\n\n";
438
439 context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
440 pcmk__add_main_args(context, extra_prog_entries);
441 g_option_context_set_description(context, description);
442
443 pcmk__add_arg_group(context, "operations", "Operations:",
444 "Show operations options", operation_entries);
445 pcmk__add_arg_group(context, "synthetic", "Synthetic Cluster Events:",
446 "Show synthetic cluster event options", synthetic_entries);
447 pcmk__add_arg_group(context, "artifact", "Artifact Options:",
448 "Show artifact options", artifact_entries);
449 pcmk__add_arg_group(context, "source", "Data Source:",
450 "Show data source options", source_entries);
451
452 return context;
453 }
454
455 int
456 main(int argc, char **argv)
457 {
458 int rc = pcmk_rc_ok;
459 pcmk_scheduler_t *scheduler = NULL;
460 pcmk__output_t *out = NULL;
461
462 GError *error = NULL;
463
464 GOptionGroup *output_group = NULL;
465 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
466 gchar **processed_args = pcmk__cmdline_preproc(argv, "bdefgiqrtuwxDFGINOP");
467 GOptionContext *context = build_arg_context(args, &output_group);
468
469 options.injections = calloc(1, sizeof(pcmk_injections_t));
470 if (options.injections == NULL) {
471 rc = ENOMEM;
472 goto done;
473 }
474
475
476 options.xml_file = strdup("-");
477
478 pcmk__register_formats(output_group, formats);
479 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
480 exit_code = CRM_EX_USAGE;
481 goto done;
482 }
483
484 pcmk__cli_init_logging("crm_simulate", args->verbosity);
485
486 rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
487 if (rc != pcmk_rc_ok) {
488 fprintf(stderr, "Error creating output format %s: %s\n",
489 args->output_ty, pcmk_rc_str(rc));
490 exit_code = CRM_EX_ERROR;
491 goto done;
492 }
493
494 if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches) &&
495 !(pcmk_is_set(options.flags, pcmk_sim_show_scores) && args->quiet)) {
496 pcmk__output_text_set_fancy(out, true);
497 }
498
499 pe__register_messages(out);
500 pcmk__register_lib_messages(out);
501
502 out->quiet = args->quiet;
503
504 if (args->version) {
505 out->version(out, false);
506 goto done;
507 }
508
509 if (args->verbosity > 0) {
510 options.flags |= pcmk_sim_verbose;
511
512 #ifdef PCMK__COMPAT_2_0
513
514 close(STDERR_FILENO);
515 dup2(STDOUT_FILENO, STDERR_FILENO);
516 #endif
517 }
518
519 scheduler = pe_new_working_set();
520 if (scheduler == NULL) {
521 rc = ENOMEM;
522 g_set_error(&error, PCMK__RC_ERROR, rc,
523 "Could not allocate scheduler data");
524 goto done;
525 }
526
527 if (pcmk_is_set(options.flags, pcmk_sim_show_scores)) {
528 pcmk__set_scheduler_flags(scheduler, pcmk_sched_output_scores);
529 }
530 if (pcmk_is_set(options.flags, pcmk_sim_show_utilization)) {
531 pcmk__set_scheduler_flags(scheduler, pcmk_sched_show_utilization);
532 }
533 pcmk__set_scheduler_flags(scheduler, pcmk_sched_no_compat);
534
535 if (options.test_dir != NULL) {
536 scheduler->priv = out;
537 pcmk__profile_dir(options.test_dir, options.repeat, scheduler,
538 options.use_date);
539 rc = pcmk_rc_ok;
540 goto done;
541 }
542
543 rc = setup_input(out, options.xml_file,
544 options.store? options.xml_file : options.output_file,
545 &error);
546 if (rc != pcmk_rc_ok) {
547 goto done;
548 }
549
550 rc = pcmk__simulate(scheduler, out, options.injections, options.flags,
551 section_opts, options.use_date, options.input_file,
552 options.graph_file, options.dot_file);
553
554 done:
555 pcmk__output_and_clear_error(&error, NULL);
556
557
558 free(options.dot_file);
559 free(options.graph_file);
560 g_free(options.input_file);
561 g_free(options.output_file);
562 g_free(options.test_dir);
563 free(options.use_date);
564 free(options.xml_file);
565
566 pcmk_free_injections(options.injections);
567 pcmk__free_arg_context(context);
568 g_strfreev(processed_args);
569
570 if (scheduler != NULL) {
571 pe_free_working_set(scheduler);
572 }
573
574 fflush(stderr);
575
576 if (temp_shadow) {
577 unlink(temp_shadow);
578 free(temp_shadow);
579 }
580
581 if (rc != pcmk_rc_ok) {
582 exit_code = pcmk_rc2exitc(rc);
583 }
584
585 if (out != NULL) {
586 out->finish(out, exit_code, true, NULL);
587 pcmk__output_free(out);
588 }
589
590 pcmk__unregister_formats();
591 crm_exit(exit_code);
592 }