1 /*
2 * Copyright 2009-2026 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
12 #include <stdbool.h>
13 #include <stdio.h>
14 #include <libxml/tree.h> // xmlNode
15 #include <libxml/xpath.h> // xmlXPathObject, etc.
16
17 #include <crm/crm.h>
18 #include <crm/common/xml.h>
19
20 #include <crm/cluster/internal.h>
21
22 #include <crm/cib.h>
23 #include <crm/cib/internal.h>
24
25 #include <pacemaker-fenced.h>
26
27 static xmlNode *local_cib = NULL;
28 static cib_t *cib_api = NULL;
29 static bool have_cib_devices = FALSE;
30
31 /*!
32 * \internal
33 * \brief Check whether a node has a specific attribute name/value
34 *
35 * \param[in] node Name of node to check
36 * \param[in] name Name of an attribute to look for
37 * \param[in] value The value the named attribute needs to be set to in order to be considered a match
38 *
39 * \return TRUE if the locally cached CIB has the specified node attribute
40 */
41 gboolean
42 node_has_attr(const char *node, const char *name, const char *value)
43 {
44 GString *xpath = NULL;
45 xmlNode *match;
46
47 CRM_CHECK((local_cib != NULL) && (node != NULL) && (name != NULL)
48 && (value != NULL), return FALSE);
49
50 /* Search for the node's attributes in the CIB. While the schema allows
51 * multiple sets of instance attributes, and allows instance attributes to
52 * use id-ref to reference values elsewhere, that is intended for resources,
53 * so we ignore that here.
54 */
55 xpath = g_string_sized_new(256);
56 pcmk__g_strcat(xpath,
57 "//" PCMK_XE_NODES "/" PCMK_XE_NODE
58 "[@" PCMK_XA_UNAME "='", node, "']"
59 "/" PCMK_XE_INSTANCE_ATTRIBUTES
60 "/" PCMK_XE_NVPAIR
61 "[@" PCMK_XA_NAME "='", name, "' "
62 "and @" PCMK_XA_VALUE "='", value, "']", NULL);
63
64 match = pcmk__xpath_find_one(local_cib->doc, xpath->str, PCMK__LOG_NEVER);
65
66 g_string_free(xpath, TRUE);
67 return (match != NULL);
68 }
69
70 static void
71 remove_topology_level(xmlNode *match)
72 {
73 int index = 0;
74 char *key = NULL;
75 xmlNode *data = NULL;
76
77 CRM_CHECK(match != NULL, return);
78
79 key = stonith_level_key(match, fenced_target_by_unknown);
80 pcmk__xe_get_int(match, PCMK_XA_INDEX, &index);
81
82 data = pcmk__xe_create(NULL, PCMK_XE_FENCING_LEVEL);
83 pcmk__xe_set(data, PCMK__XA_ST_ORIGIN, __func__);
84 pcmk__xe_set(data, PCMK_XA_TARGET, key);
85 pcmk__xe_set_int(data, PCMK_XA_INDEX, index);
86
87 fenced_unregister_level(data, NULL);
88
89 free(key);
90 pcmk__xml_free(data);
91 }
92
93 static void
94 register_fencing_topology(xmlXPathObjectPtr xpathObj)
95 {
96 int max = pcmk__xpath_num_results(xpathObj);
97
98 for (int lpc = 0; lpc < max; lpc++) {
99 xmlNode *match = pcmk__xpath_result(xpathObj, lpc);
100
101 if (match == NULL) {
102 continue;
103 }
104 remove_topology_level(match);
105 fenced_register_level(match, NULL);
106 }
107 }
108
109 /* Fencing
110 <diff crm_feature_set="3.0.6">
111 <diff-removed>
112 <fencing-topology>
113 <fencing-level id="f-p1.1" target="pcmk-1" index="1" devices="poison-pill" __crm_diff_marker__="removed:top"/>
114 <fencing-level id="f-p1.2" target="pcmk-1" index="2" devices="power" __crm_diff_marker__="removed:top"/>
115 <fencing-level devices="disk,network" id="f-p2.1"/>
116 </fencing-topology>
117 </diff-removed>
118 <diff-added>
119 <fencing-topology>
120 <fencing-level id="f-p.1" target="pcmk-1" index="1" devices="poison-pill" __crm_diff_marker__="added:top"/>
121 <fencing-level id="f-p2.1" target="pcmk-2" index="1" devices="disk,something"/>
122 <fencing-level id="f-p3.1" target="pcmk-2" index="2" devices="power" __crm_diff_marker__="added:top"/>
123 </fencing-topology>
124 </diff-added>
125 </diff>
126 */
127
128 void
129 fencing_topology_init(void)
130 {
131 xmlXPathObject *xpathObj = NULL;
132 const char *xpath = "//" PCMK_XE_FENCING_LEVEL;
133
134 pcmk__trace("Full topology refresh");
135 free_topology_list();
136 init_topology_list();
137
138 /* Grab everything */
139 xpathObj = pcmk__xpath_search(local_cib->doc, xpath);
140 register_fencing_topology(xpathObj);
141
142 xmlXPathFreeObject(xpathObj);
143 }
144
145 #define XPATH_WATCHDOG_TIMEOUT "//" PCMK_XE_NVPAIR \
146 "[@" PCMK_XA_NAME "='" \
147 PCMK_OPT_FENCING_WATCHDOG_TIMEOUT "']"
148
149 /* @COMPAT The "stonith-watchdog-timeout" option has been a deprecated alias
150 * for "fencing-watchdog-timeout" since 3.0.2. Make sure it still works as a
151 * fallback until it's dropped in a future release.
152 */
153 #define XPATH_STONITH_WATCHDOG_TIMEOUT "//" PCMK_XE_NVPAIR \
154 "[@" PCMK_XA_NAME "='" \
155 PCMK_OPT_STONITH_WATCHDOG_TIMEOUT "']"
156
157 static long long
158 get_fencing_watchdog_timeout(xmlNode *cib)
159 {
160 xmlNode *stonith_watchdog_xml = NULL;
161 const char *value = NULL;
162 int rc = pcmk_rc_ok;
163 long long timeout_ms = 0;
164
165 // @TODO An XPath search can't handle multiple instances or rules
166 stonith_watchdog_xml = pcmk__xpath_find_one(cib->doc,
167 XPATH_WATCHDOG_TIMEOUT,
168 PCMK__LOG_NEVER);
169
170 /* @COMPAT The "stonith-watchdog-timeout" option has been a deprecated alias
171 * for "fencing-watchdog-timeout" since 3.0.2. Make sure it still works as a
172 * fallback until it's dropped in a future release.
173 */
174 if (stonith_watchdog_xml == NULL) {
175 stonith_watchdog_xml = pcmk__xpath_find_one(cib->doc,
176 XPATH_STONITH_WATCHDOG_TIMEOUT,
177 PCMK__LOG_NEVER);
178 }
179
180 if (stonith_watchdog_xml == NULL) {
181 return 0;
182 }
183
184 value = pcmk__xe_get(stonith_watchdog_xml, PCMK_XA_VALUE);
185 if (value == NULL) {
186 return 0;
187 }
188
189 rc = pcmk__parse_ms(value, &timeout_ms);
190 if ((rc == pcmk_rc_ok) && (timeout_ms >= 0)) {
191 return timeout_ms;
192 }
193
194 return pcmk__auto_fencing_watchdog_timeout();
195 }
196
197 /*!
198 * \internal
199 * \brief Mark a fence device dirty if its \c fenced_df_cib_registered flag is
200 * set
201 *
202 * \param[in] key Ignored
203 * \param[in,out] value Fence device (<tt>fenced_device_t *</tt>)
204 * \param[in] user_data Ignored
205 *
206 * \note This function is suitable for use with \c g_hash_table_foreach().
207 */
208 static void
209 mark_dirty_if_cib_registered(gpointer key, gpointer value, gpointer user_data)
210 {
211 fenced_device_t *device = value;
212
213 if (pcmk__is_set(device->flags, fenced_df_cib_registered)) {
214 fenced_device_set_flags(device, fenced_df_dirty);
215 }
216 }
217
218 /*!
219 * \internal
220 * \brief Return the value of a fence device's \c dirty flag
221 *
222 * \param[in] key Ignored
223 * \param[in] value Fence device (<tt>fenced_device_t *</tt>)
224 * \param[in] user_data Ignored
225 *
226 * \return \c dirty flag of \p value
227 *
228 * \note This function is suitable for use with
229 * \c g_hash_table_foreach_remove().
230 */
231 static gboolean
232 device_is_dirty(gpointer key, gpointer value, gpointer user_data)
233 {
234 fenced_device_t *device = value;
235
236 return pcmk__is_set(device->flags, fenced_df_dirty);
237 }
238
239 /*!
240 * \internal
241 * \brief Update all STONITH device definitions based on current CIB
242 */
243 static void
244 cib_devices_update(void)
245 {
246 pcmk__info("Updating devices to version %s.%s.%s",
247 pcmk__xe_get(local_cib, PCMK_XA_ADMIN_EPOCH),
248 pcmk__xe_get(local_cib, PCMK_XA_EPOCH),
249 pcmk__xe_get(local_cib, PCMK_XA_NUM_UPDATES));
250
251 fenced_foreach_device(mark_dirty_if_cib_registered, NULL);
252
253 /* have list repopulated if cib has a watchdog-fencing-resource
254 TODO: keep a cached list for queries happening while we are refreshing
255 */
256 g_list_free_full(stonith_watchdog_targets, free);
257 stonith_watchdog_targets = NULL;
258
259 fenced_scheduler_run(local_cib);
260
261 fenced_foreach_device_remove(device_is_dirty);
262 }
263
264 #define PRIMITIVE_ID_XP_FRAGMENT "/" PCMK_XE_PRIMITIVE "[@" PCMK_XA_ID "='"
265
266 static void
267 update_cib_stonith_devices(const xmlNode *patchset)
268 {
269 char *reason = NULL;
270
271 for (const xmlNode *change = pcmk__xe_first_child(patchset, NULL, NULL,
272 NULL);
273 change != NULL; change = pcmk__xe_next(change, NULL)) {
274
275 const char *op = pcmk__xe_get(change, PCMK_XA_OPERATION);
276 const char *xpath = pcmk__xe_get(change, PCMK_XA_PATH);
277 const char *primitive_xpath = NULL;
278
279 if (pcmk__str_eq(op, PCMK_VALUE_MOVE, pcmk__str_null_matches)
280 || (strstr(xpath, "/" PCMK_XE_STATUS) != NULL)) {
281 continue;
282 }
283
284 primitive_xpath = strstr(xpath, PRIMITIVE_ID_XP_FRAGMENT);
285 if ((primitive_xpath != NULL)
286 && pcmk__str_eq(op, PCMK_VALUE_DELETE, pcmk__str_none)) {
287
288 const char *rsc_id = NULL;
289 const char *end_quote = NULL;
290
291 if ((strstr(primitive_xpath, PCMK_XE_INSTANCE_ATTRIBUTES) != NULL)
292 || (strstr(primitive_xpath, PCMK_XE_META_ATTRIBUTES) != NULL)) {
293
294 reason = pcmk__str_copy("(meta) attribute deleted from "
295 "resource");
296 break;
297 }
298
299 rsc_id = primitive_xpath + sizeof(PRIMITIVE_ID_XP_FRAGMENT) - 1;
300 end_quote = strchr(rsc_id, '\'');
301
302 CRM_LOG_ASSERT(end_quote != NULL);
303 if (end_quote == NULL) {
304 pcmk__err("Bug: Malformed item in Pacemaker-generated patchset");
305 continue;
306 }
307
308 if (strchr(end_quote, '/') == NULL) {
309 /* The primitive element itself was deleted. If this was a
310 * fencing resource, it's faster to remove it directly than to
311 * run the scheduler and update all device registrations.
312 */
313 char *copy = strndup(rsc_id, end_quote - rsc_id);
314
315 pcmk__assert(copy != NULL);
316 stonith_device_remove(copy, true);
317
318 /* watchdog_device_update called afterwards
319 to fall back to implicit definition if needed */
320
321 free(copy);
322 continue;
323 }
324 }
325
326 if (strstr(xpath, "/" PCMK_XE_RESOURCES)
327 || strstr(xpath, "/" PCMK_XE_CONSTRAINTS)
328 || strstr(xpath, "/" PCMK_XE_RSC_DEFAULTS)) {
329
330 const char *shortpath = strrchr(xpath, '/');
331
332 reason = pcmk__assert_asprintf("%s %s", op, shortpath + 1);
333 break;
334 }
335 }
336
337 if (reason != NULL) {
338 pcmk__info("Updating device list from CIB: %s", reason);
339 cib_devices_update();
340 free(reason);
341 } else {
342 pcmk__trace("No updates for device list found in CIB");
343 }
344 }
345
346 static void
347 watchdog_device_update(void)
348 {
349 if (fencing_watchdog_timeout_ms > 0) {
350 if (!fenced_has_watchdog_device()
351 && (stonith_watchdog_targets == NULL)) {
352 /* getting here watchdog-fencing enabled, no device there yet
353 and reason isn't stonith_watchdog_targets preventing that
354 */
355 int rc;
356 xmlNode *xml;
357
358 xml = create_device_registration_xml(
359 STONITH_WATCHDOG_ID,
360 st_namespace_internal,
361 STONITH_WATCHDOG_AGENT,
362 NULL, /* fenced_device_register() will add our
363 own name as PCMK_FENCING_HOST_LIST param
364 so we can skip that here
365 */
366 NULL);
367 rc = fenced_device_register(xml, true);
368 pcmk__xml_free(xml);
369 if (rc != pcmk_rc_ok) {
370 exit_code = CRM_EX_FATAL;
371 pcmk__crit("Cannot register watchdog pseudo fence agent: %s",
372 pcmk_rc_str(rc));
373 stonith_shutdown(0);
374 }
375 }
376
377 } else if (fenced_has_watchdog_device()) {
378 /* be silent if no device - todo parameter to stonith_device_remove */
379 stonith_device_remove(STONITH_WATCHDOG_ID, true);
380 }
381 }
382
383 /*!
384 * \internal
385 * \brief Query the full CIB
386 *
387 * \return Standard Pacemaker return code
388 */
389 static int
390 fenced_query_cib(void)
391 {
392 int rc = pcmk_ok;
393
394 pcmk__trace("Re-requesting full CIB");
395 rc = cib_api->cmds->query(cib_api, NULL, &local_cib, cib_sync_call);
396 rc = pcmk_legacy2rc(rc);
397 if (rc == pcmk_rc_ok) {
398 pcmk__assert(local_cib != NULL);
399 } else {
400 pcmk__err("Couldn't retrieve the CIB: %s " QB_XS " rc=%d",
401 pcmk_rc_str(rc), rc);
402 }
403 return rc;
404 }
405
406 static void
407 update_fencing_topology(const char *event, xmlNode *msg)
408 {
409 xmlNode *wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT,
410 NULL, NULL);
411 xmlNode *patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
412
413 int format = 1;
414
415 int add[] = { 0, 0, 0 };
416 int del[] = { 0, 0, 0 };
417
418 CRM_CHECK(patchset != NULL, return);
419
420 pcmk__xe_get_int(patchset, PCMK_XA_FORMAT, &format);
421 if (format != 2) {
422 pcmk__warn("Unknown patch format: %d", format);
423 return;
424 }
425
426 pcmk__xml_patchset_versions(patchset, del, add);
427
428 for (xmlNode *change = pcmk__xe_first_child(patchset, NULL, NULL, NULL);
429 change != NULL; change = pcmk__xe_next(change, NULL)) {
430
431 const char *op = pcmk__xe_get(change, PCMK_XA_OPERATION);
432 const char *xpath = pcmk__xe_get(change, PCMK_XA_PATH);
433
434 if (op == NULL) {
435 continue;
436 }
437
438 if (strstr(xpath, "/" PCMK_XE_FENCING_LEVEL) != NULL) {
439 // Change to a specific entry
440 pcmk__trace("Handling %s operation %d.%d.%d for %s", op,
441 add[0], add[1], add[2], xpath);
442
443 if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
444 /* We have only path and ID, which is not enough info to remove
445 * a specific entry. Re-initialize the whole topology.
446 */
447 pcmk__info("Re-initializing fencing topology after %s "
448 "operation %d.%d.%d for %s",
449 op, add[0], add[1], add[2], xpath);
450 fencing_topology_init();
451 return;
452 }
453
454 if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
455 fenced_register_level(change->children, NULL);
456
457 } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
458 xmlNode *match = pcmk__xe_first_child(change,
459 PCMK_XE_CHANGE_RESULT,
460 NULL, NULL);
461
462 if (match != NULL) {
463 remove_topology_level(match->children);
464 fenced_register_level(match->children, NULL);
465 }
466 }
467 continue;
468 }
469
470 if (strstr(xpath, "/" PCMK_XE_FENCING_TOPOLOGY) != NULL) {
471 // Change to the topology in general
472 pcmk__info("Re-initializing fencing topology after top-level %s "
473 "operation %d.%d.%d for %s",
474 op, add[0], add[1], add[2], xpath);
475 fencing_topology_init();
476 return;
477 }
478
479 if ((strstr(xpath, "/" PCMK_XE_CONFIGURATION) != NULL)
480 && (pcmk__xe_first_child(change, PCMK_XE_FENCING_TOPOLOGY, NULL,
481 NULL) != NULL)
482 && pcmk__str_any_of(op, PCMK_VALUE_CREATE, PCMK_VALUE_DELETE,
483 NULL)) {
484
485 // Topology was created or entire configuration section was deleted
486 pcmk__info("Re-initializing fencing topology after top-level %s "
487 "operation %d.%d.%d for %s",
488 op, add[0], add[1], add[2], xpath);
489 fencing_topology_init();
490 return;
491 }
492
493 pcmk__trace("Nothing for us in %s operation %d.%d.%d for %s", op,
494 add[0], add[1], add[2], xpath);
495 }
496 }
497
498 static void
499 update_cib_cache_cb(const char *event, xmlNode * msg)
500 {
501 xmlNode *patchset = NULL;
502 long long timeout_ms_saved = fencing_watchdog_timeout_ms;
503 bool need_full_refresh = false;
504
|
(1) Event path: |
Condition "!have_cib_devices", taking false branch. |
505 if(!have_cib_devices) {
506 pcmk__trace("Skipping updates until we get a full dump");
507 return;
508
|
(2) Event path: |
Condition "msg == NULL", taking false branch. |
509 } else if(msg == NULL) {
510 pcmk__trace("Missing %s update", event);
511 return;
512 }
513
514 /* Maintain a local copy of the CIB so that we have full access
515 * to device definitions, location constraints, and node attributes
516 */
|
(3) Event path: |
Condition "local_cib != NULL", taking true branch. |
517 if (local_cib != NULL) {
518 int rc = pcmk_ok;
519 xmlNode *wrapper = NULL;
520
521 pcmk__xe_get_int(msg, PCMK__XA_CIB_RC, &rc);
|
(4) Event path: |
Condition "rc != 0", taking false branch. |
522 if (rc != pcmk_ok) {
523 return;
524 }
525
526 wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT, NULL,
527 NULL);
528 patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
529
530 rc = xml_apply_patchset(local_cib, patchset, TRUE);
|
(5) Event path: |
Switch case value "-206". |
531 switch (rc) {
532 case pcmk_ok:
533 case -pcmk_err_old_data:
534 /* @TODO Full refresh (with or without query) in case of
535 * -pcmk_err_old_data? It seems wrong to call
536 * stonith_device_remove() based on primitive deletion in an
537 * old diff.
538 */
539 break;
540 case -pcmk_err_diff_failed:
541 pcmk__notice("[%s] Patch aborted: %s (%d)", event,
542 pcmk_strerror(rc), rc);
|
CID (unavailable; MK=e25f75fe95a6ce09d89a0a9b355fe1a1) (#1 of 2): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(6) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(7) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
543 g_clear_pointer(&local_cib, pcmk__xml_free);
544 break;
545 default:
546 pcmk__warn("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc),
547 rc);
548 g_clear_pointer(&local_cib, pcmk__xml_free);
549 break;
550 }
551 }
552
553 if (local_cib == NULL) {
554 if (fenced_query_cib() != pcmk_rc_ok) {
555 return;
556 }
557 need_full_refresh = true;
558 }
559
560 pcmk__refresh_node_caches_from_cib(local_cib);
561 fencing_watchdog_timeout_ms = get_fencing_watchdog_timeout(local_cib);
562
563 if (timeout_ms_saved != fencing_watchdog_timeout_ms) {
564 need_full_refresh = true;
565 }
566
567 if (need_full_refresh) {
568 fencing_topology_init();
569 cib_devices_update();
570 } else {
571 // Partial refresh
572 update_fencing_topology(event, msg);
573 update_cib_stonith_devices(patchset);
574 }
575
576 watchdog_device_update();
577 }
578
579 static void
580 init_cib_cache_cb(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
581 {
582 pcmk__info("Updating device list from CIB");
583 have_cib_devices = TRUE;
584 local_cib = pcmk__xml_copy(NULL, output);
585
586 pcmk__refresh_node_caches_from_cib(local_cib);
587 fencing_watchdog_timeout_ms = get_fencing_watchdog_timeout(local_cib);
588
589 fencing_topology_init();
590 cib_devices_update();
591 watchdog_device_update();
592 }
593
594 static void
595 cib_connection_destroy(gpointer user_data)
596 {
597 if (stonith_shutdown_flag) {
598 pcmk__info("Connection to the CIB manager closed");
599 return;
600 } else {
601 pcmk__crit("Lost connection to the CIB manager, shutting down");
602 }
603 if (cib_api) {
604 cib_api->cmds->signoff(cib_api);
605 }
606 stonith_shutdown(0);
607 }
608
609 /*!
610 * \internal
611 * \brief Disconnect from CIB manager
612 */
613 void
614 fenced_cib_cleanup(void)
615 {
616 if (cib_api != NULL) {
617 cib_api->cmds->del_notify_callback(cib_api, PCMK__VALUE_CIB_DIFF_NOTIFY,
618 update_cib_cache_cb);
619 cib__clean_up_connection(&cib_api);
620 }
621
622 g_clear_pointer(&local_cib, pcmk__xml_free);
623 }
624
625 void
626 setup_cib(void)
627 {
628 int rc, retries = 0;
629
630 cib_api = cib_new();
631 if (cib_api == NULL) {
632 pcmk__err("No connection to the CIB manager");
633 return;
634 }
635
636 do {
637 sleep(retries);
638 rc = cib_api->cmds->signon(cib_api, crm_system_name, cib_command);
639 } while (rc == -ENOTCONN && ++retries < 5);
640
641 if (rc != pcmk_ok) {
642 pcmk__err("Could not connect to the CIB manager: %s (%d)",
643 pcmk_strerror(rc), rc);
644 return;
645 }
646
647 rc = cib_api->cmds->add_notify_callback(cib_api,
648 PCMK__VALUE_CIB_DIFF_NOTIFY,
649 update_cib_cache_cb);
650 if (rc != pcmk_ok) {
651 pcmk__err("Could not set CIB notification callback");
652 return;
653 }
654
655 rc = cib_api->cmds->query(cib_api, NULL, NULL, cib_none);
656 cib_api->cmds->register_callback(cib_api, rc, 120, FALSE, NULL,
657 "init_cib_cache_cb", init_cib_cache_cb);
658 cib_api->cmds->set_connection_dnotify(cib_api, cib_connection_destroy);
659 pcmk__info("Watching for fencing topology changes");
660 }
661