diff --git a/daemons/fenced/pacemaker-fenced.c b/daemons/fenced/pacemaker-fenced.c index 092d872d59..a199803501 100644 --- a/daemons/fenced/pacemaker-fenced.c +++ b/daemons/fenced/pacemaker-fenced.c @@ -1,1751 +1,1752 @@ /* * Copyright 2009-2023 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 #include #include #include #include #include #include #include #include #include // PRIu32, PRIx32 #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #define SUMMARY "daemon for executing fencing devices in a Pacemaker cluster" char *stonith_our_uname = NULL; long stonith_watchdog_timeout_ms = 0; GList *stonith_watchdog_targets = NULL; static GMainLoop *mainloop = NULL; gboolean stand_alone = FALSE; static gboolean stonith_shutdown_flag = FALSE; static qb_ipcs_service_t *ipcs = NULL; static xmlNode *local_cib = NULL; static pe_working_set_t *fenced_data_set = NULL; static const unsigned long long data_set_flags = pe_flag_quick_location | pe_flag_no_compat | pe_flag_no_counts; static cib_t *cib_api = NULL; static pcmk__output_t *logger_out = NULL; static pcmk__output_t *out = NULL; pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; static struct { bool no_cib_connect; gchar **log_files; } options; static crm_exit_t exit_code = CRM_EX_OK; static void stonith_shutdown(int nsig); static void stonith_cleanup(void); static int32_t st_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { if (stonith_shutdown_flag) { crm_info("Ignoring new client [%d] during shutdown", pcmk__client_pid(c)); return -EPERM; } if (pcmk__new_client(c, uid, gid) == NULL) { return -EIO; } return 0; } /* Exit code means? */ static int32_t st_ipc_dispatch(qb_ipcs_connection_t * qbc, void *data, size_t size) { uint32_t id = 0; uint32_t flags = 0; int call_options = 0; xmlNode *request = NULL; pcmk__client_t *c = pcmk__find_client(qbc); const char *op = NULL; if (c == NULL) { crm_info("Invalid client: %p", qbc); return 0; } request = pcmk__client_data2xml(c, data, &id, &flags); if (request == NULL) { pcmk__ipc_send_ack(c, id, flags, "nack", NULL, CRM_EX_PROTOCOL); return 0; } op = crm_element_value(request, F_CRM_TASK); if(pcmk__str_eq(op, CRM_OP_RM_NODE_CACHE, pcmk__str_casei)) { crm_xml_add(request, F_TYPE, T_STONITH_NG); crm_xml_add(request, F_STONITH_OPERATION, op); crm_xml_add(request, F_STONITH_CLIENTID, c->id); crm_xml_add(request, F_STONITH_CLIENTNAME, pcmk__client_name(c)); crm_xml_add(request, F_STONITH_CLIENTNODE, stonith_our_uname); send_cluster_message(NULL, crm_msg_stonith_ng, request, FALSE); free_xml(request); return 0; } if (c->name == NULL) { const char *value = crm_element_value(request, F_STONITH_CLIENTNAME); if (value == NULL) { value = "unknown"; } c->name = crm_strdup_printf("%s.%u", value, c->pid); } crm_element_value_int(request, F_STONITH_CALLOPTS, &call_options); crm_trace("Flags %#08" PRIx32 "/%#08x for command %" PRIu32 " from client %s", flags, call_options, id, pcmk__client_name(c)); if (pcmk_is_set(call_options, st_opt_sync_call)) { CRM_ASSERT(flags & crm_ipc_client_response); CRM_LOG_ASSERT(c->request_id == 0); /* This means the client has two synchronous events in-flight */ c->request_id = id; /* Reply only to the last one */ } crm_xml_add(request, F_STONITH_CLIENTID, c->id); crm_xml_add(request, F_STONITH_CLIENTNAME, pcmk__client_name(c)); crm_xml_add(request, F_STONITH_CLIENTNODE, stonith_our_uname); crm_log_xml_trace(request, "ipc-received"); stonith_command(c, id, flags, request, NULL); free_xml(request); return 0; } /* Error code means? */ static int32_t st_ipc_closed(qb_ipcs_connection_t * c) { pcmk__client_t *client = pcmk__find_client(c); if (client == NULL) { return 0; } crm_trace("Connection %p closed", c); pcmk__free_client(client); /* 0 means: yes, go ahead and destroy the connection */ return 0; } static void st_ipc_destroy(qb_ipcs_connection_t * c) { crm_trace("Connection %p destroyed", c); st_ipc_closed(c); } static void stonith_peer_callback(xmlNode * msg, void *private_data) { const char *remote_peer = crm_element_value(msg, F_ORIG); const char *op = crm_element_value(msg, F_STONITH_OPERATION); if (pcmk__str_eq(op, "poke", pcmk__str_none)) { return; } crm_log_xml_trace(msg, "Peer[inbound]"); stonith_command(NULL, 0, 0, msg, remote_peer); } #if SUPPORT_COROSYNC static void stonith_peer_ais_callback(cpg_handle_t handle, const struct cpg_name *groupName, uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len) { uint32_t kind = 0; xmlNode *xml = NULL; const char *from = NULL; char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from); if(data == NULL) { return; } if (kind == crm_class_cluster) { xml = string2xml(data); if (xml == NULL) { crm_err("Invalid XML: '%.120s'", data); free(data); return; } crm_xml_add(xml, F_ORIG, from); /* crm_xml_add_int(xml, F_SEQ, wrapper->id); */ stonith_peer_callback(xml, NULL); } free_xml(xml); free(data); return; } static void stonith_peer_cs_destroy(gpointer user_data) { crm_crit("Lost connection to cluster layer, shutting down"); stonith_shutdown(0); } #endif void do_local_reply(xmlNode *notify_src, pcmk__client_t *client, int call_options) { /* send callback to originating child */ int local_rc = pcmk_rc_ok; int rid = 0; uint32_t ipc_flags = crm_ipc_server_event; if (pcmk_is_set(call_options, st_opt_sync_call)) { CRM_LOG_ASSERT(client->request_id); rid = client->request_id; client->request_id = 0; ipc_flags = crm_ipc_flags_none; } local_rc = pcmk__ipc_send_xml(client, rid, notify_src, ipc_flags); if (local_rc == pcmk_rc_ok) { crm_trace("Sent response %d to client %s", rid, pcmk__client_name(client)); } else { crm_warn("%synchronous reply to client %s failed: %s", (pcmk_is_set(call_options, st_opt_sync_call)? "S" : "As"), pcmk__client_name(client), pcmk_rc_str(local_rc)); } } uint64_t get_stonith_flag(const char *name) { if (pcmk__str_eq(name, T_STONITH_NOTIFY_FENCE, pcmk__str_casei)) { return st_callback_notify_fence; } else if (pcmk__str_eq(name, STONITH_OP_DEVICE_ADD, pcmk__str_casei)) { return st_callback_device_add; } else if (pcmk__str_eq(name, STONITH_OP_DEVICE_DEL, pcmk__str_casei)) { return st_callback_device_del; } else if (pcmk__str_eq(name, T_STONITH_NOTIFY_HISTORY, pcmk__str_casei)) { return st_callback_notify_history; } else if (pcmk__str_eq(name, T_STONITH_NOTIFY_HISTORY_SYNCED, pcmk__str_casei)) { return st_callback_notify_history_synced; } return st_callback_unknown; } static void stonith_notify_client(gpointer key, gpointer value, gpointer user_data) { xmlNode *update_msg = user_data; pcmk__client_t *client = value; const char *type = NULL; CRM_CHECK(client != NULL, return); CRM_CHECK(update_msg != NULL, return); type = crm_element_value(update_msg, F_SUBTYPE); CRM_CHECK(type != NULL, crm_log_xml_err(update_msg, "notify"); return); if (client->ipcs == NULL) { crm_trace("Skipping client with NULL channel"); return; } if (pcmk_is_set(client->flags, get_stonith_flag(type))) { int rc = pcmk__ipc_send_xml(client, 0, update_msg, crm_ipc_server_event); if (rc != pcmk_rc_ok) { crm_warn("%s notification of client %s failed: %s " CRM_XS " id=%.8s rc=%d", type, pcmk__client_name(client), pcmk_rc_str(rc), client->id, rc); } else { crm_trace("Sent %s notification to client %s", type, pcmk__client_name(client)); } } } void do_stonith_async_timeout_update(const char *client_id, const char *call_id, int timeout) { pcmk__client_t *client = NULL; xmlNode *notify_data = NULL; if (!timeout || !call_id || !client_id) { return; } client = pcmk__find_client_by_id(client_id); if (!client) { return; } notify_data = create_xml_node(NULL, T_STONITH_TIMEOUT_VALUE); crm_xml_add(notify_data, F_TYPE, T_STONITH_TIMEOUT_VALUE); crm_xml_add(notify_data, F_STONITH_CALLID, call_id); crm_xml_add_int(notify_data, F_STONITH_TIMEOUT, timeout); crm_trace("timeout update is %d for client %s and call id %s", timeout, client_id, call_id); if (client) { pcmk__ipc_send_xml(client, 0, notify_data, crm_ipc_server_event); } free_xml(notify_data); } /*! * \internal * \brief Notify relevant IPC clients of a fencing operation result * * \param[in] type Notification type * \param[in] result Result of fencing operation (assume success if NULL) * \param[in] data If not NULL, add to notification as call data */ void fenced_send_notification(const char *type, const pcmk__action_result_t *result, xmlNode *data) { /* TODO: Standardize the contents of data */ xmlNode *update_msg = create_xml_node(NULL, "notify"); CRM_LOG_ASSERT(type != NULL); crm_xml_add(update_msg, F_TYPE, T_STONITH_NOTIFY); crm_xml_add(update_msg, F_SUBTYPE, type); crm_xml_add(update_msg, F_STONITH_OPERATION, type); stonith__xe_set_result(update_msg, result); if (data != NULL) { add_message_xml(update_msg, F_STONITH_CALLDATA, data); } crm_trace("Notifying clients"); pcmk__foreach_ipc_client(stonith_notify_client, update_msg); free_xml(update_msg); crm_trace("Notify complete"); } /*! * \internal * \brief Send notifications for a configuration change to subscribed clients * * \param[in] op Notification type (STONITH_OP_DEVICE_ADD, * STONITH_OP_DEVICE_DEL, STONITH_OP_LEVEL_ADD, or * STONITH_OP_LEVEL_DEL) * \param[in] result Operation result * \param[in] desc Description of what changed * \param[in] active Current number of devices or topologies in use */ static void send_config_notification(const char *op, const pcmk__action_result_t *result, const char *desc, int active) { xmlNode *notify_data = create_xml_node(NULL, op); CRM_CHECK(notify_data != NULL, return); crm_xml_add(notify_data, F_STONITH_DEVICE, desc); crm_xml_add_int(notify_data, F_STONITH_ACTIVE, active); fenced_send_notification(op, result, notify_data); free_xml(notify_data); } /*! * \internal * \brief Send notifications for a device change to subscribed clients * * \param[in] op Notification type (STONITH_OP_DEVICE_ADD or * STONITH_OP_DEVICE_DEL) * \param[in] result Operation result * \param[in] desc ID of device that changed */ void fenced_send_device_notification(const char *op, const pcmk__action_result_t *result, const char *desc) { send_config_notification(op, result, desc, g_hash_table_size(device_list)); } /*! * \internal * \brief Send notifications for a topology level change to subscribed clients * * \param[in] op Notification type (STONITH_OP_LEVEL_ADD or * STONITH_OP_LEVEL_DEL) * \param[in] result Operation result * \param[in] desc String representation of level ([]) */ void fenced_send_level_notification(const char *op, const pcmk__action_result_t *result, const char *desc) { send_config_notification(op, result, desc, g_hash_table_size(topology)); } static void topology_remove_helper(const char *node, int level) { char *desc = NULL; pcmk__action_result_t result = PCMK__UNKNOWN_RESULT; xmlNode *data = create_xml_node(NULL, XML_TAG_FENCING_LEVEL); crm_xml_add(data, F_STONITH_ORIGIN, __func__); crm_xml_add_int(data, XML_ATTR_STONITH_INDEX, level); crm_xml_add(data, XML_ATTR_STONITH_TARGET, node); fenced_unregister_level(data, &desc, &result); fenced_send_level_notification(STONITH_OP_LEVEL_DEL, &result, desc); pcmk__reset_result(&result); free_xml(data); free(desc); } static void remove_cib_device(xmlXPathObjectPtr xpathObj) { int max = numXpathResults(xpathObj), lpc = 0; for (lpc = 0; lpc < max; lpc++) { const char *rsc_id = NULL; const char *standard = NULL; xmlNode *match = getXpathResult(xpathObj, lpc); CRM_LOG_ASSERT(match != NULL); if(match != NULL) { standard = crm_element_value(match, XML_AGENT_ATTR_CLASS); } if (!pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) { continue; } rsc_id = crm_element_value(match, XML_ATTR_ID); stonith_device_remove(rsc_id, true); } } static void remove_topology_level(xmlNode *match) { int index = 0; char *key = NULL; CRM_CHECK(match != NULL, return); key = stonith_level_key(match, fenced_target_by_unknown); crm_element_value_int(match, XML_ATTR_STONITH_INDEX, &index); topology_remove_helper(key, index); free(key); } static void add_topology_level(xmlNode *match) { char *desc = NULL; pcmk__action_result_t result = PCMK__UNKNOWN_RESULT; CRM_CHECK(match != NULL, return); fenced_register_level(match, &desc, &result); fenced_send_level_notification(STONITH_OP_LEVEL_ADD, &result, desc); pcmk__reset_result(&result); free(desc); } static void remove_fencing_topology(xmlXPathObjectPtr xpathObj) { int max = numXpathResults(xpathObj), lpc = 0; for (lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpathObj, lpc); CRM_LOG_ASSERT(match != NULL); if (match && crm_element_value(match, XML_DIFF_MARKER)) { /* Deletion */ int index = 0; char *target = stonith_level_key(match, fenced_target_by_unknown); crm_element_value_int(match, XML_ATTR_STONITH_INDEX, &index); if (target == NULL) { crm_err("Invalid fencing target in element %s", ID(match)); } else if (index <= 0) { crm_err("Invalid level for %s in element %s", target, ID(match)); } else { topology_remove_helper(target, index); } /* } else { Deal with modifications during the 'addition' stage */ } } } static void register_fencing_topology(xmlXPathObjectPtr xpathObj) { int max = numXpathResults(xpathObj), lpc = 0; for (lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpathObj, lpc); remove_topology_level(match); add_topology_level(match); } } /* Fencing */ static void fencing_topology_init(void) { xmlXPathObjectPtr xpathObj = NULL; const char *xpath = "//" XML_TAG_FENCING_LEVEL; crm_trace("Full topology refresh"); free_topology_list(); init_topology_list(); /* Grab everything */ xpathObj = xpath_search(local_cib, xpath); register_fencing_topology(xpathObj); freeXpathObject(xpathObj); } #define rsc_name(x) x->clone_name?x->clone_name:x->id /*! * \internal * \brief Check whether our uname is in a resource's allowed node list * * \param[in] rsc Resource to check * * \return Pointer to node object if found, NULL otherwise */ static pe_node_t * our_node_allowed_for(const pe_resource_t *rsc) { GHashTableIter iter; pe_node_t *node = NULL; if (rsc && stonith_our_uname) { g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { if (node && strcmp(node->details->uname, stonith_our_uname) == 0) { break; } node = NULL; } } return node; } static void watchdog_device_update(void) { if (stonith_watchdog_timeout_ms > 0) { if (!g_hash_table_lookup(device_list, STONITH_WATCHDOG_ID) && !stonith_watchdog_targets) { /* getting here watchdog-fencing enabled, no device there yet and reason isn't stonith_watchdog_targets preventing that */ int rc; xmlNode *xml; xml = create_device_registration_xml( STONITH_WATCHDOG_ID, st_namespace_internal, STONITH_WATCHDOG_AGENT, NULL, /* stonith_device_register will add our own name as PCMK_STONITH_HOST_LIST param so we can skip that here */ NULL); rc = stonith_device_register(xml, TRUE); free_xml(xml); if (rc != pcmk_ok) { rc = pcmk_legacy2rc(rc); exit_code = CRM_EX_FATAL; crm_crit("Cannot register watchdog pseudo fence agent: %s", pcmk_rc_str(rc)); stonith_shutdown(0); } } } else if (g_hash_table_lookup(device_list, STONITH_WATCHDOG_ID) != NULL) { /* be silent if no device - todo parameter to stonith_device_remove */ stonith_device_remove(STONITH_WATCHDOG_ID, true); } } static void update_stonith_watchdog_timeout_ms(xmlNode *cib) { long timeout_ms = 0; xmlNode *stonith_watchdog_xml = NULL; const char *value = NULL; stonith_watchdog_xml = get_xpath_object("//nvpair[@name='stonith-watchdog-timeout']", cib, LOG_NEVER); if (stonith_watchdog_xml) { value = crm_element_value(stonith_watchdog_xml, XML_NVPAIR_ATTR_VALUE); } if (value) { timeout_ms = crm_get_msec(value); } if (timeout_ms < 0) { timeout_ms = pcmk__auto_watchdog_timeout(); } stonith_watchdog_timeout_ms = timeout_ms; } /*! * \internal * \brief If a resource or any of its children are STONITH devices, update their * definitions given a cluster working set. * * \param[in,out] rsc Resource to check * \param[in,out] data_set Cluster working set with device information */ static void cib_device_update(pe_resource_t *rsc, pe_working_set_t *data_set) { pe_node_t *node = NULL; const char *value = NULL; const char *rclass = NULL; pe_node_t *parent = NULL; /* If this is a complex resource, check children rather than this resource itself. */ if(rsc->children) { GList *gIter = NULL; for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) { cib_device_update(gIter->data, data_set); if(pe_rsc_is_clone(rsc)) { crm_trace("Only processing one copy of the clone %s", rsc->id); break; } } return; } /* We only care about STONITH resources. */ rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); if (!pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) { return; } /* If this STONITH resource is disabled, remove it. */ if (pe__resource_is_disabled(rsc)) { crm_info("Device %s has been disabled", rsc->id); return; } /* if watchdog-fencing is disabled handle any watchdog-fence resource as if it was disabled */ if ((stonith_watchdog_timeout_ms <= 0) && pcmk__str_eq(rsc->id, STONITH_WATCHDOG_ID, pcmk__str_none)) { crm_info("Watchdog-fencing disabled thus handling " "device %s as disabled", rsc->id); return; } /* Check whether our node is allowed for this resource (and its parent if in a group) */ node = our_node_allowed_for(rsc); if (rsc->parent && (rsc->parent->variant == pe_group)) { parent = our_node_allowed_for(rsc->parent); } if(node == NULL) { /* Our node is disallowed, so remove the device */ GHashTableIter iter; crm_info("Device %s has been disabled on %s: unknown", rsc->id, stonith_our_uname); g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { crm_trace("Available: %s = %d", pe__node_name(node), node->weight); } return; } else if(node->weight < 0 || (parent && parent->weight < 0)) { /* Our node (or its group) is disallowed by score, so remove the device */ int score = (node->weight < 0)? node->weight : parent->weight; crm_info("Device %s has been disabled on %s: score=%s", rsc->id, stonith_our_uname, pcmk_readable_score(score)); return; } else { /* Our node is allowed, so update the device information */ int rc; xmlNode *data; GHashTable *rsc_params = NULL; GHashTableIter gIter; stonith_key_value_t *params = NULL; const char *name = NULL; const char *agent = crm_element_value(rsc->xml, XML_EXPR_ATTR_TYPE); const char *rsc_provides = NULL; crm_debug("Device %s is allowed on %s: score=%d", rsc->id, stonith_our_uname, node->weight); rsc_params = pe_rsc_params(rsc, node, data_set); get_meta_attributes(rsc->meta, rsc, node, data_set); rsc_provides = g_hash_table_lookup(rsc->meta, PCMK_STONITH_PROVIDES); g_hash_table_iter_init(&gIter, rsc_params); while (g_hash_table_iter_next(&gIter, (gpointer *) & name, (gpointer *) & value)) { if (!name || !value) { continue; } params = stonith_key_value_add(params, name, value); crm_trace(" %s=%s", name, value); } data = create_device_registration_xml(rsc_name(rsc), st_namespace_any, agent, params, rsc_provides); stonith_key_value_freeall(params, 1, 1); rc = stonith_device_register(data, TRUE); CRM_ASSERT(rc == pcmk_ok); free_xml(data); } } /*! * \internal * \brief Update all STONITH device definitions based on current CIB */ static void cib_devices_update(void) { GHashTableIter iter; stonith_device_t *device = NULL; crm_info("Updating devices to version %s.%s.%s", crm_element_value(local_cib, XML_ATTR_GENERATION_ADMIN), crm_element_value(local_cib, XML_ATTR_GENERATION), crm_element_value(local_cib, XML_ATTR_NUMUPDATES)); if (fenced_data_set->now != NULL) { crm_time_free(fenced_data_set->now); fenced_data_set->now = NULL; } fenced_data_set->localhost = stonith_our_uname; pcmk__schedule_actions(local_cib, data_set_flags, fenced_data_set); g_hash_table_iter_init(&iter, device_list); while (g_hash_table_iter_next(&iter, NULL, (void **)&device)) { if (device->cib_registered) { device->dirty = TRUE; } } /* have list repopulated if cib has a watchdog-fencing-resource TODO: keep a cached list for queries happening while we are refreshing */ g_list_free_full(stonith_watchdog_targets, free); stonith_watchdog_targets = NULL; g_list_foreach(fenced_data_set->resources, (GFunc) cib_device_update, fenced_data_set); g_hash_table_iter_init(&iter, device_list); while (g_hash_table_iter_next(&iter, NULL, (void **)&device)) { if (device->dirty) { g_hash_table_iter_remove(&iter); } } fenced_data_set->input = NULL; // Wasn't a copy, so don't let API free it pe_reset_working_set(fenced_data_set); } static void update_cib_stonith_devices_v2(const char *event, xmlNode * msg) { xmlNode *change = NULL; char *reason = NULL; bool needs_update = FALSE; xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT); for (change = pcmk__xml_first_child(patchset); change != NULL; change = pcmk__xml_next(change)) { const char *op = crm_element_value(change, XML_DIFF_OP); const char *xpath = crm_element_value(change, XML_DIFF_PATH); const char *shortpath = NULL; if ((op == NULL) || (strcmp(op, "move") == 0) || strstr(xpath, "/"XML_CIB_TAG_STATUS)) { continue; } else if (pcmk__str_eq(op, "delete", pcmk__str_casei) && strstr(xpath, "/"XML_CIB_TAG_RESOURCE)) { const char *rsc_id = NULL; char *search = NULL; char *mutable = NULL; if (strstr(xpath, XML_TAG_ATTR_SETS) || strstr(xpath, XML_TAG_META_SETS)) { needs_update = TRUE; pcmk__str_update(&reason, "(meta) attribute deleted from resource"); break; } pcmk__str_update(&mutable, xpath); rsc_id = strstr(mutable, "primitive[@" XML_ATTR_ID "=\'"); if (rsc_id != NULL) { rsc_id += strlen("primitive[@" XML_ATTR_ID "=\'"); search = strchr(rsc_id, '\''); } if (search != NULL) { *search = 0; stonith_device_remove(rsc_id, true); /* watchdog_device_update called afterwards to fall back to implicit definition if needed */ } else { crm_warn("Ignoring malformed CIB update (resource deletion)"); } free(mutable); } else if (strstr(xpath, "/"XML_CIB_TAG_RESOURCES) || strstr(xpath, "/"XML_CIB_TAG_CONSTRAINTS) || strstr(xpath, "/"XML_CIB_TAG_RSCCONFIG)) { shortpath = strrchr(xpath, '/'); CRM_ASSERT(shortpath); reason = crm_strdup_printf("%s %s", op, shortpath+1); needs_update = TRUE; break; } } if(needs_update) { crm_info("Updating device list from CIB: %s", reason); cib_devices_update(); } else { crm_trace("No updates for device list found in CIB"); } free(reason); } static void update_cib_stonith_devices_v1(const char *event, xmlNode * msg) { const char *reason = "none"; gboolean needs_update = FALSE; xmlXPathObjectPtr xpath_obj = NULL; /* process new constraints */ xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_CONS_TAG_RSC_LOCATION); if (numXpathResults(xpath_obj) > 0) { int max = numXpathResults(xpath_obj), lpc = 0; /* Safest and simplest to always recompute */ needs_update = TRUE; reason = "new location constraint"; for (lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpath_obj, lpc); crm_log_xml_trace(match, "new constraint"); } } freeXpathObject(xpath_obj); /* process deletions */ xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_REMOVED "//" XML_CIB_TAG_RESOURCE); if (numXpathResults(xpath_obj) > 0) { remove_cib_device(xpath_obj); } freeXpathObject(xpath_obj); /* process additions */ xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_CIB_TAG_RESOURCE); if (numXpathResults(xpath_obj) > 0) { int max = numXpathResults(xpath_obj), lpc = 0; for (lpc = 0; lpc < max; lpc++) { const char *rsc_id = NULL; const char *standard = NULL; xmlNode *match = getXpathResult(xpath_obj, lpc); rsc_id = crm_element_value(match, XML_ATTR_ID); standard = crm_element_value(match, XML_AGENT_ATTR_CLASS); if (!pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) { continue; } crm_trace("Fencing resource %s was added or modified", rsc_id); reason = "new resource"; needs_update = TRUE; } } freeXpathObject(xpath_obj); if(needs_update) { crm_info("Updating device list from CIB: %s", reason); cib_devices_update(); } } static void update_cib_stonith_devices(const char *event, xmlNode * msg) { int format = 1; xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT); CRM_ASSERT(patchset); crm_element_value_int(patchset, "format", &format); switch(format) { case 1: update_cib_stonith_devices_v1(event, msg); break; case 2: update_cib_stonith_devices_v2(event, msg); break; default: crm_warn("Unknown patch format: %d", format); } } /*! * \internal * \brief Check whether a node has a specific attribute name/value * * \param[in] node Name of node to check * \param[in] name Name of an attribute to look for * \param[in] value The value the named attribute needs to be set to in order to be considered a match * * \return TRUE if the locally cached CIB has the specified node attribute */ gboolean node_has_attr(const char *node, const char *name, const char *value) { GString *xpath = NULL; xmlNode *match; CRM_CHECK((local_cib != NULL) && (node != NULL) && (name != NULL) && (value != NULL), return FALSE); /* Search for the node's attributes in the CIB. While the schema allows * multiple sets of instance attributes, and allows instance attributes to * use id-ref to reference values elsewhere, that is intended for resources, * so we ignore that here. */ xpath = g_string_sized_new(256); pcmk__g_strcat(xpath, "//" XML_CIB_TAG_NODES "/" XML_CIB_TAG_NODE "[@" XML_ATTR_UNAME "='", node, "']/" XML_TAG_ATTR_SETS "/" XML_CIB_TAG_NVPAIR "[@" XML_NVPAIR_ATTR_NAME "='", name, "' " "and @" XML_NVPAIR_ATTR_VALUE "='", value, "']", NULL); match = get_xpath_object((const char *) xpath->str, local_cib, LOG_NEVER); g_string_free(xpath, TRUE); return (match != NULL); } /*! * \internal * \brief Check whether a node does watchdog-fencing * * \param[in] node Name of node to check * * \return TRUE if node found in stonith_watchdog_targets * or stonith_watchdog_targets is empty indicating * all nodes are doing watchdog-fencing */ gboolean node_does_watchdog_fencing(const char *node) { return ((stonith_watchdog_targets == NULL) || pcmk__str_in_list(node, stonith_watchdog_targets, pcmk__str_casei)); } static void update_fencing_topology(const char *event, xmlNode * msg) { int format = 1; const char *xpath; xmlXPathObjectPtr xpathObj = NULL; xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT); CRM_ASSERT(patchset); crm_element_value_int(patchset, "format", &format); if(format == 1) { /* Process deletions (only) */ xpath = "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_REMOVED "//" XML_TAG_FENCING_LEVEL; xpathObj = xpath_search(msg, xpath); remove_fencing_topology(xpathObj); freeXpathObject(xpathObj); /* Process additions and changes */ xpath = "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_TAG_FENCING_LEVEL; xpathObj = xpath_search(msg, xpath); register_fencing_topology(xpathObj); freeXpathObject(xpathObj); } else if(format == 2) { xmlNode *change = NULL; int add[] = { 0, 0, 0 }; int del[] = { 0, 0, 0 }; xml_patch_versions(patchset, add, del); for (change = pcmk__xml_first_child(patchset); change != NULL; change = pcmk__xml_next(change)) { const char *op = crm_element_value(change, XML_DIFF_OP); const char *xpath = crm_element_value(change, XML_DIFF_PATH); if(op == NULL) { continue; } else if(strstr(xpath, "/" XML_TAG_FENCING_LEVEL) != NULL) { /* Change to a specific entry */ crm_trace("Handling %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath); if(strcmp(op, "move") == 0) { continue; } else if(strcmp(op, "create") == 0) { add_topology_level(change->children); } else if(strcmp(op, "modify") == 0) { xmlNode *match = first_named_child(change, XML_DIFF_RESULT); if(match) { remove_topology_level(match->children); add_topology_level(match->children); } } else if(strcmp(op, "delete") == 0) { /* Nuclear option, all we have is the path and an id... not enough to remove a specific entry */ crm_info("Re-initializing fencing topology after %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath); fencing_topology_init(); return; } } else if (strstr(xpath, "/" XML_TAG_FENCING_TOPOLOGY) != NULL) { /* Change to the topology in general */ crm_info("Re-initializing fencing topology after top-level %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath); fencing_topology_init(); return; } else if (strstr(xpath, "/" XML_CIB_TAG_CONFIGURATION)) { /* Changes to the whole config section, possibly including the topology as a whild */ if(first_named_child(change, XML_TAG_FENCING_TOPOLOGY) == NULL) { crm_trace("Nothing for us in %s operation %d.%d.%d for %s.", op, add[0], add[1], add[2], xpath); } else if(strcmp(op, "delete") == 0 || strcmp(op, "create") == 0) { crm_info("Re-initializing fencing topology after top-level %s operation %d.%d.%d for %s.", op, add[0], add[1], add[2], xpath); fencing_topology_init(); return; } } else { crm_trace("Nothing for us in %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath); } } } else { crm_warn("Unknown patch format: %d", format); } } static bool have_cib_devices = FALSE; static void update_cib_cache_cb(const char *event, xmlNode * msg) { int rc = pcmk_ok; long timeout_ms_saved = stonith_watchdog_timeout_ms; bool need_full_refresh = false; if(!have_cib_devices) { crm_trace("Skipping updates until we get a full dump"); return; } else if(msg == NULL) { crm_trace("Missing %s update", event); return; } /* Maintain a local copy of the CIB so that we have full access * to device definitions, location constraints, and node attributes */ if (local_cib != NULL) { int rc = pcmk_ok; xmlNode *patchset = NULL; crm_element_value_int(msg, F_CIB_RC, &rc); if (rc != pcmk_ok) { return; } patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT); pcmk__output_set_log_level(logger_out, LOG_TRACE); out->message(out, "xml-patchset", patchset); rc = xml_apply_patchset(local_cib, patchset, TRUE); switch (rc) { case pcmk_ok: case -pcmk_err_old_data: break; case -pcmk_err_diff_resync: case -pcmk_err_diff_failed: crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc); free_xml(local_cib); local_cib = NULL; break; default: crm_warn("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc); free_xml(local_cib); local_cib = NULL; } } if (local_cib == NULL) { crm_trace("Re-requesting full CIB"); rc = cib_api->cmds->query(cib_api, NULL, &local_cib, cib_scope_local | cib_sync_call); if(rc != pcmk_ok) { crm_err("Couldn't retrieve the CIB: %s (%d)", pcmk_strerror(rc), rc); return; } CRM_ASSERT(local_cib != NULL); need_full_refresh = true; } pcmk__refresh_node_caches_from_cib(local_cib); update_stonith_watchdog_timeout_ms(local_cib); if (timeout_ms_saved != stonith_watchdog_timeout_ms) { need_full_refresh = true; } if (need_full_refresh) { fencing_topology_init(); cib_devices_update(); } else { // Partial refresh update_fencing_topology(event, msg); update_cib_stonith_devices(event, msg); } watchdog_device_update(); } static void init_cib_cache_cb(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data) { crm_info("Updating device list from CIB"); have_cib_devices = TRUE; local_cib = copy_xml(output); pcmk__refresh_node_caches_from_cib(local_cib); update_stonith_watchdog_timeout_ms(local_cib); fencing_topology_init(); cib_devices_update(); watchdog_device_update(); } static void stonith_shutdown(int nsig) { crm_info("Terminating with %d clients", pcmk__ipc_client_count()); stonith_shutdown_flag = TRUE; if (mainloop != NULL && g_main_loop_is_running(mainloop)) { g_main_loop_quit(mainloop); } } static void cib_connection_destroy(gpointer user_data) { if (stonith_shutdown_flag) { crm_info("Connection to the CIB manager closed"); return; } else { crm_crit("Lost connection to the CIB manager, shutting down"); } if (cib_api) { cib_api->cmds->signoff(cib_api); } stonith_shutdown(0); } static void stonith_cleanup(void) { if (cib_api) { cib_api->cmds->del_notify_callback(cib_api, T_CIB_DIFF_NOTIFY, update_cib_cache_cb); cib_api->cmds->signoff(cib_api); } if (ipcs) { qb_ipcs_destroy(ipcs); } crm_peer_destroy(); pcmk__client_cleanup(); free_stonith_remote_op_list(); free_topology_list(); free_device_list(); free_metadata_cache(); fenced_unregister_handlers(); free(stonith_our_uname); stonith_our_uname = NULL; free_xml(local_cib); local_cib = NULL; } static gboolean stand_alone_cpg_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { stand_alone = FALSE; options.no_cib_connect = true; return TRUE; } static void setup_cib(void) { int rc, retries = 0; cib_api = cib_new(); if (cib_api == NULL) { crm_err("No connection to the CIB manager"); return; } do { sleep(retries); rc = cib_api->cmds->signon(cib_api, CRM_SYSTEM_STONITHD, cib_command); } while (rc == -ENOTCONN && ++retries < 5); if (rc != pcmk_ok) { crm_err("Could not connect to the CIB manager: %s (%d)", pcmk_strerror(rc), rc); } else if (pcmk_ok != cib_api->cmds->add_notify_callback(cib_api, T_CIB_DIFF_NOTIFY, update_cib_cache_cb)) { crm_err("Could not set CIB notification callback"); } else { rc = cib_api->cmds->query(cib_api, NULL, NULL, cib_scope_local); cib_api->cmds->register_callback(cib_api, rc, 120, FALSE, NULL, "init_cib_cache_cb", init_cib_cache_cb); cib_api->cmds->set_connection_dnotify(cib_api, cib_connection_destroy); crm_info("Watching for fencing topology changes"); } } struct qb_ipcs_service_handlers ipc_callbacks = { .connection_accept = st_ipc_accept, .connection_created = NULL, .msg_process = st_ipc_dispatch, .connection_closed = st_ipc_closed, .connection_destroyed = st_ipc_destroy }; /*! * \internal * \brief Callback for peer status changes * * \param[in] type What changed * \param[in] node What peer had the change * \param[in] data Previous value of what changed */ static void st_peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *data) { if ((type != crm_status_processes) && !pcmk_is_set(node->flags, crm_remote_node)) { /* * This is a hack until we can send to a nodeid and/or we fix node name lookups * These messages are ignored in stonith_peer_callback() */ xmlNode *query = create_xml_node(NULL, "stonith_command"); crm_xml_add(query, F_XML_TAGNAME, "stonith_command"); crm_xml_add(query, F_TYPE, T_STONITH_NG); crm_xml_add(query, F_STONITH_OPERATION, "poke"); crm_debug("Broadcasting our uname because of node %u", node->id); send_cluster_message(NULL, crm_msg_stonith_ng, query, FALSE); free_xml(query); } } static pcmk__cluster_option_t fencer_options[] = { /* name, old name, type, allowed values, * default value, validator, * short description, * long description */ { PCMK_STONITH_HOST_ARGUMENT, NULL, "string", NULL, "port", NULL, N_("Advanced use only: An alternate parameter to supply instead of 'port'"), N_("some devices do not support the " "standard 'port' parameter or may provide additional ones. Use " "this to specify an alternate, device-specific, parameter " "that should indicate the machine to be fenced. A value of " "none can be used to tell the cluster not to supply any " "additional parameters.") }, { PCMK_STONITH_HOST_MAP,NULL, "string", NULL, "", NULL, N_("A mapping of host names to ports numbers for devices that do not support host names."), N_("Eg. node1:1;node2:2,3 would tell the cluster to use port 1 for node1 and ports 2 and 3 for node2") }, { PCMK_STONITH_HOST_LIST,NULL, "string", NULL, "", NULL, N_("Eg. node1,node2,node3"), N_("A list of machines controlled by " "this device (Optional unless pcmk_host_list=static-list)") }, { PCMK_STONITH_HOST_CHECK,NULL, "string", NULL, "dynamic-list", NULL, N_("How to determine which machines are controlled by the device."), N_("Allowed values: dynamic-list " "(query the device via the 'list' command), static-list " "(check the pcmk_host_list attribute), status " "(query the device via the 'status' command), " "none (assume every device can fence every " "machine)") }, { PCMK_STONITH_DELAY_MAX,NULL, "time", NULL, "0s", NULL, N_("Enable a base delay for fencing actions and specify base delay value."), N_("Enable a delay of no more than the " "time specified before executing fencing actions. Pacemaker " "derives the overall delay by taking the value of " "pcmk_delay_base and adding a random delay value such " "that the sum is kept below this maximum.") }, { PCMK_STONITH_DELAY_BASE,NULL, "string", NULL, "0s", NULL, N_("Enable a base delay for " "fencing actions and specify base delay value."), N_("This enables a static delay for " "fencing actions, which can help avoid \"death matches\" where " "two nodes try to fence each other at the same time. If " "pcmk_delay_max is also used, a random delay will be " "added such that the total delay is kept below that value." "This can be set to a single time value to apply to any node " "targeted by this device (useful if a separate device is " "configured for each target), or to a node map (for example, " "\"node1:1s;node2:5\") to set a different value per target.") }, { PCMK_STONITH_ACTION_LIMIT,NULL, "integer", NULL, "1", NULL, N_("The maximum number of actions can be performed in parallel on this device"), N_("Cluster property concurrent-fencing=true needs to be configured first." "Then use this to specify the maximum number of actions can be performed in parallel on this device. -1 is unlimited.") }, { "pcmk_reboot_action",NULL, "string", NULL, "reboot", NULL, N_("Advanced use only: An alternate command to run instead of 'reboot'"), N_("Some devices do not support the standard commands or may provide additional ones.\n" "Use this to specify an alternate, device-specific, command that implements the \'reboot\' action.") }, { "pcmk_reboot_timeout",NULL, "time", NULL, "60s", NULL, N_("Advanced use only: Specify an alternate timeout to use for reboot actions instead of stonith-timeout"), N_("Some devices need much more/less time to complete than normal." "Use this to specify an alternate, device-specific, timeout for \'reboot\' actions.") }, { "pcmk_reboot_retries",NULL, "integer", NULL, "2", NULL, N_("Advanced use only: The maximum number of times to retry the 'reboot' command within the timeout period"), N_("Some devices do not support multiple connections." " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining." " Use this option to alter the number of times Pacemaker retries \'reboot\' actions before giving up.") }, { "pcmk_off_action",NULL, "string", NULL, "off", NULL, N_("Advanced use only: An alternate command to run instead of \'off\'"), N_("Some devices do not support the standard commands or may provide additional ones." "Use this to specify an alternate, device-specific, command that implements the \'off\' action.") }, { "pcmk_off_timeout",NULL, "time", NULL, "60s", NULL, N_("Advanced use only: Specify an alternate timeout to use for off actions instead of stonith-timeout"), N_("Some devices need much more/less time to complete than normal." "Use this to specify an alternate, device-specific, timeout for \'off\' actions.") }, { "pcmk_off_retries",NULL, "integer", NULL, "2", NULL, N_("Advanced use only: The maximum number of times to retry the 'off' command within the timeout period"), N_("Some devices do not support multiple connections." " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining." " Use this option to alter the number of times Pacemaker retries \'off\' actions before giving up.") }, { "pcmk_on_action",NULL, "string", NULL, "on", NULL, N_("Advanced use only: An alternate command to run instead of 'on'"), N_("Some devices do not support the standard commands or may provide additional ones." "Use this to specify an alternate, device-specific, command that implements the \'on\' action.") }, { "pcmk_on_timeout",NULL, "time", NULL, "60s", NULL, N_("Advanced use only: Specify an alternate timeout to use for on actions instead of stonith-timeout"), N_("Some devices need much more/less time to complete than normal." "Use this to specify an alternate, device-specific, timeout for \'on\' actions.") }, { "pcmk_on_retries",NULL, "integer", NULL, "2", NULL, N_("Advanced use only: The maximum number of times to retry the 'on' command within the timeout period"), N_("Some devices do not support multiple connections." " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining." " Use this option to alter the number of times Pacemaker retries \'on\' actions before giving up.") }, { "pcmk_list_action",NULL, "string", NULL, "list", NULL, N_("Advanced use only: An alternate command to run instead of \'list\'"), N_("Some devices do not support the standard commands or may provide additional ones." "Use this to specify an alternate, device-specific, command that implements the \'list\' action.") }, { "pcmk_list_timeout",NULL, "time", NULL, "60s", NULL, N_("Advanced use only: Specify an alternate timeout to use for list actions instead of stonith-timeout"), N_("Some devices need much more/less time to complete than normal." "Use this to specify an alternate, device-specific, timeout for \'list\' actions.") }, { "pcmk_list_retries",NULL, "integer", NULL, "2", NULL, N_("Advanced use only: The maximum number of times to retry the \'list\' command within the timeout period"), N_("Some devices do not support multiple connections." " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining." " Use this option to alter the number of times Pacemaker retries \'list\' actions before giving up.") }, { "pcmk_monitor_action",NULL, "string", NULL, "monitor", NULL, N_("Advanced use only: An alternate command to run instead of \'monitor\'"), N_("Some devices do not support the standard commands or may provide additional ones." "Use this to specify an alternate, device-specific, command that implements the \'monitor\' action.") }, { "pcmk_monitor_timeout",NULL, "time", NULL, "60s", NULL, N_("Advanced use only: Specify an alternate timeout to use for monitor actions instead of stonith-timeout"), N_("Some devices need much more/less time to complete than normal.\n" "Use this to specify an alternate, device-specific, timeout for \'monitor\' actions.") }, { "pcmk_monitor_retries",NULL, "integer", NULL, "2", NULL, N_("Advanced use only: The maximum number of times to retry the \'monitor\' command within the timeout period"), N_("Some devices do not support multiple connections." " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining." " Use this option to alter the number of times Pacemaker retries \'monitor\' actions before giving up.") }, { "pcmk_status_action",NULL, "string", NULL, "status", NULL, N_("Advanced use only: An alternate command to run instead of \'status\'"), N_("Some devices do not support the standard commands or may provide additional ones." "Use this to specify an alternate, device-specific, command that implements the \'status\' action.") }, { "pcmk_status_timeout",NULL, "time", NULL, "60s", NULL, N_("Advanced use only: Specify an alternate timeout to use for status actions instead of stonith-timeout"), N_("Some devices need much more/less time to complete than normal." "Use this to specify an alternate, device-specific, timeout for \'status\' actions.") }, { "pcmk_status_retries",NULL, "integer", NULL, "2", NULL, N_("Advanced use only: The maximum number of times to retry the \'status\' command within the timeout period"), N_("Some devices do not support multiple connections." " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining." " Use this option to alter the number of times Pacemaker retries \'status\' actions before giving up.") }, }; void fencer_metadata(void) { const char *desc_short = N_("Instance attributes available for all " "\"stonith\"-class resources"); const char *desc_long = N_("Instance attributes available for all \"stonith\"-" "class resources and used by Pacemaker's fence " "daemon, formerly known as stonithd"); gchar *s = pcmk__format_option_metadata("pacemaker-fenced", desc_short, desc_long, fencer_options, PCMK__NELEM(fencer_options)); printf("%s", s); g_free(s); } static GOptionEntry entries[] = { { "stand-alone", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &stand_alone, N_("Deprecated (will be removed in a future release)"), NULL }, { "stand-alone-w-cpg", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, stand_alone_cpg_cb, N_("Intended for use in regression testing only"), NULL }, { "logfile", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY, &options.log_files, N_("Send logs to the additional named logfile"), NULL }, { NULL } }; static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; context = pcmk__build_arg_context(args, "text (default), xml", group, "[metadata]"); pcmk__add_main_args(context, entries); return context; } int main(int argc, char **argv) { int rc = pcmk_rc_ok; crm_cluster_t *cluster = NULL; crm_ipc_t *old_instance = NULL; GError *error = NULL; GOptionGroup *output_group = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "l"); GOptionContext *context = build_arg_context(args, &output_group); crm_log_preinit(NULL, argc, argv); pcmk__register_formats(output_group, formats); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); if (rc != pcmk_rc_ok) { exit_code = CRM_EX_ERROR; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s", args->output_ty, pcmk_rc_str(rc)); goto done; } if (args->version) { out->version(out, false); goto done; } if ((g_strv_length(processed_args) >= 2) && pcmk__str_eq(processed_args[1], "metadata", pcmk__str_none)) { fencer_metadata(); goto done; } // Open additional log files pcmk__add_logfiles(options.log_files, out); crm_log_init(NULL, LOG_INFO + args->verbosity, TRUE, (args->verbosity > 0), argc, argv, FALSE); crm_notice("Starting Pacemaker fencer"); old_instance = crm_ipc_new("stonith-ng", 0); if (old_instance == NULL) { /* crm_ipc_new() will have already logged an error message with * crm_err() */ exit_code = CRM_EX_FATAL; goto done; } if (crm_ipc_connect(old_instance)) { // IPC endpoint already up crm_ipc_close(old_instance); crm_ipc_destroy(old_instance); crm_err("pacemaker-fenced is already active, aborting startup"); goto done; } else { // Not up or not authentic, we'll proceed either way crm_ipc_destroy(old_instance); old_instance = NULL; } mainloop_add_signal(SIGTERM, stonith_shutdown); crm_peer_init(); fenced_data_set = pe_new_working_set(); CRM_ASSERT(fenced_data_set != NULL); cluster = pcmk_cluster_new(); /* Initialize the logger prior to setup_cib(). update_cib_cache_cb() may * call the "xml-patchset" message function, which needs the logger, after * setup_cib() has run. */ rc = pcmk__log_output_new(&logger_out) != pcmk_rc_ok; if (rc != pcmk_rc_ok) { exit_code = CRM_EX_FATAL; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format log: %s", pcmk_rc_str(rc)); goto done; } pe__register_messages(logger_out); pcmk__register_lib_messages(logger_out); pcmk__output_set_log_level(logger_out, LOG_TRACE); fenced_data_set->priv = logger_out; if (!stand_alone) { #if SUPPORT_COROSYNC if (is_corosync_cluster()) { cluster->destroy = stonith_peer_cs_destroy; cluster->cpg.cpg_deliver_fn = stonith_peer_ais_callback; cluster->cpg.cpg_confchg_fn = pcmk_cpg_membership; } #endif // SUPPORT_COROSYNC crm_set_status_callback(&st_peer_update_callback); if (crm_cluster_connect(cluster) == FALSE) { exit_code = CRM_EX_FATAL; crm_crit("Cannot sign in to the cluster... terminating"); goto done; } pcmk__str_update(&stonith_our_uname, cluster->uname); if (!options.no_cib_connect) { setup_cib(); } } else { pcmk__str_update(&stonith_our_uname, "localhost"); crm_warn("Stand-alone mode is deprecated and will be removed " "in a future release"); } init_device_list(); init_topology_list(); pcmk__serve_fenced_ipc(&ipcs, &ipc_callbacks); // Create the mainloop and run it... mainloop = g_main_loop_new(NULL, FALSE); crm_notice("Pacemaker fencer successfully started and accepting connections"); g_main_loop_run(mainloop); done: g_strfreev(processed_args); pcmk__free_arg_context(context); g_strfreev(options.log_files); stonith_cleanup(); pcmk_cluster_free(cluster); pe_free_working_set(fenced_data_set); pcmk__output_and_clear_error(&error, out); if (logger_out != NULL) { logger_out->finish(logger_out, exit_code, true, NULL); pcmk__output_free(logger_out); } if (out != NULL) { out->finish(out, exit_code, true, NULL); pcmk__output_free(out); } pcmk__unregister_formats(); crm_exit(exit_code); } diff --git a/include/pacemaker-internal.h b/include/pacemaker-internal.h index 8610d1ed4d..5cc904b845 100644 --- a/include/pacemaker-internal.h +++ b/include/pacemaker-internal.h @@ -1,27 +1,25 @@ /* - * Copyright 2019-2022 the Pacemaker project contributors + * Copyright 2019-2023 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 PACEMAKER_INTERNAL__H # define PACEMAKER_INTERNAL__H # include # include # include # include # include # include # include -# include -# include # include # include # include # include #endif diff --git a/include/pcmki/Makefile.am b/include/pcmki/Makefile.am index b379fdb0c0..824ef6c838 100644 --- a/include/pcmki/Makefile.am +++ b/include/pcmki/Makefile.am @@ -1,26 +1,24 @@ # -# Copyright 2019-2022 the Pacemaker project contributors +# Copyright 2019-2023 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. # MAINTAINERCLEANFILES = Makefile.in noinst_HEADERS = pcmki_acl.h \ pcmki_cluster_queries.h \ pcmki_fence.h \ pcmki_output.h \ pcmki_resource.h \ pcmki_result_code.h \ pcmki_rule.h \ - pcmki_sched_allocate.h \ - pcmki_sched_utils.h \ pcmki_scheduler.h \ pcmki_simulate.h \ pcmki_status.h \ pcmki_transition.h .PHONY: $(ARCHIVE_VERSION) diff --git a/include/pcmki/pcmki_sched_allocate.h b/include/pcmki/pcmki_sched_allocate.h deleted file mode 100644 index 83440fe305..0000000000 --- a/include/pcmki/pcmki_sched_allocate.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2004-2023 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__PCMKI_PCMKI_SCHED_ALLOCATE__H -# define PCMK__PCMKI_PCMKI_SCHED_ALLOCATE__H - -# include -# include -# include -# include -# include -# include -# include -# include - -void pcmk__log_transition_summary(const char *filename); - -#endif diff --git a/include/pcmki/pcmki_sched_utils.h b/include/pcmki/pcmki_sched_utils.h deleted file mode 100644 index 154856aea1..0000000000 --- a/include/pcmki/pcmki_sched_utils.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2004-2023 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__PCMKI_PCMKI_SCHED_UTILS__H -# define PCMK__PCMKI_PCMKI_SCHED_UTILS__H - -#include // bool -#include // GList, GHashTable, gboolean, guint -#include // lrmd_event_data_t -#include // cib_t -#include -#include -#include -#include -#include -#include - -/* Constraint helper functions */ -GList *pcmk__copy_node_list(const GList *list, bool reset); - -xmlNode *pcmk__create_history_xml(xmlNode *parent, lrmd_event_data_t *event, - const char *caller_version, int target_rc, - const char *node, const char *origin); - -#endif diff --git a/include/pcmki/pcmki_scheduler.h b/include/pcmki/pcmki_scheduler.h index dde50a57e3..1f0729eca6 100644 --- a/include/pcmki/pcmki_scheduler.h +++ b/include/pcmki/pcmki_scheduler.h @@ -1,43 +1,48 @@ /* * Copyright 2014-2023 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__PCMKI_PCMKI_SCHEDULER__H # define PCMK__PCMKI_PCMKI_SCHEDULER__H -# include -# include -# include -# include -# include -# include +# include // GList +# include // bool +# include // xmlNode -# include +# include +# include // lrmd_event_data_t +# include // pe_resource_t, pe_working_set_t typedef struct { const char *id; const char *node_attribute; pe_resource_t *dependent; // The resource being colocated pe_resource_t *primary; // The resource the dependent is colocated with int dependent_role; // Colocation applies only if dependent has this role int primary_role; // Colocation applies only if primary has this role int score; bool influence; // Whether dependent influences active primary placement } pcmk__colocation_t; void pcmk__unpack_constraints(pe_working_set_t *data_set); void pcmk__schedule_actions(xmlNode *cib, unsigned long long flags, pe_working_set_t *data_set); GList *pcmk__with_this_colocations(const pe_resource_t *rsc); GList *pcmk__this_with_colocations(const pe_resource_t *rsc); +GList *pcmk__copy_node_list(const GList *list, bool reset); + +xmlNode *pcmk__create_history_xml(xmlNode *parent, lrmd_event_data_t *event, + const char *caller_version, int target_rc, + const char *node, const char *origin); + #endif diff --git a/include/pcmki/pcmki_transition.h b/include/pcmki/pcmki_transition.h index 5dc310179d..85f2d61be9 100644 --- a/include/pcmki/pcmki_transition.h +++ b/include/pcmki/pcmki_transition.h @@ -1,176 +1,177 @@ /* * Copyright 2004-2023 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__PCMKI_PCMKI_TRANSITION__H # define PCMK__PCMKI_PCMKI_TRANSITION__H # include # include # include # include #ifdef __cplusplus extern "C" { #endif enum pcmk__graph_action_type { pcmk__pseudo_graph_action, pcmk__rsc_graph_action, pcmk__cluster_graph_action, }; enum pcmk__synapse_flags { pcmk__synapse_ready = (1 << 0), pcmk__synapse_failed = (1 << 1), pcmk__synapse_executed = (1 << 2), pcmk__synapse_confirmed = (1 << 3), }; typedef struct { int id; int priority; uint32_t flags; // Group of pcmk__synapse_flags GList *actions; /* pcmk__graph_action_t* */ GList *inputs; /* pcmk__graph_action_t* */ } pcmk__graph_synapse_t; #define pcmk__set_synapse_flags(synapse, flags_to_set) do { \ (synapse)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Synapse", "synapse", \ (synapse)->flags, (flags_to_set), #flags_to_set); \ } while (0) #define pcmk__clear_synapse_flags(synapse, flags_to_clear) do { \ (synapse)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Synapse", "synapse", \ (synapse)->flags, (flags_to_clear), #flags_to_clear); \ } while (0) enum pcmk__graph_action_flags { pcmk__graph_action_sent_update = (1 << 0), /* sent to the CIB */ pcmk__graph_action_executed = (1 << 1), /* sent to the CRM */ pcmk__graph_action_confirmed = (1 << 2), pcmk__graph_action_failed = (1 << 3), pcmk__graph_action_can_fail = (1 << 4), //! \deprecated Will be removed in a future release }; typedef struct { int id; int timeout; int timer; guint interval_ms; GHashTable *params; enum pcmk__graph_action_type type; pcmk__graph_synapse_t *synapse; uint32_t flags; // Group of pcmk__graph_action_flags xmlNode *xml; } pcmk__graph_action_t; #define pcmk__set_graph_action_flags(action, flags_to_set) do { \ (action)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Action", "action", \ (action)->flags, (flags_to_set), #flags_to_set); \ } while (0) #define pcmk__clear_graph_action_flags(action, flags_to_clear) do { \ (action)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Action", "action", \ (action)->flags, (flags_to_clear), #flags_to_clear); \ } while (0) // What to do after finished processing a transition graph enum pcmk__graph_next { // Order matters: lowest priority to highest pcmk__graph_done, // Transition complete, nothing further needed pcmk__graph_wait, // Transition interrupted, wait for further changes pcmk__graph_restart, // Transition interrupted, start a new one pcmk__graph_shutdown, // Transition interrupted, local shutdown needed }; typedef struct { int id; char *source; int abort_priority; bool complete; const char *abort_reason; enum pcmk__graph_next completion_action; int num_actions; int num_synapses; int batch_limit; guint network_delay; guint stonith_timeout; int fired; int pending; int skipped; int completed; int incomplete; GList *synapses; /* pcmk__graph_synapse_t* */ int migration_limit; //! Failcount after one failed stop action char *failed_stop_offset; //! Failcount after one failed start action char *failed_start_offset; //! Time (from epoch) by which the controller should re-run the scheduler time_t recheck_by; } pcmk__graph_t; typedef struct { int (*pseudo) (pcmk__graph_t *graph, pcmk__graph_action_t *action); int (*rsc) (pcmk__graph_t *graph, pcmk__graph_action_t *action); int (*cluster) (pcmk__graph_t *graph, pcmk__graph_action_t *action); int (*fence) (pcmk__graph_t *graph, pcmk__graph_action_t *action); bool (*allowed) (pcmk__graph_t *graph, pcmk__graph_action_t *action); } pcmk__graph_functions_t; enum pcmk__graph_status { pcmk__graph_active, // Some actions have been performed pcmk__graph_pending, // No actions performed yet pcmk__graph_complete, pcmk__graph_terminated, }; void pcmk__set_graph_functions(pcmk__graph_functions_t *fns); pcmk__graph_t *pcmk__unpack_graph(const xmlNode *xml_graph, const char *reference); enum pcmk__graph_status pcmk__execute_graph(pcmk__graph_t *graph); void pcmk__update_graph(pcmk__graph_t *graph, const pcmk__graph_action_t *action); void pcmk__free_graph(pcmk__graph_t *graph); const char *pcmk__graph_status2text(enum pcmk__graph_status state); void pcmk__log_graph(unsigned int log_level, pcmk__graph_t *graph); void pcmk__log_graph_action(int log_level, pcmk__graph_action_t *action); +void pcmk__log_transition_summary(const char *filename); lrmd_event_data_t *pcmk__event_from_graph_action(const xmlNode *resource, const pcmk__graph_action_t *action, int status, int rc, const char *exit_reason); #ifdef __cplusplus } #endif #endif diff --git a/lib/pacemaker/libpacemaker_private.h b/lib/pacemaker/libpacemaker_private.h index 4adaffdd99..dac081ee83 100644 --- a/lib/pacemaker/libpacemaker_private.h +++ b/lib/pacemaker/libpacemaker_private.h @@ -1,1055 +1,1056 @@ /* * Copyright 2021-2023 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__LIBPACEMAKER_PRIVATE__H # define PCMK__LIBPACEMAKER_PRIVATE__H /* This header is for the sole use of libpacemaker, so that functions can be * declared with G_GNUC_INTERNAL for efficiency. */ #include // pe_action_t, pe_node_t, pe_working_set_t +#include // pe__location_t // Flags to modify the behavior of add_colocated_node_scores() enum pcmk__coloc_select { // With no other flags, apply all "with this" colocations pcmk__coloc_select_default = 0, // Apply "this with" colocations instead of "with this" colocations pcmk__coloc_select_this_with = (1 << 0), // Apply only colocations with non-negative scores pcmk__coloc_select_nonnegative = (1 << 1), // Apply only colocations with at least one matching node pcmk__coloc_select_active = (1 << 2), }; // Flags the update_ordered_actions() method can return enum pcmk__updated { pcmk__updated_none = 0, // Nothing changed pcmk__updated_first = (1 << 0), // First action was updated pcmk__updated_then = (1 << 1), // Then action was updated }; #define pcmk__set_updated_flags(au_flags, action, flags_to_set) do { \ au_flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "Action update", \ (action)->uuid, au_flags, \ (flags_to_set), #flags_to_set); \ } while (0) #define pcmk__clear_updated_flags(au_flags, action, flags_to_clear) do { \ au_flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "Action update", \ (action)->uuid, au_flags, \ (flags_to_clear), #flags_to_clear); \ } while (0) // Resource allocation methods struct resource_alloc_functions_s { /*! * \internal * \brief Assign a resource to a node * * \param[in,out] rsc Resource to assign to a node * \param[in] prefer Node to prefer, if all else is equal * * \return Node that \p rsc is assigned to, if assigned entirely to one node */ pe_node_t *(*assign)(pe_resource_t *rsc, const pe_node_t *prefer); /*! * \internal * \brief Create all actions needed for a given resource * * \param[in,out] rsc Resource to create actions for */ void (*create_actions)(pe_resource_t *rsc); /*! * \internal * \brief Schedule any probes needed for a resource on a node * * \param[in,out] rsc Resource to create probe for * \param[in,out] node Node to create probe on * * \return true if any probe was created, otherwise false */ bool (*create_probe)(pe_resource_t *rsc, pe_node_t *node); /*! * \internal * \brief Create implicit constraints needed for a resource * * \param[in,out] rsc Resource to create implicit constraints for */ void (*internal_constraints)(pe_resource_t *rsc); /*! * \internal * \brief Apply a colocation's score to node weights or resource priority * * Given a colocation constraint, apply its score to the dependent's * allowed node weights (if we are still placing resources) or priority (if * we are choosing promotable clone instance roles). * * \param[in,out] dependent Dependent resource in colocation * \param[in,out] primary Primary resource in colocation * \param[in] colocation Colocation constraint to apply * \param[in] for_dependent true if called on behalf of dependent */ void (*apply_coloc_score)(pe_resource_t *dependent, pe_resource_t *primary, const pcmk__colocation_t *colocation, bool for_dependent); /*! * \internal * \brief Create list of all resources in colocations with a given resource * * Given a resource, create a list of all resources involved in mandatory * colocations with it, whether directly or indirectly via chained colocations. * * \param[in] rsc Resource to add to colocated list * \param[in] orig_rsc Resource originally requested * \param[in,out] colocated_rscs Existing list * * \return List of given resource and all resources involved in colocations * * \note This function is recursive; top-level callers should pass NULL as * \p colocated_rscs and \p orig_rsc, and the desired resource as * \p rsc. The recursive calls will use other values. */ GList *(*colocated_resources)(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList *colocated_rscs); /*! * \internal * \brief Add colocations affecting a resource as primary to a list * * Given a resource being assigned (\p orig_rsc) and a resource somewhere in * its chain of ancestors (\p rsc, which may be \p orig_rsc), get * colocations that affect the ancestor as primary and should affect the * resource, and add them to a given list. * * \param[in] rsc Resource whose colocations should be added * \param[in] orig_rsc Affected resource (\p rsc or a descendant) * \param[in,out] list List of colocations to add to * * \note All arguments should be non-NULL. * \note The pcmk__with_this_colocations() wrapper should usually be used * instead of using this method directly. */ void (*with_this_colocations)(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list); /*! * \internal * \brief Add colocations affecting a resource as dependent to a list * * Given a resource being assigned (\p orig_rsc) and a resource somewhere in * its chain of ancestors (\p rsc, which may be \p orig_rsc), get * colocations that affect the ancestor as dependent and should affect the * resource, and add them to a given list. * * * \param[in] rsc Resource whose colocations should be added * \param[in] orig_rsc Affected resource (\p rsc or a descendant) * \param[in,out] list List of colocations to add to * * \note All arguments should be non-NULL. * \note The pcmk__this_with_colocations() wrapper should usually be used * instead of using this method directly. */ void (*this_with_colocations)(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list); /*! * \internal * \brief Update nodes with scores of colocated resources' nodes * * Given a table of nodes and a resource, update the nodes' scores with the * scores of the best nodes matching the attribute used for each of the * resource's relevant colocations. * * \param[in,out] rsc Resource to check colocations for * \param[in] log_id Resource ID for logs (if NULL, use \p rsc ID) * \param[in,out] nodes Nodes to update (set initial contents to NULL * to copy \p rsc's allowed nodes) * \param[in] colocation Original colocation constraint (used to get * configured primary resource's stickiness, and * to get colocation node attribute; if NULL, * \p rsc's own matching node scores will not be * added, and *nodes must be NULL as well) * \param[in] factor Incorporate scores multiplied by this factor * \param[in] flags Bitmask of enum pcmk__coloc_select values * * \note NULL *nodes, NULL colocation, and the pcmk__coloc_select_this_with * flag are used together (and only by cmp_resources()). * \note The caller remains responsible for freeing \p *nodes. */ void (*add_colocated_node_scores)(pe_resource_t *rsc, const char *log_id, GHashTable **nodes, pcmk__colocation_t *colocation, float factor, uint32_t flags); /*! * \internal * \brief Apply a location constraint to a resource's allowed node scores * * \param[in,out] rsc Resource to apply constraint to * \param[in,out] location Location constraint to apply */ void (*apply_location)(pe_resource_t *rsc, pe__location_t *location); /*! * \internal * \brief Return action flags for a given resource action * * \param[in,out] action Action to get flags for * \param[in] node If not NULL, limit effects to this node * * \return Flags appropriate to \p action on \p node * \note For primitives, this will be the same as action->flags regardless * of node. For collective resources, the flags can differ due to * multiple instances possibly being involved. */ uint32_t (*action_flags)(pe_action_t *action, const pe_node_t *node); /*! * \internal * \brief Update two actions according to an ordering between them * * Given information about an ordering of two actions, update the actions' * flags (and runnable_before members if appropriate) as appropriate for the * ordering. Effects may cascade to other orderings involving the actions as * well. * * \param[in,out] first 'First' action in an ordering * \param[in,out] then 'Then' action in an ordering * \param[in] node If not NULL, limit scope of ordering to this * node (only used when interleaving instances) * \param[in] flags Action flags for \p first for ordering purposes * \param[in] filter Action flags to limit scope of certain updates * (may include pe_action_optional to affect only * mandatory actions, and pe_action_runnable to * affect only runnable actions) * \param[in] type Group of enum pe_ordering flags to apply * \param[in,out] data_set Cluster working set * * \return Group of enum pcmk__updated flags indicating what was updated */ uint32_t (*update_ordered_actions)(pe_action_t *first, pe_action_t *then, const pe_node_t *node, uint32_t flags, uint32_t filter, uint32_t type, pe_working_set_t *data_set); /*! * \internal * \brief Output a summary of scheduled actions for a resource * * \param[in,out] rsc Resource to output actions for */ void (*output_actions)(pe_resource_t *rsc); /*! * \internal * \brief Add a resource's actions to the transition graph * * \param[in,out] rsc Resource whose actions should be added */ void (*add_actions_to_graph)(pe_resource_t *rsc); /*! * \internal * \brief Add meta-attributes relevant to transition graph actions to XML * * If a given resource supports variant-specific meta-attributes that are * needed for transition graph actions, add them to a given XML element. * * \param[in] rsc Resource whose meta-attributes should be added * \param[in,out] xml Transition graph action attributes XML to add to */ void (*add_graph_meta)(const pe_resource_t *rsc, xmlNode *xml); /*! * \internal * \brief Add a resource's utilization to a table of utilization values * * This function is used when summing the utilization of a resource and all * resources colocated with it, to determine whether a node has sufficient * capacity. Given a resource and a table of utilization values, it will add * the resource's utilization to the existing values, if the resource has * not yet been allocated to a node. * * \param[in] rsc Resource with utilization to add * \param[in] orig_rsc Resource being allocated (for logging only) * \param[in] all_rscs List of all resources that will be summed * \param[in,out] utilization Table of utilization values to add to */ void (*add_utilization)(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList *all_rscs, GHashTable *utilization); /*! * \internal * \brief Apply a shutdown lock for a resource, if appropriate * * \param[in,out] rsc Resource to check for shutdown lock */ void (*shutdown_lock)(pe_resource_t *rsc); }; // Actions (pcmk_sched_actions.c) G_GNUC_INTERNAL void pcmk__update_action_for_orderings(pe_action_t *action, pe_working_set_t *data_set); G_GNUC_INTERNAL uint32_t pcmk__update_ordered_actions(pe_action_t *first, pe_action_t *then, const pe_node_t *node, uint32_t flags, uint32_t filter, uint32_t type, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__log_action(const char *pre_text, const pe_action_t *action, bool details); G_GNUC_INTERNAL pe_action_t *pcmk__new_cancel_action(pe_resource_t *rsc, const char *name, guint interval_ms, const pe_node_t *node); G_GNUC_INTERNAL pe_action_t *pcmk__new_shutdown_action(pe_node_t *node); G_GNUC_INTERNAL bool pcmk__action_locks_rsc_to_node(const pe_action_t *action); G_GNUC_INTERNAL void pcmk__deduplicate_action_inputs(pe_action_t *action); G_GNUC_INTERNAL void pcmk__output_actions(pe_working_set_t *data_set); G_GNUC_INTERNAL bool pcmk__check_action_config(pe_resource_t *rsc, pe_node_t *node, const xmlNode *xml_op); G_GNUC_INTERNAL void pcmk__handle_rsc_config_changes(pe_working_set_t *data_set); // Recurring actions (pcmk_sched_recurring.c) G_GNUC_INTERNAL void pcmk__create_recurring_actions(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__schedule_cancel(pe_resource_t *rsc, const char *call_id, const char *task, guint interval_ms, const pe_node_t *node, const char *reason); G_GNUC_INTERNAL void pcmk__reschedule_recurring(pe_resource_t *rsc, const char *task, guint interval_ms, pe_node_t *node); G_GNUC_INTERNAL bool pcmk__action_is_recurring(const pe_action_t *action); // Producing transition graphs (pcmk_graph_producer.c) G_GNUC_INTERNAL bool pcmk__graph_has_loop(const pe_action_t *init_action, const pe_action_t *action, pe_action_wrapper_t *input); G_GNUC_INTERNAL void pcmk__add_rsc_actions_to_graph(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__create_graph(pe_working_set_t *data_set); // Fencing (pcmk_sched_fencing.c) G_GNUC_INTERNAL void pcmk__order_vs_fence(pe_action_t *stonith_op, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__order_vs_unfence(const pe_resource_t *rsc, pe_node_t *node, pe_action_t *action, enum pe_ordering order); G_GNUC_INTERNAL void pcmk__fence_guest(pe_node_t *node); G_GNUC_INTERNAL bool pcmk__node_unfenced(const pe_node_t *node); G_GNUC_INTERNAL void pcmk__order_restart_vs_unfence(gpointer data, gpointer user_data); // Injected scheduler inputs (pcmk_sched_injections.c) void pcmk__inject_scheduler_input(pe_working_set_t *data_set, cib_t *cib, const pcmk_injections_t *injections); // Constraints of any type (pcmk_sched_constraints.c) G_GNUC_INTERNAL pe_resource_t *pcmk__find_constraint_resource(GList *rsc_list, const char *id); G_GNUC_INTERNAL xmlNode *pcmk__expand_tags_in_sets(xmlNode *xml_obj, const pe_working_set_t *data_set); G_GNUC_INTERNAL bool pcmk__valid_resource_or_tag(const pe_working_set_t *data_set, const char *id, pe_resource_t **rsc, pe_tag_t **tag); G_GNUC_INTERNAL bool pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr, bool convert_rsc, const pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__create_internal_constraints(pe_working_set_t *data_set); // Location constraints G_GNUC_INTERNAL void pcmk__unpack_location(xmlNode *xml_obj, pe_working_set_t *data_set); G_GNUC_INTERNAL pe__location_t *pcmk__new_location(const char *id, pe_resource_t *rsc, int node_weight, const char *discover_mode, pe_node_t *foo_node, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__apply_locations(pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__apply_location(pe_resource_t *rsc, pe__location_t *constraint); // Colocation constraints (pcmk_sched_colocation.c) enum pcmk__coloc_affects { pcmk__coloc_affects_nothing = 0, pcmk__coloc_affects_location, pcmk__coloc_affects_role, }; G_GNUC_INTERNAL enum pcmk__coloc_affects pcmk__colocation_affects(const pe_resource_t *dependent, const pe_resource_t *primary, const pcmk__colocation_t *colocation, bool preview); G_GNUC_INTERNAL void pcmk__apply_coloc_to_weights(pe_resource_t *dependent, const pe_resource_t *primary, const pcmk__colocation_t *colocation); G_GNUC_INTERNAL void pcmk__apply_coloc_to_priority(pe_resource_t *dependent, const pe_resource_t *primary, const pcmk__colocation_t *colocation); G_GNUC_INTERNAL void pcmk__add_colocated_node_scores(pe_resource_t *rsc, const char *log_id, GHashTable **nodes, pcmk__colocation_t *colocation, float factor, uint32_t flags); G_GNUC_INTERNAL void pcmk__add_dependent_scores(gpointer data, gpointer user_data); G_GNUC_INTERNAL void pcmk__unpack_colocation(xmlNode *xml_obj, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__add_this_with(GList **list, const pcmk__colocation_t *colocation); G_GNUC_INTERNAL void pcmk__add_this_with_list(GList **list, GList *addition); G_GNUC_INTERNAL void pcmk__add_with_this(GList **list, const pcmk__colocation_t *colocation); G_GNUC_INTERNAL void pcmk__add_with_this_list(GList **list, GList *addition); G_GNUC_INTERNAL void pcmk__new_colocation(const char *id, const char *node_attr, int score, pe_resource_t *dependent, pe_resource_t *primary, const char *dependent_role, const char *primary_role, bool influence, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__block_colocation_dependents(pe_action_t *action, pe_working_set_t *data_set); /*! * \internal * \brief Check whether colocation's dependent preferences should be considered * * \param[in] colocation Colocation constraint * \param[in] rsc Primary instance (normally this will be * colocation->primary, which NULL will be treated as, * but for clones or bundles with multiple instances * this can be a particular instance) * * \return true if colocation influence should be effective, otherwise false */ static inline bool pcmk__colocation_has_influence(const pcmk__colocation_t *colocation, const pe_resource_t *rsc) { if (rsc == NULL) { rsc = colocation->primary; } /* A bundle replica colocates its remote connection with its container, * using a finite score so that the container can run on Pacemaker Remote * nodes. * * Moving a connection is lightweight and does not interrupt the service, * while moving a container is heavyweight and does interrupt the service, * so don't move a clean, active container based solely on the preferences * of its connection. * * This also avoids problematic scenarios where two containers want to * perpetually swap places. */ if (pcmk_is_set(colocation->dependent->flags, pe_rsc_allow_remote_remotes) && !pcmk_is_set(rsc->flags, pe_rsc_failed) && pcmk__list_of_1(rsc->running_on)) { return false; } /* The dependent in a colocation influences the primary's location * if the influence option is true or the primary is not yet active. */ return colocation->influence || (rsc->running_on == NULL); } // Ordering constraints (pcmk_sched_ordering.c) G_GNUC_INTERNAL void pcmk__new_ordering(pe_resource_t *first_rsc, char *first_task, pe_action_t *first_action, pe_resource_t *then_rsc, char *then_task, pe_action_t *then_action, uint32_t flags, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__unpack_ordering(xmlNode *xml_obj, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__disable_invalid_orderings(pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__order_stops_before_shutdown(pe_node_t *node, pe_action_t *shutdown_op); G_GNUC_INTERNAL void pcmk__apply_orderings(pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__order_after_each(pe_action_t *after, GList *list); /*! * \internal * \brief Create a new ordering between two resource actions * * \param[in,out] first_rsc Resource for 'first' action * \param[in,out] first_task Action key for 'first' action * \param[in] then_rsc Resource for 'then' action * \param[in,out] then_task Action key for 'then' action * \param[in] flags Bitmask of enum pe_ordering flags */ #define pcmk__order_resource_actions(first_rsc, first_task, \ then_rsc, then_task, flags) \ pcmk__new_ordering((first_rsc), \ pcmk__op_key((first_rsc)->id, (first_task), 0), \ NULL, \ (then_rsc), \ pcmk__op_key((then_rsc)->id, (then_task), 0), \ NULL, (flags), (first_rsc)->cluster) #define pcmk__order_starts(rsc1, rsc2, flags) \ pcmk__order_resource_actions((rsc1), CRMD_ACTION_START, \ (rsc2), CRMD_ACTION_START, (flags)) #define pcmk__order_stops(rsc1, rsc2, flags) \ pcmk__order_resource_actions((rsc1), CRMD_ACTION_STOP, \ (rsc2), CRMD_ACTION_STOP, (flags)) // Ticket constraints (pcmk_sched_tickets.c) G_GNUC_INTERNAL void pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pe_working_set_t *data_set); // Promotable clone resources (pcmk_sched_promotable.c) G_GNUC_INTERNAL void pcmk__add_promotion_scores(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__require_promotion_tickets(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__set_instance_roles(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__create_promotable_actions(pe_resource_t *clone); G_GNUC_INTERNAL void pcmk__promotable_restart_ordering(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__order_promotable_instances(pe_resource_t *clone); G_GNUC_INTERNAL void pcmk__update_dependent_with_promotable(const pe_resource_t *primary, pe_resource_t *dependent, const pcmk__colocation_t *colocation); G_GNUC_INTERNAL void pcmk__update_promotable_dependent_priority(const pe_resource_t *primary, pe_resource_t *dependent, const pcmk__colocation_t *colocation); // Pacemaker Remote nodes (pcmk_sched_remote.c) G_GNUC_INTERNAL bool pcmk__is_failed_remote_node(const pe_node_t *node); G_GNUC_INTERNAL void pcmk__order_remote_connection_actions(pe_working_set_t *data_set); G_GNUC_INTERNAL bool pcmk__rsc_corresponds_to_guest(const pe_resource_t *rsc, const pe_node_t *node); G_GNUC_INTERNAL pe_node_t *pcmk__connection_host_for_action(const pe_action_t *action); G_GNUC_INTERNAL void pcmk__substitute_remote_addr(pe_resource_t *rsc, GHashTable *params); G_GNUC_INTERNAL void pcmk__add_bundle_meta_to_xml(xmlNode *args_xml, const pe_action_t *action); // Primitives (pcmk_sched_primitive.c) G_GNUC_INTERNAL pe_node_t *pcmk__primitive_assign(pe_resource_t *rsc, const pe_node_t *prefer); G_GNUC_INTERNAL void pcmk__primitive_create_actions(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__primitive_internal_constraints(pe_resource_t *rsc); G_GNUC_INTERNAL uint32_t pcmk__primitive_action_flags(pe_action_t *action, const pe_node_t *node); G_GNUC_INTERNAL void pcmk__primitive_apply_coloc_score(pe_resource_t *dependent, pe_resource_t *primary, const pcmk__colocation_t *colocation, bool for_dependent); G_GNUC_INTERNAL void pcmk__with_primitive_colocations(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list); G_GNUC_INTERNAL void pcmk__primitive_with_colocations(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list); G_GNUC_INTERNAL void pcmk__schedule_cleanup(pe_resource_t *rsc, const pe_node_t *node, bool optional); G_GNUC_INTERNAL void pcmk__primitive_add_graph_meta(const pe_resource_t *rsc, xmlNode *xml); G_GNUC_INTERNAL void pcmk__primitive_add_utilization(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList *all_rscs, GHashTable *utilization); G_GNUC_INTERNAL void pcmk__primitive_shutdown_lock(pe_resource_t *rsc); // Groups (pcmk_sched_group.c) G_GNUC_INTERNAL pe_node_t *pcmk__group_assign(pe_resource_t *rsc, const pe_node_t *prefer); G_GNUC_INTERNAL void pcmk__group_create_actions(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__group_internal_constraints(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__group_apply_coloc_score(pe_resource_t *dependent, pe_resource_t *primary, const pcmk__colocation_t *colocation, bool for_dependent); G_GNUC_INTERNAL void pcmk__with_group_colocations(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list); G_GNUC_INTERNAL void pcmk__group_with_colocations(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list); G_GNUC_INTERNAL void pcmk__group_add_colocated_node_scores(pe_resource_t *rsc, const char *log_id, GHashTable **nodes, pcmk__colocation_t *colocation, float factor, uint32_t flags); G_GNUC_INTERNAL void pcmk__group_apply_location(pe_resource_t *rsc, pe__location_t *location); G_GNUC_INTERNAL uint32_t pcmk__group_action_flags(pe_action_t *action, const pe_node_t *node); G_GNUC_INTERNAL uint32_t pcmk__group_update_ordered_actions(pe_action_t *first, pe_action_t *then, const pe_node_t *node, uint32_t flags, uint32_t filter, uint32_t type, pe_working_set_t *data_set); G_GNUC_INTERNAL GList *pcmk__group_colocated_resources(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList *colocated_rscs); G_GNUC_INTERNAL void pcmk__group_add_utilization(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList *all_rscs, GHashTable *utilization); G_GNUC_INTERNAL void pcmk__group_shutdown_lock(pe_resource_t *rsc); // Clones (pcmk_sched_clone.c) G_GNUC_INTERNAL pe_node_t *pcmk__clone_assign(pe_resource_t *rsc, const pe_node_t *prefer); G_GNUC_INTERNAL void pcmk__clone_create_actions(pe_resource_t *rsc); G_GNUC_INTERNAL bool pcmk__clone_create_probe(pe_resource_t *rsc, pe_node_t *node); G_GNUC_INTERNAL void pcmk__clone_internal_constraints(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__clone_apply_coloc_score(pe_resource_t *dependent, pe_resource_t *primary, const pcmk__colocation_t *colocation, bool for_dependent); G_GNUC_INTERNAL void pcmk__with_clone_colocations(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list); G_GNUC_INTERNAL void pcmk__clone_with_colocations(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list); G_GNUC_INTERNAL void pcmk__clone_apply_location(pe_resource_t *rsc, pe__location_t *constraint); G_GNUC_INTERNAL uint32_t pcmk__clone_action_flags(pe_action_t *action, const pe_node_t *node); G_GNUC_INTERNAL void pcmk__clone_add_actions_to_graph(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__clone_add_graph_meta(const pe_resource_t *rsc, xmlNode *xml); G_GNUC_INTERNAL void pcmk__clone_add_utilization(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList *all_rscs, GHashTable *utilization); G_GNUC_INTERNAL void pcmk__clone_shutdown_lock(pe_resource_t *rsc); // Bundles (pcmk_sched_bundle.c) G_GNUC_INTERNAL pe_node_t *pcmk__bundle_assign(pe_resource_t *rsc, const pe_node_t *prefer); G_GNUC_INTERNAL void pcmk__bundle_create_actions(pe_resource_t *rsc); G_GNUC_INTERNAL bool pcmk__bundle_create_probe(pe_resource_t *rsc, pe_node_t *node); G_GNUC_INTERNAL void pcmk__bundle_internal_constraints(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__bundle_apply_coloc_score(pe_resource_t *dependent, pe_resource_t *primary, const pcmk__colocation_t *colocation, bool for_dependent); G_GNUC_INTERNAL void pcmk__with_bundle_colocations(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list); G_GNUC_INTERNAL void pcmk__bundle_with_colocations(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list); G_GNUC_INTERNAL void pcmk__bundle_apply_location(pe_resource_t *rsc, pe__location_t *constraint); G_GNUC_INTERNAL uint32_t pcmk__bundle_action_flags(pe_action_t *action, const pe_node_t *node); G_GNUC_INTERNAL void pcmk__output_bundle_actions(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__bundle_add_actions_to_graph(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__bundle_add_utilization(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList *all_rscs, GHashTable *utilization); G_GNUC_INTERNAL void pcmk__bundle_shutdown_lock(pe_resource_t *rsc); // Clone instances or bundle replica containers (pcmk_sched_instances.c) G_GNUC_INTERNAL void pcmk__assign_instances(pe_resource_t *collective, GList *instances, int max_total, int max_per_node); G_GNUC_INTERNAL void pcmk__create_instance_actions(pe_resource_t *rsc, GList *instances); G_GNUC_INTERNAL bool pcmk__instance_matches(const pe_resource_t *instance, const pe_node_t *node, enum rsc_role_e role, bool current); G_GNUC_INTERNAL pe_resource_t *pcmk__find_compatible_instance(const pe_resource_t *match_rsc, const pe_resource_t *rsc, enum rsc_role_e role, bool current); G_GNUC_INTERNAL uint32_t pcmk__instance_update_ordered_actions(pe_action_t *first, pe_action_t *then, const pe_node_t *node, uint32_t flags, uint32_t filter, uint32_t type, pe_working_set_t *data_set); G_GNUC_INTERNAL uint32_t pcmk__collective_action_flags(pe_action_t *action, const GList *instances, const pe_node_t *node); G_GNUC_INTERNAL void pcmk__add_collective_constraints(GList **list, const pe_resource_t *instance, const pe_resource_t *collective, bool with_this); // Injections (pcmk_injections.c) G_GNUC_INTERNAL xmlNode *pcmk__inject_node(cib_t *cib_conn, const char *node, const char *uuid); G_GNUC_INTERNAL xmlNode *pcmk__inject_node_state_change(cib_t *cib_conn, const char *node, bool up); G_GNUC_INTERNAL xmlNode *pcmk__inject_resource_history(pcmk__output_t *out, xmlNode *cib_node, const char *resource, const char *lrm_name, const char *rclass, const char *rtype, const char *rprovider); G_GNUC_INTERNAL void pcmk__inject_failcount(pcmk__output_t *out, xmlNode *cib_node, const char *resource, const char *task, guint interval_ms, int rc); G_GNUC_INTERNAL xmlNode *pcmk__inject_action_result(xmlNode *cib_resource, lrmd_event_data_t *op, int target_rc); // Nodes (pcmk_sched_nodes.c) G_GNUC_INTERNAL bool pcmk__node_available(const pe_node_t *node, bool consider_score, bool consider_guest); G_GNUC_INTERNAL bool pcmk__any_node_available(GHashTable *nodes); G_GNUC_INTERNAL GHashTable *pcmk__copy_node_table(GHashTable *nodes); G_GNUC_INTERNAL GList *pcmk__sort_nodes(GList *nodes, pe_node_t *active_node); G_GNUC_INTERNAL void pcmk__apply_node_health(pe_working_set_t *data_set); G_GNUC_INTERNAL pe_node_t *pcmk__top_allowed_node(const pe_resource_t *rsc, const pe_node_t *node); // Functions applying to more than one variant (pcmk_sched_resource.c) G_GNUC_INTERNAL void pcmk__set_allocation_methods(pe_working_set_t *data_set); G_GNUC_INTERNAL bool pcmk__rsc_agent_changed(pe_resource_t *rsc, pe_node_t *node, const xmlNode *rsc_entry, bool active_on_node); G_GNUC_INTERNAL GList *pcmk__rscs_matching_id(const char *id, const pe_working_set_t *data_set); G_GNUC_INTERNAL GList *pcmk__colocated_resources(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList *colocated_rscs); G_GNUC_INTERNAL void pcmk__noop_add_graph_meta(const pe_resource_t *rsc, xmlNode *xml); G_GNUC_INTERNAL void pcmk__output_resource_actions(pe_resource_t *rsc); G_GNUC_INTERNAL bool pcmk__finalize_assignment(pe_resource_t *rsc, pe_node_t *chosen, bool force); G_GNUC_INTERNAL bool pcmk__assign_resource(pe_resource_t *rsc, pe_node_t *node, bool force); G_GNUC_INTERNAL void pcmk__unassign_resource(pe_resource_t *rsc); G_GNUC_INTERNAL bool pcmk__threshold_reached(pe_resource_t *rsc, const pe_node_t *node, pe_resource_t **failed); G_GNUC_INTERNAL void pcmk__sort_resources(pe_working_set_t *data_set); G_GNUC_INTERNAL gint pcmk__cmp_instance(gconstpointer a, gconstpointer b); G_GNUC_INTERNAL gint pcmk__cmp_instance_number(gconstpointer a, gconstpointer b); // Functions related to probes (pcmk_sched_probes.c) G_GNUC_INTERNAL bool pcmk__probe_rsc_on_node(pe_resource_t *rsc, pe_node_t *node); G_GNUC_INTERNAL void pcmk__order_probes(pe_working_set_t *data_set); G_GNUC_INTERNAL bool pcmk__probe_resource_list(GList *rscs, pe_node_t *node); G_GNUC_INTERNAL void pcmk__schedule_probes(pe_working_set_t *data_set); // Functions related to live migration (pcmk_sched_migration.c) void pcmk__create_migration_actions(pe_resource_t *rsc, const pe_node_t *current); void pcmk__abort_dangling_migration(void *data, void *user_data); bool pcmk__rsc_can_migrate(const pe_resource_t *rsc, const pe_node_t *current); void pcmk__order_migration_equivalents(pe__ordering_t *order); // Functions related to node utilization (pcmk_sched_utilization.c) G_GNUC_INTERNAL int pcmk__compare_node_capacities(const pe_node_t *node1, const pe_node_t *node2); G_GNUC_INTERNAL void pcmk__consume_node_capacity(GHashTable *current_utilization, const pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__release_node_capacity(GHashTable *current_utilization, const pe_resource_t *rsc); G_GNUC_INTERNAL const pe_node_t *pcmk__ban_insufficient_capacity(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__create_utilization_constraints(pe_resource_t *rsc, const GList *allowed_nodes); G_GNUC_INTERNAL void pcmk__show_node_capacities(const char *desc, pe_working_set_t *data_set); #endif // PCMK__LIBPACEMAKER_PRIVATE__H diff --git a/lib/pacemaker/pcmk_rule.c b/lib/pacemaker/pcmk_rule.c index b8ca453a52..3ad8a62be4 100644 --- a/lib/pacemaker/pcmk_rule.c +++ b/lib/pacemaker/pcmk_rule.c @@ -1,295 +1,296 @@ /* * Copyright 2022-2023 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 #include #include #include +#include #include #include /*! * \internal * \brief Evaluate a date expression for a specific time * * \param[in] expr date_expression XML * \param[in] now Time for which to evaluate expression * * \return Standard Pacemaker return code */ static int eval_date_expression(const xmlNode *expr, crm_time_t *now) { pe_rule_eval_data_t rule_data = { .node_hash = NULL, .role = RSC_ROLE_UNKNOWN, .now = now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; return pe__eval_date_expr(expr, &rule_data, NULL); } /*! * \internal * \brief Initialize the cluster working set for checking rules * * Make our own copies of the CIB XML and date/time object, if they're not * \c NULL. This way we don't have to take ownership of the objects passed via * the API. * * \param[in,out] out Output object * \param[in] input The CIB XML to check (if \c NULL, use current CIB) * \param[in] date Check whether the rule is in effect at this date * and time (if \c NULL, use current date and time) * \param[out] data_set Where to store the cluster working set * * \return Standard Pacemaker return code */ static int init_rule_check(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date, pe_working_set_t **data_set) { // Allows for cleaner syntax than dereferencing the data_set argument pe_working_set_t *new_data_set = NULL; new_data_set = pe_new_working_set(); if (new_data_set == NULL) { return ENOMEM; } pe__set_working_set_flags(new_data_set, pe_flag_no_counts|pe_flag_no_compat); // Populate the working set instance // Make our own copy of the given input or fetch the CIB and use that if (input != NULL) { new_data_set->input = copy_xml(input); if (new_data_set->input == NULL) { out->err(out, "Failed to copy input XML"); pe_free_working_set(new_data_set); return ENOMEM; } } else { int rc = cib__signon_query(out, NULL, &(new_data_set->input)); if (rc != pcmk_rc_ok) { pe_free_working_set(new_data_set); return rc; } } // Make our own copy of the given crm_time_t object; otherwise // cluster_status() populates with the current time if (date != NULL) { // pcmk_copy_time() guarantees non-NULL new_data_set->now = pcmk_copy_time(date); } // Unpack everything cluster_status(new_data_set); *data_set = new_data_set; return pcmk_rc_ok; } #define XPATH_NODE_RULE "//" XML_TAG_RULE "[@" XML_ATTR_ID "='%s']" /*! * \internal * \brief Check whether a given rule is in effect * * \param[in] data_set Cluster working set * \param[in] rule_id The ID of the rule to check * \param[out] error Where to store a rule evaluation error message * * \return Standard Pacemaker return code */ static int eval_rule(pe_working_set_t *data_set, const char *rule_id, const char **error) { xmlNodePtr cib_constraints = NULL; xmlNodePtr match = NULL; xmlXPathObjectPtr xpath_obj = NULL; char *xpath = NULL; int rc = pcmk_rc_ok; int num_results = 0; *error = NULL; /* Rules are under the constraints node in the XML, so first find that. */ cib_constraints = pcmk_find_cib_element(data_set->input, XML_CIB_TAG_CONSTRAINTS); /* Get all rules matching the given ID that are also simple enough for us * to check. For the moment, these rules must only have a single * date_expression child and: * - Do not have a date_spec operation, or * - Have a date_spec operation that contains years= but does not contain * moon=. * * We do this in steps to provide better error messages. First, check that * there's any rule with the given ID. */ xpath = crm_strdup_printf(XPATH_NODE_RULE, rule_id); xpath_obj = xpath_search(cib_constraints, xpath); num_results = numXpathResults(xpath_obj); free(xpath); freeXpathObject(xpath_obj); if (num_results == 0) { *error = "Rule not found"; return ENXIO; } if (num_results > 1) { // Should not be possible; schema prevents this *error = "Found more than one rule with matching ID"; return pcmk_rc_duplicate_id; } /* Next, make sure it has exactly one date_expression. */ xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression", rule_id); xpath_obj = xpath_search(cib_constraints, xpath); num_results = numXpathResults(xpath_obj); free(xpath); freeXpathObject(xpath_obj); if (num_results != 1) { if (num_results == 0) { *error = "Rule does not have a date expression"; } else { *error = "Rule has more than one date expression"; } return EOPNOTSUPP; } /* Then, check that it's something we actually support. */ xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression[" "@" XML_EXPR_ATTR_OPERATION "!='date_spec']", rule_id); xpath_obj = xpath_search(cib_constraints, xpath); num_results = numXpathResults(xpath_obj); free(xpath); if (num_results == 0) { freeXpathObject(xpath_obj); xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression[" "@" XML_EXPR_ATTR_OPERATION "='date_spec' " "and date_spec/@years " "and not(date_spec/@moon)]", rule_id); xpath_obj = xpath_search(cib_constraints, xpath); num_results = numXpathResults(xpath_obj); free(xpath); if (num_results == 0) { freeXpathObject(xpath_obj); *error = "Rule must either not use date_spec, or use date_spec " "with years= but not moon="; return EOPNOTSUPP; } } match = getXpathResult(xpath_obj, 0); /* We should have ensured this with the xpath query above, but double- * checking can't hurt. */ CRM_ASSERT(match != NULL); CRM_ASSERT(find_expression_type(match) == time_expr); rc = eval_date_expression(match, data_set->now); if (rc == pcmk_rc_undetermined) { /* pe__eval_date_expr() should return this only if something is * malformed or missing */ *error = "Error parsing rule"; } freeXpathObject(xpath_obj); return rc; } /*! * \internal * \brief Check whether each rule in a list is in effect * * \param[in,out] out Output object * \param[in] input The CIB XML to check (if \c NULL, use current CIB) * \param[in] date Check whether the rule is in effect at this date and * time (if \c NULL, use current date and time) * \param[in] rule_ids The IDs of the rules to check, as a NULL- * terminated list. * * \return Standard Pacemaker return code */ int pcmk__check_rules(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date, const char **rule_ids) { pe_working_set_t *data_set = NULL; int rc = pcmk_rc_ok; CRM_ASSERT(out != NULL); if (rule_ids == NULL) { // Trivial case; every rule specified is in effect return pcmk_rc_ok; } rc = init_rule_check(out, input, date, &data_set); if (rc != pcmk_rc_ok) { return rc; } for (const char **rule_id = rule_ids; *rule_id != NULL; rule_id++) { const char *error = NULL; int last_rc = eval_rule(data_set, *rule_id, &error); out->message(out, "rule-check", *rule_id, last_rc, error); if (last_rc != pcmk_rc_ok) { rc = last_rc; } } pe_free_working_set(data_set); return rc; } // Documented in pacemaker.h int pcmk_check_rules(xmlNodePtr *xml, xmlNodePtr input, const crm_time_t *date, const char **rule_ids) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__check_rules(out, input, date, rule_ids); pcmk__xml_output_finish(out, xml); return rc; } diff --git a/lib/pacemaker/pcmk_sched_location.c b/lib/pacemaker/pcmk_sched_location.c index b4ce4ff07d..64c0354950 100644 --- a/lib/pacemaker/pcmk_sched_location.c +++ b/lib/pacemaker/pcmk_sched_location.c @@ -1,678 +1,679 @@ /* * Copyright 2004-2023 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 #include #include #include +#include #include #include "libpacemaker_private.h" static int get_node_score(const char *rule, const char *score, bool raw, pe_node_t *node, pe_resource_t *rsc) { int score_f = 0; if (score == NULL) { pe_err("Rule %s: no score specified. Assuming 0.", rule); } else if (raw) { score_f = char2score(score); } else { const char *attr_score = pe_node_attribute_calculated(node, score, rsc); if (attr_score == NULL) { crm_debug("Rule %s: %s did not have a value for %s", rule, pe__node_name(node), score); score_f = -INFINITY; } else { crm_debug("Rule %s: %s had value %s for %s", rule, pe__node_name(node), attr_score, score); score_f = char2score(attr_score); } } return score_f; } static pe__location_t * generate_location_rule(pe_resource_t *rsc, xmlNode *rule_xml, const char *discovery, crm_time_t *next_change, pe_working_set_t *data_set, pe_re_match_data_t *re_match_data) { const char *rule_id = NULL; const char *score = NULL; const char *boolean = NULL; const char *role = NULL; GList *gIter = NULL; GList *match_L = NULL; bool do_and = true; bool accept = true; bool raw_score = true; bool score_allocated = false; pe__location_t *location_rule = NULL; rule_xml = expand_idref(rule_xml, data_set->input); if (rule_xml == NULL) { return NULL; } rule_id = crm_element_value(rule_xml, XML_ATTR_ID); boolean = crm_element_value(rule_xml, XML_RULE_ATTR_BOOLEAN_OP); role = crm_element_value(rule_xml, XML_RULE_ATTR_ROLE); crm_trace("Processing rule: %s", rule_id); if ((role != NULL) && (text2role(role) == RSC_ROLE_UNKNOWN)) { pe_err("Bad role specified for %s: %s", rule_id, role); return NULL; } score = crm_element_value(rule_xml, XML_RULE_ATTR_SCORE); if (score == NULL) { score = crm_element_value(rule_xml, XML_RULE_ATTR_SCORE_ATTRIBUTE); if (score != NULL) { raw_score = false; } } if (pcmk__str_eq(boolean, "or", pcmk__str_casei)) { do_and = false; } location_rule = pcmk__new_location(rule_id, rsc, 0, discovery, NULL, data_set); if (location_rule == NULL) { return NULL; } if ((re_match_data != NULL) && (re_match_data->nregs > 0) && (re_match_data->pmatch[0].rm_so != -1) && !raw_score) { char *result = pe_expand_re_matches(score, re_match_data); if (result != NULL) { score = result; score_allocated = true; } } if (role != NULL) { crm_trace("Setting role filter: %s", role); location_rule->role_filter = text2role(role); if (location_rule->role_filter == RSC_ROLE_UNPROMOTED) { /* Any promotable clone cannot be promoted without being in the * unpromoted role first. Ergo, any constraint for the unpromoted * role applies to every role. */ location_rule->role_filter = RSC_ROLE_UNKNOWN; } } if (do_and) { GList *gIter = NULL; match_L = pcmk__copy_node_list(data_set->nodes, true); for (gIter = match_L; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; node->weight = get_node_score(rule_id, score, raw_score, node, rsc); } } for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) { int score_f = 0; pe_node_t *node = (pe_node_t *) gIter->data; pe_match_data_t match_data = { .re = re_match_data, .params = pe_rsc_params(rsc, node, data_set), .meta = rsc->meta, }; accept = pe_test_rule(rule_xml, node->details->attrs, RSC_ROLE_UNKNOWN, data_set->now, next_change, &match_data); crm_trace("Rule %s %s on %s", ID(rule_xml), accept? "passed" : "failed", pe__node_name(node)); score_f = get_node_score(rule_id, score, raw_score, node, rsc); if (accept) { pe_node_t *local = pe_find_node_id(match_L, node->details->id); if ((local == NULL) && do_and) { continue; } else if (local == NULL) { local = pe__copy_node(node); match_L = g_list_append(match_L, local); } if (!do_and) { local->weight = pcmk__add_scores(local->weight, score_f); } crm_trace("%s has score %s after %s", pe__node_name(node), pcmk_readable_score(local->weight), rule_id); } else if (do_and && !accept) { // Remove it pe_node_t *delete = pe_find_node_id(match_L, node->details->id); if (delete != NULL) { match_L = g_list_remove(match_L, delete); crm_trace("%s did not match", pe__node_name(node)); } free(delete); } } if (score_allocated) { free((char *)score); } location_rule->node_list_rh = match_L; if (location_rule->node_list_rh == NULL) { crm_trace("No matching nodes for rule %s", rule_id); return NULL; } crm_trace("%s: %d nodes matched", rule_id, g_list_length(location_rule->node_list_rh)); return location_rule; } static void unpack_rsc_location(xmlNode *xml_obj, pe_resource_t *rsc, const char *role, const char *score, pe_working_set_t *data_set, pe_re_match_data_t *re_match_data) { pe__location_t *location = NULL; const char *rsc_id = crm_element_value(xml_obj, XML_LOC_ATTR_SOURCE); const char *id = crm_element_value(xml_obj, XML_ATTR_ID); const char *node = crm_element_value(xml_obj, XML_CIB_TAG_NODE); const char *discovery = crm_element_value(xml_obj, XML_LOCATION_ATTR_DISCOVERY); if (rsc == NULL) { pcmk__config_warn("Ignoring constraint '%s' because resource '%s' " "does not exist", id, rsc_id); return; } if (score == NULL) { score = crm_element_value(xml_obj, XML_RULE_ATTR_SCORE); } if ((node != NULL) && (score != NULL)) { int score_i = char2score(score); pe_node_t *match = pe_find_node(data_set->nodes, node); if (!match) { return; } location = pcmk__new_location(id, rsc, score_i, discovery, match, data_set); } else { bool empty = true; crm_time_t *next_change = crm_time_new_undefined(); /* This loop is logically parallel to pe_evaluate_rules(), except * instead of checking whether any rule is active, we set up location * constraints for each active rule. */ for (xmlNode *rule_xml = first_named_child(xml_obj, XML_TAG_RULE); rule_xml != NULL; rule_xml = crm_next_same_xml(rule_xml)) { empty = false; crm_trace("Unpacking %s/%s", id, ID(rule_xml)); generate_location_rule(rsc, rule_xml, discovery, next_change, data_set, re_match_data); } if (empty) { pcmk__config_err("Ignoring constraint '%s' because it contains " "no rules", id); } /* If there is a point in the future when the evaluation of a rule will * change, make sure the scheduler is re-run by that time. */ if (crm_time_is_defined(next_change)) { time_t t = (time_t) crm_time_get_seconds_since_epoch(next_change); pe__update_recheck_time(t, data_set); } crm_time_free(next_change); return; } if (role == NULL) { role = crm_element_value(xml_obj, XML_RULE_ATTR_ROLE); } if ((location != NULL) && (role != NULL)) { if (text2role(role) == RSC_ROLE_UNKNOWN) { pe_err("Invalid constraint %s: Bad role %s", id, role); return; } else { enum rsc_role_e r = text2role(role); switch(r) { case RSC_ROLE_UNKNOWN: case RSC_ROLE_STARTED: case RSC_ROLE_UNPROMOTED: /* Applies to all */ location->role_filter = RSC_ROLE_UNKNOWN; break; default: location->role_filter = r; break; } } } } static void unpack_simple_location(xmlNode *xml_obj, pe_working_set_t *data_set) { const char *id = crm_element_value(xml_obj, XML_ATTR_ID); const char *value = crm_element_value(xml_obj, XML_LOC_ATTR_SOURCE); if (value) { pe_resource_t *rsc; rsc = pcmk__find_constraint_resource(data_set->resources, value); unpack_rsc_location(xml_obj, rsc, NULL, NULL, data_set, NULL); } value = crm_element_value(xml_obj, XML_LOC_ATTR_SOURCE_PATTERN); if (value) { regex_t *r_patt = calloc(1, sizeof(regex_t)); bool invert = false; GList *rIter = NULL; if (value[0] == '!') { value++; invert = true; } if (regcomp(r_patt, value, REG_EXTENDED) != 0) { pcmk__config_err("Ignoring constraint '%s' because " XML_LOC_ATTR_SOURCE_PATTERN " has invalid value '%s'", id, value); free(r_patt); return; } for (rIter = data_set->resources; rIter; rIter = rIter->next) { pe_resource_t *r = rIter->data; int nregs = 0; regmatch_t *pmatch = NULL; int status; if(r_patt->re_nsub > 0) { nregs = r_patt->re_nsub + 1; } else { nregs = 1; } pmatch = calloc(nregs, sizeof(regmatch_t)); status = regexec(r_patt, r->id, nregs, pmatch, 0); if (!invert && (status == 0)) { pe_re_match_data_t re_match_data = { .string = r->id, .nregs = nregs, .pmatch = pmatch }; crm_debug("'%s' matched '%s' for %s", r->id, value, id); unpack_rsc_location(xml_obj, r, NULL, NULL, data_set, &re_match_data); } else if (invert && (status != 0)) { crm_debug("'%s' is an inverted match of '%s' for %s", r->id, value, id); unpack_rsc_location(xml_obj, r, NULL, NULL, data_set, NULL); } else { crm_trace("'%s' does not match '%s' for %s", r->id, value, id); } free(pmatch); } regfree(r_patt); free(r_patt); } } // \return Standard Pacemaker return code static int unpack_location_tags(xmlNode *xml_obj, xmlNode **expanded_xml, pe_working_set_t *data_set) { const char *id = NULL; const char *rsc_id = NULL; const char *state = NULL; pe_resource_t *rsc = NULL; pe_tag_t *tag = NULL; xmlNode *rsc_set = NULL; *expanded_xml = NULL; CRM_CHECK(xml_obj != NULL, return EINVAL); id = ID(xml_obj); if (id == NULL) { pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID, crm_element_name(xml_obj)); return pcmk_rc_unpack_error; } // Check whether there are any resource sets with template or tag references *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, data_set); if (*expanded_xml != NULL) { crm_log_xml_trace(*expanded_xml, "Expanded rsc_location"); return pcmk_rc_ok; } rsc_id = crm_element_value(xml_obj, XML_LOC_ATTR_SOURCE); if (rsc_id == NULL) { return pcmk_rc_ok; } if (!pcmk__valid_resource_or_tag(data_set, rsc_id, &rsc, &tag)) { pcmk__config_err("Ignoring constraint '%s' because '%s' is not a " "valid resource or tag", id, rsc_id); return pcmk_rc_unpack_error; } else if (rsc != NULL) { // No template is referenced return pcmk_rc_ok; } state = crm_element_value(xml_obj, XML_RULE_ATTR_ROLE); *expanded_xml = copy_xml(xml_obj); // Convert template/tag reference in "rsc" into resource_set under constraint if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, XML_LOC_ATTR_SOURCE, false, data_set)) { free_xml(*expanded_xml); *expanded_xml = NULL; return pcmk_rc_unpack_error; } if (rsc_set != NULL) { if (state != NULL) { // Move "rsc-role" into converted resource_set as "role" attribute crm_xml_add(rsc_set, "role", state); xml_remove_prop(*expanded_xml, XML_RULE_ATTR_ROLE); } crm_log_xml_trace(*expanded_xml, "Expanded rsc_location"); } else { // No sets free_xml(*expanded_xml); *expanded_xml = NULL; } return pcmk_rc_ok; } // \return Standard Pacemaker return code static int unpack_location_set(xmlNode *location, xmlNode *set, pe_working_set_t *data_set) { xmlNode *xml_rsc = NULL; pe_resource_t *resource = NULL; const char *set_id; const char *role; const char *local_score; CRM_CHECK(set != NULL, return EINVAL); set_id = ID(set); if (set_id == NULL) { pcmk__config_err("Ignoring " XML_CONS_TAG_RSC_SET " without " XML_ATTR_ID " in constraint '%s'", pcmk__s(ID(location), "(missing ID)")); return pcmk_rc_unpack_error; } role = crm_element_value(set, "role"); local_score = crm_element_value(set, XML_RULE_ATTR_SCORE); for (xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF); xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) { resource = pcmk__find_constraint_resource(data_set->resources, ID(xml_rsc)); if (resource == NULL) { pcmk__config_err("%s: No resource found for %s", set_id, ID(xml_rsc)); return pcmk_rc_unpack_error; } unpack_rsc_location(location, resource, role, local_score, data_set, NULL); } return pcmk_rc_ok; } void pcmk__unpack_location(xmlNode *xml_obj, pe_working_set_t *data_set) { xmlNode *set = NULL; bool any_sets = false; xmlNode *orig_xml = NULL; xmlNode *expanded_xml = NULL; if (unpack_location_tags(xml_obj, &expanded_xml, data_set) != pcmk_rc_ok) { return; } if (expanded_xml) { orig_xml = xml_obj; xml_obj = expanded_xml; } for (set = first_named_child(xml_obj, XML_CONS_TAG_RSC_SET); set != NULL; set = crm_next_same_xml(set)) { any_sets = true; set = expand_idref(set, data_set->input); if ((set == NULL) // Configuration error, message already logged || (unpack_location_set(xml_obj, set, data_set) != pcmk_rc_ok)) { if (expanded_xml) { free_xml(expanded_xml); } return; } } if (expanded_xml) { free_xml(expanded_xml); xml_obj = orig_xml; } if (!any_sets) { unpack_simple_location(xml_obj, data_set); } } /*! * \internal * \brief Add a new location constraint to a cluster working set * * \param[in] id XML ID of location constraint * \param[in,out] rsc Resource in location constraint * \param[in] node_weight Constraint score * \param[in] discover_mode Resource discovery option for constraint * \param[in] node Node in constraint (or NULL if rule-based) * \param[in,out] data_set Cluster working set to add constraint to * * \return Newly allocated location constraint * \note The result will be added to \p data_set and should not be freed * separately. */ pe__location_t * pcmk__new_location(const char *id, pe_resource_t *rsc, int node_weight, const char *discover_mode, pe_node_t *node, pe_working_set_t *data_set) { pe__location_t *new_con = NULL; if (id == NULL) { pe_err("Invalid constraint: no ID specified"); return NULL; } else if (rsc == NULL) { pe_err("Invalid constraint %s: no resource specified", id); return NULL; } else if (node == NULL) { CRM_CHECK(node_weight == 0, return NULL); } new_con = calloc(1, sizeof(pe__location_t)); if (new_con != NULL) { new_con->id = strdup(id); new_con->rsc_lh = rsc; new_con->node_list_rh = NULL; new_con->role_filter = RSC_ROLE_UNKNOWN; if (pcmk__str_eq(discover_mode, "always", pcmk__str_null_matches|pcmk__str_casei)) { new_con->discover_mode = pe_discover_always; } else if (pcmk__str_eq(discover_mode, "never", pcmk__str_casei)) { new_con->discover_mode = pe_discover_never; } else if (pcmk__str_eq(discover_mode, "exclusive", pcmk__str_casei)) { new_con->discover_mode = pe_discover_exclusive; rsc->exclusive_discover = TRUE; } else { pe_err("Invalid " XML_LOCATION_ATTR_DISCOVERY " value %s " "in location constraint", discover_mode); } if (node != NULL) { pe_node_t *copy = pe__copy_node(node); copy->weight = node_weight; new_con->node_list_rh = g_list_prepend(NULL, copy); } data_set->placement_constraints = g_list_prepend(data_set->placement_constraints, new_con); rsc->rsc_location = g_list_prepend(rsc->rsc_location, new_con); } return new_con; } /*! * \internal * \brief Apply all location constraints * * \param[in,out] data_set Cluster working set */ void pcmk__apply_locations(pe_working_set_t *data_set) { for (GList *iter = data_set->placement_constraints; iter != NULL; iter = iter->next) { pe__location_t *location = iter->data; location->rsc_lh->cmds->apply_location(location->rsc_lh, location); } } /*! * \internal * \brief Apply a location constraint to a resource's allowed node scores * * \param[in,out] rsc Resource to apply constraint to * \param[in,out] location Location constraint to apply * * \note This does not consider the resource's children, so the resource's * apply_location() method should be used instead in most cases. */ void pcmk__apply_location(pe_resource_t *rsc, pe__location_t *location) { bool need_role = false; CRM_CHECK((rsc != NULL) && (location != NULL), return); // If a role was specified, ensure constraint is applicable need_role = (location->role_filter > RSC_ROLE_UNKNOWN); if (need_role && (location->role_filter != rsc->next_role)) { pe_rsc_trace(rsc, "Not applying %s to %s because role will be %s not %s", location->id, rsc->id, role2text(rsc->next_role), role2text(location->role_filter)); return; } if (location->node_list_rh == NULL) { pe_rsc_trace(rsc, "Not applying %s to %s because no nodes match", location->id, rsc->id); return; } pe_rsc_trace(rsc, "Applying %s%s%s to %s", location->id, (need_role? " for role " : ""), (need_role? role2text(location->role_filter) : ""), rsc->id); for (GList *gIter = location->node_list_rh; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; pe_node_t *weighted_node = NULL; weighted_node = (pe_node_t *) pe_hash_table_lookup(rsc->allowed_nodes, node->details->id); if (weighted_node == NULL) { pe_rsc_trace(rsc, "* = %d on %s", node->weight, pe__node_name(node)); weighted_node = pe__copy_node(node); g_hash_table_insert(rsc->allowed_nodes, (gpointer) weighted_node->details->id, weighted_node); } else { pe_rsc_trace(rsc, "* + %d on %s", node->weight, pe__node_name(node)); weighted_node->weight = pcmk__add_scores(weighted_node->weight, node->weight); } if (weighted_node->rsc_discover_mode < location->discover_mode) { if (location->discover_mode == pe_discover_exclusive) { rsc->exclusive_discover = TRUE; } /* exclusive > never > always... always is default */ weighted_node->rsc_discover_mode = location->discover_mode; } } } diff --git a/lib/pacemaker/pcmk_status.c b/lib/pacemaker/pcmk_status.c index 0e82633fb9..be929fb6b2 100644 --- a/lib/pacemaker/pcmk_status.c +++ b/lib/pacemaker/pcmk_status.c @@ -1,368 +1,369 @@ /* * Copyright 2004-2023 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 #include #include #include #include #include #include +#include #include #include #include static stonith_t * fencing_connect(void) { stonith_t *st = stonith_api_new(); int rc = pcmk_rc_ok; if (st == NULL) { return NULL; } rc = st->cmds->connect(st, crm_system_name, NULL); if (rc == pcmk_rc_ok) { return st; } else { stonith_api_delete(st); return NULL; } } /*! * \internal * \brief Output the cluster status given a fencer and CIB connection * * \param[in,out] out Output object * \param[in,out] stonith Fencer connection * \param[in,out] cib CIB connection * \param[in] current_cib Current CIB XML * \param[in] pcmkd_state \p pacemakerd state * \param[in] fence_history How much of the fencing history to output * \param[in] show Group of \p pcmk_section_e flags * \param[in] show_opts Group of \p pcmk_show_opt_e flags * \param[in] only_node If a node name or tag, include only the * matching node(s) (if any) in the output. * If \p "*" or \p NULL, include all nodes * in the output. * \param[in] only_rsc If a resource ID or tag, include only the * matching resource(s) (if any) in the * output. If \p "*" or \p NULL, include all * resources in the output. * \param[in] neg_location_prefix Prefix denoting a ban in a constraint ID * \param[in] simple_output Whether to use a simple output format. * Note: This is for use by \p crm_mon only * and is planned to be deprecated. * * \return Standard Pacemaker return code */ int pcmk__output_cluster_status(pcmk__output_t *out, stonith_t *stonith, cib_t *cib, xmlNode *current_cib, enum pcmk_pacemakerd_state pcmkd_state, enum pcmk__fence_history fence_history, uint32_t show, uint32_t show_opts, const char *only_node, const char *only_rsc, const char *neg_location_prefix, bool simple_output) { xmlNode *cib_copy = copy_xml(current_cib); stonith_history_t *stonith_history = NULL; int history_rc = 0; pe_working_set_t *data_set = NULL; GList *unames = NULL; GList *resources = NULL; int rc = pcmk_rc_ok; if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) { cib__clean_up_connection(&cib); free_xml(cib_copy); rc = pcmk_rc_schema_validation; out->err(out, "Upgrade failed: %s", pcmk_rc_str(rc)); return rc; } /* get the stonith-history if there is evidence we need it */ if (fence_history != pcmk__fence_history_none) { history_rc = pcmk__get_fencing_history(stonith, &stonith_history, fence_history); } data_set = pe_new_working_set(); CRM_ASSERT(data_set != NULL); pe__set_working_set_flags(data_set, pe_flag_no_compat); data_set->input = cib_copy; data_set->priv = out; cluster_status(data_set); if ((cib->variant == cib_native) && pcmk_is_set(show, pcmk_section_times)) { if (pcmk__our_nodename == NULL) { // Currently used only in the times section pcmk__query_node_name(out, 0, &pcmk__our_nodename, 0); } data_set->localhost = pcmk__our_nodename; } /* Unpack constraints if any section will need them * (tickets may be referenced in constraints but not granted yet, * and bans need negative location constraints) */ if (pcmk_is_set(show, pcmk_section_bans) || pcmk_is_set(show, pcmk_section_tickets)) { pcmk__unpack_constraints(data_set); } unames = pe__build_node_name_list(data_set, only_node); resources = pe__build_rsc_list(data_set, only_rsc); /* Always print DC if NULL. */ if (data_set->dc_node == NULL) { show |= pcmk_section_dc; } if (simple_output) { rc = pcmk__output_simple_status(out, data_set); } else { out->message(out, "cluster-status", data_set, pcmkd_state, pcmk_rc2exitc(history_rc), stonith_history, fence_history, show, show_opts, neg_location_prefix, unames, resources); } g_list_free_full(unames, free); g_list_free_full(resources, free); stonith_history_free(stonith_history); stonith_history = NULL; pe_free_working_set(data_set); return rc; } int pcmk_status(xmlNodePtr *xml) { cib_t *cib = NULL; pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; uint32_t show_opts = pcmk_show_pending | pcmk_show_inactive_rscs | pcmk_show_timing; cib = cib_new(); if (cib == NULL) { return pcmk_rc_cib_corrupt; } rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { cib_delete(cib); return rc; } pcmk__register_lib_messages(out); pe__register_messages(out); stonith__register_messages(out); rc = pcmk__status(out, cib, pcmk__fence_history_full, pcmk_section_all, show_opts, NULL, NULL, NULL, false, 0); pcmk__xml_output_finish(out, xml); cib_delete(cib); return rc; } /*! * \internal * \brief Query and output the cluster status * * The operation is considered a success if we're able to get the \p pacemakerd * state. If possible, we'll also try to connect to the fencer and CIB and * output their respective status information. * * \param[in,out] out Output object * \param[in,out] cib CIB connection * \param[in] fence_history How much of the fencing history to output * \param[in] show Group of \p pcmk_section_e flags * \param[in] show_opts Group of \p pcmk_show_opt_e flags * \param[in] only_node If a node name or tag, include only the * matching node(s) (if any) in the output. * If \p "*" or \p NULL, include all nodes * in the output. * \param[in] only_rsc If a resource ID or tag, include only the * matching resource(s) (if any) in the * output. If \p "*" or \p NULL, include all * resources in the output. * \param[in] neg_location_prefix Prefix denoting a ban in a constraint ID * \param[in] simple_output Whether to use a simple output format. * Note: This is for use by \p crm_mon only * and is planned to be deprecated. * \param[in] timeout_ms How long to wait for a reply from the * \p pacemakerd API. If 0, * \p pcmk_ipc_dispatch_sync will be used. * If positive, \p pcmk_ipc_dispatch_main * will be used, and a new mainloop will be * created for this purpose (freed before * return). * * \return Standard Pacemaker return code */ int pcmk__status(pcmk__output_t *out, cib_t *cib, enum pcmk__fence_history fence_history, uint32_t show, uint32_t show_opts, const char *only_node, const char *only_rsc, const char *neg_location_prefix, bool simple_output, unsigned int timeout_ms) { xmlNode *current_cib = NULL; int rc = pcmk_rc_ok; stonith_t *stonith = NULL; enum pcmk_pacemakerd_state pcmkd_state = pcmk_pacemakerd_state_invalid; time_t last_updated = 0; if (cib == NULL) { return ENOTCONN; } if (cib->variant == cib_native) { rc = pcmk__pacemakerd_status(out, crm_system_name, timeout_ms, false, &pcmkd_state); if (rc != pcmk_rc_ok) { return rc; } last_updated = time(NULL); switch (pcmkd_state) { case pcmk_pacemakerd_state_running: case pcmk_pacemakerd_state_shutting_down: case pcmk_pacemakerd_state_remote: /* Fencer and CIB may still be available while shutting down or * running on a Pacemaker Remote node */ break; default: // Fencer and CIB are definitely unavailable out->message(out, "pacemakerd-health", NULL, pcmkd_state, NULL, last_updated); return rc; } if (fence_history != pcmk__fence_history_none) { stonith = fencing_connect(); } } rc = cib__signon_query(out, &cib, ¤t_cib); if (rc != pcmk_rc_ok) { if (pcmkd_state != pcmk_pacemakerd_state_invalid) { // Invalid at this point means we didn't query the pcmkd state out->message(out, "pacemakerd-health", NULL, pcmkd_state, NULL, last_updated); } goto done; } rc = pcmk__output_cluster_status(out, stonith, cib, current_cib, pcmkd_state, fence_history, show, show_opts, only_node, only_rsc, neg_location_prefix, simple_output); if (rc != pcmk_rc_ok) { out->err(out, "Error outputting status info from the fencer or CIB"); } done: stonith_api_delete(stonith); free_xml(current_cib); return pcmk_rc_ok; } /* This is an internal-only function that is planned to be deprecated and removed. * It should only ever be called from crm_mon. */ int pcmk__output_simple_status(pcmk__output_t *out, const pe_working_set_t *data_set) { int nodes_online = 0; int nodes_standby = 0; int nodes_maintenance = 0; GString *offline_nodes = NULL; bool no_dc = false; bool offline = false; bool has_warnings = false; if (data_set->dc_node == NULL) { has_warnings = true; no_dc = true; } for (GList *iter = data_set->nodes; iter != NULL; iter = iter->next) { pe_node_t *node = (pe_node_t *) iter->data; if (node->details->standby && node->details->online) { nodes_standby++; } else if (node->details->maintenance && node->details->online) { nodes_maintenance++; } else if (node->details->online) { nodes_online++; } else { pcmk__add_word(&offline_nodes, 1024, "offline node:"); pcmk__add_word(&offline_nodes, 0, pe__node_name(node)); has_warnings = true; offline = true; } } if (has_warnings) { out->info(out, "CLUSTER WARN: %s%s%s", no_dc ? "No DC" : "", no_dc && offline ? ", " : "", (offline? (const char *) offline_nodes->str : "")); if (offline_nodes != NULL) { g_string_free(offline_nodes, TRUE); } } else { char *nodes_standby_s = NULL; char *nodes_maint_s = NULL; if (nodes_standby > 0) { nodes_standby_s = crm_strdup_printf(", %d standby node%s", nodes_standby, pcmk__plural_s(nodes_standby)); } if (nodes_maintenance > 0) { nodes_maint_s = crm_strdup_printf(", %d maintenance node%s", nodes_maintenance, pcmk__plural_s(nodes_maintenance)); } out->info(out, "CLUSTER OK: %d node%s online%s%s, " "%d resource instance%s configured", nodes_online, pcmk__plural_s(nodes_online), nodes_standby_s != NULL ? nodes_standby_s : "", nodes_maint_s != NULL ? nodes_maint_s : "", data_set->ninstances, pcmk__plural_s(data_set->ninstances)); free(nodes_standby_s); free(nodes_maint_s); } if (has_warnings) { return pcmk_rc_error; } else { return pcmk_rc_ok; } /* coverity[leaked_storage] False positive */ } diff --git a/lib/pengine/pe_notif.c b/lib/pengine/pe_notif.c index 7ed490f7dd..21852987d5 100644 --- a/lib/pengine/pe_notif.c +++ b/lib/pengine/pe_notif.c @@ -1,996 +1,998 @@ /* * Copyright 2004-2023 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 + +#include #include #include "pe_status_private.h" typedef struct notify_entry_s { const pe_resource_t *rsc; const pe_node_t *node; } notify_entry_t; /*! * \internal * \brief Compare two notification entries * * Compare two notification entries, where the one with the alphabetically first * resource name (or if equal, node name) sorts as first, with NULL sorting as * less than non-NULL. * * \param[in] a First notification entry to compare * \param[in] b Second notification entry to compare * * \return -1 if \p a sorts before \p b, 0 if they are equal, otherwise 1 */ static gint compare_notify_entries(gconstpointer a, gconstpointer b) { int tmp; const notify_entry_t *entry_a = a; const notify_entry_t *entry_b = b; // NULL a or b is not actually possible if ((entry_a == NULL) && (entry_b == NULL)) { return 0; } if (entry_a == NULL) { return 1; } if (entry_b == NULL) { return -1; } // NULL resources sort first if ((entry_a->rsc == NULL) && (entry_b->rsc == NULL)) { return 0; } if (entry_a->rsc == NULL) { return 1; } if (entry_b->rsc == NULL) { return -1; } // Compare resource names tmp = strcmp(entry_a->rsc->id, entry_b->rsc->id); if (tmp != 0) { return tmp; } // Otherwise NULL nodes sort first if ((entry_a->node == NULL) && (entry_b->node == NULL)) { return 0; } if (entry_a->node == NULL) { return 1; } if (entry_b->node == NULL) { return -1; } // Finally, compare node names return strcmp(entry_a->node->details->id, entry_b->node->details->id); } /*! * \internal * \brief Duplicate a notification entry * * \param[in] entry Entry to duplicate * * \return Newly allocated duplicate of \p entry * \note It is the caller's responsibility to free the return value. */ static notify_entry_t * dup_notify_entry(const notify_entry_t *entry) { notify_entry_t *dup = calloc(1, sizeof(notify_entry_t)); CRM_ASSERT(dup != NULL); dup->rsc = entry->rsc; dup->node = entry->node; return dup; } /*! * \internal * \brief Given a list of nodes, create strings with node names * * \param[in] list List of nodes (as pe_node_t *) * \param[out] all_node_names If not NULL, will be set to space-separated list * of the names of all nodes in \p list * \param[out] host_node_names Same as \p all_node_names, except active * guest nodes will list the name of their host * * \note The caller is responsible for freeing the output argument values using * \p g_string_free(). */ static void get_node_names(const GList *list, GString **all_node_names, GString **host_node_names) { if (all_node_names != NULL) { *all_node_names = NULL; } if (host_node_names != NULL) { *host_node_names = NULL; } for (const GList *iter = list; iter != NULL; iter = iter->next) { const pe_node_t *node = (const pe_node_t *) iter->data; if (node->details->uname == NULL) { continue; } // Always add to list of all node names if (all_node_names != NULL) { pcmk__add_word(all_node_names, 1024, node->details->uname); } // Add to host node name list if appropriate if (host_node_names != NULL) { if (pe__is_guest_node(node) && (node->details->remote_rsc->container->running_on != NULL)) { node = pe__current_node(node->details->remote_rsc->container); if (node->details->uname == NULL) { continue; } } pcmk__add_word(host_node_names, 1024, node->details->uname); } } if ((all_node_names != NULL) && (*all_node_names == NULL)) { *all_node_names = g_string_new(" "); } if ((host_node_names != NULL) && (*host_node_names == NULL)) { *host_node_names = g_string_new(" "); } } /*! * \internal * \brief Create strings of instance and node names from notification entries * * \param[in,out] list List of notification entries (will be sorted here) * \param[out] rsc_names If not NULL, will be set to space-separated list * of clone instances from \p list * \param[out] node_names If not NULL, will be set to space-separated list * of node names from \p list * * \return (Possibly new) head of sorted \p list * \note The caller is responsible for freeing the output argument values using * \p g_list_free_full() and \p g_string_free(). */ static GList * notify_entries_to_strings(GList *list, GString **rsc_names, GString **node_names) { const char *last_rsc_id = NULL; // Initialize output lists to NULL if (rsc_names != NULL) { *rsc_names = NULL; } if (node_names != NULL) { *node_names = NULL; } // Sort input list for user-friendliness (and ease of filtering duplicates) list = g_list_sort(list, compare_notify_entries); for (GList *gIter = list; gIter != NULL; gIter = gIter->next) { notify_entry_t *entry = (notify_entry_t *) gIter->data; // Entry must have a resource (with ID) CRM_LOG_ASSERT((entry != NULL) && (entry->rsc != NULL) && (entry->rsc->id != NULL)); if ((entry == NULL) || (entry->rsc == NULL) || (entry->rsc->id == NULL)) { continue; } // Entry must have a node unless listing inactive resources CRM_LOG_ASSERT((node_names == NULL) || (entry->node != NULL)); if ((node_names != NULL) && (entry->node == NULL)) { continue; } // Don't add duplicates of a particular clone instance if (pcmk__str_eq(entry->rsc->id, last_rsc_id, pcmk__str_none)) { continue; } last_rsc_id = entry->rsc->id; if (rsc_names != NULL) { pcmk__add_word(rsc_names, 1024, entry->rsc->id); } if ((node_names != NULL) && (entry->node->details->uname != NULL)) { pcmk__add_word(node_names, 1024, entry->node->details->uname); } } // If there are no entries, return "empty" lists if ((rsc_names != NULL) && (*rsc_names == NULL)) { *rsc_names = g_string_new(" "); } if ((node_names != NULL) && (*node_names == NULL)) { *node_names = g_string_new(" "); } return list; } /*! * \internal * \brief Copy a meta-attribute into a notify action * * \param[in] key Name of meta-attribute to copy * \param[in] value Value of meta-attribute to copy * \param[in,out] user_data Notify action to copy into */ static void copy_meta_to_notify(gpointer key, gpointer value, gpointer user_data) { pe_action_t *notify = (pe_action_t *) user_data; /* Any existing meta-attributes (for example, the action timeout) are for * the notify action itself, so don't override those. */ if (g_hash_table_lookup(notify->meta, (const char *) key) != NULL) { return; } g_hash_table_insert(notify->meta, strdup((const char *) key), strdup((const char *) value)); } static void add_notify_data_to_action_meta(const notify_data_t *n_data, pe_action_t *action) { for (const GSList *item = n_data->keys; item; item = item->next) { const pcmk_nvpair_t *nvpair = (const pcmk_nvpair_t *) item->data; add_hash_param(action->meta, nvpair->name, nvpair->value); } } /*! * \internal * \brief Create a new notify pseudo-action for a clone resource * * \param[in,out] rsc Clone resource that notification is for * \param[in] action Action to use in notify action key * \param[in] notif_action RSC_NOTIFY or RSC_NOTIFIED * \param[in] notif_type "pre", "post", "confirmed-pre", "confirmed-post" * * \return Newly created notify pseudo-action */ static pe_action_t * new_notify_pseudo_action(pe_resource_t *rsc, const pe_action_t *action, const char *notif_action, const char *notif_type) { pe_action_t *notify = NULL; notify = custom_action(rsc, pcmk__notify_key(rsc->id, notif_type, action->task), notif_action, NULL, pcmk_is_set(action->flags, pe_action_optional), TRUE, rsc->cluster); pe__set_action_flags(notify, pe_action_pseudo); add_hash_param(notify->meta, "notify_key_type", notif_type); add_hash_param(notify->meta, "notify_key_operation", action->task); return notify; } /*! * \internal * \brief Create a new notify action for a clone instance * * \param[in,out] rsc Clone instance that notification is for * \param[in] node Node that notification is for * \param[in,out] op Action that notification is for * \param[in,out] notify_done Parent pseudo-action for notifications complete * \param[in] n_data Notification values to add to action meta-data * * \return Newly created notify action */ static pe_action_t * new_notify_action(pe_resource_t *rsc, const pe_node_t *node, pe_action_t *op, pe_action_t *notify_done, const notify_data_t *n_data) { char *key = NULL; pe_action_t *notify_action = NULL; const char *value = NULL; const char *task = NULL; const char *skip_reason = NULL; CRM_CHECK((rsc != NULL) && (node != NULL), return NULL); // Ensure we have all the info we need if (op == NULL) { skip_reason = "no action"; } else if (notify_done == NULL) { skip_reason = "no parent notification"; } else if (!node->details->online) { skip_reason = "node offline"; } else if (!pcmk_is_set(op->flags, pe_action_runnable)) { skip_reason = "original action not runnable"; } if (skip_reason != NULL) { pe_rsc_trace(rsc, "Skipping notify action for %s on %s: %s", rsc->id, pe__node_name(node), skip_reason); return NULL; } value = g_hash_table_lookup(op->meta, "notify_type"); // "pre" or "post" task = g_hash_table_lookup(op->meta, "notify_operation"); // original action pe_rsc_trace(rsc, "Creating notify action for %s on %s (%s-%s)", rsc->id, pe__node_name(node), value, task); // Create the notify action key = pcmk__notify_key(rsc->id, value, task); notify_action = custom_action(rsc, key, op->task, node, pcmk_is_set(op->flags, pe_action_optional), TRUE, rsc->cluster); // Add meta-data to notify action g_hash_table_foreach(op->meta, copy_meta_to_notify, notify_action); add_notify_data_to_action_meta(n_data, notify_action); // Order notify after original action and before parent notification order_actions(op, notify_action, pe_order_optional); order_actions(notify_action, notify_done, pe_order_optional); return notify_action; } /*! * \internal * \brief Create a new "post-" notify action for a clone instance * * \param[in,out] rsc Clone instance that notification is for * \param[in] node Node that notification is for * \param[in,out] n_data Notification values to add to action meta-data */ static void new_post_notify_action(pe_resource_t *rsc, const pe_node_t *node, notify_data_t *n_data) { pe_action_t *notify = NULL; CRM_ASSERT(n_data != NULL); // Create the "post-" notify action for specified instance notify = new_notify_action(rsc, node, n_data->post, n_data->post_done, n_data); if (notify != NULL) { notify->priority = INFINITY; } // Order recurring monitors after all "post-" notifications complete if (n_data->post_done == NULL) { return; } for (GList *iter = rsc->actions; iter != NULL; iter = iter->next) { pe_action_t *mon = (pe_action_t *) iter->data; const char *interval_ms_s = NULL; interval_ms_s = g_hash_table_lookup(mon->meta, XML_LRM_ATTR_INTERVAL_MS); if (pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches) || pcmk__str_eq(mon->task, RSC_CANCEL, pcmk__str_none)) { continue; // Not a recurring monitor } order_actions(n_data->post_done, mon, pe_order_optional); } } /*! * \internal * \brief Create and order notification pseudo-actions for a clone action * * In addition to the actual notify actions needed for each clone instance, * clone notifications also require pseudo-actions to provide ordering points * in the notification process. This creates the notification data, along with * appropriate pseudo-actions and their orderings. * * For example, the ordering sequence for starting a clone is: * * "pre-" notify pseudo-action for clone * -> "pre-" notify actions for each clone instance * -> "pre-" notifications complete pseudo-action for clone * -> start actions for each clone instance * -> "started" pseudo-action for clone * -> "post-" notify pseudo-action for clone * -> "post-" notify actions for each clone instance * -> "post-" notifications complete pseudo-action for clone * * \param[in,out] rsc Clone that notifications are for * \param[in] task Name of action that notifications are for * \param[in,out] action If not NULL, create a "pre-" pseudo-action ordered * before a "pre-" complete pseudo-action, ordered * before this action * \param[in,out] complete If not NULL, create a "post-" pseudo-action ordered * after this action, and a "post-" complete * pseudo-action ordered after that * * \return Newly created notification data */ notify_data_t * pe__action_notif_pseudo_ops(pe_resource_t *rsc, const char *task, pe_action_t *action, pe_action_t *complete) { notify_data_t *n_data = NULL; if (!pcmk_is_set(rsc->flags, pe_rsc_notify)) { return NULL; } n_data = calloc(1, sizeof(notify_data_t)); CRM_ASSERT(n_data != NULL); n_data->action = task; if (action != NULL) { // Need "pre-" pseudo-actions // Create "pre-" notify pseudo-action for clone n_data->pre = new_notify_pseudo_action(rsc, action, RSC_NOTIFY, "pre"); pe__set_action_flags(n_data->pre, pe_action_runnable); add_hash_param(n_data->pre->meta, "notify_type", "pre"); add_hash_param(n_data->pre->meta, "notify_operation", n_data->action); // Create "pre-" notifications complete pseudo-action for clone n_data->pre_done = new_notify_pseudo_action(rsc, action, RSC_NOTIFIED, "confirmed-pre"); pe__set_action_flags(n_data->pre_done, pe_action_runnable); add_hash_param(n_data->pre_done->meta, "notify_type", "pre"); add_hash_param(n_data->pre_done->meta, "notify_operation", n_data->action); // Order "pre-" -> "pre-" complete -> original action order_actions(n_data->pre, n_data->pre_done, pe_order_optional); order_actions(n_data->pre_done, action, pe_order_optional); } if (complete != NULL) { // Need "post-" pseudo-actions // Create "post-" notify pseudo-action for clone n_data->post = new_notify_pseudo_action(rsc, complete, RSC_NOTIFY, "post"); n_data->post->priority = INFINITY; if (pcmk_is_set(complete->flags, pe_action_runnable)) { pe__set_action_flags(n_data->post, pe_action_runnable); } else { pe__clear_action_flags(n_data->post, pe_action_runnable); } add_hash_param(n_data->post->meta, "notify_type", "post"); add_hash_param(n_data->post->meta, "notify_operation", n_data->action); // Create "post-" notifications complete pseudo-action for clone n_data->post_done = new_notify_pseudo_action(rsc, complete, RSC_NOTIFIED, "confirmed-post"); n_data->post_done->priority = INFINITY; if (pcmk_is_set(complete->flags, pe_action_runnable)) { pe__set_action_flags(n_data->post_done, pe_action_runnable); } else { pe__clear_action_flags(n_data->post_done, pe_action_runnable); } add_hash_param(n_data->post_done->meta, "notify_type", "post"); add_hash_param(n_data->post_done->meta, "notify_operation", n_data->action); // Order original action complete -> "post-" -> "post-" complete order_actions(complete, n_data->post, pe_order_implies_then); order_actions(n_data->post, n_data->post_done, pe_order_implies_then); } // If we created both, order "pre-" complete -> "post-" if ((action != NULL) && (complete != NULL)) { order_actions(n_data->pre_done, n_data->post, pe_order_optional); } return n_data; } /*! * \internal * \brief Create a new notification entry * * \param[in] rsc Resource for notification * \param[in] node Node for notification * * \return Newly allocated notification entry * \note The caller is responsible for freeing the return value. */ static notify_entry_t * new_notify_entry(const pe_resource_t *rsc, const pe_node_t *node) { notify_entry_t *entry = calloc(1, sizeof(notify_entry_t)); CRM_ASSERT(entry != NULL); entry->rsc = rsc; entry->node = node; return entry; } /*! * \internal * \brief Add notification data for resource state and optionally actions * * \param[in] rsc Clone or clone instance being notified * \param[in] activity Whether to add notification entries for actions * \param[in,out] n_data Notification data for clone */ static void collect_resource_data(const pe_resource_t *rsc, bool activity, notify_data_t *n_data) { const GList *iter = NULL; notify_entry_t *entry = NULL; const pe_node_t *node = NULL; if (n_data == NULL) { return; } if (n_data->allowed_nodes == NULL) { n_data->allowed_nodes = rsc->allowed_nodes; } // If this is a clone, call recursively for each instance if (rsc->children != NULL) { for (iter = rsc->children; iter != NULL; iter = iter->next) { const pe_resource_t *child = (const pe_resource_t *) iter->data; collect_resource_data(child, activity, n_data); } return; } // This is a notification for a single clone instance if (rsc->running_on != NULL) { node = rsc->running_on->data; // First is sufficient } entry = new_notify_entry(rsc, node); // Add notification indicating the resource state switch (rsc->role) { case RSC_ROLE_STOPPED: n_data->inactive = g_list_prepend(n_data->inactive, entry); break; case RSC_ROLE_STARTED: n_data->active = g_list_prepend(n_data->active, entry); break; case RSC_ROLE_UNPROMOTED: n_data->unpromoted = g_list_prepend(n_data->unpromoted, entry); n_data->active = g_list_prepend(n_data->active, dup_notify_entry(entry)); break; case RSC_ROLE_PROMOTED: n_data->promoted = g_list_prepend(n_data->promoted, entry); n_data->active = g_list_prepend(n_data->active, dup_notify_entry(entry)); break; default: crm_err("Resource %s role on %s (%s) is not supported for " "notifications (bug?)", rsc->id, pe__node_name(node), role2text(rsc->role)); free(entry); break; } if (!activity) { return; } // Add notification entries for each of the resource's actions for (iter = rsc->actions; iter != NULL; iter = iter->next) { const pe_action_t *op = (const pe_action_t *) iter->data; if (!pcmk_is_set(op->flags, pe_action_optional) && (op->node != NULL)) { enum action_tasks task = text2task(op->task); if ((task == stop_rsc) && op->node->details->unclean) { // Create anyway (additional noise if node can't be fenced) } else if (!pcmk_is_set(op->flags, pe_action_runnable)) { continue; } entry = new_notify_entry(rsc, op->node); switch (task) { case start_rsc: n_data->start = g_list_prepend(n_data->start, entry); break; case stop_rsc: n_data->stop = g_list_prepend(n_data->stop, entry); break; case action_promote: n_data->promote = g_list_prepend(n_data->promote, entry); break; case action_demote: n_data->demote = g_list_prepend(n_data->demote, entry); break; default: free(entry); break; } } } } // For (char *) value #define add_notify_env(n_data, key, value) do { \ n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, value); \ } while (0) // For (GString *) value #define add_notify_env_gs(n_data, key, value) do { \ n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, \ (const char *) value->str); \ } while (0) // For (GString *) value #define add_notify_env_free_gs(n_data, key, value) do { \ n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, \ (const char *) value->str); \ g_string_free(value, TRUE); value = NULL; \ } while (0) /*! * \internal * \brief Create notification name/value pairs from structured data * * \param[in] rsc Resource that notification is for * \param[in,out] n_data Notification data */ static void add_notif_keys(const pe_resource_t *rsc, notify_data_t *n_data) { bool required = false; // Whether to make notify actions required GString *rsc_list = NULL; GString *node_list = NULL; GString *metal_list = NULL; const char *source = NULL; GList *nodes = NULL; n_data->stop = notify_entries_to_strings(n_data->stop, &rsc_list, &node_list); if ((strcmp(" ", (const char *) rsc_list->str) != 0) && pcmk__str_eq(n_data->action, RSC_STOP, pcmk__str_none)) { required = true; } add_notify_env_free_gs(n_data, "notify_stop_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_stop_uname", node_list); if ((n_data->start != NULL) && pcmk__str_eq(n_data->action, RSC_START, pcmk__str_none)) { required = true; } n_data->start = notify_entries_to_strings(n_data->start, &rsc_list, &node_list); add_notify_env_free_gs(n_data, "notify_start_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_start_uname", node_list); if ((n_data->demote != NULL) && pcmk__str_eq(n_data->action, RSC_DEMOTE, pcmk__str_none)) { required = true; } n_data->demote = notify_entries_to_strings(n_data->demote, &rsc_list, &node_list); add_notify_env_free_gs(n_data, "notify_demote_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_demote_uname", node_list); if ((n_data->promote != NULL) && pcmk__str_eq(n_data->action, RSC_PROMOTE, pcmk__str_none)) { required = true; } n_data->promote = notify_entries_to_strings(n_data->promote, &rsc_list, &node_list); add_notify_env_free_gs(n_data, "notify_promote_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_promote_uname", node_list); n_data->active = notify_entries_to_strings(n_data->active, &rsc_list, &node_list); add_notify_env_free_gs(n_data, "notify_active_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_active_uname", node_list); n_data->unpromoted = notify_entries_to_strings(n_data->unpromoted, &rsc_list, &node_list); add_notify_env_gs(n_data, "notify_unpromoted_resource", rsc_list); add_notify_env_gs(n_data, "notify_unpromoted_uname", node_list); // Deprecated: kept for backward compatibility with older resource agents add_notify_env_free_gs(n_data, "notify_slave_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_slave_uname", node_list); n_data->promoted = notify_entries_to_strings(n_data->promoted, &rsc_list, &node_list); add_notify_env_gs(n_data, "notify_promoted_resource", rsc_list); add_notify_env_gs(n_data, "notify_promoted_uname", node_list); // Deprecated: kept for backward compatibility with older resource agents add_notify_env_free_gs(n_data, "notify_master_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_master_uname", node_list); n_data->inactive = notify_entries_to_strings(n_data->inactive, &rsc_list, NULL); add_notify_env_free_gs(n_data, "notify_inactive_resource", rsc_list); nodes = g_hash_table_get_values(n_data->allowed_nodes); if (!pcmk__is_daemon) { /* For display purposes, sort the node list, for consistent * regression test output (while avoiding the performance hit * for the live cluster). */ nodes = g_list_sort(nodes, pe__cmp_node_name); } get_node_names(nodes, &node_list, NULL); add_notify_env_free_gs(n_data, "notify_available_uname", node_list); g_list_free(nodes); source = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET); if (pcmk__str_eq("host", source, pcmk__str_none)) { get_node_names(rsc->cluster->nodes, &node_list, &metal_list); add_notify_env_free_gs(n_data, "notify_all_hosts", metal_list); } else { get_node_names(rsc->cluster->nodes, &node_list, NULL); } add_notify_env_free_gs(n_data, "notify_all_uname", node_list); if (required && (n_data->pre != NULL)) { pe__clear_action_flags(n_data->pre, pe_action_optional); pe__clear_action_flags(n_data->pre_done, pe_action_optional); } if (required && (n_data->post != NULL)) { pe__clear_action_flags(n_data->post, pe_action_optional); pe__clear_action_flags(n_data->post_done, pe_action_optional); } } /* * \internal * \brief Find any remote connection start relevant to an action * * \param[in] action Action to check * * \return If action is behind a remote connection, connection's start */ static pe_action_t * find_remote_start(pe_action_t *action) { if ((action != NULL) && (action->node != NULL)) { pe_resource_t *remote_rsc = action->node->details->remote_rsc; if (remote_rsc != NULL) { return find_first_action(remote_rsc->actions, NULL, RSC_START, NULL); } } return NULL; } /*! * \internal * \brief Create notify actions, and add notify data to original actions * * \param[in,out] rsc Clone or clone instance that notification is for * \param[in,out] n_data Clone notification data for some action */ static void create_notify_actions(pe_resource_t *rsc, notify_data_t *n_data) { GList *iter = NULL; pe_action_t *stop = NULL; pe_action_t *start = NULL; enum action_tasks task = text2task(n_data->action); // If this is a clone, call recursively for each instance if (rsc->children != NULL) { g_list_foreach(rsc->children, (GFunc) create_notify_actions, n_data); return; } // Add notification meta-attributes to original actions for (iter = rsc->actions; iter != NULL; iter = iter->next) { pe_action_t *op = (pe_action_t *) iter->data; if (!pcmk_is_set(op->flags, pe_action_optional) && (op->node != NULL)) { switch (text2task(op->task)) { case start_rsc: case stop_rsc: case action_promote: case action_demote: add_notify_data_to_action_meta(n_data, op); break; default: break; } } } // Skip notify action itself if original action was not needed switch (task) { case start_rsc: if (n_data->start == NULL) { pe_rsc_trace(rsc, "No notify action needed for %s %s", rsc->id, n_data->action); return; } break; case action_promote: if (n_data->promote == NULL) { pe_rsc_trace(rsc, "No notify action needed for %s %s", rsc->id, n_data->action); return; } break; case action_demote: if (n_data->demote == NULL) { pe_rsc_trace(rsc, "No notify action needed for %s %s", rsc->id, n_data->action); return; } break; default: // We cannot do same for stop because it might be implied by fencing break; } pe_rsc_trace(rsc, "Creating notify actions for %s %s", rsc->id, n_data->action); // Create notify actions for stop or demote if ((rsc->role != RSC_ROLE_STOPPED) && ((task == stop_rsc) || (task == action_demote))) { stop = find_first_action(rsc->actions, NULL, RSC_STOP, NULL); for (iter = rsc->running_on; iter != NULL; iter = iter->next) { pe_node_t *current_node = (pe_node_t *) iter->data; /* If a stop is a pseudo-action implied by fencing, don't try to * notify the node getting fenced. */ if ((stop != NULL) && pcmk_is_set(stop->flags, pe_action_pseudo) && (current_node->details->unclean || current_node->details->remote_requires_reset)) { continue; } new_notify_action(rsc, current_node, n_data->pre, n_data->pre_done, n_data); if ((task == action_demote) || (stop == NULL) || pcmk_is_set(stop->flags, pe_action_optional)) { new_post_notify_action(rsc, current_node, n_data); } } } // Create notify actions for start or promote if ((rsc->next_role != RSC_ROLE_STOPPED) && ((task == start_rsc) || (task == action_promote))) { start = find_first_action(rsc->actions, NULL, RSC_START, NULL); if (start != NULL) { pe_action_t *remote_start = find_remote_start(start); if ((remote_start != NULL) && !pcmk_is_set(remote_start->flags, pe_action_runnable)) { /* Start and promote actions for a clone instance behind * a Pacemaker Remote connection happen after the * connection starts. If the connection start is blocked, do * not schedule notifications for these actions. */ return; } } if (rsc->allocated_to == NULL) { pe_proc_err("Next role '%s' but %s is not allocated", role2text(rsc->next_role), rsc->id); return; } if ((task != start_rsc) || (start == NULL) || pcmk_is_set(start->flags, pe_action_optional)) { new_notify_action(rsc, rsc->allocated_to, n_data->pre, n_data->pre_done, n_data); } new_post_notify_action(rsc, rsc->allocated_to, n_data); } } /*! * \internal * \brief Create notification data and actions for one clone action * * \param[in,out] rsc Clone resource that notification is for * \param[in,out] n_data Clone notification data for some action */ void pe__create_action_notifications(pe_resource_t *rsc, notify_data_t *n_data) { if ((rsc == NULL) || (n_data == NULL)) { return; } collect_resource_data(rsc, true, n_data); add_notif_keys(rsc, n_data); create_notify_actions(rsc, n_data); } /*! * \internal * \brief Free notification data for one action * * \param[in,out] n_data Notification data to free */ void pe__free_action_notification_data(notify_data_t *n_data) { if (n_data == NULL) { return; } g_list_free_full(n_data->stop, free); g_list_free_full(n_data->start, free); g_list_free_full(n_data->demote, free); g_list_free_full(n_data->promote, free); g_list_free_full(n_data->promoted, free); g_list_free_full(n_data->unpromoted, free); g_list_free_full(n_data->active, free); g_list_free_full(n_data->inactive, free); pcmk_free_nvpairs(n_data->keys); free(n_data); } /*! * \internal * \brief Order clone "notifications complete" pseudo-action after fencing * * If a stop action is implied by fencing, the usual notification pseudo-actions * will not be sufficient to order things properly, or even create all needed * notifications if the clone is also stopping on another node, and another * clone is ordered after it. This function creates new notification * pseudo-actions relative to the fencing to ensure everything works properly. * * \param[in] stop Stop action implied by fencing * \param[in,out] rsc Clone resource that notification is for * \param[in,out] stonith_op Fencing action that implies \p stop */ void pe__order_notifs_after_fencing(const pe_action_t *stop, pe_resource_t *rsc, pe_action_t *stonith_op) { notify_data_t *n_data; crm_info("Ordering notifications for implied %s after fencing", stop->uuid); n_data = pe__action_notif_pseudo_ops(rsc, RSC_STOP, NULL, stonith_op); if (n_data != NULL) { collect_resource_data(rsc, false, n_data); add_notify_env(n_data, "notify_stop_resource", rsc->id); add_notify_env(n_data, "notify_stop_uname", stop->node->details->uname); create_notify_actions(uber_parent(rsc), n_data); pe__free_action_notification_data(n_data); } } diff --git a/lib/pengine/pe_status_private.h b/lib/pengine/pe_status_private.h index e0acc8f74e..017b4bf46a 100644 --- a/lib/pengine/pe_status_private.h +++ b/lib/pengine/pe_status_private.h @@ -1,141 +1,146 @@ /* * Copyright 2018-2023 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 PE_STATUS_PRIVATE__H # define PE_STATUS_PRIVATE__H /* This header is for the sole use of libpe_status, so that functions can be * declared with G_GNUC_INTERNAL for efficiency. */ #if defined(PCMK__UNIT_TESTING) #undef G_GNUC_INTERNAL #define G_GNUC_INTERNAL #endif +#include // GSList, GList, GHashTable +#include // xmlNode + +#include // pe_action_t, pe_resource_t, etc. + /*! * \internal * \deprecated This macro will be removed in a future release */ # define status_print(fmt, args...) \ if(options & pe_print_html) { \ FILE *stream = print_data; \ fprintf(stream, fmt, ##args); \ } else if(options & pe_print_printf || options & pe_print_ncurses) { \ FILE *stream = print_data; \ fprintf(stream, fmt, ##args); \ } else if(options & pe_print_xml) { \ FILE *stream = print_data; \ fprintf(stream, fmt, ##args); \ } else if(options & pe_print_log) { \ int log_level = *(int*)print_data; \ do_crm_log(log_level, fmt, ##args); \ } typedef struct notify_data_s { GSList *keys; // Environment variable name/value pairs const char *action; pe_action_t *pre; pe_action_t *post; pe_action_t *pre_done; pe_action_t *post_done; GList *active; /* notify_entry_t* */ GList *inactive; /* notify_entry_t* */ GList *start; /* notify_entry_t* */ GList *stop; /* notify_entry_t* */ GList *demote; /* notify_entry_t* */ GList *promote; /* notify_entry_t* */ GList *promoted; /* notify_entry_t* */ GList *unpromoted; /* notify_entry_t* */ GHashTable *allowed_nodes; } notify_data_t; G_GNUC_INTERNAL pe_resource_t *pe__create_clone_child(pe_resource_t *rsc, pe_working_set_t *data_set); G_GNUC_INTERNAL void pe__create_action_notifications(pe_resource_t *rsc, notify_data_t *n_data); G_GNUC_INTERNAL void pe__free_action_notification_data(notify_data_t *n_data); G_GNUC_INTERNAL notify_data_t *pe__action_notif_pseudo_ops(pe_resource_t *rsc, const char *task, pe_action_t *action, pe_action_t *complete); G_GNUC_INTERNAL void pe__force_anon(const char *standard, pe_resource_t *rsc, const char *rid, pe_working_set_t *data_set); G_GNUC_INTERNAL gint pe__cmp_rsc_priority(gconstpointer a, gconstpointer b); G_GNUC_INTERNAL gboolean pe__unpack_resource(xmlNode *xml_obj, pe_resource_t **rsc, pe_resource_t *parent, pe_working_set_t *data_set); G_GNUC_INTERNAL gboolean unpack_remote_nodes(xmlNode *xml_resources, pe_working_set_t *data_set); G_GNUC_INTERNAL gboolean unpack_resources(const xmlNode *xml_resources, pe_working_set_t *data_set); G_GNUC_INTERNAL gboolean unpack_config(xmlNode *config, pe_working_set_t *data_set); G_GNUC_INTERNAL gboolean unpack_nodes(xmlNode *xml_nodes, pe_working_set_t *data_set); G_GNUC_INTERNAL gboolean unpack_tags(xmlNode *xml_tags, pe_working_set_t *data_set); G_GNUC_INTERNAL gboolean unpack_status(xmlNode *status, pe_working_set_t *data_set); G_GNUC_INTERNAL op_digest_cache_t *pe__compare_fencing_digest(pe_resource_t *rsc, const char *agent, pe_node_t *node, pe_working_set_t *data_set); G_GNUC_INTERNAL void pe__unpack_node_health_scores(pe_working_set_t *data_set); // Primitive resource methods G_GNUC_INTERNAL unsigned int pe__primitive_max_per_node(const pe_resource_t *rsc); // Group resource methods G_GNUC_INTERNAL unsigned int pe__group_max_per_node(const pe_resource_t *rsc); // Clone resource methods G_GNUC_INTERNAL unsigned int pe__clone_max_per_node(const pe_resource_t *rsc); // Bundle resource methods G_GNUC_INTERNAL pe_node_t *pe__bundle_active_node(const pe_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean); G_GNUC_INTERNAL unsigned int pe__bundle_max_per_node(const pe_resource_t *rsc); #endif // PE_STATUS_PRIVATE__H diff --git a/tools/crm_simulate.c b/tools/crm_simulate.c index 932c5bd229..7e12b49a42 100644 --- a/tools/crm_simulate.c +++ b/tools/crm_simulate.c @@ -1,587 +1,588 @@ /* * Copyright 2009-2023 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #define SUMMARY "crm_simulate - simulate a Pacemaker cluster's response to events" struct { char *dot_file; char *graph_file; gchar *input_file; pcmk_injections_t *injections; unsigned int flags; gchar *output_file; long long repeat; gboolean store; gchar *test_dir; char *use_date; char *xml_file; } options = { .flags = pcmk_sim_show_pending | pcmk_sim_sanitized, .repeat = 1 }; uint32_t section_opts = 0; char *temp_shadow = NULL; crm_exit_t exit_code = CRM_EX_OK; #define INDENT " " static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; static gboolean all_actions_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.flags |= pcmk_sim_all_actions; return TRUE; } static gboolean attrs_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { section_opts |= pcmk_section_attributes; return TRUE; } static gboolean failcounts_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { section_opts |= pcmk_section_failcounts | pcmk_section_failures; return TRUE; } static gboolean in_place_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.store = TRUE; options.flags |= pcmk_sim_process | pcmk_sim_simulate; return TRUE; } static gboolean live_check_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (options.xml_file) { free(options.xml_file); } options.xml_file = NULL; options.flags &= ~pcmk_sim_sanitized; return TRUE; } static gboolean node_down_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.injections->node_down = g_list_append(options.injections->node_down, g_strdup(optarg)); return TRUE; } static gboolean node_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.injections->node_fail = g_list_append(options.injections->node_fail, g_strdup(optarg)); return TRUE; } static gboolean node_up_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { pcmk__simulate_node_config = true; options.injections->node_up = g_list_append(options.injections->node_up, g_strdup(optarg)); return TRUE; } static gboolean op_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.flags |= pcmk_sim_process | pcmk_sim_simulate; options.injections->op_fail = g_list_append(options.injections->op_fail, g_strdup(optarg)); return TRUE; } static gboolean op_inject_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.injections->op_inject = g_list_append(options.injections->op_inject, g_strdup(optarg)); return TRUE; } static gboolean pending_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.flags |= pcmk_sim_show_pending; return TRUE; } static gboolean process_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.flags |= pcmk_sim_process; return TRUE; } static gboolean quorum_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { pcmk__str_update(&options.injections->quorum, optarg); return TRUE; } static gboolean save_dotfile_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.flags |= pcmk_sim_process; pcmk__str_update(&options.dot_file, optarg); return TRUE; } static gboolean save_graph_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.flags |= pcmk_sim_process; pcmk__str_update(&options.graph_file, optarg); return TRUE; } static gboolean show_scores_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.flags |= pcmk_sim_process | pcmk_sim_show_scores; return TRUE; } static gboolean simulate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.flags |= pcmk_sim_process | pcmk_sim_simulate; return TRUE; } static gboolean ticket_activate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.injections->ticket_activate = g_list_append(options.injections->ticket_activate, g_strdup(optarg)); return TRUE; } static gboolean ticket_grant_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.injections->ticket_grant = g_list_append(options.injections->ticket_grant, g_strdup(optarg)); return TRUE; } static gboolean ticket_revoke_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.injections->ticket_revoke = g_list_append(options.injections->ticket_revoke, g_strdup(optarg)); return TRUE; } static gboolean ticket_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.injections->ticket_standby = g_list_append(options.injections->ticket_standby, g_strdup(optarg)); return TRUE; } static gboolean utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.flags |= pcmk_sim_process | pcmk_sim_show_utilization; return TRUE; } static gboolean watchdog_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { pcmk__str_update(&options.injections->watchdog, optarg); return TRUE; } static gboolean xml_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { pcmk__str_update(&options.xml_file, optarg); options.flags |= pcmk_sim_sanitized; return TRUE; } static gboolean xml_pipe_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { pcmk__str_update(&options.xml_file, "-"); options.flags |= pcmk_sim_sanitized; return TRUE; } static GOptionEntry operation_entries[] = { { "run", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, process_cb, "Process the supplied input and show what actions the cluster will take in response", NULL }, { "simulate", 'S', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, simulate_cb, "Like --run, but also simulate taking those actions and show the resulting new status", NULL }, { "in-place", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, in_place_cb, "Like --simulate, but also store the results back to the input file", NULL }, { "show-attrs", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attrs_cb, "Show node attributes", NULL }, { "show-failcounts", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, failcounts_cb, "Show resource fail counts", NULL }, { "show-scores", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_scores_cb, "Show allocation scores", NULL }, { "show-utilization", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb, "Show utilization information", NULL }, { "profile", 'P', 0, G_OPTION_ARG_FILENAME, &options.test_dir, "Process all the XML files in the named directory to create profiling data", "DIR" }, { "repeat", 'N', 0, G_OPTION_ARG_INT, &options.repeat, "With --profile, repeat each test N times and print timings", "N" }, /* Deprecated */ { "pending", 'j', G_OPTION_FLAG_NO_ARG|G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, pending_cb, "Display pending state if 'record-pending' is enabled", NULL }, { NULL } }; static GOptionEntry synthetic_entries[] = { { "node-up", 'u', 0, G_OPTION_ARG_CALLBACK, node_up_cb, "Simulate bringing a node online", "NODE" }, { "node-down", 'd', 0, G_OPTION_ARG_CALLBACK, node_down_cb, "Simulate taking a node offline", "NODE" }, { "node-fail", 'f', 0, G_OPTION_ARG_CALLBACK, node_fail_cb, "Simulate a node failing", "NODE" }, { "op-inject", 'i', 0, G_OPTION_ARG_CALLBACK, op_inject_cb, "Generate a failure for the cluster to react to in the simulation.\n" INDENT "See `Operation Specification` help for more information.", "OPSPEC" }, { "op-fail", 'F', 0, G_OPTION_ARG_CALLBACK, op_fail_cb, "If the specified task occurs during the simulation, have it fail with return code ${rc}.\n" INDENT "The transition will normally stop at the failed action.\n" INDENT "Save the result with --save-output and re-run with --xml-file.\n" INDENT "See `Operation Specification` help for more information.", "OPSPEC" }, { "set-datetime", 't', 0, G_OPTION_ARG_STRING, &options.use_date, "Set date/time (ISO 8601 format, see https://en.wikipedia.org/wiki/ISO_8601)", "DATETIME" }, { "quorum", 'q', 0, G_OPTION_ARG_CALLBACK, quorum_cb, "Set to '1' (or 'true') to indicate cluster has quorum", "QUORUM" }, { "watchdog", 'w', 0, G_OPTION_ARG_CALLBACK, watchdog_cb, "Set to '1' (or 'true') to indicate cluster has an active watchdog device", "DEVICE" }, { "ticket-grant", 'g', 0, G_OPTION_ARG_CALLBACK, ticket_grant_cb, "Simulate granting a ticket", "TICKET" }, { "ticket-revoke", 'r', 0, G_OPTION_ARG_CALLBACK, ticket_revoke_cb, "Simulate revoking a ticket", "TICKET" }, { "ticket-standby", 'b', 0, G_OPTION_ARG_CALLBACK, ticket_standby_cb, "Simulate making a ticket standby", "TICKET" }, { "ticket-activate", 'e', 0, G_OPTION_ARG_CALLBACK, ticket_activate_cb, "Simulate activating a ticket", "TICKET" }, { NULL } }; static GOptionEntry artifact_entries[] = { { "save-input", 'I', 0, G_OPTION_ARG_FILENAME, &options.input_file, "Save the input configuration to the named file", "FILE" }, { "save-output", 'O', 0, G_OPTION_ARG_FILENAME, &options.output_file, "Save the output configuration to the named file", "FILE" }, { "save-graph", 'G', 0, G_OPTION_ARG_CALLBACK, save_graph_cb, "Save the transition graph (XML format) to the named file", "FILE" }, { "save-dotfile", 'D', 0, G_OPTION_ARG_CALLBACK, save_dotfile_cb, "Save the transition graph (DOT format) to the named file", "FILE" }, { "all-actions", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, all_actions_cb, "Display all possible actions in DOT graph (even if not part of transition)", NULL }, { NULL } }; static GOptionEntry source_entries[] = { { "live-check", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, live_check_cb, "Connect to CIB manager and use the current CIB contents as input", NULL }, { "xml-file", 'x', 0, G_OPTION_ARG_CALLBACK, xml_file_cb, "Retrieve XML from the named file", "FILE" }, { "xml-pipe", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, xml_pipe_cb, "Retrieve XML from stdin", NULL }, { NULL } }; static int setup_input(pcmk__output_t *out, const char *input, const char *output, GError **error) { int rc = pcmk_rc_ok; xmlNode *cib_object = NULL; char *local_output = NULL; if (input == NULL) { /* Use live CIB */ rc = cib__signon_query(out, NULL, &cib_object); if (rc != pcmk_rc_ok) { // cib__signon_query() outputs any relevant error return rc; } } else if (pcmk__str_eq(input, "-", pcmk__str_casei)) { cib_object = filename2xml(NULL); } else { cib_object = filename2xml(input); } if (pcmk_find_cib_element(cib_object, XML_CIB_TAG_STATUS) == NULL) { create_xml_node(cib_object, XML_CIB_TAG_STATUS); } if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) { free_xml(cib_object); return pcmk_rc_transform_failed; } if (validate_xml(cib_object, NULL, FALSE) != TRUE) { free_xml(cib_object); return pcmk_rc_schema_validation; } if (output == NULL) { char *pid = pcmk__getpid_s(); local_output = get_shadow_file(pid); temp_shadow = strdup(local_output); output = local_output; free(pid); } rc = write_xml_file(cib_object, output, FALSE); free_xml(cib_object); cib_object = NULL; if (rc < 0) { rc = pcmk_legacy2rc(rc); g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_CANTCREAT, "Could not create '%s': %s", output, pcmk_rc_str(rc)); return rc; } else { setenv("CIB_file", output, 1); free(local_output); return pcmk_rc_ok; } } static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; GOptionEntry extra_prog_entries[] = { { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet), "Display only essential output", NULL }, { NULL } }; const char *description = "Operation Specification:\n\n" "The OPSPEC in any command line option is of the form\n" "${resource}_${task}_${interval_in_ms}@${node}=${rc}\n" "(memcached_monitor_20000@bart.example.com=7, for example).\n" "${rc} is an OCF return code. For more information on these\n" "return codes, refer to https://clusterlabs.org/pacemaker/doc/2.1/Pacemaker_Administration/html/agents.html#ocf-return-codes\n\n" "Examples:\n\n" "Pretend a recurring monitor action found memcached stopped on node\n" "fred.example.com and, during recovery, that the memcached stop\n" "action failed:\n\n" "\tcrm_simulate -LS --op-inject memcached:0_monitor_20000@bart.example.com=7 " "--op-fail memcached:0_stop_0@fred.example.com=1 --save-output /tmp/memcached-test.xml\n\n" "Now see what the reaction to the stop failed would be:\n\n" "\tcrm_simulate -S --xml-file /tmp/memcached-test.xml\n\n"; context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); pcmk__add_main_args(context, extra_prog_entries); g_option_context_set_description(context, description); pcmk__add_arg_group(context, "operations", "Operations:", "Show operations options", operation_entries); pcmk__add_arg_group(context, "synthetic", "Synthetic Cluster Events:", "Show synthetic cluster event options", synthetic_entries); pcmk__add_arg_group(context, "artifact", "Artifact Options:", "Show artifact options", artifact_entries); pcmk__add_arg_group(context, "source", "Data Source:", "Show data source options", source_entries); return context; } int main(int argc, char **argv) { int rc = pcmk_rc_ok; pe_working_set_t *data_set = NULL; pcmk__output_t *out = NULL; GError *error = NULL; GOptionGroup *output_group = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "bdefgiqrtuwxDFGINOP"); GOptionContext *context = build_arg_context(args, &output_group); options.injections = calloc(1, sizeof(pcmk_injections_t)); if (options.injections == NULL) { rc = ENOMEM; goto done; } /* This must come before g_option_context_parse_strv. */ options.xml_file = strdup("-"); pcmk__register_formats(output_group, formats); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("crm_simulate", args->verbosity); rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); if (rc != pcmk_rc_ok) { fprintf(stderr, "Error creating output format %s: %s\n", args->output_ty, pcmk_rc_str(rc)); exit_code = CRM_EX_ERROR; goto done; } if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches) && !pcmk_is_set(options.flags, pcmk_sim_show_scores) && !pcmk_is_set(options.flags, pcmk_sim_show_utilization)) { pcmk__force_args(context, &error, "%s --text-fancy", g_get_prgname()); } else if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) { pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname()); } pe__register_messages(out); pcmk__register_lib_messages(out); out->quiet = args->quiet; if (args->version) { out->version(out, false); goto done; } if (args->verbosity > 0) { options.flags |= pcmk_sim_verbose; #ifdef PCMK__COMPAT_2_0 /* Redirect stderr to stdout so we can grep the output */ close(STDERR_FILENO); dup2(STDOUT_FILENO, STDERR_FILENO); #endif } data_set = pe_new_working_set(); if (data_set == NULL) { rc = ENOMEM; g_set_error(&error, PCMK__RC_ERROR, rc, "Could not allocate working set"); goto done; } if (pcmk_is_set(options.flags, pcmk_sim_show_scores)) { pe__set_working_set_flags(data_set, pe_flag_show_scores); } if (pcmk_is_set(options.flags, pcmk_sim_show_utilization)) { pe__set_working_set_flags(data_set, pe_flag_show_utilization); } pe__set_working_set_flags(data_set, pe_flag_no_compat); if (options.test_dir != NULL) { data_set->priv = out; pcmk__profile_dir(options.test_dir, options.repeat, data_set, options.use_date); rc = pcmk_rc_ok; goto done; } rc = setup_input(out, options.xml_file, options.store? options.xml_file : options.output_file, &error); if (rc != pcmk_rc_ok) { goto done; } rc = pcmk__simulate(data_set, out, options.injections, options.flags, section_opts, options.use_date, options.input_file, options.graph_file, options.dot_file); done: pcmk__output_and_clear_error(&error, NULL); /* There sure is a lot to free in options. */ free(options.dot_file); free(options.graph_file); g_free(options.input_file); g_free(options.output_file); g_free(options.test_dir); free(options.use_date); free(options.xml_file); pcmk_free_injections(options.injections); pcmk__free_arg_context(context); g_strfreev(processed_args); if (data_set) { pe_free_working_set(data_set); } fflush(stderr); if (temp_shadow) { unlink(temp_shadow); free(temp_shadow); } if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); } if (out != NULL) { out->finish(out, exit_code, true, NULL); pcmk__output_free(out); } pcmk__unregister_formats(); crm_exit(exit_code); } diff --git a/tools/crm_ticket.c b/tools/crm_ticket.c index 4cf0045c32..05b026dc0e 100644 --- a/tools/crm_ticket.c +++ b/tools/crm_ticket.c @@ -1,999 +1,1000 @@ /* * Copyright 2012-2023 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include GError *error = NULL; #define SUMMARY "Perform tasks related to cluster tickets\n\n" \ "Allows ticket attributes to be queried, modified and deleted." struct { gchar *attr_default; gchar *attr_id; char *attr_name; char *attr_value; gboolean force; char *get_attr_name; gboolean quiet; gchar *set_name; char ticket_cmd; gchar *ticket_id; gchar *xml_file; } options = { .ticket_cmd = 'S' }; GList *attr_delete; GHashTable *attr_set; bool modified = false; int cib_options = cib_sync_call; #define INDENT " " static gboolean attr_value_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { pcmk__str_update(&options.attr_value, optarg); if (!options.attr_name || !options.attr_value) { return TRUE; } g_hash_table_insert(attr_set, strdup(options.attr_name), strdup(options.attr_value)); pcmk__str_update(&options.attr_name, NULL); pcmk__str_update(&options.attr_value, NULL); modified = true; return TRUE; } static gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { if (pcmk__str_any_of(option_name, "--info", "-l", NULL)) { options.ticket_cmd = 'l'; } else if (pcmk__str_any_of(option_name, "--details", "-L", NULL)) { options.ticket_cmd = 'L'; } else if (pcmk__str_any_of(option_name, "--raw", "-w", NULL)) { options.ticket_cmd = 'w'; } else if (pcmk__str_any_of(option_name, "--query-xml", "-q", NULL)) { options.ticket_cmd = 'q'; } else if (pcmk__str_any_of(option_name, "--constraints", "-c", NULL)) { options.ticket_cmd = 'c'; } else if (pcmk__str_any_of(option_name, "--cleanup", "-C", NULL)) { options.ticket_cmd = 'C'; } return TRUE; } static gboolean delete_attr_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { attr_delete = g_list_append(attr_delete, strdup(optarg)); modified = true; return TRUE; } static gboolean get_attr_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { pcmk__str_update(&options.get_attr_name, optarg); options.ticket_cmd = 'G'; return TRUE; } static gboolean grant_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { if (pcmk__str_any_of(option_name, "--grant", "-g", NULL)) { g_hash_table_insert(attr_set, strdup("granted"), strdup("true")); modified = true; } else if (pcmk__str_any_of(option_name, "--revoke", "-r", NULL)) { g_hash_table_insert(attr_set, strdup("granted"), strdup("false")); modified = true; } else if (pcmk__str_any_of(option_name, "--standby", "-s", NULL)) { g_hash_table_insert(attr_set, strdup("standby"), strdup("true")); modified = true; } else if (pcmk__str_any_of(option_name, "--activate", "-a", NULL)) { g_hash_table_insert(attr_set, strdup("standby"), strdup("false")); modified = true; } return TRUE; } static gboolean set_attr_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { pcmk__str_update(&options.attr_name, optarg); if (!options.attr_name || !options.attr_value) { return TRUE; } g_hash_table_insert(attr_set, strdup(options.attr_name), strdup(options.attr_value)); pcmk__str_update(&options.attr_name, NULL); pcmk__str_update(&options.attr_value, NULL); modified = true; return TRUE; } static GOptionEntry query_entries[] = { { "info", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the information of ticket(s)", NULL }, { "details", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the details of ticket(s)", NULL }, { "raw", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the IDs of ticket(s)", NULL }, { "query-xml", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Query the XML of ticket(s)", NULL }, { "constraints", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the rsc_ticket constraints that apply to ticket(s)", NULL }, { NULL } }; static GOptionEntry command_entries[] = { { "grant", 'g', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb, "Grant a ticket to this cluster site", NULL }, { "revoke", 'r', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb, "Revoke a ticket from this cluster site", NULL }, { "standby", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb, "Tell this cluster site this ticket is standby", NULL }, { "activate", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb, "Tell this cluster site this ticket is active", NULL }, { NULL } }; static GOptionEntry advanced_entries[] = { { "get-attr", 'G', 0, G_OPTION_ARG_CALLBACK, get_attr_cb, "Display the named attribute for a ticket", "ATTRIBUTE" }, { "set-attr", 'S', 0, G_OPTION_ARG_CALLBACK, set_attr_cb, "Set the named attribute for a ticket", "ATTRIBUTE" }, { "delete-attr", 'D', 0, G_OPTION_ARG_CALLBACK, delete_attr_cb, "Delete the named attribute for a ticket", "ATTRIBUTE" }, { "cleanup", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Delete all state of a ticket at this cluster site", NULL }, { NULL} }; static GOptionEntry addl_entries[] = { { "attr-value", 'v', 0, G_OPTION_ARG_CALLBACK, attr_value_cb, "Attribute value to use with -S", "VALUE" }, { "default", 'd', 0, G_OPTION_ARG_STRING, &options.attr_default, "(Advanced) Default attribute value to display if none is found\n" INDENT "(for use with -G)", "VALUE" }, { "force", 'f', 0, G_OPTION_ARG_NONE, &options.force, "(Advanced) Force the action to be performed", NULL }, { "ticket", 't', 0, G_OPTION_ARG_STRING, &options.ticket_id, "Ticket ID", "ID" }, { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.xml_file, NULL, NULL }, { NULL } }; static GOptionEntry deprecated_entries[] = { { "set-name", 'n', 0, G_OPTION_ARG_STRING, &options.set_name, "(Advanced) ID of the instance_attributes object to change", "ID" }, { "nvpair", 'i', 0, G_OPTION_ARG_STRING, &options.attr_id, "(Advanced) ID of the nvpair object to change/delete", "ID" }, { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &options.quiet, "Print only the value on stdout", NULL }, { NULL } }; static pe_ticket_t * find_ticket(gchar *ticket_id, pe_working_set_t * data_set) { return g_hash_table_lookup(data_set->tickets, ticket_id); } static void print_date(time_t time) { int lpc = 0; char date_str[26]; asctime_r(localtime(&time), date_str); for (; lpc < 26; lpc++) { if (date_str[lpc] == '\n') { date_str[lpc] = 0; } } fprintf(stdout, "'%s'", date_str); } static void print_ticket(pe_ticket_t * ticket, bool raw, bool details) { if (raw) { fprintf(stdout, "%s\n", ticket->id); return; } fprintf(stdout, "%s\t%s %s", ticket->id, ticket->granted ? "granted" : "revoked", ticket->standby ? "[standby]" : " "); if (details && g_hash_table_size(ticket->state) > 0) { GHashTableIter iter; const char *name = NULL; const char *value = NULL; int lpc = 0; fprintf(stdout, " ("); g_hash_table_iter_init(&iter, ticket->state); while (g_hash_table_iter_next(&iter, (void **)&name, (void **)&value)) { if (lpc > 0) { fprintf(stdout, ", "); } fprintf(stdout, "%s=", name); if (pcmk__str_any_of(name, "last-granted", "expires", NULL)) { long long time_ll; pcmk__scan_ll(value, &time_ll, 0); print_date((time_t) time_ll); } else { fprintf(stdout, "%s", value); } lpc++; } fprintf(stdout, ")\n"); } else { if (ticket->last_granted > -1) { fprintf(stdout, " last-granted="); print_date(ticket->last_granted); } fprintf(stdout, "\n"); } return; } static void print_ticket_list(pe_working_set_t * data_set, bool raw, bool details) { GHashTableIter iter; pe_ticket_t *ticket = NULL; g_hash_table_iter_init(&iter, data_set->tickets); while (g_hash_table_iter_next(&iter, NULL, (void **)&ticket)) { print_ticket(ticket, raw, details); } } static int find_ticket_state(cib_t * the_cib, gchar *ticket_id, xmlNode ** ticket_state_xml) { int rc = pcmk_rc_ok; xmlNode *xml_search = NULL; GString *xpath = NULL; CRM_ASSERT(ticket_state_xml != NULL); *ticket_state_xml = NULL; xpath = g_string_sized_new(1024); g_string_append(xpath, "/" XML_TAG_CIB "/" XML_CIB_TAG_STATUS "/" XML_CIB_TAG_TICKETS); if (ticket_id != NULL) { pcmk__g_strcat(xpath, "/" XML_CIB_TAG_TICKET_STATE "[@" XML_ATTR_ID "=\"", ticket_id, "\"]", NULL); } rc = the_cib->cmds->query(the_cib, (const char *) xpath->str, &xml_search, cib_sync_call | cib_scope_local | cib_xpath); rc = pcmk_legacy2rc(rc); g_string_free(xpath, TRUE); if (rc != pcmk_rc_ok) { return rc; } crm_log_xml_debug(xml_search, "Match"); if (xml_has_children(xml_search)) { if (ticket_id) { fprintf(stdout, "Multiple ticket_states match ticket_id=%s\n", ticket_id); } *ticket_state_xml = xml_search; } else { *ticket_state_xml = xml_search; } return rc; } static int find_ticket_constraints(cib_t * the_cib, gchar *ticket_id, xmlNode ** ticket_cons_xml) { int rc = pcmk_rc_ok; xmlNode *xml_search = NULL; GString *xpath = NULL; const char *xpath_base = NULL; CRM_ASSERT(ticket_cons_xml != NULL); *ticket_cons_xml = NULL; xpath_base = pcmk_cib_xpath_for(XML_CIB_TAG_CONSTRAINTS); if (xpath_base == NULL) { crm_err(XML_CIB_TAG_CONSTRAINTS " CIB element not known (bug?)"); return -ENOMSG; } xpath = g_string_sized_new(1024); pcmk__g_strcat(xpath, xpath_base, "/" XML_CONS_TAG_RSC_TICKET, NULL); if (ticket_id != NULL) { pcmk__g_strcat(xpath, "[@" XML_TICKET_ATTR_TICKET "=\"", ticket_id, "\"]", NULL); } rc = the_cib->cmds->query(the_cib, (const char *) xpath->str, &xml_search, cib_sync_call | cib_scope_local | cib_xpath); rc = pcmk_legacy2rc(rc); g_string_free(xpath, TRUE); if (rc != pcmk_rc_ok) { return rc; } crm_log_xml_debug(xml_search, "Match"); *ticket_cons_xml = xml_search; return rc; } static int dump_ticket_xml(cib_t * the_cib, gchar *ticket_id) { int rc = pcmk_rc_ok; xmlNode *state_xml = NULL; rc = find_ticket_state(the_cib, ticket_id, &state_xml); if (state_xml == NULL) { return rc; } fprintf(stdout, "State XML:\n"); if (state_xml) { fprintf(stdout, "\n"); pcmk__xml2fd(STDOUT_FILENO, state_xml); } return rc; } static int dump_constraints(cib_t * the_cib, gchar *ticket_id) { int rc = pcmk_rc_ok; xmlNode *cons_xml = NULL; rc = find_ticket_constraints(the_cib, ticket_id, &cons_xml); if (cons_xml == NULL) { return rc; } fprintf(stdout, "Constraints XML:\n\n"); pcmk__xml2fd(STDOUT_FILENO, cons_xml); return rc; } static int get_ticket_state_attr(gchar *ticket_id, const char *attr_name, const char **attr_value, pe_working_set_t * data_set) { pe_ticket_t *ticket = NULL; CRM_ASSERT(attr_value != NULL); *attr_value = NULL; ticket = g_hash_table_lookup(data_set->tickets, ticket_id); if (ticket == NULL) { return ENXIO; } *attr_value = g_hash_table_lookup(ticket->state, attr_name); if (*attr_value == NULL) { return ENXIO; } return pcmk_rc_ok; } static void ticket_warning(gchar *ticket_id, const char *action) { GString *warning = g_string_sized_new(1024); const char *word = NULL; CRM_ASSERT(action != NULL); if (strcmp(action, "grant") == 0) { pcmk__g_strcat(warning, "This command cannot help you verify whether '", ticket_id, "' has been already granted elsewhere.\n", NULL); word = "to"; } else { pcmk__g_strcat(warning, "Revoking '", ticket_id, "' can trigger the specified " "'loss-policy'(s) relating to '", ticket_id, "'.\n\n" "You can check that with:\n" "crm_ticket --ticket ", ticket_id, " --constraints\n\n" "Otherwise before revoking '", ticket_id, "', " "you may want to make '", ticket_id, "' " "standby with:\n" "crm_ticket --ticket ", ticket_id, " --standby\n\n", NULL); word = "from"; } pcmk__g_strcat(warning, "If you really want to ", action, " '", ticket_id, "' ", word, " this site now, and you know what you are doing,\n" "please specify --force.", NULL); fprintf(stdout, "%s\n", (const char *) warning->str); g_string_free(warning, TRUE); } static bool allow_modification(gchar *ticket_id) { const char *value = NULL; GList *list_iter = NULL; if (options.force) { return true; } if (g_hash_table_lookup_extended(attr_set, "granted", NULL, (gpointer *) & value)) { if (crm_is_true(value)) { ticket_warning(ticket_id, "grant"); return false; } else { ticket_warning(ticket_id, "revoke"); return false; } } for(list_iter = attr_delete; list_iter; list_iter = list_iter->next) { const char *key = (const char *)list_iter->data; if (pcmk__str_eq(key, "granted", pcmk__str_casei)) { ticket_warning(ticket_id, "revoke"); return false; } } return true; } static int modify_ticket_state(gchar * ticket_id, cib_t * cib, pe_working_set_t * data_set) { int rc = pcmk_rc_ok; xmlNode *xml_top = NULL; xmlNode *ticket_state_xml = NULL; bool found = false; GList *list_iter = NULL; GHashTableIter hash_iter; char *key = NULL; char *value = NULL; pe_ticket_t *ticket = NULL; rc = find_ticket_state(cib, ticket_id, &ticket_state_xml); if (rc == pcmk_rc_ok) { crm_debug("Found a match state for ticket: id=%s", ticket_id); xml_top = ticket_state_xml; found = true; } else if (rc != ENXIO) { return rc; } else if (g_hash_table_size(attr_set) == 0){ return pcmk_rc_ok; } else { xmlNode *xml_obj = NULL; xml_top = create_xml_node(NULL, XML_CIB_TAG_STATUS); xml_obj = create_xml_node(xml_top, XML_CIB_TAG_TICKETS); ticket_state_xml = create_xml_node(xml_obj, XML_CIB_TAG_TICKET_STATE); crm_xml_add(ticket_state_xml, XML_ATTR_ID, ticket_id); } for(list_iter = attr_delete; list_iter; list_iter = list_iter->next) { const char *key = (const char *)list_iter->data; xml_remove_prop(ticket_state_xml, key); } ticket = find_ticket(ticket_id, data_set); g_hash_table_iter_init(&hash_iter, attr_set); while (g_hash_table_iter_next(&hash_iter, (gpointer *) & key, (gpointer *) & value)) { crm_xml_add(ticket_state_xml, key, value); if (pcmk__str_eq(key, "granted", pcmk__str_casei) && (ticket == NULL || ticket->granted == FALSE) && crm_is_true(value)) { char *now = pcmk__ttoa(time(NULL)); crm_xml_add(ticket_state_xml, "last-granted", now); free(now); } } if (found && (attr_delete != NULL)) { crm_log_xml_debug(xml_top, "Replace"); rc = cib->cmds->replace(cib, XML_CIB_TAG_STATUS, ticket_state_xml, cib_options); rc = pcmk_legacy2rc(rc); } else { crm_log_xml_debug(xml_top, "Update"); rc = cib->cmds->modify(cib, XML_CIB_TAG_STATUS, xml_top, cib_options); rc = pcmk_legacy2rc(rc); } free_xml(xml_top); return rc; } static int delete_ticket_state(gchar *ticket_id, cib_t * cib) { xmlNode *ticket_state_xml = NULL; int rc = pcmk_rc_ok; rc = find_ticket_state(cib, ticket_id, &ticket_state_xml); if (rc == ENXIO) { return pcmk_rc_ok; } else if (rc != pcmk_rc_ok) { return rc; } crm_log_xml_debug(ticket_state_xml, "Delete"); rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, ticket_state_xml, cib_options); rc = pcmk_legacy2rc(rc); if (rc == pcmk_rc_ok) { fprintf(stdout, "Cleaned up %s\n", ticket_id); } free_xml(ticket_state_xml); return rc; } static GOptionContext * build_arg_context(pcmk__common_args_t *args) { GOptionContext *context = NULL; const char *description = "Examples:\n\n" "Display the info of tickets:\n\n" "\tcrm_ticket --info\n\n" "Display the detailed info of tickets:\n\n" "\tcrm_ticket --details\n\n" "Display the XML of 'ticketA':\n\n" "\tcrm_ticket --ticket ticketA --query-xml\n\n" "Display the rsc_ticket constraints that apply to 'ticketA':\n\n" "\tcrm_ticket --ticket ticketA --constraints\n\n" "Grant 'ticketA' to this cluster site:\n\n" "\tcrm_ticket --ticket ticketA --grant\n\n" "Revoke 'ticketA' from this cluster site:\n\n" "\tcrm_ticket --ticket ticketA --revoke\n\n" "Make 'ticketA' standby (the cluster site will treat a granted\n" "'ticketA' as 'standby', and the dependent resources will be\n" "stopped or demoted gracefully without triggering loss-policies):\n\n" "\tcrm_ticket --ticket ticketA --standby\n\n" "Activate 'ticketA' from being standby:\n\n" "\tcrm_ticket --ticket ticketA --activate\n\n" "Get the value of the 'granted' attribute for 'ticketA':\n\n" "\tcrm_ticket --ticket ticketA --get-attr granted\n\n" "Set the value of the 'standby' attribute for 'ticketA':\n\n" "\tcrm_ticket --ticket ticketA --set-attr standby --attr-value true\n\n" "Delete the 'granted' attribute for 'ticketA':\n\n" "\tcrm_ticket --ticket ticketA --delete-attr granted\n\n" "Erase the operation history of 'ticketA' at this cluster site,\n" "causing the cluster site to 'forget' the existing ticket state:\n\n" "\tcrm_ticket --ticket ticketA --cleanup\n\n"; context = pcmk__build_arg_context(args, NULL, NULL, NULL); g_option_context_set_description(context, description); pcmk__add_arg_group(context, "queries", "Queries:", "Show queries", query_entries); pcmk__add_arg_group(context, "commands", "Commands:", "Show command options", command_entries); pcmk__add_arg_group(context, "advanced", "Advanced Options:", "Show advanced options", advanced_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); pcmk__add_arg_group(context, "deprecated", "Deprecated Options:", "Show deprecated options", deprecated_entries); return context; } int main(int argc, char **argv) { pe_working_set_t *data_set = NULL; xmlNode *cib_xml_copy = NULL; cib_t *cib_conn = NULL; crm_exit_t exit_code = CRM_EX_OK; int rc = pcmk_rc_ok; pcmk__common_args_t *args = NULL; GOptionContext *context = NULL; gchar **processed_args = NULL; attr_set = pcmk__strkey_table(free, free); attr_delete = NULL; args = pcmk__new_common_args(SUMMARY); context = build_arg_context(args); processed_args = pcmk__cmdline_preproc(argv, "dintvxCDGS"); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("crm_ticket", args->verbosity); if (args->version) { g_strfreev(processed_args); pcmk__free_arg_context(context); /* FIXME: When crm_ticket is converted to use formatted output, this can go. */ pcmk__cli_help('v'); } data_set = pe_new_working_set(); if (data_set == NULL) { crm_perror(LOG_CRIT, "Could not allocate working set"); exit_code = CRM_EX_OSERR; goto done; } pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); cib_conn = cib_new(); if (cib_conn == NULL) { exit_code = CRM_EX_DISCONNECT; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to the CIB manager"); goto done; } rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to the CIB: %s", pcmk_rc_str(rc)); goto done; } if (options.xml_file != NULL) { cib_xml_copy = filename2xml(options.xml_file); } else { rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not get local CIB: %s", pcmk_rc_str(rc)); goto done; } } if (!cli_config_update(&cib_xml_copy, NULL, FALSE)) { exit_code = CRM_EX_CONFIG; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not update local CIB to latest schema version"); goto done; } data_set->input = cib_xml_copy; data_set->now = crm_time_new(NULL); cluster_status(data_set); /* For recording the tickets that are referenced in rsc_ticket constraints * but have never been granted yet. */ pcmk__unpack_constraints(data_set); if (options.ticket_cmd == 'l' || options.ticket_cmd == 'L' || options.ticket_cmd == 'w') { bool raw = false; bool details = false; if (options.ticket_cmd == 'L') { details = true; } else if (options.ticket_cmd == 'w') { raw = true; } if (options.ticket_id) { pe_ticket_t *ticket = find_ticket(options.ticket_id, data_set); if (ticket == NULL) { exit_code = CRM_EX_NOSUCH; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "No such ticket '%s'", options.ticket_id); goto done; } print_ticket(ticket, raw, details); } else { print_ticket_list(data_set, raw, details); } } else if (options.ticket_cmd == 'q') { rc = dump_ticket_xml(cib_conn, options.ticket_id); exit_code = pcmk_rc2exitc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not query ticket XML: %s", pcmk_rc_str(rc)); } } else if (options.ticket_cmd == 'c') { rc = dump_constraints(cib_conn, options.ticket_id); exit_code = pcmk_rc2exitc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not show ticket constraints: %s", pcmk_rc_str(rc)); } } else if (options.ticket_cmd == 'G') { const char *value = NULL; if (options.ticket_id == NULL) { exit_code = CRM_EX_NOSUCH; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply ticket ID with -t"); goto done; } rc = get_ticket_state_attr(options.ticket_id, options.get_attr_name, &value, data_set); if (rc == pcmk_rc_ok) { fprintf(stdout, "%s\n", value); } else if (rc == ENXIO && options.attr_default) { fprintf(stdout, "%s\n", options.attr_default); rc = pcmk_rc_ok; } exit_code = pcmk_rc2exitc(rc); } else if (options.ticket_cmd == 'C') { if (options.ticket_id == NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply ticket ID with -t"); goto done; } if (options.force == FALSE) { pe_ticket_t *ticket = NULL; ticket = find_ticket(options.ticket_id, data_set); if (ticket == NULL) { exit_code = CRM_EX_NOSUCH; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "No such ticket '%s'", options.ticket_id); goto done; } if (ticket->granted) { ticket_warning(options.ticket_id, "revoke"); exit_code = CRM_EX_INSUFFICIENT_PRIV; goto done; } } rc = delete_ticket_state(options.ticket_id, cib_conn); exit_code = pcmk_rc2exitc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not clean up ticket: %s", pcmk_rc_str(rc)); } } else if (modified) { if (options.ticket_id == NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply ticket ID with -t"); goto done; } if (options.attr_value && (pcmk__str_empty(options.attr_name))) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply attribute name with -S for -v %s", options.attr_value); goto done; } if (options.attr_name && (pcmk__str_empty(options.attr_value))) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply attribute value with -v for -S %s", options.attr_value); goto done; } if (!allow_modification(options.ticket_id)) { exit_code = CRM_EX_INSUFFICIENT_PRIV; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Ticket modification not allowed"); goto done; } rc = modify_ticket_state(options.ticket_id, cib_conn, data_set); exit_code = pcmk_rc2exitc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not modify ticket: %s", pcmk_rc_str(rc)); } } else if (options.ticket_cmd == 'S') { /* Correct usage was handled in the "if (modified)" block above, so * this is just for reporting usage errors */ if (pcmk__str_empty(options.attr_name)) { // We only get here if ticket_cmd was left as default exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply a command"); goto done; } if (options.ticket_id == NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply ticket ID with -t"); goto done; } if (pcmk__str_empty(options.attr_value)) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply value with -v for -S %s", options.attr_name); goto done; } } else { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Unknown command: %c", options.ticket_cmd); } done: if (attr_set) { g_hash_table_destroy(attr_set); } attr_set = NULL; if (attr_delete) { g_list_free_full(attr_delete, free); } attr_delete = NULL; pe_free_working_set(data_set); data_set = NULL; cib__clean_up_connection(&cib_conn); g_strfreev(processed_args); pcmk__free_arg_context(context); g_free(options.attr_default); g_free(options.attr_id); free(options.attr_name); free(options.attr_value); free(options.get_attr_name); g_free(options.set_name); g_free(options.ticket_id); g_free(options.xml_file); pcmk__output_and_clear_error(&error, NULL); crm_exit(exit_code); }