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/common/xml.h>
13 #include <crm/pengine/rules.h>
14 #include <crm/common/alerts_internal.h>
15 #include <crm/common/xml_internal.h>
16 #include <crm/pengine/rules_internal.h>
17
18 /*!
19 * \internal
20 * \brief Unpack an alert's or alert recipient's meta attributes
21 *
22 * \param[in,out] basenode Alert or recipient XML
23 * \param[in,out] entry Where to store unpacked values
24 * \param[in,out] max_timeout Max timeout of all alerts and recipients thus far
25 *
26 * \return Standard Pacemaker return code
27 */
28 static int
29 get_meta_attrs_from_cib(xmlNode *basenode, pcmk__alert_t *entry,
30 guint *max_timeout)
31 {
32 GHashTable *config_hash = pcmk__strkey_table(free, free);
33 crm_time_t *now = crm_time_new(NULL);
34 const char *value = NULL;
35 int rc = pcmk_rc_ok;
36
37 pe_unpack_nvpairs(basenode, basenode, PCMK_XE_META_ATTRIBUTES, NULL,
38 config_hash, NULL, FALSE, now, NULL);
39 crm_time_free(now);
40
41 value = g_hash_table_lookup(config_hash, PCMK_META_ENABLED);
42 if ((value != NULL) && !crm_is_true(value)) {
43 // No need to continue unpacking
44 rc = pcmk_rc_disabled;
45 goto done;
46 }
47
48 value = g_hash_table_lookup(config_hash, PCMK_META_TIMEOUT);
49 if (value) {
50 long long timeout_ms = crm_get_msec(value);
51
52 entry->timeout = (int) QB_MIN(timeout_ms, INT_MAX);
53 if (entry->timeout <= 0) {
54 if (entry->timeout == 0) {
55 crm_trace("Alert %s uses default timeout of %dmsec",
56 entry->id, PCMK__ALERT_DEFAULT_TIMEOUT_MS);
57 } else {
58 pcmk__config_warn("Alert %s has invalid timeout value '%s', "
59 "using default (%d ms)",
60 entry->id, value,
61 PCMK__ALERT_DEFAULT_TIMEOUT_MS);
62 }
63 entry->timeout = PCMK__ALERT_DEFAULT_TIMEOUT_MS;
64 } else {
65 crm_trace("Alert %s uses timeout of %dmsec",
66 entry->id, entry->timeout);
67 }
68 if (entry->timeout > *max_timeout) {
69 *max_timeout = entry->timeout;
70 }
71 }
72 value = g_hash_table_lookup(config_hash, PCMK_META_TIMESTAMP_FORMAT);
73 if (value) {
74 /* hard to do any checks here as merely anything can
75 * can be a valid time-format-string
76 */
77 entry->tstamp_format = strdup(value);
78 crm_trace("Alert %s uses timestamp format '%s'",
79 entry->id, entry->tstamp_format);
80 }
81
82 done:
83 g_hash_table_destroy(config_hash);
84 return rc;
85 }
86
87 static void
88 get_envvars_from_cib(xmlNode *basenode, pcmk__alert_t *entry)
89 {
90 xmlNode *child;
91
92 if ((basenode == NULL) || (entry == NULL)) {
93 return;
94 }
95
96 child = pcmk__xe_first_child(basenode, PCMK_XE_INSTANCE_ATTRIBUTES, NULL,
97 NULL);
98 if (child == NULL) {
99 return;
100 }
101
102 if (entry->envvars == NULL) {
103 entry->envvars = pcmk__strkey_table(free, free);
104 }
105
106 for (child = pcmk__xe_first_child(child, PCMK_XE_NVPAIR, NULL, NULL);
107 child != NULL; child = pcmk__xe_next_same(child)) {
108
109 const char *name = crm_element_value(child, PCMK_XA_NAME);
110 const char *value = crm_element_value(child, PCMK_XA_VALUE);
111
112 if (value == NULL) {
113 value = "";
114 }
115 pcmk__insert_dup(entry->envvars, name, value);
116 crm_trace("Alert %s: added environment variable %s='%s'",
117 entry->id, name, value);
118 }
119 }
120
121 static void
122 unpack_alert_filter(xmlNode *basenode, pcmk__alert_t *entry)
123 {
124 xmlNode *select = pcmk__xe_first_child(basenode, PCMK_XE_SELECT, NULL,
125 NULL);
126 xmlNode *event_type = NULL;
127 uint32_t flags = pcmk__alert_none;
128
129 for (event_type = pcmk__xe_first_child(select, NULL, NULL, NULL);
130 event_type != NULL; event_type = pcmk__xe_next(event_type)) {
131
132 if (pcmk__xe_is(event_type, PCMK_XE_SELECT_FENCING)) {
133 flags |= pcmk__alert_fencing;
134
135 } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_NODES)) {
136 flags |= pcmk__alert_node;
137
138 } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_RESOURCES)) {
139 flags |= pcmk__alert_resource;
140
141 } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_ATTRIBUTES)) {
142 xmlNode *attr;
143 const char *attr_name;
144 int nattrs = 0;
145
146 flags |= pcmk__alert_attribute;
147 for (attr = pcmk__xe_first_child(event_type, PCMK_XE_ATTRIBUTE,
148 NULL, NULL);
149 attr != NULL; attr = pcmk__xe_next_same(attr)) {
150
151 attr_name = crm_element_value(attr, PCMK_XA_NAME);
152 if (attr_name) {
153 if (nattrs == 0) {
154 g_strfreev(entry->select_attribute_name);
155 entry->select_attribute_name = NULL;
156 }
157 ++nattrs;
158 entry->select_attribute_name = pcmk__realloc(entry->select_attribute_name,
159 (nattrs + 1) * sizeof(char*));
160 entry->select_attribute_name[nattrs - 1] = strdup(attr_name);
161 entry->select_attribute_name[nattrs] = NULL;
162 }
163 }
164 }
165 }
166
167 if (flags != pcmk__alert_none) {
168 entry->flags = flags;
169 crm_debug("Alert %s receives events: attributes:%s%s%s%s",
170 entry->id,
171 (pcmk_is_set(flags, pcmk__alert_attribute)?
172 (entry->select_attribute_name? "some" : "all") : "none"),
173 (pcmk_is_set(flags, pcmk__alert_fencing)? " fencing" : ""),
174 (pcmk_is_set(flags, pcmk__alert_node)? " nodes" : ""),
175 (pcmk_is_set(flags, pcmk__alert_resource)? " resources" : ""));
176 }
177 }
178
179 /*!
180 * \internal
181 * \brief Unpack an alert or an alert recipient
182 *
183 * \param[in,out] alert Alert or recipient XML
184 * \param[in,out] entry Where to store unpacked values
185 * \param[in,out] max_timeout Max timeout of all alerts and recipients thus far
186 *
187 * \return Standard Pacemaker return code
188 */
189 static int
190 unpack_alert(xmlNode *alert, pcmk__alert_t *entry, guint *max_timeout)
191 {
192 int rc = pcmk_rc_ok;
193
194 get_envvars_from_cib(alert, entry);
195 rc = get_meta_attrs_from_cib(alert, entry, max_timeout);
196 if (rc == pcmk_rc_ok) {
197 unpack_alert_filter(alert, entry);
198 }
199 return rc;
200 }
201
202 /*!
203 * \internal
204 * \brief Unpack a CIB alerts section
205 *
206 * \param[in] alerts XML of alerts section
207 *
208 * \return List of unpacked alert entries
209 *
210 * \note Unlike most unpack functions, this is not used by the scheduler itself,
211 * but is supplied for use by daemons that need to send alerts.
212 */
213 GList *
214 pe_unpack_alerts(const xmlNode *alerts)
215 {
216 xmlNode *alert;
217 pcmk__alert_t *entry;
218 guint max_timeout = 0;
219 GList *alert_list = NULL;
220
|
(1) Event path: |
Condition "alerts == NULL", taking false branch. |
221 if (alerts == NULL) {
222 return alert_list;
223 }
224
|
(2) Event path: |
Condition "alert != NULL", taking true branch. |
225 for (alert = pcmk__xe_first_child(alerts, PCMK_XE_ALERT, NULL, NULL);
226 alert != NULL; alert = pcmk__xe_next_same(alert)) {
227
228 xmlNode *recipient;
229 int recipients = 0;
230 const char *alert_id = pcmk__xe_id(alert);
231 const char *alert_path = crm_element_value(alert, PCMK_XA_PATH);
232
233 /* The schema should enforce this, but to be safe ... */
|
(3) Event path: |
Condition "alert_id == NULL", taking false branch. |
234 if (alert_id == NULL) {
235 pcmk__config_warn("Ignoring invalid alert without " PCMK_XA_ID);
236 crm_log_xml_info(alert, "missing-id");
237 continue;
238 }
|
(4) Event path: |
Condition "alert_path == NULL", taking false branch. |
239 if (alert_path == NULL) {
240 pcmk__config_warn("Ignoring alert %s: No " PCMK_XA_PATH, alert_id);
241 continue;
242 }
243
244 entry = pcmk__alert_new(alert_id, alert_path);
245
|
(5) Event path: |
Condition "unpack_alert(alert, entry, &max_timeout) != pcmk_rc_ok", taking false branch. |
246 if (unpack_alert(alert, entry, &max_timeout) != pcmk_rc_ok) {
247 // Don't allow recipients to override if entire alert is disabled
248 crm_debug("Alert %s is disabled", entry->id);
249 pcmk__free_alert(entry);
250 continue;
251 }
252
|
(6) Event path: |
Condition "entry->tstamp_format == NULL", taking false branch. |
253 if (entry->tstamp_format == NULL) {
254 entry->tstamp_format = strdup(PCMK__ALERT_DEFAULT_TSTAMP_FORMAT);
255 }
256
|
(7) Event path: |
Switch case default. |
|
(8) Event path: |
Condition "trace_cs == NULL", taking true branch. |
|
(9) Event path: |
Condition "crm_is_callsite_active(trace_cs, _level, 0)", taking false branch. |
|
(10) Event path: |
Breaking from switch. |
257 crm_debug("Alert %s: path=%s timeout=%dms tstamp-format='%s' %u vars",
258 entry->id, entry->path, entry->timeout, entry->tstamp_format,
259 (entry->envvars? g_hash_table_size(entry->envvars) : 0));
260
|
(11) Event path: |
Condition "recipient != NULL", taking true branch. |
261 for (recipient = pcmk__xe_first_child(alert, PCMK_XE_RECIPIENT, NULL,
262 NULL);
263 recipient != NULL; recipient = pcmk__xe_next_same(recipient)) {
264
|
(12) Event alloc_arg: |
"pcmk__dup_alert" allocates memory that is stored into "pcmk__dup_alert(entry)->recipient". [details] |
|
(13) Event var_assign: |
Assigning: "recipient_entry->recipient" = "pcmk__dup_alert(entry)->recipient". |
| Also see events: |
[overwrite_var] |
265 pcmk__alert_t *recipient_entry = pcmk__dup_alert(entry);
266
267 recipients++;
|
CID (unavailable; MK=572212c294aa86235787254f0bb0aa6c) (#1 of 1): Resource leak (RESOURCE_LEAK): |
|
(14) Event overwrite_var: |
Overwriting "recipient_entry->recipient" in "recipient_entry->recipient = crm_element_value_copy(recipient, "value")" leaks the storage that "recipient_entry->recipient" points to. |
| Also see events: |
[alloc_arg][var_assign] |
268 recipient_entry->recipient = crm_element_value_copy(recipient,
269 PCMK_XA_VALUE);
270
271 if (unpack_alert(recipient, recipient_entry,
272 &max_timeout) != pcmk_rc_ok) {
273 crm_debug("Alert %s: recipient %s is disabled",
274 entry->id, recipient_entry->id);
275 pcmk__free_alert(recipient_entry);
276 continue;
277 }
278 alert_list = g_list_prepend(alert_list, recipient_entry);
279 crm_debug("Alert %s has recipient %s with value %s and %d envvars",
280 entry->id, pcmk__xe_id(recipient),
281 recipient_entry->recipient,
282 (recipient_entry->envvars?
283 g_hash_table_size(recipient_entry->envvars) : 0));
284 }
285
286 if (recipients == 0) {
287 alert_list = g_list_prepend(alert_list, entry);
288 } else {
289 pcmk__free_alert(entry);
290 }
291 }
292 return alert_list;
293 }
294
295 /*!
296 * \internal
297 * \brief Free an alert list generated by pe_unpack_alerts()
298 *
299 * \param[in,out] alert_list Alert list to free
300 */
301 void
302 pe_free_alert_list(GList *alert_list)
303 {
304 if (alert_list) {
305 g_list_free_full(alert_list, (GDestroyNotify) pcmk__free_alert);
306 }
307 }
308