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. pcmk__filter_op_for_digest
  12. rsc_op_expected_rc
  13. did_rsc_op_fail
  14. crm_create_op_xml
  15. crm_op_needs_metadata

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

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