root/tools/crm_rule.c

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

DEFINITIONS

This source file includes following definitions.
  1. mode_cb
  2. eval_date_expression
  3. PCMK__OUTPUT_ARGS
  4. PCMK__OUTPUT_ARGS
  5. crm_rule_check
  6. build_arg_context
  7. main

   1 /*
   2  * Copyright 2019-2022 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/cib/internal.h>
  14 #include <crm/common/cmdline_internal.h>
  15 #include <crm/common/output_internal.h>
  16 #include <crm/common/iso8601.h>
  17 #include <crm/msg_xml.h>
  18 #include <crm/pengine/rules_internal.h>
  19 #include <crm/pengine/status.h>
  20 #include <pacemaker-internal.h>
  21 
  22 #include <sys/stat.h>
  23 
  24 #define SUMMARY "evaluate rules from the Pacemaker configuration"
  25 
  26 GError *error = NULL;
  27 
  28 static pcmk__supported_format_t formats[] = {
  29     PCMK__SUPPORTED_FORMAT_NONE,
  30     PCMK__SUPPORTED_FORMAT_TEXT,
  31     PCMK__SUPPORTED_FORMAT_XML,
  32     { NULL, NULL, NULL }
  33 };
  34 
  35 enum crm_rule_mode {
  36     crm_rule_mode_none,
  37     crm_rule_mode_check
  38 };
  39 
  40 struct {
  41     char *date;
  42     char *input_xml;
  43     enum crm_rule_mode mode;
  44     gchar **rules;
  45 } options = {
  46     .mode = crm_rule_mode_none
  47 };
  48 
  49 static int crm_rule_check(pcmk__output_t *out, pe_working_set_t *data_set,
  50                           const char *rule_id, crm_time_t *effective_date);
  51 
  52 static gboolean mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
  53 
  54 static GOptionEntry mode_entries[] = {
  55     { "check", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, mode_cb,
  56       "Check whether a rule is in effect",
  57       NULL },
  58 
  59     { NULL }
  60 };
  61 
  62 static GOptionEntry data_entries[] = {
  63     { "xml-text", 'X', 0, G_OPTION_ARG_STRING, &options.input_xml,
  64       "Use argument for XML (or stdin if '-')",
  65       NULL },
  66 
  67     { NULL }
  68 };
  69 
  70 static GOptionEntry addl_entries[] = {
  71     { "date", 'd', 0, G_OPTION_ARG_STRING, &options.date,
  72       "Whether the rule is in effect on a given date",
  73       NULL },
  74     { "rule", 'r', 0, G_OPTION_ARG_STRING_ARRAY, &options.rules,
  75       "The ID of the rule to check (may be specified multiple times)",
  76       NULL },
  77 
  78     { NULL }
  79 };
  80 
  81 static gboolean
  82 mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
  83     if (strcmp(option_name, "c")) {
  84         options.mode = crm_rule_mode_check;
  85     }
  86 
  87     return TRUE;
  88 }
  89 
  90 /*!
  91  * \internal
  92  * \brief Evaluate a date expression for a specific time
  93  *
  94  * \param[in]  time_expr    date_expression XML
  95  * \param[in]  now          Time for which to evaluate expression
  96  * \param[out] next_change  If not NULL, set to when evaluation will change
  97  *
  98  * \return Standard Pacemaker return code
  99  */
 100 static int
 101 eval_date_expression(xmlNode *expr, crm_time_t *now, crm_time_t *next_change)
     /* [previous][next][first][last][top][bottom][index][help] */
 102 {
 103     pe_rule_eval_data_t rule_data = {
 104         .node_hash = NULL,
 105         .role = RSC_ROLE_UNKNOWN,
 106         .now = now,
 107         .match_data = NULL,
 108         .rsc_data = NULL,
 109         .op_data = NULL
 110     };
 111 
 112     return pe__eval_date_expr(expr, &rule_data, next_change);
 113 }
 114 
 115 PCMK__OUTPUT_ARGS("rule-check", "const char *", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
 116 static int
 117 rule_check_default(pcmk__output_t *out, va_list args)
 118 {
 119     const char *rule_id = va_arg(args, const char *);
 120     int result = va_arg(args, int);
 121 
 122     if (result == pcmk_rc_within_range) {
 123         out->info(out, "Rule %s is still in effect", rule_id);
 124     } else if (result == pcmk_rc_ok) {
 125         out->info(out, "Rule %s satisfies conditions", rule_id);
 126     } else if (result == pcmk_rc_after_range) {
 127         out->info(out, "Rule %s is expired", rule_id);
 128     } else if (result == pcmk_rc_before_range) {
 129         out->info(out, "Rule %s has not yet taken effect", rule_id);
 130     } else if (result == pcmk_rc_op_unsatisfied) {
 131         out->info(out, "Rule %s does not satisfy conditions", rule_id);
 132     } else {
 133         out->info(out, "Could not determine whether rule %s is expired", rule_id);
 134     }
 135 
 136     return pcmk_rc_ok;
 137 }
 138 
 139 PCMK__OUTPUT_ARGS("rule-check", "const char *", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
 140 static int
 141 rule_check_xml(pcmk__output_t *out, va_list args)
 142 {
 143     const char *rule_id = va_arg(args, const char *);
 144     int result = va_arg(args, int);
 145 
 146     char *rc_str = pcmk__itoa(pcmk_rc2exitc(result));
 147 
 148     pcmk__output_create_xml_node(out, "rule-check",
 149                                  "rule-id", rule_id,
 150                                  "rc", rc_str,
 151                                  NULL);
 152 
 153     free(rc_str);
 154 
 155     return pcmk_rc_ok;
 156 }
 157 
 158 static pcmk__message_entry_t fmt_functions[] = {
 159     { "rule-check", "default", rule_check_default },
 160     { "rule-check", "xml", rule_check_xml },
 161 
 162     { NULL, NULL, NULL }
 163 };
 164 
 165 static int
 166 crm_rule_check(pcmk__output_t *out, pe_working_set_t *data_set, const char *rule_id,
     /* [previous][next][first][last][top][bottom][index][help] */
 167                crm_time_t *effective_date)
 168 {
 169     xmlNode *cib_constraints = NULL;
 170     xmlNode *match = NULL;
 171     xmlXPathObjectPtr xpathObj = NULL;
 172     char *xpath = NULL;
 173     int rc = pcmk_rc_ok;
 174     int max = 0;
 175 
 176     /* Rules are under the constraints node in the XML, so first find that. */
 177     cib_constraints = pcmk_find_cib_element(data_set->input,
 178                                             XML_CIB_TAG_CONSTRAINTS);
 179 
 180     /* Get all rules matching the given ID which are also simple enough for us to check.
 181      * For the moment, these rules must only have a single date_expression child and:
 182      * - Do not have a date_spec operation, or
 183      * - Have a date_spec operation that contains years= but does not contain moon=.
 184      *
 185      * We do this in steps to provide better error messages.  First, check that there's
 186      * any rule with the given ID.
 187      */
 188     xpath = crm_strdup_printf("//rule[@id='%s']", rule_id);
 189     xpathObj = xpath_search(cib_constraints, xpath);
 190     max = numXpathResults(xpathObj);
 191 
 192     if (max == 0) {
 193         rc = ENXIO;
 194         g_set_error(&error, PCMK__RC_ERROR, rc, "No rule found with ID=%s", rule_id);
 195         goto done;
 196     } else if (max > 1) {
 197         rc = ENXIO;
 198         g_set_error(&error, PCMK__RC_ERROR, rc, "More than one rule with ID=%s found", rule_id);
 199         goto done;
 200     }
 201 
 202     free(xpath);
 203     freeXpathObject(xpathObj);
 204 
 205     /* Next, make sure it has exactly one date_expression. */
 206     xpath = crm_strdup_printf("//rule[@id='%s']//date_expression", rule_id);
 207     xpathObj = xpath_search(cib_constraints, xpath);
 208     max = numXpathResults(xpathObj);
 209 
 210     if (max != 1) {
 211         rc = EOPNOTSUPP;
 212         g_set_error(&error, PCMK__RC_ERROR, rc,
 213                     "Can't check rule %s because it does not have exactly one date_expression", rule_id);
 214         goto done;
 215     }
 216 
 217     free(xpath);
 218     freeXpathObject(xpathObj);
 219 
 220     /* Then, check that it's something we actually support. */
 221     xpath = crm_strdup_printf("//rule[@id='%s']//date_expression[@operation!='date_spec']", rule_id);
 222     xpathObj = xpath_search(cib_constraints, xpath);
 223     max = numXpathResults(xpathObj);
 224 
 225     if (max == 0) {
 226         free(xpath);
 227         freeXpathObject(xpathObj);
 228 
 229         xpath = crm_strdup_printf("//rule[@id='%s']//date_expression[@operation='date_spec' and date_spec/@years and not(date_spec/@moon)]",
 230                                   rule_id);
 231         xpathObj = xpath_search(cib_constraints, xpath);
 232         max = numXpathResults(xpathObj);
 233 
 234         if (max == 0) {
 235             rc = ENXIO;
 236             g_set_error(&error, PCMK__RC_ERROR, rc,
 237                         "Rule either must not use date_spec, or use date_spec with years= but not moon=");
 238             goto done;
 239         }
 240     }
 241 
 242     match = getXpathResult(xpathObj, 0);
 243 
 244     /* We should have ensured both of these pass with the xpath query above, but
 245      * double checking can't hurt.
 246      */
 247     CRM_ASSERT(match != NULL);
 248     CRM_ASSERT(find_expression_type(match) == time_expr);
 249 
 250     rc = eval_date_expression(match, effective_date, NULL);
 251     out->message(out, "rule-check", rule_id, rc);
 252 
 253 done:
 254     free(xpath);
 255     freeXpathObject(xpathObj);
 256     return rc;
 257 }
 258 
 259 static GOptionContext *
 260 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     /* [previous][next][first][last][top][bottom][index][help] */
 261     GOptionContext *context = NULL;
 262 
 263     context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
 264 
 265     pcmk__add_arg_group(context, "modes", "Modes (mutually exclusive):",
 266                         "Show modes of operation", mode_entries);
 267     pcmk__add_arg_group(context, "data", "Data:",
 268                         "Show data options", data_entries);
 269     pcmk__add_arg_group(context, "additional", "Additional Options:",
 270                         "Show additional options", addl_entries);
 271     return context;
 272 }
 273 
 274 int
 275 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 276 {
 277     pe_working_set_t *data_set = NULL;
 278 
 279     crm_time_t *rule_date = NULL;
 280     xmlNode *input = NULL;
 281 
 282     int rc = pcmk_rc_ok;
 283     crm_exit_t exit_code = CRM_EX_OK;
 284 
 285     pcmk__output_t *out = NULL;
 286 
 287     GOptionGroup *output_group = NULL;
 288     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 289     GOptionContext *context = build_arg_context(args, &output_group);
 290     gchar **processed_args = pcmk__cmdline_preproc(argv, "drX");
 291 
 292     pcmk__register_formats(output_group, formats);
 293     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 294         exit_code = CRM_EX_USAGE;
 295         goto done;
 296     }
 297 
 298     pcmk__cli_init_logging("crm_rule", args->verbosity);
 299 
 300     rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
 301     if (rc != pcmk_rc_ok) {
 302         exit_code = CRM_EX_ERROR;
 303         g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s",
 304                     args->output_ty, pcmk_rc_str(rc));
 305         goto done;
 306     }
 307 
 308     pcmk__register_messages(out, fmt_functions);
 309 
 310     if (args->version) {
 311         out->version(out, false);
 312         goto done;
 313     }
 314 
 315     /* Check command line arguments before opening a connection to
 316      * the CIB manager or doing anything else important.
 317      */
 318     switch(options.mode) {
 319         case crm_rule_mode_check:
 320             if (options.rules == NULL) {
 321                 exit_code = CRM_EX_USAGE;
 322                 g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "--check requires use of --rule=");
 323                 goto done;
 324             }
 325 
 326             break;
 327 
 328         default:
 329             exit_code = CRM_EX_USAGE;
 330             g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "No mode operation given");
 331             goto done;
 332             break;
 333     }
 334 
 335     /* Set up some defaults. */
 336     rule_date = crm_time_new(options.date);
 337     if (rule_date == NULL) {
 338         exit_code = CRM_EX_DATAERR;
 339         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 340                     "No --date given and can't determine current date");
 341         goto done;
 342     }
 343 
 344     /* Where does the XML come from?  If one of various command line options were
 345      * given, use those.  Otherwise, connect to the CIB and use that.
 346      */
 347     if (pcmk__str_eq(options.input_xml, "-", pcmk__str_casei)) {
 348         input = stdin2xml();
 349 
 350         if (input == NULL) {
 351             exit_code = CRM_EX_DATAERR;
 352             g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Couldn't parse input from STDIN\n");
 353             goto done;
 354         }
 355     } else if (options.input_xml != NULL) {
 356         input = string2xml(options.input_xml);
 357 
 358         if (input == NULL) {
 359             exit_code = CRM_EX_DATAERR;
 360             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 361                         "Couldn't parse input string: %s\n", options.input_xml);
 362             goto done;
 363         }
 364     } else {
 365         rc = cib__signon_query(NULL, &input);
 366 
 367         if (rc != pcmk_rc_ok) {
 368             exit_code = pcmk_rc2exitc(rc);
 369             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 370                         "CIB query failed: %s", pcmk_rc_str(rc));
 371             goto done;
 372         }
 373     }
 374 
 375     /* Populate the working set instance */
 376     data_set = pe_new_working_set();
 377     if (data_set == NULL) {
 378         exit_code = crm_errno2exit(ENOMEM);
 379         goto done;
 380     }
 381     pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat);
 382 
 383     data_set->input = input;
 384     data_set->now = rule_date;
 385 
 386     /* Unpack everything. */
 387     cluster_status(data_set);
 388 
 389     /* Now do whichever operation mode was asked for.  There's only one at the
 390      * moment so this looks a little silly, but I expect there will be more
 391      * modes in the future.
 392      */
 393     switch(options.mode) {
 394         case crm_rule_mode_check:
 395             for (char **s = options.rules; *s != NULL; s++) {
 396                 int last_rc = crm_rule_check(out, data_set, *s, rule_date);
 397 
 398                 if (last_rc != pcmk_rc_ok) {
 399                     rc = last_rc;
 400                 }
 401             }
 402 
 403             exit_code = pcmk_rc2exitc(rc);
 404             break;
 405 
 406         default:
 407             break;
 408     }
 409 
 410 done:
 411     g_strfreev(processed_args);
 412     pcmk__free_arg_context(context);
 413     pe_free_working_set(data_set);
 414 
 415     pcmk__output_and_clear_error(error, out);
 416 
 417     if (out != NULL) {
 418         out->finish(out, exit_code, true, NULL);
 419         pcmk__output_free(out);
 420     }
 421 
 422     return crm_exit(exit_code);
 423 }

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