diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c index 1e3b96552e..dbd2aacfcb 100644 --- a/daemons/attrd/attrd_sync.c +++ b/daemons/attrd/attrd_sync.c @@ -1,580 +1,580 @@ /* * Copyright 2022-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include "pacemaker-attrd.h" /* A hash table storing clients that are waiting on a sync point to be reached. * The key is waitlist_client - just a plain int. The obvious key would be * the IPC client's ID, but this is not guaranteed to be unique. A single client * could be waiting on a sync point for multiple attributes at the same time. * * It is not expected that this hash table will ever be especially large. */ static GHashTable *waitlist = NULL; static int waitlist_client = 0; struct waitlist_node { /* What kind of sync point does this node describe? */ enum attrd_sync_point sync_point; /* Information required to construct and send a reply to the client. */ char *client_id; uint32_t ipc_id; uint32_t flags; }; /* A hash table storing information on in-progress IPC requests that are awaiting * confirmations. These requests are currently being processed by peer attrds and * we are waiting to receive confirmation messages from each peer indicating that * processing is complete. * * Multiple requests could be waiting on confirmations at the same time. * * The key is the unique callid for the IPC request, and the value is a * confirmation_action struct. */ static GHashTable *expected_confirmations = NULL; /*! * \internal * \brief A structure describing a single IPC request that is awaiting confirmations */ struct confirmation_action { /*! * \brief A list of peer attrds that we are waiting to receive confirmation * messages from * * This list is dynamic - as confirmations arrive from peer attrds, they will * be removed from this list. When the list is empty, all peers have processed * the request and the associated confirmation action will be taken. */ GList *respondents; /*! * \brief A timer that will be used to remove the client should it time out * before receiving all confirmations */ mainloop_timer_t *timer; /*! * \brief A function to run when all confirmations have been received */ attrd_confirmation_action_fn fn; /*! * \brief Information required to construct and send a reply to the client */ char *client_id; uint32_t ipc_id; uint32_t flags; /*! * \brief The XML request containing the callid associated with this action */ void *xml; }; static void next_key(void) { do { waitlist_client++; if (waitlist_client < 0) { waitlist_client = 1; } } while (g_hash_table_contains(waitlist, GINT_TO_POINTER(waitlist_client))); } static void free_waitlist_node(gpointer data) { struct waitlist_node *wl = (struct waitlist_node *) data; free(wl->client_id); free(wl); } static const char * sync_point_str(enum attrd_sync_point sync_point) { if (sync_point == attrd_sync_point_local) { return PCMK__VALUE_LOCAL; } else if (sync_point == attrd_sync_point_cluster) { return PCMK__VALUE_CLUSTER; } else { - return "unknown"; + return PCMK_VALUE_UNKNOWN; } } /*! * \internal * \brief Add a client to the attrd waitlist * * Typically, a client receives an ACK for its XML IPC request immediately. However, * some clients want to wait until their request has been processed and taken effect. * This is called a sync point. Any client placed on this waitlist will have its * ACK message delayed until either its requested sync point is hit, or until it * times out. * * The XML IPC request must specify the type of sync point it wants to wait for. * * \param[in,out] request The request describing the client to place on the waitlist. */ void attrd_add_client_to_waitlist(pcmk__request_t *request) { const char *sync_point = attrd_request_sync_point(request->xml); struct waitlist_node *wl = NULL; if (sync_point == NULL) { return; } if (waitlist == NULL) { waitlist = pcmk__intkey_table(free_waitlist_node); } wl = calloc(sizeof(struct waitlist_node), 1); CRM_ASSERT(wl != NULL); wl->client_id = strdup(request->ipc_client->id); CRM_ASSERT(wl->client_id); if (pcmk__str_eq(sync_point, PCMK__VALUE_LOCAL, pcmk__str_none)) { wl->sync_point = attrd_sync_point_local; } else if (pcmk__str_eq(sync_point, PCMK__VALUE_CLUSTER, pcmk__str_none)) { wl->sync_point = attrd_sync_point_cluster; } else { free_waitlist_node(wl); return; } wl->ipc_id = request->ipc_id; wl->flags = request->flags; next_key(); pcmk__intkey_table_insert(waitlist, waitlist_client, wl); crm_trace("Added client %s to waitlist for %s sync point", wl->client_id, sync_point_str(wl->sync_point)); crm_trace("%d clients now on waitlist", g_hash_table_size(waitlist)); /* And then add the key to the request XML so we can uniquely identify * it when it comes time to issue the ACK. */ crm_xml_add_int(request->xml, PCMK__XA_CALL_ID, waitlist_client); } /*! * \internal * \brief Free all memory associated with the waitlist. This is most typically * used when attrd shuts down. */ void attrd_free_waitlist(void) { if (waitlist == NULL) { return; } g_hash_table_destroy(waitlist); waitlist = NULL; } /*! * \internal * \brief Unconditionally remove a client from the waitlist, such as when the client * node disconnects from the cluster * * \param[in] client The client to remove */ void attrd_remove_client_from_waitlist(pcmk__client_t *client) { GHashTableIter iter; gpointer value; if (waitlist == NULL) { return; } g_hash_table_iter_init(&iter, waitlist); while (g_hash_table_iter_next(&iter, NULL, &value)) { struct waitlist_node *wl = (struct waitlist_node *) value; if (pcmk__str_eq(wl->client_id, client->id, pcmk__str_none)) { g_hash_table_iter_remove(&iter); crm_trace("%d clients now on waitlist", g_hash_table_size(waitlist)); } } } /*! * \internal * \brief Send an IPC ACK message to all awaiting clients * * This function will search the waitlist for all clients that are currently awaiting * an ACK indicating their attrd operation is complete. Only those clients with a * matching sync point type and callid from their original XML IPC request will be * ACKed. Once they have received an ACK, they will be removed from the waitlist. * * \param[in] sync_point What kind of sync point have we hit? * \param[in] xml The original XML IPC request. */ void attrd_ack_waitlist_clients(enum attrd_sync_point sync_point, const xmlNode *xml) { int callid; gpointer value; if (waitlist == NULL) { return; } if (crm_element_value_int(xml, PCMK__XA_CALL_ID, &callid) == -1) { crm_warn("Could not get callid from request XML"); return; } value = pcmk__intkey_table_lookup(waitlist, callid); if (value != NULL) { struct waitlist_node *wl = (struct waitlist_node *) value; pcmk__client_t *client = NULL; if (wl->sync_point != sync_point) { return; } crm_notice("Alerting client %s for reached %s sync point", wl->client_id, sync_point_str(wl->sync_point)); client = pcmk__find_client_by_id(wl->client_id); if (client == NULL) { return; } attrd_send_ack(client, wl->ipc_id, wl->flags | crm_ipc_client_response); /* And then remove the client so it doesn't get alerted again. */ pcmk__intkey_table_remove(waitlist, callid); crm_trace("%d clients now on waitlist", g_hash_table_size(waitlist)); } } /*! * \internal * \brief Action to take when a cluster sync point is hit for a * PCMK__ATTRD_CMD_UPDATE* message. * * \param[in] xml The request that should be passed along to * attrd_ack_waitlist_clients. This should be the original * IPC request containing the callid for this update message. */ int attrd_cluster_sync_point_update(xmlNode *xml) { crm_trace("Hit cluster sync point for attribute update"); attrd_ack_waitlist_clients(attrd_sync_point_cluster, xml); return pcmk_rc_ok; } /*! * \internal * \brief Return the sync point attribute for an IPC request * * This function will check both the top-level element of \p xml for a sync * point attribute, as well as all of its \p op children, if any. The latter * is useful for newer versions of attrd that can put multiple IPC requests * into a single message. * * \param[in] xml An XML IPC request * * \note It is assumed that if one child element has a sync point attribute, * all will have a sync point attribute and they will all be the same * sync point. No other configuration is supported. * * \return The sync point attribute of \p xml, or NULL if none. */ const char * attrd_request_sync_point(xmlNode *xml) { CRM_CHECK(xml != NULL, return NULL); if (xml->children != NULL) { xmlNode *child = pcmk__xe_match(xml, PCMK_XE_OP, PCMK__XA_ATTR_SYNC_POINT, NULL); if (child) { return crm_element_value(child, PCMK__XA_ATTR_SYNC_POINT); } else { return NULL; } } else { return crm_element_value(xml, PCMK__XA_ATTR_SYNC_POINT); } } /*! * \internal * \brief Does an IPC request contain any sync point attribute? * * \param[in] xml An XML IPC request * * \return true if there's a sync point attribute, false otherwise */ bool attrd_request_has_sync_point(xmlNode *xml) { return attrd_request_sync_point(xml) != NULL; } static void free_action(gpointer data) { struct confirmation_action *action = (struct confirmation_action *) data; g_list_free_full(action->respondents, free); mainloop_timer_del(action->timer); free_xml(action->xml); free(action->client_id); free(action); } /* Remove an IPC request from the expected_confirmations table if the peer attrds * don't respond before the timeout is hit. We set the timeout to 15s. The exact * number isn't critical - we just want to make sure that the table eventually gets * cleared of things that didn't complete. */ static gboolean confirmation_timeout_cb(gpointer data) { struct confirmation_action *action = (struct confirmation_action *) data; GHashTableIter iter; gpointer value; if (expected_confirmations == NULL) { return G_SOURCE_REMOVE; } g_hash_table_iter_init(&iter, expected_confirmations); while (g_hash_table_iter_next(&iter, NULL, &value)) { if (value == action) { pcmk__client_t *client = pcmk__find_client_by_id(action->client_id); if (client == NULL) { return G_SOURCE_REMOVE; } crm_trace("Timed out waiting for confirmations for client %s", client->id); pcmk__ipc_send_ack(client, action->ipc_id, action->flags | crm_ipc_client_response, "ack", ATTRD_PROTOCOL_VERSION, CRM_EX_TIMEOUT); g_hash_table_iter_remove(&iter); crm_trace("%d requests now in expected confirmations table", g_hash_table_size(expected_confirmations)); break; } } return G_SOURCE_REMOVE; } /*! * \internal * \brief When a peer disconnects from the cluster, no longer wait for its confirmation * for any IPC action. If this peer is the last one being waited on, this will * trigger the confirmation action. * * \param[in] host The disconnecting peer attrd's uname */ void attrd_do_not_expect_from_peer(const char *host) { GList *keys = NULL; if (expected_confirmations == NULL) { return; } keys = g_hash_table_get_keys(expected_confirmations); crm_trace("Removing peer %s from expected confirmations", host); for (GList *node = keys; node != NULL; node = node->next) { int callid = *(int *) node->data; attrd_handle_confirmation(callid, host); } g_list_free(keys); } /*! * \internal * \brief When a client disconnects from the cluster, no longer wait on confirmations * for it. Because the peer attrds may still be processing the original IPC * message, they may still send us confirmations. However, we will take no * action on them. * * \param[in] client The disconnecting client */ void attrd_do_not_wait_for_client(pcmk__client_t *client) { GHashTableIter iter; gpointer value; if (expected_confirmations == NULL) { return; } g_hash_table_iter_init(&iter, expected_confirmations); while (g_hash_table_iter_next(&iter, NULL, &value)) { struct confirmation_action *action = (struct confirmation_action *) value; if (pcmk__str_eq(action->client_id, client->id, pcmk__str_none)) { crm_trace("Removing client %s from expected confirmations", client->id); g_hash_table_iter_remove(&iter); crm_trace("%d requests now in expected confirmations table", g_hash_table_size(expected_confirmations)); break; } } } /*! * \internal * \brief Register some action to be taken when IPC request confirmations are * received * * When this function is called, a list of all peer attrds that support confirming * requests is generated. As confirmations from these peer attrds are received, * they are removed from this list. When the list is empty, the registered action * will be called. * * \note This function should always be called before attrd_send_message is called * to broadcast to the peers to ensure that we know what replies we are * waiting on. Otherwise, it is possible the peer could finish and confirm * before we know to expect it. * * \param[in] request The request that is awaiting confirmations * \param[in] fn A function to be run after all confirmations are received */ void attrd_expect_confirmations(pcmk__request_t *request, attrd_confirmation_action_fn fn) { struct confirmation_action *action = NULL; GHashTableIter iter; gpointer host, ver; GList *respondents = NULL; int callid; if (expected_confirmations == NULL) { expected_confirmations = pcmk__intkey_table((GDestroyNotify) free_action); } if (crm_element_value_int(request->xml, PCMK__XA_CALL_ID, &callid) == -1) { crm_err("Could not get callid from xml"); return; } if (pcmk__intkey_table_lookup(expected_confirmations, callid)) { crm_err("Already waiting on confirmations for call id %d", callid); return; } g_hash_table_iter_init(&iter, peer_protocol_vers); while (g_hash_table_iter_next(&iter, &host, &ver)) { if (ATTRD_SUPPORTS_CONFIRMATION(GPOINTER_TO_INT(ver))) { char *s = strdup((char *) host); CRM_ASSERT(s != NULL); respondents = g_list_prepend(respondents, s); } } action = calloc(1, sizeof(struct confirmation_action)); CRM_ASSERT(action != NULL); action->respondents = respondents; action->fn = fn; action->xml = copy_xml(request->xml); action->client_id = strdup(request->ipc_client->id); CRM_ASSERT(action->client_id != NULL); action->ipc_id = request->ipc_id; action->flags = request->flags; action->timer = mainloop_timer_add(NULL, 15000, FALSE, confirmation_timeout_cb, action); mainloop_timer_start(action->timer); pcmk__intkey_table_insert(expected_confirmations, callid, action); crm_trace("Callid %d now waiting on %d confirmations", callid, g_list_length(respondents)); crm_trace("%d requests now in expected confirmations table", g_hash_table_size(expected_confirmations)); } void attrd_free_confirmations(void) { if (expected_confirmations != NULL) { g_hash_table_destroy(expected_confirmations); expected_confirmations = NULL; } } /*! * \internal * \brief Process a confirmation message from a peer attrd * * This function is called every time a PCMK__ATTRD_CMD_CONFIRM message is * received from a peer attrd. If this is the last confirmation we are waiting * on for a given operation, the registered action will be called. * * \param[in] callid The unique callid for the XML IPC request * \param[in] host The confirming peer attrd's uname */ void attrd_handle_confirmation(int callid, const char *host) { struct confirmation_action *action = NULL; GList *node = NULL; if (expected_confirmations == NULL) { return; } action = pcmk__intkey_table_lookup(expected_confirmations, callid); if (action == NULL) { return; } node = g_list_find_custom(action->respondents, host, (GCompareFunc) strcasecmp); if (node == NULL) { return; } action->respondents = g_list_remove(action->respondents, node->data); crm_trace("Callid %d now waiting on %d confirmations", callid, g_list_length(action->respondents)); if (action->respondents == NULL) { action->fn(action->xml); pcmk__intkey_table_remove(expected_confirmations, callid); crm_trace("%d requests now in expected confirmations table", g_hash_table_size(expected_confirmations)); } } diff --git a/daemons/controld/controld_join_client.c b/daemons/controld/controld_join_client.c index f30d480d33..b0d9593580 100644 --- a/daemons/controld/controld_join_client.c +++ b/daemons/controld/controld_join_client.c @@ -1,364 +1,365 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include void join_query_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data); extern ha_msg_input_t *copy_ha_msg_input(ha_msg_input_t * orig); /*! * \internal * \brief Remember if DC is shutting down as we join * * If we're joining while the current DC is shutting down, update its expected * state, so we don't fence it if we become the new DC. (We weren't a peer * when it broadcast its shutdown request.) * * \param[in] msg A join message from the DC */ static void update_dc_expected(const xmlNode *msg) { if ((controld_globals.dc_name != NULL) && pcmk__xe_attr_is_true(msg, PCMK__XA_DC_LEAVING)) { crm_node_t *dc_node = pcmk__get_node(0, controld_globals.dc_name, NULL, pcmk__node_search_cluster); pcmk__update_peer_expected(__func__, dc_node, CRMD_JOINSTATE_DOWN); } } /* A_CL_JOIN_QUERY */ /* is there a DC out there? */ void do_cl_join_query(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { xmlNode *req = create_request(CRM_OP_JOIN_ANNOUNCE, NULL, NULL, CRM_SYSTEM_DC, CRM_SYSTEM_CRMD, NULL); sleep(1); // Give the cluster layer time to propagate to the DC update_dc(NULL); /* Unset any existing value so that the result is not discarded */ crm_debug("Querying for a DC"); send_cluster_message(NULL, crm_msg_crmd, req, FALSE); free_xml(req); } /* A_CL_JOIN_ANNOUNCE */ /* this is kind of a workaround for the fact that we may not be around or * are otherwise unable to reply when the DC sends out A_DC_JOIN_OFFER_ALL */ void do_cl_join_announce(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { /* don't announce if we're in one of these states */ if (cur_state != S_PENDING) { crm_warn("Not announcing cluster join because in state %s", fsa_state2string(cur_state)); return; } if (!pcmk_is_set(controld_globals.fsa_input_register, R_STARTING)) { /* send as a broadcast */ xmlNode *req = create_request(CRM_OP_JOIN_ANNOUNCE, NULL, NULL, CRM_SYSTEM_DC, CRM_SYSTEM_CRMD, NULL); crm_debug("Announcing availability"); update_dc(NULL); send_cluster_message(NULL, crm_msg_crmd, req, FALSE); free_xml(req); } else { /* Delay announce until we have finished local startup */ crm_warn("Delaying announce of cluster join until local startup is complete"); return; } } static int query_call_id = 0; /* A_CL_JOIN_REQUEST */ /* aka. accept the welcome offer */ void do_cl_join_offer_respond(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { cib_t *cib_conn = controld_globals.cib_conn; ha_msg_input_t *input = fsa_typed_data(fsa_dt_ha_msg); const char *welcome_from; const char *join_id; CRM_CHECK(input != NULL, return); welcome_from = crm_element_value(input->msg, PCMK__XA_SRC); join_id = crm_element_value(input->msg, PCMK__XA_JOIN_ID); crm_trace("Accepting cluster join offer from node %s "CRM_XS" join-%s", welcome_from, crm_element_value(input->msg, PCMK__XA_JOIN_ID)); /* we only ever want the last one */ if (query_call_id > 0) { crm_trace("Cancelling previous join query: %d", query_call_id); remove_cib_op_callback(query_call_id, FALSE); query_call_id = 0; } if (update_dc(input->msg) == FALSE) { crm_warn("Discarding cluster join offer from node %s (expected %s)", welcome_from, controld_globals.dc_name); return; } update_dc_expected(input->msg); query_call_id = cib_conn->cmds->query(cib_conn, NULL, NULL, cib_scope_local|cib_no_children); fsa_register_cib_callback(query_call_id, strdup(join_id), join_query_callback); crm_trace("Registered join query callback: %d", query_call_id); controld_set_fsa_action_flags(A_DC_TIMER_STOP); controld_trigger_fsa(); } void join_query_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data) { char *join_id = user_data; xmlNode *generation = create_xml_node(NULL, PCMK__XE_GENERATION_TUPLE); CRM_LOG_ASSERT(join_id != NULL); if (query_call_id != call_id) { crm_trace("Query %d superseded", call_id); goto done; } query_call_id = 0; if(rc != pcmk_ok || output == NULL) { crm_err("Could not retrieve version details for join-%s: %s (%d)", join_id, pcmk_strerror(rc), rc); register_fsa_error_adv(C_FSA_INTERNAL, I_ERROR, NULL, NULL, __func__); } else if (controld_globals.dc_name == NULL) { crm_debug("Membership is in flux, not continuing join-%s", join_id); } else { xmlNode *reply = NULL; crm_debug("Respond to join offer join-%s from %s", join_id, controld_globals.dc_name); copy_in_properties(generation, output); reply = create_request(CRM_OP_JOIN_REQUEST, generation, controld_globals.dc_name, CRM_SYSTEM_DC, CRM_SYSTEM_CRMD, NULL); crm_xml_add(reply, PCMK__XA_JOIN_ID, join_id); crm_xml_add(reply, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); send_cluster_message(pcmk__get_node(0, controld_globals.dc_name, NULL, pcmk__node_search_cluster), crm_msg_crmd, reply, TRUE); free_xml(reply); } done: free_xml(generation); } void set_join_state(const char *start_state, const char *node_name, const char *node_uuid, bool remote) { if (pcmk__str_eq(start_state, PCMK_VALUE_STANDBY, pcmk__str_casei)) { crm_notice("Forcing node %s to join in %s state per configured " "environment", node_name, start_state); cib__update_node_attr(controld_globals.logger_out, controld_globals.cib_conn, cib_sync_call, PCMK_XE_NODES, node_uuid, NULL, NULL, NULL, "standby", "on", NULL, remote? PCMK_VALUE_REMOTE : NULL); - } else if (pcmk__str_eq(start_state, "online", pcmk__str_casei)) { + } else if (pcmk__str_eq(start_state, PCMK_VALUE_ONLINE, pcmk__str_casei)) { crm_notice("Forcing node %s to join in %s state per configured " "environment", node_name, start_state); cib__update_node_attr(controld_globals.logger_out, controld_globals.cib_conn, cib_sync_call, PCMK_XE_NODES, node_uuid, NULL, NULL, NULL, "standby", "off", NULL, remote? PCMK_VALUE_REMOTE : NULL); - } else if (pcmk__str_eq(start_state, "default", pcmk__str_casei)) { + } else if (pcmk__str_eq(start_state, PCMK_VALUE_DEFAULT, pcmk__str_casei)) { crm_debug("Not forcing a starting state on node %s", node_name); } else { - crm_warn("Unrecognized start state '%s', using 'default' (%s)", + crm_warn("Unrecognized start state '%s', using " + "'" PCMK_VALUE_DEFAULT "' (%s)", start_state, node_name); } } static int update_conn_host_cache(xmlNode *node, void *userdata) { const char *remote = crm_element_value(node, PCMK_XA_ID); const char *conn_host = crm_element_value(node, PCMK__XA_CONN_HOST); const char *state = crm_element_value(node, PCMK__XA_NODE_STATE); crm_node_t *remote_peer = crm_remote_peer_get(remote); if (remote_peer == NULL) { return pcmk_rc_ok; } if (conn_host != NULL) { pcmk__str_update(&remote_peer->conn_host, conn_host); } if (state != NULL) { pcmk__update_peer_state(__func__, remote_peer, state, 0); } return pcmk_rc_ok; } /* A_CL_JOIN_RESULT */ /* aka. this is notification that we have (or have not) been accepted */ void do_cl_join_finalize_respond(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { xmlNode *tmp1 = NULL; gboolean was_nack = TRUE; static gboolean first_join = TRUE; ha_msg_input_t *input = fsa_typed_data(fsa_dt_ha_msg); const char *start_state = pcmk__env_option(PCMK__ENV_NODE_START_STATE); int join_id = -1; const char *op = crm_element_value(input->msg, PCMK__XA_CRM_TASK); const char *welcome_from = crm_element_value(input->msg, PCMK__XA_SRC); if (!pcmk__str_eq(op, CRM_OP_JOIN_ACKNAK, pcmk__str_casei)) { crm_trace("Ignoring op=%s message", op); return; } /* calculate if it was an ack or a nack */ if (pcmk__xe_attr_is_true(input->msg, CRM_OP_JOIN_ACKNAK)) { was_nack = FALSE; } crm_element_value_int(input->msg, PCMK__XA_JOIN_ID, &join_id); if (was_nack) { crm_err("Shutting down because cluster join with leader %s failed " CRM_XS" join-%d NACK'd", welcome_from, join_id); register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); controld_set_fsa_input_flags(R_STAYDOWN); return; } if (!AM_I_DC && pcmk__str_eq(welcome_from, controld_globals.our_nodename, pcmk__str_casei)) { crm_warn("Discarding our own welcome - we're no longer the DC"); return; } if (update_dc(input->msg) == FALSE) { crm_warn("Discarding %s from node %s (expected from %s)", op, welcome_from, controld_globals.dc_name); return; } update_dc_expected(input->msg); /* record the node's feature set as a transient attribute */ update_attrd(controld_globals.our_nodename, CRM_ATTR_FEATURE_SET, CRM_FEATURE_SET, NULL, FALSE); /* send our status section to the DC */ tmp1 = controld_query_executor_state(); if (tmp1 != NULL) { xmlNode *remotes = NULL; xmlNode *reply = create_request(CRM_OP_JOIN_CONFIRM, tmp1, controld_globals.dc_name, CRM_SYSTEM_DC, CRM_SYSTEM_CRMD, NULL); crm_xml_add_int(reply, PCMK__XA_JOIN_ID, join_id); crm_debug("Confirming join-%d: sending local operation history to %s", join_id, controld_globals.dc_name); /* * If this is the node's first join since the controller started on it, * set its initial state (standby or member) according to the user's * preference. * * We do not clear the LRM history here. Even if the DC failed to do it * when we last left, removing them here creates a race condition if the * controller is being recovered. Instead of a list of active resources * from the executor, we may end up with a blank status section. If we * are _NOT_ lucky, we will probe for the "wrong" instance of anonymous * clones and end up with multiple active instances on the machine. */ if (first_join && !pcmk_is_set(controld_globals.fsa_input_register, R_SHUTDOWN)) { first_join = FALSE; if (start_state) { set_join_state(start_state, controld_globals.our_nodename, controld_globals.our_uuid, false); } } send_cluster_message(pcmk__get_node(0, controld_globals.dc_name, NULL, pcmk__node_search_cluster), crm_msg_crmd, reply, TRUE); free_xml(reply); if (AM_I_DC == FALSE) { register_fsa_input_adv(cause, I_NOT_DC, NULL, A_NOTHING, TRUE, __func__); } free_xml(tmp1); /* Update the remote node cache with information about which node * is hosting the connection. */ remotes = pcmk__xe_match(input->msg, PCMK_XE_NODES, NULL, NULL); if (remotes != NULL) { pcmk__xe_foreach_child(remotes, PCMK_XE_NODE, update_conn_host_cache, NULL); } } else { crm_err("Could not confirm join-%d with %s: Local operation history " "failed", join_id, controld_globals.dc_name); register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL); } } diff --git a/daemons/controld/controld_metadata.c b/daemons/controld/controld_metadata.c index 48bbcfd493..f945f3d47f 100644 --- a/daemons/controld/controld_metadata.c +++ b/daemons/controld/controld_metadata.c @@ -1,320 +1,320 @@ /* * Copyright 2017-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include static void ra_param_free(void *param) { if (param) { struct ra_param_s *p = (struct ra_param_s *) param; if (p->rap_name) { free(p->rap_name); } free(param); } } static void metadata_free(void *metadata) { if (metadata) { struct ra_metadata_s *md = (struct ra_metadata_s *) metadata; g_list_free_full(md->ra_params, ra_param_free); free(metadata); } } GHashTable * metadata_cache_new(void) { return pcmk__strkey_table(free, metadata_free); } void metadata_cache_free(GHashTable *mdc) { if (mdc) { crm_trace("Destroying metadata cache with %d members", g_hash_table_size(mdc)); g_hash_table_destroy(mdc); } } void metadata_cache_reset(GHashTable *mdc) { if (mdc) { crm_trace("Resetting metadata cache with %d members", g_hash_table_size(mdc)); g_hash_table_remove_all(mdc); } } static struct ra_param_s * ra_param_from_xml(xmlNode *param_xml) { const char *param_name = crm_element_value(param_xml, PCMK_XA_NAME); struct ra_param_s *p; p = calloc(1, sizeof(struct ra_param_s)); if (p == NULL) { return NULL; } p->rap_name = strdup(param_name); if (p->rap_name == NULL) { free(p); return NULL; } - if (pcmk__xe_attr_is_true(param_xml, "reloadable")) { + if (pcmk__xe_attr_is_true(param_xml, PCMK_XA_RELOADABLE)) { controld_set_ra_param_flags(p, ra_param_reloadable); } - if (pcmk__xe_attr_is_true(param_xml, "unique")) { + if (pcmk__xe_attr_is_true(param_xml, PCMK_XA_UNIQUE)) { controld_set_ra_param_flags(p, ra_param_unique); } if (pcmk__xe_attr_is_true(param_xml, "private")) { controld_set_ra_param_flags(p, ra_param_private); } return p; } static void log_ra_ocf_version(const char *ra_key, const char *ra_ocf_version) { if (pcmk__str_empty(ra_ocf_version)) { crm_warn("%s does not advertise OCF version supported", ra_key); } else if (compare_version(ra_ocf_version, "2") >= 0) { crm_warn("%s supports OCF version %s (this Pacemaker version supports " PCMK_OCF_VERSION " and might not work properly with agent)", ra_key, ra_ocf_version); } else if (compare_version(ra_ocf_version, PCMK_OCF_VERSION) > 0) { crm_info("%s supports OCF version %s (this Pacemaker version supports " PCMK_OCF_VERSION " and might not use all agent features)", ra_key, ra_ocf_version); } else { crm_debug("%s supports OCF version %s", ra_key, ra_ocf_version); } } struct ra_metadata_s * controld_cache_metadata(GHashTable *mdc, const lrmd_rsc_info_t *rsc, const char *metadata_str) { char *key = NULL; const char *reason = NULL; xmlNode *metadata = NULL; xmlNode *match = NULL; struct ra_metadata_s *md = NULL; bool any_private_params = false; bool ocf1_1 = false; CRM_CHECK(mdc && rsc && metadata_str, return NULL); key = crm_generate_ra_key(rsc->standard, rsc->provider, rsc->type); if (!key) { reason = "Invalid resource agent standard or type"; goto err; } metadata = string2xml(metadata_str); if (!metadata) { reason = "Metadata is not valid XML"; goto err; } md = calloc(1, sizeof(struct ra_metadata_s)); if (md == NULL) { reason = "Could not allocate memory"; goto err; } if (strcmp(rsc->standard, PCMK_RESOURCE_CLASS_OCF) == 0) { xmlChar *content = NULL; xmlNode *version_element = first_named_child(metadata, PCMK_XE_VERSION); if (version_element != NULL) { content = xmlNodeGetContent(version_element); } log_ra_ocf_version(key, (const char *) content); if (content != NULL) { ocf1_1 = (compare_version((const char *) content, "1.1") >= 0); xmlFree(content); } } // Check supported actions match = first_named_child(metadata, PCMK_XE_ACTIONS); for (match = first_named_child(match, PCMK_XE_ACTION); match != NULL; match = crm_next_same_xml(match)) { const char *action_name = crm_element_value(match, PCMK_XA_NAME); if (pcmk__str_eq(action_name, PCMK_ACTION_RELOAD_AGENT, pcmk__str_none)) { if (ocf1_1) { controld_set_ra_flags(md, key, ra_supports_reload_agent); } else { crm_notice("reload-agent action will not be used with %s " "because it does not support OCF 1.1 or later", key); } } else if (!ocf1_1 && pcmk__str_eq(action_name, PCMK_ACTION_RELOAD, pcmk__str_casei)) { controld_set_ra_flags(md, key, ra_supports_legacy_reload); } } // Build a parameter list match = first_named_child(metadata, PCMK_XE_PARAMETERS); for (match = first_named_child(match, PCMK_XE_PARAMETER); match != NULL; match = crm_next_same_xml(match)) { const char *param_name = crm_element_value(match, PCMK_XA_NAME); if (param_name == NULL) { crm_warn("Metadata for %s:%s:%s has parameter without a name", rsc->standard, rsc->provider, rsc->type); } else { struct ra_param_s *p = ra_param_from_xml(match); if (p == NULL) { reason = "Could not allocate memory"; goto err; } if (pcmk_is_set(p->rap_flags, ra_param_private)) { any_private_params = true; } md->ra_params = g_list_prepend(md->ra_params, p); } } /* Newer resource agents support the "private" parameter attribute to * indicate sensitive parameters. For backward compatibility with older * agents, implicitly treat a few common names as private when the agent * doesn't specify any explicitly. */ if (!any_private_params) { for (GList *iter = md->ra_params; iter != NULL; iter = iter->next) { struct ra_param_s *p = iter->data; if (pcmk__str_any_of(p->rap_name, "password", "passwd", "user", NULL)) { controld_set_ra_param_flags(p, ra_param_private); } } } g_hash_table_replace(mdc, key, md); free_xml(metadata); return md; err: crm_warn("Unable to update metadata for %s (%s%s%s:%s): %s", rsc->id, rsc->standard, ((rsc->provider == NULL)? "" : ":"), pcmk__s(rsc->provider, ""), rsc->type, reason); free(key); free_xml(metadata); metadata_free(md); return NULL; } /*! * \internal * \brief Get meta-data for a resource * * \param[in,out] lrm_state Use meta-data cache from this executor connection * \param[in] rsc Resource to get meta-data for * \param[in] source Allowed meta-data sources (bitmask of * enum controld_metadata_source_e values) * * \return Meta-data cache entry for given resource, or NULL if not available */ struct ra_metadata_s * controld_get_rsc_metadata(lrm_state_t *lrm_state, const lrmd_rsc_info_t *rsc, uint32_t source) { struct ra_metadata_s *metadata = NULL; char *metadata_str = NULL; char *key = NULL; int rc = pcmk_ok; CRM_CHECK((lrm_state != NULL) && (rsc != NULL), return NULL); if (pcmk_is_set(source, controld_metadata_from_cache)) { key = crm_generate_ra_key(rsc->standard, rsc->provider, rsc->type); if (key != NULL) { metadata = g_hash_table_lookup(lrm_state->metadata_cache, key); free(key); } if (metadata != NULL) { crm_debug("Retrieved metadata for %s (%s%s%s:%s) from cache", rsc->id, rsc->standard, ((rsc->provider == NULL)? "" : ":"), ((rsc->provider == NULL)? "" : rsc->provider), rsc->type); return metadata; } } if (!pcmk_is_set(source, controld_metadata_from_agent)) { return NULL; } /* For most actions, metadata was cached asynchronously before action * execution (via metadata_complete()). * * However if that failed, and for other actions, retrieve the metadata now * via a local, synchronous, direct execution of the agent. * * This has multiple issues, which is why this is just a fallback: the * executor should execute agents, not the controller; metadata for * Pacemaker Remote nodes should be collected on those nodes, not locally; * the metadata call shouldn't eat into the timeout of the real action being * performed; and the synchronous call blocks the controller (which also * means that if the metadata action tries to contact the controller, * everything will hang until the timeout). */ crm_debug("Retrieving metadata for %s (%s%s%s:%s) synchronously", rsc->id, rsc->standard, ((rsc->provider == NULL)? "" : ":"), ((rsc->provider == NULL)? "" : rsc->provider), rsc->type); rc = lrm_state_get_metadata(lrm_state, rsc->standard, rsc->provider, rsc->type, &metadata_str, 0); if (rc != pcmk_ok) { crm_warn("Failed to get metadata for %s (%s%s%s:%s): %s", rsc->id, rsc->standard, ((rsc->provider == NULL)? "" : ":"), ((rsc->provider == NULL)? "" : rsc->provider), rsc->type, pcmk_strerror(rc)); return NULL; } metadata = controld_cache_metadata(lrm_state->metadata_cache, rsc, metadata_str); free(metadata_str); return metadata; } diff --git a/daemons/execd/remoted_pidone.c b/daemons/execd/remoted_pidone.c index 08271bfb2a..d3e9d5fa80 100644 --- a/daemons/execd/remoted_pidone.c +++ b/daemons/execd/remoted_pidone.c @@ -1,302 +1,302 @@ /* - * Copyright 2017-2023 the Pacemaker project contributors + * Copyright 2017-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include "pacemaker-execd.h" static pid_t main_pid = 0; static void sigdone(void) { exit(CRM_EX_OK); } static void sigreap(void) { pid_t pid = 0; int status; do { /* * Opinions seem to differ as to what to put here: * -1, any child process * 0, any child process whose process group ID is equal to that of the calling process */ pid = waitpid(-1, &status, WNOHANG); if (pid == main_pid) { /* Exit when pacemaker-remote exits and use the same return code */ if (WIFEXITED(status)) { exit(WEXITSTATUS(status)); } exit(CRM_EX_ERROR); } } while (pid > 0); } static struct { int sig; void (*handler)(void); } sigmap[] = { { SIGCHLD, sigreap }, { SIGINT, sigdone }, }; /*! * \internal * \brief Check a line of text for a valid environment variable name * * \param[in] line Text to check * \param[out] first First character of valid name if found, NULL otherwise * \param[out] last Last character of valid name if found, NULL otherwise * * \return TRUE if valid name found, FALSE otherwise * \note It's reasonable to impose limitations on environment variable names * beyond what C or setenv() does: We only allow names that contain only * [a-zA-Z0-9_] characters and do not start with a digit. */ static bool find_env_var_name(char *line, char **first, char **last) { // Skip leading whitespace *first = line; while (isspace(**first)) { ++*first; } if (isalpha(**first) || (**first == '_')) { // Valid first character *last = *first; while (isalnum(*(*last + 1)) || (*(*last + 1) == '_')) { ++*last; } return TRUE; } *first = *last = NULL; return FALSE; } static void load_env_vars(const char *filename) { /* We haven't forked or initialized logging yet, so don't leave any file * descriptors open, and don't log -- silently ignore errors. */ FILE *fp = fopen(filename, "r"); if (fp != NULL) { char line[LINE_MAX] = { '\0', }; while (fgets(line, LINE_MAX, fp) != NULL) { char *name = NULL; char *end = NULL; char *value = NULL; char *quote = NULL; // Look for valid name immediately followed by equals sign if (find_env_var_name(line, &name, &end) && (*++end == '=')) { // Null-terminate name, and advance beyond equals sign *end++ = '\0'; // Check whether value is quoted if ((*end == '\'') || (*end == '"')) { quote = end++; } value = end; if (quote) { /* Value is remaining characters up to next non-backslashed * matching quote character. */ while (((*end != *quote) || (*(end - 1) == '\\')) && (*end != '\0')) { end++; } if (*end == *quote) { // Null-terminate value, and advance beyond close quote *end++ = '\0'; } else { // Matching closing quote wasn't found value = NULL; } } else { /* Value is remaining characters up to next non-backslashed * whitespace. */ while ((!isspace(*end) || (*(end - 1) == '\\')) && (*end != '\0')) { ++end; } if (end == (line + LINE_MAX - 1)) { // Line was too long value = NULL; } // Do NOT null-terminate value (yet) } /* We have a valid name and value, and end is now the character * after the closing quote or the first whitespace after the * unquoted value. Make sure the rest of the line is just * whitespace or a comment. */ if (value) { char *value_end = end; while (isspace(*end) && (*end != '\n')) { ++end; } if ((*end == '\n') || (*end == '#')) { if (quote == NULL) { // Now we can null-terminate an unquoted value *value_end = '\0'; } // Don't overwrite (bundle options take precedence) setenv(name, value, 0); } else { value = NULL; } } } if ((value == NULL) && (strchr(line, '\n') == NULL)) { // Eat remainder of line beyond LINE_MAX if (fscanf(fp, "%*[^\n]\n") == EOF) { value = NULL; // Don't care, make compiler happy } } } fclose(fp); } } void remoted_spawn_pidone(int argc, char **argv, char **envp) { sigset_t set; /* This environment variable exists for two purposes: * - For testing, setting it to "full" enables full PID 1 behavior even * when PID is not 1 * - Setting to "vars" enables just the loading of environment variables * from /etc/pacemaker/pcmk-init.env, which could be useful for testing or * containers with a custom PID 1 script that launches pacemaker-remoted. */ - const char *pid1 = "default"; + const char *pid1 = PCMK_VALUE_DEFAULT; if (getpid() != 1) { pid1 = pcmk__env_option(PCMK__ENV_REMOTE_PID1); if (!pcmk__str_any_of(pid1, "full", "vars", NULL)) { // Default, unset, or invalid return; } } /* When a container is launched, it may be given specific environment * variables, which for Pacemaker bundles are given in the bundle * configuration. However, that does not allow for host-specific values. * To allow for that, look for a special file containing a shell-like syntax * of name/value pairs, and export those into the environment. */ load_env_vars("/etc/pacemaker/pcmk-init.env"); if (strcmp(pid1, "vars") == 0) { return; } /* Containers can be expected to have /var/log, but they may not have * /var/log/pacemaker, so use a different default if no value has been * explicitly configured in the container's environment. */ if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) { pcmk__set_env_option(PCMK__ENV_LOGFILE, "/var/log/pcmk-init.log", true); } sigfillset(&set); sigprocmask(SIG_BLOCK, &set, 0); main_pid = fork(); switch (main_pid) { case 0: sigprocmask(SIG_UNBLOCK, &set, NULL); setsid(); setpgid(0, 0); // Child remains as pacemaker-remoted return; case -1: crm_err("fork failed: %s", pcmk_rc_str(errno)); } /* Parent becomes the reaper of zombie processes */ /* Safe to initialize logging now if needed */ # ifdef HAVE_PROGNAME /* Differentiate ourselves in the 'ps' output */ { char *p; int i, maxlen; char *LastArgv = NULL; const char *name = "pcmk-init"; for (i = 0; i < argc; i++) { if (!i || (LastArgv + 1 == argv[i])) LastArgv = argv[i] + strlen(argv[i]); } for (i = 0; envp[i] != NULL; i++) { if ((LastArgv + 1) == envp[i]) { LastArgv = envp[i] + strlen(envp[i]); } } maxlen = (LastArgv - argv[0]) - 2; i = strlen(name); /* We can overwrite individual argv[] arguments */ snprintf(argv[0], maxlen, "%s", name); /* Now zero out everything else */ p = &argv[0][i]; while (p < LastArgv) { *p++ = '\0'; } argv[1] = NULL; } # endif // HAVE_PROGNAME while (1) { int sig; size_t i; sigwait(&set, &sig); for (i = 0; i < PCMK__NELEM(sigmap); i++) { if (sigmap[i].sig == sig) { sigmap[i].handler(); break; } } } } diff --git a/daemons/fenced/pacemaker-fenced.c b/daemons/fenced/pacemaker-fenced.c index b3bd34546e..c3dc1fb861 100644 --- a/daemons/fenced/pacemaker-fenced.c +++ b/daemons/fenced/pacemaker-fenced.c @@ -1,1002 +1,999 @@ /* * Copyright 2009-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include // PRIu32, PRIx32 #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 long stonith_watchdog_timeout_ms = 0; GList *stonith_watchdog_targets = NULL; static GMainLoop *mainloop = NULL; gboolean stand_alone = FALSE; gboolean stonith_shutdown_flag = FALSE; static qb_ipcs_service_t *ipcs = 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; crm_exit_t exit_code = CRM_EX_OK; 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, PCMK__XA_CRM_TASK); if(pcmk__str_eq(op, CRM_OP_RM_NODE_CACHE, pcmk__str_casei)) { crm_xml_add(request, PCMK__XA_T, 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); + c->name = crm_strdup_printf("%s.%u", pcmk__s(value, "unknown"), 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, PCMK__XA_SRC); 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, PCMK__XA_SRC, from); 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(const 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) { const 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, PCMK__XA_SUBT); 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, PCMK__XA_T, 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, PCMK__XA_T, T_STONITH_NOTIFY); crm_xml_add(update_msg, PCMK__XA_SUBT, 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)); } /*! * \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)); } 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 stonith_cleanup(void) { fenced_cib_cleanup(); 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; } 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; } 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, PCMK__XA_T, 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, * context, * short description, * long description */ { PCMK_STONITH_HOST_ARGUMENT, NULL, "string", NULL, "port", NULL, pcmk__opt_context_none, 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, pcmk__opt_context_none, N_("A mapping of node names to port numbers for devices that do not " "support node names."), N_("For example, \"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, pcmk__opt_context_none, N_("A list of nodes that can be targeted by this device (optional " "unless pcmk_host_list=\"static-list\")"), N_("For example, \"node1,node2,node3\"."), }, { PCMK_STONITH_HOST_CHECK, NULL, "select", "dynamic-list, static-list, status, none", "dynamic-list", NULL, pcmk__opt_context_none, N_("How to determine which nodes can be targeted by the device"), N_("Use \"dynamic-list\" to query the device via the 'list' command; " "\"static-list\" to check the pcmk_host_list attribute; " "\"status\" to query the device via the 'status' command; or " "\"none\" to assume every device can fence every node."), }, { PCMK_STONITH_DELAY_MAX, NULL, "time", NULL, "0s", NULL, pcmk__opt_context_none, 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, pcmk__opt_context_none, 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 for each target."), }, { PCMK_STONITH_ACTION_LIMIT, NULL, "integer", NULL, "1", NULL, pcmk__opt_context_none, 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. A value of " "-1 means an unlimited number of actions can be performed in " "parallel."), }, { "pcmk_reboot_action", NULL, "string", NULL, PCMK_ACTION_REBOOT, NULL, pcmk__opt_context_none, 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. Use this to specify an alternate, device-" "specific, command that implements the 'reboot' action."), }, { "pcmk_reboot_timeout", NULL, "time", NULL, "60s", NULL, pcmk__opt_context_none, 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, pcmk__opt_context_none, N_("*** Advanced Use Only *** " "The maximum number of times to try 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. In that case, " "Pacemaker will automatically retry the operation if there is time " "remaining. Use this option to alter the number of times Pacemaker " "tries a 'reboot' action before giving up."), }, { "pcmk_off_action", NULL, "string", NULL, PCMK_ACTION_OFF, NULL, pcmk__opt_context_none, 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, pcmk__opt_context_none, 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, pcmk__opt_context_none, N_("*** Advanced Use Only *** " "The maximum number of times to try 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. In that case, " "Pacemaker will automatically retry the operation if there is time " "remaining. Use this option to alter the number of times Pacemaker " "tries a 'off' action before giving up."), }, { "pcmk_on_action", NULL, "string", NULL, PCMK_ACTION_ON, NULL, pcmk__opt_context_none, 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, pcmk__opt_context_none, 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, pcmk__opt_context_none, N_("*** Advanced Use Only *** " "The maximum number of times to try 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. In that case, " "Pacemaker will automatically retry the operation if there is time " "remaining. Use this option to alter the number of times Pacemaker " "tries a 'on' action before giving up."), }, { "pcmk_list_action", NULL, "string", NULL, PCMK_ACTION_LIST, NULL, pcmk__opt_context_none, 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, pcmk__opt_context_none, 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, pcmk__opt_context_none, N_("*** Advanced Use Only *** " "The maximum number of times to try 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. In that case, " "Pacemaker will automatically retry the operation if there is time " "remaining. Use this option to alter the number of times Pacemaker " "tries a 'list' action before giving up."), }, { "pcmk_monitor_action", NULL, "string", NULL, PCMK_ACTION_MONITOR, NULL, pcmk__opt_context_none, 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, pcmk__opt_context_none, 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. " "Use this to specify an alternate, device-specific, timeout for " "'monitor' actions."), }, { "pcmk_monitor_retries", NULL, "integer", NULL, "2", NULL, pcmk__opt_context_none, N_("*** Advanced Use Only *** " "The maximum number of times to try 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. In that case, " "Pacemaker will automatically retry the operation if there is time " "remaining. Use this option to alter the number of times Pacemaker " "tries a 'monitor' action before giving up."), }, { "pcmk_status_action", NULL, "string", NULL, PCMK_ACTION_STATUS, NULL, pcmk__opt_context_none, 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, pcmk__opt_context_none, 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, pcmk__opt_context_none, N_("*** Advanced Use Only *** " "The maximum number of times to try 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. In that case, " "Pacemaker will automatically retry the operation if there is time " "remaining. Use this option to alter the number of times Pacemaker " "tries a 'status' action before giving up."), }, }; void fencer_metadata(void) { const char *name = "pacemaker-fenced"; 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(name, desc_short, desc_long, pcmk__opt_context_none, 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 (pcmk__connect_generic_ipc(old_instance) == pcmk_rc_ok) { // 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(); rc = fenced_scheduler_init(); if (rc != pcmk_rc_ok) { exit_code = CRM_EX_FATAL; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error initializing scheduler data: %s", pcmk_rc_str(rc)); goto done; } cluster = pcmk_cluster_new(); 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); fenced_scheduler_cleanup(); pcmk__output_and_clear_error(&error, 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/daemons/pacemakerd/pacemakerd.c b/daemons/pacemakerd/pacemakerd.c index 4924ab947a..77d90ac909 100644 --- a/daemons/pacemakerd/pacemakerd.c +++ b/daemons/pacemakerd/pacemakerd.c @@ -1,489 +1,489 @@ /* * Copyright 2010-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include "pacemakerd.h" #if SUPPORT_COROSYNC #include "pcmkd_corosync.h" #endif #include #include #include #include #include #include #include #include #include #include /* indirectly: CRM_EX_* */ #include #include #include #include #include #include #include #define SUMMARY "pacemakerd - primary Pacemaker daemon that launches and monitors all subsidiary Pacemaker daemons" struct { gboolean features; gboolean foreground; gboolean shutdown; gboolean standby; } options; static pcmk__output_t *out = NULL; static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; PCMK__OUTPUT_ARGS("features") static int pacemakerd_features(pcmk__output_t *out, va_list args) { out->info(out, "Pacemaker %s (Build: %s)\n Supporting v%s: %s", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURE_SET, CRM_FEATURES); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("features") static int pacemakerd_features_xml(pcmk__output_t *out, va_list args) { gchar **feature_list = g_strsplit(CRM_FEATURES, " ", 0); - pcmk__output_xml_create_parent(out, "pacemakerd", + pcmk__output_xml_create_parent(out, PCMK_XE_PACEMAKERD, PCMK_XA_VERSION, PACEMAKER_VERSION, - "build", BUILD_VERSION, - "feature_set", CRM_FEATURE_SET, + PCMK_XA_BUILD, BUILD_VERSION, + PCMK_XA_FEATURE_SET, CRM_FEATURE_SET, NULL); - out->begin_list(out, NULL, NULL, "features"); + out->begin_list(out, NULL, NULL, PCMK_XE_FEATURES); for (char **s = feature_list; *s != NULL; s++) { - pcmk__output_create_xml_text_node(out, "feature", *s); + pcmk__output_create_xml_text_node(out, PCMK_XE_FEATURE, *s); } out->end_list(out); g_strfreev(feature_list); return pcmk_rc_ok; } static pcmk__message_entry_t fmt_functions[] = { { "features", "default", pacemakerd_features }, { "features", "xml", pacemakerd_features_xml }, { NULL, NULL, NULL } }; static gboolean pid_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { return TRUE; } static gboolean standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { options.standby = TRUE; pcmk__set_env_option(PCMK__ENV_NODE_START_STATE, PCMK_VALUE_STANDBY, false); return TRUE; } static GOptionEntry entries[] = { { "features", 'F', 0, G_OPTION_ARG_NONE, &options.features, "Display full version and list of features Pacemaker was built with", NULL }, { "foreground", 'f', 0, G_OPTION_ARG_NONE, &options.foreground, "(Ignored) Pacemaker always runs in the foreground", NULL }, { "pid-file", 'p', 0, G_OPTION_ARG_CALLBACK, pid_cb, "(Ignored) Daemon pid file location", "FILE" }, { "shutdown", 'S', 0, G_OPTION_ARG_NONE, &options.shutdown, "Instruct Pacemaker to shutdown on this machine", NULL }, { "standby", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, standby_cb, "Start node in standby state", NULL }, { NULL } }; static void pcmk_ignore(int nsig) { crm_info("Ignoring signal %s (%d)", strsignal(nsig), nsig); } static void pcmk_sigquit(int nsig) { pcmk__panic(__func__); } static void pacemakerd_chown(const char *path, uid_t uid, gid_t gid) { int rc = chown(path, uid, gid); if (rc < 0) { crm_warn("Cannot change the ownership of %s to user %s and gid %d: %s", path, CRM_DAEMON_USER, gid, pcmk_rc_str(errno)); } } static void create_pcmk_dirs(void) { uid_t pcmk_uid = 0; gid_t pcmk_gid = 0; const char *dirs[] = { CRM_PACEMAKER_DIR, // core/blackbox/scheduler/CIB files CRM_CORE_DIR, // core files CRM_BLACKBOX_DIR, // blackbox dumps PE_STATE_DIR, // scheduler inputs CRM_CONFIG_DIR, // the Cluster Information Base (CIB) // Don't build CRM_RSCTMP_DIR, pacemaker-execd will do it NULL }; if (pcmk_daemon_user(&pcmk_uid, &pcmk_gid) < 0) { crm_err("Cluster user %s does not exist, aborting Pacemaker startup", CRM_DAEMON_USER); crm_exit(CRM_EX_NOUSER); } // Used by some resource agents if ((mkdir(CRM_STATE_DIR, 0750) < 0) && (errno != EEXIST)) { crm_warn("Could not create directory " CRM_STATE_DIR ": %s", pcmk_rc_str(errno)); } else { pacemakerd_chown(CRM_STATE_DIR, pcmk_uid, pcmk_gid); } for (int i = 0; dirs[i] != NULL; ++i) { int rc = pcmk__build_path(dirs[i], 0750); if (rc != pcmk_rc_ok) { crm_warn("Could not create directory %s: %s", dirs[i], pcmk_rc_str(rc)); } else { pacemakerd_chown(dirs[i], pcmk_uid, pcmk_gid); } } } static void remove_core_file_limit(void) { struct rlimit cores; // Get current limits if (getrlimit(RLIMIT_CORE, &cores) < 0) { crm_notice("Unable to check system core file limits " "(consider ensuring the size is unlimited): %s", strerror(errno)); return; } // Check whether core dumps are disabled if (cores.rlim_max == 0) { if (geteuid() != 0) { // Yes, and there's nothing we can do about it crm_notice("Core dumps are disabled (consider enabling them)"); return; } cores.rlim_max = RLIM_INFINITY; // Yes, but we're root, so enable them } // Raise soft limit to hard limit (if not already done) if (cores.rlim_cur != cores.rlim_max) { cores.rlim_cur = cores.rlim_max; if (setrlimit(RLIMIT_CORE, &cores) < 0) { crm_notice("Unable to raise system limit on core file size " "(consider doing so manually): %s", strerror(errno)); return; } } if (cores.rlim_cur == RLIM_INFINITY) { crm_trace("Core file size is unlimited"); } else { crm_trace("Core file size is limited to %llu bytes", (unsigned long long) cores.rlim_cur); } } static void pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { pcmk_pacemakerd_api_reply_t *reply = event_data; switch (event_type) { case pcmk_ipc_event_reply: break; default: return; } if (status != CRM_EX_OK) { out->err(out, "Bad reply from pacemakerd: %s", crm_exit_str(status)); return; } if (reply->reply_type != pcmk_pacemakerd_reply_shutdown) { out->err(out, "Unknown reply type %d from pacemakerd", reply->reply_type); } } 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, NULL); pcmk__add_main_args(context, entries); return context; } int main(int argc, char **argv) { int rc = pcmk_rc_ok; crm_exit_t exit_code = CRM_EX_OK; GError *error = NULL; GOptionGroup *output_group = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "p"); GOptionContext *context = build_arg_context(args, &output_group); bool old_instance_connected = false; pcmk_ipc_api_t *old_instance = NULL; qb_ipcs_service_t *ipcs = NULL; subdaemon_check_progress = time(NULL); setenv("LC_ALL", "C", 1); // Ensure logs are in a common language crm_log_preinit(NULL, argc, argv); mainloop_add_signal(SIGHUP, pcmk_ignore); mainloop_add_signal(SIGQUIT, pcmk_sigquit); 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) || (out == NULL)) { 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; } pcmk__force_args(context, &error, "%s --xml-simple-list", g_get_prgname()); pcmk__register_messages(out, fmt_functions); if (options.features) { out->message(out, "features"); exit_code = CRM_EX_OK; goto done; } if (args->version) { out->version(out, false); goto done; } // @COMPAT Drop at 3.0.0; likely last used in 1.1.24 pcmk__set_env_option(PCMK__ENV_MCP, PCMK_VALUE_TRUE, true); if (options.shutdown) { pcmk__cli_init_logging("pacemakerd", args->verbosity); } else { crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); } crm_debug("Checking for existing Pacemaker instance"); rc = pcmk_new_ipc_api(&old_instance, pcmk_ipc_pacemakerd); if (old_instance == NULL) { out->err(out, "Could not check for existing pacemakerd: %s", pcmk_rc_str(rc)); exit_code = pcmk_rc2exitc(rc); goto done; } pcmk_register_ipc_callback(old_instance, pacemakerd_event_cb, NULL); rc = pcmk__connect_ipc(old_instance, pcmk_ipc_dispatch_sync, 2); if (rc != pcmk_rc_ok) { crm_debug("No existing %s instance found: %s", pcmk_ipc_name(old_instance, true), pcmk_rc_str(rc)); } old_instance_connected = pcmk_ipc_is_connected(old_instance); if (options.shutdown) { if (old_instance_connected) { rc = pcmk_pacemakerd_api_shutdown(old_instance, crm_system_name); pcmk_dispatch_ipc(old_instance); exit_code = pcmk_rc2exitc(rc); if (exit_code != CRM_EX_OK) { pcmk_free_ipc_api(old_instance); goto done; } /* We get the ACK immediately, and the response right after that, * but it might take a while for pacemakerd to get around to * shutting down. Wait for that to happen (with 30-minute timeout). */ for (int i = 0; i < 900; i++) { if (!pcmk_ipc_is_connected(old_instance)) { exit_code = CRM_EX_OK; pcmk_free_ipc_api(old_instance); goto done; } sleep(2); } exit_code = CRM_EX_TIMEOUT; pcmk_free_ipc_api(old_instance); goto done; } else { out->err(out, "Could not request shutdown " "of existing Pacemaker instance: %s", pcmk_rc_str(rc)); pcmk_free_ipc_api(old_instance); exit_code = CRM_EX_DISCONNECT; goto done; } } else if (old_instance_connected) { pcmk_free_ipc_api(old_instance); crm_err("Aborting start-up because active Pacemaker instance found"); exit_code = CRM_EX_FATAL; goto done; } pcmk_free_ipc_api(old_instance); /* Don't allow any accidental output after this point. */ if (out != NULL) { out->finish(out, exit_code, true, NULL); pcmk__output_free(out); out = NULL; } #ifdef SUPPORT_COROSYNC if (pacemakerd_read_config() == FALSE) { crm_exit(CRM_EX_UNAVAILABLE); } #endif // OCF shell functions and cluster-glue need facility under different name { const char *facility = pcmk__env_option(PCMK__ENV_LOGFACILITY); if (!pcmk__str_eq(facility, PCMK__VALUE_NONE, pcmk__str_casei|pcmk__str_null_matches)) { pcmk__set_env_option("LOGFACILITY", facility, true); } } crm_notice("Starting Pacemaker %s "CRM_XS" build=%s features:%s", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); mainloop = g_main_loop_new(NULL, FALSE); remove_core_file_limit(); create_pcmk_dirs(); pcmk__serve_pacemakerd_ipc(&ipcs, &pacemakerd_ipc_callbacks); #ifdef SUPPORT_COROSYNC /* Allows us to block shutdown */ if (!cluster_connect_cfg()) { exit_code = CRM_EX_PROTOCOL; goto done; } #endif if (pcmk__locate_sbd() > 0) { running_with_sbd = TRUE; } switch (find_and_track_existing_processes()) { case pcmk_rc_ok: break; case pcmk_rc_ipc_unauthorized: exit_code = CRM_EX_CANTCREAT; goto done; default: exit_code = CRM_EX_FATAL; goto done; }; mainloop_add_signal(SIGTERM, pcmk_shutdown); mainloop_add_signal(SIGINT, pcmk_shutdown); if ((running_with_sbd) && pcmk__get_sbd_sync_resource_startup()) { crm_notice("Waiting for startup-trigger from SBD."); pacemakerd_state = PCMK__VALUE_WAIT_FOR_PING; startup_trigger = mainloop_add_trigger(G_PRIORITY_HIGH, init_children_processes, NULL); } else { if (running_with_sbd) { crm_warn("Enabling SBD_SYNC_RESOURCE_STARTUP would (if supported " "by your SBD version) improve reliability of " "interworking between SBD & pacemaker."); } pacemakerd_state = PCMK__VALUE_STARTING_DAEMONS; init_children_processes(NULL); } crm_notice("Pacemaker daemon successfully started and accepting connections"); g_main_loop_run(mainloop); if (ipcs) { crm_trace("Closing IPC server"); mainloop_del_ipc_server(ipcs); ipcs = NULL; } g_main_loop_unref(mainloop); #ifdef SUPPORT_COROSYNC cluster_disconnect_cfg(); #endif done: g_strfreev(processed_args); pcmk__free_arg_context(context); pcmk__output_and_clear_error(&error, 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/crm/common/options.h b/include/crm/common/options.h index 628636c21f..2b4f23f9f6 100644 --- a/include/crm/common/options.h +++ b/include/crm/common/options.h @@ -1,185 +1,196 @@ /* * Copyright 2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_COMMON_OPTIONS__H # define PCMK__CRM_COMMON_OPTIONS__H #ifdef __cplusplus extern "C" { #endif /** * \file * \brief API related to options * \ingroup core */ /* * Cluster options */ #define PCMK_OPT_BATCH_LIMIT "batch-limit" #define PCMK_OPT_CLUSTER_DELAY "cluster-delay" #define PCMK_OPT_CLUSTER_INFRASTRUCTURE "cluster-infrastructure" #define PCMK_OPT_CLUSTER_IPC_LIMIT "cluster-ipc-limit" #define PCMK_OPT_CLUSTER_NAME "cluster-name" #define PCMK_OPT_CLUSTER_RECHECK_INTERVAL "cluster-recheck-interval" #define PCMK_OPT_CONCURRENT_FENCING "concurrent-fencing" #define PCMK_OPT_DC_DEADTIME "dc-deadtime" #define PCMK_OPT_DC_VERSION "dc-version" #define PCMK_OPT_ELECTION_TIMEOUT "election-timeout" #define PCMK_OPT_ENABLE_ACL "enable-acl" #define PCMK_OPT_ENABLE_STARTUP_PROBES "enable-startup-probes" #define PCMK_OPT_FENCE_REACTION "fence-reaction" #define PCMK_OPT_HAVE_WATCHDOG "have-watchdog" #define PCMK_OPT_JOIN_FINALIZATION_TIMEOUT "join-finalization-timeout" #define PCMK_OPT_JOIN_INTEGRATION_TIMEOUT "join-integration-timeout" #define PCMK_OPT_LOAD_THRESHOLD "load-threshold" #define PCMK_OPT_MAINTENANCE_MODE "maintenance-mode" #define PCMK_OPT_MIGRATION_LIMIT "migration-limit" #define PCMK_OPT_NO_QUORUM_POLICY "no-quorum-policy" #define PCMK_OPT_NODE_ACTION_LIMIT "node-action-limit" #define PCMK_OPT_NODE_HEALTH_BASE "node-health-base" #define PCMK_OPT_NODE_HEALTH_GREEN "node-health-green" #define PCMK_OPT_NODE_HEALTH_RED "node-health-red" #define PCMK_OPT_NODE_HEALTH_STRATEGY "node-health-strategy" #define PCMK_OPT_NODE_HEALTH_YELLOW "node-health-yellow" #define PCMK_OPT_NODE_PENDING_TIMEOUT "node-pending-timeout" #define PCMK_OPT_PE_ERROR_SERIES_MAX "pe-error-series-max" #define PCMK_OPT_PE_INPUT_SERIES_MAX "pe-input-series-max" #define PCMK_OPT_PE_WARN_SERIES_MAX "pe-warn-series-max" #define PCMK_OPT_PLACEMENT_STRATEGY "placement-strategy" #define PCMK_OPT_PRIORITY_FENCING_DELAY "priority-fencing-delay" #define PCMK_OPT_SHUTDOWN_ESCALATION "shutdown-escalation" #define PCMK_OPT_SHUTDOWN_LOCK "shutdown-lock" #define PCMK_OPT_SHUTDOWN_LOCK_LIMIT "shutdown-lock-limit" #define PCMK_OPT_START_FAILURE_IS_FATAL "start-failure-is-fatal" #define PCMK_OPT_STARTUP_FENCING "startup-fencing" #define PCMK_OPT_STONITH_ACTION "stonith-action" #define PCMK_OPT_STONITH_ENABLED "stonith-enabled" #define PCMK_OPT_STONITH_MAX_ATTEMPTS "stonith-max-attempts" #define PCMK_OPT_STONITH_TIMEOUT "stonith-timeout" #define PCMK_OPT_STONITH_WATCHDOG_TIMEOUT "stonith-watchdog-timeout" #define PCMK_OPT_STOP_ALL_RESOURCES "stop-all-resources" #define PCMK_OPT_STOP_ORPHAN_ACTIONS "stop-orphan-actions" #define PCMK_OPT_STOP_ORPHAN_RESOURCES "stop-orphan-resources" #define PCMK_OPT_SYMMETRIC_CLUSTER "symmetric-cluster" #define PCMK_OPT_TRANSITION_DELAY "transition-delay" /* * Meta-attributes */ #define PCMK_META_ALLOW_MIGRATE "allow-migrate" #define PCMK_META_ALLOW_UNHEALTHY_NODES "allow-unhealthy-nodes" #define PCMK_META_CLONE_MAX "clone-max" #define PCMK_META_CLONE_MIN "clone-min" #define PCMK_META_CLONE_NODE_MAX "clone-node-max" #define PCMK_META_CONTAINER_ATTR_TARGET "container-attribute-target" #define PCMK_META_CRITICAL "critical" #define PCMK_META_ENABLED "enabled" #define PCMK_META_FAILURE_TIMEOUT "failure-timeout" #define PCMK_META_GLOBALLY_UNIQUE "globally-unique" #define PCMK_META_INTERLEAVE "interleave" #define PCMK_META_INTERVAL "interval" #define PCMK_META_IS_MANAGED "is-managed" #define PCMK_META_INTERVAL_ORIGIN "interval-origin" #define PCMK_META_MAINTENANCE "maintenance" #define PCMK_META_MIGRATION_THRESHOLD "migration-threshold" #define PCMK_META_MULTIPLE_ACTIVE "multiple-active" #define PCMK_META_NOTIFY "notify" #define PCMK_META_ON_FAIL "on-fail" #define PCMK_META_ORDERED "ordered" #define PCMK_META_PRIORITY "priority" #define PCMK_META_PROMOTABLE "promotable" #define PCMK_META_PROMOTED_MAX "promoted-max" #define PCMK_META_PROMOTED_NODE_MAX "promoted-node-max" #define PCMK_META_RECORD_PENDING "record-pending" #define PCMK_META_REMOTE_ADDR "remote-addr" #define PCMK_META_REMOTE_ALLOW_MIGRATE "remote-allow-migrate" #define PCMK_META_REMOTE_CONNECT_TIMEOUT "remote-connect-timeout" #define PCMK_META_REMOTE_NODE "remote-node" #define PCMK_META_REMOTE_PORT "remote-port" #define PCMK_META_REQUIRES "requires" #define PCMK_META_RESOURCE_STICKINESS "resource-stickiness" #define PCMK_META_START_DELAY "start-delay" #define PCMK_META_TARGET_ROLE "target-role" #define PCMK_META_TIMEOUT "timeout" #define PCMK_META_TIMESTAMP_FORMAT "timestamp-format" /* * Remote resource instance attributes */ #define PCMK_REMOTE_RA_ADDR "addr" #define PCMK_REMOTE_RA_PORT "port" #define PCMK_REMOTE_RA_RECONNECT_INTERVAL "reconnect_interval" #define PCMK_REMOTE_RA_SERVER "server" /* * Enumerated values */ #define PCMK_VALUE_ALWAYS "always" #define PCMK_VALUE_AND "and" +#define PCMK_VALUE_BALANCED "balanced" #define PCMK_VALUE_BLOCK "block" #define PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS "cib-bootstrap-options" #define PCMK_VALUE_DATE_SPEC "date_spec" +#define PCMK_VALUE_DEFAULT "default" #define PCMK_VALUE_DEFINED "defined" #define PCMK_VALUE_DEMOTE "demote" #define PCMK_VALUE_DENY "deny" #define PCMK_VALUE_EQ "eq" #define PCMK_VALUE_EXCLUSIVE "exclusive" +#define PCMK_VALUE_FAILED "failed" #define PCMK_VALUE_FALSE "false" #define PCMK_VALUE_FENCE "fence" #define PCMK_VALUE_FREEZE "freeze" +#define PCMK_VALUE_GRANTED "granted" #define PCMK_VALUE_GREEN "green" #define PCMK_VALUE_GT "gt" #define PCMK_VALUE_GTE "gte" #define PCMK_VALUE_IGNORE "ignore" #define PCMK_VALUE_INTEGER "integer" #define PCMK_VALUE_LITERAL "literal" #define PCMK_VALUE_LT "lt" #define PCMK_VALUE_LTE "lte" #define PCMK_VALUE_MANDATORY "Mandatory" #define PCMK_VALUE_MEMBER "member" #define PCMK_VALUE_META "meta" +#define PCMK_VALUE_MINIMAL "minimal" #define PCMK_VALUE_NE "ne" #define PCMK_VALUE_NEVER "never" #define PCMK_VALUE_NOT_DEFINED "not_defined" #define PCMK_VALUE_NUMBER "number" +#define PCMK_VALUE_ONLINE "online" #define PCMK_VALUE_OPTIONAL "Optional" #define PCMK_VALUE_OR "or" #define PCMK_VALUE_PARAM "param" +#define PCMK_VALUE_PENDING "pending" #define PCMK_VALUE_READ "read" #define PCMK_VALUE_RED "red" #define PCMK_VALUE_REMOTE "remote" #define PCMK_VALUE_RESTART "restart" #define PCMK_VALUE_RESTART_CONTAINER "restart-container" +#define PCMK_VALUE_REVOKED "revoked" #define PCMK_VALUE_SERIALIZE "Serialize" #define PCMK_VALUE_STANDBY "standby" #define PCMK_VALUE_STRING "string" #define PCMK_VALUE_STOP "stop" +#define PCMK_VALUE_SUCCESS "success" #define PCMK_VALUE_TRUE "true" +#define PCMK_VALUE_UNKNOWN "unknown" +#define PCMK_VALUE_UTILIZATION "utilization" #define PCMK_VALUE_VERSION "version" #define PCMK_VALUE_WRITE "write" #define PCMK_VALUE_YELLOW "yellow" // @COMPAT This will become a deprecated alias for PCMK_VALUE_FENCE (see T279) #define PCMK_VALUE_FENCE_LEGACY "suicide" #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_OPTIONS__H diff --git a/include/crm/msg_xml.h b/include/crm/msg_xml.h index f99b5dcd3b..97e69d6de1 100644 --- a/include/crm/msg_xml.h +++ b/include/crm/msg_xml.h @@ -1,273 +1,314 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_MSG_XML__H # define PCMK__CRM_MSG_XML__H # include #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) #include #endif #ifdef __cplusplus extern "C" { #endif /* This file defines constants for various XML syntax (mainly element and * attribute names). * * For consistency, new constants should start with "PCMK_", followed by: * * "XE" for XML element names * * "XA" for XML attribute names * * "OPT" for cluster option (property) names * * "META" for meta-attribute names * * "VALUE" for enumerated values for various options * * Old names that don't follow this policy should eventually be deprecated and * replaced with names that do. * * Symbols should be public if the user may specify them somewhere (especially * the CIB) or if they're part of a well-defined structure that a user may need * to parse. They should be internal if they're used only internally to * Pacemaker (such as daemon IPC/CPG message XML). * * Constants belong in the following locations: * * Public "XE" and "XA": msg_xml.h * * Internal "XE" and "XA": crm_internal.h * * Public "OPT", "META", and "VALUE": options.h * * Internal "OPT", "META", and "VALUE": options_internal.h * * For meta-attributes that can be specified as either XML attributes or nvpair * names, use "META" unless using both "XA" and "META" constants adds clarity. * An example is operation attributes, which can be specified either as * attributes of the PCMK_XE_OP element or as nvpairs in a meta-attribute set * beneath the PCMK_XE_OP element. */ /* * XML elements */ #define PCMK_XE_ACL_GROUP "acl_group" #define PCMK_XE_ACL_PERMISSION "acl_permission" #define PCMK_XE_ACL_ROLE "acl_role" #define PCMK_XE_ACL_TARGET "acl_target" #define PCMK_XE_ACLS "acls" #define PCMK_XE_ACTION "action" #define PCMK_XE_ACTIONS "actions" #define PCMK_XE_ALERT "alert" #define PCMK_XE_ALERTS "alerts" #define PCMK_XE_ATTRIBUTE "attribute" #define PCMK_XE_BUNDLE "bundle" #define PCMK_XE_CHANGE "change" #define PCMK_XE_CHANGE_ATTR "change-attr" #define PCMK_XE_CHANGE_LIST "change-list" #define PCMK_XE_CHANGE_RESULT "change-result" #define PCMK_XE_CIB "cib" #define PCMK_XE_CLONE "clone" #define PCMK_XE_CLUSTER_PROPERTY_SET "cluster_property_set" #define PCMK_XE_CONFIGURATION "configuration" #define PCMK_XE_CONSTRAINTS "constraints" #define PCMK_XE_CONTENT "content" #define PCMK_XE_CRM_CONFIG "crm_config" #define PCMK_XE_DATE_EXPRESSION "date_expression" #define PCMK_XE_DATE_SPEC "date_spec" #define PCMK_XE_DIFF "diff" #define PCMK_XE_DURATION "duration" #define PCMK_XE_EXPRESSION "expression" +#define PCMK_XE_FEATURE "feature" +#define PCMK_XE_FEATURES "features" #define PCMK_XE_FENCING_LEVEL "fencing-level" #define PCMK_XE_FENCING_TOPOLOGY "fencing-topology" #define PCMK_XE_GROUP "group" #define PCMK_XE_INSTANCE_ATTRIBUTES "instance_attributes" #define PCMK_XE_LONGDESC "longdesc" #define PCMK_XE_META_ATTRIBUTES "meta_attributes" #define PCMK_XE_NETWORK "network" #define PCMK_XE_NODE "node" #define PCMK_XE_NODES "nodes" #define PCMK_XE_NVPAIR "nvpair" #define PCMK_XE_OBJ_REF "obj_ref" #define PCMK_XE_OP "op" #define PCMK_XE_OP_DEFAULTS "op_defaults" #define PCMK_XE_OPERATION "operation" #define PCMK_XE_OP_EXPRESSION "op_expression" #define PCMK_XE_OPTION "option" +#define PCMK_XE_PACEMAKERD "pacemakerd" #define PCMK_XE_PARAMETER "parameter" #define PCMK_XE_PARAMETERS "parameters" #define PCMK_XE_PORT_MAPPING "port-mapping" #define PCMK_XE_POSITION "position" #define PCMK_XE_PRIMITIVE "primitive" #define PCMK_XE_RECIPIENT "recipient" +#define PCMK_XE_RESOURCE "resource" #define PCMK_XE_RESOURCE_AGENT "resource-agent" #define PCMK_XE_RESOURCE_REF "resource_ref" #define PCMK_XE_RESOURCE_SET "resource_set" #define PCMK_XE_RESOURCES "resources" #define PCMK_XE_ROLE "role" -#define PCMK_XE_RULE "rule" +#define PCMK_XE_RSC_ACTION "rsc_action" #define PCMK_XE_RSC_COLOCATION "rsc_colocation" #define PCMK_XE_RSC_DEFAULTS "rsc_defaults" #define PCMK_XE_RSC_EXPRESSION "rsc_expression" #define PCMK_XE_RSC_LOCATION "rsc_location" #define PCMK_XE_RSC_ORDER "rsc_order" #define PCMK_XE_RSC_TICKET "rsc_ticket" +#define PCMK_XE_RULE "rule" #define PCMK_XE_SELECT "select" #define PCMK_XE_SELECT_ATTRIBUTES "select_attributes" #define PCMK_XE_SELECT_FENCING "select_fencing" #define PCMK_XE_SELECT_NODES "select_nodes" #define PCMK_XE_SELECT_RESOURCES "select_resources" #define PCMK_XE_SHORTDESC "shortdesc" #define PCMK_XE_SOURCE "source" #define PCMK_XE_STATUS "status" #define PCMK_XE_STORAGE "storage" #define PCMK_XE_STORAGE_MAPPING "storage-mapping" #define PCMK_XE_TAG "tag" #define PCMK_XE_TAGS "tags" #define PCMK_XE_TARGET "target" #define PCMK_XE_TEMPLATE "template" #define PCMK_XE_TICKET "ticket" #define PCMK_XE_TICKETS "tickets" #define PCMK_XE_UTILIZATION "utilization" #define PCMK_XE_VERSION "version" /* * XML attributes */ #define PCMK_XA_ACTION "action" +#define PCMK_XA_ACTIVE "active" #define PCMK_XA_ADD_HOST "add-host" #define PCMK_XA_ADMIN_EPOCH "admin_epoch" #define PCMK_XA_ATTRIBUTE "attribute" +#define PCMK_XA_AUTHOR "author" +#define PCMK_XA_BLOCKED "blocked" #define PCMK_XA_BOOLEAN_OP "boolean-op" +#define PCMK_XA_BUILD "build" #define PCMK_XA_CIB_LAST_WRITTEN "cib-last-written" #define PCMK_XA_CLASS "class" #define PCMK_XA_CLIENT "client" +#define PCMK_XA_COMPLETED "completed" #define PCMK_XA_CONTROL_PORT "control-port" #define PCMK_XA_CRM_DEBUG_ORIGIN "crm-debug-origin" #define PCMK_XA_CRM_FEATURE_SET "crm_feature_set" #define PCMK_XA_CRM_TIMESTAMP "crm-timestamp" #define PCMK_XA_DAYS "days" #define PCMK_XA_DC_UUID "dc-uuid" #define PCMK_XA_DEFAULT "default" #define PCMK_XA_DELEGATE "delegate" #define PCMK_XA_DESCRIPTION "description" +#define PCMK_XA_DEST "dest" #define PCMK_XA_DEVICES "devices" +#define PCMK_XA_DISABLED "disabled" #define PCMK_XA_DURATION "duration" #define PCMK_XA_END "end" #define PCMK_XA_EPOCH "epoch" #define PCMK_XA_EXEC_TIME "exec-time" #define PCMK_XA_EXECUTION_DATE "execution-date" #define PCMK_XA_EXIT_REASON "exit-reason" +#define PCMK_XA_EXPECTED_UP "expected_up" +#define PCMK_XA_EXTENDED_STATUS "extended-status" #define PCMK_XA_FAILED "failed" +#define PCMK_XA_FAILURE_IGNORED "failure_ignored" +#define PCMK_XA_FEATURE_SET "feature_set" +#define PCMK_XA_FEATURES "features" #define PCMK_XA_FIRST "first" #define PCMK_XA_FIRST_ACTION "first-action" #define PCMK_XA_FORMAT "format" #define PCMK_XA_HAVE_QUORUM "have-quorum" +#define PCMK_XA_HEALTH "health" #define PCMK_XA_HOST "host" #define PCMK_XA_HOST_INTERFACE "host-interface" #define PCMK_XA_HOST_NETMASK "host-netmask" #define PCMK_XA_HOURS "hours" #define PCMK_XA_ID "id" #define PCMK_XA_ID_REF "id-ref" #define PCMK_XA_IMAGE "image" #define PCMK_XA_INDEX "index" #define PCMK_XA_INFLUENCE "influence" #define PCMK_XA_INTERNAL_PORT "internal-port" #define PCMK_XA_IP_RANGE_START "ip-range-start" +#define PCMK_XA_IS_DC "is_dc" #define PCMK_XA_KIND "kind" #define PCMK_XA_LANG "lang" +#define PCMK_XA_LAST_GRANTED "last-granted" #define PCMK_XA_LAST_RC_CHANGE "last-rc-change" +#define PCMK_XA_LOCKED_TO "locked_to" #define PCMK_XA_LOSS_POLICY "loss-policy" +#define PCMK_XA_MAINTENANCE "maintenance" +#define PCMK_XA_MANAGED "managed" #define PCMK_XA_MINUTES "minutes" #define PCMK_XA_MIXED_VERSION "mixed_version" #define PCMK_XA_MONTHDAYS "monthdays" #define PCMK_XA_MONTHS "months" +#define PCMK_XA_MULTI_STATE "multi_state" #define PCMK_XA_NAME "name" #define PCMK_XA_NETWORK "network" +#define PCMK_XA_NEXT_ROLE "next-role" #define PCMK_XA_NO_QUORUM_PANIC "no-quorum-panic" #define PCMK_XA_NODE "node" #define PCMK_XA_NODE_ATTRIBUTE "node-attribute" +#define PCMK_XA_NODES_RUNNING_ON "nodes_running_on" #define PCMK_XA_NUM_UPDATES "num_updates" #define PCMK_XA_NUMBER "number" #define PCMK_XA_OBJECT_TYPE "object-type" +#define PCMK_XA_ONLINE "online" #define PCMK_XA_OP "op" #define PCMK_XA_OPERATION "operation" #define PCMK_XA_OPTIONS "options" #define PCMK_XA_ORIGIN "origin" +#define PCMK_XA_ORPHANED "orphaned" #define PCMK_XA_PATH "path" +#define PCMK_XA_PENDING "pending" #define PCMK_XA_PORT "port" #define PCMK_XA_PRESENT "present" +#define PCMK_XA_PROGRAM "program" #define PCMK_XA_PROMOTED_MAX "promoted-max" #define PCMK_XA_PROVIDER "provider" #define PCMK_XA_QUEUE_TIME "queue-time" #define PCMK_XA_RANGE "range" #define PCMK_XA_REASON "reason" #define PCMK_XA_REFERENCE "reference" +#define PCMK_XA_RELOADABLE "reloadable" #define PCMK_XA_REMOTE_CLEAR_PORT "remote-clear-port" #define PCMK_XA_REMOTE_TLS_PORT "remote-tls-port" #define PCMK_XA_REPLICAS "replicas" #define PCMK_XA_REPLICAS_PER_HOST "replicas-per-host" #define PCMK_XA_REQUEST "request" #define PCMK_XA_REQUIRE_ALL "require-all" +#define PCMK_XA_RESOURCE "resource" +#define PCMK_XA_RESOURCE_AGENT "resource_agent" #define PCMK_XA_RESOURCE_DISCOVERY "resource-discovery" +#define PCMK_XA_RESOURCES_RUNNING "resources_running" #define PCMK_XA_RESULT "result" #define PCMK_XA_ROLE "role" #define PCMK_XA_RSC "rsc" #define PCMK_XA_RSC_PATTERN "rsc-pattern" #define PCMK_XA_RSC_ROLE "rsc-role" #define PCMK_XA_RUN_COMMAND "run-command" #define PCMK_XA_RUNNING "running" #define PCMK_XA_SCOPE "scope" #define PCMK_XA_SCORE "score" #define PCMK_XA_SCORE_ATTRIBUTE "score-attribute" #define PCMK_XA_SEQUENTIAL "sequential" #define PCMK_XA_SECONDS "seconds" +#define PCMK_XA_SHUTDOWN "shutdown" +#define PCMK_XA_SOURCE "source" #define PCMK_XA_SOURCE_DIR "source-dir" #define PCMK_XA_SOURCE_DIR_ROOT "source-dir-root" +#define PCMK_XA_STANDBY "standby" +#define PCMK_XA_STANDBY_ONFAIL "standby_onfail" #define PCMK_XA_START "start" #define PCMK_XA_STATUS "status" #define PCMK_XA_SYMMETRICAL "symmetrical" #define PCMK_XA_TARGET "target" #define PCMK_XA_TARGET_ATTRIBUTE "target-attribute" #define PCMK_XA_TARGET_DIR "target-dir" #define PCMK_XA_TARGET_PATTERN "target-pattern" +#define PCMK_XA_TARGET_ROLE "target_role" #define PCMK_XA_TARGET_VALUE "target-value" #define PCMK_XA_TEMPLATE "template" #define PCMK_XA_TICKET "ticket" #define PCMK_XA_TIME "time" #define PCMK_XA_THEN "then" #define PCMK_XA_THEN_ACTION "then-action" #define PCMK_XA_TYPE "type" #define PCMK_XA_UNAME "uname" -#define PCMK_XA_USER "user" +#define PCMK_XA_UNCLEAN "unclean" +#define PCMK_XA_UNIQUE "unique" #define PCMK_XA_UPDATE_CLIENT "update-client" #define PCMK_XA_UPDATE_ORIGIN "update-origin" #define PCMK_XA_UPDATE_USER "update-user" +#define PCMK_XA_USER "user" #define PCMK_XA_VALIDATE_WITH "validate-with" #define PCMK_XA_VALUE "value" #define PCMK_XA_VALUE_SOURCE "value-source" #define PCMK_XA_VERSION "version" #define PCMK_XA_WEEKDAYS "weekdays" #define PCMK_XA_WEEKS "weeks" #define PCMK_XA_WEEKYEARS "weekyears" #define PCMK_XA_WITH_QUORUM "with_quorum" #define PCMK_XA_WITH_RSC "with-rsc" #define PCMK_XA_WITH_RSC_ROLE "with-rsc-role" #define PCMK_XA_XPATH "xpath" #define PCMK_XA_YEARDAYS "yeardays" #define PCMK_XA_YEARS "years" # define ID(x) crm_element_value(x, PCMK_XA_ID) #ifdef __cplusplus } #endif #endif diff --git a/include/crm_internal.h b/include/crm_internal.h index 69584d6a4a..e0bc12bcf0 100644 --- a/include/crm_internal.h +++ b/include/crm_internal.h @@ -1,258 +1,259 @@ /* * Copyright 2006-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef CRM_INTERNAL__H # define CRM_INTERNAL__H # ifndef PCMK__CONFIG_H # define PCMK__CONFIG_H # include # endif # include /* Our minimum glib dependency is 2.42. Define that as both the minimum and * maximum glib APIs that are allowed (i.e. APIs that were already deprecated * in 2.42, and APIs introduced after 2.42, cannot be used by Pacemaker code). */ #define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_42 #define GLIB_VERSION_MAX_ALLOWED GLIB_VERSION_2_42 # include # include # include /* Public API headers can guard including deprecated API headers with this * symbol, thus preventing internal code (which includes this header) from using * deprecated APIs, while still allowing external code to use them by default. */ #define PCMK_ALLOW_DEPRECATED 0 # include # include # include # include # include # include # include # include # include # include # include # include #define N_(String) (String) #ifdef ENABLE_NLS # define _(String) gettext(String) #else # define _(String) (String) #endif /* * XML element names used only by internal code */ #define PCMK__XE_ATTRIBUTES "attributes" #define PCMK__XE_CRM_EVENT "crm_event" #define PCMK__XE_CRM_XML "crm_xml" #define PCMK__XE_DOWNED "downed" #define PCMK__XE_FAILED "failed" #define PCMK__XE_FAILED_UPDATE "failed_update" #define PCMK__XE_GENERATION_TUPLE "generation_tuple" #define PCMK__XE_LRM "lrm" #define PCMK__XE_LRM_RESOURCE "lrm_resource" #define PCMK__XE_LRM_RESOURCES "lrm_resources" #define PCMK__XE_LRM_RSC_OP "lrm_rsc_op" #define PCMK__XE_MAINTENANCE "maintenance" #define PCMK__XE_META "meta" #define PCMK__XE_NODE_STATE "node_state" #define PCMK__XE_OPTIONS "options" #define PCMK__XE_PARAM "param" #define PCMK__XE_PING "ping" #define PCMK__XE_PING_RESPONSE "ping_response" #define PCMK__XE_PSEUDO_EVENT "pseudo_event" #define PCMK__XE_RSC_OP "rsc_op" #define PCMK__XE_SHUTDOWN "shutdown" #define PCMK__XE_TICKET_STATE "ticket_state" #define PCMK__XE_TRANSIENT_ATTRIBUTES "transient_attributes" #define PCMK__XE_TRANSITION_GRAPH "transition_graph" // @COMPAT Deprecated since 1.1.12 #define PCMK__XE_ACL_USER "acl_user" /* @TODO Deprecate somehow. It's undocumented and behaves the same as * PCMK__XE_CIB in places where it's recognized. */ #define PCMK__XE_ALL "all" // @COMPAT Deprecated since 2.1.7 #define PCMK__XE_DIFF_ADDED "diff-added" // @COMPAT Deprecated since 2.1.7 #define PCMK__XE_DIFF_REMOVED "diff-removed" /* @COMPAT Deprecated since 2.0.0; alias for with PCMK_META_PROMOTABLE * set to "true" */ #define PCMK__XE_PROMOTABLE_LEGACY "master" // @COMPAT Deprecated since 1.1.12 #define PCMK__XE_ROLE_REF "role_ref" /* * XML attribute names used only by internal code */ #define PCMK__XA_ATTR_DAMPENING "attr_dampening" #define PCMK__XA_ATTR_FORCE "attrd_is_force_write" #define PCMK__XA_ATTR_INTERVAL "attr_clear_interval" #define PCMK__XA_ATTR_IS_PRIVATE "attr_is_private" #define PCMK__XA_ATTR_IS_REMOTE "attr_is_remote" #define PCMK__XA_ATTR_NAME "attr_name" #define PCMK__XA_ATTR_NODE_ID "attr_host_id" #define PCMK__XA_ATTR_NODE_NAME "attr_host" #define PCMK__XA_ATTR_OPERATION "attr_clear_operation" #define PCMK__XA_ATTR_PATTERN "attr_regex" #define PCMK__XA_ATTR_RESOURCE "attr_resource" #define PCMK__XA_ATTR_SECTION "attr_section" #define PCMK__XA_ATTR_SET "attr_set" #define PCMK__XA_ATTR_SET_TYPE "attr_set_type" #define PCMK__XA_ATTR_SYNC_POINT "attr_sync_point" #define PCMK__XA_ATTR_USER "attr_user" #define PCMK__XA_ATTR_UUID "attr_key" #define PCMK__XA_ATTR_VALUE "attr_value" #define PCMK__XA_ATTR_VERSION "attr_version" #define PCMK__XA_ATTR_WRITER "attr_writer" #define PCMK__XA_CALL_ID "call-id" #define PCMK__XA_CONFIG_ERRORS "config-errors" #define PCMK__XA_CONFIG_WARNINGS "config-warnings" #define PCMK__XA_CONFIRM "confirm" #define PCMK__XA_CONN_HOST "connection_host" #define PCMK__XA_CRMD "crmd" #define PCMK__XA_CRMD_STATE "crmd_state" #define PCMK__XA_CRM_HOST_TO "crm_host_to" #define PCMK__XA_CRM_LIMIT_MAX "crm-limit-max" #define PCMK__XA_CRM_LIMIT_MODE "crm-limit-mode" #define PCMK__XA_CRM_SUBSYSTEM "crm_subsystem" #define PCMK__XA_CRM_SYS_FROM "crm_sys_from" #define PCMK__XA_CRM_SYS_TO "crm_sys_to" #define PCMK__XA_CRM_TASK "crm_task" #define PCMK__XA_CRM_TGRAPH_IN "crm-tgraph-in" #define PCMK__XA_CRM_USER "crm_user" #define PCMK__XA_DC_LEAVING "dc-leaving" #define PCMK__XA_DIGEST "digest" #define PCMK__XA_ELECTION_AGE_SEC "election-age-sec" #define PCMK__XA_ELECTION_AGE_NANO_SEC "election-age-nano-sec" #define PCMK__XA_ELECTION_ID "election-id" #define PCMK__XA_ELECTION_OWNER "election-owner" #define PCMK__XA_EXPECTED "expected" #define PCMK__XA_FILE "file" +#define PCMK__XA_GRANTED "granted" #define PCMK__XA_GRAPH_ERRORS "graph-errors" #define PCMK__XA_GRAPH_WARNINGS "graph-warnings" #define PCMK__XA_HIDDEN "hidden" #define PCMK__XA_IN_CCM "in_ccm" #define PCMK__XA_JOIN "join" #define PCMK__XA_JOIN_ID "join_id" #define PCMK__XA_LONG_ID "long-id" #define PCMK__XA_MODE "mode" #define PCMK__XA_MOON "moon" #define PCMK__XA_NODE_FENCED "node_fenced" #define PCMK__XA_NODE_IN_MAINTENANCE "node_in_maintenance" #define PCMK__XA_NODE_START_STATE "node_start_state" #define PCMK__XA_NODE_STATE "node_state" #define PCMK__XA_OP_DIGEST "op-digest" #define PCMK__XA_OP_FORCE_RESTART "op-force-restart" #define PCMK__XA_OP_RESTART_DIGEST "op-restart-digest" #define PCMK__XA_OP_SECURE_DIGEST "op-secure-digest" #define PCMK__XA_OP_SECURE_PARAMS "op-secure-params" #define PCMK__XA_OP_STATUS "op-status" #define PCMK__XA_PACEMAKERD_STATE "pacemakerd_state" #define PCMK__XA_PASSWORD "password" #define PCMK__XA_PRIORITY "priority" #define PCMK__XA_RC_CODE "rc-code" #define PCMK__XA_REAP "reap" #define PCMK__XA_REMOTE_NODE "remote_node" /* Actions to be executed on Pacemaker Remote nodes are routed through the * controller on the cluster node hosting the remote connection. That cluster * node is considered the router node for the action. */ #define PCMK__XA_ROUTER_NODE "router_node" #define PCMK__XA_RSC_ID "rsc-id" #define PCMK__XA_SCHEMA "schema" #define PCMK__XA_SCHEMAS "schemas" #define PCMK__XA_SRC "src" #define PCMK__XA_SUBT "subt" // subtype #define PCMK__XA_T "t" // type #define PCMK__XA_TASK "task" #define PCMK__XA_TRANSITION_KEY "transition-key" #define PCMK__XA_TRANSITION_MAGIC "transition-magic" #define PCMK__XA_UPTIME "uptime" // @COMPAT Used only with v1 patchsets #define PCMK__XA_CRM_DIFF_MARKER "__crm_diff_marker__" // @COMPAT Deprecated since 2.1.5 #define PCMK__XA_FIRST_INSTANCE "first-instance" // @COMPAT Deprecated since 2.1.7 #define PCMK__XA_ORDERING "ordering" // @COMPAT Deprecated alias for PCMK_XA_PROMOTED_MAX since 2.0.0 #define PCMK__XA_PROMOTED_MAX_LEGACY "masters" // @COMPAT Deprecated since 1.1.12 #define PCMK__XA_REF "ref" // @COMPAT Deprecated since 2.1.6 #define PCMK__XA_REPLACE "replace" // @COMPAT Deprecated since 2.1.5 #define PCMK__XA_RSC_INSTANCE "rsc-instance" // @COMPAT Deprecated since 1.1.12 #define PCMK__XA_TAG "tag" // @COMPAT Deprecated since 2.1.5 #define PCMK__XA_THEN_INSTANCE "then-instance" // @COMPAT Deprecated since 2.1.5 #define PCMK__XA_WITH_RSC_INSTANCE "with-rsc-instance" /* * IPC service names that are only used internally */ # define PCMK__SERVER_BASED_RO "cib_ro" # define PCMK__SERVER_BASED_RW "cib_rw" # define PCMK__SERVER_BASED_SHM "cib_shm" /* * IPC commands that can be sent to Pacemaker daemons */ #define PCMK__ATTRD_CMD_PEER_REMOVE "peer-remove" #define PCMK__ATTRD_CMD_UPDATE "update" #define PCMK__ATTRD_CMD_UPDATE_BOTH "update-both" #define PCMK__ATTRD_CMD_UPDATE_DELAY "update-delay" #define PCMK__ATTRD_CMD_QUERY "query" #define PCMK__ATTRD_CMD_REFRESH "refresh" #define PCMK__ATTRD_CMD_FLUSH "flush" #define PCMK__ATTRD_CMD_SYNC "sync" #define PCMK__ATTRD_CMD_SYNC_RESPONSE "sync-response" #define PCMK__ATTRD_CMD_CLEAR_FAILURE "clear-failure" #define PCMK__ATTRD_CMD_CONFIRM "confirm" #define PCMK__CONTROLD_CMD_NODES "list-nodes" #endif /* CRM_INTERNAL__H */ diff --git a/lib/common/options.c b/lib/common/options.c index 9dd66becf6..aab3bac073 100644 --- a/lib/common/options.c +++ b/lib/common/options.c @@ -1,1154 +1,1156 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include void pcmk__cli_help(char cmd) { if (cmd == 'v' || cmd == '$') { printf("Pacemaker %s\n", PACEMAKER_VERSION); printf("Written by Andrew Beekhof and " "the Pacemaker project contributors\n"); } else if (cmd == '!') { printf("Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); } crm_exit(CRM_EX_OK); while(1); // above does not return } /* * Option metadata */ static pcmk__cluster_option_t cluster_options[] = { /* name, old name, type, allowed values, * default value, validator, * context, * short description, * long description */ { PCMK_OPT_DC_VERSION, NULL, "string", NULL, PCMK__VALUE_NONE, NULL, pcmk__opt_context_controld, N_("Pacemaker version on cluster node elected Designated Controller " "(DC)"), N_("Includes a hash which identifies the exact changeset the code was " "built from. Used for diagnostic purposes."), }, { PCMK_OPT_CLUSTER_INFRASTRUCTURE, NULL, "string", NULL, "corosync", NULL, pcmk__opt_context_controld, N_("The messaging stack on which Pacemaker is currently running"), N_("Used for informational and diagnostic purposes."), }, { PCMK_OPT_CLUSTER_NAME, NULL, "string", NULL, NULL, NULL, pcmk__opt_context_controld, N_("An arbitrary name for the cluster"), N_("This optional value is mostly for users' convenience as desired " "in administration, but may also be used in Pacemaker " "configuration rules via the #cluster-name node attribute, and " "by higher-level tools and resource agents."), }, { PCMK_OPT_DC_DEADTIME, NULL, "time", NULL, "20s", pcmk__valid_interval_spec, pcmk__opt_context_controld, N_("How long to wait for a response from other nodes during start-up"), N_("The optimal value will depend on the speed and load of your " "network and the type of switches used."), }, { PCMK_OPT_CLUSTER_RECHECK_INTERVAL, NULL, "time", N_("Zero disables polling, while positive values are an interval in " "seconds (unless other units are specified, for example \"5min\")"), "15min", pcmk__valid_interval_spec, pcmk__opt_context_controld, N_("Polling interval to recheck cluster state and evaluate rules " "with date specifications"), N_("Pacemaker is primarily event-driven, and looks ahead to know when " "to recheck cluster state for failure timeouts and most time-based " "rules. However, it will also recheck the cluster after this " "amount of inactivity, to evaluate rules with date specifications " "and serve as a fail-safe for certain types of scheduler bugs."), }, { PCMK_OPT_FENCE_REACTION, NULL, "select", PCMK_VALUE_STOP ", panic", PCMK_VALUE_STOP, NULL, pcmk__opt_context_controld, N_("How a cluster node should react if notified of its own fencing"), N_("A cluster node may receive notification of its own fencing if " "fencing is misconfigured, or if fabric fencing is in use that " "doesn't cut cluster communication. Use \"stop\" to attempt to " "immediately stop Pacemaker and stay stopped, or \"panic\" to " "attempt to immediately reboot the local node, falling back to " "stop on failure."), }, { PCMK_OPT_ELECTION_TIMEOUT, NULL, "time", NULL, "2min", pcmk__valid_interval_spec, pcmk__opt_context_controld, N_("*** Advanced Use Only ***"), N_("Declare an election failed if it is not decided within this much " "time. If you need to adjust this value, it probably indicates " "the presence of a bug."), }, { PCMK_OPT_SHUTDOWN_ESCALATION, NULL, "time", NULL, "20min", pcmk__valid_interval_spec, pcmk__opt_context_controld, N_("*** Advanced Use Only ***"), N_("Exit immediately if shutdown does not complete within this much " "time. If you need to adjust this value, it probably indicates " "the presence of a bug."), }, { PCMK_OPT_JOIN_INTEGRATION_TIMEOUT, "crmd-integration-timeout", "time", NULL, "3min", pcmk__valid_interval_spec, pcmk__opt_context_controld, N_("*** Advanced Use Only ***"), N_("If you need to adjust this value, it probably indicates " "the presence of a bug."), }, { PCMK_OPT_JOIN_FINALIZATION_TIMEOUT, "crmd-finalization-timeout", "time", NULL, "30min", pcmk__valid_interval_spec, pcmk__opt_context_controld, N_("*** Advanced Use Only ***"), N_("If you need to adjust this value, it probably indicates " "the presence of a bug."), }, { PCMK_OPT_TRANSITION_DELAY, "crmd-transition-delay", "time", NULL, "0s", pcmk__valid_interval_spec, pcmk__opt_context_controld, N_("*** Advanced Use Only *** " "Enabling this option will slow down cluster recovery under all " "conditions"), N_("Delay cluster recovery for this much time to allow for additional " "events to occur. Useful if your configuration is sensitive to " "the order in which ping updates arrive."), }, { PCMK_OPT_NO_QUORUM_POLICY, NULL, "select", PCMK_VALUE_STOP ", " PCMK_VALUE_FREEZE ", " PCMK_VALUE_IGNORE ", " PCMK_VALUE_DEMOTE ", " PCMK_VALUE_FENCE_LEGACY, PCMK_VALUE_STOP, pcmk__valid_no_quorum_policy, pcmk__opt_context_schedulerd, N_("What to do when the cluster does not have quorum"), NULL, }, { PCMK_OPT_SHUTDOWN_LOCK, NULL, "boolean", NULL, PCMK_VALUE_FALSE, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("Whether to lock resources to a cleanly shut down node"), N_("When true, resources active on a node when it is cleanly shut down " "are kept \"locked\" to that node (not allowed to run elsewhere) " "until they start again on that node after it rejoins (or for at " "most shutdown-lock-limit, if set). Stonith resources and " "Pacemaker Remote connections are never locked. Clone and bundle " "instances and the promoted role of promotable clones are " "currently never locked, though support could be added in a future " "release."), }, { PCMK_OPT_SHUTDOWN_LOCK_LIMIT, NULL, "time", NULL, "0", pcmk__valid_interval_spec, pcmk__opt_context_schedulerd, N_("Do not lock resources to a cleanly shut down node longer than " "this"), N_("If shutdown-lock is true and this is set to a nonzero time " "duration, shutdown locks will expire after this much time has " "passed since the shutdown was initiated, even if the node has not " "rejoined."), }, { PCMK_OPT_ENABLE_ACL, NULL, "boolean", NULL, PCMK_VALUE_FALSE, pcmk__valid_boolean, pcmk__opt_context_based, N_("Enable Access Control Lists (ACLs) for the CIB"), NULL, }, { PCMK_OPT_SYMMETRIC_CLUSTER, NULL, "boolean", NULL, PCMK_VALUE_TRUE, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("Whether resources can run on any node by default"), NULL, }, { PCMK_OPT_MAINTENANCE_MODE, NULL, "boolean", NULL, PCMK_VALUE_FALSE, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("Whether the cluster should refrain from monitoring, starting, and " "stopping resources"), NULL, }, { PCMK_OPT_START_FAILURE_IS_FATAL, NULL, "boolean", NULL, PCMK_VALUE_TRUE, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("Whether a start failure should prevent a resource from being " "recovered on the same node"), N_("When true, the cluster will immediately ban a resource from a node " "if it fails to start there. When false, the cluster will instead " "check the resource's fail count against its migration-threshold.") }, { PCMK_OPT_ENABLE_STARTUP_PROBES, NULL, "boolean", NULL, PCMK_VALUE_TRUE, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("Whether the cluster should check for active resources during " "start-up"), NULL, }, // Fencing-related options { PCMK_OPT_STONITH_ENABLED, NULL, "boolean", NULL, PCMK_VALUE_TRUE, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("*** Advanced Use Only *** " "Whether nodes may be fenced as part of recovery"), N_("If false, unresponsive nodes are immediately assumed to be " "harmless, and resources that were active on them may be recovered " "elsewhere. This can result in a \"split-brain\" situation, " "potentially leading to data loss and/or service unavailability."), }, { PCMK_OPT_STONITH_ACTION, NULL, "select", "reboot, off, poweroff", PCMK_ACTION_REBOOT, pcmk__is_fencing_action, pcmk__opt_context_schedulerd, N_("Action to send to fence device when a node needs to be fenced " "(\"poweroff\" is a deprecated alias for \"off\")"), NULL, }, { PCMK_OPT_STONITH_TIMEOUT, NULL, "time", NULL, "60s", pcmk__valid_interval_spec, pcmk__opt_context_schedulerd, N_("How long to wait for on, off, and reboot fence actions to complete " "by default"), NULL, }, { PCMK_OPT_HAVE_WATCHDOG, NULL, "boolean", NULL, PCMK_VALUE_FALSE, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("Whether watchdog integration is enabled"), N_("This is set automatically by the cluster according to whether SBD " "is detected to be in use. User-configured values are ignored. " "The value `true` is meaningful if diskless SBD is used and " "`stonith-watchdog-timeout` is nonzero. In that case, if fencing " "is required, watchdog-based self-fencing will be performed via " "SBD without requiring a fencing resource explicitly configured."), }, { /* @COMPAT Currently, unparsable values default to -1 (auto-calculate), * while missing values default to 0 (disable). All values are accepted * (unless the controller finds that the value conflicts with the * SBD_WATCHDOG_TIMEOUT). * * At a compatibility break: properly validate as a timeout, let * either negative values or a particular string like "auto" mean auto- * calculate, and use 0 as the single default for when the option either * is unset or fails to validate. */ PCMK_OPT_STONITH_WATCHDOG_TIMEOUT, NULL, "time", NULL, "0", NULL, pcmk__opt_context_controld, N_("How long before nodes can be assumed to be safely down when " "watchdog-based self-fencing via SBD is in use"), N_("If this is set to a positive value, lost nodes are assumed to " "self-fence using watchdog-based SBD within this much time. This " "does not require a fencing resource to be explicitly configured, " "though a fence_watchdog resource can be configured, to limit use " "to specific nodes. If this is set to 0 (the default), the cluster " "will never assume watchdog-based self-fencing. If this is set to a " "negative value, the cluster will use twice the local value of the " "`SBD_WATCHDOG_TIMEOUT` environment variable if that is positive, " "or otherwise treat this as 0. WARNING: When used, this timeout " "must be larger than `SBD_WATCHDOG_TIMEOUT` on all nodes that use " "watchdog-based SBD, and Pacemaker will refuse to start on any of " "those nodes where this is not true for the local value or SBD is " "not active. When this is set to a negative value, " "`SBD_WATCHDOG_TIMEOUT` must be set to the same value on all nodes " "that use SBD, otherwise data corruption or loss could occur."), }, { PCMK_OPT_STONITH_MAX_ATTEMPTS, NULL, "integer", NULL, "10", pcmk__valid_positive_int, pcmk__opt_context_controld, N_("How many times fencing can fail before it will no longer be " "immediately re-attempted on a target"), NULL, }, { PCMK_OPT_CONCURRENT_FENCING, NULL, "boolean", NULL, PCMK__CONCURRENT_FENCING_DEFAULT, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("Allow performing fencing operations in parallel"), NULL, }, { PCMK_OPT_STARTUP_FENCING, NULL, "boolean", NULL, PCMK_VALUE_TRUE, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("*** Advanced Use Only *** " "Whether to fence unseen nodes at start-up"), N_("Setting this to false may lead to a \"split-brain\" situation, " "potentially leading to data loss and/or service unavailability."), }, { PCMK_OPT_PRIORITY_FENCING_DELAY, NULL, "time", NULL, "0", pcmk__valid_interval_spec, pcmk__opt_context_schedulerd, N_("Apply fencing delay targeting the lost nodes with the highest " "total resource priority"), N_("Apply specified delay for the fencings that are targeting the lost " "nodes with the highest total resource priority in case we don't " "have the majority of the nodes in our cluster partition, so that " "the more significant nodes potentially win any fencing match, " "which is especially meaningful under split-brain of 2-node " "cluster. A promoted resource instance takes the base priority + 1 " "on calculation if the base priority is not 0. Any static/random " "delays that are introduced by `pcmk_delay_base/max` configured " "for the corresponding fencing resources will be added to this " "delay. This delay should be significantly greater than, safely " "twice, the maximum `pcmk_delay_base/max`. By default, priority " "fencing delay is disabled."), }, { PCMK_OPT_NODE_PENDING_TIMEOUT, NULL, "time", NULL, "0", pcmk__valid_interval_spec, pcmk__opt_context_schedulerd, N_("How long to wait for a node that has joined the cluster to join " "the controller process group"), N_("Fence nodes that do not join the controller process group within " "this much time after joining the cluster, to allow the cluster " "to continue managing resources. A value of 0 means never fence " "pending nodes. Setting the value to 2h means fence nodes after " "2 hours."), }, { PCMK_OPT_CLUSTER_DELAY, NULL, "time", NULL, "60s", pcmk__valid_interval_spec, pcmk__opt_context_schedulerd, N_("Maximum time for node-to-node communication"), N_("The node elected Designated Controller (DC) will consider an action " "failed if it does not get a response from the node executing the " "action within this time (after considering the action's own " "timeout). The \"correct\" value will depend on the speed and " "load of your network and cluster nodes.") }, // Limits { PCMK_OPT_LOAD_THRESHOLD, NULL, "percentage", NULL, "80%", pcmk__valid_percentage, pcmk__opt_context_controld, N_("Maximum amount of system load that should be used by cluster " "nodes"), N_("The cluster will slow down its recovery process when the amount of " "system resources used (currently CPU) approaches this limit"), }, { PCMK_OPT_NODE_ACTION_LIMIT, NULL, "integer", NULL, "0", pcmk__valid_int, pcmk__opt_context_controld, N_("Maximum number of jobs that can be scheduled per node (defaults to " "2x cores)"), NULL, }, { PCMK_OPT_BATCH_LIMIT, NULL, "integer", NULL, "0", pcmk__valid_int, pcmk__opt_context_schedulerd, N_("Maximum number of jobs that the cluster may execute in parallel " "across all nodes"), N_("The \"correct\" value will depend on the speed and load of your " "network and cluster nodes. If set to 0, the cluster will " "impose a dynamically calculated limit when any node has a " "high load."), }, { PCMK_OPT_MIGRATION_LIMIT, NULL, "integer", NULL, "-1", pcmk__valid_int, pcmk__opt_context_schedulerd, N_("The number of live migration actions that the cluster is allowed " "to execute in parallel on a node (-1 means no limit)"), NULL, }, { PCMK_OPT_CLUSTER_IPC_LIMIT, NULL, "integer", NULL, "500", pcmk__valid_positive_int, pcmk__opt_context_based, N_("Maximum IPC message backlog before disconnecting a cluster daemon"), N_("Raise this if log has \"Evicting client\" messages for cluster " "daemon PIDs (a good value is the number of resources in the " "cluster multiplied by the number of nodes)."), }, // Orphans and stopping { PCMK_OPT_STOP_ALL_RESOURCES, NULL, "boolean", NULL, PCMK_VALUE_FALSE, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("Whether the cluster should stop all active resources"), NULL, }, { PCMK_OPT_STOP_ORPHAN_RESOURCES, NULL, "boolean", NULL, PCMK_VALUE_TRUE, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("Whether to stop resources that were removed from the " "configuration"), NULL, }, { PCMK_OPT_STOP_ORPHAN_ACTIONS, NULL, "boolean", NULL, PCMK_VALUE_TRUE, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("Whether to cancel recurring actions removed from the " "configuration"), NULL, }, { PCMK__OPT_REMOVE_AFTER_STOP, NULL, "boolean", NULL, PCMK_VALUE_FALSE, pcmk__valid_boolean, pcmk__opt_context_schedulerd, N_("*** Deprecated *** " "Whether to remove stopped resources from the executor"), N_("Values other than default are poorly tested and potentially " "dangerous. This option will be removed in a future release."), }, // Storing inputs { PCMK_OPT_PE_ERROR_SERIES_MAX, NULL, "integer", NULL, "-1", pcmk__valid_int, pcmk__opt_context_schedulerd, N_("The number of scheduler inputs resulting in errors to save"), N_("Zero to disable, -1 to store unlimited."), }, { PCMK_OPT_PE_WARN_SERIES_MAX, NULL, "integer", NULL, "5000", pcmk__valid_int, pcmk__opt_context_schedulerd, N_("The number of scheduler inputs resulting in warnings to save"), N_("Zero to disable, -1 to store unlimited."), }, { PCMK_OPT_PE_INPUT_SERIES_MAX, NULL, "integer", NULL, "4000", pcmk__valid_int, pcmk__opt_context_schedulerd, N_("The number of scheduler inputs without errors or warnings to save"), N_("Zero to disable, -1 to store unlimited."), }, // Node health { PCMK_OPT_NODE_HEALTH_STRATEGY, NULL, "select", PCMK__VALUE_NONE ", " PCMK__VALUE_MIGRATE_ON_RED ", " PCMK__VALUE_ONLY_GREEN ", " PCMK__VALUE_PROGRESSIVE ", " PCMK__VALUE_CUSTOM, PCMK__VALUE_NONE, pcmk__validate_health_strategy, pcmk__opt_context_schedulerd, N_("How cluster should react to node health attributes"), N_("Requires external entities to create node attributes (named with " "the prefix \"#health\") with values \"red\", \"yellow\", or " "\"green\".") }, { PCMK_OPT_NODE_HEALTH_BASE, NULL, "integer", NULL, "0", pcmk__valid_int, pcmk__opt_context_schedulerd, N_("Base health score assigned to a node"), N_("Only used when \"node-health-strategy\" is set to " "\"progressive\"."), }, { PCMK_OPT_NODE_HEALTH_GREEN, NULL, "integer", NULL, "0", pcmk__valid_int, pcmk__opt_context_schedulerd, N_("The score to use for a node health attribute whose value is " "\"green\""), N_("Only used when \"node-health-strategy\" is set to \"custom\" or " "\"progressive\"."), }, { PCMK_OPT_NODE_HEALTH_YELLOW, NULL, "integer", NULL, "0", pcmk__valid_int, pcmk__opt_context_schedulerd, N_("The score to use for a node health attribute whose value is " "\"yellow\""), N_("Only used when \"node-health-strategy\" is set to \"custom\" or " "\"progressive\"."), }, { PCMK_OPT_NODE_HEALTH_RED, NULL, "integer", NULL, "-INFINITY", pcmk__valid_int, pcmk__opt_context_schedulerd, N_("The score to use for a node health attribute whose value is " "\"red\""), N_("Only used when \"node-health-strategy\" is set to \"custom\" or " "\"progressive\".") }, // Placement strategy { PCMK_OPT_PLACEMENT_STRATEGY, NULL, "select", - "default, utilization, minimal, balanced", - "default", pcmk__valid_placement_strategy, + PCMK_VALUE_DEFAULT ", " PCMK_VALUE_UTILIZATION ", " + PCMK_VALUE_MINIMAL ", " PCMK_VALUE_BALANCED, + PCMK_VALUE_DEFAULT, pcmk__valid_placement_strategy, pcmk__opt_context_schedulerd, N_("How the cluster should allocate resources to nodes"), NULL, }, }; /* * Environment variable option handling */ /*! * \internal * \brief Get the value of a Pacemaker environment variable option * * If an environment variable option is set, with either a PCMK_ or (for * backward compatibility) HA_ prefix, log and return the value. * * \param[in] option Environment variable name (without prefix) * * \return Value of environment variable option, or NULL in case of * option name too long or value not found */ const char * pcmk__env_option(const char *option) { const char *const prefixes[] = {"PCMK_", "HA_"}; char env_name[NAME_MAX]; const char *value = NULL; CRM_CHECK(!pcmk__str_empty(option), return NULL); for (int i = 0; i < PCMK__NELEM(prefixes); i++) { int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option); if (rv < 0) { crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option, strerror(errno)); return NULL; } if (rv >= sizeof(env_name)) { crm_trace("\"%s%s\" is too long", prefixes[i], option); continue; } value = getenv(env_name); if (value != NULL) { crm_trace("Found %s = %s", env_name, value); return value; } } crm_trace("Nothing found for %s", option); return NULL; } /*! * \brief Set or unset a Pacemaker environment variable option * * Set an environment variable option with a \c "PCMK_" prefix and optionally * an \c "HA_" prefix for backward compatibility. * * \param[in] option Environment variable name (without prefix) * \param[in] value New value (or NULL to unset) * \param[in] compat If false and \p value is not \c NULL, set only * \c "PCMK_