diff --git a/include/crm/common/nodes.h b/include/crm/common/nodes.h index ecf75aae58..c4acb91c4b 100644 --- a/include/crm/common/nodes.h +++ b/include/crm/common/nodes.h @@ -1,102 +1,105 @@ /* * 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" //! \internal Do not use typedef struct pcmk__node_private pcmk__node_private_t; // Basic node information (all node objects for the same node share this) // @COMPAT Drop this struct once all members are moved to pcmk__node_private_t //!@{ //! \deprecated Do not use (public access will be removed in a future release) struct pcmk__node_details { /* @COMPAT Convert these gbooleans into new enum pcmk__node_flags values * when we no longer support versions of sbd that use them */ // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Call pcmk_node_is_online() instead gboolean online; // Whether online // 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 // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Call pcmk_node_is_shutting_down() instead gboolean shutdown; // Whether shutting down // 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 // 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 }; //!@} // 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 pcmk__scored_node { struct pcmk__node_assignment *assign; // NOTE: sbd (as of at least 1.5.2) uses this struct pcmk__node_details *details; // Basic node information //! \internal Do not use pcmk__node_private_t *priv; }; //!@} 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); + #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_NODES__H diff --git a/lib/common/nodes.c b/lib/common/nodes.c index 1809814941..13b97d4c99 100644 --- a/lib/common/nodes.c +++ b/lib/common/nodes.c @@ -1,162 +1,192 @@ /* * 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->priv->name, 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..274279eb8e --- /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")); + pcmk__xml_free(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")); + pcmk__xml_free(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"); + pcmk__xml_free(xml); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(null_args), + cmocka_unit_test(shutdown_absent), + cmocka_unit_test(shutdown_present))