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