Line data Source code
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 0 : get_meta_attrs_from_cib(xmlNode *basenode, pcmk__alert_t *entry,
30 : guint *max_timeout)
31 : {
32 0 : GHashTable *config_hash = pcmk__strkey_table(free, free);
33 0 : crm_time_t *now = crm_time_new(NULL);
34 0 : const char *value = NULL;
35 0 : int rc = pcmk_rc_ok;
36 :
37 0 : pe_unpack_nvpairs(basenode, basenode, PCMK_XE_META_ATTRIBUTES, NULL,
38 : config_hash, NULL, FALSE, now, NULL);
39 0 : crm_time_free(now);
40 :
41 0 : value = g_hash_table_lookup(config_hash, PCMK_META_ENABLED);
42 0 : if ((value != NULL) && !crm_is_true(value)) {
43 : // No need to continue unpacking
44 0 : rc = pcmk_rc_disabled;
45 0 : goto done;
46 : }
47 :
48 0 : value = g_hash_table_lookup(config_hash, PCMK_META_TIMEOUT);
49 0 : if (value) {
50 0 : long long timeout_ms = crm_get_msec(value);
51 :
52 0 : entry->timeout = (int) QB_MIN(timeout_ms, INT_MAX);
53 0 : if (entry->timeout <= 0) {
54 0 : if (entry->timeout == 0) {
55 0 : crm_trace("Alert %s uses default timeout of %dmsec",
56 : entry->id, PCMK__ALERT_DEFAULT_TIMEOUT_MS);
57 : } else {
58 0 : 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 0 : entry->timeout = PCMK__ALERT_DEFAULT_TIMEOUT_MS;
64 : } else {
65 0 : crm_trace("Alert %s uses timeout of %dmsec",
66 : entry->id, entry->timeout);
67 : }
68 0 : if (entry->timeout > *max_timeout) {
69 0 : *max_timeout = entry->timeout;
70 : }
71 : }
72 0 : value = g_hash_table_lookup(config_hash, PCMK_META_TIMESTAMP_FORMAT);
73 0 : if (value) {
74 : /* hard to do any checks here as merely anything can
75 : * can be a valid time-format-string
76 : */
77 0 : entry->tstamp_format = strdup(value);
78 0 : crm_trace("Alert %s uses timestamp format '%s'",
79 : entry->id, entry->tstamp_format);
80 : }
81 :
82 0 : done:
83 0 : g_hash_table_destroy(config_hash);
84 0 : return rc;
85 : }
86 :
87 : static void
88 0 : get_envvars_from_cib(xmlNode *basenode, pcmk__alert_t *entry)
89 : {
90 : xmlNode *child;
91 :
92 0 : if ((basenode == NULL) || (entry == NULL)) {
93 0 : return;
94 : }
95 :
96 0 : child = pcmk__xe_first_child(basenode, PCMK_XE_INSTANCE_ATTRIBUTES, NULL,
97 : NULL);
98 0 : if (child == NULL) {
99 0 : return;
100 : }
101 :
102 0 : if (entry->envvars == NULL) {
103 0 : entry->envvars = pcmk__strkey_table(free, free);
104 : }
105 :
106 0 : for (child = pcmk__xe_first_child(child, PCMK_XE_NVPAIR, NULL, NULL);
107 0 : child != NULL; child = pcmk__xe_next_same(child)) {
108 :
109 0 : const char *name = crm_element_value(child, PCMK_XA_NAME);
110 0 : const char *value = crm_element_value(child, PCMK_XA_VALUE);
111 :
112 0 : if (value == NULL) {
113 0 : value = "";
114 : }
115 0 : pcmk__insert_dup(entry->envvars, name, value);
116 0 : crm_trace("Alert %s: added environment variable %s='%s'",
117 : entry->id, name, value);
118 : }
119 : }
120 :
121 : static void
122 0 : unpack_alert_filter(xmlNode *basenode, pcmk__alert_t *entry)
123 : {
124 0 : xmlNode *select = pcmk__xe_first_child(basenode, PCMK_XE_SELECT, NULL,
125 : NULL);
126 0 : xmlNode *event_type = NULL;
127 0 : uint32_t flags = pcmk__alert_none;
128 :
129 0 : for (event_type = pcmk__xe_first_child(select, NULL, NULL, NULL);
130 0 : event_type != NULL; event_type = pcmk__xe_next(event_type)) {
131 :
132 0 : if (pcmk__xe_is(event_type, PCMK_XE_SELECT_FENCING)) {
133 0 : flags |= pcmk__alert_fencing;
134 :
135 0 : } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_NODES)) {
136 0 : flags |= pcmk__alert_node;
137 :
138 0 : } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_RESOURCES)) {
139 0 : flags |= pcmk__alert_resource;
140 :
141 0 : } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_ATTRIBUTES)) {
142 : xmlNode *attr;
143 : const char *attr_name;
144 0 : int nattrs = 0;
145 :
146 0 : flags |= pcmk__alert_attribute;
147 0 : for (attr = pcmk__xe_first_child(event_type, PCMK_XE_ATTRIBUTE,
148 : NULL, NULL);
149 0 : attr != NULL; attr = pcmk__xe_next_same(attr)) {
150 :
151 0 : attr_name = crm_element_value(attr, PCMK_XA_NAME);
152 0 : if (attr_name) {
153 0 : if (nattrs == 0) {
154 0 : g_strfreev(entry->select_attribute_name);
155 0 : entry->select_attribute_name = NULL;
156 : }
157 0 : ++nattrs;
158 0 : entry->select_attribute_name = pcmk__realloc(entry->select_attribute_name,
159 0 : (nattrs + 1) * sizeof(char*));
160 0 : entry->select_attribute_name[nattrs - 1] = strdup(attr_name);
161 0 : entry->select_attribute_name[nattrs] = NULL;
162 : }
163 : }
164 : }
165 : }
166 :
167 0 : if (flags != pcmk__alert_none) {
168 0 : entry->flags = flags;
169 0 : 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 0 : }
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 0 : unpack_alert(xmlNode *alert, pcmk__alert_t *entry, guint *max_timeout)
191 : {
192 0 : int rc = pcmk_rc_ok;
193 :
194 0 : get_envvars_from_cib(alert, entry);
195 0 : rc = get_meta_attrs_from_cib(alert, entry, max_timeout);
196 0 : if (rc == pcmk_rc_ok) {
197 0 : unpack_alert_filter(alert, entry);
198 : }
199 0 : 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 0 : pe_unpack_alerts(const xmlNode *alerts)
215 : {
216 : xmlNode *alert;
217 : pcmk__alert_t *entry;
218 0 : guint max_timeout = 0;
219 0 : GList *alert_list = NULL;
220 :
221 0 : if (alerts == NULL) {
222 0 : return alert_list;
223 : }
224 :
225 0 : for (alert = pcmk__xe_first_child(alerts, PCMK_XE_ALERT, NULL, NULL);
226 0 : alert != NULL; alert = pcmk__xe_next_same(alert)) {
227 :
228 : xmlNode *recipient;
229 0 : int recipients = 0;
230 0 : const char *alert_id = pcmk__xe_id(alert);
231 0 : const char *alert_path = crm_element_value(alert, PCMK_XA_PATH);
232 :
233 : /* The schema should enforce this, but to be safe ... */
234 0 : if (alert_id == NULL) {
235 0 : pcmk__config_warn("Ignoring invalid alert without " PCMK_XA_ID);
236 0 : crm_log_xml_info(alert, "missing-id");
237 0 : continue;
238 : }
239 0 : if (alert_path == NULL) {
240 0 : pcmk__config_warn("Ignoring alert %s: No " PCMK_XA_PATH, alert_id);
241 0 : continue;
242 : }
243 :
244 0 : entry = pcmk__alert_new(alert_id, alert_path);
245 :
246 0 : if (unpack_alert(alert, entry, &max_timeout) != pcmk_rc_ok) {
247 : // Don't allow recipients to override if entire alert is disabled
248 0 : crm_debug("Alert %s is disabled", entry->id);
249 0 : pcmk__free_alert(entry);
250 0 : continue;
251 : }
252 :
253 0 : if (entry->tstamp_format == NULL) {
254 0 : entry->tstamp_format = strdup(PCMK__ALERT_DEFAULT_TSTAMP_FORMAT);
255 : }
256 :
257 0 : 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 :
261 0 : for (recipient = pcmk__xe_first_child(alert, PCMK_XE_RECIPIENT, NULL,
262 : NULL);
263 0 : recipient != NULL; recipient = pcmk__xe_next_same(recipient)) {
264 :
265 0 : pcmk__alert_t *recipient_entry = pcmk__dup_alert(entry);
266 :
267 0 : recipients++;
268 0 : recipient_entry->recipient = crm_element_value_copy(recipient,
269 : PCMK_XA_VALUE);
270 :
271 0 : if (unpack_alert(recipient, recipient_entry,
272 : &max_timeout) != pcmk_rc_ok) {
273 0 : crm_debug("Alert %s: recipient %s is disabled",
274 : entry->id, recipient_entry->id);
275 0 : pcmk__free_alert(recipient_entry);
276 0 : continue;
277 : }
278 0 : alert_list = g_list_prepend(alert_list, recipient_entry);
279 0 : 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 0 : if (recipients == 0) {
287 0 : alert_list = g_list_prepend(alert_list, entry);
288 : } else {
289 0 : pcmk__free_alert(entry);
290 : }
291 : }
292 0 : 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 0 : pe_free_alert_list(GList *alert_list)
303 : {
304 0 : if (alert_list) {
305 0 : g_list_free_full(alert_list, (GDestroyNotify) pcmk__free_alert);
306 : }
307 0 : }
|