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 '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 = filename2xml(NULL);
363
364 } else {
365 cib_object = filename2xml(input);
366 }
367
368 if (pcmk_find_cib_element(cib_object, XML_CIB_TAG_STATUS) == NULL) {
369 create_xml_node(cib_object, XML_CIB_TAG_STATUS);
370 }
371
372 if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
373 free_xml(cib_object);
374 return pcmk_rc_transform_failed;
375 }
376
377 if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
378 free_xml(cib_object);
379 return pcmk_rc_schema_validation;
380 }
381
382 if (output == NULL) {
383 char *pid = pcmk__getpid_s();
384
385 local_output = get_shadow_file(pid);
386 temp_shadow = strdup(local_output);
387 output = local_output;
388 free(pid);
389 }
390
391 rc = write_xml_file(cib_object, output, FALSE);
392 free_xml(cib_object);
393 cib_object = NULL;
394
395 if (rc < 0) {
396 rc = pcmk_legacy2rc(rc);
397 g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_CANTCREAT,
398 "Could not create '%s': %s", output, pcmk_rc_str(rc));
399 return rc;
400 } else {
401 setenv("CIB_file", output, 1);
402 free(local_output);
403 return pcmk_rc_ok;
404 }
405 }
406
407 static GOptionContext *
408 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
409 GOptionContext *context = NULL;
410
411 GOptionEntry extra_prog_entries[] = {
412 { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
413 "Display only essential output",
414 NULL },
415
416 { NULL }
417 };
418
419 const char *description = "Operation Specification:\n\n"
420 "The OPSPEC in any command line option is of the form\n"
421 "${resource}_${task}_${interval_in_ms}@${node}=${rc}\n"
422 "(memcached_monitor_20000@bart.example.com=7, for example).\n"
423 "${rc} is an OCF return code. For more information on these\n"
424 "return codes, refer to https://clusterlabs.org/pacemaker/doc/2.1/Pacemaker_Administration/html/agents.html#ocf-return-codes\n\n"
425 "Examples:\n\n"
426 "Pretend a recurring monitor action found memcached stopped on node\n"
427 "fred.example.com and, during recovery, that the memcached stop\n"
428 "action failed:\n\n"
429 "\tcrm_simulate -LS --op-inject memcached:0_monitor_20000@bart.example.com=7 "
430 "--op-fail memcached:0_stop_0@fred.example.com=1 --save-output /tmp/memcached-test.xml\n\n"
431 "Now see what the reaction to the stop failed would be:\n\n"
432 "\tcrm_simulate -S --xml-file /tmp/memcached-test.xml\n\n";
433
434 context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
435 pcmk__add_main_args(context, extra_prog_entries);
436 g_option_context_set_description(context, description);
437
438 pcmk__add_arg_group(context, "operations", "Operations:",
439 "Show operations options", operation_entries);
440 pcmk__add_arg_group(context, "synthetic", "Synthetic Cluster Events:",
441 "Show synthetic cluster event options", synthetic_entries);
442 pcmk__add_arg_group(context, "artifact", "Artifact Options:",
443 "Show artifact options", artifact_entries);
444 pcmk__add_arg_group(context, "source", "Data Source:",
445 "Show data source options", source_entries);
446
447 return context;
448 }
449
450 int
451 main(int argc, char **argv)
452 {
453 int rc = pcmk_rc_ok;
454 pcmk_scheduler_t *scheduler = NULL;
455 pcmk__output_t *out = NULL;
456
457 GError *error = NULL;
458
459 GOptionGroup *output_group = NULL;
460 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
461 gchar **processed_args = pcmk__cmdline_preproc(argv, "bdefgiqrtuwxDFGINOP");
462 GOptionContext *context = build_arg_context(args, &output_group);
463
464 options.injections = calloc(1, sizeof(pcmk_injections_t));
465 if (options.injections == NULL) {
466 rc = ENOMEM;
467 goto done;
468 }
469
470
471 options.xml_file = strdup("-");
472
473 pcmk__register_formats(output_group, formats);
474 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
475 exit_code = CRM_EX_USAGE;
476 goto done;
477 }
478
479 pcmk__cli_init_logging("crm_simulate", args->verbosity);
480
481 rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
482 if (rc != pcmk_rc_ok) {
483 fprintf(stderr, "Error creating output format %s: %s\n",
484 args->output_ty, pcmk_rc_str(rc));
485 exit_code = CRM_EX_ERROR;
486 goto done;
487 }
488
489 if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches) &&
490 !pcmk_is_set(options.flags, pcmk_sim_show_scores) &&
491 !pcmk_is_set(options.flags, pcmk_sim_show_utilization)) {
492 pcmk__force_args(context, &error, "%s --text-fancy", g_get_prgname());
493 } else if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) {
494 pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname());
495 }
496
497 pe__register_messages(out);
498 pcmk__register_lib_messages(out);
499
500 out->quiet = args->quiet;
501
502 if (args->version) {
503 out->version(out, false);
504 goto done;
505 }
506
507 if (args->verbosity > 0) {
508 options.flags |= pcmk_sim_verbose;
509
510 #ifdef PCMK__COMPAT_2_0
511
512 close(STDERR_FILENO);
513 dup2(STDOUT_FILENO, STDERR_FILENO);
514 #endif
515 }
516
517 scheduler = pe_new_working_set();
518 if (scheduler == NULL) {
519 rc = ENOMEM;
520 g_set_error(&error, PCMK__RC_ERROR, rc,
521 "Could not allocate scheduler data");
522 goto done;
523 }
524
525 if (pcmk_is_set(options.flags, pcmk_sim_show_scores)) {
526 pe__set_working_set_flags(scheduler, pcmk_sched_output_scores);
527 }
528 if (pcmk_is_set(options.flags, pcmk_sim_show_utilization)) {
529 pe__set_working_set_flags(scheduler, pcmk_sched_show_utilization);
530 }
531 pe__set_working_set_flags(scheduler, pcmk_sched_no_compat);
532
533 if (options.test_dir != NULL) {
534 scheduler->priv = out;
535 pcmk__profile_dir(options.test_dir, options.repeat, scheduler,
536 options.use_date);
537 rc = pcmk_rc_ok;
538 goto done;
539 }
540
541 rc = setup_input(out, options.xml_file,
542 options.store? options.xml_file : options.output_file,
543 &error);
544 if (rc != pcmk_rc_ok) {
545 goto done;
546 }
547
548 rc = pcmk__simulate(scheduler, out, options.injections, options.flags,
549 section_opts, options.use_date, options.input_file,
550 options.graph_file, options.dot_file);
551
552 done:
553 pcmk__output_and_clear_error(&error, NULL);
554
555
556 free(options.dot_file);
557 free(options.graph_file);
558 g_free(options.input_file);
559 g_free(options.output_file);
560 g_free(options.test_dir);
561 free(options.use_date);
562 free(options.xml_file);
563
564 pcmk_free_injections(options.injections);
565 pcmk__free_arg_context(context);
566 g_strfreev(processed_args);
567
568 if (scheduler != NULL) {
569 pe_free_working_set(scheduler);
570 }
571
572 fflush(stderr);
573
574 if (temp_shadow) {
575 unlink(temp_shadow);
576 free(temp_shadow);
577 }
578
579 if (rc != pcmk_rc_ok) {
580 exit_code = pcmk_rc2exitc(rc);
581 }
582
583 if (out != NULL) {
584 out->finish(out, exit_code, true, NULL);
585 pcmk__output_free(out);
586 }
587
588 pcmk__unregister_formats();
589 crm_exit(exit_code);
590 }