This source file includes following definitions.
- add_env_params
- add_stonith_device
- add_tolerance
- add_stonith_params
- set_tag
- build_arg_context
- request_fencing
- main
1
2
3
4
5
6
7
8
9
10 #include <crm_internal.h>
11
12 #include <sys/param.h>
13 #include <stdio.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17 #include <sys/utsname.h>
18
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <stdbool.h>
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include <crm/crm.h>
26 #include <crm/msg_xml.h>
27 #include <crm/common/ipc.h>
28 #include <crm/cluster/internal.h>
29 #include <crm/common/cmdline_internal.h>
30 #include <crm/common/output_internal.h>
31
32 #include <crm/stonith-ng.h>
33 #include <crm/fencing/internal.h>
34 #include <crm/cib.h>
35 #include <crm/pengine/status.h>
36
37 #include <crm/common/xml.h>
38 #include <pacemaker-internal.h>
39
40 #define SUMMARY "stonith_admin - Access the Pacemaker fencing API"
41
42 char action = 0;
43
44 struct {
45 gboolean as_nodeid;
46 gboolean broadcast;
47 gboolean cleanup;
48 gboolean installed;
49 gboolean metadata;
50 gboolean registered;
51 gboolean validate_cfg;
52 stonith_key_value_t *devices;
53 stonith_key_value_t *params;
54 int fence_level;
55 int timeout ;
56 int tolerance;
57 int delay;
58 char *agent;
59 char *confirm_host;
60 char *fence_host;
61 char *history;
62 char *last_fenced;
63 char *query;
64 char *reboot_host;
65 char *register_dev;
66 char *register_level;
67 char *targets;
68 char *terminate;
69 char *unfence_host;
70 char *unregister_dev;
71 char *unregister_level;
72 } options = {
73 .timeout = 120,
74 .delay = 0
75 };
76
77 gboolean add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
78 gboolean add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
79 gboolean add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
80 gboolean add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
81 gboolean set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
82
83 #define INDENT " "
84
85
86 static GOptionEntry defn_entries[] = {
87 { "register", 'R', 0, G_OPTION_ARG_STRING, &options.register_dev,
88 "Register the named stonith device. Requires: --agent.\n"
89 INDENT "Optional: --option, --env-option.",
90 "DEVICE" },
91 { "deregister", 'D', 0, G_OPTION_ARG_STRING, &options.unregister_dev,
92 "De-register the named stonith device.",
93 "DEVICE" },
94 { "register-level", 'r', 0, G_OPTION_ARG_STRING, &options.register_level,
95 "Register a stonith level for the named target,\n"
96 INDENT "specified as one of NAME, @PATTERN, or ATTR=VALUE.\n"
97 INDENT "Requires: --index and one or more --device entries.",
98 "TARGET" },
99 { "deregister-level", 'd', 0, G_OPTION_ARG_STRING, &options.unregister_level,
100 "Unregister a stonith level for the named target,\n"
101 INDENT "specified as for --register-level. Requires: --index",
102 "TARGET" },
103
104 { NULL }
105 };
106
107 static GOptionEntry query_entries[] = {
108 { "list", 'l', 0, G_OPTION_ARG_STRING, &options.terminate,
109 "List devices that can terminate the specified host.\n"
110 INDENT "Optional: --timeout",
111 "HOST" },
112 { "list-registered", 'L', 0, G_OPTION_ARG_NONE, &options.registered,
113 "List all registered devices. Optional: --timeout.",
114 NULL },
115 { "list-installed", 'I', 0, G_OPTION_ARG_NONE, &options.installed,
116 "List all installed devices. Optional: --timeout.",
117 NULL },
118 { "list-targets", 's', 0, G_OPTION_ARG_STRING, &options.targets,
119 "List the targets that can be fenced by the\n"
120 INDENT "named device. Optional: --timeout.",
121 "DEVICE" },
122 { "metadata", 'M', 0, G_OPTION_ARG_NONE, &options.metadata,
123 "Show agent metadata. Requires: --agent.\n"
124 INDENT "Optional: --timeout.",
125 NULL },
126 { "query", 'Q', 0, G_OPTION_ARG_STRING, &options.query,
127 "Check the named device's status. Optional: --timeout.",
128 "DEVICE" },
129 { "history", 'H', 0, G_OPTION_ARG_STRING, &options.history,
130 "Show last successful fencing operation for named node\n"
131 INDENT "(or '*' for all nodes). Optional: --timeout, --cleanup,\n"
132 INDENT "--quiet (show only the operation's epoch timestamp),\n"
133 INDENT "--verbose (show all recorded and pending operations),\n"
134 INDENT "--broadcast (update history from all nodes available).",
135 "NODE" },
136 { "last", 'h', 0, G_OPTION_ARG_STRING, &options.last_fenced,
137 "Indicate when the named node was last fenced.\n"
138 INDENT "Optional: --as-node-id.",
139 "NODE" },
140 { "validate", 'K', 0, G_OPTION_ARG_NONE, &options.validate_cfg,
141 "Validate a fence device configuration.\n"
142 INDENT "Requires: --agent. Optional: --option, --env-option,\n"
143 INDENT "--quiet (print no output, only return status).",
144 NULL },
145
146 { NULL }
147 };
148
149 static GOptionEntry fence_entries[] = {
150 { "fence", 'F', 0, G_OPTION_ARG_STRING, &options.fence_host,
151 "Fence named host. Optional: --timeout, --tolerance, --delay.",
152 "HOST" },
153 { "unfence", 'U', 0, G_OPTION_ARG_STRING, &options.unfence_host,
154 "Unfence named host. Optional: --timeout, --tolerance, --delay.",
155 "HOST" },
156 { "reboot", 'B', 0, G_OPTION_ARG_STRING, &options.reboot_host,
157 "Reboot named host. Optional: --timeout, --tolerance, --delay.",
158 "HOST" },
159 { "confirm", 'C', 0, G_OPTION_ARG_STRING, &options.confirm_host,
160 "Tell cluster that named host is now safely down.",
161 "HOST", },
162
163 { NULL }
164 };
165
166 static GOptionEntry addl_entries[] = {
167 { "cleanup", 'c', 0, G_OPTION_ARG_NONE, &options.cleanup,
168 "Cleanup wherever appropriate. Requires --history.",
169 NULL },
170 { "broadcast", 'b', 0, G_OPTION_ARG_NONE, &options.broadcast,
171 "Broadcast wherever appropriate.",
172 NULL },
173 { "agent", 'a', 0, G_OPTION_ARG_STRING, &options.agent,
174 "The agent to use (for example, fence_xvm;\n"
175 INDENT "with --register, --metadata, --validate).",
176 "AGENT" },
177 { "option", 'o', 0, G_OPTION_ARG_CALLBACK, add_stonith_params,
178 "Specify a device configuration parameter as NAME=VALUE\n"
179 INDENT "(may be specified multiple times; with --register,\n"
180 INDENT "--validate).",
181 "PARAM" },
182 { "env-option", 'e', 0, G_OPTION_ARG_CALLBACK, add_env_params,
183 "Specify a device configuration parameter with the\n"
184 INDENT "specified name, using the value of the\n"
185 INDENT "environment variable of the same name prefixed with\n"
186 INDENT "OCF_RESKEY_ (may be specified multiple times;\n"
187 INDENT "with --register, --validate).",
188 "PARAM" },
189 { "tag", 'T', 0, G_OPTION_ARG_CALLBACK, set_tag,
190 "Identify fencing operations in logs with the specified\n"
191 INDENT "tag; useful when multiple entities might invoke\n"
192 INDENT "stonith_admin (used with most commands).",
193 "TAG" },
194 { "device", 'v', 0, G_OPTION_ARG_CALLBACK, add_stonith_device,
195 "Device ID (with --register-level, device to associate with\n"
196 INDENT "a given host and level; may be specified multiple times)"
197 #if SUPPORT_CIBSECRETS
198 "\n" INDENT "(with --validate, name to use to load CIB secrets)"
199 #endif
200 ".",
201 "DEVICE" },
202 { "index", 'i', 0, G_OPTION_ARG_INT, &options.fence_level,
203 "The stonith level (1-9) (with --register-level,\n"
204 INDENT "--deregister-level).",
205 "LEVEL" },
206 { "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout,
207 "Operation timeout in seconds (default 120;\n"
208 INDENT "used with most commands).",
209 "SECONDS" },
210 { "delay", 'y', 0, G_OPTION_ARG_INT, &options.delay,
211 "Apply a fencing delay in seconds. Any static/random delays from\n"
212 INDENT "pcmk_delay_base/max will be added, otherwise all\n"
213 INDENT "disabled with the value -1\n"
214 INDENT "(default 0; with --fence, --reboot, --unfence).",
215 "SECONDS" },
216 { "as-node-id", 'n', 0, G_OPTION_ARG_NONE, &options.as_nodeid,
217 "(Advanced) The supplied node is the corosync node ID\n"
218 INDENT "(with --last).",
219 NULL },
220 { "tolerance", 0, 0, G_OPTION_ARG_CALLBACK, add_tolerance,
221 "(Advanced) Do nothing if an equivalent --fence request\n"
222 INDENT "succeeded less than this many seconds earlier\n"
223 INDENT "(with --fence, --unfence, --reboot).",
224 "SECONDS" },
225
226 { NULL }
227 };
228
229
230 static pcmk__supported_format_t formats[] = {
231 PCMK__SUPPORTED_FORMAT_HTML,
232 PCMK__SUPPORTED_FORMAT_NONE,
233 PCMK__SUPPORTED_FORMAT_TEXT,
234 PCMK__SUPPORTED_FORMAT_XML,
235 { NULL, NULL, NULL }
236 };
237
238 static const int st_opts = st_opt_sync_call | st_opt_allow_suicide;
239
240 static char *name = NULL;
241
242 gboolean
243 add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
244 char *key = crm_strdup_printf("OCF_RESKEY_%s", optarg);
245 const char *env = getenv(key);
246 gboolean retval = TRUE;
247
248 if (env == NULL) {
249 g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Invalid option: -e %s", optarg);
250 retval = FALSE;
251 } else {
252 crm_info("Got: '%s'='%s'", optarg, env);
253 options.params = stonith_key_value_add(options.params, optarg, env);
254 }
255
256 free(key);
257 return retval;
258 }
259
260 gboolean
261 add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
262 options.devices = stonith_key_value_add(options.devices, NULL, optarg);
263 return TRUE;
264 }
265
266 gboolean
267 add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
268 options.tolerance = crm_get_msec(optarg) / 1000;
269 return TRUE;
270 }
271
272 gboolean
273 add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
274 char *name = NULL;
275 char *value = NULL;
276 int rc = 0;
277 gboolean retval = TRUE;
278
279 crm_info("Scanning: -o %s", optarg);
280
281 rc = pcmk__scan_nvpair(optarg, &name, &value);
282
283 if (rc != 2) {
284 rc = pcmk_legacy2rc(rc);
285 g_set_error(error, PCMK__RC_ERROR, rc, "Invalid option: -o %s: %s", optarg, pcmk_rc_str(rc));
286 retval = FALSE;
287 } else {
288 crm_info("Got: '%s'='%s'", name, value);
289 options.params = stonith_key_value_add(options.params, name, value);
290 }
291
292 free(name);
293 free(value);
294 return retval;
295 }
296
297 gboolean
298 set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
299 free(name);
300 name = crm_strdup_printf("%s.%s", crm_system_name, optarg);
301 return TRUE;
302 }
303
304 static GOptionContext *
305 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
306 GOptionContext *context = NULL;
307
308 GOptionEntry extra_prog_entries[] = {
309 { "quiet", 'q', 0, G_OPTION_ARG_NONE, &(args->quiet),
310 "Be less descriptive in output.",
311 NULL },
312
313 { NULL }
314 };
315
316 context = pcmk__build_arg_context(args, "text (default), html, xml", group, NULL);
317
318
319
320
321 pcmk__add_main_args(context, extra_prog_entries);
322
323 pcmk__add_arg_group(context, "definition", "Device Definition Commands:",
324 "Show device definition help", defn_entries);
325 pcmk__add_arg_group(context, "queries", "Queries:",
326 "Show query help", query_entries);
327 pcmk__add_arg_group(context, "fence", "Fencing Commands:",
328 "Show fence help", fence_entries);
329 pcmk__add_arg_group(context, "additional", "Additional Options:",
330 "Show additional options", addl_entries);
331 return context;
332 }
333
334
335 static int
336 request_fencing(stonith_t *st, const char *target, const char *command,
337 GError **error)
338 {
339 char *reason = NULL;
340 int rc = pcmk__request_fencing(st, target, command, name,
341 options.timeout * 1000,
342 options.tolerance * 1000,
343 options.delay, &reason);
344
345 if (rc != pcmk_rc_ok) {
346 const char *rc_str = pcmk_rc_str(rc);
347 const char *what = "fence";
348
349 if (strcmp(command, PCMK_ACTION_ON) == 0) {
350 what = "unfence";
351 }
352
353
354 if (pcmk__str_eq(rc_str, reason, pcmk__str_none)) {
355 free(reason);
356 reason = NULL;
357 }
358
359 g_set_error(error, PCMK__RC_ERROR, rc,
360 "Couldn't %s %s: %s%s%s%s",
361 what, target, rc_str,
362 ((reason == NULL)? "" : " ("),
363 ((reason == NULL)? "" : reason),
364 ((reason == NULL)? "" : ")"));
365 }
366 free(reason);
367 return rc;
368 }
369
370 int
371 main(int argc, char **argv)
372 {
373 int rc = 0;
374 crm_exit_t exit_code = CRM_EX_OK;
375 bool no_connect = false;
376 bool required_agent = false;
377
378 char *target = NULL;
379 const char *device = NULL;
380 stonith_t *st = NULL;
381
382 GError *error = NULL;
383
384 pcmk__output_t *out = NULL;
385
386 GOptionGroup *output_group = NULL;
387 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
388 gchar **processed_args = pcmk__cmdline_preproc(argv, "adehilorstvyBCDFHQRTU");
389 GOptionContext *context = build_arg_context(args, &output_group);
390
391 pcmk__register_formats(output_group, formats);
392 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
393 exit_code = CRM_EX_USAGE;
394 goto done;
395 }
396
397 pcmk__cli_init_logging("stonith_admin", args->verbosity);
398
399 if (name == NULL) {
400 name = strdup(crm_system_name);
401 }
402
403 rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
404 if (rc != pcmk_rc_ok) {
405 exit_code = CRM_EX_ERROR;
406 g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s",
407 args->output_ty, pcmk_rc_str(rc));
408 goto done;
409 }
410
411 stonith__register_messages(out);
412
413 if (args->version) {
414 out->version(out, false);
415 goto done;
416 }
417
418 if (options.validate_cfg) {
419 required_agent = true;
420 no_connect = true;
421 action = 'K';
422 }
423
424 if (options.installed) {
425 no_connect = true;
426 action = 'I';
427 }
428
429 if (options.registered) {
430 action = 'L';
431 }
432
433 if (options.register_dev != NULL) {
434 required_agent = true;
435 action = 'R';
436 device = options.register_dev;
437 }
438
439 if (options.query != NULL) {
440 action = 'Q';
441 device = options.query;
442 }
443
444 if (options.unregister_dev != NULL) {
445 action = 'D';
446 device = options.unregister_dev;
447 }
448
449 if (options.targets != NULL) {
450 action = 's';
451 device = options.targets;
452 }
453
454 if (options.terminate != NULL) {
455 action = 'L';
456 target = options.terminate;
457 }
458
459 if (options.metadata) {
460 no_connect = true;
461 required_agent = true;
462 action = 'M';
463 }
464
465 if (options.reboot_host != NULL) {
466 no_connect = true;
467 action = 'B';
468 target = options.reboot_host;
469 crm_log_args(argc, argv);
470 }
471
472 if (options.fence_host != NULL) {
473 no_connect = true;
474 action = 'F';
475 target = options.fence_host;
476 crm_log_args(argc, argv);
477 }
478
479 if (options.unfence_host != NULL) {
480 no_connect = true;
481 action = 'U';
482 target = options.unfence_host;
483 crm_log_args(argc, argv);
484 }
485
486 if (options.confirm_host != NULL) {
487 action = 'C';
488 target = options.confirm_host;
489 crm_log_args(argc, argv);
490 }
491
492 if (options.last_fenced != NULL) {
493 action = 'h';
494 target = options.last_fenced;
495 }
496
497 if (options.history != NULL) {
498 action = 'H';
499 target = options.history;
500 }
501
502 if (options.register_level != NULL) {
503 action = 'r';
504 target = options.register_level;
505 }
506
507 if (options.unregister_level != NULL) {
508 action = 'd';
509 target = options.unregister_level;
510 }
511
512 if (action == 0) {
513 char *help = g_option_context_get_help(context, TRUE, NULL);
514
515 out->err(out, "%s", help);
516 g_free(help);
517 exit_code = CRM_EX_USAGE;
518 goto done;
519 }
520
521 if (required_agent && options.agent == NULL) {
522 char *help = g_option_context_get_help(context, TRUE, NULL);
523
524 out->err(out, "Please specify an agent to query using -a,--agent [value]");
525 out->err(out, "%s", help);
526 g_free(help);
527 exit_code = CRM_EX_USAGE;
528 goto done;
529 }
530
531 out->quiet = args->quiet;
532
533 st = stonith_api_new();
534 if (st == NULL) {
535 rc = -ENOMEM;
536 } else if (!no_connect) {
537 rc = st->cmds->connect(st, name, NULL);
538 }
539 if (rc < 0) {
540 out->err(out, "Could not connect to fencer: %s", pcmk_strerror(rc));
541 exit_code = CRM_EX_DISCONNECT;
542 goto done;
543 }
544
545 switch (action) {
546 case 'I':
547 rc = pcmk__fence_installed(out, st, options.timeout*1000);
548 if (rc != pcmk_rc_ok) {
549 out->err(out, "Failed to list installed devices: %s", pcmk_rc_str(rc));
550 }
551
552 break;
553
554 case 'L':
555 rc = pcmk__fence_registered(out, st, target, options.timeout*1000);
556 if (rc != pcmk_rc_ok) {
557 out->err(out, "Failed to list registered devices: %s", pcmk_rc_str(rc));
558 }
559
560 break;
561
562 case 'Q':
563 rc = st->cmds->monitor(st, st_opts, device, options.timeout);
564 if (rc != pcmk_rc_ok) {
565 rc = st->cmds->list(st, st_opts, device, NULL, options.timeout);
566 }
567 rc = pcmk_legacy2rc(rc);
568 break;
569
570 case 's':
571 rc = pcmk__fence_list_targets(out, st, device, options.timeout*1000);
572 if (rc != pcmk_rc_ok) {
573 out->err(out, "Couldn't list targets: %s", pcmk_rc_str(rc));
574 }
575
576 break;
577
578 case 'R':
579 rc = st->cmds->register_device(st, st_opts, device, NULL, options.agent,
580 options.params);
581 rc = pcmk_legacy2rc(rc);
582 if (rc != pcmk_rc_ok) {
583 out->err(out, "Can't register device %s using agent %s: %s",
584 device, options.agent, pcmk_rc_str(rc));
585 }
586 break;
587
588 case 'D':
589 rc = st->cmds->remove_device(st, st_opts, device);
590 rc = pcmk_legacy2rc(rc);
591 if (rc != pcmk_rc_ok) {
592 out->err(out, "Can't unregister device %s: %s",
593 device, pcmk_rc_str(rc));
594 }
595 break;
596
597 case 'd':
598 rc = pcmk__fence_unregister_level(st, target, options.fence_level);
599 if (rc != pcmk_rc_ok) {
600 out->err(out, "Can't unregister topology level %d for %s: %s",
601 options.fence_level, target, pcmk_rc_str(rc));
602 }
603 break;
604
605 case 'r':
606 rc = pcmk__fence_register_level(st, target, options.fence_level, options.devices);
607 if (rc != pcmk_rc_ok) {
608 out->err(out, "Can't register topology level %d for %s: %s",
609 options.fence_level, target, pcmk_rc_str(rc));
610 }
611 break;
612
613 case 'M':
614 rc = pcmk__fence_metadata(out, st, options.agent, options.timeout*1000);
615 if (rc != pcmk_rc_ok) {
616 out->err(out, "Can't get fence agent meta-data: %s",
617 pcmk_rc_str(rc));
618 }
619
620 break;
621
622 case 'C':
623 rc = st->cmds->confirm(st, st_opts, target);
624 rc = pcmk_legacy2rc(rc);
625 break;
626
627 case 'B':
628 rc = request_fencing(st, target, PCMK_ACTION_REBOOT, &error);
629 break;
630
631 case 'F':
632 rc = request_fencing(st, target, PCMK_ACTION_OFF, &error);
633 break;
634
635 case 'U':
636 rc = request_fencing(st, target, PCMK_ACTION_ON, &error);
637 break;
638
639 case 'h':
640 rc = pcmk__fence_last(out, target, options.as_nodeid);
641 break;
642
643 case 'H':
644 rc = pcmk__fence_history(out, st, target, options.timeout*1000, args->verbosity,
645 options.broadcast, options.cleanup);
646 break;
647
648 case 'K':
649 device = options.devices ? options.devices->key : NULL;
650 rc = pcmk__fence_validate(out, st, options.agent, device, options.params,
651 options.timeout*1000);
652 break;
653 }
654
655 crm_info("Command returned: %s (%d)", pcmk_rc_str(rc), rc);
656 exit_code = pcmk_rc2exitc(rc);
657
658 done:
659 g_strfreev(processed_args);
660 pcmk__free_arg_context(context);
661
662 pcmk__output_and_clear_error(&error, out);
663
664 if (out != NULL) {
665 out->finish(out, exit_code, true, NULL);
666 pcmk__output_free(out);
667 }
668 pcmk__unregister_formats();
669 free(name);
670 stonith_key_value_freeall(options.params, 1, 1);
671
672 if (st != NULL) {
673 st->cmds->disconnect(st);
674 stonith_api_delete(st);
675 }
676
677 return exit_code;
678 }