root/lib/common/actions.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk_action_text
  2. pcmk_parse_action
  3. pcmk_on_fail_text
  4. pcmk__op_key
  5. convert_interval
  6. match_before
  7. parse_op_key
  8. pcmk__notify_key
  9. decode_transition_magic
  10. pcmk__transition_key
  11. decode_transition_key
  12. rsc_op_expected_rc
  13. did_rsc_op_fail
  14. crm_create_op_xml
  15. crm_op_needs_metadata
  16. pcmk__is_fencing_action

   1 /*
   2  * Copyright 2004-2024 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/common/xml.h>
  25 #include <crm/common/xml_internal.h>
  26 #include <crm/common/util.h>
  27 #include <crm/common/scheduler.h>
  28 
  29 /*!
  30  * \brief Get string equivalent of an action type
  31  *
  32  * \param[in] action  Action type
  33  *
  34  * \return Static string describing \p action
  35  */
  36 const char *
  37 pcmk_action_text(enum action_tasks action)
     /* [previous][next][first][last][top][bottom][index][help] */
  38 {
  39     switch (action) {
  40         case pcmk_action_stop:
  41             return PCMK_ACTION_STOP;
  42 
  43         case pcmk_action_stopped:
  44             return PCMK_ACTION_STOPPED;
  45 
  46         case pcmk_action_start:
  47             return PCMK_ACTION_START;
  48 
  49         case pcmk_action_started:
  50             return PCMK_ACTION_RUNNING;
  51 
  52         case pcmk_action_shutdown:
  53             return PCMK_ACTION_DO_SHUTDOWN;
  54 
  55         case pcmk_action_fence:
  56             return PCMK_ACTION_STONITH;
  57 
  58         case pcmk_action_monitor:
  59             return PCMK_ACTION_MONITOR;
  60 
  61         case pcmk_action_notify:
  62             return PCMK_ACTION_NOTIFY;
  63 
  64         case pcmk_action_notified:
  65             return PCMK_ACTION_NOTIFIED;
  66 
  67         case pcmk_action_promote:
  68             return PCMK_ACTION_PROMOTE;
  69 
  70         case pcmk_action_promoted:
  71             return PCMK_ACTION_PROMOTED;
  72 
  73         case pcmk_action_demote:
  74             return PCMK_ACTION_DEMOTE;
  75 
  76         case pcmk_action_demoted:
  77             return PCMK_ACTION_DEMOTED;
  78 
  79         default: // pcmk_action_unspecified or invalid
  80             return "no_action";
  81     }
  82 }
  83 
  84 /*!
  85  * \brief Parse an action type from an action name
  86  *
  87  * \param[in] action_name  Action name
  88  *
  89  * \return Action type corresponding to \p action_name
  90  */
  91 enum action_tasks
  92 pcmk_parse_action(const char *action_name)
     /* [previous][next][first][last][top][bottom][index][help] */
  93 {
  94     if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)) {
  95         return pcmk_action_stop;
  96 
  97     } else if (pcmk__str_eq(action_name, PCMK_ACTION_STOPPED, pcmk__str_none)) {
  98         return pcmk_action_stopped;
  99 
 100     } else if (pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)) {
 101         return pcmk_action_start;
 102 
 103     } else if (pcmk__str_eq(action_name, PCMK_ACTION_RUNNING, pcmk__str_none)) {
 104         return pcmk_action_started;
 105 
 106     } else if (pcmk__str_eq(action_name, PCMK_ACTION_DO_SHUTDOWN,
 107                             pcmk__str_none)) {
 108         return pcmk_action_shutdown;
 109 
 110     } else if (pcmk__str_eq(action_name, PCMK_ACTION_STONITH, pcmk__str_none)) {
 111         return pcmk_action_fence;
 112 
 113     } else if (pcmk__str_eq(action_name, PCMK_ACTION_MONITOR, pcmk__str_none)) {
 114         return pcmk_action_monitor;
 115 
 116     } else if (pcmk__str_eq(action_name, PCMK_ACTION_NOTIFY, pcmk__str_none)) {
 117         return pcmk_action_notify;
 118 
 119     } else if (pcmk__str_eq(action_name, PCMK_ACTION_NOTIFIED,
 120                             pcmk__str_none)) {
 121         return pcmk_action_notified;
 122 
 123     } else if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
 124         return pcmk_action_promote;
 125 
 126     } else if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTE, pcmk__str_none)) {
 127         return pcmk_action_demote;
 128 
 129     } else if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTED,
 130                             pcmk__str_none)) {
 131         return pcmk_action_promoted;
 132 
 133     } else if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTED, pcmk__str_none)) {
 134         return pcmk_action_demoted;
 135     }
 136     return pcmk_action_unspecified;
 137 }
 138 
 139 /*!
 140  * \brief Get string equivalent of a failure handling type
 141  *
 142  * \param[in] on_fail  Failure handling type
 143  *
 144  * \return Static string describing \p on_fail
 145  */
 146 const char *
 147 pcmk_on_fail_text(enum action_fail_response on_fail)
     /* [previous][next][first][last][top][bottom][index][help] */
 148 {
 149     switch (on_fail) {
 150         case pcmk_on_fail_ignore:
 151             return "ignore";
 152 
 153         case pcmk_on_fail_demote:
 154             return "demote";
 155 
 156         case pcmk_on_fail_block:
 157             return "block";
 158 
 159         case pcmk_on_fail_restart:
 160             return "recover";
 161 
 162         case pcmk_on_fail_ban:
 163             return "migrate";
 164 
 165         case pcmk_on_fail_stop:
 166             return "stop";
 167 
 168         case pcmk_on_fail_fence_node:
 169             return "fence";
 170 
 171         case pcmk_on_fail_standby_node:
 172             return "standby";
 173 
 174         case pcmk_on_fail_restart_container:
 175             return "restart-container";
 176 
 177         case pcmk_on_fail_reset_remote:
 178             return "reset-remote";
 179     }
 180     return "<unknown>";
 181 }
 182 
 183 /*!
 184  * \brief Generate an operation key (RESOURCE_ACTION_INTERVAL)
 185  *
 186  * \param[in] rsc_id       ID of resource being operated on
 187  * \param[in] op_type      Operation name
 188  * \param[in] interval_ms  Operation interval
 189  *
 190  * \return Newly allocated memory containing operation key as string
 191  *
 192  * \note This function asserts on errors, so it will never return NULL.
 193  *       The caller is responsible for freeing the result with free().
 194  */
 195 char *
 196 pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
 197 {
 198     CRM_ASSERT(rsc_id != NULL);
 199     CRM_ASSERT(op_type != NULL);
 200     return crm_strdup_printf(PCMK__OP_FMT, rsc_id, op_type, interval_ms);
 201 }
 202 
 203 static inline gboolean
 204 convert_interval(const char *s, guint *interval_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
 205 {
 206     unsigned long l;
 207 
 208     errno = 0;
 209     l = strtoul(s, NULL, 10);
 210 
 211     if (errno != 0) {
 212         return FALSE;
 213     }
 214 
 215     *interval_ms = (guint) l;
 216     return TRUE;
 217 }
 218 
 219 /*!
 220  * \internal
 221  * \brief Check for underbar-separated substring match
 222  *
 223  * \param[in] key       Overall string being checked
 224  * \param[in] position  Match before underbar at this \p key index
 225  * \param[in] matches   Substrings to match (may contain underbars)
 226  *
 227  * \return \p key index of underbar before any matching substring,
 228  *         or 0 if none
 229  */
 230 static size_t
 231 match_before(const char *key, size_t position, const char **matches)
     /* [previous][next][first][last][top][bottom][index][help] */
 232 {
 233     for (int i = 0; matches[i] != NULL; ++i) {
 234         const size_t match_len = strlen(matches[i]);
 235 
 236         // Must have at least X_MATCH before position
 237         if (position > (match_len + 1)) {
 238             const size_t possible = position - match_len - 1;
 239 
 240             if ((key[possible] == '_')
 241                 && (strncmp(key + possible + 1, matches[i], match_len) == 0)) {
 242                 return possible;
 243             }
 244         }
 245     }
 246     return 0;
 247 }
 248 
 249 gboolean
 250 parse_op_key(const char *key, char **rsc_id, char **op_type, guint *interval_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
 251 {
 252     guint local_interval_ms = 0;
 253     const size_t key_len = (key == NULL)? 0 : strlen(key);
 254 
 255     // Operation keys must be formatted as RSC_ACTION_INTERVAL
 256     size_t action_underbar = 0;   // Index in key of underbar before ACTION
 257     size_t interval_underbar = 0; // Index in key of underbar before INTERVAL
 258     size_t possible = 0;
 259 
 260     /* Underbar was a poor choice of separator since both RSC and ACTION can
 261      * contain underbars. Here, list action names and name prefixes that can.
 262      */
 263     const char *actions_with_underbars[] = {
 264         PCMK_ACTION_MIGRATE_FROM,
 265         PCMK_ACTION_MIGRATE_TO,
 266         NULL
 267     };
 268     const char *action_prefixes_with_underbars[] = {
 269         "pre_" PCMK_ACTION_NOTIFY,
 270         "post_" PCMK_ACTION_NOTIFY,
 271         "confirmed-pre_" PCMK_ACTION_NOTIFY,
 272         "confirmed-post_" PCMK_ACTION_NOTIFY,
 273         NULL,
 274     };
 275 
 276     // Initialize output variables in case of early return
 277     if (rsc_id) {
 278         *rsc_id = NULL;
 279     }
 280     if (op_type) {
 281         *op_type = NULL;
 282     }
 283     if (interval_ms) {
 284         *interval_ms = 0;
 285     }
 286 
 287     // RSC_ACTION_INTERVAL implies a minimum of 5 characters
 288     if (key_len < 5) {
 289         return FALSE;
 290     }
 291 
 292     // Find, parse, and validate interval
 293     interval_underbar = key_len - 2;
 294     while ((interval_underbar > 2) && (key[interval_underbar] != '_')) {
 295         --interval_underbar;
 296     }
 297     if ((interval_underbar == 2)
 298         || !convert_interval(key + interval_underbar + 1, &local_interval_ms)) {
 299         return FALSE;
 300     }
 301 
 302     // Find the base (OCF) action name, disregarding prefixes
 303     action_underbar = match_before(key, interval_underbar,
 304                                    actions_with_underbars);
 305     if (action_underbar == 0) {
 306         action_underbar = interval_underbar - 2;
 307         while ((action_underbar > 0) && (key[action_underbar] != '_')) {
 308             --action_underbar;
 309         }
 310         if (action_underbar == 0) {
 311             return FALSE;
 312         }
 313     }
 314     possible = match_before(key, action_underbar,
 315                             action_prefixes_with_underbars);
 316     if (possible != 0) {
 317         action_underbar = possible;
 318     }
 319 
 320     // Set output variables
 321     if (rsc_id != NULL) {
 322         *rsc_id = strndup(key, action_underbar);
 323         pcmk__mem_assert(*rsc_id);
 324     }
 325     if (op_type != NULL) {
 326         *op_type = strndup(key + action_underbar + 1,
 327                            interval_underbar - action_underbar - 1);
 328         pcmk__mem_assert(*op_type);
 329     }
 330     if (interval_ms != NULL) {
 331         *interval_ms = local_interval_ms;
 332     }
 333     return TRUE;
 334 }
 335 
 336 char *
 337 pcmk__notify_key(const char *rsc_id, const char *notify_type,
     /* [previous][next][first][last][top][bottom][index][help] */
 338                  const char *op_type)
 339 {
 340     CRM_CHECK(rsc_id != NULL, return NULL);
 341     CRM_CHECK(op_type != NULL, return NULL);
 342     CRM_CHECK(notify_type != NULL, return NULL);
 343     return crm_strdup_printf("%s_%s_notify_%s_0",
 344                              rsc_id, notify_type, op_type);
 345 }
 346 
 347 /*!
 348  * \brief Parse a transition magic string into its constituent parts
 349  *
 350  * \param[in]  magic          Magic string to parse (must be non-NULL)
 351  * \param[out] uuid           If non-NULL, where to store copy of parsed UUID
 352  * \param[out] transition_id  If non-NULL, where to store parsed transition ID
 353  * \param[out] action_id      If non-NULL, where to store parsed action ID
 354  * \param[out] op_status      If non-NULL, where to store parsed result status
 355  * \param[out] op_rc          If non-NULL, where to store parsed actual rc
 356  * \param[out] target_rc      If non-NULL, where to stored parsed target rc
 357  *
 358  * \return TRUE if key was valid, FALSE otherwise
 359  * \note If uuid is supplied and this returns TRUE, the caller is responsible
 360  *       for freeing the memory for *uuid using free().
 361  */
 362 gboolean
 363 decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id,
     /* [previous][next][first][last][top][bottom][index][help] */
 364                         int *op_status, int *op_rc, int *target_rc)
 365 {
 366     int res = 0;
 367     char *key = NULL;
 368     gboolean result = TRUE;
 369     int local_op_status = -1;
 370     int local_op_rc = -1;
 371 
 372     CRM_CHECK(magic != NULL, return FALSE);
 373 
 374 #ifdef HAVE_SSCANF_M
 375     res = sscanf(magic, "%d:%d;%ms", &local_op_status, &local_op_rc, &key);
 376 #else
 377     // magic must have >=4 other characters
 378     key = pcmk__assert_alloc(1, strlen(magic) - 3);
 379     res = sscanf(magic, "%d:%d;%s", &local_op_status, &local_op_rc, key);
 380 #endif
 381     if (res == EOF) {
 382         crm_err("Could not decode transition information '%s': %s",
 383                 magic, pcmk_rc_str(errno));
 384         result = FALSE;
 385     } else if (res < 3) {
 386         crm_warn("Transition information '%s' incomplete (%d of 3 expected items)",
 387                  magic, res);
 388         result = FALSE;
 389     } else {
 390         if (op_status) {
 391             *op_status = local_op_status;
 392         }
 393         if (op_rc) {
 394             *op_rc = local_op_rc;
 395         }
 396         result = decode_transition_key(key, uuid, transition_id, action_id,
 397                                        target_rc);
 398     }
 399     free(key);
 400     return result;
 401 }
 402 
 403 char *
 404 pcmk__transition_key(int transition_id, int action_id, int target_rc,
     /* [previous][next][first][last][top][bottom][index][help] */
 405                      const char *node)
 406 {
 407     CRM_CHECK(node != NULL, return NULL);
 408     return crm_strdup_printf("%d:%d:%d:%-*s",
 409                              action_id, transition_id, target_rc, 36, node);
 410 }
 411 
 412 /*!
 413  * \brief Parse a transition key into its constituent parts
 414  *
 415  * \param[in]  key            Transition key to parse (must be non-NULL)
 416  * \param[out] uuid           If non-NULL, where to store copy of parsed UUID
 417  * \param[out] transition_id  If non-NULL, where to store parsed transition ID
 418  * \param[out] action_id      If non-NULL, where to store parsed action ID
 419  * \param[out] target_rc      If non-NULL, where to stored parsed target rc
 420  *
 421  * \return TRUE if key was valid, FALSE otherwise
 422  * \note If uuid is supplied and this returns TRUE, the caller is responsible
 423  *       for freeing the memory for *uuid using free().
 424  */
 425 gboolean
 426 decode_transition_key(const char *key, char **uuid, int *transition_id, int *action_id,
     /* [previous][next][first][last][top][bottom][index][help] */
 427                       int *target_rc)
 428 {
 429     int local_transition_id = -1;
 430     int local_action_id = -1;
 431     int local_target_rc = -1;
 432     char local_uuid[37] = { '\0' };
 433 
 434     // Initialize any supplied output arguments
 435     if (uuid) {
 436         *uuid = NULL;
 437     }
 438     if (transition_id) {
 439         *transition_id = -1;
 440     }
 441     if (action_id) {
 442         *action_id = -1;
 443     }
 444     if (target_rc) {
 445         *target_rc = -1;
 446     }
 447 
 448     CRM_CHECK(key != NULL, return FALSE);
 449     if (sscanf(key, "%d:%d:%d:%36s", &local_action_id, &local_transition_id,
 450                &local_target_rc, local_uuid) != 4) {
 451         crm_err("Invalid transition key '%s'", key);
 452         return FALSE;
 453     }
 454     if (strlen(local_uuid) != 36) {
 455         crm_warn("Invalid UUID '%s' in transition key '%s'", local_uuid, key);
 456     }
 457     if (uuid) {
 458         *uuid = pcmk__str_copy(local_uuid);
 459     }
 460     if (transition_id) {
 461         *transition_id = local_transition_id;
 462     }
 463     if (action_id) {
 464         *action_id = local_action_id;
 465     }
 466     if (target_rc) {
 467         *target_rc = local_target_rc;
 468     }
 469     return TRUE;
 470 }
 471 
 472 int
 473 rsc_op_expected_rc(const lrmd_event_data_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 474 {
 475     int rc = 0;
 476 
 477     if (op && op->user_data) {
 478         decode_transition_key(op->user_data, NULL, NULL, NULL, &rc);
 479     }
 480     return rc;
 481 }
 482 
 483 gboolean
 484 did_rsc_op_fail(lrmd_event_data_t * op, int target_rc)
     /* [previous][next][first][last][top][bottom][index][help] */
 485 {
 486     switch (op->op_status) {
 487         case PCMK_EXEC_CANCELLED:
 488         case PCMK_EXEC_PENDING:
 489             return FALSE;
 490 
 491         case PCMK_EXEC_NOT_SUPPORTED:
 492         case PCMK_EXEC_TIMEOUT:
 493         case PCMK_EXEC_ERROR:
 494         case PCMK_EXEC_NOT_CONNECTED:
 495         case PCMK_EXEC_NO_FENCE_DEVICE:
 496         case PCMK_EXEC_NO_SECRETS:
 497         case PCMK_EXEC_INVALID:
 498             return TRUE;
 499 
 500         default:
 501             if (target_rc != op->rc) {
 502                 return TRUE;
 503             }
 504     }
 505 
 506     return FALSE;
 507 }
 508 
 509 /*!
 510  * \brief Create a CIB XML element for an operation
 511  *
 512  * \param[in,out] parent         If not NULL, make new XML node a child of this
 513  * \param[in]     prefix         Generate an ID using this prefix
 514  * \param[in]     task           Operation task to set
 515  * \param[in]     interval_spec  Operation interval to set
 516  * \param[in]     timeout        If not NULL, operation timeout to set
 517  *
 518  * \return New XML object on success, NULL otherwise
 519  */
 520 xmlNode *
 521 crm_create_op_xml(xmlNode *parent, const char *prefix, const char *task,
     /* [previous][next][first][last][top][bottom][index][help] */
 522                   const char *interval_spec, const char *timeout)
 523 {
 524     xmlNode *xml_op;
 525 
 526     CRM_CHECK(prefix && task && interval_spec, return NULL);
 527 
 528     xml_op = pcmk__xe_create(parent, PCMK_XE_OP);
 529     crm_xml_set_id(xml_op, "%s-%s-%s", prefix, task, interval_spec);
 530     crm_xml_add(xml_op, PCMK_META_INTERVAL, interval_spec);
 531     crm_xml_add(xml_op, PCMK_XA_NAME, task);
 532     if (timeout) {
 533         crm_xml_add(xml_op, PCMK_META_TIMEOUT, timeout);
 534     }
 535     return xml_op;
 536 }
 537 
 538 /*!
 539  * \brief Check whether an operation requires resource agent meta-data
 540  *
 541  * \param[in] rsc_class  Resource agent class (or NULL to skip class check)
 542  * \param[in] op         Operation action (or NULL to skip op check)
 543  *
 544  * \return true if operation needs meta-data, false otherwise
 545  * \note At least one of rsc_class and op must be specified.
 546  */
 547 bool
 548 crm_op_needs_metadata(const char *rsc_class, const char *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 549 {
 550     /* Agent metadata is used to determine whether an agent reload is possible,
 551      * so if this op is not relevant to that feature, we don't need metadata.
 552      */
 553 
 554     CRM_CHECK((rsc_class != NULL) || (op != NULL), return false);
 555 
 556     if ((rsc_class != NULL)
 557         && !pcmk_is_set(pcmk_get_ra_caps(rsc_class), pcmk_ra_cap_params)) {
 558         // Metadata is needed only for resource classes that use parameters
 559         return false;
 560     }
 561     if (op == NULL) {
 562         return true;
 563     }
 564 
 565     // Metadata is needed only for these actions
 566     return pcmk__str_any_of(op, PCMK_ACTION_START, PCMK_ACTION_MONITOR,
 567                             PCMK_ACTION_PROMOTE, PCMK_ACTION_DEMOTE,
 568                             PCMK_ACTION_RELOAD, PCMK_ACTION_RELOAD_AGENT,
 569                             PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM,
 570                             PCMK_ACTION_NOTIFY, NULL);
 571 }
 572 
 573 /*!
 574  * \internal
 575  * \brief Check whether an action name is for a fencing action
 576  *
 577  * \param[in] action  Action name to check
 578  *
 579  * \return \c true if \p action is \c PCMK_ACTION_OFF, \c PCMK_ACTION_REBOOT,
 580  *         or \c PCMK__ACTION_POWEROFF, otherwise \c false
 581  */
 582 bool
 583 pcmk__is_fencing_action(const char *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 584 {
 585     return pcmk__str_any_of(action, PCMK_ACTION_OFF, PCMK_ACTION_REBOOT,
 586                             PCMK__ACTION_POWEROFF, NULL);
 587 }

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