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

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