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. crm_rule_check
  4. build_arg_context
  5. main

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

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