root/lib/common/operations.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__op_key
  2. convert_interval
  3. try_fast_match
  4. try_basic_match
  5. try_migrate_notify_match
  6. parse_op_key
  7. pcmk__notify_key
  8. decode_transition_magic
  9. pcmk__transition_key
  10. decode_transition_key
  11. should_filter_for_digest
  12. pcmk__filter_op_for_digest
  13. rsc_op_expected_rc
  14. did_rsc_op_fail
  15. crm_create_op_xml
  16. crm_op_needs_metadata

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

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