diff --git a/include/crm/common/nodes.h b/include/crm/common/nodes.h index 360ea82923..729064b976 100644 --- a/include/crm/common/nodes.h +++ b/include/crm/common/nodes.h @@ -1,196 +1,199 @@ /* * 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 #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) 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 #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) 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 // @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 /*! * 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 /*! * 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 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 /*! * 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 }; //! Implementation of pcmk_node_t 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 // @COMPAT This should be 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 63eaf68a81..7e9e2d2820 100644 --- a/lib/common/nodes.c +++ b/lib/common/nodes.c @@ -1,106 +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 0adf580304..67c223dbbd 100644 --- a/lib/common/tests/nodes/Makefile.am +++ b/lib/common/tests/nodes/Makefile.am @@ -1,21 +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_node_is_clean_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))