1 /*
2  * Copyright 2009-2021 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 General Public License version 2
7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
8  */
9 
10 #include <crm_internal.h>
11 
12 #include <stdio.h>
13 #include <unistd.h>
14 #include <stdlib.h>
15 #include <time.h>
16 
17 #include <sys/stat.h>
18 #include <sys/param.h>
19 #include <sys/types.h>
20 #include <dirent.h>
21 
22 #include <crm/crm.h>
23 #include <crm/cib.h>
24 #include <crm/common/cmdline_internal.h>
25 #include <crm/common/output_internal.h>
26 #include <crm/common/output.h>
27 #include <crm/common/util.h>
28 #include <crm/common/iso8601.h>
29 #include <crm/pengine/status.h>
30 #include <pacemaker-internal.h>
31 
32 #define SUMMARY "crm_simulate - simulate a Pacemaker cluster's response to events"
33 
34 struct {
35     gboolean all_actions;
36     char *dot_file;
37     char *graph_file;
38     gchar *input_file;
39     guint modified;
40     GList *node_up;
41     GList *node_down;
42     GList *node_fail;
43     GList *op_fail;
44     GList *op_inject;
45     gchar *output_file;
46     gboolean print_pending;
47     gboolean process;
48     char *quorum;
49     long long repeat;
50     gboolean show_attrs;
51     gboolean show_failcounts;
52     gboolean show_scores;
53     gboolean show_utilization;
54     gboolean simulate;
55     gboolean store;
56     gchar *test_dir;
57     GList *ticket_grant;
58     GList *ticket_revoke;
59     GList *ticket_standby;
60     GList *ticket_activate;
61     char *use_date;
62     char *watchdog;
63     char *xml_file;
64 } options = {
65     .print_pending = TRUE,
66     .repeat = 1
67 };
68 
69 cib_t *global_cib = NULL;
70 bool action_numbers = FALSE;
71 char *temp_shadow = NULL;
72 extern gboolean bringing_nodes_online;
73 crm_exit_t exit_code = CRM_EX_OK;
74 
75 #define INDENT "                                   "
76 
77 static pcmk__supported_format_t formats[] = {
78     PCMK__SUPPORTED_FORMAT_NONE,
79     PCMK__SUPPORTED_FORMAT_TEXT,
80     PCMK__SUPPORTED_FORMAT_XML,
81     { NULL, NULL, NULL }
82 };
83 
84 static gboolean
in_place_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)85 in_place_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
86     options.store = TRUE;
87     options.process = TRUE;
88     options.simulate = TRUE;
89     return TRUE;
90 }
91 
92 static gboolean
live_check_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)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     return TRUE;
100 }
101 
102 static gboolean
node_down_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)103 node_down_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
104     options.modified++;
105     options.node_down = g_list_append(options.node_down, (gchar *) g_strdup(optarg));
106     return TRUE;
107 }
108 
109 static gboolean
node_fail_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)110 node_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
111     options.modified++;
112     options.node_fail = g_list_append(options.node_fail, (gchar *) g_strdup(optarg));
113     return TRUE;
114 }
115 
116 static gboolean
node_up_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)117 node_up_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
118     options.modified++;
119     bringing_nodes_online = TRUE;
120     options.node_up = g_list_append(options.node_up, (gchar *) g_strdup(optarg));
121     return TRUE;
122 }
123 
124 static gboolean
op_fail_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)125 op_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
126     options.process = TRUE;
127     options.simulate = TRUE;
128     options.op_fail = g_list_append(options.op_fail, (gchar *) g_strdup(optarg));
129     return TRUE;
130 }
131 
132 static gboolean
op_inject_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)133 op_inject_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
134     options.modified++;
135     options.op_inject = g_list_append(options.op_inject, (gchar *) g_strdup(optarg));
136     return TRUE;
137 }
138 
139 static gboolean
quorum_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)140 quorum_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
141     if (options.quorum) {
142         free(options.quorum);
143     }
144 
145     options.modified++;
146     options.quorum = strdup(optarg);
147     return TRUE;
148 }
149 
150 static gboolean
save_dotfile_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)151 save_dotfile_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
152     if (options.dot_file) {
153         free(options.dot_file);
154     }
155 
156     options.process = TRUE;
157     options.dot_file = strdup(optarg);
158     return TRUE;
159 }
160 
161 static gboolean
save_graph_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)162 save_graph_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
163     if (options.graph_file) {
164         free(options.graph_file);
165     }
166 
167     options.process = TRUE;
168     options.graph_file = strdup(optarg);
169     return TRUE;
170 }
171 
172 static gboolean
show_scores_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)173 show_scores_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
174     options.process = TRUE;
175     options.show_scores = TRUE;
176     return TRUE;
177 }
178 
179 static gboolean
simulate_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)180 simulate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
181     options.process = TRUE;
182     options.simulate = TRUE;
183     return TRUE;
184 }
185 
186 static gboolean
ticket_activate_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)187 ticket_activate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
188     options.modified++;
189     options.ticket_activate = g_list_append(options.ticket_activate, (gchar *) g_strdup(optarg));
190     return TRUE;
191 }
192 
193 static gboolean
ticket_grant_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)194 ticket_grant_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
195     options.modified++;
196     options.ticket_grant = g_list_append(options.ticket_grant, (gchar *) g_strdup(optarg));
197     return TRUE;
198 }
199 
200 static gboolean
ticket_revoke_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)201 ticket_revoke_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
202     options.modified++;
203     options.ticket_revoke = g_list_append(options.ticket_revoke, (gchar *) g_strdup(optarg));
204     return TRUE;
205 }
206 
207 static gboolean
ticket_standby_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)208 ticket_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
209     options.modified++;
210     options.ticket_standby = g_list_append(options.ticket_standby, (gchar *) g_strdup(optarg));
211     return TRUE;
212 }
213 
214 static gboolean
utilization_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)215 utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
216     options.process = TRUE;
217     options.show_utilization = TRUE;
218     return TRUE;
219 }
220 
221 static gboolean
watchdog_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)222 watchdog_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
223     if (options.watchdog) {
224         free(options.watchdog);
225     }
226 
227     options.modified++;
228     options.watchdog = strdup(optarg);
229     return TRUE;
230 }
231 
232 static gboolean
xml_file_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)233 xml_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
234     if (options.xml_file) {
235         free(options.xml_file);
236     }
237 
238     options.xml_file = strdup(optarg);
239     return TRUE;
240 }
241 
242 static gboolean
xml_pipe_cb(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)243 xml_pipe_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
244     if (options.xml_file) {
245         free(options.xml_file);
246     }
247 
248     options.xml_file = strdup("-");
249     return TRUE;
250 }
251 
252 static GOptionEntry operation_entries[] = {
253     { "run", 'R', 0, G_OPTION_ARG_NONE, &options.process,
254       "Process the supplied input and show what actions the cluster will take in response",
255       NULL },
256     { "simulate", 'S', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, simulate_cb,
257       "Like --run, but also simulate taking those actions and show the resulting new status",
258       NULL },
259     { "in-place", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, in_place_cb,
260       "Like --simulate, but also store the results back to the input file",
261       NULL },
262     { "show-attrs", 'A', 0, G_OPTION_ARG_NONE, &options.show_attrs,
263       "Show node attributes",
264       NULL },
265     { "show-failcounts", 'c', 0, G_OPTION_ARG_NONE, &options.show_failcounts,
266       "Show resource fail counts",
267       NULL },
268     { "show-scores", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_scores_cb,
269       "Show allocation scores",
270       NULL },
271     { "show-utilization", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb,
272       "Show utilization information",
273       NULL },
274     { "profile", 'P', 0, G_OPTION_ARG_FILENAME, &options.test_dir,
275       "Process all the XML files in the named directory to create profiling data",
276       "DIR" },
277     { "repeat", 'N', 0, G_OPTION_ARG_INT, &options.repeat,
278       "With --profile, repeat each test N times and print timings",
279       "N" },
280     /* Deprecated */
281     { "pending", 'j', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.print_pending,
282       "Display pending state if 'record-pending' is enabled",
283       NULL },
284 
285     { NULL }
286 };
287 
288 static GOptionEntry synthetic_entries[] = {
289     { "node-up", 'u', 0, G_OPTION_ARG_CALLBACK, node_up_cb,
290       "Simulate bringing a node online",
291       "NODE" },
292     { "node-down", 'd', 0, G_OPTION_ARG_CALLBACK, node_down_cb,
293       "Simulate taking a node offline",
294       "NODE" },
295     { "node-fail", 'f', 0, G_OPTION_ARG_CALLBACK, node_fail_cb,
296       "Simulate a node failing",
297       "NODE" },
298     { "op-inject", 'i', 0, G_OPTION_ARG_CALLBACK, op_inject_cb,
299       "Generate a failure for the cluster to react to in the simulation.\n"
300       INDENT "See `Operation Specification` help for more information.",
301       "OPSPEC" },
302     { "op-fail", 'F', 0, G_OPTION_ARG_CALLBACK, op_fail_cb,
303       "If the specified task occurs during the simulation, have it fail with return code ${rc}.\n"
304       INDENT "The transition will normally stop at the failed action.\n"
305       INDENT "Save the result with --save-output and re-run with --xml-file.\n"
306       INDENT "See `Operation Specification` help for more information.",
307       "OPSPEC" },
308     { "set-datetime", 't', 0, G_OPTION_ARG_STRING, &options.use_date,
309       "Set date/time (ISO 8601 format, see https://en.wikipedia.org/wiki/ISO_8601)",
310       "DATETIME" },
311     { "quorum", 'q', 0, G_OPTION_ARG_CALLBACK, quorum_cb,
312       "Set to '1' (or 'true') to indicate cluster has quorum",
313       "QUORUM" },
314     { "watchdog", 'w', 0, G_OPTION_ARG_CALLBACK, watchdog_cb,
315       "Set to '1' (or 'true') to indicate cluster has an active watchdog device",
316       "DEVICE" },
317     { "ticket-grant", 'g', 0, G_OPTION_ARG_CALLBACK, ticket_grant_cb,
318       "Simulate granting a ticket",
319       "TICKET" },
320     { "ticket-revoke", 'r', 0, G_OPTION_ARG_CALLBACK, ticket_revoke_cb,
321       "Simulate revoking a ticket",
322       "TICKET" },
323     { "ticket-standby", 'b', 0, G_OPTION_ARG_CALLBACK, ticket_standby_cb,
324       "Simulate making a ticket standby",
325       "TICKET" },
326     { "ticket-activate", 'e', 0, G_OPTION_ARG_CALLBACK, ticket_activate_cb,
327       "Simulate activating a ticket",
328       "TICKET" },
329 
330     { NULL }
331 };
332 
333 static GOptionEntry artifact_entries[] = {
334     { "save-input", 'I', 0, G_OPTION_ARG_FILENAME, &options.input_file,
335       "Save the input configuration to the named file",
336       "FILE" },
337     { "save-output", 'O', 0, G_OPTION_ARG_FILENAME, &options.output_file,
338       "Save the output configuration to the named file",
339       "FILE" },
340     { "save-graph", 'G', 0, G_OPTION_ARG_CALLBACK, save_graph_cb,
341       "Save the transition graph (XML format) to the named file",
342       "FILE" },
343     { "save-dotfile", 'D', 0, G_OPTION_ARG_CALLBACK, save_dotfile_cb,
344       "Save the transition graph (DOT format) to the named file",
345       "FILE" },
346     { "all-actions", 'a', 0, G_OPTION_ARG_NONE, &options.all_actions,
347       "Display all possible actions in DOT graph (even if not part of transition)",
348       NULL },
349 
350     { NULL }
351 };
352 
353 static GOptionEntry source_entries[] = {
354     { "live-check", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, live_check_cb,
355       "Connect to CIB manager and use the current CIB contents as input",
356       NULL },
357     { "xml-file", 'x', 0, G_OPTION_ARG_CALLBACK, xml_file_cb,
358       "Retrieve XML from the named file",
359       "FILE" },
360     { "xml-pipe", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, xml_pipe_cb,
361       "Retrieve XML from stdin",
362       NULL },
363 
364     { NULL }
365 };
366 
367 static void
get_date(pe_working_set_t * data_set,bool print_original,char * use_date)368 get_date(pe_working_set_t *data_set, bool print_original, char *use_date)
369 {
370     pcmk__output_t *out = data_set->priv;
371     time_t original_date = 0;
372 
373     crm_element_value_epoch(data_set->input, "execution-date", &original_date);
374 
375     if (use_date) {
376         data_set->now = crm_time_new(use_date);
377         out->info(out, "Setting effective cluster time: %s", use_date);
378         crm_time_log(LOG_NOTICE, "Pretending 'now' is", data_set->now,
379                      crm_time_log_date | crm_time_log_timeofday);
380 
381 
382     } else if (original_date) {
383 
384         data_set->now = crm_time_new(NULL);
385         crm_time_set_timet(data_set->now, &original_date);
386 
387         if (print_original) {
388             char *when = crm_time_as_string(data_set->now,
389                             crm_time_log_date|crm_time_log_timeofday);
390 
391             out->info(out, "Using the original execution date of: %s", when);
392             free(when);
393         }
394     }
395 }
396 
397 static void
print_cluster_status(pe_working_set_t * data_set,unsigned int show_opts)398 print_cluster_status(pe_working_set_t * data_set, unsigned int show_opts)
399 {
400     pcmk__output_t *out = data_set->priv;
401     int rc = pcmk_rc_no_output;
402     GList *all = NULL;
403 
404     all = g_list_prepend(all, (gpointer) "*");
405 
406     rc = out->message(out, "node-list", data_set->nodes, all, all, show_opts);
407     PCMK__OUTPUT_SPACER_IF(out, rc == pcmk_rc_ok);
408     rc = out->message(out, "resource-list", data_set, show_opts | pcmk_show_inactive_rscs,
409                       FALSE, all, all, FALSE);
410 
411     if (options.show_attrs) {
412         rc = out->message(out, "node-attribute-list", data_set,
413                           0, rc == pcmk_rc_ok, all, all);
414     }
415 
416     if (options.show_failcounts) {
417         rc = out->message(out, "node-summary", data_set, all, all,
418                           0, show_opts, rc == pcmk_rc_ok);
419 
420         out->message(out, "failed-action-list", data_set, all, all,
421                      rc == pcmk_rc_ok);
422     }
423 
424     g_list_free(all);
425 }
426 
427 static char *
create_action_name(pe_action_t * action)428 create_action_name(pe_action_t *action)
429 {
430     char *action_name = NULL;
431     const char *prefix = "";
432     const char *action_host = NULL;
433     const char *clone_name = NULL;
434     const char *task = action->task;
435 
436     if (action->node) {
437         action_host = action->node->details->uname;
438     } else if (!pcmk_is_set(action->flags, pe_action_pseudo)) {
439         action_host = "<none>";
440     }
441 
442     if (pcmk__str_eq(action->task, RSC_CANCEL, pcmk__str_casei)) {
443         prefix = "Cancel ";
444         task = action->cancel_task;
445     }
446 
447     if (action->rsc && action->rsc->clone_name) {
448         clone_name = action->rsc->clone_name;
449     }
450 
451     if (clone_name) {
452         char *key = NULL;
453         guint interval_ms = 0;
454 
455         if (pcmk__guint_from_hash(action->meta,
456                                   XML_LRM_ATTR_INTERVAL_MS, 0,
457                                   &interval_ms) != pcmk_rc_ok) {
458             interval_ms = 0;
459         }
460 
461         if (pcmk__strcase_any_of(action->task, RSC_NOTIFY, RSC_NOTIFIED, NULL)) {
462             const char *n_type = g_hash_table_lookup(action->meta, "notify_key_type");
463             const char *n_task = g_hash_table_lookup(action->meta, "notify_key_operation");
464 
465             CRM_ASSERT(n_type != NULL);
466             CRM_ASSERT(n_task != NULL);
467             key = pcmk__notify_key(clone_name, n_type, n_task);
468 
469         } else {
470             key = pcmk__op_key(clone_name, task, interval_ms);
471         }
472 
473         if (action_host) {
474             action_name = crm_strdup_printf("%s%s %s", prefix, key, action_host);
475         } else {
476             action_name = crm_strdup_printf("%s%s", prefix, key);
477         }
478         free(key);
479 
480     } else if (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei)) {
481         const char *op = g_hash_table_lookup(action->meta, "stonith_action");
482 
483         action_name = crm_strdup_printf("%s%s '%s' %s", prefix, action->task, op, action_host);
484 
485     } else if (action->rsc && action_host) {
486         action_name = crm_strdup_printf("%s%s %s", prefix, action->uuid, action_host);
487 
488     } else if (action_host) {
489         action_name = crm_strdup_printf("%s%s %s", prefix, action->task, action_host);
490 
491     } else {
492         action_name = crm_strdup_printf("%s", action->uuid);
493     }
494 
495     if (action_numbers) { // i.e. verbose
496         char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id);
497 
498         free(action_name);
499         action_name = with_id;
500     }
501     return action_name;
502 }
503 
504 static bool
create_dotfile(pe_working_set_t * data_set,const char * dot_file,gboolean all_actions,GError ** error)505 create_dotfile(pe_working_set_t * data_set, const char *dot_file, gboolean all_actions,
506                GError **error)
507 {
508     GList *gIter = NULL;
509     FILE *dot_strm = fopen(dot_file, "w");
510 
511     if (dot_strm == NULL) {
512         g_set_error(error, PCMK__RC_ERROR, errno,
513                     "Could not open %s for writing: %s", dot_file,
514                     pcmk_rc_str(errno));
515         return false;
516     }
517 
518     fprintf(dot_strm, " digraph \"g\" {\n");
519     for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
520         pe_action_t *action = (pe_action_t *) gIter->data;
521         const char *style = "dashed";
522         const char *font = "black";
523         const char *color = "black";
524         char *action_name = create_action_name(action);
525 
526         crm_trace("Action %d: %s %s %p", action->id, action_name, action->uuid, action);
527 
528         if (pcmk_is_set(action->flags, pe_action_pseudo)) {
529             font = "orange";
530         }
531 
532         if (pcmk_is_set(action->flags, pe_action_dumped)) {
533             style = "bold";
534             color = "green";
535 
536         } else if ((action->rsc != NULL)
537                    && !pcmk_is_set(action->rsc->flags, pe_rsc_managed)) {
538             color = "red";
539             font = "purple";
540             if (all_actions == FALSE) {
541                 goto do_not_write;
542             }
543 
544         } else if (pcmk_is_set(action->flags, pe_action_optional)) {
545             color = "blue";
546             if (all_actions == FALSE) {
547                 goto do_not_write;
548             }
549 
550         } else {
551             color = "red";
552             CRM_CHECK(!pcmk_is_set(action->flags, pe_action_runnable), ;);
553         }
554 
555         pe__set_action_flags(action, pe_action_dumped);
556         crm_trace("\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]",
557                 action_name, style, color, font);
558         fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n",
559                 action_name, style, color, font);
560   do_not_write:
561         free(action_name);
562     }
563 
564     for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
565         pe_action_t *action = (pe_action_t *) gIter->data;
566 
567         GList *gIter2 = NULL;
568 
569         for (gIter2 = action->actions_before; gIter2 != NULL; gIter2 = gIter2->next) {
570             pe_action_wrapper_t *before = (pe_action_wrapper_t *) gIter2->data;
571 
572             char *before_name = NULL;
573             char *after_name = NULL;
574             const char *style = "dashed";
575             gboolean optional = TRUE;
576 
577             if (before->state == pe_link_dumped) {
578                 optional = FALSE;
579                 style = "bold";
580             } else if (pcmk_is_set(action->flags, pe_action_pseudo)
581                        && (before->type & pe_order_stonith_stop)) {
582                 continue;
583             } else if (before->type == pe_order_none) {
584                 continue;
585             } else if (pcmk_is_set(before->action->flags, pe_action_dumped)
586                        && pcmk_is_set(action->flags, pe_action_dumped)
587                        && before->type != pe_order_load) {
588                 optional = FALSE;
589             }
590 
591             if (all_actions || optional == FALSE) {
592                 before_name = create_action_name(before->action);
593                 after_name = create_action_name(action);
594                 crm_trace("\"%s\" -> \"%s\" [ style = %s]",
595                         before_name, after_name, style);
596                 fprintf(dot_strm, "\"%s\" -> \"%s\" [ style = %s]\n",
597                         before_name, after_name, style);
598                 free(before_name);
599                 free(after_name);
600             }
601         }
602     }
603 
604     fprintf(dot_strm, "}\n");
605     fflush(dot_strm);
606     fclose(dot_strm);
607     return true;
608 }
609 
610 static int
setup_input(const char * input,const char * output,GError ** error)611 setup_input(const char *input, const char *output, GError **error)
612 {
613     int rc = pcmk_rc_ok;
614     cib_t *cib_conn = NULL;
615     xmlNode *cib_object = NULL;
616     char *local_output = NULL;
617 
618     if (input == NULL) {
619         /* Use live CIB */
620         cib_conn = cib_new();
621         rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
622         rc = pcmk_legacy2rc(rc);
623 
624         if (rc == pcmk_rc_ok) {
625             rc = cib_conn->cmds->query(cib_conn, NULL, &cib_object, cib_scope_local | cib_sync_call);
626         }
627 
628         cib_conn->cmds->signoff(cib_conn);
629         cib_delete(cib_conn);
630         cib_conn = NULL;
631 
632         if (rc != pcmk_rc_ok) {
633             rc = pcmk_legacy2rc(rc);
634             g_set_error(error, PCMK__RC_ERROR, rc,
635                         "Live CIB query failed: %s (%d)", pcmk_rc_str(rc), rc);
636             return rc;
637 
638         } else if (cib_object == NULL) {
639             g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_NOINPUT,
640                         "Live CIB query failed: empty result");
641             return pcmk_rc_no_input;
642         }
643 
644     } else if (pcmk__str_eq(input, "-", pcmk__str_casei)) {
645         cib_object = filename2xml(NULL);
646 
647     } else {
648         cib_object = filename2xml(input);
649     }
650 
651     if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) {
652         create_xml_node(cib_object, XML_CIB_TAG_STATUS);
653     }
654 
655     if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
656         free_xml(cib_object);
657         return pcmk_rc_transform_failed;
658     }
659 
660     if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
661         free_xml(cib_object);
662         return pcmk_rc_schema_validation;
663     }
664 
665     if (output == NULL) {
666         char *pid = pcmk__getpid_s();
667 
668         local_output = get_shadow_file(pid);
669         temp_shadow = strdup(local_output);
670         output = local_output;
671         free(pid);
672     }
673 
674     rc = write_xml_file(cib_object, output, FALSE);
675     free_xml(cib_object);
676     cib_object = NULL;
677 
678     if (rc < 0) {
679         rc = pcmk_legacy2rc(rc);
680         g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_CANTCREAT,
681                     "Could not create '%s': %s", output, pcmk_rc_str(rc));
682         return rc;
683     } else {
684         setenv("CIB_file", output, 1);
685         free(local_output);
686         return pcmk_rc_ok;
687     }
688 }
689 
690 static void
profile_one(const char * xml_file,long long repeat,pe_working_set_t * data_set,char * use_date)691 profile_one(const char *xml_file, long long repeat, pe_working_set_t *data_set, char *use_date)
692 {
693     pcmk__output_t *out = data_set->priv;
694     xmlNode *cib_object = NULL;
695     clock_t start = 0;
696     clock_t end;
697 
698     cib_object = filename2xml(xml_file);
699     start = clock();
700 
701     if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) {
702         create_xml_node(cib_object, XML_CIB_TAG_STATUS);
703     }
704 
705 
706     if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
707         free_xml(cib_object);
708         return;
709     }
710 
711     if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
712         free_xml(cib_object);
713         return;
714     }
715 
716     for (int i = 0; i < repeat; ++i) {
717         xmlNode *input = (repeat == 1)? cib_object : copy_xml(cib_object);
718 
719         data_set->input = input;
720         get_date(data_set, false, use_date);
721         pcmk__schedule_actions(data_set, input, NULL);
722         pe_reset_working_set(data_set);
723     }
724 
725     end = clock();
726     out->message(out, "profile", xml_file, start, end);
727 }
728 
729 #ifndef FILENAME_MAX
730 #  define FILENAME_MAX 512
731 #endif
732 
733 static void
profile_all(const char * dir,long long repeat,pe_working_set_t * data_set,char * use_date)734 profile_all(const char *dir, long long repeat, pe_working_set_t *data_set, char *use_date)
735 {
736     pcmk__output_t *out = data_set->priv;
737     struct dirent **namelist;
738 
739     int file_num = scandir(dir, &namelist, 0, alphasort);
740 
741     if (file_num > 0) {
742         struct stat prop;
743         char buffer[FILENAME_MAX];
744 
745         out->begin_list(out, NULL, NULL, "Timings");
746 
747         while (file_num--) {
748             if ('.' == namelist[file_num]->d_name[0]) {
749                 free(namelist[file_num]);
750                 continue;
751 
752             } else if (!pcmk__ends_with_ext(namelist[file_num]->d_name,
753                                             ".xml")) {
754                 free(namelist[file_num]);
755                 continue;
756             }
757             snprintf(buffer, sizeof(buffer), "%s/%s", dir, namelist[file_num]->d_name);
758             if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
759                 profile_one(buffer, repeat, data_set, use_date);
760             }
761             free(namelist[file_num]);
762         }
763         free(namelist);
764 
765         out->end_list(out);
766     }
767 }
768 
769 PCMK__OUTPUT_ARGS("profile", "const char *", "clock_t", "clock_t")
770 static int
profile_default(pcmk__output_t * out,va_list args)771 profile_default(pcmk__output_t *out, va_list args) {
772     const char *xml_file = va_arg(args, const char *);
773     clock_t start = va_arg(args, clock_t);
774     clock_t end = va_arg(args, clock_t);
775 
776     out->list_item(out, NULL, "Testing %s ... %.2f secs", xml_file,
777                    (end - start) / (float) CLOCKS_PER_SEC);
778 
779     return pcmk_rc_ok;
780 }
781 
782 PCMK__OUTPUT_ARGS("profile", "const char *", "clock_t", "clock_t")
783 static int
profile_xml(pcmk__output_t * out,va_list args)784 profile_xml(pcmk__output_t *out, va_list args) {
785     const char *xml_file = va_arg(args, const char *);
786     clock_t start = va_arg(args, clock_t);
787     clock_t end = va_arg(args, clock_t);
788 
789     char *duration = pcmk__ftoa((end - start) / (float) CLOCKS_PER_SEC);
790 
791     pcmk__output_create_xml_node(out, "timing",
792                                  "file", xml_file,
793                                  "duration", duration,
794                                  NULL);
795 
796     free(duration);
797     return pcmk_rc_ok;
798 }
799 
800 static pcmk__message_entry_t fmt_functions[] = {
801     { "profile", "default", profile_default, },
802     { "profile", "xml", profile_xml },
803 
804     { NULL }
805 };
806 
807 static void
crm_simulate_register_messages(pcmk__output_t * out)808 crm_simulate_register_messages(pcmk__output_t *out) {
809     pcmk__register_messages(out, fmt_functions);
810 }
811 
812 static GOptionContext *
build_arg_context(pcmk__common_args_t * args,GOptionGroup ** group)813 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
814     GOptionContext *context = NULL;
815 
816     GOptionEntry extra_prog_entries[] = {
817         { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
818           "Display only essential output",
819           NULL },
820 
821         { NULL }
822     };
823 
824     const char *description = "Operation Specification:\n\n"
825                               "The OPSPEC in any command line option is of the form\n"
826                               "${resource}_${task}_${interval_in_ms}@${node}=${rc}\n"
827                               "(memcached_monitor_20000@bart.example.com=7, for example).\n"
828                               "${rc} is an OCF return code.  For more information on these\n"
829                               "return codes, refer to https://clusterlabs.org/pacemaker/doc/2.1/Pacemaker_Administration/html/agents.html#ocf-return-codes\n\n"
830                               "Examples:\n\n"
831                               "Pretend a recurring monitor action found memcached stopped on node\n"
832                               "fred.example.com and, during recovery, that the memcached stop\n"
833                               "action failed:\n\n"
834                               "\tcrm_simulate -LS --op-inject memcached:0_monitor_20000@bart.example.com=7 "
835                               "--op-fail memcached:0_stop_0@fred.example.com=1 --save-output /tmp/memcached-test.xml\n\n"
836                               "Now see what the reaction to the stop failed would be:\n\n"
837                               "\tcrm_simulate -S --xml-file /tmp/memcached-test.xml\n\n";
838 
839     context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
840     pcmk__add_main_args(context, extra_prog_entries);
841     g_option_context_set_description(context, description);
842 
843     pcmk__add_arg_group(context, "operations", "Operations:",
844                         "Show operations options", operation_entries);
845     pcmk__add_arg_group(context, "synthetic", "Synthetic Cluster Events:",
846                         "Show synthetic cluster event options", synthetic_entries);
847     pcmk__add_arg_group(context, "artifact", "Artifact Options:",
848                         "Show artifact options", artifact_entries);
849     pcmk__add_arg_group(context, "source", "Data Source:",
850                         "Show data source options", source_entries);
851 
852     return context;
853 }
854 
855 int
main(int argc,char ** argv)856 main(int argc, char **argv)
857 {
858     int printed = pcmk_rc_no_output;
859     int rc = pcmk_rc_ok;
860     pe_working_set_t *data_set = NULL;
861     pcmk__output_t *out = NULL;
862     xmlNode *input = NULL;
863 
864     GError *error = NULL;
865 
866     GOptionGroup *output_group = NULL;
867     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
868     gchar **processed_args = pcmk__cmdline_preproc(argv, "bdefgiqrtuwxDFGINOP");
869     GOptionContext *context = build_arg_context(args, &output_group);
870 
871     /* This must come before g_option_context_parse_strv. */
872     options.xml_file = strdup("-");
873 
874     pcmk__register_formats(output_group, formats);
875     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
876         exit_code = CRM_EX_USAGE;
877         goto done;
878     }
879 
880     pcmk__cli_init_logging("crm_simulate", args->verbosity);
881 
882     rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
883     if (rc != pcmk_rc_ok) {
884         fprintf(stderr, "Error creating output format %s: %s\n",
885                 args->output_ty, pcmk_rc_str(rc));
886         exit_code = CRM_EX_ERROR;
887         goto done;
888     }
889 
890     if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches) &&
891         !options.show_scores && !options.show_utilization) {
892         pcmk__force_args(context, &error, "%s --text-fancy", g_get_prgname());
893     } else if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) {
894         pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname());
895     }
896 
897     crm_simulate_register_messages(out);
898     pe__register_messages(out);
899     pcmk__register_lib_messages(out);
900 
901     out->quiet = args->quiet;
902 
903     if (args->version) {
904         out->version(out, false);
905         goto done;
906     }
907 
908     if (args->verbosity > 0) {
909 #ifdef PCMK__COMPAT_2_0
910         /* Redirect stderr to stdout so we can grep the output */
911         close(STDERR_FILENO);
912         dup2(STDOUT_FILENO, STDERR_FILENO);
913 #endif
914         action_numbers = TRUE;
915     }
916 
917     data_set = pe_new_working_set();
918     if (data_set == NULL) {
919         rc = ENOMEM;
920         g_set_error(&error, PCMK__RC_ERROR, rc, "Could not allocate working set");
921         goto done;
922     }
923 
924     if (options.show_scores) {
925         pe__set_working_set_flags(data_set, pe_flag_show_scores);
926     }
927     if (options.show_utilization) {
928         pe__set_working_set_flags(data_set, pe_flag_show_utilization);
929     }
930     pe__set_working_set_flags(data_set, pe_flag_no_compat);
931 
932     if (options.test_dir != NULL) {
933         data_set->priv = out;
934         profile_all(options.test_dir, options.repeat, data_set, options.use_date);
935         rc = pcmk_rc_ok;
936         goto done;
937     }
938 
939     rc = setup_input(options.xml_file, options.store ? options.xml_file : options.output_file, &error);
940     if (rc != pcmk_rc_ok) {
941         goto done;
942     }
943 
944     global_cib = cib_new();
945     rc = global_cib->cmds->signon(global_cib, crm_system_name, cib_command);
946     if (rc != pcmk_rc_ok) {
947         rc = pcmk_legacy2rc(rc);
948         g_set_error(&error, PCMK__RC_ERROR, rc,
949                     "Could not connect to the CIB: %s", pcmk_rc_str(rc));
950         goto done;
951     }
952 
953     rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call | cib_scope_local);
954     if (rc != pcmk_rc_ok) {
955         rc = pcmk_legacy2rc(rc);
956         g_set_error(&error, PCMK__RC_ERROR, rc,
957                     "Could not get local CIB: %s", pcmk_rc_str(rc));
958         goto done;
959     }
960 
961     data_set->input = input;
962     data_set->priv = out;
963     get_date(data_set, true, options.use_date);
964     if(options.xml_file) {
965         pe__set_working_set_flags(data_set, pe_flag_sanitized);
966     }
967     if (options.show_scores) {
968         pe__set_working_set_flags(data_set, pe_flag_show_scores);
969     }
970     if (options.show_utilization) {
971         pe__set_working_set_flags(data_set, pe_flag_show_utilization);
972     }
973     cluster_status(data_set);
974 
975     if (!out->is_quiet(out)) {
976         unsigned int show_opts = options.print_pending ? pcmk_show_pending : 0;
977 
978         if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) {
979             printed = out->message(out, "maint-mode", data_set->flags);
980         }
981 
982         if (data_set->disabled_resources || data_set->blocked_resources) {
983             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
984             printed = out->info(out, "%d of %d resource instances DISABLED and %d BLOCKED "
985                                 "from further action due to failure",
986                                 data_set->disabled_resources, data_set->ninstances,
987                                 data_set->blocked_resources);
988         }
989 
990         PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
991         /* Most formatted output headers use caps for each word, but this one
992          * only has the first word capitalized for compatibility with pcs.
993          */
994         out->begin_list(out, NULL, NULL, "Current cluster status");
995         print_cluster_status(data_set, show_opts);
996         out->end_list(out);
997         printed = pcmk_rc_ok;
998     }
999 
1000     if (options.modified) {
1001         PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
1002         modify_configuration(data_set, global_cib, options.quorum, options.watchdog, options.node_up,
1003                              options.node_down, options.node_fail, options.op_inject,
1004                              options.ticket_grant, options.ticket_revoke, options.ticket_standby,
1005                              options.ticket_activate);
1006         printed = pcmk_rc_ok;
1007 
1008         rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call);
1009         if (rc != pcmk_rc_ok) {
1010             rc = pcmk_legacy2rc(rc);
1011             g_set_error(&error, PCMK__RC_ERROR, rc,
1012                         "Could not get modified CIB: %s", pcmk_rc_str(rc));
1013             goto done;
1014         }
1015 
1016         cleanup_calculations(data_set);
1017         data_set->input = input;
1018         data_set->priv = out;
1019         get_date(data_set, true, options.use_date);
1020 
1021         if(options.xml_file) {
1022             pe__set_working_set_flags(data_set, pe_flag_sanitized);
1023         }
1024         if (options.show_scores) {
1025             pe__set_working_set_flags(data_set, pe_flag_show_scores);
1026         }
1027         if (options.show_utilization) {
1028             pe__set_working_set_flags(data_set, pe_flag_show_utilization);
1029         }
1030         cluster_status(data_set);
1031     }
1032 
1033     if (options.input_file != NULL) {
1034         rc = write_xml_file(input, options.input_file, FALSE);
1035         if (rc < 0) {
1036             rc = pcmk_legacy2rc(rc);
1037             g_set_error(&error, PCMK__RC_ERROR, rc,
1038                         "Could not create '%s': %s", options.input_file, pcmk_rc_str(rc));
1039             goto done;
1040         }
1041     }
1042 
1043     if (options.process || options.simulate) {
1044         crm_time_t *local_date = NULL;
1045         pcmk__output_t *logger_out = NULL;
1046 
1047         if (pcmk_all_flags_set(data_set->flags, pe_flag_show_scores|pe_flag_show_utilization)) {
1048             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
1049             out->begin_list(out, NULL, NULL, "Allocation Scores and Utilization Information");
1050             printed = pcmk_rc_ok;
1051         } else if (pcmk_is_set(data_set->flags, pe_flag_show_scores)) {
1052             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
1053             out->begin_list(out, NULL, NULL, "Allocation Scores");
1054             printed = pcmk_rc_ok;
1055         } else if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) {
1056             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
1057             out->begin_list(out, NULL, NULL, "Utilization Information");
1058             printed = pcmk_rc_ok;
1059         } else {
1060             logger_out = pcmk__new_logger();
1061             if (logger_out == NULL) {
1062                 goto done;
1063             }
1064 
1065             data_set->priv = logger_out;
1066         }
1067 
1068         pcmk__schedule_actions(data_set, input, local_date);
1069 
1070         if (logger_out == NULL) {
1071             out->end_list(out);
1072         } else {
1073             logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
1074             pcmk__output_free(logger_out);
1075             data_set->priv = out;
1076         }
1077 
1078         input = NULL;           /* Don't try and free it twice */
1079 
1080         if (options.graph_file != NULL) {
1081             write_xml_file(data_set->graph, options.graph_file, FALSE);
1082         }
1083 
1084         if (options.dot_file != NULL) {
1085             if (!create_dotfile(data_set, options.dot_file, options.all_actions, &error)) {
1086                 goto done;
1087             }
1088         }
1089 
1090         if (!out->is_quiet(out)) {
1091             GList *gIter = NULL;
1092 
1093             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
1094             out->begin_list(out, NULL, NULL, "Transition Summary");
1095 
1096             LogNodeActions(data_set);
1097             for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
1098                 pe_resource_t *rsc = (pe_resource_t *) gIter->data;
1099 
1100                 LogActions(rsc, data_set);
1101             }
1102 
1103             out->end_list(out);
1104             printed = pcmk_rc_ok;
1105         }
1106     }
1107 
1108     rc = pcmk_rc_ok;
1109 
1110     if (options.simulate) {
1111         PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
1112         if (run_simulation(data_set, global_cib, options.op_fail) != pcmk_rc_ok) {
1113             rc = pcmk_rc_error;
1114         }
1115 
1116         printed = pcmk_rc_ok;
1117 
1118         if (!out->is_quiet(out)) {
1119             get_date(data_set, true, options.use_date);
1120 
1121             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
1122             out->begin_list(out, NULL, NULL, "Revised Cluster Status");
1123 
1124             if (options.show_scores) {
1125                 pe__set_working_set_flags(data_set, pe_flag_show_scores);
1126             }
1127             if (options.show_utilization) {
1128                 pe__set_working_set_flags(data_set, pe_flag_show_utilization);
1129             }
1130 
1131             cluster_status(data_set);
1132             print_cluster_status(data_set, 0);
1133 
1134             out->end_list(out);
1135         }
1136     }
1137 
1138   done:
1139     pcmk__output_and_clear_error(error, NULL);
1140 
1141     /* There sure is a lot to free in options. */
1142     free(options.dot_file);
1143     free(options.graph_file);
1144     g_free(options.input_file);
1145     g_list_free_full(options.node_up, g_free);
1146     g_list_free_full(options.node_down, g_free);
1147     g_list_free_full(options.node_fail, g_free);
1148     g_list_free_full(options.op_fail, g_free);
1149     g_list_free_full(options.op_inject, g_free);
1150     g_free(options.output_file);
1151     free(options.quorum);
1152     g_free(options.test_dir);
1153     g_list_free_full(options.ticket_grant, g_free);
1154     g_list_free_full(options.ticket_revoke, g_free);
1155     g_list_free_full(options.ticket_standby, g_free);
1156     g_list_free_full(options.ticket_activate, g_free);
1157     free(options.use_date);
1158     free(options.watchdog);
1159     free(options.xml_file);
1160 
1161     pcmk__free_arg_context(context);
1162     g_strfreev(processed_args);
1163 
1164     if (data_set) {
1165         pe_free_working_set(data_set);
1166     }
1167 
1168     if (global_cib) {
1169         global_cib->cmds->signoff(global_cib);
1170         cib_delete(global_cib);
1171     }
1172 
1173     fflush(stderr);
1174 
1175     if (temp_shadow) {
1176         unlink(temp_shadow);
1177         free(temp_shadow);
1178     }
1179 
1180     if (rc != pcmk_rc_ok) {
1181         exit_code = pcmk_rc2exitc(rc);
1182     }
1183 
1184     if (out != NULL) {
1185         out->finish(out, exit_code, true, NULL);
1186         pcmk__output_free(out);
1187     }
1188 
1189     crm_exit(exit_code);
1190 }
1191