diff --git a/include/crm/common/nodes.h b/include/crm/common/nodes.h index 150b29f1e7..a4f1977002 100644 --- a/include/crm/common/nodes.h +++ b/include/crm/common/nodes.h @@ -1,190 +1,219 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_COMMON_NODES__H # define PCMK__CRM_COMMON_NODES__H #include // bool #include // gboolean, GList, GHashTable #include // pcmk_resource_t, pcmk_scheduler_t #ifdef __cplusplus extern "C" { #endif /*! * \file * \brief Scheduler API for nodes * \ingroup core */ // Special node attributes #define PCMK_NODE_ATTR_MAINTENANCE "maintenance" #define PCMK_NODE_ATTR_STANDBY "standby" #define PCMK_NODE_ATTR_TERMINATE "terminate" -//! Possible node types -enum node_type { - pcmk_node_variant_cluster = 1, //!< Cluster layer node - pcmk_node_variant_remote = 2, //!< Pacemaker Remote node - - node_ping = 0, //!< \deprecated Do not use +// @COMPAT Make this internal when we can break API backward compatibility +//!@{ +//! \deprecated Do not use (public access will be removed in a future release) +enum node_type { // Possible node types + pcmk_node_variant_cluster = 1, // Cluster layer node + pcmk_node_variant_remote = 2, // Pacemaker Remote node + + node_ping = 0, // deprecated #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) - //! \deprecated Use pcmk_node_variant_cluster instead node_member = pcmk_node_variant_cluster, - - //! \deprecated Use pcmk_node_variant_remote instead node_remote = pcmk_node_variant_remote, #endif }; +//!@} -//! When to probe a resource on a node (as specified in location constraints) +// When to probe a resource on a node (as specified in location constraints) +// @COMPAT Make this internal when we can break API backward compatibility +//!@{ +//! \deprecated Do not use (public access will be removed in a future release) enum pe_discover_e { - pcmk_probe_always = 0, //! Always probe resource on node - pcmk_probe_never = 1, //! Never probe resource on node - pcmk_probe_exclusive = 2, //! Probe only on designated nodes + pcmk_probe_always = 0, // Always probe resource on node + pcmk_probe_never = 1, // Never probe resource on node + pcmk_probe_exclusive = 2, // Probe only on designated nodes #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) - //! \deprecated Use pcmk_probe_always instead pe_discover_always = pcmk_probe_always, - - //! \deprecated Use pcmk_probe_never instead pe_discover_never = pcmk_probe_never, - - //! \deprecated Use pcmk_probe_exclusive instead pe_discover_exclusive = pcmk_probe_exclusive, #endif }; +//!@} -//! Basic node information (all node objects for the same node share this) +// Basic node information (all node objects for the same node share this) +// @COMPAT Make this internal when we can break API backward compatibility +//!@{ +//! \deprecated Do not use (public access will be removed in a future release) struct pe_node_shared_s { - const char *id; //!< Node ID at the cluster layer - const char *uname; //!< Node name in cluster - enum node_type type; //!< Node variant + const char *id; // Node ID at the cluster layer + const char *uname; // Node name in cluster + enum node_type type; // Node variant // @TODO Convert these into a flag group - gboolean online; //!< Whether online - gboolean standby; //!< Whether in standby mode - gboolean standby_onfail; //!< Whether in standby mode due to on-fail - gboolean pending; //!< Whether controller membership is pending - gboolean unclean; //!< Whether node requires fencing - gboolean unseen; //!< Whether node has never joined cluster - gboolean shutdown; //!< Whether shutting down - gboolean expected_up; //!< Whether expected join state is member - gboolean is_dc; //!< Whether node is cluster's DC - gboolean maintenance; //!< Whether in maintenance mode - gboolean rsc_discovery_enabled; //!< Whether probes are allowed on node - - /*! + + //! \deprecated Call pcmk_node_is_online() instead + gboolean online; // Whether online + + gboolean standby; // Whether in standby mode + gboolean standby_onfail; // Whether in standby mode due to on-fail + + //! \deprecated Call pcmk_node_is_pending() instead + gboolean pending; // Whether controller membership is pending + + //! \deprecated Call !pcmk_node_is_clean() instead + gboolean unclean; // Whether node requires fencing + + gboolean unseen; // Whether node has never joined cluster + + //! \deprecated Call pcmk_node_is_shutting_down() instead + gboolean shutdown; // Whether shutting down + gboolean expected_up; // Whether expected join state is member + gboolean is_dc; // Whether node is cluster's DC + + //! \deprecated Call pcmk_node_is_in_maintenance() instead + gboolean maintenance; // Whether in maintenance mode + + gboolean rsc_discovery_enabled; // Whether probes are allowed on node + + /* * Whether this is a guest node whose guest resource must be recovered or a * remote node that must be fenced */ gboolean remote_requires_reset; - /*! + /* * Whether this is a Pacemaker Remote node that was fenced since it was last * connected by the cluster */ gboolean remote_was_fenced; - /*! + /* * Whether this is a Pacemaker Remote node previously marked in its * node state as being in maintenance mode */ gboolean remote_maintenance; - gboolean unpacked; //!< Whether node history has been unpacked + gboolean unpacked; // Whether node history has been unpacked - /*! + /* * Number of resources active on this node (valid after CIB status section * has been unpacked, as long as pcmk_sched_no_counts was not set) */ int num_resources; - //! Remote connection resource for node, if it is a Pacemaker Remote node + // Remote connection resource for node, if it is a Pacemaker Remote node pcmk_resource_t *remote_rsc; - GList *running_rsc; //!< List of resources active on node - GList *allocated_rsc; //!< List of resources assigned to node - GHashTable *attrs; //!< Node attributes - GHashTable *utilization; //!< Node utilization attributes - GHashTable *digest_cache; //!< Cache of calculated resource digests + GList *running_rsc; // List of resources active on node + GList *allocated_rsc; // List of resources assigned to node + GHashTable *attrs; // Node attributes + GHashTable *utilization; // Node utilization attributes + GHashTable *digest_cache; // Cache of calculated resource digests - /*! + /* * Sum of priorities of all resources active on node and on any guest nodes * connected to this node, with +1 for promoted instances (used to compare * nodes for PCMK_OPT_PRIORITY_FENCING_DELAY) */ int priority; - pcmk_scheduler_t *data_set; //!< Cluster that node is part of + pcmk_scheduler_t *data_set; // Cluster that node is part of }; +//!@} -//! Implementation of pcmk_node_t +// Implementation of pcmk_node_t +// @COMPAT Make contents internal when we can break API backward compatibility +//!@{ +//! \deprecated Do not use (public access will be removed in a future release) struct pe_node_s { - int weight; //!< Node score for a given resource - gboolean fixed; //!< \deprecated Do not use - int count; //!< Counter reused by assignment and promotion code - struct pe_node_shared_s *details; //!< Basic node information + int weight; // Node score for a given resource + gboolean fixed; // \deprecated Do not use + int count; // Counter reused by assignment and promotion code + struct pe_node_shared_s *details; // Basic node information // @COMPAT This should be enum pe_discover_e - int rsc_discover_mode; //!< Probe mode (enum pe_discover_e) + int rsc_discover_mode; // Probe mode (enum pe_discover_e) }; +//!@} + +bool pcmk_node_is_online(const pcmk_node_t *node); +bool pcmk_node_is_pending(const pcmk_node_t *node); +bool pcmk_node_is_clean(const pcmk_node_t *node); +bool pcmk_node_is_shutting_down(const pcmk_node_t *node); +bool pcmk_node_is_in_maintenance(const pcmk_node_t *node); +bool pcmk_foreach_active_resource(pcmk_node_t *node, + bool (*fn)(pcmk_resource_t *, void *), + void *user_data); /*! * \internal * \brief Return a string suitable for logging as a node name * * \param[in] node Node to return a node name string for * * \return Node name if available, otherwise node ID if available, * otherwise "unspecified node" if node is NULL or "unidentified node" * if node has neither a name nor ID. */ static inline const char * pcmk__node_name(const pcmk_node_t *node) { if (node == NULL) { return "unspecified node"; } else if (node->details->uname != NULL) { return node->details->uname; } else if (node->details->id != NULL) { return node->details->id; } else { return "unidentified node"; } } /*! * \internal * \brief Check whether two node objects refer to the same node * * \param[in] node1 First node object to compare * \param[in] node2 Second node object to compare * * \return true if \p node1 and \p node2 refer to the same node */ static inline bool pcmk__same_node(const pcmk_node_t *node1, const pcmk_node_t *node2) { return (node1 != NULL) && (node2 != NULL) && (node1->details == node2->details); } #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_NODES__H diff --git a/lib/common/nodes.c b/lib/common/nodes.c index b79a52e270..7e9e2d2820 100644 --- a/lib/common/nodes.c +++ b/lib/common/nodes.c @@ -1,26 +1,138 @@ /* * Copyright 2022-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include +#include // xmlNode #include +/*! + * \internal + * \brief Check whether a node is online + * + * \param[in] node Node to check + * + * \return true if \p node is online, otherwise false + */ +bool +pcmk_node_is_online(const pcmk_node_t *node) +{ + return (node != NULL) && node->details->online; +} + +/*! + * \internal + * \brief Check whether a node is pending + * + * Check whether a node is pending. A node is pending if it is a member of the + * cluster but not the controller group, which means it is in the process of + * either joining or leaving the cluster. + * + * \param[in] node Node to check + * + * \return true if \p node is pending, otherwise false + */ +bool +pcmk_node_is_pending(const pcmk_node_t *node) +{ + return (node != NULL) && node->details->pending; +} + +/*! + * \internal + * \brief Check whether a node is clean + * + * Check whether a node is clean. A node is clean if it is a cluster node or + * remote node that has been seen by the cluster at least once, or the + * startup-fencing cluster option is false; and the node, and its host if a + * guest or bundle node, are not scheduled to be fenced. + * + * \param[in] node Node to check + * + * \return true if \p node is clean, otherwise false + */ +bool +pcmk_node_is_clean(const pcmk_node_t *node) +{ + return (node != NULL) && !(node->details->unclean); +} + +/*! + * \internal + * \brief Check whether a node is shutting down + * + * \param[in] node Node to check + * + * \return true if \p node is shutting down, otherwise false + */ +bool +pcmk_node_is_shutting_down(const pcmk_node_t *node) +{ + return (node != NULL) && node->details->shutdown; +} + +/*! + * \internal + * \brief Check whether a node is in maintenance mode + * + * \param[in] node Node to check + * + * \return true if \p node is in maintenance mode, otherwise false + */ +bool +pcmk_node_is_in_maintenance(const pcmk_node_t *node) +{ + return (node != NULL) && node->details->maintenance; +} + +/*! + * \internal + * \brief Call a function for each resource active on a node + * + * Call a caller-supplied function with a caller-supplied argument for each + * resource that is active on a given node. If the function returns false, this + * function will return immediately without processing any remaining resources. + * + * \param[in] node Node to check + * + * \return Result of last call of \p fn (or false if none) + */ +bool +pcmk_foreach_active_resource(pcmk_node_t *node, + bool (*fn)(pcmk_resource_t *, void *), + void *user_data) +{ + bool result = false; + + if ((node != NULL) && (fn != NULL)) { + for (GList *item = node->details->running_rsc; item != NULL; + item = item->next) { + + result = fn((pcmk_resource_t *) item->data, user_data); + if (!result) { + break; + } + } + } + return result; +} + void pcmk__xe_add_node(xmlNode *xml, const char *node, int nodeid) { CRM_ASSERT(xml != NULL); if (node != NULL) { crm_xml_add(xml, PCMK__XA_ATTR_HOST, node); } if (nodeid > 0) { crm_xml_add_int(xml, PCMK__XA_ATTR_HOST_ID, nodeid); } } diff --git a/lib/common/tests/nodes/Makefile.am b/lib/common/tests/nodes/Makefile.am index fc19925dd6..67c223dbbd 100644 --- a/lib/common/tests/nodes/Makefile.am +++ b/lib/common/tests/nodes/Makefile.am @@ -1,16 +1,22 @@ # # Copyright 2024 the Pacemaker project contributors # # The version control history for this file may have further details. # # This source code is licensed under the GNU General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = pcmk__xe_add_node_test +check_PROGRAMS = pcmk_foreach_active_resource_test \ + pcmk_node_is_clean_test \ + pcmk_node_is_in_maintenance_test \ + pcmk_node_is_online_test \ + pcmk_node_is_pending_test \ + pcmk_node_is_shutting_down_test \ + pcmk__xe_add_node_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/nodes/pcmk_foreach_active_resource_test.c b/lib/common/tests/nodes/pcmk_foreach_active_resource_test.c new file mode 100644 index 0000000000..2402789230 --- /dev/null +++ b/lib/common/tests/nodes/pcmk_foreach_active_resource_test.c @@ -0,0 +1,149 @@ +/* + * Copyright 2024 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include + +#include // NULL +#include // GList, TRUE, FALSE + +#include +#include +#include + +static int counter = 1; +static int return_false = -1; + +static char rsc1_id[] = "rsc1"; +static char rsc2_id[] = "rsc2"; +static char rsc3_id[] = "rsc3"; + +static pcmk_resource_t rsc1 = { + .id = rsc1_id, +}; +static pcmk_resource_t rsc2 = { + .id = rsc2_id, +}; +static pcmk_resource_t rsc3 = { + .id = rsc3_id, +}; + +static bool +fn(pcmk_resource_t *rsc, void *user_data) +{ + char *expected_id = crm_strdup_printf("rsc%d", counter); + + assert_string_equal(rsc->id, expected_id); + free(expected_id); + + return counter++ != return_false; +} + +static void +null_args(void **state) +{ + struct pe_node_shared_s shared = { + .running_rsc = NULL, + }; + pcmk_node_t node = { + .details = &shared, + }; + + counter = 1; + + // These just test that it doesn't crash + pcmk_foreach_active_resource(NULL, NULL, NULL); + pcmk_foreach_active_resource(&node, NULL, NULL); + + pcmk_foreach_active_resource(NULL, fn, NULL); + assert_int_equal(counter, 1); +} + +static void +list_of_0(void **state) +{ + struct pe_node_shared_s shared = { + .running_rsc = NULL, + }; + pcmk_node_t node = { + .details = &shared, + }; + + counter = 1; + pcmk_foreach_active_resource(&node, fn, NULL); + assert_int_equal(counter, 1); +} + +static void +list_of_1(void **state) +{ + struct pe_node_shared_s shared = { + .running_rsc = NULL, + }; + pcmk_node_t node = { + .details = &shared, + }; + + shared.running_rsc = g_list_append(shared.running_rsc, &rsc1); + + counter = 1; + pcmk_foreach_active_resource(&node, fn, NULL); + assert_int_equal(counter, 2); + + g_list_free(shared.running_rsc); +} + +static void +list_of_3(void **state) +{ + struct pe_node_shared_s shared = { + .running_rsc = NULL, + }; + pcmk_node_t node = { + .details = &shared, + }; + + shared.running_rsc = g_list_append(shared.running_rsc, &rsc1); + shared.running_rsc = g_list_append(shared.running_rsc, &rsc2); + shared.running_rsc = g_list_append(shared.running_rsc, &rsc3); + + counter = 1; + pcmk_foreach_active_resource(&node, fn, NULL); + assert_int_equal(counter, 4); + + g_list_free(shared.running_rsc); +} + +static void +list_of_3_return_false(void **state) +{ + struct pe_node_shared_s shared = { + .running_rsc = NULL, + }; + pcmk_node_t node = { + .details = &shared, + }; + + shared.running_rsc = g_list_append(shared.running_rsc, &rsc1); + shared.running_rsc = g_list_append(shared.running_rsc, &rsc2); + shared.running_rsc = g_list_append(shared.running_rsc, &rsc3); + + counter = 1; + return_false = 2; + pcmk_foreach_active_resource(&node, fn, NULL); + assert_int_equal(counter, 3); + + g_list_free(shared.running_rsc); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(null_args), + cmocka_unit_test(list_of_0), + cmocka_unit_test(list_of_1), + cmocka_unit_test(list_of_3), + cmocka_unit_test(list_of_3_return_false)) diff --git a/lib/common/tests/nodes/pcmk_node_is_clean_test.c b/lib/common/tests/nodes/pcmk_node_is_clean_test.c new file mode 100644 index 0000000000..0534633fdf --- /dev/null +++ b/lib/common/tests/nodes/pcmk_node_is_clean_test.c @@ -0,0 +1,54 @@ +/* + * Copyright 2024 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include + +#include // NULL +#include // TRUE, FALSE + +#include +#include + +static void +null_is_unclean(void **state) +{ + assert_false(pcmk_node_is_clean(NULL)); +} + +static void +node_is_clean(void **state) +{ + struct pe_node_shared_s shared = { + .unclean = FALSE, + }; + + pcmk_node_t node = { + .details = &shared, + }; + + assert_true(pcmk_node_is_clean(&node)); +} + +static void +node_is_unclean(void **state) +{ + struct pe_node_shared_s shared = { + .unclean = TRUE, + }; + pcmk_node_t node = { + .details = &shared, + }; + + assert_false(pcmk_node_is_clean(&node)); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(null_is_unclean), + cmocka_unit_test(node_is_clean), + cmocka_unit_test(node_is_unclean)) diff --git a/lib/common/tests/nodes/pcmk_node_is_in_maintenance_test.c b/lib/common/tests/nodes/pcmk_node_is_in_maintenance_test.c new file mode 100644 index 0000000000..45a3b6fdca --- /dev/null +++ b/lib/common/tests/nodes/pcmk_node_is_in_maintenance_test.c @@ -0,0 +1,54 @@ +/* + * Copyright 2024 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include + +#include // NULL +#include // TRUE, FALSE + +#include +#include + +static void +null_is_not_in_maintenance(void **state) +{ + assert_false(pcmk_node_is_in_maintenance(NULL)); +} + +static void +node_is_in_maintenance(void **state) +{ + struct pe_node_shared_s shared = { + .maintenance = TRUE, + }; + + pcmk_node_t node = { + .details = &shared, + }; + + assert_true(pcmk_node_is_in_maintenance(&node)); +} + +static void +node_is_not_in_maintenance(void **state) +{ + struct pe_node_shared_s shared = { + .maintenance = FALSE, + }; + pcmk_node_t node = { + .details = &shared, + }; + + assert_false(pcmk_node_is_in_maintenance(&node)); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(null_is_not_in_maintenance), + cmocka_unit_test(node_is_in_maintenance), + cmocka_unit_test(node_is_not_in_maintenance)) diff --git a/lib/common/tests/nodes/pcmk_node_is_online_test.c b/lib/common/tests/nodes/pcmk_node_is_online_test.c new file mode 100644 index 0000000000..d22e3b4dd3 --- /dev/null +++ b/lib/common/tests/nodes/pcmk_node_is_online_test.c @@ -0,0 +1,54 @@ +/* + * Copyright 2024 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include + +#include // NULL +#include // TRUE, FALSE + +#include +#include + +static void +null_is_offline(void **state) +{ + assert_false(pcmk_node_is_online(NULL)); +} + +static void +node_is_online(void **state) +{ + struct pe_node_shared_s shared = { + .online = TRUE, + }; + + pcmk_node_t node = { + .details = &shared, + }; + + assert_true(pcmk_node_is_online(&node)); +} + +static void +node_is_offline(void **state) +{ + struct pe_node_shared_s shared = { + .online = FALSE, + }; + pcmk_node_t node = { + .details = &shared, + }; + + assert_false(pcmk_node_is_online(&node)); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(null_is_offline), + cmocka_unit_test(node_is_online), + cmocka_unit_test(node_is_offline)) diff --git a/lib/common/tests/nodes/pcmk_node_is_pending_test.c b/lib/common/tests/nodes/pcmk_node_is_pending_test.c new file mode 100644 index 0000000000..9f2abca4e9 --- /dev/null +++ b/lib/common/tests/nodes/pcmk_node_is_pending_test.c @@ -0,0 +1,54 @@ +/* + * Copyright 2024 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include + +#include // NULL +#include // TRUE, FALSE + +#include +#include + +static void +null_is_not_pending(void **state) +{ + assert_false(pcmk_node_is_pending(NULL)); +} + +static void +node_is_pending(void **state) +{ + struct pe_node_shared_s shared = { + .pending = TRUE, + }; + + pcmk_node_t node = { + .details = &shared, + }; + + assert_true(pcmk_node_is_pending(&node)); +} + +static void +node_is_not_pending(void **state) +{ + struct pe_node_shared_s shared = { + .pending = FALSE, + }; + pcmk_node_t node = { + .details = &shared, + }; + + assert_false(pcmk_node_is_pending(&node)); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(null_is_not_pending), + cmocka_unit_test(node_is_pending), + cmocka_unit_test(node_is_not_pending)) diff --git a/lib/common/tests/nodes/pcmk_node_is_shutting_down_test.c b/lib/common/tests/nodes/pcmk_node_is_shutting_down_test.c new file mode 100644 index 0000000000..b6054b07dd --- /dev/null +++ b/lib/common/tests/nodes/pcmk_node_is_shutting_down_test.c @@ -0,0 +1,54 @@ +/* + * Copyright 2024 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include + +#include // NULL +#include // TRUE, FALSE + +#include +#include + +static void +null_is_not_shutting_down(void **state) +{ + assert_false(pcmk_node_is_shutting_down(NULL)); +} + +static void +node_is_shutting_down(void **state) +{ + struct pe_node_shared_s shared = { + .shutdown = TRUE, + }; + + pcmk_node_t node = { + .details = &shared, + }; + + assert_true(pcmk_node_is_shutting_down(&node)); +} + +static void +node_is_not_shutting_down(void **state) +{ + struct pe_node_shared_s shared = { + .shutdown = FALSE, + }; + pcmk_node_t node = { + .details = &shared, + }; + + assert_false(pcmk_node_is_shutting_down(&node)); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(null_is_not_shutting_down), + cmocka_unit_test(node_is_shutting_down), + cmocka_unit_test(node_is_not_shutting_down))