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

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