root/lib/common/actions.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__op_key
  2. convert_interval
  3. match_before
  4. parse_op_key
  5. pcmk__notify_key
  6. decode_transition_magic
  7. pcmk__transition_key
  8. decode_transition_key
  9. should_filter_for_digest
  10. pcmk__filter_op_for_digest
  11. rsc_op_expected_rc
  12. did_rsc_op_fail
  13. crm_create_op_xml
  14. crm_op_needs_metadata
  15. pcmk__is_fencing_action
  16. pcmk_is_probe
  17. pcmk_xe_is_probe
  18. pcmk_xe_mask_probe_failure

   1 /*
   2  * Copyright 2004-2023 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 Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #ifndef _GNU_SOURCE
  13 #  define _GNU_SOURCE
  14 #endif
  15 
  16 #include <stdio.h>
  17 #include <string.h>
  18 #include <stdlib.h>
  19 #include <sys/types.h>
  20 #include <ctype.h>
  21 
  22 #include <crm/crm.h>
  23 #include <crm/lrmd.h>
  24 #include <crm/msg_xml.h>
  25 #include <crm/common/xml.h>
  26 #include <crm/common/xml_internal.h>
  27 #include <crm/common/util.h>
  28 
  29 /*!
  30  * \brief Generate an operation key (RESOURCE_ACTION_INTERVAL)
  31  *
  32  * \param[in] rsc_id       ID of resource being operated on
  33  * \param[in] op_type      Operation name
  34  * \param[in] interval_ms  Operation interval
  35  *
  36  * \return Newly allocated memory containing operation key as string
  37  *
  38  * \note This function asserts on errors, so it will never return NULL.
  39  *       The caller is responsible for freeing the result with free().
  40  */
  41 char *
  42 pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
  43 {
  44     CRM_ASSERT(rsc_id != NULL);
  45     CRM_ASSERT(op_type != NULL);
  46     return crm_strdup_printf(PCMK__OP_FMT, rsc_id, op_type, interval_ms);
  47 }
  48 
  49 static inline gboolean
  50 convert_interval(const char *s, guint *interval_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
  51 {
  52     unsigned long l;
  53 
  54     errno = 0;
  55     l = strtoul(s, NULL, 10);
  56 
  57     if (errno != 0) {
  58         return FALSE;
  59     }
  60 
  61     *interval_ms = (guint) l;
  62     return TRUE;
  63 }
  64 
  65 /*!
  66  * \internal
  67  * \brief Check for underbar-separated substring match
  68  *
  69  * \param[in] key       Overall string being checked
  70  * \param[in] position  Match before underbar at this \p key index
  71  * \param[in] matches   Substrings to match (may contain underbars)
  72  *
  73  * \return \p key index of underbar before any matching substring,
  74  *         or 0 if none
  75  */
  76 static size_t
  77 match_before(const char *key, size_t position, const char **matches)
     /* [previous][next][first][last][top][bottom][index][help] */
  78 {
  79     for (int i = 0; matches[i] != NULL; ++i) {
  80         const size_t match_len = strlen(matches[i]);
  81 
  82         // Must have at least X_MATCH before position
  83         if (position > (match_len + 1)) {
  84             const size_t possible = position - match_len - 1;
  85 
  86             if ((key[possible] == '_')
  87                 && (strncmp(key + possible + 1, matches[i], match_len) == 0)) {
  88                 return possible;
  89             }
  90         }
  91     }
  92     return 0;
  93 }
  94 
  95 gboolean
  96 parse_op_key(const char *key, char **rsc_id, char **op_type, guint *interval_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
  97 {
  98     guint local_interval_ms = 0;
  99     const size_t key_len = (key == NULL)? 0 : strlen(key);
 100 
 101     // Operation keys must be formatted as RSC_ACTION_INTERVAL
 102     size_t action_underbar = 0;   // Index in key of underbar before ACTION
 103     size_t interval_underbar = 0; // Index in key of underbar before INTERVAL
 104     size_t possible = 0;
 105 
 106     /* Underbar was a poor choice of separator since both RSC and ACTION can
 107      * contain underbars. Here, list action names and name prefixes that can.
 108      */
 109     const char *actions_with_underbars[] = {
 110         PCMK_ACTION_MIGRATE_FROM,
 111         PCMK_ACTION_MIGRATE_TO,
 112         NULL
 113     };
 114     const char *action_prefixes_with_underbars[] = {
 115         "pre_" PCMK_ACTION_NOTIFY,
 116         "post_" PCMK_ACTION_NOTIFY,
 117         "confirmed-pre_" PCMK_ACTION_NOTIFY,
 118         "confirmed-post_" PCMK_ACTION_NOTIFY,
 119         NULL,
 120     };
 121 
 122     // Initialize output variables in case of early return
 123     if (rsc_id) {
 124         *rsc_id = NULL;
 125     }
 126     if (op_type) {
 127         *op_type = NULL;
 128     }
 129     if (interval_ms) {
 130         *interval_ms = 0;
 131     }
 132 
 133     // RSC_ACTION_INTERVAL implies a minimum of 5 characters
 134     if (key_len < 5) {
 135         return FALSE;
 136     }
 137 
 138     // Find, parse, and validate interval
 139     interval_underbar = key_len - 2;
 140     while ((interval_underbar > 2) && (key[interval_underbar] != '_')) {
 141         --interval_underbar;
 142     }
 143     if ((interval_underbar == 2)
 144         || !convert_interval(key + interval_underbar + 1, &local_interval_ms)) {
 145         return FALSE;
 146     }
 147 
 148     // Find the base (OCF) action name, disregarding prefixes
 149     action_underbar = match_before(key, interval_underbar,
 150                                    actions_with_underbars);
 151     if (action_underbar == 0) {
 152         action_underbar = interval_underbar - 2;
 153         while ((action_underbar > 0) && (key[action_underbar] != '_')) {
 154             --action_underbar;
 155         }
 156         if (action_underbar == 0) {
 157             return FALSE;
 158         }
 159     }
 160     possible = match_before(key, action_underbar,
 161                             action_prefixes_with_underbars);
 162     if (possible != 0) {
 163         action_underbar = possible;
 164     }
 165 
 166     // Set output variables
 167     if (rsc_id != NULL) {
 168         *rsc_id = strndup(key, action_underbar);
 169         CRM_ASSERT(*rsc_id != NULL);
 170     }
 171     if (op_type != NULL) {
 172         *op_type = strndup(key + action_underbar + 1,
 173                            interval_underbar - action_underbar - 1);
 174         CRM_ASSERT(*op_type != NULL);
 175     }
 176     if (interval_ms != NULL) {
 177         *interval_ms = local_interval_ms;
 178     }
 179     return TRUE;
 180 }
 181 
 182 char *
 183 pcmk__notify_key(const char *rsc_id, const char *notify_type,
     /* [previous][next][first][last][top][bottom][index][help] */
 184                  const char *op_type)
 185 {
 186     CRM_CHECK(rsc_id != NULL, return NULL);
 187     CRM_CHECK(op_type != NULL, return NULL);
 188     CRM_CHECK(notify_type != NULL, return NULL);
 189     return crm_strdup_printf("%s_%s_notify_%s_0",
 190                              rsc_id, notify_type, op_type);
 191 }
 192 
 193 /*!
 194  * \brief Parse a transition magic string into its constituent parts
 195  *
 196  * \param[in]  magic          Magic string to parse (must be non-NULL)
 197  * \param[out] uuid           If non-NULL, where to store copy of parsed UUID
 198  * \param[out] transition_id  If non-NULL, where to store parsed transition ID
 199  * \param[out] action_id      If non-NULL, where to store parsed action ID
 200  * \param[out] op_status      If non-NULL, where to store parsed result status
 201  * \param[out] op_rc          If non-NULL, where to store parsed actual rc
 202  * \param[out] target_rc      If non-NULL, where to stored parsed target rc
 203  *
 204  * \return TRUE if key was valid, FALSE otherwise
 205  * \note If uuid is supplied and this returns TRUE, the caller is responsible
 206  *       for freeing the memory for *uuid using free().
 207  */
 208 gboolean
 209 decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id,
     /* [previous][next][first][last][top][bottom][index][help] */
 210                         int *op_status, int *op_rc, int *target_rc)
 211 {
 212     int res = 0;
 213     char *key = NULL;
 214     gboolean result = TRUE;
 215     int local_op_status = -1;
 216     int local_op_rc = -1;
 217 
 218     CRM_CHECK(magic != NULL, return FALSE);
 219 
 220 #ifdef HAVE_SSCANF_M
 221     res = sscanf(magic, "%d:%d;%ms", &local_op_status, &local_op_rc, &key);
 222 #else
 223     key = calloc(1, strlen(magic) - 3); // magic must have >=4 other characters
 224     CRM_ASSERT(key);
 225     res = sscanf(magic, "%d:%d;%s", &local_op_status, &local_op_rc, key);
 226 #endif
 227     if (res == EOF) {
 228         crm_err("Could not decode transition information '%s': %s",
 229                 magic, pcmk_rc_str(errno));
 230         result = FALSE;
 231     } else if (res < 3) {
 232         crm_warn("Transition information '%s' incomplete (%d of 3 expected items)",
 233                  magic, res);
 234         result = FALSE;
 235     } else {
 236         if (op_status) {
 237             *op_status = local_op_status;
 238         }
 239         if (op_rc) {
 240             *op_rc = local_op_rc;
 241         }
 242         result = decode_transition_key(key, uuid, transition_id, action_id,
 243                                        target_rc);
 244     }
 245     free(key);
 246     return result;
 247 }
 248 
 249 char *
 250 pcmk__transition_key(int transition_id, int action_id, int target_rc,
     /* [previous][next][first][last][top][bottom][index][help] */
 251                      const char *node)
 252 {
 253     CRM_CHECK(node != NULL, return NULL);
 254     return crm_strdup_printf("%d:%d:%d:%-*s",
 255                              action_id, transition_id, target_rc, 36, node);
 256 }
 257 
 258 /*!
 259  * \brief Parse a transition key into its constituent parts
 260  *
 261  * \param[in]  key            Transition key to parse (must be non-NULL)
 262  * \param[out] uuid           If non-NULL, where to store copy of parsed UUID
 263  * \param[out] transition_id  If non-NULL, where to store parsed transition ID
 264  * \param[out] action_id      If non-NULL, where to store parsed action ID
 265  * \param[out] target_rc      If non-NULL, where to stored parsed target rc
 266  *
 267  * \return TRUE if key was valid, FALSE otherwise
 268  * \note If uuid is supplied and this returns TRUE, the caller is responsible
 269  *       for freeing the memory for *uuid using free().
 270  */
 271 gboolean
 272 decode_transition_key(const char *key, char **uuid, int *transition_id, int *action_id,
     /* [previous][next][first][last][top][bottom][index][help] */
 273                       int *target_rc)
 274 {
 275     int local_transition_id = -1;
 276     int local_action_id = -1;
 277     int local_target_rc = -1;
 278     char local_uuid[37] = { '\0' };
 279 
 280     // Initialize any supplied output arguments
 281     if (uuid) {
 282         *uuid = NULL;
 283     }
 284     if (transition_id) {
 285         *transition_id = -1;
 286     }
 287     if (action_id) {
 288         *action_id = -1;
 289     }
 290     if (target_rc) {
 291         *target_rc = -1;
 292     }
 293 
 294     CRM_CHECK(key != NULL, return FALSE);
 295     if (sscanf(key, "%d:%d:%d:%36s", &local_action_id, &local_transition_id,
 296                &local_target_rc, local_uuid) != 4) {
 297         crm_err("Invalid transition key '%s'", key);
 298         return FALSE;
 299     }
 300     if (strlen(local_uuid) != 36) {
 301         crm_warn("Invalid UUID '%s' in transition key '%s'", local_uuid, key);
 302     }
 303     if (uuid) {
 304         *uuid = strdup(local_uuid);
 305         CRM_ASSERT(*uuid);
 306     }
 307     if (transition_id) {
 308         *transition_id = local_transition_id;
 309     }
 310     if (action_id) {
 311         *action_id = local_action_id;
 312     }
 313     if (target_rc) {
 314         *target_rc = local_target_rc;
 315     }
 316     return TRUE;
 317 }
 318 
 319 // Return true if a is an attribute that should be filtered
 320 static bool
 321 should_filter_for_digest(xmlAttrPtr a, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 322 {
 323     if (strncmp((const char *) a->name, CRM_META "_",
 324                 sizeof(CRM_META " ") - 1) == 0) {
 325         return true;
 326     }
 327     return pcmk__str_any_of((const char *) a->name,
 328                             XML_ATTR_ID,
 329                             XML_ATTR_CRM_VERSION,
 330                             XML_LRM_ATTR_OP_DIGEST,
 331                             XML_LRM_ATTR_TARGET,
 332                             XML_LRM_ATTR_TARGET_UUID,
 333                             "pcmk_external_ip",
 334                             NULL);
 335 }
 336 
 337 /*!
 338  * \internal
 339  * \brief Remove XML attributes not needed for operation digest
 340  *
 341  * \param[in,out] param_set  XML with operation parameters
 342  */
 343 void
 344 pcmk__filter_op_for_digest(xmlNode *param_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 345 {
 346     char *key = NULL;
 347     char *timeout = NULL;
 348     guint interval_ms = 0;
 349 
 350     if (param_set == NULL) {
 351         return;
 352     }
 353 
 354     /* Timeout is useful for recurring operation digests, so grab it before
 355      * removing meta-attributes
 356      */
 357     key = crm_meta_name(XML_LRM_ATTR_INTERVAL_MS);
 358     if (crm_element_value_ms(param_set, key, &interval_ms) != pcmk_ok) {
 359         interval_ms = 0;
 360     }
 361     free(key);
 362     key = NULL;
 363     if (interval_ms != 0) {
 364         key = crm_meta_name(XML_ATTR_TIMEOUT);
 365         timeout = crm_element_value_copy(param_set, key);
 366     }
 367 
 368     // Remove all CRM_meta_* attributes and certain other attributes
 369     pcmk__xe_remove_matching_attrs(param_set, should_filter_for_digest, NULL);
 370 
 371     // Add timeout back for recurring operation digests
 372     if (timeout != NULL) {
 373         crm_xml_add(param_set, key, timeout);
 374     }
 375     free(timeout);
 376     free(key);
 377 }
 378 
 379 int
 380 rsc_op_expected_rc(const lrmd_event_data_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 381 {
 382     int rc = 0;
 383 
 384     if (op && op->user_data) {
 385         decode_transition_key(op->user_data, NULL, NULL, NULL, &rc);
 386     }
 387     return rc;
 388 }
 389 
 390 gboolean
 391 did_rsc_op_fail(lrmd_event_data_t * op, int target_rc)
     /* [previous][next][first][last][top][bottom][index][help] */
 392 {
 393     switch (op->op_status) {
 394         case PCMK_EXEC_CANCELLED:
 395         case PCMK_EXEC_PENDING:
 396             return FALSE;
 397 
 398         case PCMK_EXEC_NOT_SUPPORTED:
 399         case PCMK_EXEC_TIMEOUT:
 400         case PCMK_EXEC_ERROR:
 401         case PCMK_EXEC_NOT_CONNECTED:
 402         case PCMK_EXEC_NO_FENCE_DEVICE:
 403         case PCMK_EXEC_NO_SECRETS:
 404         case PCMK_EXEC_INVALID:
 405             return TRUE;
 406 
 407         default:
 408             if (target_rc != op->rc) {
 409                 return TRUE;
 410             }
 411     }
 412 
 413     return FALSE;
 414 }
 415 
 416 /*!
 417  * \brief Create a CIB XML element for an operation
 418  *
 419  * \param[in,out] parent         If not NULL, make new XML node a child of this
 420  * \param[in]     prefix         Generate an ID using this prefix
 421  * \param[in]     task           Operation task to set
 422  * \param[in]     interval_spec  Operation interval to set
 423  * \param[in]     timeout        If not NULL, operation timeout to set
 424  *
 425  * \return New XML object on success, NULL otherwise
 426  */
 427 xmlNode *
 428 crm_create_op_xml(xmlNode *parent, const char *prefix, const char *task,
     /* [previous][next][first][last][top][bottom][index][help] */
 429                   const char *interval_spec, const char *timeout)
 430 {
 431     xmlNode *xml_op;
 432 
 433     CRM_CHECK(prefix && task && interval_spec, return NULL);
 434 
 435     xml_op = create_xml_node(parent, XML_ATTR_OP);
 436     crm_xml_set_id(xml_op, "%s-%s-%s", prefix, task, interval_spec);
 437     crm_xml_add(xml_op, XML_LRM_ATTR_INTERVAL, interval_spec);
 438     crm_xml_add(xml_op, "name", task);
 439     if (timeout) {
 440         crm_xml_add(xml_op, XML_ATTR_TIMEOUT, timeout);
 441     }
 442     return xml_op;
 443 }
 444 
 445 /*!
 446  * \brief Check whether an operation requires resource agent meta-data
 447  *
 448  * \param[in] rsc_class  Resource agent class (or NULL to skip class check)
 449  * \param[in] op         Operation action (or NULL to skip op check)
 450  *
 451  * \return true if operation needs meta-data, false otherwise
 452  * \note At least one of rsc_class and op must be specified.
 453  */
 454 bool
 455 crm_op_needs_metadata(const char *rsc_class, const char *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 456 {
 457     /* Agent metadata is used to determine whether an agent reload is possible,
 458      * so if this op is not relevant to that feature, we don't need metadata.
 459      */
 460 
 461     CRM_CHECK((rsc_class != NULL) || (op != NULL), return false);
 462 
 463     if ((rsc_class != NULL)
 464         && !pcmk_is_set(pcmk_get_ra_caps(rsc_class), pcmk_ra_cap_params)) {
 465         // Metadata is needed only for resource classes that use parameters
 466         return false;
 467     }
 468     if (op == NULL) {
 469         return true;
 470     }
 471 
 472     // Metadata is needed only for these actions
 473     return pcmk__str_any_of(op, PCMK_ACTION_START, PCMK_ACTION_MONITOR,
 474                             PCMK_ACTION_PROMOTE, PCMK_ACTION_DEMOTE,
 475                             PCMK_ACTION_RELOAD, PCMK_ACTION_RELOAD_AGENT,
 476                             PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM,
 477                             PCMK_ACTION_NOTIFY, NULL);
 478 }
 479 
 480 /*!
 481  * \internal
 482  * \brief Check whether an action name is for a fencing action
 483  *
 484  * \param[in] action  Action name to check
 485  *
 486  * \return true if \p action is "off", "reboot", or "poweroff", otherwise false
 487  */
 488 bool
 489 pcmk__is_fencing_action(const char *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 490 {
 491     return pcmk__str_any_of(action, PCMK_ACTION_OFF, PCMK_ACTION_REBOOT,
 492                             "poweroff", NULL);
 493 }
 494 
 495 bool
 496 pcmk_is_probe(const char *task, guint interval)
     /* [previous][next][first][last][top][bottom][index][help] */
 497 {
 498     if (task == NULL) {
 499         return false;
 500     }
 501 
 502     return (interval == 0)
 503            && pcmk__str_eq(task, PCMK_ACTION_MONITOR, pcmk__str_none);
 504 }
 505 
 506 bool
 507 pcmk_xe_is_probe(const xmlNode *xml_op)
     /* [previous][next][first][last][top][bottom][index][help] */
 508 {
 509     const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
 510     const char *interval_ms_s = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL_MS);
 511     int interval_ms;
 512 
 513     pcmk__scan_min_int(interval_ms_s, &interval_ms, 0);
 514     return pcmk_is_probe(task, interval_ms);
 515 }
 516 
 517 bool
 518 pcmk_xe_mask_probe_failure(const xmlNode *xml_op)
     /* [previous][next][first][last][top][bottom][index][help] */
 519 {
 520     int status = PCMK_EXEC_UNKNOWN;
 521     int rc = PCMK_OCF_OK;
 522 
 523     if (!pcmk_xe_is_probe(xml_op)) {
 524         return false;
 525     }
 526 
 527     crm_element_value_int(xml_op, XML_LRM_ATTR_OPSTATUS, &status);
 528     crm_element_value_int(xml_op, XML_LRM_ATTR_RC, &rc);
 529 
 530     return rc == PCMK_OCF_NOT_INSTALLED || rc == PCMK_OCF_INVALID_PARAM ||
 531            status == PCMK_EXEC_NOT_INSTALLED;
 532 }

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