diff --git a/include/crm/common/nodes.h b/include/crm/common/nodes.h index 5f6f25ff0c..64c574ad3c 100644 --- a/include/crm/common/nodes.h +++ b/include/crm/common/nodes.h @@ -1,230 +1,234 @@ /* * 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 // xmlNode #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" // @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) node_member = pcmk_node_variant_cluster, node_remote = pcmk_node_variant_remote, #endif }; //!@} // 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 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) pe_discover_always = pcmk_probe_always, pe_discover_never = pcmk_probe_never, pe_discover_exclusive = pcmk_probe_exclusive, #endif }; //!@} // 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 // @TODO Convert these into a flag group // NOTE: sbd (as of at least 1.5.2) uses this //! \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 // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Call pcmk_node_is_pending() instead gboolean pending; // Whether controller membership is pending // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Call !pcmk_node_is_clean() instead gboolean unclean; // Whether node requires fencing gboolean unseen; // Whether node has never joined cluster // NOTE: sbd (as of at least 1.5.2) uses this //! \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 // NOTE: sbd (as of at least 1.5.2) uses this //! \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 /* * 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; // NOTE: sbd (as of at least 1.5.2) uses this // \deprecated Call pcmk_foreach_active_resource() instead 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 // @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 // NOTE: sbd (as of at least 1.5.2) uses this 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); + +const char *pcmk_cib_node_shutdown(xmlNode *cib, const char *node); + /*! * \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 4edeafbc42..5228ea2618 100644 --- a/lib/common/nodes.c +++ b/lib/common/nodes.c @@ -1,163 +1,193 @@ /* * 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); } } /*! * \internal * \brief Find a node by name in a list of nodes * * \param[in] nodes List of nodes (as pcmk_node_t*) * \param[in] node_name Name of node to find * * \return Node from \p nodes that matches \p node_name if any, otherwise NULL */ pcmk_node_t * pcmk__find_node_in_list(const GList *nodes, const char *node_name) { if (node_name != NULL) { for (const GList *iter = nodes; iter != NULL; iter = iter->next) { pcmk_node_t *node = (pcmk_node_t *) iter->data; if (pcmk__str_eq(node->details->uname, node_name, pcmk__str_casei)) { return node; } } } return NULL; } + +#define XP_SHUTDOWN "//" PCMK__XE_NODE_STATE "[@" PCMK_XA_UNAME "='%s']/" \ + PCMK__XE_TRANSIENT_ATTRIBUTES "/" PCMK_XE_INSTANCE_ATTRIBUTES "/" \ + PCMK_XE_NVPAIR "[@" PCMK_XA_NAME "='" PCMK__NODE_ATTR_SHUTDOWN "']" + +/*! + * \brief Get value of a node's shutdown attribute from CIB, if present + * + * \param[in] cib CIB to check + * \param[in] node Name of node to check + * + * \return Value of shutdown attribute for \p node in \p cib if any, + * otherwise NULL + * \note The return value is a pointer into \p cib and so is valid only for the + * lifetime of that object. + */ +const char * +pcmk_cib_node_shutdown(xmlNode *cib, const char *node) +{ + if ((cib != NULL) && (node != NULL)) { + char *xpath = crm_strdup_printf(XP_SHUTDOWN, node); + xmlNode *match = get_xpath_object(xpath, cib, LOG_TRACE); + + free(xpath); + if (match != NULL) { + return crm_element_value(match, PCMK_XA_VALUE); + } + } + return NULL; +} diff --git a/lib/common/tests/nodes/Makefile.am b/lib/common/tests/nodes/Makefile.am index f52c615e4d..6c4964e1d0 100644 --- a/lib/common/tests/nodes/Makefile.am +++ b/lib/common/tests/nodes/Makefile.am @@ -1,23 +1,24 @@ # # 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__find_node_in_list_test \ + pcmk__xe_add_node_test \ + pcmk_cib_node_shutdown_test \ 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 + pcmk_node_is_shutting_down_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/nodes/pcmk_cib_node_shutdown_test.c b/lib/common/tests/nodes/pcmk_cib_node_shutdown_test.c new file mode 100644 index 0000000000..a64a7283e8 --- /dev/null +++ b/lib/common/tests/nodes/pcmk_cib_node_shutdown_test.c @@ -0,0 +1,70 @@ +/* + * 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 // xmlNode + +#include +#include + +// Minimum CIB structure needed for function's XPath search +#define CIB_XML \ + "<" PCMK_XE_CIB ">" \ + "<" PCMK_XE_STATUS ">" \ + "<" PCMK__XE_NODE_STATE " " PCMK_XA_UNAME "='node1'>" \ + "<" PCMK__XE_TRANSIENT_ATTRIBUTES ">" \ + "<" PCMK_XE_INSTANCE_ATTRIBUTES ">" \ + "<" PCMK_XE_NVPAIR " " \ + PCMK_XA_NAME "='" PCMK__NODE_ATTR_SHUTDOWN "' " \ + PCMK_XA_VALUE "='999'/>" \ + "" \ + "" \ + "" \ + "" \ + "" + +static void +null_args(void **state) +{ + xmlNode *xml = pcmk__xml_parse(CIB_XML); + + assert_non_null(xml); + assert_null(pcmk_cib_node_shutdown(NULL, NULL)); + assert_null(pcmk_cib_node_shutdown(xml, NULL)); + assert_null(pcmk_cib_node_shutdown(NULL, "node1")); + free_xml(xml); +} + +static void +shutdown_absent(void **state) +{ + xmlNode *xml = pcmk__xml_parse(CIB_XML); + + assert_non_null(xml); + assert_null(pcmk_cib_node_shutdown(xml, "node")); + assert_null(pcmk_cib_node_shutdown(xml, "node10")); + free_xml(xml); +} + +static void +shutdown_present(void **state) +{ + xmlNode *xml = pcmk__xml_parse(CIB_XML); + + assert_non_null(xml); + assert_string_equal(pcmk_cib_node_shutdown(xml, "node1"), "999"); + free_xml(xml); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(null_args), + cmocka_unit_test(shutdown_absent), + cmocka_unit_test(shutdown_present))