root/lib/common/alerts.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__alert_new
  2. pcmk__free_alert
  3. pcmk__dup_alert
  4. pcmk__add_alert_key
  5. pcmk__add_alert_key_int
  6. unpack_alert_options
  7. unpack_alert_parameters
  8. unpack_alert_filter
  9. unpack_alert
  10. pcmk__unpack_alerts
  11. pcmk__free_alerts

   1 /*
   2  * Copyright 2015-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 #include <crm/crm.h>
  12 #include <crm/lrmd.h>
  13 #include <crm/common/xml.h>
  14 #include <crm/common/alerts_internal.h>
  15 #include <crm/common/cib_internal.h>
  16 #include <crm/common/xml_internal.h>
  17 
  18 const char *pcmk__alert_keys[PCMK__ALERT_INTERNAL_KEY_MAX] = {
  19     [PCMK__alert_key_recipient] = "CRM_alert_recipient",
  20     [PCMK__alert_key_node] = "CRM_alert_node",
  21     [PCMK__alert_key_nodeid] = "CRM_alert_nodeid",
  22     [PCMK__alert_key_rsc] = "CRM_alert_rsc",
  23     [PCMK__alert_key_task] = "CRM_alert_task",
  24     [PCMK__alert_key_interval] = "CRM_alert_interval",
  25     [PCMK__alert_key_desc] = "CRM_alert_desc",
  26     [PCMK__alert_key_status] = "CRM_alert_status",
  27     [PCMK__alert_key_target_rc] = "CRM_alert_target_rc",
  28     [PCMK__alert_key_rc] = "CRM_alert_rc",
  29     [PCMK__alert_key_kind] = "CRM_alert_kind",
  30     [PCMK__alert_key_version] = "CRM_alert_version",
  31     [PCMK__alert_key_node_sequence] = PCMK__ALERT_NODE_SEQUENCE,
  32     [PCMK__alert_key_timestamp] = "CRM_alert_timestamp",
  33     [PCMK__alert_key_attribute_name] = "CRM_alert_attribute_name",
  34     [PCMK__alert_key_attribute_value] = "CRM_alert_attribute_value",
  35     [PCMK__alert_key_timestamp_epoch] = "CRM_alert_timestamp_epoch",
  36     [PCMK__alert_key_timestamp_usec] = "CRM_alert_timestamp_usec",
  37     [PCMK__alert_key_exec_time] = "CRM_alert_exec_time",
  38 };
  39 
  40 /*!
  41  * \brief Create a new alert entry structure
  42  *
  43  * \param[in] id  ID to use
  44  * \param[in] path  Path to alert agent executable
  45  *
  46  * \return Pointer to newly allocated alert entry
  47  * \note Non-string fields will be filled in with defaults.
  48  *       It is the caller's responsibility to free the result,
  49  *       using pcmk__free_alert().
  50  */
  51 pcmk__alert_t *
  52 pcmk__alert_new(const char *id, const char *path)
     /* [previous][next][first][last][top][bottom][index][help] */
  53 {
  54     pcmk__alert_t *entry = pcmk__assert_alloc(1, sizeof(pcmk__alert_t));
  55 
  56     pcmk__assert((id != NULL) && (path != NULL));
  57     entry->id = pcmk__str_copy(id);
  58     entry->path = pcmk__str_copy(path);
  59     entry->timeout = PCMK__ALERT_DEFAULT_TIMEOUT_MS;
  60     entry->flags = pcmk__alert_default;
  61     return entry;
  62 }
  63 
  64 void
  65 pcmk__free_alert(pcmk__alert_t *entry)
     /* [previous][next][first][last][top][bottom][index][help] */
  66 {
  67     if (entry) {
  68         free(entry->id);
  69         free(entry->path);
  70         free(entry->tstamp_format);
  71         free(entry->recipient);
  72 
  73         g_strfreev(entry->select_attribute_name);
  74         if (entry->envvars) {
  75             g_hash_table_destroy(entry->envvars);
  76         }
  77         free(entry);
  78     }
  79 }
  80 
  81 /*!
  82  * \internal
  83  * \brief Duplicate an alert entry
  84  *
  85  * \param[in] entry  Alert entry to duplicate
  86  *
  87  * \return Duplicate of alert entry
  88  */
  89 pcmk__alert_t *
  90 pcmk__dup_alert(const pcmk__alert_t *entry)
     /* [previous][next][first][last][top][bottom][index][help] */
  91 {
  92     pcmk__alert_t *new_entry = pcmk__alert_new(entry->id, entry->path);
  93 
  94     new_entry->timeout = entry->timeout;
  95     new_entry->flags = entry->flags;
  96     new_entry->envvars = pcmk__str_table_dup(entry->envvars);
  97     new_entry->tstamp_format = pcmk__str_copy(entry->tstamp_format);
  98     new_entry->recipient = pcmk__str_copy(entry->recipient);
  99     if (entry->select_attribute_name) {
 100         new_entry->select_attribute_name = g_strdupv(entry->select_attribute_name);
 101     }
 102     return new_entry;
 103 }
 104 
 105 void
 106 pcmk__add_alert_key(GHashTable *table, enum pcmk__alert_keys_e name,
     /* [previous][next][first][last][top][bottom][index][help] */
 107                     const char *value)
 108 {
 109     pcmk__assert((table != NULL) && (name >= 0)
 110                  && (name < PCMK__ALERT_INTERNAL_KEY_MAX));
 111     if (value == NULL) {
 112         crm_trace("Removing alert key %s", pcmk__alert_keys[name]);
 113         g_hash_table_remove(table, pcmk__alert_keys[name]);
 114     } else {
 115         crm_trace("Inserting alert key %s = '%s'",
 116                   pcmk__alert_keys[name], value);
 117         pcmk__insert_dup(table, pcmk__alert_keys[name], value);
 118     }
 119 }
 120 
 121 void
 122 pcmk__add_alert_key_int(GHashTable *table, enum pcmk__alert_keys_e name,
     /* [previous][next][first][last][top][bottom][index][help] */
 123                         int value)
 124 {
 125     pcmk__assert((table != NULL) && (name >= 0)
 126                  && (name < PCMK__ALERT_INTERNAL_KEY_MAX));
 127     crm_trace("Inserting alert key %s = %d", pcmk__alert_keys[name], value);
 128     g_hash_table_insert(table, pcmk__str_copy(pcmk__alert_keys[name]),
 129                         pcmk__itoa(value));
 130 }
 131 
 132 #define READABLE_DEFAULT pcmk__readable_interval(PCMK__ALERT_DEFAULT_TIMEOUT_MS)
 133 
 134 /*!
 135  * \internal
 136  * \brief Unpack options for an alert or alert recipient from its
 137  *        meta-attributes in the CIB XML configuration
 138  *
 139  * \param[in,out] xml          Alert or recipient XML
 140  * \param[in,out] entry        Where to store unpacked values
 141  * \param[in,out] max_timeout  Max timeout of all alerts and recipients thus far
 142  *
 143  * \return Standard Pacemaker return code
 144  */
 145 static int
 146 unpack_alert_options(xmlNode *xml, pcmk__alert_t *entry, guint *max_timeout)
     /* [previous][next][first][last][top][bottom][index][help] */
 147 {
 148     GHashTable *config_hash = pcmk__strkey_table(free, free);
 149     crm_time_t *now = crm_time_new(NULL);
 150     const char *value = NULL;
 151     int rc = pcmk_rc_ok;
 152 
 153     pcmk_rule_input_t rule_input = {
 154         .now = now,
 155     };
 156 
 157     pcmk_unpack_nvpair_blocks(xml, PCMK_XE_META_ATTRIBUTES, NULL, &rule_input,
 158                               config_hash, NULL);
 159     crm_time_free(now);
 160 
 161     value = g_hash_table_lookup(config_hash, PCMK_META_ENABLED);
 162     if ((value != NULL) && !crm_is_true(value)) {
 163         // No need to continue unpacking
 164         rc = pcmk_rc_disabled;
 165         goto done;
 166     }
 167 
 168     value = g_hash_table_lookup(config_hash, PCMK_META_TIMEOUT);
 169     if (value != NULL) {
 170         long long timeout_ms = crm_get_msec(value);
 171 
 172         entry->timeout = (int) QB_MIN(timeout_ms, INT_MAX);
 173         if (entry->timeout <= 0) {
 174             if (entry->timeout == 0) {
 175                 crm_trace("Alert %s uses default timeout (%s)",
 176                           entry->id, READABLE_DEFAULT);
 177             } else {
 178                 pcmk__config_warn("Using default timeout (%s) for alert %s "
 179                                   "because '%s' is not a valid timeout",
 180                                   entry->id, value, READABLE_DEFAULT);
 181             }
 182             entry->timeout = PCMK__ALERT_DEFAULT_TIMEOUT_MS;
 183         } else {
 184             crm_trace("Alert %s uses timeout of %s",
 185                       entry->id, pcmk__readable_interval(entry->timeout));
 186         }
 187         if (entry->timeout > *max_timeout) {
 188             *max_timeout = entry->timeout;
 189         }
 190     }
 191     value = g_hash_table_lookup(config_hash, PCMK_META_TIMESTAMP_FORMAT);
 192     if (value != NULL) {
 193         /* hard to do any checks here as merely anything can
 194          * can be a valid time-format-string
 195          */
 196         entry->tstamp_format = strdup(value);
 197         crm_trace("Alert %s uses timestamp format '%s'",
 198                   entry->id, entry->tstamp_format);
 199     }
 200 
 201 done:
 202     g_hash_table_destroy(config_hash);
 203     return rc;
 204 }
 205 
 206 /*!
 207  * \internal
 208  * \brief Unpack agent parameters for an alert or alert recipient into an
 209  *        environment variable list based on its CIB XML configuration
 210  *
 211  * \param[in]     xml    Alert or recipient XML
 212  * \param[in,out] entry  Alert entry to create environment variables for
 213  */
 214 static void
 215 unpack_alert_parameters(const xmlNode *xml, pcmk__alert_t *entry)
     /* [previous][next][first][last][top][bottom][index][help] */
 216 {
 217     xmlNode *child;
 218 
 219     if ((xml == NULL) || (entry == NULL)) {
 220         return;
 221     }
 222 
 223     child = pcmk__xe_first_child(xml, PCMK_XE_INSTANCE_ATTRIBUTES, NULL,
 224                                  NULL);
 225     if (child == NULL) {
 226         return;
 227     }
 228 
 229     if (entry->envvars == NULL) {
 230         entry->envvars = pcmk__strkey_table(free, free);
 231     }
 232 
 233     for (child = pcmk__xe_first_child(child, PCMK_XE_NVPAIR, NULL, NULL);
 234          child != NULL; child = pcmk__xe_next(child, PCMK_XE_NVPAIR)) {
 235 
 236         const char *name = crm_element_value(child, PCMK_XA_NAME);
 237         const char *value = crm_element_value(child, PCMK_XA_VALUE);
 238 
 239         if (value == NULL) {
 240             value = "";
 241         }
 242         pcmk__insert_dup(entry->envvars, name, value);
 243         crm_trace("Alert %s: added environment variable %s='%s'",
 244                   entry->id, name, value);
 245     }
 246 }
 247 
 248 /*!
 249  * \internal
 250  * \brief Create filters for an alert or alert recipient based on its
 251  *        configuration in CIB XML
 252  *
 253  * \param[in]     xml    Alert or recipient XML
 254  * \param[in,out] entry  Alert entry to create filters for
 255  */
 256 static void
 257 unpack_alert_filter(xmlNode *xml, pcmk__alert_t *entry)
     /* [previous][next][first][last][top][bottom][index][help] */
 258 {
 259     xmlNode *select = pcmk__xe_first_child(xml, PCMK_XE_SELECT, NULL, NULL);
 260     xmlNode *event_type = NULL;
 261     uint32_t flags = pcmk__alert_none;
 262 
 263     for (event_type = pcmk__xe_first_child(select, NULL, NULL, NULL);
 264          event_type != NULL; event_type = pcmk__xe_next(event_type, NULL)) {
 265 
 266         if (pcmk__xe_is(event_type, PCMK_XE_SELECT_FENCING)) {
 267             flags |= pcmk__alert_fencing;
 268 
 269         } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_NODES)) {
 270             flags |= pcmk__alert_node;
 271 
 272         } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_RESOURCES)) {
 273             flags |= pcmk__alert_resource;
 274 
 275         } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_ATTRIBUTES)) {
 276             xmlNode *attr;
 277             const char *attr_name;
 278             int nattrs = 0;
 279 
 280             flags |= pcmk__alert_attribute;
 281             for (attr = pcmk__xe_first_child(event_type, PCMK_XE_ATTRIBUTE,
 282                                              NULL, NULL);
 283                  attr != NULL; attr = pcmk__xe_next(attr, PCMK_XE_ATTRIBUTE)) {
 284 
 285                 attr_name = crm_element_value(attr, PCMK_XA_NAME);
 286                 if (attr_name) {
 287                     if (nattrs == 0) {
 288                         g_strfreev(entry->select_attribute_name);
 289                         entry->select_attribute_name = NULL;
 290                     }
 291                     ++nattrs;
 292                     entry->select_attribute_name = pcmk__realloc(entry->select_attribute_name,
 293                                                                  (nattrs + 1) * sizeof(char*));
 294                     entry->select_attribute_name[nattrs - 1] = strdup(attr_name);
 295                     entry->select_attribute_name[nattrs] = NULL;
 296                 }
 297             }
 298         }
 299     }
 300 
 301     if (flags != pcmk__alert_none) {
 302         entry->flags = flags;
 303         crm_debug("Alert %s receives events: attributes:%s%s%s%s",
 304                   entry->id,
 305                   (pcmk_is_set(flags, pcmk__alert_attribute)?
 306                    (entry->select_attribute_name? "some" : "all") : "none"),
 307                   (pcmk_is_set(flags, pcmk__alert_fencing)? " fencing" : ""),
 308                   (pcmk_is_set(flags, pcmk__alert_node)? " nodes" : ""),
 309                   (pcmk_is_set(flags, pcmk__alert_resource)? " resources" : ""));
 310     }
 311 }
 312 
 313 /*!
 314  * \internal
 315  * \brief Unpack an alert or an alert recipient
 316  *
 317  * \param[in,out] alert        Alert or recipient XML
 318  * \param[in,out] entry        Where to store unpacked values
 319  * \param[in,out] max_timeout  Max timeout of all alerts and recipients thus far
 320  *
 321  * \return Standard Pacemaker return code
 322  */
 323 static int
 324 unpack_alert(xmlNode *alert, pcmk__alert_t *entry, guint *max_timeout)
     /* [previous][next][first][last][top][bottom][index][help] */
 325 {
 326     int rc = pcmk_rc_ok;
 327 
 328     unpack_alert_parameters(alert, entry);
 329     rc = unpack_alert_options(alert, entry, max_timeout);
 330     if (rc == pcmk_rc_ok) {
 331         unpack_alert_filter(alert, entry);
 332     }
 333     return rc;
 334 }
 335 
 336 /*!
 337  * \internal
 338  * \brief Unpack a CIB alerts section into a list of alert entries
 339  *
 340  * \param[in] alerts  XML of CIB alerts section
 341  *
 342  * \return List of unpacked alert entries
 343  */
 344 GList *
 345 pcmk__unpack_alerts(const xmlNode *alerts)
     /* [previous][next][first][last][top][bottom][index][help] */
 346 {
 347     xmlNode *alert;
 348     pcmk__alert_t *entry;
 349     guint max_timeout = 0U;
 350     GList *alert_list = NULL;
 351 
 352     for (alert = pcmk__xe_first_child(alerts, PCMK_XE_ALERT, NULL, NULL);
 353          alert != NULL; alert = pcmk__xe_next(alert, PCMK_XE_ALERT)) {
 354 
 355         xmlNode *recipient = NULL;
 356         int recipients = 0;
 357         const char *alert_id = pcmk__xe_id(alert);
 358         const char *alert_path = crm_element_value(alert, PCMK_XA_PATH);
 359 
 360         // Not possible with schema validation enabled
 361         if (alert_id == NULL) {
 362             pcmk__config_err("Ignoring invalid alert without " PCMK_XA_ID);
 363             continue;
 364         }
 365         if (alert_path == NULL) {
 366             pcmk__config_err("Ignoring invalid alert %s without " PCMK_XA_PATH,
 367                              alert_id);
 368             continue;
 369         }
 370 
 371         entry = pcmk__alert_new(alert_id, alert_path);
 372 
 373         if (unpack_alert(alert, entry, &max_timeout) != pcmk_rc_ok) {
 374             // Don't allow recipients to override if entire alert is disabled
 375             crm_debug("Alert %s is disabled", entry->id);
 376             pcmk__free_alert(entry);
 377             continue;
 378         }
 379 
 380         if (entry->tstamp_format == NULL) {
 381             entry->tstamp_format =
 382                 pcmk__str_copy(PCMK__ALERT_DEFAULT_TSTAMP_FORMAT);
 383         }
 384 
 385         crm_debug("Alert %s: path=%s timeout=%s tstamp-format='%s'",
 386                   entry->id, entry->path,
 387                   pcmk__readable_interval(entry->timeout),
 388                   entry->tstamp_format);
 389 
 390         for (recipient = pcmk__xe_first_child(alert, PCMK_XE_RECIPIENT, NULL,
 391                                               NULL);
 392              recipient != NULL;
 393              recipient = pcmk__xe_next(recipient, PCMK_XE_RECIPIENT)) {
 394 
 395             pcmk__alert_t *recipient_entry = pcmk__dup_alert(entry);
 396 
 397             recipients++;
 398             recipient_entry->recipient = crm_element_value_copy(recipient,
 399                                                                 PCMK_XA_VALUE);
 400 
 401             if (unpack_alert(recipient, recipient_entry,
 402                              &max_timeout) != pcmk_rc_ok) {
 403                 crm_debug("Alert %s: recipient %s is disabled",
 404                           entry->id, recipient_entry->id);
 405                 pcmk__free_alert(recipient_entry);
 406                 continue;
 407             }
 408             alert_list = g_list_prepend(alert_list, recipient_entry);
 409             crm_debug("Alert %s has recipient %s with value %s and %d envvars",
 410                       entry->id, pcmk__xe_id(recipient),
 411                       recipient_entry->recipient,
 412                       (recipient_entry->envvars?
 413                        g_hash_table_size(recipient_entry->envvars) : 0));
 414         }
 415 
 416         if (recipients == 0) {
 417             alert_list = g_list_prepend(alert_list, entry);
 418         } else { // Recipients were prepended individually above
 419             pcmk__free_alert(entry);
 420         }
 421     }
 422     return alert_list;
 423 }
 424 
 425 /*!
 426  * \internal
 427  * \brief Free an alert list generated by pcmk__unpack_alerts()
 428  *
 429  * \param[in,out] alert_list  Alert list to free
 430  */
 431 void
 432 pcmk__free_alerts(GList *alert_list)
     /* [previous][next][first][last][top][bottom][index][help] */
 433 {
 434     if (alert_list != NULL) {
 435         g_list_free_full(alert_list, (GDestroyNotify) pcmk__free_alert);
 436     }
 437 }

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