root/tools/crm_rule.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. mode_cb
  2. crm_rule_check
  3. build_arg_context
  4. main

   1 /*
   2  * Copyright 2019-2020 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU General Public License version 2
   7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <crm/cib.h>
  13 #include <crm/common/cmdline_internal.h>
  14 #include <crm/common/iso8601.h>
  15 #include <crm/msg_xml.h>
  16 #include <crm/pengine/rules_internal.h>
  17 #include <crm/pengine/status.h>
  18 #include <pacemaker-internal.h>
  19 
  20 #include <sys/stat.h>
  21 
  22 #define SUMMARY "evaluate rules from the Pacemaker configuration"
  23 
  24 enum crm_rule_mode {
  25     crm_rule_mode_none,
  26     crm_rule_mode_check
  27 };
  28 
  29 struct {
  30     char *date;
  31     char *input_xml;
  32     enum crm_rule_mode mode;
  33     char *rule;
  34 } options = {
  35     .mode = crm_rule_mode_none
  36 };
  37 
  38 static int crm_rule_check(pe_working_set_t *data_set, const char *rule_id, crm_time_t *effective_date);
  39 
  40 static gboolean mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
  41 
  42 static GOptionEntry mode_entries[] = {
  43     { "check", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, mode_cb,
  44       "Check whether a rule is in effect",
  45       NULL },
  46 
  47     { NULL }
  48 };
  49 
  50 static GOptionEntry data_entries[] = {
  51     { "xml-text", 'X', 0, G_OPTION_ARG_STRING, &options.input_xml,
  52       "Use argument for XML (or stdin if '-')",
  53       NULL },
  54 
  55     { NULL }
  56 };
  57 
  58 static GOptionEntry addl_entries[] = {
  59     { "date", 'd', 0, G_OPTION_ARG_STRING, &options.date,
  60       "Whether the rule is in effect on a given date",
  61       NULL },
  62     { "rule", 'r', 0, G_OPTION_ARG_STRING, &options.rule,
  63       "The ID of the rule to check",
  64       NULL },
  65 
  66     { NULL }
  67 };
  68 
  69 static gboolean
  70 mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
  71     if (strcmp(option_name, "c")) {
  72         options.mode = crm_rule_mode_check;
  73     }
  74 
  75     return TRUE;
  76 }
  77 
  78 static int
  79 crm_rule_check(pe_working_set_t *data_set, const char *rule_id, crm_time_t *effective_date)
     /* [previous][next][first][last][top][bottom][index][help] */
  80 {
  81     xmlNode *cib_constraints = NULL;
  82     xmlNode *match = NULL;
  83     xmlXPathObjectPtr xpathObj = NULL;
  84     char *xpath = NULL;
  85     int rc = pcmk_rc_ok;
  86     int max = 0;
  87 
  88     /* Rules are under the constraints node in the XML, so first find that. */
  89     cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input);
  90 
  91     /* Get all rules matching the given ID which are also simple enough for us to check.
  92      * For the moment, these rules must only have a single date_expression child and:
  93      * - Do not have a date_spec operation, or
  94      * - Have a date_spec operation that contains years= but does not contain moon=.
  95      *
  96      * We do this in steps to provide better error messages.  First, check that there's
  97      * any rule with the given ID.
  98      */
  99     xpath = crm_strdup_printf("//rule[@id='%s']", rule_id);
 100     xpathObj = xpath_search(cib_constraints, xpath);
 101     max = numXpathResults(xpathObj);
 102 
 103     if (max == 0) {
 104         CMD_ERR("No rule found with ID=%s", rule_id);
 105         rc = ENXIO;
 106         goto bail;
 107     } else if (max > 1) {
 108         CMD_ERR("More than one rule with ID=%s found", rule_id);
 109         rc = ENXIO;
 110         goto bail;
 111     }
 112 
 113     free(xpath);
 114     freeXpathObject(xpathObj);
 115 
 116     /* Next, make sure it has exactly one date_expression. */
 117     xpath = crm_strdup_printf("//rule[@id='%s']//date_expression", rule_id);
 118     xpathObj = xpath_search(cib_constraints, xpath);
 119     max = numXpathResults(xpathObj);
 120 
 121     if (max != 1) {
 122         CMD_ERR("Can't check rule %s because it does not have exactly one date_expression", rule_id);
 123         rc = EOPNOTSUPP;
 124         goto bail;
 125     }
 126 
 127     free(xpath);
 128     freeXpathObject(xpathObj);
 129 
 130     /* Then, check that it's something we actually support. */
 131     xpath = crm_strdup_printf("//rule[@id='%s']//date_expression[@operation!='date_spec']", rule_id);
 132     xpathObj = xpath_search(cib_constraints, xpath);
 133     max = numXpathResults(xpathObj);
 134 
 135     if (max == 0) {
 136         free(xpath);
 137         freeXpathObject(xpathObj);
 138 
 139         xpath = crm_strdup_printf("//rule[@id='%s']//date_expression[@operation='date_spec' and date_spec/@years and not(date_spec/@moon)]",
 140                                   rule_id);
 141         xpathObj = xpath_search(cib_constraints, xpath);
 142         max = numXpathResults(xpathObj);
 143 
 144         if (max == 0) {
 145             CMD_ERR("Rule either must not use date_spec, or use date_spec with years= but not moon=");
 146             rc = ENXIO;
 147             goto bail;
 148         }
 149     }
 150 
 151     match = getXpathResult(xpathObj, 0);
 152 
 153     /* We should have ensured both of these pass with the xpath query above, but
 154      * double checking can't hurt.
 155      */
 156     CRM_ASSERT(match != NULL);
 157     CRM_ASSERT(find_expression_type(match) == time_expr);
 158 
 159     rc = pe_eval_date_expression(match, effective_date, NULL);
 160 
 161     if (rc == pcmk_rc_within_range) {
 162         printf("Rule %s is still in effect\n", rule_id);
 163         rc = pcmk_rc_ok;
 164     } else if (rc == pcmk_rc_ok) {
 165         printf("Rule %s satisfies conditions\n", rule_id);
 166     } else if (rc == pcmk_rc_after_range) {
 167         printf("Rule %s is expired\n", rule_id);
 168     } else if (rc == pcmk_rc_before_range) {
 169         printf("Rule %s has not yet taken effect\n", rule_id);
 170     } else if (rc == pcmk_rc_op_unsatisfied) {
 171         printf("Rule %s does not satisfy conditions\n", rule_id);
 172     } else {
 173         printf("Could not determine whether rule %s is expired\n", rule_id);
 174     }
 175 
 176 bail:
 177     free(xpath);
 178     freeXpathObject(xpathObj);
 179     return rc;
 180 }
 181 
 182 static GOptionContext *
 183 build_arg_context(pcmk__common_args_t *args) {
     /* [previous][next][first][last][top][bottom][index][help] */
 184     GOptionContext *context = NULL;
 185 
 186     const char *description = "This tool is currently experimental.\n"
 187                               "The interface, behavior, and output may change with any version of pacemaker.";
 188 
 189     context = pcmk__build_arg_context(args, NULL, NULL, NULL);
 190     g_option_context_set_description(context, description);
 191 
 192     pcmk__add_arg_group(context, "modes", "Modes (mutually exclusive):",
 193                         "Show modes of operation", mode_entries);
 194     pcmk__add_arg_group(context, "data", "Data:",
 195                         "Show data options", data_entries);
 196     pcmk__add_arg_group(context, "additional", "Additional Options:",
 197                         "Show additional options", addl_entries);
 198     return context;
 199 }
 200 
 201 int
 202 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 203 {
 204     cib_t *cib_conn = NULL;
 205     pe_working_set_t *data_set = NULL;
 206 
 207     crm_time_t *rule_date = NULL;
 208     xmlNode *input = NULL;
 209 
 210     int rc = pcmk_ok;
 211     crm_exit_t exit_code = CRM_EX_OK;
 212 
 213     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 214 
 215     GError *error = NULL;
 216     GOptionContext *context = NULL;
 217     gchar **processed_args = NULL;
 218 
 219     context = build_arg_context(args);
 220 
 221     crm_log_cli_init("crm_rule");
 222 
 223     processed_args = pcmk__cmdline_preproc(argv, "nopNO");
 224 
 225     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 226         CMD_ERR("%s: %s\n", g_get_prgname(), error->message);
 227         exit_code = CRM_EX_USAGE;
 228         goto bail;
 229     }
 230 
 231     for (int i = 0; i < args->verbosity; i++) {
 232         crm_bump_log_level(argc, argv);
 233     }
 234 
 235     if (args->version) {
 236         /* FIXME:  When crm_rule is converted to use formatted output, this can go. */
 237         pcmk__cli_help('v', CRM_EX_USAGE);
 238     }
 239 
 240     if (optind > argc) {
 241         char *help = g_option_context_get_help(context, TRUE, NULL);
 242 
 243         CMD_ERR("%s", help);
 244         g_free(help);
 245         exit_code = CRM_EX_USAGE;
 246         goto bail;
 247     }
 248 
 249     /* Check command line arguments before opening a connection to
 250      * the CIB manager or doing anything else important.
 251      */
 252     switch(options.mode) {
 253         case crm_rule_mode_check:
 254             if (options.rule == NULL) {
 255                 CMD_ERR("--check requires use of --rule=");
 256                 exit_code = CRM_EX_USAGE;
 257                 goto bail;
 258             }
 259 
 260             break;
 261 
 262         default:
 263             CMD_ERR("No mode operation given");
 264             exit_code = CRM_EX_USAGE;
 265             goto bail;
 266             break;
 267     }
 268 
 269     /* Set up some defaults. */
 270     rule_date = crm_time_new(options.date);
 271     if (rule_date == NULL) {
 272         CMD_ERR("No --date given and can't determine current date");
 273         exit_code = CRM_EX_DATAERR;
 274         goto bail;
 275     }
 276 
 277     /* Where does the XML come from?  If one of various command line options were
 278      * given, use those.  Otherwise, connect to the CIB and use that.
 279      */
 280     if (pcmk__str_eq(options.input_xml, "-", pcmk__str_casei)) {
 281         input = stdin2xml();
 282 
 283         if (input == NULL) {
 284             CMD_ERR("Couldn't parse input from STDIN\n");
 285             exit_code = CRM_EX_DATAERR;
 286             goto bail;
 287         }
 288     } else if (options.input_xml != NULL) {
 289         input = string2xml(options.input_xml);
 290 
 291         if (input == NULL) {
 292             CMD_ERR("Couldn't parse input string: %s\n", options.input_xml);
 293 
 294             exit_code = CRM_EX_DATAERR;
 295             goto bail;
 296         }
 297     } else {
 298         // Establish a connection to the CIB
 299         cib_conn = cib_new();
 300         rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
 301         if (rc != pcmk_ok) {
 302             CMD_ERR("Could not connect to CIB: %s", pcmk_strerror(rc));
 303             exit_code = crm_errno2exit(rc);
 304             goto bail;
 305         }
 306     }
 307 
 308     /* Populate working set from CIB query */
 309     if (input == NULL) {
 310         rc = cib_conn->cmds->query(cib_conn, NULL, &input, cib_scope_local | cib_sync_call);
 311         if (rc != pcmk_ok) {
 312             exit_code = crm_errno2exit(rc);
 313             goto bail;
 314         }
 315     }
 316 
 317     /* Populate the working set instance */
 318     data_set = pe_new_working_set();
 319     if (data_set == NULL) {
 320         exit_code = crm_errno2exit(ENOMEM);
 321         goto bail;
 322     }
 323     pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat);
 324 
 325     data_set->input = input;
 326     data_set->now = rule_date;
 327 
 328     /* Unpack everything. */
 329     cluster_status(data_set);
 330 
 331     /* Now do whichever operation mode was asked for.  There's only one at the
 332      * moment so this looks a little silly, but I expect there will be more
 333      * modes in the future.
 334      */
 335     switch(options.mode) {
 336         case crm_rule_mode_check:
 337             rc = crm_rule_check(data_set, options.rule, rule_date);
 338 
 339             if (rc > 0) {
 340                 CMD_ERR("Error checking rule: %s", pcmk_rc_str(rc));
 341             }
 342 
 343             exit_code = pcmk_rc2exitc(rc);
 344             break;
 345 
 346         default:
 347             break;
 348     }
 349 
 350 bail:
 351     if (cib_conn != NULL) {
 352         cib_conn->cmds->signoff(cib_conn);
 353         cib_delete(cib_conn);
 354     }
 355 
 356     g_strfreev(processed_args);
 357     g_clear_error(&error);
 358     pcmk__free_arg_context(context);
 359     pe_free_working_set(data_set);
 360     crm_exit(exit_code);
 361 }

/* [previous][next][first][last][top][bottom][index][help] */