Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/tools/crm_diff.c b/tools/crm_diff.c
index f57f3d52b3..104e6403c9 100644
--- a/tools/crm_diff.c
+++ b/tools/crm_diff.c
@@ -1,396 +1,399 @@
/*
* Copyright 2005-2018 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/types.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/xml.h>
#include <crm/common/ipc.h>
#include <crm/cib.h>
#define SUMMARY "Compare two Pacemaker configurations (in XML format) to produce a custom diff-like output, " \
"or apply such an output as a patch"
struct {
gboolean apply;
gboolean as_cib;
gboolean no_version;
gboolean raw_1;
gboolean raw_2;
gboolean use_stdin;
char *xml_file_1;
char *xml_file_2;
} options;
gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
static GOptionEntry original_xml_entries[] = {
{ "original", 'o', 0, G_OPTION_ARG_STRING, &options.xml_file_1,
"XML is contained in the named file",
"FILE" },
{ "original-string", 'O', 0, G_OPTION_ARG_CALLBACK, original_string_cb,
"XML is contained in the supplied string",
"STRING" },
{ NULL }
};
static GOptionEntry operation_entries[] = {
{ "new", 'n', 0, G_OPTION_ARG_STRING, &options.xml_file_2,
"Compare the original XML to the contents of the named file",
"FILE" },
{ "new-string", 'N', 0, G_OPTION_ARG_CALLBACK, new_string_cb,
"Compare the original XML with the contents of the supplied string",
"STRING" },
{ "patch", 'p', 0, G_OPTION_ARG_CALLBACK, patch_cb,
"Patch the original XML with the contents of the named file",
"FILE" },
{ NULL }
};
static GOptionEntry addl_entries[] = {
{ "cib", 'c', 0, G_OPTION_ARG_NONE, &options.as_cib,
"Compare/patch the inputs as a CIB (includes versions details)",
NULL },
{ "stdin", 's', 0, G_OPTION_ARG_NONE, &options.use_stdin,
"",
NULL },
{ "no-version", 'u', 0, G_OPTION_ARG_NONE, &options.no_version,
"Generate the difference without versions details",
NULL },
{ NULL }
};
gboolean
new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.raw_2 = TRUE;
if (options.xml_file_2 != NULL) {
free(options.xml_file_2);
}
options.xml_file_2 = strdup(optarg);
return TRUE;
}
gboolean
original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.raw_1 = TRUE;
if (options.xml_file_1 != NULL) {
free(options.xml_file_1);
}
options.xml_file_1 = strdup(optarg);
return TRUE;
}
gboolean
patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.apply = TRUE;
if (options.xml_file_2 != NULL) {
free(options.xml_file_2);
}
options.xml_file_2 = strdup(optarg);
return TRUE;
}
static void
print_patch(xmlNode *patch)
{
char *buffer = dump_xml_formatted(patch);
printf("%s\n", crm_str(buffer));
free(buffer);
fflush(stdout);
}
static int
apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib)
{
int rc;
xmlNode *output = copy_xml(input);
rc = xml_apply_patchset(output, patch, as_cib);
if (rc != pcmk_ok) {
fprintf(stderr, "Could not apply patch: %s\n", pcmk_strerror(rc));
free_xml(output);
return rc;
}
if (output != NULL) {
const char *version;
char *buffer;
print_patch(output);
version = crm_element_value(output, XML_ATTR_CRM_VERSION);
buffer = calculate_xml_versioned_digest(output, FALSE, TRUE, version);
crm_trace("Digest: %s\n", crm_str(buffer));
free(buffer);
free_xml(output);
}
return pcmk_ok;
}
static void
log_patch_cib_versions(xmlNode *patch)
{
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
const char *fmt = NULL;
const char *digest = NULL;
xml_patch_versions(patch, add, del);
fmt = crm_element_value(patch, "format");
digest = crm_element_value(patch, XML_ATTR_DIGEST);
if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) {
crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
}
}
static void
strip_patch_cib_version(xmlNode *patch, const char **vfields, size_t nvfields)
{
int format = 1;
crm_element_value_int(patch, "format", &format);
if (format == 2) {
xmlNode *version_xml = find_xml_node(patch, "version", FALSE);
if (version_xml) {
free_xml(version_xml);
}
} else {
int i = 0;
const char *tags[] = {
XML_TAG_DIFF_REMOVED,
XML_TAG_DIFF_ADDED,
};
for (i = 0; i < DIMOF(tags); i++) {
xmlNode *tmp = NULL;
int lpc;
tmp = find_xml_node(patch, tags[i], FALSE);
if (tmp) {
for (lpc = 0; lpc < nvfields; lpc++) {
xml_remove_prop(tmp, vfields[lpc]);
}
tmp = find_xml_node(tmp, XML_TAG_CIB, FALSE);
if (tmp) {
for (lpc = 0; lpc < nvfields; lpc++) {
xml_remove_prop(tmp, vfields[lpc]);
}
}
}
}
}
}
static int
generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2,
gboolean as_cib, gboolean no_version)
{
xmlNode *output = NULL;
const char *vfields[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
/* If we're ignoring the version, make the version information
* identical, so it isn't detected as a change. */
if (no_version) {
int lpc;
for (lpc = 0; lpc < DIMOF(vfields); lpc++) {
crm_copy_xml_element(object_1, object_2, vfields[lpc]);
}
}
xml_track_changes(object_2, NULL, object_2, FALSE);
if(as_cib) {
xml_calculate_significant_changes(object_1, object_2);
} else {
xml_calculate_changes(object_1, object_2);
}
crm_log_xml_debug(object_2, (xml_file_2? xml_file_2: "target"));
output = xml_create_patchset(0, object_1, object_2, NULL, FALSE);
xml_log_changes(LOG_INFO, __FUNCTION__, object_2);
xml_accept_changes(object_2);
if (output == NULL) {
return pcmk_ok;
}
patchset_process_digest(output, object_1, object_2, as_cib);
if (as_cib) {
log_patch_cib_versions(output);
} else if (no_version) {
strip_patch_cib_version(output, vfields, DIMOF(vfields));
}
xml_log_patchset(LOG_NOTICE, __FUNCTION__, output);
print_patch(output);
free_xml(output);
return -pcmk_err_generic;
}
static GOptionContext *
build_arg_context(pcmk__common_args_t *args) {
GOptionContext *context = NULL;
const char *description = "*Examples*\n\n"
"Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n"
"\tcibadmin --query > cib-old.xml\n"
"\tcibadmin --query > cib-new.xml\n\n"
"Calculate and save the difference between the two files:\n\n"
"\tcrm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n"
"Apply the patch to the original file:\n\n"
"\tcrm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n"
"Apply the patch to the running cluster:\n\n"
"\tcibadmin --patch patch.xml\n";
context = pcmk__build_arg_context(args, NULL, NULL);
g_option_context_set_description(context, description);
pcmk__add_arg_group(context, "xml", "Original XML:",
"Show original XML options", original_xml_entries);
pcmk__add_arg_group(context, "operation", "Operation:",
"Show operation options", operation_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
return context;
}
int
main(int argc, char **argv)
{
int rc = pcmk_ok;
xmlNode *object_1 = NULL;
xmlNode *object_2 = NULL;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
GError *error = NULL;
GOptionContext *context = NULL;
gchar **processed_args = NULL;
context = build_arg_context(args);
crm_log_cli_init("crm_diff");
processed_args = pcmk__cmdline_preproc(argv, "nopNO");
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
rc = CRM_EX_USAGE;
goto done;
}
for (int i = 0; i < args->verbosity; i++) {
crm_bump_log_level(argc, argv);
}
if (args->version) {
/* FIXME: When crm_diff is converted to use formatted output, this can go. */
pcmk__cli_help('v', CRM_EX_USAGE);
}
if (optind > argc) {
- fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL));
+ char *help = g_option_context_get_help(context, TRUE, NULL);
+
+ fprintf(stderr, "%s", help);
+ free(help);
rc = CRM_EX_USAGE;
goto done;
}
if (options.apply && options.no_version) {
fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n");
} else if (options.as_cib && options.no_version) {
fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n");
rc = CRM_EX_USAGE;
goto done;
}
if (options.raw_1) {
object_1 = string2xml(options.xml_file_1);
} else if (options.use_stdin) {
fprintf(stderr, "Input first XML fragment:");
object_1 = stdin2xml();
} else if (options.xml_file_1 != NULL) {
object_1 = filename2xml(options.xml_file_1);
}
if (options.raw_2) {
object_2 = string2xml(options.xml_file_2);
} else if (options.use_stdin) {
fprintf(stderr, "Input second XML fragment:");
object_2 = stdin2xml();
} else if (options.xml_file_2 != NULL) {
object_2 = filename2xml(options.xml_file_2);
}
if (object_1 == NULL) {
fprintf(stderr, "Could not parse the first XML fragment\n");
rc = CRM_EX_DATAERR;
goto done;
}
if (object_2 == NULL) {
fprintf(stderr, "Could not parse the second XML fragment\n");
rc = CRM_EX_DATAERR;
goto done;
}
if (options.apply) {
rc = apply_patch(object_1, object_2, options.as_cib);
} else {
rc = generate_patch(object_1, object_2, options.xml_file_2, options.as_cib, options.no_version);
}
done:
g_strfreev(processed_args);
g_clear_error(&error);
pcmk__free_arg_context(context);
free(options.xml_file_1);
free(options.xml_file_2);
free_xml(object_1);
free_xml(object_2);
return crm_errno2exit(rc);
}
diff --git a/tools/crm_mon.c b/tools/crm_mon.c
index c4b4707bc1..510765a409 100644
--- a/tools/crm_mon.c
+++ b/tools/crm_mon.c
@@ -1,2164 +1,2167 @@
/*
* Copyright 2004-2020 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <crm/crm.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <signal.h>
#include <sys/utsname.h>
#include <crm/msg_xml.h>
#include <crm/services.h>
#include <crm/lrmd.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/curses_internal.h>
#include <crm/common/internal.h> // pcmk__ends_with_ext()
#include <crm/common/ipc.h>
#include <crm/common/iso8601_internal.h>
#include <crm/common/mainloop.h>
#include <crm/common/output.h>
#include <crm/common/util.h>
#include <crm/common/xml.h>
#include <crm/cib/internal.h>
#include <crm/pengine/status.h>
#include <crm/pengine/internal.h>
#include <pacemaker-internal.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#include "crm_mon.h"
#define SUMMARY "Provides a summary of cluster's current state.\n\n" \
"Outputs varying levels of detail in a number of different formats."
/*
* Definitions indicating which items to print
*/
static unsigned int show;
/*
* Definitions indicating how to output
*/
static mon_output_format_t output_format = mon_output_unset;
/* other globals */
static GMainLoop *mainloop = NULL;
static guint timer_id = 0;
static mainloop_timer_t *refresh_timer = NULL;
static pe_working_set_t *mon_data_set = NULL;
static cib_t *cib = NULL;
static stonith_t *st = NULL;
static xmlNode *current_cib = NULL;
static GError *error = NULL;
static pcmk__common_args_t *args = NULL;
static pcmk__output_t *out = NULL;
static GOptionContext *context = NULL;
static gchar **processed_args = NULL;
static time_t last_refresh = 0;
crm_trigger_t *refresh_trigger = NULL;
static pcmk__supported_format_t formats[] = {
#if CURSES_ENABLED
CRM_MON_SUPPORTED_FORMAT_CURSES,
#endif
PCMK__SUPPORTED_FORMAT_HTML,
PCMK__SUPPORTED_FORMAT_NONE,
PCMK__SUPPORTED_FORMAT_TEXT,
CRM_MON_SUPPORTED_FORMAT_XML,
{ NULL, NULL, NULL }
};
/* Define exit codes for monitoring-compatible output
* For nagios plugins, the possibilities are
* OK=0, WARN=1, CRIT=2, and UNKNOWN=3
*/
#define MON_STATUS_WARN CRM_EX_ERROR
#define MON_STATUS_CRIT CRM_EX_INVALID_PARAM
#define MON_STATUS_UNKNOWN CRM_EX_UNIMPLEMENT_FEATURE
#define RECONNECT_MSECS 5000
struct {
int reconnect_msec;
gboolean daemonize;
gboolean show_bans;
char *pid_file;
char *external_agent;
char *external_recipient;
char *neg_location_prefix;
unsigned int mon_ops;
GSList *user_includes_excludes;
GSList *includes_excludes;
} options = {
.reconnect_msec = RECONNECT_MSECS,
.mon_ops = mon_op_default
};
static void clean_up_connections(void);
static crm_exit_t clean_up(crm_exit_t exit_code);
static void crm_diff_update(const char *event, xmlNode * msg);
static gboolean mon_refresh_display(gpointer user_data);
static int cib_connect(gboolean full);
static void mon_st_callback_event(stonith_t * st, stonith_event_t * e);
static void mon_st_callback_display(stonith_t * st, stonith_event_t * e);
static void kick_refresh(gboolean data_updated);
static unsigned int
all_includes(mon_output_format_t fmt) {
if (fmt == mon_output_monitor || fmt == mon_output_plain || fmt == mon_output_console) {
return ~mon_show_options;
} else {
return mon_show_all;
}
}
static unsigned int
default_includes(mon_output_format_t fmt) {
switch (fmt) {
case mon_output_monitor:
case mon_output_plain:
case mon_output_console:
return mon_show_stack | mon_show_dc | mon_show_times | mon_show_counts |
mon_show_nodes | mon_show_resources | mon_show_failures;
break;
case mon_output_xml:
case mon_output_legacy_xml:
return all_includes(fmt);
break;
case mon_output_html:
case mon_output_cgi:
return mon_show_summary | mon_show_nodes | mon_show_resources |
mon_show_failures;
break;
default:
return 0;
break;
}
}
struct {
const char *name;
unsigned int bit;
} sections[] = {
{ "attributes", mon_show_attributes },
{ "bans", mon_show_bans },
{ "counts", mon_show_counts },
{ "dc", mon_show_dc },
{ "failcounts", mon_show_failcounts },
{ "failures", mon_show_failures },
{ "fencing", mon_show_fencing_all },
{ "fencing-failed", mon_show_fence_failed },
{ "fencing-pending", mon_show_fence_pending },
{ "fencing-succeeded", mon_show_fence_worked },
{ "nodes", mon_show_nodes },
{ "operations", mon_show_operations },
{ "options", mon_show_options },
{ "resources", mon_show_resources },
{ "stack", mon_show_stack },
{ "summary", mon_show_summary },
{ "tickets", mon_show_tickets },
{ "times", mon_show_times },
{ NULL }
};
static unsigned int
find_section_bit(const char *name) {
for (int i = 0; sections[i].name != NULL; i++) {
if (crm_str_eq(sections[i].name, name, FALSE)) {
return sections[i].bit;
}
}
return 0;
}
static gboolean
apply_exclude(const gchar *excludes, GError **error) {
char **parts = NULL;
parts = g_strsplit(excludes, ",", 0);
for (char **s = parts; *s != NULL; s++) {
unsigned int bit = find_section_bit(*s);
if (crm_str_eq(*s, "all", TRUE)) {
show = 0;
} else if (crm_str_eq(*s, "none", TRUE)) {
show = all_includes(output_format);
} else if (bit != 0) {
show &= ~bit;
} else {
g_set_error(error, G_OPTION_ERROR, CRM_EX_USAGE,
"--exclude options: all, attributes, bans, counts, dc, "
"failcounts, failures, fencing, fencing-failed, "
"fencing-pending, fencing-succeeded, nodes, none, "
"operations, options, resources, stack, summary, "
"tickets, times");
return FALSE;
}
}
g_strfreev(parts);
return TRUE;
}
static gboolean
apply_include(const gchar *includes, GError **error) {
char **parts = NULL;
parts = g_strsplit(includes, ",", 0);
for (char **s = parts; *s != NULL; s++) {
unsigned int bit = find_section_bit(*s);
if (crm_str_eq(*s, "all", TRUE)) {
show = all_includes(output_format);
} else if (pcmk__starts_with(*s, "bans")) {
show |= mon_show_bans;
if (options.neg_location_prefix != NULL) {
free(options.neg_location_prefix);
options.neg_location_prefix = NULL;
}
if (strlen(*s) > 4 && (*s)[4] == ':') {
options.neg_location_prefix = strdup(*s+5);
}
} else if (crm_str_eq(*s, "default", TRUE) || crm_str_eq(*s, "defaults", TRUE)) {
show |= default_includes(output_format);
} else if (crm_str_eq(*s, "none", TRUE)) {
show = 0;
} else if (bit != 0) {
show |= bit;
} else {
g_set_error(error, G_OPTION_ERROR, CRM_EX_USAGE,
"--include options: all, attributes, bans[:PREFIX], counts, dc, "
"default, failcounts, failures, fencing, fencing-failed, "
"fencing-pending, fencing-succeeded, nodes, none, operations, "
"options, resources, stack, summary, tickets, times");
return FALSE;
}
}
g_strfreev(parts);
return TRUE;
}
static gboolean
apply_include_exclude(GSList *lst, mon_output_format_t fmt, GError **error) {
gboolean rc = TRUE;
GSList *node = lst;
/* Set the default of what to display here. Note that we OR everything to
* show instead of set show directly because it could have already had some
* settings applied to it in main.
*/
show |= default_includes(fmt);
while (node != NULL) {
char *s = node->data;
if (pcmk__starts_with(s, "--include=")) {
rc = apply_include(s+10, error);
} else if (pcmk__starts_with(s, "-I=")) {
rc = apply_include(s+3, error);
} else if (pcmk__starts_with(s, "--exclude=")) {
rc = apply_exclude(s+10, error);
} else if (pcmk__starts_with(s, "-U=")) {
rc = apply_exclude(s+3, error);
}
if (rc != TRUE) {
break;
}
node = node->next;
}
return rc;
}
static gboolean
user_include_exclude_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
char *s = crm_strdup_printf("%s=%s", option_name, optarg);
options.user_includes_excludes = g_slist_append(options.user_includes_excludes, s);
return TRUE;
}
static gboolean
include_exclude_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
char *s = crm_strdup_printf("%s=%s", option_name, optarg);
options.includes_excludes = g_slist_append(options.includes_excludes, s);
return TRUE;
}
static gboolean
as_cgi_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
if (args->output_ty != NULL) {
free(args->output_ty);
}
args->output_ty = strdup("html");
output_format = mon_output_cgi;
options.mon_ops |= mon_op_one_shot;
return TRUE;
}
static gboolean
as_html_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
if (args->output_ty != NULL) {
free(args->output_ty);
}
if (args->output_dest != NULL) {
free(args->output_dest);
}
if (optarg != NULL) {
args->output_dest = strdup(optarg);
}
args->output_ty = strdup("html");
output_format = mon_output_html;
umask(S_IWGRP | S_IWOTH);
return TRUE;
}
static gboolean
as_simple_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
if (args->output_ty != NULL) {
free(args->output_ty);
}
args->output_ty = strdup("text");
output_format = mon_output_monitor;
options.mon_ops |= mon_op_one_shot;
return TRUE;
}
static gboolean
as_xml_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
if (args->output_ty != NULL) {
free(args->output_ty);
}
args->output_ty = strdup("xml");
output_format = mon_output_legacy_xml;
options.mon_ops |= mon_op_one_shot;
return TRUE;
}
static gboolean
fence_history_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
int rc = crm_atoi(optarg, "2");
switch (rc) {
case 3:
options.mon_ops |= mon_op_fence_full_history | mon_op_fence_history | mon_op_fence_connect;
return include_exclude_cb("--include", "fencing", data, err);
case 2:
options.mon_ops |= mon_op_fence_history | mon_op_fence_connect;
return include_exclude_cb("--include", "fencing", data, err);
case 1:
options.mon_ops |= mon_op_fence_history | mon_op_fence_connect;
return include_exclude_cb("--include", "fencing-failed,fencing-pending", data, err);
case 0:
options.mon_ops &= ~(mon_op_fence_history | mon_op_fence_connect);
return include_exclude_cb("--exclude", "fencing", data, err);
default:
g_set_error(err, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Fence history must be 0-3");
return FALSE;
}
}
static gboolean
group_by_node_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
options.mon_ops |= mon_op_group_by_node;
return TRUE;
}
static gboolean
hide_headers_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
return include_exclude_cb("--exclude", "summary", data, err);
}
static gboolean
inactive_resources_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
options.mon_ops |= mon_op_inactive_resources;
return TRUE;
}
static gboolean
no_curses_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
output_format = mon_output_plain;
return TRUE;
}
static gboolean
one_shot_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
options.mon_ops |= mon_op_one_shot;
return TRUE;
}
static gboolean
print_brief_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
options.mon_ops |= mon_op_print_brief;
return TRUE;
}
static gboolean
print_clone_detail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
options.mon_ops |= mon_op_print_clone_detail;
return TRUE;
}
static gboolean
print_pending_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
options.mon_ops |= mon_op_print_pending;
return TRUE;
}
static gboolean
print_timing_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
options.mon_ops |= mon_op_print_timing;
return include_exclude_cb("--include", "operations", data, err);
}
static gboolean
reconnect_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
int rc = crm_get_msec(optarg);
if (rc == -1) {
g_set_error(err, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Invalid value for -i: %s", optarg);
return FALSE;
} else {
options.reconnect_msec = crm_parse_interval_spec(optarg);
}
return TRUE;
}
static gboolean
show_attributes_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
return include_exclude_cb("--include", "attributes", data, err);
}
static gboolean
show_bans_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
if (optarg != NULL) {
char *s = crm_strdup_printf("bans:%s", optarg);
gboolean rc = include_exclude_cb("--include", s, data, err);
free(s);
return rc;
} else {
return include_exclude_cb("--include", "bans", data, err);
}
}
static gboolean
show_failcounts_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
return include_exclude_cb("--include", "failcounts", data, err);
}
static gboolean
show_operations_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
return include_exclude_cb("--include", "failcounts,operations", data, err);
}
static gboolean
show_tickets_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
return include_exclude_cb("--include", "tickets", data, err);
}
static gboolean
use_cib_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
setenv("CIB_file", optarg, 1);
options.mon_ops |= mon_op_one_shot;
return TRUE;
}
static gboolean
watch_fencing_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
options.mon_ops |= mon_op_watch_fencing;
return TRUE;
}
#define INDENT " "
/* *INDENT-OFF* */
static GOptionEntry addl_entries[] = {
{ "interval", 'i', 0, G_OPTION_ARG_CALLBACK, reconnect_cb,
"Update frequency (default is 5 seconds)",
"TIMESPEC" },
{ "one-shot", '1', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, one_shot_cb,
"Display the cluster status once on the console and exit",
NULL },
{ "daemonize", 'd', 0, G_OPTION_ARG_NONE, &options.daemonize,
"Run in the background as a daemon.\n"
INDENT "Requires at least one of --output-to and --external-agent.",
NULL },
{ "pid-file", 'p', 0, G_OPTION_ARG_FILENAME, &options.pid_file,
"(Advanced) Daemon pid file location",
"FILE" },
{ "external-agent", 'E', 0, G_OPTION_ARG_FILENAME, &options.external_agent,
"A program to run when resource operations take place",
"FILE" },
{ "external-recipient", 'e', 0, G_OPTION_ARG_STRING, &options.external_recipient,
"A recipient for your program (assuming you want the program to send something to someone).",
"RCPT" },
{ "watch-fencing", 'W', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, watch_fencing_cb,
"Listen for fencing events. For use with --external-agent.",
NULL },
{ "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, use_cib_file_cb,
NULL,
NULL },
{ NULL }
};
static GOptionEntry display_entries[] = {
{ "include", 'I', 0, G_OPTION_ARG_CALLBACK, user_include_exclude_cb,
"A list of sections to include in the output.\n"
INDENT "See `Output Control` help for more information.",
"SECTION(s)" },
{ "exclude", 'U', 0, G_OPTION_ARG_CALLBACK, user_include_exclude_cb,
"A list of sections to exclude from the output.\n"
INDENT "See `Output Control` help for more information.",
"SECTION(s)" },
{ "group-by-node", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, group_by_node_cb,
"Group resources by node",
NULL },
{ "inactive", 'r', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, inactive_resources_cb,
"Display inactive resources",
NULL },
{ "failcounts", 'f', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_failcounts_cb,
"Display resource fail counts",
NULL },
{ "operations", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_operations_cb,
"Display resource operation history",
NULL },
{ "timing-details", 't', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_timing_cb,
"Display resource operation history with timing details",
NULL },
{ "tickets", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_tickets_cb,
"Display cluster tickets",
NULL },
{ "fence-history", 'm', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, fence_history_cb,
"Show fence history:\n"
INDENT "0=off, 1=failures and pending (default without option),\n"
INDENT "2=add successes (default without value for option),\n"
INDENT "3=show full history without reduction to most recent of each flavor",
"LEVEL" },
{ "neg-locations", 'L', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, show_bans_cb,
"Display negative location constraints [optionally filtered by id prefix]",
NULL },
{ "show-node-attributes", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_attributes_cb,
"Display node attributes",
NULL },
{ "hide-headers", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, hide_headers_cb,
"Hide all headers",
NULL },
{ "show-detail", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_clone_detail_cb,
"Show more details (node IDs, individual clone instances)",
NULL },
{ "brief", 'b', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_brief_cb,
"Brief output",
NULL },
{ "pending", 'j', G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_pending_cb,
"Display pending state if 'record-pending' is enabled",
NULL },
{ "simple-status", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_simple_cb,
"Display the cluster status once as a simple one line output (suitable for nagios)",
NULL },
{ NULL }
};
static GOptionEntry deprecated_entries[] = {
{ "as-html", 'h', G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, as_html_cb,
"Write cluster status to the named HTML file.\n"
INDENT "Use --output-as=html --output-to=FILE instead.",
"FILE" },
{ "as-xml", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_xml_cb,
"Write cluster status as XML to stdout. This will enable one-shot mode.\n"
INDENT "Use --output-as=xml instead.",
NULL },
{ "disable-ncurses", 'N', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, no_curses_cb,
"Disable the use of ncurses.\n"
INDENT "Use --output-as=text instead.",
NULL },
{ "web-cgi", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_cgi_cb,
"Web mode with output suitable for CGI (preselected when run as *.cgi).\n"
INDENT "Use --output-as=html --html-cgi instead.",
NULL },
{ NULL }
};
/* *INDENT-ON* */
static gboolean
mon_timer_popped(gpointer data)
{
int rc = pcmk_ok;
#if CURSES_ENABLED
if (output_format == mon_output_console) {
clear();
refresh();
}
#endif
if (timer_id > 0) {
g_source_remove(timer_id);
timer_id = 0;
}
print_as(output_format, "Reconnecting...\n");
rc = cib_connect(TRUE);
if (rc != pcmk_ok) {
timer_id = g_timeout_add(options.reconnect_msec, mon_timer_popped, NULL);
}
return FALSE;
}
static void
mon_cib_connection_destroy(gpointer user_data)
{
out->err(out, "Connection to the cluster-daemons terminated");
if (refresh_timer != NULL) {
/* we'll trigger a refresh after reconnect */
mainloop_timer_stop(refresh_timer);
}
if (timer_id) {
/* we'll trigger a new reconnect-timeout at the end */
g_source_remove(timer_id);
timer_id = 0;
}
if (st) {
/* the client API won't properly reconnect notifications
* if they are still in the table - so remove them
*/
st->cmds->remove_notification(st, T_STONITH_NOTIFY_DISCONNECT);
st->cmds->remove_notification(st, T_STONITH_NOTIFY_FENCE);
st->cmds->remove_notification(st, T_STONITH_NOTIFY_HISTORY);
if (st->state != stonith_disconnected) {
st->cmds->disconnect(st);
}
}
if (cib) {
cib->cmds->signoff(cib);
timer_id = g_timeout_add(options.reconnect_msec, mon_timer_popped, NULL);
}
return;
}
/*
* Mainloop signal handler.
*/
static void
mon_shutdown(int nsig)
{
clean_up(CRM_EX_OK);
}
#if CURSES_ENABLED
static sighandler_t ncurses_winch_handler;
static void
mon_winresize(int nsig)
{
static int not_done;
int lines = 0, cols = 0;
if (!not_done++) {
if (ncurses_winch_handler)
/* the original ncurses WINCH signal handler does the
* magic of retrieving the new window size;
* otherwise, we'd have to use ioctl or tgetent */
(*ncurses_winch_handler) (SIGWINCH);
getmaxyx(stdscr, lines, cols);
resizeterm(lines, cols);
mainloop_set_trigger(refresh_trigger);
}
not_done--;
}
#endif
static int
cib_connect(gboolean full)
{
int rc = pcmk_ok;
static gboolean need_pass = TRUE;
CRM_CHECK(cib != NULL, return -EINVAL);
if (getenv("CIB_passwd") != NULL) {
need_pass = FALSE;
}
if (is_set(options.mon_ops, mon_op_fence_connect) && st == NULL) {
st = stonith_api_new();
}
if (is_set(options.mon_ops, mon_op_fence_connect) && st != NULL && st->state == stonith_disconnected) {
rc = st->cmds->connect(st, crm_system_name, NULL);
if (rc == pcmk_ok) {
crm_trace("Setting up stonith callbacks");
if (is_set(options.mon_ops, mon_op_watch_fencing)) {
st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT,
mon_st_callback_event);
st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, mon_st_callback_event);
} else {
st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT,
mon_st_callback_display);
st->cmds->register_notification(st, T_STONITH_NOTIFY_HISTORY, mon_st_callback_display);
}
}
}
if (cib->state != cib_connected_query && cib->state != cib_connected_command) {
crm_trace("Connecting to the CIB");
if ((output_format == mon_output_console) && need_pass && (cib->variant == cib_remote)) {
need_pass = FALSE;
print_as(output_format, "Password:");
}
rc = cib->cmds->signon(cib, crm_system_name, cib_query);
if (rc != pcmk_ok) {
return rc;
}
rc = cib->cmds->query(cib, NULL, &current_cib, cib_scope_local | cib_sync_call);
if (rc == pcmk_ok) {
mon_refresh_display(&output_format);
}
if (rc == pcmk_ok && full) {
if (rc == pcmk_ok) {
rc = cib->cmds->set_connection_dnotify(cib, mon_cib_connection_destroy);
if (rc == -EPROTONOSUPPORT) {
print_as
(output_format, "Notification setup not supported, won't be able to reconnect after failure");
if (output_format == mon_output_console) {
sleep(2);
}
rc = pcmk_ok;
}
}
if (rc == pcmk_ok) {
cib->cmds->del_notify_callback(cib, T_CIB_DIFF_NOTIFY, crm_diff_update);
rc = cib->cmds->add_notify_callback(cib, T_CIB_DIFF_NOTIFY, crm_diff_update);
}
if (rc != pcmk_ok) {
out->err(out, "Notification setup failed, could not monitor CIB actions");
if (output_format == mon_output_console) {
sleep(2);
}
clean_up_connections();
}
}
}
return rc;
}
#if CURSES_ENABLED
static const char *
get_option_desc(char c)
{
const char *desc = "No help available";
for (GOptionEntry *entry = display_entries; entry != NULL; entry++) {
if (entry->short_name == c) {
desc = entry->description;
break;
}
}
return desc;
}
#define print_option_help(output_format, option, condition) \
out->info(out, "%c %c: \t%s", ((condition)? '*': ' '), option, get_option_desc(option));
static gboolean
detect_user_input(GIOChannel *channel, GIOCondition condition, gpointer user_data)
{
int c;
gboolean config_mode = FALSE;
while (1) {
/* Get user input */
c = getchar();
switch (c) {
case 'm':
if (is_not_set(show, mon_show_fencing_all)) {
options.mon_ops |= mon_op_fence_history;
options.mon_ops |= mon_op_fence_connect;
if (st == NULL) {
mon_cib_connection_destroy(NULL);
}
}
if (is_set(show, mon_show_fence_failed) || is_set(show, mon_show_fence_pending) ||
is_set(show, mon_show_fence_worked)) {
show &= ~mon_show_fencing_all;
} else {
show |= mon_show_fencing_all;
}
break;
case 'c':
show ^= mon_show_tickets;
break;
case 'f':
show ^= mon_show_failcounts;
break;
case 'n':
options.mon_ops ^= mon_op_group_by_node;
break;
case 'o':
show ^= mon_show_operations;
if (is_not_set(show, mon_show_operations)) {
options.mon_ops &= ~mon_op_print_timing;
}
break;
case 'r':
options.mon_ops ^= mon_op_inactive_resources;
break;
case 'R':
options.mon_ops ^= mon_op_print_clone_detail;
break;
case 't':
options.mon_ops ^= mon_op_print_timing;
if (is_set(options.mon_ops, mon_op_print_timing)) {
show |= mon_show_operations;
}
break;
case 'A':
show ^= mon_show_attributes;
break;
case 'L':
show ^= mon_show_bans;
break;
case 'D':
/* If any header is shown, clear them all, otherwise set them all */
if (is_set(show, mon_show_stack) || is_set(show, mon_show_dc) ||
is_set(show, mon_show_times) || is_set(show, mon_show_counts)) {
show &= ~mon_show_summary;
} else {
show |= mon_show_summary;
}
/* Regardless, we don't show options in console mode. */
show &= ~mon_show_options;
break;
case 'b':
options.mon_ops ^= mon_op_print_brief;
break;
case 'j':
options.mon_ops ^= mon_op_print_pending;
break;
case '?':
config_mode = TRUE;
break;
default:
goto refresh;
}
if (!config_mode)
goto refresh;
blank_screen();
out->info(out, "%s", "Display option change mode\n");
print_option_help(out, 'c', is_set(show, mon_show_tickets));
print_option_help(out, 'f', is_set(show, mon_show_failcounts));
print_option_help(out, 'n', is_set(options.mon_ops, mon_op_group_by_node));
print_option_help(out, 'o', is_set(show, mon_show_operations));
print_option_help(out, 'r', is_set(options.mon_ops, mon_op_inactive_resources));
print_option_help(out, 't', is_set(options.mon_ops, mon_op_print_timing));
print_option_help(out, 'A', is_set(show, mon_show_attributes));
print_option_help(out, 'L', is_set(show,mon_show_bans));
print_option_help(out, 'D', is_not_set(show, mon_show_summary));
print_option_help(out, 'R', is_set(options.mon_ops, mon_op_print_clone_detail));
print_option_help(out, 'b', is_set(options.mon_ops, mon_op_print_brief));
print_option_help(out, 'j', is_set(options.mon_ops, mon_op_print_pending));
print_option_help(out, 'm', is_set(show, mon_show_fencing_all));
out->info(out, "%s", "\nToggle fields via field letter, type any other key to return");
}
refresh:
mon_refresh_display(NULL);
return TRUE;
}
#endif
// Basically crm_signal_handler(SIGCHLD, SIG_IGN) plus the SA_NOCLDWAIT flag
static void
avoid_zombies()
{
struct sigaction sa;
memset(&sa, 0, sizeof(struct sigaction));
if (sigemptyset(&sa.sa_mask) < 0) {
crm_warn("Cannot avoid zombies: %s", pcmk_strerror(errno));
return;
}
sa.sa_handler = SIG_IGN;
sa.sa_flags = SA_RESTART|SA_NOCLDWAIT;
if (sigaction(SIGCHLD, &sa, NULL) < 0) {
crm_warn("Cannot avoid zombies: %s", pcmk_strerror(errno));
}
}
static GOptionContext *
build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
GOptionContext *context = NULL;
GOptionEntry extra_prog_entries[] = {
{ "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
"Be less descriptive in output.",
NULL },
{ NULL }
};
const char *description = "Notes:\n\n"
"If this program is called as crm_mon.cgi, --output-as=html --html-cgi will\n"
"automatically be added to the command line arguments.\n\n"
"Time Specification:\n\n"
"The TIMESPEC in any command line option can be specified in many different\n"
"formats. It can be just an integer number of seconds, a number plus units\n"
"(ms/msec/us/usec/s/sec/m/min/h/hr), or an ISO 8601 period specification.\n\n"
"Output Control:\n\n"
"By default, a certain list of sections are written to the output destination.\n"
"The default varies based on the output format - XML includes everything, while\n"
"other output formats will display less. This list can be modified with the\n"
"--include and --exclude command line options. Each option may be given multiple\n"
"times on the command line, and each can give a comma-separated list of sections.\n"
"The options are applied to the default set, from left to right as seen on the\n"
"command line. For a list of valid sections, pass --include=list or --exclude=list.\n\n"
"Examples:\n\n"
"Display the cluster status on the console with updates as they occur:\n\n"
"\tcrm_mon\n\n"
"Display the cluster status on the console just once then exit:\n\n"
"\tcrm_mon -1\n\n"
"Display your cluster status, group resources by node, and include inactive resources in the list:\n\n"
"\tcrm_mon --group-by-node --inactive\n\n"
"Start crm_mon as a background daemon and have it write the cluster status to an HTML file:\n\n"
"\tcrm_mon --daemonize --output-as html --output-to /path/to/docroot/filename.html\n\n"
"Start crm_mon and export the current cluster status as XML to stdout, then exit:\n\n"
"\tcrm_mon --output-as xml\n\n";
context = pcmk__build_arg_context(args, "console (default), html, text, xml", group);
pcmk__add_main_args(context, extra_prog_entries);
g_option_context_set_description(context, description);
pcmk__add_arg_group(context, "display", "Display Options:",
"Show display options", display_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
"Show deprecated options", deprecated_entries);
return context;
}
/* If certain format options were specified, we want to set some extra
* options. We can just process these like they were given on the
* command line.
*/
static void
add_output_args() {
GError *err = NULL;
if (output_format == mon_output_plain) {
if (!pcmk__force_args(context, &err, "%s --text-fancy", g_get_prgname())) {
g_propagate_error(&error, err);
clean_up(CRM_EX_USAGE);
}
} else if (output_format == mon_output_html) {
if (!pcmk__force_args(context, &err, "%s --html-title \"Cluster Status\"",
g_get_prgname())) {
g_propagate_error(&error, err);
clean_up(CRM_EX_USAGE);
}
} else if (output_format == mon_output_cgi) {
if (!pcmk__force_args(context, &err, "%s --html-cgi --html-title \"Cluster Status\"", g_get_prgname())) {
g_propagate_error(&error, err);
clean_up(CRM_EX_USAGE);
}
} else if (output_format == mon_output_xml) {
if (!pcmk__force_args(context, &err, "%s --xml-simple-list", g_get_prgname())) {
g_propagate_error(&error, err);
clean_up(CRM_EX_USAGE);
}
} else if (output_format == mon_output_legacy_xml) {
output_format = mon_output_xml;
if (!pcmk__force_args(context, &err, "%s --xml-legacy", g_get_prgname())) {
g_propagate_error(&error, err);
clean_up(CRM_EX_USAGE);
}
}
}
/* Which output format to use could come from two places: The --as-xml
* style arguments we gave in deprecated_entries above, or the formatted output
* arguments added by pcmk__register_formats. If the latter were used,
* output_format will be mon_output_unset.
*
* Call the callbacks as if those older style arguments were provided so
* the various things they do get done.
*/
static void
reconcile_output_format(pcmk__common_args_t *args) {
gboolean retval = TRUE;
GError *err = NULL;
if (output_format != mon_output_unset) {
return;
}
if (safe_str_eq(args->output_ty, "html")) {
char *dest = NULL;
if (args->output_dest != NULL) {
dest = strdup(args->output_dest);
}
retval = as_html_cb("h", dest, NULL, &err);
free(dest);
} else if (safe_str_eq(args->output_ty, "text")) {
retval = no_curses_cb("N", NULL, NULL, &err);
} else if (safe_str_eq(args->output_ty, "xml")) {
if (args->output_ty != NULL) {
free(args->output_ty);
}
args->output_ty = strdup("xml");
output_format = mon_output_xml;
options.mon_ops |= mon_op_one_shot;
} else if (is_set(options.mon_ops, mon_op_one_shot)) {
if (args->output_ty != NULL) {
free(args->output_ty);
}
args->output_ty = strdup("text");
output_format = mon_output_plain;
} else {
/* Neither old nor new arguments were given, so set the default. */
if (args->output_ty != NULL) {
free(args->output_ty);
}
args->output_ty = strdup("console");
output_format = mon_output_console;
}
if (!retval) {
g_propagate_error(&error, err);
clean_up(CRM_EX_USAGE);
}
}
int
main(int argc, char **argv)
{
int rc = pcmk_ok;
GOptionGroup *output_group = NULL;
args = pcmk__new_common_args(SUMMARY);
context = build_arg_context(args, &output_group);
pcmk__register_formats(output_group, formats);
options.pid_file = strdup("/tmp/ClusterMon.pid");
crm_log_cli_init("crm_mon");
// Avoid needing to wait for subprocesses forked for -E/--external-agent
avoid_zombies();
if (pcmk__ends_with_ext(argv[0], ".cgi")) {
output_format = mon_output_cgi;
options.mon_ops |= mon_op_one_shot;
}
processed_args = pcmk__cmdline_preproc(argv, "ehimpxEILU");
fence_history_cb("--fence-history", "1", NULL, NULL);
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
return clean_up(CRM_EX_USAGE);
}
for (int i = 0; i < args->verbosity; i++) {
crm_bump_log_level(argc, argv);
}
if (!args->version) {
if (args->quiet) {
include_exclude_cb("--exclude", "times", NULL, NULL);
}
if (is_set(options.mon_ops, mon_op_watch_fencing)) {
fence_history_cb("--fence-history", "0", NULL, NULL);
options.mon_ops |= mon_op_fence_connect;
}
/* create the cib-object early to be able to do further
* decisions based on the cib-source
*/
cib = cib_new();
if (cib == NULL) {
rc = -EINVAL;
} else {
switch (cib->variant) {
case cib_native:
/* cib & fencing - everything available */
break;
case cib_file:
/* Don't try to connect to fencing as we
* either don't have a running cluster or
* the fencing-information would possibly
* not match the cib data from a file.
* As we don't expect cib-updates coming
* in enforce one-shot. */
fence_history_cb("--fence-history", "0", NULL, NULL);
options.mon_ops |= mon_op_one_shot;
break;
case cib_remote:
/* updates coming in but no fencing */
fence_history_cb("--fence-history", "0", NULL, NULL);
break;
case cib_undefined:
case cib_database:
default:
/* something is odd */
rc = -EINVAL;
break;
}
}
if (is_set(options.mon_ops, mon_op_one_shot)) {
if (output_format == mon_output_console) {
output_format = mon_output_plain;
}
} else if (options.daemonize) {
if ((output_format == mon_output_console) || (output_format == mon_output_plain)) {
output_format = mon_output_none;
}
crm_enable_stderr(FALSE);
if ((args->output_dest == NULL || safe_str_eq(args->output_dest, "-")) && !options.external_agent) {
g_set_error(&error, G_OPTION_ERROR, CRM_EX_USAGE, "--daemonize requires at least one of --output-to and --external-agent");
return clean_up(CRM_EX_USAGE);
}
if (cib) {
/* to be on the safe side don't have cib-object around
* when we are forking
*/
cib_delete(cib);
cib = NULL;
crm_make_daemon(crm_system_name, TRUE, options.pid_file);
cib = cib_new();
if (cib == NULL) {
rc = -EINVAL;
}
/* otherwise assume we've got the same cib-object we've just destroyed
* in our parent
*/
}
} else if (output_format == mon_output_console) {
#if CURSES_ENABLED
crm_enable_stderr(FALSE);
#else
options.mon_ops |= mon_op_one_shot;
output_format = mon_output_plain;
printf("Defaulting to one-shot mode\n");
printf("You need to have curses available at compile time to enable console mode\n");
#endif
}
}
if (rc != pcmk_ok) {
// Shouldn't really be possible
g_set_error(&error, G_OPTION_ERROR, CRM_EX_ERROR, "Invalid CIB source");
return clean_up(CRM_EX_ERROR);
}
reconcile_output_format(args);
add_output_args();
if (args->version && output_format == mon_output_console) {
/* Use the text output format here if we are in curses mode but were given
* --version. Displaying version information uses printf, and then we
* immediately exit. We don't want to initialize curses for that.
*/
rc = pcmk__output_new(&out, "text", args->output_dest, argv);
} else {
rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
}
if (rc != pcmk_rc_ok) {
g_set_error(&error, G_OPTION_ERROR, CRM_EX_ERROR, "Error creating output format %s: %s",
args->output_ty, pcmk_rc_str(rc));
return clean_up(CRM_EX_ERROR);
}
/* output_format MUST NOT BE CHANGED AFTER THIS POINT. */
/* Apply --include/--exclude flags we used internally. There's no error reporting
* here because this would be a programming error.
*/
apply_include_exclude(options.includes_excludes, output_format, &error);
/* And now apply any --include/--exclude flags the user gave on the command line.
* These are done in a separate pass from the internal ones because we want to
* make sure whatever the user specifies overrides whatever we do.
*/
if (!apply_include_exclude(options.user_includes_excludes, output_format, &error)) {
return clean_up(CRM_EX_USAGE);
}
crm_mon_register_messages(out);
pe__register_messages(out);
stonith__register_messages(out);
if (args->version) {
out->version(out, false);
return clean_up(CRM_EX_OK);
}
/* Extra sanity checks when in CGI mode */
if (output_format == mon_output_cgi) {
if (cib && cib->variant == cib_file) {
g_set_error(&error, G_OPTION_ERROR, CRM_EX_USAGE, "CGI mode used with CIB file");
return clean_up(CRM_EX_USAGE);
} else if (options.external_agent != NULL) {
g_set_error(&error, G_OPTION_ERROR, CRM_EX_USAGE, "CGI mode cannot be used with --external-agent");
return clean_up(CRM_EX_USAGE);
} else if (options.daemonize == TRUE) {
g_set_error(&error, G_OPTION_ERROR, CRM_EX_USAGE, "CGI mode cannot be used with -d");
return clean_up(CRM_EX_USAGE);
}
}
if (output_format == mon_output_xml || output_format == mon_output_legacy_xml) {
options.mon_ops |= mon_op_print_timing;
}
crm_info("Starting %s", crm_system_name);
if (cib) {
do {
if (is_not_set(options.mon_ops, mon_op_one_shot)) {
print_as(output_format ,"Waiting until cluster is available on this node ...\n");
}
rc = cib_connect(is_not_set(options.mon_ops, mon_op_one_shot));
if (is_set(options.mon_ops, mon_op_one_shot)) {
break;
} else if (rc != pcmk_ok) {
sleep(options.reconnect_msec / 1000);
#if CURSES_ENABLED
if (output_format == mon_output_console) {
clear();
refresh();
}
#endif
} else {
if (output_format == mon_output_html && out->dest != stdout) {
printf("Writing html to %s ...\n", args->output_dest);
}
}
} while (rc == -ENOTCONN);
}
if (rc != pcmk_ok) {
if (output_format == mon_output_monitor) {
g_set_error(&error, G_OPTION_ERROR, CRM_EX_ERROR, "CLUSTER CRIT: Connection to cluster failed: %s",
pcmk_strerror(rc));
return clean_up(MON_STATUS_CRIT);
} else {
if (rc == -ENOTCONN) {
g_set_error(&error, G_OPTION_ERROR, CRM_EX_ERROR, "\nError: cluster is not available on this node");
} else {
g_set_error(&error, G_OPTION_ERROR, CRM_EX_ERROR, "\nConnection to cluster failed: %s", pcmk_strerror(rc));
}
}
if (output_format == mon_output_console) {
sleep(2);
}
return clean_up(crm_errno2exit(rc));
}
if (is_set(options.mon_ops, mon_op_one_shot)) {
return clean_up(CRM_EX_OK);
}
mainloop = g_main_loop_new(NULL, FALSE);
mainloop_add_signal(SIGTERM, mon_shutdown);
mainloop_add_signal(SIGINT, mon_shutdown);
#if CURSES_ENABLED
if (output_format == mon_output_console) {
ncurses_winch_handler = crm_signal_handler(SIGWINCH, mon_winresize);
if (ncurses_winch_handler == SIG_DFL ||
ncurses_winch_handler == SIG_IGN || ncurses_winch_handler == SIG_ERR)
ncurses_winch_handler = NULL;
g_io_add_watch(g_io_channel_unix_new(STDIN_FILENO), G_IO_IN, detect_user_input, NULL);
}
#endif
refresh_trigger = mainloop_add_trigger(G_PRIORITY_LOW, mon_refresh_display, NULL);
g_main_loop_run(mainloop);
g_main_loop_unref(mainloop);
crm_info("Exiting %s", crm_system_name);
return clean_up(CRM_EX_OK);
}
/*!
* \internal
* \brief Print one-line status suitable for use with monitoring software
*
* \param[in] data_set Working set of CIB state
* \param[in] history List of stonith actions
*
* \note This function's output (and the return code when the program exits)
* should conform to https://www.monitoring-plugins.org/doc/guidelines.html
*/
static void
print_simple_status(pcmk__output_t *out, pe_working_set_t * data_set,
stonith_history_t *history, unsigned int mon_ops)
{
GListPtr gIter = NULL;
int nodes_online = 0;
int nodes_standby = 0;
int nodes_maintenance = 0;
char *offline_nodes = NULL;
gboolean no_dc = FALSE;
gboolean offline = FALSE;
if (data_set->dc_node == NULL) {
mon_ops |= mon_op_has_warnings;
no_dc = TRUE;
}
for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
pe_node_t *node = (pe_node_t *) gIter->data;
if (node->details->standby && node->details->online) {
nodes_standby++;
} else if (node->details->maintenance && node->details->online) {
nodes_maintenance++;
} else if (node->details->online) {
nodes_online++;
} else {
char *s = crm_strdup_printf("offline node: %s", node->details->uname);
offline_nodes = pcmk__add_word(offline_nodes, s);
free(s);
mon_ops |= mon_op_has_warnings;
offline = TRUE;
}
}
if (is_set(mon_ops, mon_op_has_warnings)) {
out->info(out, "CLUSTER WARN:%s%s%s",
no_dc ? " No DC" : "",
no_dc && offline ? "," : "",
offline ? offline_nodes : "");
free(offline_nodes);
} else {
char *nodes_standby_s = NULL;
char *nodes_maint_s = NULL;
if (nodes_standby > 0) {
nodes_standby_s = crm_strdup_printf(", %d standby node%s", nodes_standby,
pcmk__plural_s(nodes_standby));
}
if (nodes_maintenance > 0) {
nodes_maint_s = crm_strdup_printf(", %d maintenance node%s",
nodes_maintenance,
pcmk__plural_s(nodes_maintenance));
}
out->info(out, "CLUSTER OK: %d node%s online%s%s, "
"%d resource instance%s configured",
nodes_online, pcmk__plural_s(nodes_online),
nodes_standby_s != NULL ? nodes_standby_s : "",
nodes_maint_s != NULL ? nodes_maint_s : "",
data_set->ninstances, pcmk__plural_s(data_set->ninstances));
free(nodes_standby_s);
free(nodes_maint_s);
}
}
/*!
* \internal
* \brief Reduce the stonith-history
* for successful actions we keep the last of every action-type & target
* for failed actions we record as well who had failed
* for actions in progress we keep full track
*
* \param[in] history List of stonith actions
*
*/
static stonith_history_t *
reduce_stonith_history(stonith_history_t *history)
{
stonith_history_t *new = history, *hp, *np;
if (new) {
hp = new->next;
new->next = NULL;
while (hp) {
stonith_history_t *hp_next = hp->next;
hp->next = NULL;
for (np = new; ; np = np->next) {
if ((hp->state == st_done) || (hp->state == st_failed)) {
/* action not in progress */
if (safe_str_eq(hp->target, np->target) &&
safe_str_eq(hp->action, np->action) &&
(hp->state == np->state) &&
((hp->state == st_done) ||
safe_str_eq(hp->delegate, np->delegate))) {
/* purge older hp */
stonith_history_free(hp);
break;
}
}
if (!np->next) {
np->next = hp;
break;
}
}
hp = hp_next;
}
}
return new;
}
static int
send_custom_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
int status, const char *desc)
{
pid_t pid;
/*setenv needs chars, these are ints */
char *rc_s = crm_itoa(rc);
char *status_s = crm_itoa(status);
char *target_rc_s = crm_itoa(target_rc);
crm_debug("Sending external notification to '%s' via '%s'", options.external_recipient, options.external_agent);
if(rsc) {
setenv("CRM_notify_rsc", rsc, 1);
}
if (options.external_recipient) {
setenv("CRM_notify_recipient", options.external_recipient, 1);
}
setenv("CRM_notify_node", node, 1);
setenv("CRM_notify_task", task, 1);
setenv("CRM_notify_desc", desc, 1);
setenv("CRM_notify_rc", rc_s, 1);
setenv("CRM_notify_target_rc", target_rc_s, 1);
setenv("CRM_notify_status", status_s, 1);
pid = fork();
if (pid == -1) {
crm_perror(LOG_ERR, "notification fork() failed.");
}
if (pid == 0) {
/* crm_debug("notification: I am the child. Executing the nofitication program."); */
execl(options.external_agent, options.external_agent, NULL);
exit(CRM_EX_ERROR);
}
crm_trace("Finished running custom notification program '%s'.", options.external_agent);
free(target_rc_s);
free(status_s);
free(rc_s);
return 0;
}
static void
handle_rsc_op(xmlNode * xml, const char *node_id)
{
int rc = -1;
int status = -1;
int target_rc = -1;
gboolean notify = TRUE;
char *rsc = NULL;
char *task = NULL;
const char *desc = NULL;
const char *magic = NULL;
const char *id = NULL;
const char *node = NULL;
xmlNode *n = xml;
xmlNode * rsc_op = xml;
if(strcmp((const char*)xml->name, XML_LRM_TAG_RSC_OP) != 0) {
xmlNode *cIter;
for(cIter = xml->children; cIter; cIter = cIter->next) {
handle_rsc_op(cIter, node_id);
}
return;
}
id = crm_element_value(rsc_op, XML_LRM_ATTR_TASK_KEY);
if (id == NULL) {
/* Compatibility with <= 1.1.5 */
id = ID(rsc_op);
}
magic = crm_element_value(rsc_op, XML_ATTR_TRANSITION_MAGIC);
if (magic == NULL) {
/* non-change */
return;
}
if (!decode_transition_magic(magic, NULL, NULL, NULL, &status, &rc,
&target_rc)) {
crm_err("Invalid event %s detected for %s", magic, id);
return;
}
if (parse_op_key(id, &rsc, &task, NULL) == FALSE) {
crm_err("Invalid event detected for %s", id);
goto bail;
}
node = crm_element_value(rsc_op, XML_LRM_ATTR_TARGET);
while (n != NULL && safe_str_neq(XML_CIB_TAG_STATE, TYPE(n))) {
n = n->parent;
}
if(node == NULL && n) {
node = crm_element_value(n, XML_ATTR_UNAME);
}
if (node == NULL && n) {
node = ID(n);
}
if (node == NULL) {
node = node_id;
}
if (node == NULL) {
crm_err("No node detected for event %s (%s)", magic, id);
goto bail;
}
/* look up where we expected it to be? */
desc = pcmk_strerror(pcmk_ok);
if (status == PCMK_LRM_OP_DONE && target_rc == rc) {
crm_notice("%s of %s on %s completed: %s", task, rsc, node, desc);
if (rc == PCMK_OCF_NOT_RUNNING) {
notify = FALSE;
}
} else if (status == PCMK_LRM_OP_DONE) {
desc = services_ocf_exitcode_str(rc);
crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
} else {
desc = services_lrm_status_str(status);
crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
}
if (notify && options.external_agent) {
send_custom_trap(node, rsc, task, target_rc, rc, status, desc);
}
bail:
free(rsc);
free(task);
}
static gboolean
mon_trigger_refresh(gpointer user_data)
{
mainloop_set_trigger(refresh_trigger);
return FALSE;
}
#define NODE_PATT "/lrm[@id="
static char *
get_node_from_xpath(const char *xpath)
{
char *nodeid = NULL;
char *tmp = strstr(xpath, NODE_PATT);
if(tmp) {
tmp += strlen(NODE_PATT);
tmp += 1;
nodeid = strdup(tmp);
tmp = strstr(nodeid, "\'");
CRM_ASSERT(tmp);
tmp[0] = 0;
}
return nodeid;
}
static void
crm_diff_update_v2(const char *event, xmlNode * msg)
{
xmlNode *change = NULL;
xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
for (change = __xml_first_child(diff); change != NULL; change = __xml_next(change)) {
const char *name = NULL;
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
xmlNode *match = NULL;
const char *node = NULL;
if(op == NULL) {
continue;
} else if(strcmp(op, "create") == 0) {
match = change->children;
} else if(strcmp(op, "move") == 0) {
continue;
} else if(strcmp(op, "delete") == 0) {
continue;
} else if(strcmp(op, "modify") == 0) {
match = first_named_child(change, XML_DIFF_RESULT);
if(match) {
match = match->children;
}
}
if(match) {
name = (const char *)match->name;
}
crm_trace("Handling %s operation for %s %p, %s", op, xpath, match, name);
if(xpath == NULL) {
/* Version field, ignore */
} else if(name == NULL) {
crm_debug("No result for %s operation to %s", op, xpath);
CRM_ASSERT(strcmp(op, "delete") == 0 || strcmp(op, "move") == 0);
} else if(strcmp(name, XML_TAG_CIB) == 0) {
xmlNode *state = NULL;
xmlNode *status = first_named_child(match, XML_CIB_TAG_STATUS);
for (state = __xml_first_child_element(status); state != NULL;
state = __xml_next_element(state)) {
node = crm_element_value(state, XML_ATTR_UNAME);
if (node == NULL) {
node = ID(state);
}
handle_rsc_op(state, node);
}
} else if(strcmp(name, XML_CIB_TAG_STATUS) == 0) {
xmlNode *state = NULL;
for (state = __xml_first_child_element(match); state != NULL;
state = __xml_next_element(state)) {
node = crm_element_value(state, XML_ATTR_UNAME);
if (node == NULL) {
node = ID(state);
}
handle_rsc_op(state, node);
}
} else if(strcmp(name, XML_CIB_TAG_STATE) == 0) {
node = crm_element_value(match, XML_ATTR_UNAME);
if (node == NULL) {
node = ID(match);
}
handle_rsc_op(match, node);
} else if(strcmp(name, XML_CIB_TAG_LRM) == 0) {
node = ID(match);
handle_rsc_op(match, node);
} else if(strcmp(name, XML_LRM_TAG_RESOURCES) == 0) {
char *local_node = get_node_from_xpath(xpath);
handle_rsc_op(match, local_node);
free(local_node);
} else if(strcmp(name, XML_LRM_TAG_RESOURCE) == 0) {
char *local_node = get_node_from_xpath(xpath);
handle_rsc_op(match, local_node);
free(local_node);
} else if(strcmp(name, XML_LRM_TAG_RSC_OP) == 0) {
char *local_node = get_node_from_xpath(xpath);
handle_rsc_op(match, local_node);
free(local_node);
} else {
crm_trace("Ignoring %s operation for %s %p, %s", op, xpath, match, name);
}
}
}
static void
crm_diff_update_v1(const char *event, xmlNode * msg)
{
/* Process operation updates */
xmlXPathObject *xpathObj = xpath_search(msg,
"//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED
"//" XML_LRM_TAG_RSC_OP);
int lpc = 0, max = numXpathResults(xpathObj);
for (lpc = 0; lpc < max; lpc++) {
xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
handle_rsc_op(rsc_op, NULL);
}
freeXpathObject(xpathObj);
}
static void
crm_diff_update(const char *event, xmlNode * msg)
{
int rc = -1;
static bool stale = FALSE;
gboolean cib_updated = FALSE;
xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
print_dot(output_format);
if (current_cib != NULL) {
rc = xml_apply_patchset(current_cib, diff, TRUE);
switch (rc) {
case -pcmk_err_diff_resync:
case -pcmk_err_diff_failed:
crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
free_xml(current_cib); current_cib = NULL;
break;
case pcmk_ok:
cib_updated = TRUE;
break;
default:
crm_notice("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
free_xml(current_cib); current_cib = NULL;
}
}
if (current_cib == NULL) {
crm_trace("Re-requesting the full cib");
cib->cmds->query(cib, NULL, &current_cib, cib_scope_local | cib_sync_call);
}
if (options.external_agent) {
int format = 0;
crm_element_value_int(diff, "format", &format);
switch(format) {
case 1:
crm_diff_update_v1(event, msg);
break;
case 2:
crm_diff_update_v2(event, msg);
break;
default:
crm_err("Unknown patch format: %d", format);
}
}
if (current_cib == NULL) {
if(!stale) {
print_as(output_format, "--- Stale data ---");
}
stale = TRUE;
return;
}
stale = FALSE;
kick_refresh(cib_updated);
}
static gboolean
mon_refresh_display(gpointer user_data)
{
xmlNode *cib_copy = copy_xml(current_cib);
stonith_history_t *stonith_history = NULL;
int history_rc = 0;
last_refresh = time(NULL);
if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) {
if (cib) {
cib->cmds->signoff(cib);
}
out->err(out, "Upgrade failed: %s", pcmk_strerror(-pcmk_err_schema_validation));
if (output_format == mon_output_console) {
sleep(2);
}
clean_up(CRM_EX_CONFIG);
return FALSE;
}
/* get the stonith-history if there is evidence we need it
*/
while (is_set(options.mon_ops, mon_op_fence_history)) {
if (st != NULL) {
history_rc = st->cmds->history(st, st_opt_sync_call, NULL, &stonith_history, 120);
if (history_rc != 0) {
out->err(out, "Critical: Unable to get stonith-history");
mon_cib_connection_destroy(NULL);
} else {
stonith_history = stonith__sort_history(stonith_history);
if (is_not_set(options.mon_ops, mon_op_fence_full_history) && output_format != mon_output_xml) {
stonith_history = reduce_stonith_history(stonith_history);
}
break; /* all other cases are errors */
}
} else {
out->err(out, "Critical: No stonith-API");
}
free_xml(cib_copy);
out->err(out, "Reading stonith-history failed");
if (output_format == mon_output_console) {
sleep(2);
}
return FALSE;
}
if (mon_data_set == NULL) {
mon_data_set = pe_new_working_set();
CRM_ASSERT(mon_data_set != NULL);
}
set_bit(mon_data_set->flags, pe_flag_no_compat);
mon_data_set->input = cib_copy;
cluster_status(mon_data_set);
/* Unpack constraints if any section will need them
* (tickets may be referenced in constraints but not granted yet,
* and bans need negative location constraints) */
if (is_set(show, mon_show_bans) || is_set(show, mon_show_tickets)) {
xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS,
mon_data_set->input);
unpack_constraints(cib_constraints, mon_data_set);
}
switch (output_format) {
case mon_output_html:
case mon_output_cgi:
if (print_html_status(out, mon_data_set, stonith_history, options.mon_ops,
show, options.neg_location_prefix) != 0) {
g_set_error(&error, G_OPTION_ERROR, CRM_EX_CANTCREAT, "Critical: Unable to output html file");
clean_up(CRM_EX_CANTCREAT);
return FALSE;
}
break;
case mon_output_legacy_xml:
case mon_output_xml:
print_xml_status(out, mon_data_set, crm_errno2exit(history_rc),
stonith_history, options.mon_ops, show,
options.neg_location_prefix);
break;
case mon_output_monitor:
print_simple_status(out, mon_data_set, stonith_history, options.mon_ops);
if (is_set(options.mon_ops, mon_op_has_warnings)) {
clean_up(MON_STATUS_WARN);
return FALSE;
}
break;
case mon_output_console:
/* If curses is not enabled, this will just fall through to the plain
* text case.
*/
#if CURSES_ENABLED
blank_screen();
print_status(out, mon_data_set, stonith_history, options.mon_ops, show,
options.neg_location_prefix);
refresh();
break;
#endif
case mon_output_plain:
print_status(out, mon_data_set, stonith_history, options.mon_ops, show,
options.neg_location_prefix);
break;
case mon_output_unset:
case mon_output_none:
break;
}
stonith_history_free(stonith_history);
stonith_history = NULL;
pe_reset_working_set(mon_data_set);
return TRUE;
}
static void
mon_st_callback_event(stonith_t * st, stonith_event_t * e)
{
if (st->state == stonith_disconnected) {
/* disconnect cib as well and have everything reconnect */
mon_cib_connection_destroy(NULL);
} else if (options.external_agent) {
char *desc = crm_strdup_printf("Operation %s requested by %s for peer %s: %s (ref=%s)",
e->operation, e->origin, e->target, pcmk_strerror(e->result),
e->id);
send_custom_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
free(desc);
}
}
static void
kick_refresh(gboolean data_updated)
{
static int updates = 0;
time_t now = time(NULL);
if (data_updated) {
updates++;
}
if(refresh_timer == NULL) {
refresh_timer = mainloop_timer_add("refresh", 2000, FALSE, mon_trigger_refresh, NULL);
}
/* Refresh
* - immediately if the last update was more than 5s ago
* - every 10 cib-updates
* - at most 2s after the last update
*/
if ((now - last_refresh) > (options.reconnect_msec / 1000)) {
mainloop_set_trigger(refresh_trigger);
mainloop_timer_stop(refresh_timer);
updates = 0;
} else if(updates >= 10) {
mainloop_set_trigger(refresh_trigger);
mainloop_timer_stop(refresh_timer);
updates = 0;
} else {
mainloop_timer_start(refresh_timer);
}
}
static void
mon_st_callback_display(stonith_t * st, stonith_event_t * e)
{
if (st->state == stonith_disconnected) {
/* disconnect cib as well and have everything reconnect */
mon_cib_connection_destroy(NULL);
} else {
print_dot(output_format);
kick_refresh(TRUE);
}
}
static void
clean_up_connections(void)
{
if (cib != NULL) {
cib->cmds->signoff(cib);
cib_delete(cib);
cib = NULL;
}
if (st != NULL) {
if (st->state != stonith_disconnected) {
st->cmds->remove_notification(st, T_STONITH_NOTIFY_DISCONNECT);
st->cmds->remove_notification(st, T_STONITH_NOTIFY_FENCE);
st->cmds->remove_notification(st, T_STONITH_NOTIFY_HISTORY);
st->cmds->disconnect(st);
}
stonith_api_delete(st);
st = NULL;
}
}
static void
handle_html_output(crm_exit_t exit_code) {
xmlNodePtr html = NULL;
pcmk__html_add_header(html, "meta", "http-equiv", "refresh", "content",
crm_itoa(options.reconnect_msec/1000), NULL);
out->finish(out, exit_code, true, (void **) &html);
}
/*
* De-init ncurses, disconnect from the CIB manager, disconnect fencing,
* deallocate memory and show usage-message if requested.
*
* We don't actually return, but nominally returning crm_exit_t allows a usage
* like "return clean_up(exit_code);" which helps static analysis understand the
* code flow.
*/
static crm_exit_t
clean_up(crm_exit_t exit_code)
{
/* Quitting crm_mon is much more complicated than it ought to be. */
/* (1) Close connections, free things, etc. */
clean_up_connections();
free(options.pid_file);
free(options.neg_location_prefix);
g_slist_free_full(options.includes_excludes, free);
pe_free_working_set(mon_data_set);
mon_data_set = NULL;
g_strfreev(processed_args);
/* (2) If this is abnormal termination and we're in curses mode, shut down
* curses first. Any messages displayed to the screen before curses is shut
* down will be lost because doing the shut down will also restore the
* screen to whatever it looked like before crm_mon was started.
*/
if ((error != NULL || exit_code == CRM_EX_USAGE) && output_format == mon_output_console) {
out->finish(out, exit_code, false, NULL);
pcmk__output_free(out);
out = NULL;
}
/* (3) If this is a command line usage related failure, print the usage
* message.
*/
if (exit_code == CRM_EX_USAGE && (output_format == mon_output_console || output_format == mon_output_plain)) {
- fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL));
+ char *help = g_option_context_get_help(context, TRUE, NULL);
+
+ fprintf(stderr, "%s", help);
+ free(help);
}
pcmk__free_arg_context(context);
/* (4) If this is any kind of error, print the error out and exit. Make
* sure to handle situations both before and after formatted output is
* set up. We want errors to appear formatted if at all possible.
*/
if (error != NULL) {
if (out != NULL) {
out->err(out, "%s: %s\n", g_get_prgname(), error->message);
out->finish(out, exit_code, true, NULL);
pcmk__output_free(out);
} else {
fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
}
g_clear_error(&error);
crm_exit(exit_code);
}
/* (5) Print formatted output to the screen if we made it far enough in
* crm_mon to be able to do so.
*/
if (out != NULL) {
switch (output_format) {
case mon_output_cgi:
case mon_output_html:
handle_html_output(exit_code);
break;
default:
out->finish(out, exit_code, true, NULL);
break;
}
pcmk__output_free(out);
pcmk__unregister_formats();
}
crm_exit(exit_code);
}
diff --git a/tools/crm_node.c b/tools/crm_node.c
index 3fb9856fac..ca8372c7b9 100644
--- a/tools/crm_node.c
+++ b/tools/crm_node.c
@@ -1,665 +1,668 @@
/*
* Copyright 2004-2020 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <crm/crm.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/mainloop.h>
#include <crm/msg_xml.h>
#include <crm/cib.h>
#include <crm/common/attrd_internal.h>
#define SUMMARY "crm_node - Tool for displaying low-level node information"
struct {
gboolean corosync;
gboolean dangerous_cmd;
gboolean force_flag;
char command;
int nodeid;
char *target_uname;
} options = {
.command = '\0',
.force_flag = FALSE
};
gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
static char *pid_s = NULL;
static GMainLoop *mainloop = NULL;
static crm_exit_t exit_code = CRM_EX_OK;
#define INDENT " "
static GOptionEntry command_entries[] = {
{ "cluster-id", 'i', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display this node's cluster id",
NULL },
{ "list", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display all known members (past and present) of this cluster",
NULL },
{ "name", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display the name used by the cluster for this node",
NULL },
{ "partition", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display the members of this partition",
NULL },
{ "quorum", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display a 1 if our partition has quorum, 0 if not",
NULL },
{ "name-for-id", 'N', 0, G_OPTION_ARG_CALLBACK, name_cb,
"Display the name used by the cluster for the node with the specified ID",
"ID" },
{ "remove", 'R', 0, G_OPTION_ARG_CALLBACK, remove_cb,
"(Advanced) Remove the (stopped) node with the specified name from Pacemaker's\n"
INDENT "configuration and caches (the node must already have been removed from\n"
INDENT "the underlying cluster stack configuration",
"NAME" },
{ NULL }
};
static GOptionEntry addl_entries[] = {
{ "force", 'f', 0, G_OPTION_ARG_NONE, &options.force_flag,
NULL,
NULL },
#if SUPPORT_COROSYNC
/* Unused and deprecated */
{ "corosync", 'C', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.corosync,
NULL,
NULL },
#endif
// @TODO add timeout option for when IPC replies are needed
{ NULL }
};
gboolean
command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
if (safe_str_eq("-i", option_name) || safe_str_eq("--cluster-id", option_name)) {
options.command = 'i';
} else if (safe_str_eq("-l", option_name) || safe_str_eq("--list", option_name)) {
options.command = 'l';
} else if (safe_str_eq("-n", option_name) || safe_str_eq("--name", option_name)) {
options.command = 'n';
} else if (safe_str_eq("-p", option_name) || safe_str_eq("--partition", option_name)) {
options.command = 'p';
} else if (safe_str_eq("-q", option_name) || safe_str_eq("--quorum", option_name)) {
options.command = 'q';
} else {
g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Unknown param passed to command_cb: %s\n", option_name);
return FALSE;
}
return TRUE;
}
gboolean
name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.command = 'N';
options.nodeid = crm_parse_int(optarg, NULL);
return TRUE;
}
gboolean
remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
if (optarg == NULL) {
crm_err("-R option requires an argument");
g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "-R option requires an argument");
return FALSE;
}
options.command = 'R';
options.dangerous_cmd = TRUE;
options.target_uname = strdup(optarg);
return TRUE;
}
/*!
* \internal
* \brief Exit crm_node
* Clean up memory, and either quit mainloop (if running) or exit
*
* \param[in] value Exit status
*/
static void
crm_node_exit(crm_exit_t value)
{
if (pid_s) {
free(pid_s);
pid_s = NULL;
}
exit_code = value;
if (mainloop && g_main_loop_is_running(mainloop)) {
g_main_loop_quit(mainloop);
} else {
crm_exit(exit_code);
}
}
static void
exit_disconnect(gpointer user_data)
{
fprintf(stderr, "error: Lost connection to cluster\n");
crm_node_exit(CRM_EX_DISCONNECT);
}
typedef int (*ipc_dispatch_fn) (const char *buffer, ssize_t length,
gpointer userdata);
static crm_ipc_t *
new_mainloop_for_ipc(const char *system, ipc_dispatch_fn dispatch)
{
mainloop_io_t *source = NULL;
crm_ipc_t *ipc = NULL;
struct ipc_client_callbacks ipc_callbacks = {
.dispatch = dispatch,
.destroy = exit_disconnect
};
mainloop = g_main_loop_new(NULL, FALSE);
source = mainloop_add_ipc_client(system, G_PRIORITY_DEFAULT, 0,
NULL, &ipc_callbacks);
ipc = mainloop_get_ipc_client(source);
if (ipc == NULL) {
fprintf(stderr,
"error: Could not connect to cluster (is it running?)\n");
crm_node_exit(CRM_EX_DISCONNECT);
}
return ipc;
}
static void
run_mainloop_and_exit()
{
g_main_loop_run(mainloop);
g_main_loop_unref(mainloop);
mainloop = NULL;
crm_node_exit(exit_code);
}
static int
send_controller_hello(crm_ipc_t *controller)
{
xmlNode *hello = NULL;
int rc;
pid_s = pcmk__getpid_s();
hello = create_hello_message(pid_s, crm_system_name, "1", "0");
rc = crm_ipc_send(controller, hello, 0, 0, NULL);
free_xml(hello);
return (rc < 0)? rc : 0;
}
static int
send_node_info_request(crm_ipc_t *controller, uint32_t nodeid)
{
xmlNode *ping = NULL;
int rc;
ping = create_request(CRM_OP_NODE_INFO, NULL, NULL, CRM_SYSTEM_CRMD,
crm_system_name, pid_s);
if (nodeid > 0) {
crm_xml_add_int(ping, XML_ATTR_ID, nodeid);
}
rc = crm_ipc_send(controller, ping, 0, 0, NULL);
free_xml(ping);
return (rc < 0)? rc : 0;
}
static int
dispatch_controller(const char *buffer, ssize_t length, gpointer userdata)
{
xmlNode *message = string2xml(buffer);
xmlNode *data = NULL;
const char *value = NULL;
if (message == NULL) {
fprintf(stderr, "error: Could not understand reply from controller\n");
crm_node_exit(CRM_EX_PROTOCOL);
return 0;
}
crm_log_xml_trace(message, "controller reply");
exit_code = CRM_EX_PROTOCOL;
// Validate reply
value = crm_element_value(message, F_CRM_MSG_TYPE);
if (safe_str_neq(value, XML_ATTR_RESPONSE)) {
fprintf(stderr, "error: Message from controller was not a reply\n");
goto done;
}
value = crm_element_value(message, XML_ATTR_REFERENCE);
if (value == NULL) {
fprintf(stderr, "error: Controller reply did not specify original message\n");
goto done;
}
data = get_message_xml(message, F_CRM_DATA);
if (data == NULL) {
fprintf(stderr, "error: Controller reply did not contain any data\n");
goto done;
}
switch (options.command) {
case 'i':
value = crm_element_value(data, XML_ATTR_ID);
if (value == NULL) {
fprintf(stderr, "error: Controller reply did not contain node ID\n");
} else {
printf("%s\n", value);
exit_code = CRM_EX_OK;
}
break;
case 'n':
case 'N':
value = crm_element_value(data, XML_ATTR_UNAME);
if (value == NULL) {
fprintf(stderr, "Node is not known to cluster\n");
exit_code = CRM_EX_NOHOST;
} else {
printf("%s\n", value);
exit_code = CRM_EX_OK;
}
break;
case 'q':
value = crm_element_value(data, XML_ATTR_HAVE_QUORUM);
if (value == NULL) {
fprintf(stderr, "error: Controller reply did not contain quorum status\n");
} else {
bool quorum = crm_is_true(value);
printf("%d\n", quorum);
exit_code = quorum? CRM_EX_OK : CRM_EX_QUORUM;
}
break;
default:
fprintf(stderr, "internal error: Controller reply not expected\n");
exit_code = CRM_EX_SOFTWARE;
break;
}
done:
free_xml(message);
crm_node_exit(exit_code);
return 0;
}
static void
run_controller_mainloop(uint32_t nodeid)
{
crm_ipc_t *controller = NULL;
int rc;
controller = new_mainloop_for_ipc(CRM_SYSTEM_CRMD, dispatch_controller);
rc = send_controller_hello(controller);
if (rc < 0) {
fprintf(stderr, "error: Could not register with controller: %s\n",
pcmk_strerror(rc));
crm_node_exit(crm_errno2exit(rc));
}
rc = send_node_info_request(controller, nodeid);
if (rc < 0) {
fprintf(stderr, "error: Could not ping controller: %s\n",
pcmk_strerror(rc));
crm_node_exit(crm_errno2exit(rc));
}
// Run main loop to get controller reply via dispatch_controller()
run_mainloop_and_exit();
}
static void
print_node_name()
{
// Check environment first (i.e. when called by resource agent)
const char *name = getenv("OCF_RESKEY_" CRM_META "_" XML_LRM_ATTR_TARGET);
if (name != NULL) {
printf("%s\n", name);
crm_node_exit(CRM_EX_OK);
} else {
// Otherwise ask the controller
run_controller_mainloop(0);
}
}
static int
cib_remove_node(long id, const char *name)
{
int rc;
cib_t *cib = NULL;
xmlNode *node = NULL;
xmlNode *node_state = NULL;
crm_trace("Removing %s from the CIB", name);
if(name == NULL && id == 0) {
return -ENOTUNIQ;
}
node = create_xml_node(NULL, XML_CIB_TAG_NODE);
node_state = create_xml_node(NULL, XML_CIB_TAG_STATE);
crm_xml_add(node, XML_ATTR_UNAME, name);
crm_xml_add(node_state, XML_ATTR_UNAME, name);
if (id > 0) {
crm_xml_set_id(node, "%ld", id);
crm_xml_add(node_state, XML_ATTR_ID, ID(node));
}
cib = cib_new();
cib->cmds->signon(cib, crm_system_name, cib_command);
rc = cib->cmds->remove(cib, XML_CIB_TAG_NODES, node, cib_sync_call);
if (rc != pcmk_ok) {
printf("Could not remove %s[%ld] from " XML_CIB_TAG_NODES ": %s",
name, id, pcmk_strerror(rc));
}
rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, node_state, cib_sync_call);
if (rc != pcmk_ok) {
printf("Could not remove %s[%ld] from " XML_CIB_TAG_STATUS ": %s",
name, id, pcmk_strerror(rc));
}
cib->cmds->signoff(cib);
cib_delete(cib);
return rc;
}
static int
tools_remove_node_cache(const char *node_name, long nodeid, const char *target)
{
int rc = -1;
crm_ipc_t *conn = crm_ipc_new(target, 0);
xmlNode *cmd = NULL;
if (!conn) {
return -ENOTCONN;
}
if (!crm_ipc_connect(conn)) {
crm_perror(LOG_ERR, "Connection to %s failed", target);
crm_ipc_destroy(conn);
return -ENOTCONN;
}
if(safe_str_eq(target, CRM_SYSTEM_CRMD)) {
// The controller requires a hello message before sending a request
rc = send_controller_hello(conn);
if (rc < 0) {
fprintf(stderr, "error: Could not register with controller: %s\n",
pcmk_strerror(rc));
return rc;
}
}
crm_trace("Removing %s[%ld] from the %s membership cache",
node_name, nodeid, target);
if(safe_str_eq(target, T_ATTRD)) {
cmd = create_xml_node(NULL, __FUNCTION__);
crm_xml_add(cmd, F_TYPE, T_ATTRD);
crm_xml_add(cmd, F_ORIG, crm_system_name);
crm_xml_add(cmd, PCMK__XA_TASK, PCMK__ATTRD_CMD_PEER_REMOVE);
crm_xml_add(cmd, PCMK__XA_ATTR_NODE_NAME, node_name);
if (nodeid > 0) {
crm_xml_add_int(cmd, PCMK__XA_ATTR_NODE_ID, (int) nodeid);
}
} else {
cmd = create_request(CRM_OP_RM_NODE_CACHE,
NULL, NULL, target, crm_system_name, pid_s);
if (nodeid > 0) {
crm_xml_set_id(cmd, "%ld", nodeid);
}
crm_xml_add(cmd, XML_ATTR_UNAME, node_name);
}
rc = crm_ipc_send(conn, cmd, 0, 0, NULL);
crm_debug("%s peer cache cleanup for %s (%ld): %d",
target, node_name, nodeid, rc);
if (rc > 0) {
rc = cib_remove_node(nodeid, node_name);
}
if (conn) {
crm_ipc_close(conn);
crm_ipc_destroy(conn);
}
free_xml(cmd);
return rc > 0 ? 0 : rc;
}
static void
remove_node(const char *target_uname)
{
int d = 0;
long nodeid = 0;
const char *node_name = NULL;
char *endptr = NULL;
const char *daemons[] = {
CRM_SYSTEM_CRMD,
"stonith-ng",
T_ATTRD,
CRM_SYSTEM_MCP,
};
// Check whether node was specified by name or numeric ID
errno = 0;
nodeid = strtol(target_uname, &endptr, 10);
if ((errno != 0) || (endptr == target_uname) || (*endptr != '\0')
|| (nodeid <= 0)) {
// It's not a positive integer, so assume it's a node name
nodeid = 0;
node_name = target_uname;
}
for (d = 0; d < DIMOF(daemons); d++) {
if (tools_remove_node_cache(node_name, nodeid, daemons[d])) {
crm_err("Failed to connect to %s to remove node '%s'",
daemons[d], target_uname);
crm_node_exit(CRM_EX_ERROR);
return;
}
}
crm_node_exit(CRM_EX_OK);
}
static gint
compare_node_xml(gconstpointer a, gconstpointer b)
{
const char *a_name = crm_element_value((xmlNode*) a, "uname");
const char *b_name = crm_element_value((xmlNode*) b, "uname");
return strcmp((a_name? a_name : ""), (b_name? b_name : ""));
}
static int
node_mcp_dispatch(const char *buffer, ssize_t length, gpointer userdata)
{
GList *nodes = NULL;
xmlNode *node = NULL;
xmlNode *msg = string2xml(buffer);
const char *uname;
const char *state;
if (msg == NULL) {
fprintf(stderr, "error: Could not understand pacemakerd response\n");
crm_node_exit(CRM_EX_PROTOCOL);
return 0;
}
crm_log_xml_trace(msg, "message");
for (node = __xml_first_child(msg); node != NULL; node = __xml_next(node)) {
nodes = g_list_insert_sorted(nodes, node, compare_node_xml);
}
for (GList *iter = nodes; iter; iter = iter->next) {
node = (xmlNode*) iter->data;
uname = crm_element_value(node, "uname");
state = crm_element_value(node, "state");
if (options.command == 'l') {
int id = 0;
crm_element_value_int(node, "id", &id);
printf("%d %s %s\n", id, (uname? uname : ""), (state? state : ""));
// This is CRM_NODE_MEMBER but we don't want to include cluster header
} else if ((options.command == 'p') && safe_str_eq(state, "member")) {
printf("%s ", (uname? uname : ""));
}
}
if (options.command == 'p') {
fprintf(stdout, "\n");
}
free_xml(msg);
crm_node_exit(CRM_EX_OK);
return 0;
}
static void
run_pacemakerd_mainloop()
{
crm_ipc_t *ipc = NULL;
xmlNode *poke = NULL;
ipc = new_mainloop_for_ipc(CRM_SYSTEM_MCP, node_mcp_dispatch);
// Sending anything will get us a list of nodes
poke = create_xml_node(NULL, "poke");
crm_ipc_send(ipc, poke, 0, 0, NULL);
free_xml(poke);
// Handle reply via node_mcp_dispatch()
run_mainloop_and_exit();
}
static GOptionContext *
build_arg_context(pcmk__common_args_t *args, GOptionGroup *group) {
GOptionContext *context = NULL;
GOptionEntry extra_prog_entries[] = {
{ "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
"Be less descriptive in output.",
NULL },
{ NULL }
};
context = pcmk__build_arg_context(args, NULL, &group);
/* Add the -q option, which cannot be part of the globally supported options
* because some tools use that flag for something else.
*/
pcmk__add_main_args(context, extra_prog_entries);
pcmk__add_arg_group(context, "commands", "Commands:",
"Show command help", command_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
return context;
}
int
main(int argc, char **argv)
{
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
GError *error = NULL;
GOptionContext *context = NULL;
GOptionGroup *output_group = NULL;
gchar **processed_args = NULL;
context = build_arg_context(args, output_group);
crm_log_cli_init("crm_node");
processed_args = pcmk__cmdline_preproc(argv, "NR");
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
exit_code = CRM_EX_USAGE;
goto done;
}
for (int i = 0; i < args->verbosity; i++) {
crm_bump_log_level(argc, argv);
}
if (args->version) {
/* FIXME: When crm_node is converted to use formatted output, this can go. */
pcmk__cli_help('v', CRM_EX_USAGE);
}
if (optind > argc || options.command == 0) {
- fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL));
+ char *help = g_option_context_get_help(context, TRUE, NULL);
+
+ fprintf(stderr, "%s", help);
+ free(help);
exit_code = CRM_EX_USAGE;
goto done;
}
if (options.dangerous_cmd && options.force_flag == FALSE) {
fprintf(stderr, "The supplied command is considered dangerous."
" To prevent accidental destruction of the cluster,"
" the --force flag is required in order to proceed.\n");
exit_code = CRM_EX_USAGE;
goto done;
}
switch (options.command) {
case 'n':
print_node_name();
break;
case 'R':
remove_node(options.target_uname);
break;
case 'i':
case 'q':
case 'N':
run_controller_mainloop(options.nodeid);
break;
case 'l':
case 'p':
run_pacemakerd_mainloop();
break;
default:
break;
}
done:
g_strfreev(processed_args);
g_clear_error(&error);
pcmk__free_arg_context(context);
crm_node_exit(exit_code);
return exit_code;
}
diff --git a/tools/crm_rule.c b/tools/crm_rule.c
index f80fd95f0e..b5e4b5ba46 100644
--- a/tools/crm_rule.c
+++ b/tools/crm_rule.c
@@ -1,359 +1,362 @@
/*
* Copyright 2019-2020 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/cib.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/iso8601.h>
#include <crm/msg_xml.h>
#include <crm/pengine/rules_internal.h>
#include <crm/pengine/status.h>
#include <pacemaker-internal.h>
#include <sys/stat.h>
#define SUMMARY "evaluate rules from the Pacemaker configuration"
enum crm_rule_mode {
crm_rule_mode_none,
crm_rule_mode_check
};
struct {
char *date;
char *input_xml;
enum crm_rule_mode mode;
char *rule;
} options = {
.mode = crm_rule_mode_none
};
static int crm_rule_check(pe_working_set_t *data_set, const char *rule_id, crm_time_t *effective_date);
static gboolean mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
static GOptionEntry mode_entries[] = {
{ "check", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, mode_cb,
"Check whether a rule is in effect",
NULL },
{ NULL }
};
static GOptionEntry data_entries[] = {
{ "xml-text", 'X', 0, G_OPTION_ARG_STRING, &options.input_xml,
"Use argument for XML (or stdin if '-')",
NULL },
{ NULL }
};
static GOptionEntry addl_entries[] = {
{ "date", 'd', 0, G_OPTION_ARG_STRING, &options.date,
"Whether the rule is in effect on a given date",
NULL },
{ "rule", 'r', 0, G_OPTION_ARG_STRING, &options.rule,
"The ID of the rule to check",
NULL },
{ NULL }
};
static gboolean
mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
if (strcmp(option_name, "c")) {
options.mode = crm_rule_mode_check;
}
return TRUE;
}
static int
crm_rule_check(pe_working_set_t *data_set, const char *rule_id, crm_time_t *effective_date)
{
xmlNode *cib_constraints = NULL;
xmlNode *match = NULL;
xmlXPathObjectPtr xpathObj = NULL;
char *xpath = NULL;
int rc = pcmk_rc_ok;
int max = 0;
/* Rules are under the constraints node in the XML, so first find that. */
cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input);
/* Get all rules matching the given ID which are also simple enough for us to check.
* For the moment, these rules must only have a single date_expression child and:
* - Do not have a date_spec operation, or
* - Have a date_spec operation that contains years= but does not contain moon=.
*
* We do this in steps to provide better error messages. First, check that there's
* any rule with the given ID.
*/
xpath = crm_strdup_printf("//rule[@id='%s']", rule_id);
xpathObj = xpath_search(cib_constraints, xpath);
max = numXpathResults(xpathObj);
if (max == 0) {
CMD_ERR("No rule found with ID=%s", rule_id);
rc = ENXIO;
goto bail;
} else if (max > 1) {
CMD_ERR("More than one rule with ID=%s found", rule_id);
rc = ENXIO;
goto bail;
}
free(xpath);
freeXpathObject(xpathObj);
/* Next, make sure it has exactly one date_expression. */
xpath = crm_strdup_printf("//rule[@id='%s']//date_expression", rule_id);
xpathObj = xpath_search(cib_constraints, xpath);
max = numXpathResults(xpathObj);
if (max != 1) {
CMD_ERR("Can't check rule %s because it does not have exactly one date_expression", rule_id);
rc = EOPNOTSUPP;
goto bail;
}
free(xpath);
freeXpathObject(xpathObj);
/* Then, check that it's something we actually support. */
xpath = crm_strdup_printf("//rule[@id='%s']//date_expression[@operation!='date_spec']", rule_id);
xpathObj = xpath_search(cib_constraints, xpath);
max = numXpathResults(xpathObj);
if (max == 0) {
free(xpath);
freeXpathObject(xpathObj);
xpath = crm_strdup_printf("//rule[@id='%s']//date_expression[@operation='date_spec' and date_spec/@years and not(date_spec/@moon)]",
rule_id);
xpathObj = xpath_search(cib_constraints, xpath);
max = numXpathResults(xpathObj);
if (max == 0) {
CMD_ERR("Rule either must not use date_spec, or use date_spec with years= but not moon=");
rc = ENXIO;
goto bail;
}
}
match = getXpathResult(xpathObj, 0);
/* We should have ensured both of these pass with the xpath query above, but
* double checking can't hurt.
*/
CRM_ASSERT(match != NULL);
CRM_ASSERT(find_expression_type(match) == time_expr);
rc = pe_eval_date_expression(match, effective_date, NULL);
if (rc == pcmk_rc_within_range) {
printf("Rule %s is still in effect\n", rule_id);
rc = pcmk_rc_ok;
} else if (rc == pcmk_rc_ok) {
printf("Rule %s satisfies conditions\n", rule_id);
} else if (rc == pcmk_rc_after_range) {
printf("Rule %s is expired\n", rule_id);
} else if (rc == pcmk_rc_before_range) {
printf("Rule %s has not yet taken effect\n", rule_id);
} else if (rc == pcmk_rc_op_unsatisfied) {
printf("Rule %s does not satisfy conditions\n", rule_id);
} else {
printf("Could not determine whether rule %s is expired\n", rule_id);
}
bail:
free(xpath);
freeXpathObject(xpathObj);
return rc;
}
static GOptionContext *
build_arg_context(pcmk__common_args_t *args) {
GOptionContext *context = NULL;
const char *description = "This tool is currently experimental.\n"
"The interface, behavior, and output may change with any version of pacemaker.";
context = pcmk__build_arg_context(args, NULL, NULL);
g_option_context_set_description(context, description);
pcmk__add_arg_group(context, "modes", "Modes (mutually exclusive):",
"Show modes of operation", mode_entries);
pcmk__add_arg_group(context, "data", "Data:",
"Show data options", data_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
return context;
}
int
main(int argc, char **argv)
{
cib_t *cib_conn = NULL;
pe_working_set_t *data_set = NULL;
crm_time_t *rule_date = NULL;
xmlNode *input = NULL;
int rc = pcmk_ok;
crm_exit_t exit_code = CRM_EX_OK;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
GError *error = NULL;
GOptionContext *context = NULL;
gchar **processed_args = NULL;
context = build_arg_context(args);
crm_log_cli_init("crm_rule");
processed_args = pcmk__cmdline_preproc(argv, "nopNO");
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
CMD_ERR("%s: %s\n", g_get_prgname(), error->message);
exit_code = CRM_EX_USAGE;
goto bail;
}
for (int i = 0; i < args->verbosity; i++) {
crm_bump_log_level(argc, argv);
}
if (args->version) {
/* FIXME: When crm_rule is converted to use formatted output, this can go. */
pcmk__cli_help('v', CRM_EX_USAGE);
}
if (optind > argc) {
- CMD_ERR("%s", g_option_context_get_help(context, TRUE, NULL));
+ char *help = g_option_context_get_help(context, TRUE, NULL);
+
+ CMD_ERR("%s", help);
+ free(help);
exit_code = CRM_EX_USAGE;
goto bail;
}
/* Check command line arguments before opening a connection to
* the CIB manager or doing anything else important.
*/
switch(options.mode) {
case crm_rule_mode_check:
if (options.rule == NULL) {
CMD_ERR("--check requires use of --rule=");
exit_code = CRM_EX_USAGE;
goto bail;
}
break;
default:
CMD_ERR("No mode operation given");
exit_code = CRM_EX_USAGE;
goto bail;
break;
}
/* Set up some defaults. */
rule_date = crm_time_new(options.date);
if (rule_date == NULL) {
CMD_ERR("No --date given and can't determine current date");
exit_code = CRM_EX_DATAERR;
goto bail;
}
/* Where does the XML come from? If one of various command line options were
* given, use those. Otherwise, connect to the CIB and use that.
*/
if (safe_str_eq(options.input_xml, "-")) {
input = stdin2xml();
if (input == NULL) {
CMD_ERR("Couldn't parse input from STDIN\n");
exit_code = CRM_EX_DATAERR;
goto bail;
}
} else if (options.input_xml != NULL) {
input = string2xml(options.input_xml);
if (input == NULL) {
CMD_ERR("Couldn't parse input string: %s\n", options.input_xml);
exit_code = CRM_EX_DATAERR;
goto bail;
}
} else {
/* Establish a connection to the CIB manager */
cib_conn = cib_new();
rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
if (rc != pcmk_ok) {
CMD_ERR("Error connecting to the CIB manager: %s", pcmk_strerror(rc));
exit_code = crm_errno2exit(rc);
goto bail;
}
}
/* Populate working set from CIB query */
if (input == NULL) {
rc = cib_conn->cmds->query(cib_conn, NULL, &input, cib_scope_local | cib_sync_call);
if (rc != pcmk_ok) {
exit_code = crm_errno2exit(rc);
goto bail;
}
}
/* Populate the working set instance */
data_set = pe_new_working_set();
if (data_set == NULL) {
exit_code = crm_errno2exit(ENOMEM);
goto bail;
}
set_bit(data_set->flags, pe_flag_no_counts);
set_bit(data_set->flags, pe_flag_no_compat);
data_set->input = input;
data_set->now = rule_date;
/* Unpack everything. */
cluster_status(data_set);
/* Now do whichever operation mode was asked for. There's only one at the
* moment so this looks a little silly, but I expect there will be more
* modes in the future.
*/
switch(options.mode) {
case crm_rule_mode_check:
rc = crm_rule_check(data_set, options.rule, rule_date);
if (rc > 0) {
CMD_ERR("Error checking rule: %s", pcmk_rc_str(rc));
}
exit_code = pcmk_rc2exitc(rc);
break;
default:
break;
}
bail:
if (cib_conn != NULL) {
cib_conn->cmds->signoff(cib_conn);
cib_delete(cib_conn);
}
g_strfreev(processed_args);
g_clear_error(&error);
pcmk__free_arg_context(context);
pe_free_working_set(data_set);
crm_exit(exit_code);
}
diff --git a/tools/stonith_admin.c b/tools/stonith_admin.c
index 7dac4246ba..87b538ac94 100644
--- a/tools/stonith_admin.c
+++ b/tools/stonith_admin.c
@@ -1,613 +1,619 @@
/*
* Copyright 2009-2020 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
#include <crm/cluster/internal.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/output.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#include <crm/cib.h>
#include <crm/pengine/status.h>
#include <crm/common/xml.h>
#include <pacemaker-internal.h>
#define SUMMARY "stonith_admin - Access the Pacemaker fencing API"
char action = 0;
struct {
gboolean as_nodeid;
gboolean broadcast;
gboolean cleanup;
gboolean installed;
gboolean metadata;
gboolean registered;
gboolean validate_cfg;
stonith_key_value_t *devices;
stonith_key_value_t *params;
int fence_level;
int timeout ;
int tolerance;
char *agent;
char *confirm_host;
char *fence_host;
char *history;
char *last_fenced;
char *query;
char *reboot_host;
char *register_dev;
char *register_level;
char *targets;
char *terminate;
char *unfence_host;
char *unregister_dev;
char *unregister_level;
} options = {
.timeout = 120
};
gboolean add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
#define INDENT " "
/* *INDENT-OFF* */
static GOptionEntry defn_entries[] = {
{ "register", 'R', 0, G_OPTION_ARG_STRING, &options.register_dev,
"Register the named stonith device. Requires: --agent.\n"
INDENT "Optional: --option, --env-option.",
"DEVICE" },
{ "deregister", 'D', 0, G_OPTION_ARG_STRING, &options.unregister_dev,
"De-register the named stonith device.",
"DEVICE" },
{ "register-level", 'r', 0, G_OPTION_ARG_STRING, &options.register_level,
"Register a stonith level for the named target,\n"
INDENT "specified as one of NAME, @PATTERN, or ATTR=VALUE.\n"
INDENT "Requires: --index and one or more --device entries.",
"TARGET" },
{ "deregister-level", 'd', 0, G_OPTION_ARG_STRING, &options.unregister_level,
"Unregister a stonith level for the named target,\n"
INDENT "specified as for --register-level. Requires: --index",
"TARGET" },
{ NULL }
};
static GOptionEntry query_entries[] = {
{ "list", 'l', 0, G_OPTION_ARG_STRING, &options.terminate,
"List devices that can terminate the specified host.\n"
INDENT "Optional: --timeout",
"HOST" },
{ "list-registered", 'L', 0, G_OPTION_ARG_NONE, &options.registered,
"List all registered devices. Optional: --timeout.",
NULL },
{ "list-installed", 'I', 0, G_OPTION_ARG_NONE, &options.installed,
"List all installed devices. Optional: --timeout.",
NULL },
{ "list-targets", 's', 0, G_OPTION_ARG_STRING, &options.targets,
"List the targets that can be fenced by the\n"
INDENT "named device. Optional: --timeout.",
"DEVICE" },
{ "metadata", 'M', 0, G_OPTION_ARG_NONE, &options.metadata,
"Show agent metadata. Requires: --agent.\n"
INDENT "Optional: --timeout.",
NULL },
{ "query", 'Q', 0, G_OPTION_ARG_STRING, &options.query,
"Check the named device's status. Optional: --timeout.",
"DEVICE" },
{ "history", 'H', 0, G_OPTION_ARG_STRING, &options.history,
"Show last successful fencing operation for named node\n"
INDENT "(or '*' for all nodes). Optional: --timeout, --cleanup,\n"
INDENT "--quiet (show only the operation's epoch timestamp),\n"
INDENT "--verbose (show all recorded and pending operations),\n"
INDENT "--broadcast (update history from all nodes available).",
"NODE" },
{ "last", 'h', 0, G_OPTION_ARG_STRING, &options.last_fenced,
"Indicate when the named node was last fenced.\n"
INDENT "Optional: --as-node-id.",
"NODE" },
{ "validate", 'K', 0, G_OPTION_ARG_NONE, &options.validate_cfg,
"Validate a fence device configuration.\n"
INDENT "Requires: --agent. Optional: --option, --env-option,\n"
INDENT "--quiet (print no output, only return status).",
NULL },
{ NULL }
};
static GOptionEntry fence_entries[] = {
{ "fence", 'F', 0, G_OPTION_ARG_STRING, &options.fence_host,
"Fence named host. Optional: --timeout, --tolerance.",
"HOST" },
{ "unfence", 'U', 0, G_OPTION_ARG_STRING, &options.unfence_host,
"Unfence named host. Optional: --timeout, --tolerance.",
"HOST" },
{ "reboot", 'B', 0, G_OPTION_ARG_STRING, &options.reboot_host,
"Reboot named host. Optional: --timeout, --tolerance.",
"HOST" },
{ "confirm", 'C', 0, G_OPTION_ARG_STRING, &options.confirm_host,
"Tell cluster that named host is now safely down.",
"HOST", },
{ NULL }
};
static GOptionEntry addl_entries[] = {
{ "cleanup", 'c', 0, G_OPTION_ARG_NONE, &options.cleanup,
"Cleanup wherever appropriate. Requires --history.",
NULL },
{ "broadcast", 'b', 0, G_OPTION_ARG_NONE, &options.broadcast,
"Broadcast wherever appropriate.",
NULL },
{ "agent", 'a', 0, G_OPTION_ARG_STRING, &options.agent,
"The agent to use (for example, fence_xvm;\n"
INDENT "with --register, --metadata, --validate).",
"AGENT" },
{ "option", 'o', 0, G_OPTION_ARG_CALLBACK, add_stonith_params,
"Specify a device configuration parameter as NAME=VALUE\n"
INDENT "(may be specified multiple times; with --register,\n"
INDENT "--validate).",
"PARAM" },
{ "env-option", 'e', 0, G_OPTION_ARG_CALLBACK, add_env_params,
"Specify a device configuration parameter with the\n"
INDENT "specified name, using the value of the\n"
INDENT "environment variable of the same name prefixed with\n"
INDENT "OCF_RESKEY_ (may be specified multiple times;\n"
INDENT "with --register, --validate).",
"PARAM" },
{ "tag", 'T', 0, G_OPTION_ARG_CALLBACK, set_tag,
"Identify fencing operations in logs with the specified\n"
INDENT "tag; useful when multiple entities might invoke\n"
INDENT "stonith_admin (used with most commands).",
"TAG" },
{ "device", 'v', 0, G_OPTION_ARG_CALLBACK, add_stonith_device,
"Device ID (with --register-level, device to associate with\n"
INDENT "a given host and level; may be specified multiple times)"
#if SUPPORT_CIBSECRETS
"\n" INDENT "(with --validate, name to use to load CIB secrets)"
#endif
".",
"DEVICE" },
{ "index", 'i', 0, G_OPTION_ARG_INT, &options.fence_level,
"The stonith level (1-9) (with --register-level,\n"
INDENT "--deregister-level).",
"LEVEL" },
{ "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout,
"Operation timeout in seconds (default 120;\n"
INDENT "used with most commands).",
"SECONDS" },
{ "as-node-id", 'n', 0, G_OPTION_ARG_NONE, &options.as_nodeid,
"(Advanced) The supplied node is the corosync node ID\n"
INDENT "(with --last).",
NULL },
{ "tolerance", 0, 0, G_OPTION_ARG_CALLBACK, add_tolerance,
"(Advanced) Do nothing if an equivalent --fence request\n"
INDENT "succeeded less than this many seconds earlier\n"
INDENT "(with --fence, --unfence, --reboot).",
"SECONDS" },
{ NULL }
};
/* *INDENT-ON* */
static pcmk__supported_format_t formats[] = {
PCMK__SUPPORTED_FORMAT_HTML,
PCMK__SUPPORTED_FORMAT_TEXT,
PCMK__SUPPORTED_FORMAT_XML,
{ NULL, NULL, NULL }
};
static int st_opts = st_opt_sync_call | st_opt_allow_suicide;
static char *name = NULL;
gboolean
add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
char *key = crm_strdup_printf("OCF_RESKEY_%s", optarg);
const char *env = getenv(key);
gboolean retval = TRUE;
if (env == NULL) {
crm_err("Invalid option: -e %s", optarg);
g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Invalid option: -e %s", optarg);
retval = FALSE;
} else {
crm_info("Got: '%s'='%s'", optarg, env);
options.params = stonith_key_value_add(options.params, optarg, env);
}
free(key);
return retval;
}
gboolean
add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.devices = stonith_key_value_add(options.devices, NULL, optarg);
return TRUE;
}
gboolean
add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.tolerance = crm_get_msec(optarg) / 1000;
return TRUE;
}
gboolean
add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
char *name = NULL;
char *value = NULL;
int rc = 0;
gboolean retval = TRUE;
crm_info("Scanning: -o %s", optarg);
rc = pcmk_scan_nvpair(optarg, &name, &value);
if (rc != 2) {
crm_err("Invalid option: -o %s: %s", optarg, pcmk_strerror(rc));
g_set_error(error, G_OPTION_ERROR, rc, "Invalid option: -o %s: %s", optarg, pcmk_strerror(rc));
retval = FALSE;
} else {
crm_info("Got: '%s'='%s'", name, value);
options.params = stonith_key_value_add(options.params, name, value);
}
free(name);
free(value);
return retval;
}
gboolean
set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
free(name);
name = crm_strdup_printf("%s.%s", crm_system_name, optarg);
return TRUE;
}
static GOptionContext *
build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
GOptionContext *context = NULL;
GOptionEntry extra_prog_entries[] = {
{ "quiet", 'q', 0, G_OPTION_ARG_NONE, &(args->quiet),
"Be less descriptive in output.",
NULL },
{ NULL }
};
context = pcmk__build_arg_context(args, "text (default), html, xml", group);
/* Add the -q option, which cannot be part of the globally supported options
* because some tools use that flag for something else.
*/
pcmk__add_main_args(context, extra_prog_entries);
pcmk__add_arg_group(context, "definition", "Device Definition Commands:",
"Show device definition help", defn_entries);
pcmk__add_arg_group(context, "queries", "Queries:",
"Show query help", query_entries);
pcmk__add_arg_group(context, "fence", "Fencing Commands:",
"Show fence help", fence_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
return context;
}
int
main(int argc, char **argv)
{
int rc = 0;
bool no_connect = false;
bool required_agent = false;
char *target = NULL;
const char *device = NULL;
crm_exit_t exit_code = CRM_EX_OK;
stonith_t *st = NULL;
pcmk__output_t *out = NULL;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
GError *error = NULL;
GOptionContext *context = NULL;
GOptionGroup *output_group = NULL;
gchar **processed_args = NULL;
context = build_arg_context(args, &output_group);
pcmk__register_formats(output_group, formats);
crm_log_cli_init("stonith_admin");
name = strdup(crm_system_name);
processed_args = pcmk__cmdline_preproc(argv, "adehilorstvBCDFHQRTU");
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
exit_code = CRM_EX_USAGE;
goto done;
}
for (int i = 0; i < args->verbosity; i++) {
crm_bump_log_level(argc, argv);
}
rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
if (rc != pcmk_rc_ok) {
fprintf(stderr, "Error creating output format %s: %s\n",
args->output_ty, pcmk_rc_str(rc));
exit_code = CRM_EX_ERROR;
goto done;
}
stonith__register_messages(out);
if (args->version) {
out->version(out, false);
goto done;
}
if (options.validate_cfg) {
required_agent = true;
no_connect = true;
action = 'K';
}
if (options.installed) {
no_connect = true;
action = 'I';
}
if (options.registered) {
action = 'L';
}
if (options.register_dev != NULL) {
required_agent = true;
action = 'R';
device = options.register_dev;
}
if (options.query != NULL) {
action = 'Q';
device = options.query;
}
if (options.unregister_dev != NULL) {
action = 'D';
device = options.unregister_dev;
}
if (options.targets != NULL) {
action = 's';
device = options.targets;
}
if (options.terminate != NULL) {
action = 'L';
target = options.terminate;
}
if (options.metadata) {
no_connect = true;
required_agent = true;
action = 'M';
}
if (options.reboot_host != NULL) {
no_connect = true;
action = 'B';
target = options.reboot_host;
crm_log_args(argc, argv);
}
if (options.fence_host != NULL) {
no_connect = true;
action = 'F';
target = options.fence_host;
crm_log_args(argc, argv);
}
if (options.unfence_host != NULL) {
no_connect = true;
action = 'U';
target = options.unfence_host;
crm_log_args(argc, argv);
}
if (options.confirm_host != NULL) {
action = 'C';
target = options.confirm_host;
crm_log_args(argc, argv);
}
if (options.last_fenced != NULL) {
action = 'h';
target = options.last_fenced;
}
if (options.history != NULL) {
action = 'H';
target = options.history;
}
if (options.register_level != NULL) {
action = 'r';
target = options.register_level;
}
if (options.unregister_level != NULL) {
action = 'd';
target = options.unregister_level;
}
if (optind > argc || action == 0) {
- out->err(out, "%s", g_option_context_get_help(context, TRUE, NULL));
+ char *help = g_option_context_get_help(context, TRUE, NULL);
+
+ out->err(out, "%s", help);
+ free(help);
exit_code = CRM_EX_USAGE;
goto done;
}
if (required_agent && options.agent == NULL) {
+ char *help = g_option_context_get_help(context, TRUE, NULL);
+
out->err(out, "Please specify an agent to query using -a,--agent [value]");
- out->err(out, "%s", g_option_context_get_help(context, TRUE, NULL));
+ out->err(out, "%s", help);
+ free(help);
exit_code = CRM_EX_USAGE;
goto done;
}
st = stonith_api_new();
if (st == NULL) {
rc = -ENOMEM;
} else if (!no_connect) {
rc = st->cmds->connect(st, name, NULL);
}
if (rc < 0) {
out->err(out, "Could not connect to fencer: %s", pcmk_strerror(rc));
exit_code = CRM_EX_DISCONNECT;
goto done;
}
switch (action) {
case 'I':
rc = pcmk__fence_installed(out, st, options.timeout*1000);
if (rc != pcmk_rc_ok) {
out->err(out, "Failed to list installed devices: %s", pcmk_strerror(rc));
}
break;
case 'L':
rc = pcmk__fence_registered(out, st, target, options.timeout*1000);
if (rc != pcmk_rc_ok) {
out->err(out, "Failed to list registered devices: %s", pcmk_strerror(rc));
}
break;
case 'Q':
rc = st->cmds->monitor(st, st_opts, device, options.timeout);
if (rc != pcmk_rc_ok) {
rc = st->cmds->list(st, st_opts, device, NULL, options.timeout);
}
break;
case 's':
rc = pcmk__fence_list_targets(out, st, target, options.timeout*1000);
if (rc != pcmk_rc_ok) {
out->err(out, "Couldn't list targets: %s", pcmk_strerror(rc));
}
break;
case 'R':
rc = st->cmds->register_device(st, st_opts, device, NULL, options.agent,
options.params);
break;
case 'D':
rc = st->cmds->remove_device(st, st_opts, device);
break;
case 'd':
rc = pcmk__fence_unregister_level(st, target, options.fence_level);
break;
case 'r':
rc = pcmk__fence_register_level(st, target, options.fence_level, options.devices);
break;
case 'M':
rc = pcmk__fence_metadata(out, st, options.agent, options.timeout*1000);
if (rc != pcmk_rc_ok) {
out->err(out, "Can't get fence agent meta-data: %s", pcmk_strerror(rc));
}
break;
case 'C':
rc = st->cmds->confirm(st, st_opts, target);
break;
case 'B':
rc = pcmk__fence_action(st, target, "reboot", name, options.timeout*1000,
options.tolerance*1000);
break;
case 'F':
rc = pcmk__fence_action(st, target, "off", name, options.timeout*1000,
options.tolerance*1000);
break;
case 'U':
rc = pcmk__fence_action(st, target, "on", name, options.timeout*1000,
options.tolerance*1000);
break;
case 'h':
rc = pcmk__fence_last(out, target, options.as_nodeid);
break;
case 'H':
rc = pcmk__fence_history(out, st, target, options.timeout*1000, args->quiet,
args->verbosity, options.broadcast, options.cleanup);
break;
case 'K':
device = options.devices ? options.devices->key : NULL;
rc = pcmk__fence_validate(out, st, options.agent, device, options.params,
options.timeout*1000);
break;
}
crm_info("Command returned: %s (%d)", pcmk_strerror(rc), rc);
exit_code = crm_errno2exit(rc);
done:
g_strfreev(processed_args);
g_clear_error(&error);
pcmk__free_arg_context(context);
if (out != NULL) {
out->finish(out, exit_code, true, NULL);
pcmk__output_free(out);
}
free(name);
stonith_key_value_freeall(options.params, 1, 1);
if (st != NULL) {
st->cmds->disconnect(st);
stonith_api_delete(st);
}
return exit_code;
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 21, 8:15 PM (6 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1665709
Default Alt Text
(133 KB)

Event Timeline