Line data Source code
1 : /*
2 : * Copyright 2004-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 General Public License version 2
7 : * or later (GPLv2+) WITHOUT ANY WARRANTY.
8 : */
9 :
10 : #include <crm_internal.h>
11 : #include <crm/common/xml.h>
12 :
13 : #include <crm/pengine/internal.h>
14 : #include <pacemaker-internal.h>
15 :
16 : #include "pe_status_private.h"
17 :
18 : typedef struct notify_entry_s {
19 : const pcmk_resource_t *rsc;
20 : const pcmk_node_t *node;
21 : } notify_entry_t;
22 :
23 : /*!
24 : * \internal
25 : * \brief Compare two notification entries
26 : *
27 : * Compare two notification entries, where the one with the alphabetically first
28 : * resource name (or if equal, node name) sorts as first, with NULL sorting as
29 : * less than non-NULL.
30 : *
31 : * \param[in] a First notification entry to compare
32 : * \param[in] b Second notification entry to compare
33 : *
34 : * \return -1 if \p a sorts before \p b, 0 if they are equal, otherwise 1
35 : */
36 : static gint
37 0 : compare_notify_entries(gconstpointer a, gconstpointer b)
38 : {
39 : int tmp;
40 0 : const notify_entry_t *entry_a = a;
41 0 : const notify_entry_t *entry_b = b;
42 :
43 : // NULL a or b is not actually possible
44 0 : if ((entry_a == NULL) && (entry_b == NULL)) {
45 0 : return 0;
46 : }
47 0 : if (entry_a == NULL) {
48 0 : return 1;
49 : }
50 0 : if (entry_b == NULL) {
51 0 : return -1;
52 : }
53 :
54 : // NULL resources sort first
55 0 : if ((entry_a->rsc == NULL) && (entry_b->rsc == NULL)) {
56 0 : return 0;
57 : }
58 0 : if (entry_a->rsc == NULL) {
59 0 : return 1;
60 : }
61 0 : if (entry_b->rsc == NULL) {
62 0 : return -1;
63 : }
64 :
65 : // Compare resource names
66 0 : tmp = strcmp(entry_a->rsc->id, entry_b->rsc->id);
67 0 : if (tmp != 0) {
68 0 : return tmp;
69 : }
70 :
71 : // Otherwise NULL nodes sort first
72 0 : if ((entry_a->node == NULL) && (entry_b->node == NULL)) {
73 0 : return 0;
74 : }
75 0 : if (entry_a->node == NULL) {
76 0 : return 1;
77 : }
78 0 : if (entry_b->node == NULL) {
79 0 : return -1;
80 : }
81 :
82 : // Finally, compare node names
83 0 : return strcmp(entry_a->node->details->id, entry_b->node->details->id);
84 : }
85 :
86 : /*!
87 : * \internal
88 : * \brief Duplicate a notification entry
89 : *
90 : * \param[in] entry Entry to duplicate
91 : *
92 : * \return Newly allocated duplicate of \p entry
93 : * \note It is the caller's responsibility to free the return value.
94 : */
95 : static notify_entry_t *
96 0 : dup_notify_entry(const notify_entry_t *entry)
97 : {
98 0 : notify_entry_t *dup = pcmk__assert_alloc(1, sizeof(notify_entry_t));
99 :
100 0 : dup->rsc = entry->rsc;
101 0 : dup->node = entry->node;
102 0 : return dup;
103 : }
104 :
105 : /*!
106 : * \internal
107 : * \brief Given a list of nodes, create strings with node names
108 : *
109 : * \param[in] list List of nodes (as pcmk_node_t *)
110 : * \param[out] all_node_names If not NULL, will be set to space-separated list
111 : * of the names of all nodes in \p list
112 : * \param[out] host_node_names Same as \p all_node_names, except active
113 : * guest nodes will list the name of their host
114 : *
115 : * \note The caller is responsible for freeing the output argument values using
116 : * \p g_string_free().
117 : */
118 : static void
119 0 : get_node_names(const GList *list, GString **all_node_names,
120 : GString **host_node_names)
121 : {
122 0 : if (all_node_names != NULL) {
123 0 : *all_node_names = NULL;
124 : }
125 0 : if (host_node_names != NULL) {
126 0 : *host_node_names = NULL;
127 : }
128 :
129 0 : for (const GList *iter = list; iter != NULL; iter = iter->next) {
130 0 : const pcmk_node_t *node = (const pcmk_node_t *) iter->data;
131 :
132 0 : if (node->details->uname == NULL) {
133 0 : continue;
134 : }
135 :
136 : // Always add to list of all node names
137 0 : if (all_node_names != NULL) {
138 0 : pcmk__add_word(all_node_names, 1024, node->details->uname);
139 : }
140 :
141 : // Add to host node name list if appropriate
142 0 : if (host_node_names != NULL) {
143 0 : if (pcmk__is_guest_or_bundle_node(node)
144 0 : && (node->details->remote_rsc->container->running_on != NULL)) {
145 0 : node = pcmk__current_node(node->details->remote_rsc->container);
146 0 : if (node->details->uname == NULL) {
147 0 : continue;
148 : }
149 : }
150 0 : pcmk__add_word(host_node_names, 1024, node->details->uname);
151 : }
152 : }
153 :
154 0 : if ((all_node_names != NULL) && (*all_node_names == NULL)) {
155 0 : *all_node_names = g_string_new(" ");
156 : }
157 0 : if ((host_node_names != NULL) && (*host_node_names == NULL)) {
158 0 : *host_node_names = g_string_new(" ");
159 : }
160 0 : }
161 :
162 : /*!
163 : * \internal
164 : * \brief Create strings of instance and node names from notification entries
165 : *
166 : * \param[in,out] list List of notification entries (will be sorted here)
167 : * \param[out] rsc_names If not NULL, will be set to space-separated list
168 : * of clone instances from \p list
169 : * \param[out] node_names If not NULL, will be set to space-separated list
170 : * of node names from \p list
171 : *
172 : * \return (Possibly new) head of sorted \p list
173 : * \note The caller is responsible for freeing the output argument values using
174 : * \p g_list_free_full() and \p g_string_free().
175 : */
176 : static GList *
177 0 : notify_entries_to_strings(GList *list, GString **rsc_names,
178 : GString **node_names)
179 : {
180 0 : const char *last_rsc_id = NULL;
181 :
182 : // Initialize output lists to NULL
183 0 : if (rsc_names != NULL) {
184 0 : *rsc_names = NULL;
185 : }
186 0 : if (node_names != NULL) {
187 0 : *node_names = NULL;
188 : }
189 :
190 : // Sort input list for user-friendliness (and ease of filtering duplicates)
191 0 : list = g_list_sort(list, compare_notify_entries);
192 :
193 0 : for (GList *gIter = list; gIter != NULL; gIter = gIter->next) {
194 0 : notify_entry_t *entry = (notify_entry_t *) gIter->data;
195 :
196 : // Entry must have a resource (with ID)
197 0 : CRM_LOG_ASSERT((entry != NULL) && (entry->rsc != NULL)
198 : && (entry->rsc->id != NULL));
199 0 : if ((entry == NULL) || (entry->rsc == NULL)
200 0 : || (entry->rsc->id == NULL)) {
201 0 : continue;
202 : }
203 :
204 : // Entry must have a node unless listing inactive resources
205 0 : CRM_LOG_ASSERT((node_names == NULL) || (entry->node != NULL));
206 0 : if ((node_names != NULL) && (entry->node == NULL)) {
207 0 : continue;
208 : }
209 :
210 : // Don't add duplicates of a particular clone instance
211 0 : if (pcmk__str_eq(entry->rsc->id, last_rsc_id, pcmk__str_none)) {
212 0 : continue;
213 : }
214 0 : last_rsc_id = entry->rsc->id;
215 :
216 0 : if (rsc_names != NULL) {
217 0 : pcmk__add_word(rsc_names, 1024, entry->rsc->id);
218 : }
219 0 : if ((node_names != NULL) && (entry->node->details->uname != NULL)) {
220 0 : pcmk__add_word(node_names, 1024, entry->node->details->uname);
221 : }
222 : }
223 :
224 : // If there are no entries, return "empty" lists
225 0 : if ((rsc_names != NULL) && (*rsc_names == NULL)) {
226 0 : *rsc_names = g_string_new(" ");
227 : }
228 0 : if ((node_names != NULL) && (*node_names == NULL)) {
229 0 : *node_names = g_string_new(" ");
230 : }
231 :
232 0 : return list;
233 : }
234 :
235 : /*!
236 : * \internal
237 : * \brief Copy a meta-attribute into a notify action
238 : *
239 : * \param[in] key Name of meta-attribute to copy
240 : * \param[in] value Value of meta-attribute to copy
241 : * \param[in,out] user_data Notify action to copy into
242 : */
243 : static void
244 0 : copy_meta_to_notify(gpointer key, gpointer value, gpointer user_data)
245 : {
246 0 : pcmk_action_t *notify = (pcmk_action_t *) user_data;
247 :
248 : /* Any existing meta-attributes (for example, the action timeout) are for
249 : * the notify action itself, so don't override those.
250 : */
251 0 : if (g_hash_table_lookup(notify->meta, (const char *) key) != NULL) {
252 0 : return;
253 : }
254 :
255 0 : pcmk__insert_dup(notify->meta, (const char *) key, (const char *) value);
256 : }
257 :
258 : static void
259 0 : add_notify_data_to_action_meta(const notify_data_t *n_data,
260 : pcmk_action_t *action)
261 : {
262 0 : for (const GSList *item = n_data->keys; item; item = item->next) {
263 0 : const pcmk_nvpair_t *nvpair = (const pcmk_nvpair_t *) item->data;
264 :
265 0 : pcmk__insert_meta(action, nvpair->name, nvpair->value);
266 : }
267 0 : }
268 :
269 : /*!
270 : * \internal
271 : * \brief Create a new notify pseudo-action for a clone resource
272 : *
273 : * \param[in,out] rsc Clone resource that notification is for
274 : * \param[in] action Action to use in notify action key
275 : * \param[in] notif_action PCMK_ACTION_NOTIFY or PCMK_ACTION_NOTIFIED
276 : * \param[in] notif_type "pre", "post", "confirmed-pre", "confirmed-post"
277 : *
278 : * \return Newly created notify pseudo-action
279 : */
280 : static pcmk_action_t *
281 0 : new_notify_pseudo_action(pcmk_resource_t *rsc, const pcmk_action_t *action,
282 : const char *notif_action, const char *notif_type)
283 : {
284 0 : pcmk_action_t *notify = NULL;
285 :
286 0 : notify = custom_action(rsc,
287 0 : pcmk__notify_key(rsc->id, notif_type, action->task),
288 : notif_action, NULL,
289 0 : pcmk_is_set(action->flags, pcmk_action_optional),
290 : rsc->cluster);
291 0 : pcmk__set_action_flags(notify, pcmk_action_pseudo);
292 0 : pcmk__insert_meta(notify, "notify_key_type", notif_type);
293 0 : pcmk__insert_meta(notify, "notify_key_operation", action->task);
294 0 : return notify;
295 : }
296 :
297 : /*!
298 : * \internal
299 : * \brief Create a new notify action for a clone instance
300 : *
301 : * \param[in,out] rsc Clone instance that notification is for
302 : * \param[in] node Node that notification is for
303 : * \param[in,out] op Action that notification is for
304 : * \param[in,out] notify_done Parent pseudo-action for notifications complete
305 : * \param[in] n_data Notification values to add to action meta-data
306 : *
307 : * \return Newly created notify action
308 : */
309 : static pcmk_action_t *
310 0 : new_notify_action(pcmk_resource_t *rsc, const pcmk_node_t *node,
311 : pcmk_action_t *op, pcmk_action_t *notify_done,
312 : const notify_data_t *n_data)
313 : {
314 0 : char *key = NULL;
315 0 : pcmk_action_t *notify_action = NULL;
316 0 : const char *value = NULL;
317 0 : const char *task = NULL;
318 0 : const char *skip_reason = NULL;
319 :
320 0 : CRM_CHECK((rsc != NULL) && (node != NULL), return NULL);
321 :
322 : // Ensure we have all the info we need
323 0 : if (op == NULL) {
324 0 : skip_reason = "no action";
325 0 : } else if (notify_done == NULL) {
326 0 : skip_reason = "no parent notification";
327 0 : } else if (!node->details->online) {
328 0 : skip_reason = "node offline";
329 0 : } else if (!pcmk_is_set(op->flags, pcmk_action_runnable)) {
330 0 : skip_reason = "original action not runnable";
331 : }
332 0 : if (skip_reason != NULL) {
333 0 : pcmk__rsc_trace(rsc, "Skipping notify action for %s on %s: %s",
334 : rsc->id, pcmk__node_name(node), skip_reason);
335 0 : return NULL;
336 : }
337 :
338 0 : value = g_hash_table_lookup(op->meta, "notify_type"); // "pre" or "post"
339 0 : task = g_hash_table_lookup(op->meta, "notify_operation"); // original action
340 :
341 0 : pcmk__rsc_trace(rsc, "Creating notify action for %s on %s (%s-%s)",
342 : rsc->id, pcmk__node_name(node), value, task);
343 :
344 : // Create the notify action
345 0 : key = pcmk__notify_key(rsc->id, value, task);
346 0 : notify_action = custom_action(rsc, key, op->task, node,
347 0 : pcmk_is_set(op->flags, pcmk_action_optional),
348 : rsc->cluster);
349 :
350 : // Add meta-data to notify action
351 0 : g_hash_table_foreach(op->meta, copy_meta_to_notify, notify_action);
352 0 : add_notify_data_to_action_meta(n_data, notify_action);
353 :
354 : // Order notify after original action and before parent notification
355 0 : order_actions(op, notify_action, pcmk__ar_ordered);
356 0 : order_actions(notify_action, notify_done, pcmk__ar_ordered);
357 0 : return notify_action;
358 : }
359 :
360 : /*!
361 : * \internal
362 : * \brief Create a new "post-" notify action for a clone instance
363 : *
364 : * \param[in,out] rsc Clone instance that notification is for
365 : * \param[in] node Node that notification is for
366 : * \param[in,out] n_data Notification values to add to action meta-data
367 : */
368 : static void
369 0 : new_post_notify_action(pcmk_resource_t *rsc, const pcmk_node_t *node,
370 : notify_data_t *n_data)
371 : {
372 0 : pcmk_action_t *notify = NULL;
373 :
374 0 : CRM_ASSERT(n_data != NULL);
375 :
376 : // Create the "post-" notify action for specified instance
377 0 : notify = new_notify_action(rsc, node, n_data->post, n_data->post_done,
378 : n_data);
379 0 : if (notify != NULL) {
380 0 : notify->priority = PCMK_SCORE_INFINITY;
381 : }
382 :
383 : // Order recurring monitors after all "post-" notifications complete
384 0 : if (n_data->post_done == NULL) {
385 0 : return;
386 : }
387 0 : for (GList *iter = rsc->actions; iter != NULL; iter = iter->next) {
388 0 : pcmk_action_t *mon = (pcmk_action_t *) iter->data;
389 0 : const char *interval_ms_s = NULL;
390 :
391 0 : interval_ms_s = g_hash_table_lookup(mon->meta, PCMK_META_INTERVAL);
392 0 : if (pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches)
393 0 : || pcmk__str_eq(mon->task, PCMK_ACTION_CANCEL, pcmk__str_none)) {
394 0 : continue; // Not a recurring monitor
395 : }
396 0 : order_actions(n_data->post_done, mon, pcmk__ar_ordered);
397 : }
398 : }
399 :
400 : /*!
401 : * \internal
402 : * \brief Create and order notification pseudo-actions for a clone action
403 : *
404 : * In addition to the actual notify actions needed for each clone instance,
405 : * clone notifications also require pseudo-actions to provide ordering points
406 : * in the notification process. This creates the notification data, along with
407 : * appropriate pseudo-actions and their orderings.
408 : *
409 : * For example, the ordering sequence for starting a clone is:
410 : *
411 : * "pre-" notify pseudo-action for clone
412 : * -> "pre-" notify actions for each clone instance
413 : * -> "pre-" notifications complete pseudo-action for clone
414 : * -> start actions for each clone instance
415 : * -> "started" pseudo-action for clone
416 : * -> "post-" notify pseudo-action for clone
417 : * -> "post-" notify actions for each clone instance
418 : * -> "post-" notifications complete pseudo-action for clone
419 : *
420 : * \param[in,out] rsc Clone that notifications are for
421 : * \param[in] task Name of action that notifications are for
422 : * \param[in,out] action If not NULL, create a "pre-" pseudo-action ordered
423 : * before a "pre-" complete pseudo-action, ordered
424 : * before this action
425 : * \param[in,out] complete If not NULL, create a "post-" pseudo-action ordered
426 : * after this action, and a "post-" complete
427 : * pseudo-action ordered after that
428 : *
429 : * \return Newly created notification data
430 : */
431 : notify_data_t *
432 0 : pe__action_notif_pseudo_ops(pcmk_resource_t *rsc, const char *task,
433 : pcmk_action_t *action, pcmk_action_t *complete)
434 : {
435 0 : notify_data_t *n_data = NULL;
436 :
437 0 : if (!pcmk_is_set(rsc->flags, pcmk_rsc_notify)) {
438 0 : return NULL;
439 : }
440 :
441 0 : n_data = pcmk__assert_alloc(1, sizeof(notify_data_t));
442 :
443 0 : n_data->action = task;
444 :
445 0 : if (action != NULL) { // Need "pre-" pseudo-actions
446 :
447 : // Create "pre-" notify pseudo-action for clone
448 0 : n_data->pre = new_notify_pseudo_action(rsc, action, PCMK_ACTION_NOTIFY,
449 : "pre");
450 0 : pcmk__set_action_flags(n_data->pre, pcmk_action_runnable);
451 0 : pcmk__insert_meta(n_data->pre, "notify_type", "pre");
452 0 : pcmk__insert_meta(n_data->pre, "notify_operation", n_data->action);
453 :
454 : // Create "pre-" notifications complete pseudo-action for clone
455 0 : n_data->pre_done = new_notify_pseudo_action(rsc, action,
456 : PCMK_ACTION_NOTIFIED,
457 : "confirmed-pre");
458 0 : pcmk__set_action_flags(n_data->pre_done, pcmk_action_runnable);
459 0 : pcmk__insert_meta(n_data->pre_done, "notify_type", "pre");
460 0 : pcmk__insert_meta(n_data->pre_done, "notify_operation", n_data->action);
461 :
462 : // Order "pre-" -> "pre-" complete -> original action
463 0 : order_actions(n_data->pre, n_data->pre_done, pcmk__ar_ordered);
464 0 : order_actions(n_data->pre_done, action, pcmk__ar_ordered);
465 : }
466 :
467 0 : if (complete != NULL) { // Need "post-" pseudo-actions
468 :
469 : // Create "post-" notify pseudo-action for clone
470 0 : n_data->post = new_notify_pseudo_action(rsc, complete,
471 : PCMK_ACTION_NOTIFY, "post");
472 0 : n_data->post->priority = PCMK_SCORE_INFINITY;
473 0 : if (pcmk_is_set(complete->flags, pcmk_action_runnable)) {
474 0 : pcmk__set_action_flags(n_data->post, pcmk_action_runnable);
475 : } else {
476 0 : pcmk__clear_action_flags(n_data->post, pcmk_action_runnable);
477 : }
478 0 : pcmk__insert_meta(n_data->post, "notify_type", "post");
479 0 : pcmk__insert_meta(n_data->post, "notify_operation", n_data->action);
480 :
481 : // Create "post-" notifications complete pseudo-action for clone
482 0 : n_data->post_done = new_notify_pseudo_action(rsc, complete,
483 : PCMK_ACTION_NOTIFIED,
484 : "confirmed-post");
485 0 : n_data->post_done->priority = PCMK_SCORE_INFINITY;
486 0 : if (pcmk_is_set(complete->flags, pcmk_action_runnable)) {
487 0 : pcmk__set_action_flags(n_data->post_done, pcmk_action_runnable);
488 : } else {
489 0 : pcmk__clear_action_flags(n_data->post_done, pcmk_action_runnable);
490 : }
491 0 : pcmk__insert_meta(n_data->post_done, "notify_type", "post");
492 0 : pcmk__insert_meta(n_data->post_done,
493 : "notify_operation", n_data->action);
494 :
495 : // Order original action complete -> "post-" -> "post-" complete
496 0 : order_actions(complete, n_data->post, pcmk__ar_first_implies_then);
497 0 : order_actions(n_data->post, n_data->post_done,
498 : pcmk__ar_first_implies_then);
499 : }
500 :
501 : // If we created both, order "pre-" complete -> "post-"
502 0 : if ((action != NULL) && (complete != NULL)) {
503 0 : order_actions(n_data->pre_done, n_data->post, pcmk__ar_ordered);
504 : }
505 0 : return n_data;
506 : }
507 :
508 : /*!
509 : * \internal
510 : * \brief Create a new notification entry
511 : *
512 : * \param[in] rsc Resource for notification
513 : * \param[in] node Node for notification
514 : *
515 : * \return Newly allocated notification entry
516 : * \note The caller is responsible for freeing the return value.
517 : */
518 : static notify_entry_t *
519 0 : new_notify_entry(const pcmk_resource_t *rsc, const pcmk_node_t *node)
520 : {
521 0 : notify_entry_t *entry = pcmk__assert_alloc(1, sizeof(notify_entry_t));
522 :
523 0 : entry->rsc = rsc;
524 0 : entry->node = node;
525 0 : return entry;
526 : }
527 :
528 : /*!
529 : * \internal
530 : * \brief Add notification data for resource state and optionally actions
531 : *
532 : * \param[in] rsc Clone or clone instance being notified
533 : * \param[in] activity Whether to add notification entries for actions
534 : * \param[in,out] n_data Notification data for clone
535 : */
536 : static void
537 0 : collect_resource_data(const pcmk_resource_t *rsc, bool activity,
538 : notify_data_t *n_data)
539 : {
540 0 : const GList *iter = NULL;
541 0 : notify_entry_t *entry = NULL;
542 0 : const pcmk_node_t *node = NULL;
543 :
544 0 : if (n_data == NULL) {
545 0 : return;
546 : }
547 :
548 0 : if (n_data->allowed_nodes == NULL) {
549 0 : n_data->allowed_nodes = rsc->allowed_nodes;
550 : }
551 :
552 : // If this is a clone, call recursively for each instance
553 0 : if (rsc->children != NULL) {
554 0 : for (iter = rsc->children; iter != NULL; iter = iter->next) {
555 0 : const pcmk_resource_t *child = (const pcmk_resource_t *) iter->data;
556 :
557 0 : collect_resource_data(child, activity, n_data);
558 : }
559 0 : return;
560 : }
561 :
562 : // This is a notification for a single clone instance
563 :
564 0 : if (rsc->running_on != NULL) {
565 0 : node = rsc->running_on->data; // First is sufficient
566 : }
567 0 : entry = new_notify_entry(rsc, node);
568 :
569 : // Add notification indicating the resource state
570 0 : switch (rsc->role) {
571 0 : case pcmk_role_stopped:
572 0 : n_data->inactive = g_list_prepend(n_data->inactive, entry);
573 0 : break;
574 :
575 0 : case pcmk_role_started:
576 0 : n_data->active = g_list_prepend(n_data->active, entry);
577 0 : break;
578 :
579 0 : case pcmk_role_unpromoted:
580 0 : n_data->unpromoted = g_list_prepend(n_data->unpromoted, entry);
581 0 : n_data->active = g_list_prepend(n_data->active,
582 0 : dup_notify_entry(entry));
583 0 : break;
584 :
585 0 : case pcmk_role_promoted:
586 0 : n_data->promoted = g_list_prepend(n_data->promoted, entry);
587 0 : n_data->active = g_list_prepend(n_data->active,
588 0 : dup_notify_entry(entry));
589 0 : break;
590 :
591 0 : default:
592 0 : pcmk__sched_err("Resource %s role on %s (%s) is not supported for "
593 : "notifications (bug?)",
594 : rsc->id, pcmk__node_name(node),
595 : pcmk_role_text(rsc->role));
596 0 : free(entry);
597 0 : break;
598 : }
599 :
600 0 : if (!activity) {
601 0 : return;
602 : }
603 :
604 : // Add notification entries for each of the resource's actions
605 0 : for (iter = rsc->actions; iter != NULL; iter = iter->next) {
606 0 : const pcmk_action_t *op = (const pcmk_action_t *) iter->data;
607 :
608 0 : if (!pcmk_is_set(op->flags, pcmk_action_optional)
609 0 : && (op->node != NULL)) {
610 0 : enum action_tasks task = pcmk_parse_action(op->task);
611 :
612 0 : if ((task == pcmk_action_stop) && op->node->details->unclean) {
613 : // Create anyway (additional noise if node can't be fenced)
614 0 : } else if (!pcmk_is_set(op->flags, pcmk_action_runnable)) {
615 0 : continue;
616 : }
617 :
618 0 : entry = new_notify_entry(rsc, op->node);
619 :
620 0 : switch (task) {
621 0 : case pcmk_action_start:
622 0 : n_data->start = g_list_prepend(n_data->start, entry);
623 0 : break;
624 0 : case pcmk_action_stop:
625 0 : n_data->stop = g_list_prepend(n_data->stop, entry);
626 0 : break;
627 0 : case pcmk_action_promote:
628 0 : n_data->promote = g_list_prepend(n_data->promote, entry);
629 0 : break;
630 0 : case pcmk_action_demote:
631 0 : n_data->demote = g_list_prepend(n_data->demote, entry);
632 0 : break;
633 0 : default:
634 0 : free(entry);
635 0 : break;
636 : }
637 : }
638 : }
639 : }
640 :
641 : // For (char *) value
642 : #define add_notify_env(n_data, key, value) do { \
643 : n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, value); \
644 : } while (0)
645 :
646 : // For (GString *) value
647 : #define add_notify_env_gs(n_data, key, value) do { \
648 : n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, \
649 : (const char *) value->str); \
650 : } while (0)
651 :
652 : // For (GString *) value
653 : #define add_notify_env_free_gs(n_data, key, value) do { \
654 : n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, \
655 : (const char *) value->str); \
656 : g_string_free(value, TRUE); value = NULL; \
657 : } while (0)
658 :
659 : /*!
660 : * \internal
661 : * \brief Create notification name/value pairs from structured data
662 : *
663 : * \param[in] rsc Resource that notification is for
664 : * \param[in,out] n_data Notification data
665 : */
666 : static void
667 0 : add_notif_keys(const pcmk_resource_t *rsc, notify_data_t *n_data)
668 : {
669 0 : bool required = false; // Whether to make notify actions required
670 0 : GString *rsc_list = NULL;
671 0 : GString *node_list = NULL;
672 0 : GString *metal_list = NULL;
673 0 : const char *source = NULL;
674 0 : GList *nodes = NULL;
675 :
676 0 : n_data->stop = notify_entries_to_strings(n_data->stop,
677 : &rsc_list, &node_list);
678 0 : if ((strcmp(" ", (const char *) rsc_list->str) != 0)
679 0 : && pcmk__str_eq(n_data->action, PCMK_ACTION_STOP, pcmk__str_none)) {
680 0 : required = true;
681 : }
682 0 : add_notify_env_free_gs(n_data, "notify_stop_resource", rsc_list);
683 0 : add_notify_env_free_gs(n_data, "notify_stop_uname", node_list);
684 :
685 0 : if ((n_data->start != NULL)
686 0 : && pcmk__str_eq(n_data->action, PCMK_ACTION_START, pcmk__str_none)) {
687 0 : required = true;
688 : }
689 0 : n_data->start = notify_entries_to_strings(n_data->start,
690 : &rsc_list, &node_list);
691 0 : add_notify_env_free_gs(n_data, "notify_start_resource", rsc_list);
692 0 : add_notify_env_free_gs(n_data, "notify_start_uname", node_list);
693 :
694 0 : if ((n_data->demote != NULL)
695 0 : && pcmk__str_eq(n_data->action, PCMK_ACTION_DEMOTE, pcmk__str_none)) {
696 0 : required = true;
697 : }
698 0 : n_data->demote = notify_entries_to_strings(n_data->demote,
699 : &rsc_list, &node_list);
700 0 : add_notify_env_free_gs(n_data, "notify_demote_resource", rsc_list);
701 0 : add_notify_env_free_gs(n_data, "notify_demote_uname", node_list);
702 :
703 0 : if ((n_data->promote != NULL)
704 0 : && pcmk__str_eq(n_data->action, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
705 0 : required = true;
706 : }
707 0 : n_data->promote = notify_entries_to_strings(n_data->promote,
708 : &rsc_list, &node_list);
709 0 : add_notify_env_free_gs(n_data, "notify_promote_resource", rsc_list);
710 0 : add_notify_env_free_gs(n_data, "notify_promote_uname", node_list);
711 :
712 0 : n_data->active = notify_entries_to_strings(n_data->active,
713 : &rsc_list, &node_list);
714 0 : add_notify_env_free_gs(n_data, "notify_active_resource", rsc_list);
715 0 : add_notify_env_free_gs(n_data, "notify_active_uname", node_list);
716 :
717 0 : n_data->unpromoted = notify_entries_to_strings(n_data->unpromoted,
718 : &rsc_list, &node_list);
719 0 : add_notify_env_gs(n_data, "notify_unpromoted_resource", rsc_list);
720 0 : add_notify_env_gs(n_data, "notify_unpromoted_uname", node_list);
721 :
722 : // Deprecated: kept for backward compatibility with older resource agents
723 0 : add_notify_env_free_gs(n_data, "notify_slave_resource", rsc_list);
724 0 : add_notify_env_free_gs(n_data, "notify_slave_uname", node_list);
725 :
726 0 : n_data->promoted = notify_entries_to_strings(n_data->promoted,
727 : &rsc_list, &node_list);
728 0 : add_notify_env_gs(n_data, "notify_promoted_resource", rsc_list);
729 0 : add_notify_env_gs(n_data, "notify_promoted_uname", node_list);
730 :
731 : // Deprecated: kept for backward compatibility with older resource agents
732 0 : add_notify_env_free_gs(n_data, "notify_master_resource", rsc_list);
733 0 : add_notify_env_free_gs(n_data, "notify_master_uname", node_list);
734 :
735 0 : n_data->inactive = notify_entries_to_strings(n_data->inactive,
736 : &rsc_list, NULL);
737 0 : add_notify_env_free_gs(n_data, "notify_inactive_resource", rsc_list);
738 :
739 0 : nodes = g_hash_table_get_values(n_data->allowed_nodes);
740 0 : if (!pcmk__is_daemon) {
741 : /* For display purposes, sort the node list, for consistent
742 : * regression test output (while avoiding the performance hit
743 : * for the live cluster).
744 : */
745 0 : nodes = g_list_sort(nodes, pe__cmp_node_name);
746 : }
747 0 : get_node_names(nodes, &node_list, NULL);
748 0 : add_notify_env_free_gs(n_data, "notify_available_uname", node_list);
749 0 : g_list_free(nodes);
750 :
751 0 : source = g_hash_table_lookup(rsc->meta,
752 : PCMK_META_CONTAINER_ATTRIBUTE_TARGET);
753 0 : if (pcmk__str_eq(PCMK_VALUE_HOST, source, pcmk__str_none)) {
754 0 : get_node_names(rsc->cluster->nodes, &node_list, &metal_list);
755 0 : add_notify_env_free_gs(n_data, "notify_all_hosts", metal_list);
756 : } else {
757 0 : get_node_names(rsc->cluster->nodes, &node_list, NULL);
758 : }
759 0 : add_notify_env_free_gs(n_data, "notify_all_uname", node_list);
760 :
761 0 : if (required && (n_data->pre != NULL)) {
762 0 : pcmk__clear_action_flags(n_data->pre, pcmk_action_optional);
763 0 : pcmk__clear_action_flags(n_data->pre_done, pcmk_action_optional);
764 : }
765 :
766 0 : if (required && (n_data->post != NULL)) {
767 0 : pcmk__clear_action_flags(n_data->post, pcmk_action_optional);
768 0 : pcmk__clear_action_flags(n_data->post_done, pcmk_action_optional);
769 : }
770 0 : }
771 :
772 : /*
773 : * \internal
774 : * \brief Find any remote connection start relevant to an action
775 : *
776 : * \param[in] action Action to check
777 : *
778 : * \return If action is behind a remote connection, connection's start
779 : */
780 : static pcmk_action_t *
781 0 : find_remote_start(pcmk_action_t *action)
782 : {
783 0 : if ((action != NULL) && (action->node != NULL)) {
784 0 : pcmk_resource_t *remote_rsc = action->node->details->remote_rsc;
785 :
786 0 : if (remote_rsc != NULL) {
787 0 : return find_first_action(remote_rsc->actions, NULL,
788 : PCMK_ACTION_START,
789 : NULL);
790 : }
791 : }
792 0 : return NULL;
793 : }
794 :
795 : /*!
796 : * \internal
797 : * \brief Create notify actions, and add notify data to original actions
798 : *
799 : * \param[in,out] rsc Clone or clone instance that notification is for
800 : * \param[in,out] n_data Clone notification data for some action
801 : */
802 : static void
803 0 : create_notify_actions(pcmk_resource_t *rsc, notify_data_t *n_data)
804 : {
805 0 : GList *iter = NULL;
806 0 : pcmk_action_t *stop = NULL;
807 0 : pcmk_action_t *start = NULL;
808 0 : enum action_tasks task = pcmk_parse_action(n_data->action);
809 :
810 : // If this is a clone, call recursively for each instance
811 0 : if (rsc->children != NULL) {
812 0 : g_list_foreach(rsc->children, (GFunc) create_notify_actions, n_data);
813 0 : return;
814 : }
815 :
816 : // Add notification meta-attributes to original actions
817 0 : for (iter = rsc->actions; iter != NULL; iter = iter->next) {
818 0 : pcmk_action_t *op = (pcmk_action_t *) iter->data;
819 :
820 0 : if (!pcmk_is_set(op->flags, pcmk_action_optional)
821 0 : && (op->node != NULL)) {
822 0 : switch (pcmk_parse_action(op->task)) {
823 0 : case pcmk_action_start:
824 : case pcmk_action_stop:
825 : case pcmk_action_promote:
826 : case pcmk_action_demote:
827 0 : add_notify_data_to_action_meta(n_data, op);
828 0 : break;
829 0 : default:
830 0 : break;
831 : }
832 : }
833 : }
834 :
835 : // Skip notify action itself if original action was not needed
836 0 : switch (task) {
837 0 : case pcmk_action_start:
838 0 : if (n_data->start == NULL) {
839 0 : pcmk__rsc_trace(rsc, "No notify action needed for %s %s",
840 : rsc->id, n_data->action);
841 0 : return;
842 : }
843 0 : break;
844 :
845 0 : case pcmk_action_promote:
846 0 : if (n_data->promote == NULL) {
847 0 : pcmk__rsc_trace(rsc, "No notify action needed for %s %s",
848 : rsc->id, n_data->action);
849 0 : return;
850 : }
851 0 : break;
852 :
853 0 : case pcmk_action_demote:
854 0 : if (n_data->demote == NULL) {
855 0 : pcmk__rsc_trace(rsc, "No notify action needed for %s %s",
856 : rsc->id, n_data->action);
857 0 : return;
858 : }
859 0 : break;
860 :
861 0 : default:
862 : // We cannot do same for stop because it might be implied by fencing
863 0 : break;
864 : }
865 :
866 0 : pcmk__rsc_trace(rsc, "Creating notify actions for %s %s",
867 : rsc->id, n_data->action);
868 :
869 : // Create notify actions for stop or demote
870 0 : if ((rsc->role != pcmk_role_stopped)
871 0 : && ((task == pcmk_action_stop) || (task == pcmk_action_demote))) {
872 :
873 0 : stop = find_first_action(rsc->actions, NULL, PCMK_ACTION_STOP, NULL);
874 :
875 0 : for (iter = rsc->running_on; iter != NULL; iter = iter->next) {
876 0 : pcmk_node_t *current_node = (pcmk_node_t *) iter->data;
877 :
878 : /* If a stop is a pseudo-action implied by fencing, don't try to
879 : * notify the node getting fenced.
880 : */
881 0 : if ((stop != NULL)
882 0 : && pcmk_is_set(stop->flags, pcmk_action_pseudo)
883 0 : && (current_node->details->unclean
884 0 : || current_node->details->remote_requires_reset)) {
885 0 : continue;
886 : }
887 :
888 0 : new_notify_action(rsc, current_node, n_data->pre,
889 : n_data->pre_done, n_data);
890 :
891 0 : if ((task == pcmk_action_demote) || (stop == NULL)
892 0 : || pcmk_is_set(stop->flags, pcmk_action_optional)) {
893 0 : new_post_notify_action(rsc, current_node, n_data);
894 : }
895 : }
896 : }
897 :
898 : // Create notify actions for start or promote
899 0 : if ((rsc->next_role != pcmk_role_stopped)
900 0 : && ((task == pcmk_action_start) || (task == pcmk_action_promote))) {
901 :
902 0 : start = find_first_action(rsc->actions, NULL, PCMK_ACTION_START, NULL);
903 0 : if (start != NULL) {
904 0 : pcmk_action_t *remote_start = find_remote_start(start);
905 :
906 0 : if ((remote_start != NULL)
907 0 : && !pcmk_is_set(remote_start->flags, pcmk_action_runnable)) {
908 : /* Start and promote actions for a clone instance behind
909 : * a Pacemaker Remote connection happen after the
910 : * connection starts. If the connection start is blocked, do
911 : * not schedule notifications for these actions.
912 : */
913 0 : return;
914 : }
915 : }
916 0 : if (rsc->allocated_to == NULL) {
917 0 : pcmk__sched_err("Next role '%s' but %s is not allocated",
918 : pcmk_role_text(rsc->next_role), rsc->id);
919 0 : return;
920 : }
921 0 : if ((task != pcmk_action_start) || (start == NULL)
922 0 : || pcmk_is_set(start->flags, pcmk_action_optional)) {
923 :
924 0 : new_notify_action(rsc, rsc->allocated_to, n_data->pre,
925 : n_data->pre_done, n_data);
926 : }
927 0 : new_post_notify_action(rsc, rsc->allocated_to, n_data);
928 : }
929 : }
930 :
931 : /*!
932 : * \internal
933 : * \brief Create notification data and actions for one clone action
934 : *
935 : * \param[in,out] rsc Clone resource that notification is for
936 : * \param[in,out] n_data Clone notification data for some action
937 : */
938 : void
939 0 : pe__create_action_notifications(pcmk_resource_t *rsc, notify_data_t *n_data)
940 : {
941 0 : if ((rsc == NULL) || (n_data == NULL)) {
942 0 : return;
943 : }
944 0 : collect_resource_data(rsc, true, n_data);
945 0 : add_notif_keys(rsc, n_data);
946 0 : create_notify_actions(rsc, n_data);
947 : }
948 :
949 : /*!
950 : * \internal
951 : * \brief Free notification data for one action
952 : *
953 : * \param[in,out] n_data Notification data to free
954 : */
955 : void
956 0 : pe__free_action_notification_data(notify_data_t *n_data)
957 : {
958 0 : if (n_data == NULL) {
959 0 : return;
960 : }
961 0 : g_list_free_full(n_data->stop, free);
962 0 : g_list_free_full(n_data->start, free);
963 0 : g_list_free_full(n_data->demote, free);
964 0 : g_list_free_full(n_data->promote, free);
965 0 : g_list_free_full(n_data->promoted, free);
966 0 : g_list_free_full(n_data->unpromoted, free);
967 0 : g_list_free_full(n_data->active, free);
968 0 : g_list_free_full(n_data->inactive, free);
969 0 : pcmk_free_nvpairs(n_data->keys);
970 0 : free(n_data);
971 : }
972 :
973 : /*!
974 : * \internal
975 : * \brief Order clone "notifications complete" pseudo-action after fencing
976 : *
977 : * If a stop action is implied by fencing, the usual notification pseudo-actions
978 : * will not be sufficient to order things properly, or even create all needed
979 : * notifications if the clone is also stopping on another node, and another
980 : * clone is ordered after it. This function creates new notification
981 : * pseudo-actions relative to the fencing to ensure everything works properly.
982 : *
983 : * \param[in] stop Stop action implied by fencing
984 : * \param[in,out] rsc Clone resource that notification is for
985 : * \param[in,out] stonith_op Fencing action that implies \p stop
986 : */
987 : void
988 0 : pe__order_notifs_after_fencing(const pcmk_action_t *stop, pcmk_resource_t *rsc,
989 : pcmk_action_t *stonith_op)
990 : {
991 : notify_data_t *n_data;
992 :
993 0 : crm_info("Ordering notifications for implied %s after fencing", stop->uuid);
994 0 : n_data = pe__action_notif_pseudo_ops(rsc, PCMK_ACTION_STOP, NULL,
995 : stonith_op);
996 :
997 0 : if (n_data != NULL) {
998 0 : collect_resource_data(rsc, false, n_data);
999 0 : add_notify_env(n_data, "notify_stop_resource", rsc->id);
1000 0 : add_notify_env(n_data, "notify_stop_uname", stop->node->details->uname);
1001 0 : create_notify_actions(uber_parent(rsc), n_data);
1002 0 : pe__free_action_notification_data(n_data);
1003 : }
1004 0 : }
|