diff --git a/daemons/based/Makefile.am b/daemons/based/Makefile.am index 053d93cea6..fb843f8466 100644 --- a/daemons/based/Makefile.am +++ b/daemons/based/Makefile.am @@ -1,47 +1,49 @@ # # Copyright 2004-2021 the Pacemaker project contributors # # The version control history for this file may have further details. # # This source code is licensed under the GNU General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # include $(top_srcdir)/mk/common.mk EXTRA_DIST = cib.pam halibdir = $(CRM_DAEMON_DIR) COMMONLIBS = $(top_builddir)/lib/common/libcrmcommon.la \ $(top_builddir)/lib/cib/libcib.la halib_PROGRAMS = pacemaker-based -noinst_HEADERS = pacemaker-based.h +noinst_HEADERS = based_transaction.h \ + pacemaker-based.h pacemaker_based_CFLAGS = $(CFLAGS_HARDENED_EXE) pacemaker_based_LDFLAGS = $(LDFLAGS_HARDENED_EXE) pacemaker_based_LDADD = $(top_builddir)/lib/cluster/libcrmcluster.la \ $(COMMONLIBS) $(CLUSTERLIBS) pacemaker_based_SOURCES = pacemaker-based.c \ based_callbacks.c \ based_common.c \ based_io.c \ based_messages.c \ based_notify.c \ - based_remote.c + based_remote.c \ + based_transaction.c clean-generic: rm -f *.log *.debug *.xml *~ if BUILD_LEGACY_LINKS install-exec-hook: $(MKDIR_P) -- $(DESTDIR)$(CRM_DAEMON_DIR) cd $(DESTDIR)$(CRM_DAEMON_DIR) && rm -f cib && $(LN_S) pacemaker-based cib uninstall-hook: cd $(DESTDIR)$(CRM_DAEMON_DIR) && rm -f cib endif diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c index d01a6c48eb..fd9a88c9ca 100644 --- a/daemons/based/based_callbacks.c +++ b/daemons/based/based_callbacks.c @@ -1,1774 +1,1846 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include // uint32_t, uint64_t, UINT64_C() #include #include #include // PRIu64 #include #include #include #include #include #include #include #define EXIT_ESCALATION_MS 10000 static unsigned long cib_local_bcast_num = 0; typedef struct cib_local_notify_s { xmlNode *notify_src; char *client_id; gboolean from_peer; gboolean sync_reply; } cib_local_notify_t; struct digest_data { char *nodes; char *alerts; char *status; }; int next_client_id = 0; gboolean legacy_mode = FALSE; qb_ipcs_service_t *ipcs_ro = NULL; qb_ipcs_service_t *ipcs_rw = NULL; qb_ipcs_service_t *ipcs_shm = NULL; static int cib_process_command(xmlNode *request, const cib_operation_t *operation, xmlNode **reply, xmlNode **cib_diff, bool privileged); static gboolean cib_common_callback(qb_ipcs_connection_t *c, void *data, size_t size, gboolean privileged); gboolean cib_legacy_mode(void) { return legacy_mode; } /*! * \internal * \brief Free a struct digest_data object's members * * \param[in,out] digests Object whose members to free * * \note This does not free the object itself, which may be stack-allocated. */ static void free_digests(struct digest_data *digests) { if (digests != NULL) { free(digests->nodes); free(digests->alerts); free(digests->status); } } static int32_t cib_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { if (cib_shutdown_flag) { crm_info("Ignoring new IPC client [%d] during shutdown", pcmk__client_pid(c)); return -EPERM; } if (pcmk__new_client(c, uid, gid) == NULL) { return -EIO; } return 0; } static int32_t cib_ipc_dispatch_rw(qb_ipcs_connection_t * c, void *data, size_t size) { pcmk__client_t *client = pcmk__find_client(c); crm_trace("%p message from %s", c, client->id); return cib_common_callback(c, data, size, TRUE); } static int32_t cib_ipc_dispatch_ro(qb_ipcs_connection_t * c, void *data, size_t size) { pcmk__client_t *client = pcmk__find_client(c); crm_trace("%p message from %s", c, client->id); return cib_common_callback(c, data, size, FALSE); } /* Error code means? */ static int32_t cib_ipc_closed(qb_ipcs_connection_t * c) { pcmk__client_t *client = pcmk__find_client(c); if (client == NULL) { return 0; } crm_trace("Connection %p", c); + based_discard_transaction(client); pcmk__free_client(client); return 0; } static void cib_ipc_destroy(qb_ipcs_connection_t * c) { crm_trace("Connection %p", c); cib_ipc_closed(c); if (cib_shutdown_flag) { cib_shutdown(0); } } struct qb_ipcs_service_handlers ipc_ro_callbacks = { .connection_accept = cib_ipc_accept, .connection_created = NULL, .msg_process = cib_ipc_dispatch_ro, .connection_closed = cib_ipc_closed, .connection_destroyed = cib_ipc_destroy }; struct qb_ipcs_service_handlers ipc_rw_callbacks = { .connection_accept = cib_ipc_accept, .connection_created = NULL, .msg_process = cib_ipc_dispatch_rw, .connection_closed = cib_ipc_closed, .connection_destroyed = cib_ipc_destroy }; /*! * \internal * \brief Create reply XML for a CIB request * * \param[in] op CIB operation type * \param[in] call_id CIB call ID * \param[in] client_id CIB client ID * \param[in] call_options Group of enum cib_call_options flags * \param[in] rc Request return code * \param[in] call_data Request output data * * \return Reply XML * * \note The caller is responsible for freeing the return value using * \p free_xml(). */ static xmlNode * create_cib_reply(const char *op, const char *call_id, const char *client_id, int call_options, int rc, xmlNode *call_data) { xmlNode *reply = create_xml_node(NULL, "cib-reply"); CRM_ASSERT(reply != NULL); crm_xml_add(reply, F_TYPE, T_CIB); crm_xml_add(reply, F_CIB_OPERATION, op); crm_xml_add(reply, F_CIB_CALLID, call_id); crm_xml_add(reply, F_CIB_CLIENTID, client_id); crm_xml_add_int(reply, F_CIB_CALLOPTS, call_options); crm_xml_add_int(reply, F_CIB_RC, rc); if (call_data != NULL) { crm_trace("Attaching reply output"); add_message_xml(reply, F_CIB_CALLDATA, call_data); } crm_log_xml_explicit(reply, "cib:reply"); return reply; } static void do_local_notify(xmlNode *notify_src, const char *client_id, bool sync_reply, bool from_peer) { int rid = 0; int call_id = 0; pcmk__client_t *client_obj = NULL; CRM_ASSERT(notify_src && client_id); crm_element_value_int(notify_src, F_CIB_CALLID, &call_id); client_obj = pcmk__find_client_by_id(client_id); if (client_obj == NULL) { crm_debug("Could not send response %d: client %s not found", call_id, client_id); return; } if (sync_reply) { if (client_obj->ipcs) { CRM_LOG_ASSERT(client_obj->request_id); rid = client_obj->request_id; client_obj->request_id = 0; crm_trace("Sending response %d to client %s%s", rid, pcmk__client_name(client_obj), (from_peer? " (originator of delegated request)" : "")); } else { crm_trace("Sending response (call %d) to client %s%s", call_id, pcmk__client_name(client_obj), (from_peer? " (originator of delegated request)" : "")); } } else { crm_trace("Sending event %d to client %s%s", call_id, pcmk__client_name(client_obj), (from_peer? " (originator of delegated request)" : "")); } switch (PCMK__CLIENT_TYPE(client_obj)) { case pcmk__client_ipc: { int rc = pcmk__ipc_send_xml(client_obj, rid, notify_src, (sync_reply? crm_ipc_flags_none : crm_ipc_server_event)); if (rc != pcmk_rc_ok) { crm_warn("%s reply to client %s failed: %s " CRM_XS " rc=%d", (sync_reply? "Synchronous" : "Asynchronous"), pcmk__client_name(client_obj), pcmk_rc_str(rc), rc); } } break; #ifdef HAVE_GNUTLS_GNUTLS_H case pcmk__client_tls: #endif case pcmk__client_tcp: pcmk__remote_send_xml(client_obj->remote, notify_src); break; default: crm_err("Unknown transport for client %s " CRM_XS " flags=%#016" PRIx64, pcmk__client_name(client_obj), client_obj->flags); } } void cib_common_callback_worker(uint32_t id, uint32_t flags, xmlNode * op_request, pcmk__client_t *cib_client, gboolean privileged) { const char *op = crm_element_value(op_request, F_CIB_OPERATION); + int call_options = cib_none; + + crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options); + + if (pcmk_is_set(call_options, cib_transaction)) { + // Append request to client's transaction + const char *call_id = crm_element_value(op_request, F_CIB_CALLID); + + int rc = based_extend_transaction(cib_client, op_request, privileged); + + xmlNode *reply = create_cib_reply(op, call_id, cib_client->id, + call_options, rc, NULL); + + if (rc != pcmk_rc_ok) { + crm_warn("Could not extend transaction for client %s: %s", + pcmk__client_name(cib_client), pcmk_rc_str(rc)); + } + + do_local_notify(reply, cib_client->id, + pcmk_is_set(call_options, cib_sync_call), false); + return; + } if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) { if (flags & crm_ipc_client_response) { xmlNode *ack = create_xml_node(NULL, __func__); crm_xml_add(ack, F_CIB_OPERATION, CRM_OP_REGISTER); crm_xml_add(ack, F_CIB_CLIENTID, cib_client->id); pcmk__ipc_send_xml(cib_client, id, ack, flags); cib_client->request_id = 0; free_xml(ack); } return; } else if (pcmk__str_eq(op, T_CIB_NOTIFY, pcmk__str_none)) { /* Update the notify filters for this client */ int on_off = 0; crm_exit_t status = CRM_EX_OK; uint64_t bit = UINT64_C(0); const char *type = crm_element_value(op_request, F_CIB_NOTIFY_TYPE); crm_element_value_int(op_request, F_CIB_NOTIFY_ACTIVATE, &on_off); crm_debug("Setting %s callbacks %s for client %s", type, (on_off? "on" : "off"), pcmk__client_name(cib_client)); if (pcmk__str_eq(type, T_CIB_POST_NOTIFY, pcmk__str_casei)) { bit = cib_notify_post; } else if (pcmk__str_eq(type, T_CIB_PRE_NOTIFY, pcmk__str_casei)) { bit = cib_notify_pre; } else if (pcmk__str_eq(type, T_CIB_UPDATE_CONFIRM, pcmk__str_casei)) { bit = cib_notify_confirm; } else if (pcmk__str_eq(type, T_CIB_DIFF_NOTIFY, pcmk__str_casei)) { bit = cib_notify_diff; } else if (pcmk__str_eq(type, T_CIB_REPLACE_NOTIFY, pcmk__str_casei)) { bit = cib_notify_replace; } else { status = CRM_EX_INVALID_PARAM; } if (bit != 0) { if (on_off) { pcmk__set_client_flags(cib_client, bit); } else { pcmk__clear_client_flags(cib_client, bit); } } pcmk__ipc_send_ack(cib_client, id, flags, "ack", NULL, status); return; } cib_process_request(op_request, privileged, cib_client); } int32_t cib_common_callback(qb_ipcs_connection_t * c, void *data, size_t size, gboolean privileged) { uint32_t id = 0; uint32_t flags = 0; int call_options = 0; pcmk__client_t *cib_client = pcmk__find_client(c); xmlNode *op_request = pcmk__client_data2xml(cib_client, data, &id, &flags); if (op_request) { crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options); } if (op_request == NULL) { crm_trace("Invalid message from %p", c); pcmk__ipc_send_ack(cib_client, id, flags, "nack", NULL, CRM_EX_PROTOCOL); return 0; } else if(cib_client == NULL) { crm_trace("Invalid client %p", c); return 0; } if (pcmk_is_set(call_options, cib_sync_call)) { CRM_LOG_ASSERT(flags & crm_ipc_client_response); CRM_LOG_ASSERT(cib_client->request_id == 0); /* This means the client has two synchronous events in-flight */ cib_client->request_id = id; /* Reply only to the last one */ } if (cib_client->name == NULL) { const char *value = crm_element_value(op_request, F_CIB_CLIENTNAME); if (value == NULL) { cib_client->name = pcmk__itoa(cib_client->pid); } else { cib_client->name = strdup(value); if (crm_is_daemon_name(value)) { pcmk__set_client_flags(cib_client, cib_is_daemon); } } } /* Allow cluster daemons more leeway before being evicted */ if (pcmk_is_set(cib_client->flags, cib_is_daemon)) { const char *qmax = cib_config_lookup("cluster-ipc-limit"); if (pcmk__set_client_queue_max(cib_client, qmax)) { crm_trace("IPC threshold for client %s[%u] is now %u", pcmk__client_name(cib_client), cib_client->pid, cib_client->queue_max); } } crm_xml_add(op_request, F_CIB_CLIENTID, cib_client->id); crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name); CRM_LOG_ASSERT(cib_client->user != NULL); pcmk__update_acl_user(op_request, F_CIB_USER, cib_client->user); cib_common_callback_worker(id, flags, op_request, cib_client, privileged); free_xml(op_request); return 0; } static uint64_t ping_seq = 0; static char *ping_digest = NULL; static bool ping_modified_since = FALSE; static gboolean cib_digester_cb(gpointer data) { if (based_is_primary) { char buffer[32]; xmlNode *ping = create_xml_node(NULL, "ping"); ping_seq++; free(ping_digest); ping_digest = NULL; ping_modified_since = FALSE; snprintf(buffer, 32, "%" PRIu64, ping_seq); crm_trace("Requesting peer digests (%s)", buffer); crm_xml_add(ping, F_TYPE, "cib"); crm_xml_add(ping, F_CIB_OPERATION, CRM_OP_PING); crm_xml_add(ping, F_CIB_PING_ID, buffer); crm_xml_add(ping, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); send_cluster_message(NULL, crm_msg_cib, ping, TRUE); free_xml(ping); } return FALSE; } static void process_ping_reply(xmlNode *reply) { uint64_t seq = 0; const char *host = crm_element_value(reply, F_ORIG); xmlNode *pong = get_message_xml(reply, F_CIB_CALLDATA); const char *seq_s = crm_element_value(pong, F_CIB_PING_ID); const char *digest = crm_element_value(pong, XML_ATTR_DIGEST); if (seq_s == NULL) { crm_debug("Ignoring ping reply with no " F_CIB_PING_ID); return; } else { long long seq_ll; if (pcmk__scan_ll(seq_s, &seq_ll, 0LL) != pcmk_rc_ok) { return; } seq = (uint64_t) seq_ll; } if(digest == NULL) { crm_trace("Ignoring ping reply %s from %s with no digest", seq_s, host); } else if(seq != ping_seq) { crm_trace("Ignoring out of sequence ping reply %s from %s", seq_s, host); } else if(ping_modified_since) { crm_trace("Ignoring ping reply %s from %s: cib updated since", seq_s, host); } else { const char *version = crm_element_value(pong, XML_ATTR_CRM_VERSION); if(ping_digest == NULL) { crm_trace("Calculating new digest"); ping_digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, version); } crm_trace("Processing ping reply %s from %s (%s)", seq_s, host, digest); if (!pcmk__str_eq(ping_digest, digest, pcmk__str_casei)) { xmlNode *remote_cib = get_message_xml(pong, F_CIB_CALLDATA); crm_notice("Local CIB %s.%s.%s.%s differs from %s: %s.%s.%s.%s %p", crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN), crm_element_value(the_cib, XML_ATTR_GENERATION), crm_element_value(the_cib, XML_ATTR_NUMUPDATES), ping_digest, host, remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION_ADMIN):"_", remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION):"_", remote_cib?crm_element_value(remote_cib, XML_ATTR_NUMUPDATES):"_", digest, remote_cib); if(remote_cib && remote_cib->children) { // Additional debug xml_calculate_changes(the_cib, remote_cib); pcmk__output_set_log_level(logger_out, LOG_INFO); pcmk__xml_show_changes(logger_out, remote_cib); crm_trace("End of differences"); } free_xml(remote_cib); sync_our_cib(reply, FALSE); } } } static void local_notify_destroy_callback(gpointer data) { cib_local_notify_t *notify = data; free_xml(notify->notify_src); free(notify->client_id); free(notify); } static void check_local_notify(int bcast_id) { cib_local_notify_t *notify = NULL; if (!local_notify_queue) { return; } notify = pcmk__intkey_table_lookup(local_notify_queue, bcast_id); if (notify) { do_local_notify(notify->notify_src, notify->client_id, notify->sync_reply, notify->from_peer); pcmk__intkey_table_remove(local_notify_queue, bcast_id); } } static void queue_local_notify(xmlNode * notify_src, const char *client_id, gboolean sync_reply, gboolean from_peer) { cib_local_notify_t *notify = calloc(1, sizeof(cib_local_notify_t)); notify->notify_src = notify_src; notify->client_id = strdup(client_id); notify->sync_reply = sync_reply; notify->from_peer = from_peer; if (!local_notify_queue) { local_notify_queue = pcmk__intkey_table(local_notify_destroy_callback); } pcmk__intkey_table_insert(local_notify_queue, cib_local_bcast_num, notify); // cppcheck doesn't know notify will get freed when hash table is destroyed // cppcheck-suppress memleak } static void parse_local_options_v1(const pcmk__client_t *cib_client, const cib_operation_t *operation, int call_options, const char *host, const char *op, gboolean *local_notify, gboolean *needs_reply, gboolean *process, gboolean *needs_forward) { if (pcmk_is_set(operation->flags, cib_op_attr_modifies) && !pcmk_is_set(call_options, cib_inhibit_bcast)) { /* we need to send an update anyway */ *needs_reply = TRUE; } else { *needs_reply = FALSE; } if (host == NULL && (call_options & cib_scope_local)) { crm_trace("Processing locally scoped %s op from client %s", op, pcmk__client_name(cib_client)); *local_notify = TRUE; } else if ((host == NULL) && based_is_primary) { crm_trace("Processing %s op locally from client %s as primary", op, pcmk__client_name(cib_client)); *local_notify = TRUE; } else if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) { crm_trace("Processing locally addressed %s op from client %s", op, pcmk__client_name(cib_client)); *local_notify = TRUE; } else if (stand_alone) { *needs_forward = FALSE; *local_notify = TRUE; *process = TRUE; } else { crm_trace("%s op from %s needs to be forwarded to client %s", op, pcmk__client_name(cib_client), pcmk__s(host, "the primary instance")); *needs_forward = TRUE; *process = FALSE; } } static void parse_local_options_v2(const pcmk__client_t *cib_client, const cib_operation_t *operation, int call_options, const char *host, const char *op, gboolean *local_notify, gboolean *needs_reply, gboolean *process, gboolean *needs_forward) { // Process locally and notify local client *process = TRUE; *needs_reply = FALSE; *local_notify = TRUE; *needs_forward = FALSE; if (pcmk_is_set(operation->flags, cib_op_attr_local)) { /* Always process locally if cib_op_attr_local is set. * * @COMPAT: Currently host is ignored. At a compatibility break, throw * an error (from cib_process_request() or earlier) if host is not NULL or * OUR_NODENAME. */ crm_trace("Processing always-local %s op from client %s", op, pcmk__client_name(cib_client)); if (!pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei|pcmk__str_null_matches)) { crm_warn("Operation '%s' is always local but its target host is " "set to '%s'", op, host); } return; } if (pcmk_is_set(operation->flags, cib_op_attr_modifies) || !pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei|pcmk__str_null_matches)) { // Forward modifying and non-local requests via cluster *process = FALSE; *needs_reply = FALSE; *local_notify = FALSE; *needs_forward = TRUE; crm_trace("%s op from %s needs to be forwarded to %s", op, pcmk__client_name(cib_client), pcmk__s(host, "all nodes")); return; } if (stand_alone) { crm_trace("Processing %s op from client %s (stand-alone)", op, pcmk__client_name(cib_client)); } else { crm_trace("Processing %saddressed %s op from client %s", ((host != NULL)? "locally " : "un"), op, pcmk__client_name(cib_client)); } } static void parse_local_options(const pcmk__client_t *cib_client, const cib_operation_t *operation, int call_options, const char *host, const char *op, gboolean *local_notify, gboolean *needs_reply, gboolean *process, gboolean *needs_forward) { + if (pcmk_is_set(call_options, cib_transaction)) { + /* Process locally and don't notify local client. + * + * Reaching cib_process_request() with cib_transaction set means we're + * committing a transaction (always local and atomic). + */ + *process = TRUE; + *needs_reply = FALSE; + *local_notify = FALSE; + *needs_forward = FALSE; + return; + } + if(cib_legacy_mode()) { parse_local_options_v1(cib_client, operation, call_options, host, op, local_notify, needs_reply, process, needs_forward); } else { parse_local_options_v2(cib_client, operation, call_options, host, op, local_notify, needs_reply, process, needs_forward); } } static gboolean parse_peer_options_v1(const cib_operation_t *operation, xmlNode *request, gboolean *local_notify, gboolean *needs_reply, gboolean *process) { const char *op = NULL; const char *host = NULL; const char *delegated = NULL; const char *originator = crm_element_value(request, F_ORIG); const char *reply_to = crm_element_value(request, F_CIB_ISREPLY); gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei); if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) { *needs_reply = FALSE; if (is_reply) { *local_notify = TRUE; crm_trace("Processing global/peer update from %s" " that originated from us", originator); } else { crm_trace("Processing global/peer update from %s", originator); } return TRUE; } op = crm_element_value(request, F_CIB_OPERATION); crm_trace("Processing %s request sent by %s", op, originator); if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) { /* Always process these */ *local_notify = FALSE; if (reply_to == NULL || is_reply) { *process = TRUE; } if (is_reply) { *needs_reply = FALSE; } return *process; } if (is_reply && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { process_ping_reply(request); return FALSE; } if (is_reply) { crm_trace("Forward reply sent from %s to local clients", originator); *process = FALSE; *needs_reply = FALSE; *local_notify = TRUE; return TRUE; } host = crm_element_value(request, F_CIB_HOST); if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) { crm_trace("Processing %s request sent to us from %s", op, originator); return TRUE; } else if(is_reply == FALSE && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { crm_trace("Processing %s request sent to %s by %s", op, host?host:"everyone", originator); *needs_reply = TRUE; return TRUE; } else if ((host == NULL) && based_is_primary) { crm_trace("Processing %s request sent to primary instance from %s", op, originator); return TRUE; } delegated = crm_element_value(request, F_CIB_DELEGATED); if (delegated != NULL) { crm_trace("Ignoring message for primary instance"); } else if (host != NULL) { /* this is for a specific instance and we're not it */ crm_trace("Ignoring msg for instance on %s", host); } else if ((reply_to == NULL) && !based_is_primary) { // This is for the primary instance, and we're not it crm_trace("Ignoring reply for primary instance"); } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) { if (reply_to != NULL) { crm_debug("Processing %s from %s", op, originator); *needs_reply = FALSE; } else { crm_debug("Processing %s reply from %s", op, originator); } return TRUE; } else { crm_err("Nothing for us to do?"); crm_log_xml_err(request, "Peer[inbound]"); } return FALSE; } static gboolean parse_peer_options_v2(const cib_operation_t *operation, xmlNode *request, gboolean *local_notify, gboolean *needs_reply, gboolean *process) { const char *host = NULL; const char *delegated = crm_element_value(request, F_CIB_DELEGATED); const char *op = crm_element_value(request, F_CIB_OPERATION); const char *originator = crm_element_value(request, F_ORIG); const char *reply_to = crm_element_value(request, F_CIB_ISREPLY); gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei); if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, pcmk__str_none)) { /* sync_our_cib() sets F_CIB_ISREPLY */ if (reply_to) { delegated = reply_to; } goto skip_is_reply; } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SYNC_TO_ALL, pcmk__str_none)) { // Nothing to do } else if (is_reply && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { process_ping_reply(request); return FALSE; } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_UPGRADE, pcmk__str_none)) { /* Only the DC (node with the oldest software) should process * this operation if F_CIB_SCHEMA_MAX is unset * * If the DC is happy it will then send out another * PCMK__CIB_REQUEST_UPGRADE which will tell all nodes to do the actual * upgrade. * * Except this time F_CIB_SCHEMA_MAX will be set which puts a * limit on how far newer nodes will go */ const char *max = crm_element_value(request, F_CIB_SCHEMA_MAX); const char *upgrade_rc = crm_element_value(request, F_CIB_UPGRADE_RC); crm_trace("Parsing %s operation%s for %s with max=%s and upgrade_rc=%s", op, (is_reply? " reply" : ""), (based_is_primary? "primary" : "secondary"), (max? max : "none"), (upgrade_rc? upgrade_rc : "none")); if (upgrade_rc != NULL) { // Our upgrade request was rejected by DC, notify clients of result crm_xml_add(request, F_CIB_RC, upgrade_rc); } else if ((max == NULL) && based_is_primary) { /* We are the DC, check if this upgrade is allowed */ goto skip_is_reply; } else if(max) { /* Ok, go ahead and upgrade to 'max' */ goto skip_is_reply; } else { // Ignore broadcast client requests when we're not DC return FALSE; } } else if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) { crm_info("Detected legacy %s global update from %s", op, originator); send_sync_request(NULL); legacy_mode = TRUE; return FALSE; } else if (is_reply && pcmk_is_set(operation->flags, cib_op_attr_modifies)) { crm_trace("Ignoring legacy %s reply sent from %s to local clients", op, originator); return FALSE; } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) { // @COMPAT: Legacy handling crm_debug("Legacy handling of %s message from %s", op, originator); *local_notify = FALSE; if (reply_to == NULL) { *process = TRUE; } return *process; } if(is_reply) { crm_trace("Handling %s reply sent from %s to local clients", op, originator); *process = FALSE; *needs_reply = FALSE; *local_notify = TRUE; return TRUE; } skip_is_reply: *process = TRUE; *needs_reply = FALSE; *local_notify = pcmk__str_eq(delegated, OUR_NODENAME, pcmk__str_casei); host = crm_element_value(request, F_CIB_HOST); if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) { crm_trace("Processing %s request sent to us from %s", op, originator); *needs_reply = TRUE; return TRUE; } else if (host != NULL) { /* this is for a specific instance and we're not it */ crm_trace("Ignoring %s operation for instance on %s", op, host); return FALSE; } else if(is_reply == FALSE && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { *needs_reply = TRUE; } crm_trace("Processing %s request sent to everyone by %s/%s on %s %s", op, crm_element_value(request, F_CIB_CLIENTNAME), crm_element_value(request, F_CIB_CALLID), originator, (*local_notify)?"(notify)":""); return TRUE; } static gboolean parse_peer_options(const cib_operation_t *operation, xmlNode *request, gboolean *local_notify, gboolean *needs_reply, gboolean *process) { /* TODO: What happens when an update comes in after node A * requests the CIB from node B, but before it gets the reply (and * sends out the replace operation) */ if(cib_legacy_mode()) { return parse_peer_options_v1(operation, request, local_notify, needs_reply, process); } else { return parse_peer_options_v2(operation, request, local_notify, needs_reply, process); } } /*! * \internal * \brief Forward a CIB request to the appropriate target host(s) * * \param[in] request CIB request to forward */ static void forward_request(xmlNode *request) { const char *op = crm_element_value(request, F_CIB_OPERATION); const char *section = crm_element_value(request, F_CIB_SECTION); const char *host = crm_element_value(request, F_CIB_HOST); const char *originator = crm_element_value(request, F_ORIG); const char *client_name = crm_element_value(request, F_CIB_CLIENTNAME); const char *call_id = crm_element_value(request, F_CIB_CALLID); int log_level = LOG_INFO; if (pcmk__str_eq(op, PCMK__CIB_REQUEST_NOOP, pcmk__str_none)) { log_level = LOG_DEBUG; } do_crm_log(log_level, "Forwarding %s operation for section %s to %s (origin=%s/%s/%s)", pcmk__s(op, "invalid"), pcmk__s(section, "all"), pcmk__s(host, (cib_legacy_mode()? "primary" : "all")), pcmk__s(originator, "local"), pcmk__s(client_name, "unspecified"), pcmk__s(call_id, "unspecified")); crm_xml_add(request, F_CIB_DELEGATED, OUR_NODENAME); send_cluster_message(((host != NULL)? crm_get_peer(0, host) : NULL), crm_msg_cib, request, FALSE); // Return the request to its original state xml_remove_prop(request, F_CIB_DELEGATED); } static gboolean send_peer_reply(xmlNode * msg, xmlNode * result_diff, const char *originator, gboolean broadcast) { CRM_ASSERT(msg != NULL); if (broadcast) { /* @COMPAT: Legacy code * * This successful call modified the CIB, and the change needs to be * broadcast (sent via cluster to all nodes). */ int diff_add_updates = 0; int diff_add_epoch = 0; int diff_add_admin_epoch = 0; int diff_del_updates = 0; int diff_del_epoch = 0; int diff_del_admin_epoch = 0; const char *digest = NULL; int format = 1; CRM_LOG_ASSERT(result_diff != NULL); digest = crm_element_value(result_diff, XML_ATTR_DIGEST); crm_element_value_int(result_diff, "format", &format); cib_diff_version_details(result_diff, &diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates, &diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates); crm_trace("Sending update diff %d.%d.%d -> %d.%d.%d %s", diff_del_admin_epoch, diff_del_epoch, diff_del_updates, diff_add_admin_epoch, diff_add_epoch, diff_add_updates, digest); crm_xml_add(msg, F_CIB_ISREPLY, originator); pcmk__xe_set_bool_attr(msg, F_CIB_GLOBAL_UPDATE, true); crm_xml_add(msg, F_CIB_OPERATION, PCMK__CIB_REQUEST_APPLY_PATCH); crm_xml_add(msg, F_CIB_USER, CRM_DAEMON_USER); if (format == 1) { CRM_ASSERT(digest != NULL); } add_message_xml(msg, F_CIB_UPDATE_DIFF, result_diff); crm_log_xml_explicit(msg, "copy"); return send_cluster_message(NULL, crm_msg_cib, msg, TRUE); } else if (originator != NULL) { /* send reply via HA to originating node */ crm_trace("Sending request result to %s only", originator); crm_xml_add(msg, F_CIB_ISREPLY, originator); return send_cluster_message(crm_get_peer(0, originator), crm_msg_cib, msg, FALSE); } return FALSE; } /*! * \internal * \brief Handle an IPC or CPG message containing a request * * \param[in,out] request Request XML * \param[in] privileged Whether privileged commands may be run * (see cib_server_ops[] definition) * \param[in] cib_client IPC client that sent request (or NULL if CPG) * * \return Legacy Pacemaker return code */ int cib_process_request(xmlNode *request, gboolean privileged, const pcmk__client_t *cib_client) { + // @TODO: Break into multiple smaller functions int call_options = 0; gboolean process = TRUE; // Whether to process request locally now gboolean is_update = TRUE; // Whether request would modify CIB gboolean needs_reply = TRUE; // Whether to build a reply gboolean local_notify = FALSE; // Whether to notify (local) requester gboolean needs_forward = FALSE; // Whether to forward request somewhere else xmlNode *op_reply = NULL; xmlNode *result_diff = NULL; int rc = pcmk_ok; const char *op = crm_element_value(request, F_CIB_OPERATION); const char *originator = crm_element_value(request, F_ORIG); const char *host = crm_element_value(request, F_CIB_HOST); const char *target = NULL; const char *call_id = crm_element_value(request, F_CIB_CALLID); const char *client_id = crm_element_value(request, F_CIB_CLIENTID); const char *client_name = crm_element_value(request, F_CIB_CLIENTNAME); const char *reply_to = crm_element_value(request, F_CIB_ISREPLY); const cib_operation_t *operation = NULL; crm_element_value_int(request, F_CIB_CALLOPTS, &call_options); if ((host != NULL) && (*host == '\0')) { host = NULL; } + // @TODO: Improve trace messages. Target is accurate only for legacy mode. if (host) { target = host; } else if (call_options & cib_scope_local) { target = "local host"; } else { target = "primary"; } if (cib_client == NULL) { crm_trace("Processing peer %s operation from %s/%s on %s intended for %s (reply=%s)", op, client_name, call_id, originator, target, reply_to); } else { crm_xml_add(request, F_ORIG, OUR_NODENAME); crm_trace("Processing local %s operation from %s/%s intended for %s", op, client_name, call_id, target); } rc = cib_get_operation(op, &operation); if (rc != pcmk_ok) { /* TODO: construct error reply? */ crm_err("Pre-processing of command failed: %s", pcmk_strerror(rc)); return rc; } + if (pcmk_is_set(call_options, cib_transaction)) { + /* We're committing a transaction now, processing each request. If + * cib_op_attr_transaction is not set, based_extend_transaction() should + * have blocked the request from being added to the transaction. + */ + CRM_CHECK(pcmk_is_set(operation->flags, cib_op_attr_transaction), + return -EOPNOTSUPP); + } + if (cib_client != NULL) { parse_local_options(cib_client, operation, call_options, host, op, &local_notify, &needs_reply, &process, &needs_forward); } else if (!parse_peer_options(operation, request, &local_notify, &needs_reply, &process)) { return rc; } is_update = pcmk_is_set(operation->flags, cib_op_attr_modifies); if (pcmk_is_set(call_options, cib_discard_reply)) { /* If the request will modify the CIB, and we are in legacy mode, we * need to build a reply so we can broadcast a diff, even if the * requester doesn't want one. */ needs_reply = is_update && cib_legacy_mode(); local_notify = FALSE; crm_trace("Client is not interested in the reply"); } if (needs_forward) { forward_request(request); return rc; } if (cib_status != pcmk_ok) { rc = cib_status; crm_err("Operation ignored, cluster configuration is invalid." " Please repair and restart: %s", pcmk_strerror(cib_status)); op_reply = create_cib_reply(op, call_id, client_id, call_options, rc, the_cib); } else if (process) { time_t finished = 0; time_t now = time(NULL); int level = LOG_INFO; const char *section = crm_element_value(request, F_CIB_SECTION); rc = cib_process_command(request, operation, &op_reply, &result_diff, privileged); if (!is_update) { level = LOG_TRACE; } else if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) { switch (rc) { case pcmk_ok: level = LOG_INFO; break; case -pcmk_err_old_data: case -pcmk_err_diff_resync: case -pcmk_err_diff_failed: level = LOG_TRACE; break; default: level = LOG_ERR; } } else if (rc != pcmk_ok) { level = LOG_WARNING; } do_crm_log(level, "Completed %s operation for section %s: %s (rc=%d, origin=%s/%s/%s, version=%s.%s.%s)", op, section ? section : "'all'", pcmk_strerror(rc), rc, originator ? originator : "local", client_name, call_id, the_cib ? crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN) : "0", the_cib ? crm_element_value(the_cib, XML_ATTR_GENERATION) : "0", the_cib ? crm_element_value(the_cib, XML_ATTR_NUMUPDATES) : "0"); finished = time(NULL); if ((finished - now) > 3) { crm_trace("%s operation took %lds to complete", op, (long)(finished - now)); crm_write_blackbox(0, NULL); } if (op_reply == NULL && (needs_reply || local_notify)) { crm_err("Unexpected NULL reply to message"); crm_log_xml_err(request, "null reply"); needs_reply = FALSE; local_notify = FALSE; } } if (is_update && !cib_legacy_mode()) { crm_trace("Completed pre-sync update from %s/%s/%s%s", originator ? originator : "local", client_name, call_id, local_notify?" with local notification":""); } else if (!needs_reply || stand_alone) { // This was a non-originating secondary update crm_trace("Completed update as secondary"); } else if (cib_legacy_mode() && rc == pcmk_ok && result_diff != NULL && !(call_options & cib_inhibit_bcast)) { gboolean broadcast = FALSE; cib_local_bcast_num++; crm_xml_add_int(request, F_CIB_LOCAL_NOTIFY_ID, cib_local_bcast_num); broadcast = send_peer_reply(request, result_diff, originator, TRUE); if (broadcast && client_id && local_notify && op_reply) { /* If we have been asked to sync the reply, * and a bcast msg has gone out, we queue the local notify * until we know the bcast message has been received */ local_notify = FALSE; crm_trace("Queuing local %ssync notification for %s", (call_options & cib_sync_call) ? "" : "a-", client_id); queue_local_notify(op_reply, client_id, pcmk_is_set(call_options, cib_sync_call), (cib_client == NULL)); op_reply = NULL; /* the reply is queued, so don't free here */ } } else if ((cib_client == NULL) && !pcmk_is_set(call_options, cib_discard_reply)) { if (is_update == FALSE || result_diff == NULL) { crm_trace("Request not broadcast: R/O call"); } else if (call_options & cib_inhibit_bcast) { crm_trace("Request not broadcast: inhibited"); } else if (rc != pcmk_ok) { crm_trace("Request not broadcast: call failed: %s", pcmk_strerror(rc)); } else { crm_trace("Directing reply to %s", originator); } send_peer_reply(op_reply, result_diff, originator, FALSE); } if (local_notify && client_id) { crm_trace("Performing local %ssync notification for %s", (pcmk_is_set(call_options, cib_sync_call)? "" : "a"), client_id); if (process == FALSE) { do_local_notify(request, client_id, pcmk_is_set(call_options, cib_sync_call), (cib_client == NULL)); } else { do_local_notify(op_reply, client_id, pcmk_is_set(call_options, cib_sync_call), (cib_client == NULL)); } } free_xml(op_reply); free_xml(result_diff); return rc; } static char * calculate_section_digest(const char *xpath, xmlNode * xml_obj) { xmlNode *xml_section = NULL; if (xml_obj == NULL) { return NULL; } xml_section = get_xpath_object(xpath, xml_obj, LOG_TRACE); if (xml_section == NULL) { return NULL; } return calculate_xml_versioned_digest(xml_section, FALSE, TRUE, CRM_FEATURE_SET); } #define XPATH_CONFIG "/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION #define XPATH_NODES XPATH_CONFIG "/" XML_CIB_TAG_NODES #define XPATH_ALERTS XPATH_CONFIG "/" XML_CIB_TAG_ALERTS #define XPATH_STATUS "/" XML_TAG_CIB "/" XML_CIB_TAG_STATUS /*! * \internal * \brief Calculate digests of CIB sections * * \param[in] cib CIB to get digests from * \param[out] digests Where to store digests * * \note The caller is responsible for freeing the output argument's members * using \p free_digests(). */ static void get_digests(xmlNode *cib, struct digest_data *digests) { digests->nodes = calculate_section_digest(XPATH_NODES, cib); digests->alerts = calculate_section_digest(XPATH_ALERTS, cib); digests->status = calculate_section_digest(XPATH_STATUS, cib); } /*! * \internal * \brief Determine which CIB sections were changed by an operation * * \param[in] before Digests before the operation * \param[in] after Digests after the operation * * \return Group of enum cib_change_section_info flags indicating which * sections have changed */ static uint32_t get_change_sections(const struct digest_data *before, const struct digest_data *after) { uint32_t change_sections = cib_change_section_none; if (!pcmk__str_eq(before->nodes, after->nodes, pcmk__str_none)) { pcmk__set_change_section(change_sections, cib_change_section_nodes); } if (!pcmk__str_eq(before->alerts, after->alerts, pcmk__str_none)) { pcmk__set_change_section(change_sections, cib_change_section_alerts); } if (!pcmk__str_eq(before->status, after->status, pcmk__str_none)) { pcmk__set_change_section(change_sections, cib_change_section_status); } return change_sections; } /*! * \internal * \brief Check whether a CIB replace notification should be sent * * A CIB replace notification should be sent after an operation if the operation * is of an appropriate type, notifications are not disabled, and any of certain * CIB sections has changed. * * \param[in] before_digests Digests before operation * \param[in] after_cib Result CIB after operation * \param[in] operation CIB operation * \param[in] call_options Group of enum cib_call_options flags * \param[out] change_sections Group of enum cib_change_section_info * flags indicating which sections have changed * * \return \p true if a replace notification should be sent, or \p false * otherwise */ static bool should_replace_notify(const struct digest_data *before_digests, xmlNode *after_cib, const cib_operation_t *operation, int call_options, uint32_t *change_sections) { struct digest_data after_digests = { 0, }; *change_sections = cib_change_section_none; if (!pcmk_is_set(operation->flags, cib_op_attr_replaces) || pcmk_is_set(call_options, cib_inhibit_notify)) { return false; } get_digests(after_cib, &after_digests); crm_trace("after-digest %s:%s:%s", pcmk__s(after_digests.nodes, "(null)"), pcmk__s(after_digests.alerts, "(null)"), pcmk__s(after_digests.status, "(null)")); *change_sections = get_change_sections(before_digests, &after_digests); free_digests(&after_digests); return (*change_sections != cib_change_section_none); } // v1 and v2 patch formats #define XPATH_CONFIG_CHANGE \ "//" XML_CIB_TAG_CRMCONFIG " | " \ "//" XML_DIFF_CHANGE \ "[contains(@" XML_DIFF_PATH ",'/" XML_CIB_TAG_CRMCONFIG "/')]" static bool contains_config_change(xmlNode *diff) { bool changed = false; if (diff) { xmlXPathObject *xpathObj = xpath_search(diff, XPATH_CONFIG_CHANGE); if (numXpathResults(xpathObj) > 0) { changed = true; } freeXpathObject(xpathObj); } return changed; } static int cib_process_command(xmlNode *request, const cib_operation_t *operation, xmlNode **reply, xmlNode **cib_diff, bool privileged) { xmlNode *input = NULL; xmlNode *output = NULL; xmlNode *result_cib = NULL; int call_options = 0; const char *op = NULL; const char *section = NULL; const char *call_id = crm_element_value(request, F_CIB_CALLID); const char *client_id = crm_element_value(request, F_CIB_CLIENTID); const char *client_name = crm_element_value(request, F_CIB_CLIENTNAME); const char *origin = crm_element_value(request, F_ORIG); int rc = pcmk_ok; gboolean config_changed = FALSE; gboolean manage_counters = TRUE; static mainloop_timer_t *digest_timer = NULL; struct digest_data before_digests = { 0, }; CRM_ASSERT(cib_status == pcmk_ok); if(digest_timer == NULL) { digest_timer = mainloop_timer_add("digester", 5000, FALSE, cib_digester_cb, NULL); } *reply = NULL; *cib_diff = NULL; /* Start processing the request... */ op = crm_element_value(request, F_CIB_OPERATION); crm_element_value_int(request, F_CIB_CALLOPTS, &call_options); if (!privileged && pcmk_is_set(operation->flags, cib_op_attr_privileged)) { rc = -EACCES; crm_trace("Failed due to lack of privileges: %s", pcmk_strerror(rc)); goto done; } rc = operation->prepare(request, &input, §ion); if (rc != pcmk_ok) { crm_trace("Failed to prepare operation: %s", pcmk_strerror(rc)); goto done; } if (!pcmk_is_set(operation->flags, cib_op_attr_modifies)) { rc = cib_perform_op(op, call_options, operation->fn, TRUE, section, request, input, FALSE, &config_changed, &the_cib, &result_cib, NULL, &output); CRM_CHECK(result_cib == NULL, free_xml(result_cib)); goto done; } /* @COMPAT: Handle a valid write action (legacy) * * @TODO: Re-evaluate whether this is all truly legacy. The cib_force_diff * portion is. However, F_CIB_GLOBAL_UPDATE may be set by a sync operation * even in non-legacy mode, and manage_counters tells xml_create_patchset() * whether to update version/epoch info. */ if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) { manage_counters = FALSE; cib__set_call_options(call_options, "call", cib_force_diff); crm_trace("Global update detected"); CRM_LOG_ASSERT(pcmk__str_any_of(op, PCMK__CIB_REQUEST_APPLY_PATCH, PCMK__CIB_REQUEST_REPLACE, NULL)); } ping_modified_since = TRUE; if (pcmk_is_set(call_options, cib_inhibit_bcast)) { crm_trace("Skipping update: inhibit broadcast"); manage_counters = FALSE; } if (!pcmk_is_set(call_options, cib_dryrun) - && pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) { - // Copying large CIBs accounts for a huge percentage of our CIB usage + && !pcmk__str_eq(op, PCMK__CIB_REQUEST_COMMIT_TRANSACT, pcmk__str_none) + && (pcmk_is_set(call_options, cib_transaction) + || pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_none))) { + /* Dry runs and commit-transaction requests must always make a copy. + * + * Requests in a transaction: we made a working CIB copy earlier in the + * call chain, and we need to accumulate changes in it. + * + * Status updates: copying large CIBs accounts for a huge percentage of + * our CIB usage, and this avoids some of it. + */ cib__set_call_options(call_options, "call", cib_zero_copy); } else { cib__clear_call_options(call_options, "call", cib_zero_copy); } // Calculate the digests of relevant sections before the operation if (pcmk_is_set(operation->flags, cib_op_attr_replaces) - && !pcmk_any_flags_set(call_options, cib_dryrun|cib_inhibit_notify)) { + && !pcmk_any_flags_set(call_options, + cib_dryrun|cib_inhibit_notify|cib_transaction)) { get_digests(the_cib, &before_digests); crm_trace("before-digest %s:%s:%s", pcmk__s(before_digests.nodes, "(null)"), pcmk__s(before_digests.alerts, "(null)"), pcmk__s(before_digests.status, "(null)")); } // result_cib must not be modified after cib_perform_op() returns rc = cib_perform_op(op, call_options, operation->fn, FALSE, section, request, input, manage_counters, &config_changed, &the_cib, &result_cib, cib_diff, &output); // @COMPAT: Legacy code if (!manage_counters) { int format = 1; // If the diff is NULL at this point, it's because nothing changed if (*cib_diff != NULL) { crm_element_value_int(*cib_diff, "format", &format); } if (format == 1) { config_changed = cib__config_changed_v1(NULL, NULL, cib_diff); } } /* Always write to disk for successful ops with the flag set. This also * negates the need to detect ordering changes. */ if ((rc == pcmk_ok) && pcmk_is_set(operation->flags, cib_op_attr_writes_through)) { config_changed = TRUE; } - if (rc == pcmk_ok && !pcmk_is_set(call_options, cib_dryrun)) { + if ((rc == pcmk_ok) + && !pcmk_any_flags_set(call_options, cib_dryrun|cib_transaction)) { + uint32_t change_sections = cib_change_section_none; crm_trace("Activating %s->%s%s%s", crm_element_value(the_cib, XML_ATTR_NUMUPDATES), crm_element_value(result_cib, XML_ATTR_NUMUPDATES), (pcmk_is_set(call_options, cib_zero_copy)? " zero-copy" : ""), (config_changed? " changed" : "")); if (!pcmk_is_set(call_options, cib_zero_copy)) { rc = activateCibXml(result_cib, config_changed, op); if (rc != pcmk_ok) { crm_err("Failed to activate new CIB: %s", pcmk_strerror(rc)); } } if ((rc == pcmk_ok) && contains_config_change(*cib_diff)) { cib_read_config(config_hash, result_cib); } if (should_replace_notify(&before_digests, result_cib, operation, call_options, &change_sections)) { // @TODO: Should update argument be result_cib instead of the_cib? cib_replace_notify(op, rc, call_id, client_id, client_name, origin, the_cib, *cib_diff, change_sections); } + /* Commit-transaction is special. We process it and activate the result + * only locally, but we still need to sync the updated CIB to all nodes. + * However, if we run the sync within cib_process_commit_transaction(), + * it will be rejected locally due to an older epoch in the replacement + * CIB. cib_perform_op() updates the epoch via xml_create_patchset() + * after cib_process_commit_transaction() has already returned. + */ + if (pcmk__str_eq(op, PCMK__CIB_REQUEST_COMMIT_TRANSACT, + pcmk__str_none)) { + sync_our_cib(request, TRUE); + } + mainloop_timer_stop(digest_timer); mainloop_timer_start(digest_timer); } else if (rc == -pcmk_err_schema_validation) { CRM_ASSERT(!pcmk_is_set(call_options, cib_zero_copy)); if (output != NULL) { crm_log_xml_info(output, "cib:output"); free_xml(output); } output = result_cib; } else { crm_trace("Not activating %d %d %s", rc, pcmk_is_set(call_options, cib_dryrun), crm_element_value(result_cib, XML_ATTR_NUMUPDATES)); if (!pcmk_is_set(call_options, cib_zero_copy)) { free_xml(result_cib); } } - if ((call_options & (cib_inhibit_notify|cib_dryrun)) == 0) { + if (!pcmk_any_flags_set(call_options, + cib_dryrun|cib_inhibit_notify|cib_transaction)) { crm_trace("Sending notifications %d", pcmk_is_set(call_options, cib_dryrun)); cib_diff_notify(op, rc, call_id, client_id, client_name, origin, input, *cib_diff); } pcmk__output_set_log_level(logger_out, LOG_TRACE); logger_out->message(logger_out, "xml-patchset", *cib_diff); done: if (!pcmk_is_set(call_options, cib_discard_reply) || cib_legacy_mode()) { *reply = create_cib_reply(op, call_id, client_id, call_options, rc, output); } crm_trace("cleanup"); operation->cleanup(call_options, &input, &output); free_digests(&before_digests); crm_trace("done"); return rc; } void cib_peer_callback(xmlNode * msg, void *private_data) { const char *reason = NULL; const char *originator = crm_element_value(msg, F_ORIG); if (cib_legacy_mode() && pcmk__str_eq(originator, OUR_NODENAME, pcmk__str_casei|pcmk__str_null_matches)) { /* message is from ourselves */ int bcast_id = 0; if (!(crm_element_value_int(msg, F_CIB_LOCAL_NOTIFY_ID, &bcast_id))) { check_local_notify(bcast_id); } return; } else if (crm_peer_cache == NULL) { reason = "membership not established"; goto bail; } if (crm_element_value(msg, F_CIB_CLIENTNAME) == NULL) { crm_xml_add(msg, F_CIB_CLIENTNAME, originator); } /* crm_log_xml_trace(msg, "Peer[inbound]"); */ cib_process_request(msg, TRUE, NULL); return; bail: if (reason) { const char *seq = crm_element_value(msg, F_SEQ); const char *op = crm_element_value(msg, F_CIB_OPERATION); crm_warn("Discarding %s message (%s) from %s: %s", op, seq, originator, reason); } } static gboolean cib_force_exit(gpointer data) { crm_notice("Forcing exit!"); terminate_cib(__func__, CRM_EX_ERROR); return FALSE; } static void disconnect_remote_client(gpointer key, gpointer value, gpointer user_data) { pcmk__client_t *a_client = value; crm_err("Can't disconnect client %s: Not implemented", pcmk__client_name(a_client)); } static void initiate_exit(void) { int active = 0; xmlNode *leaving = NULL; active = crm_active_peers(); if (active < 2) { terminate_cib(__func__, 0); return; } crm_info("Sending disconnect notification to %d peers...", active); leaving = create_xml_node(NULL, "exit-notification"); crm_xml_add(leaving, F_TYPE, "cib"); crm_xml_add(leaving, F_CIB_OPERATION, PCMK__CIB_REQUEST_SHUTDOWN); send_cluster_message(NULL, crm_msg_cib, leaving, TRUE); free_xml(leaving); g_timeout_add(EXIT_ESCALATION_MS, cib_force_exit, NULL); } void cib_shutdown(int nsig) { struct qb_ipcs_stats srv_stats; if (cib_shutdown_flag == FALSE) { int disconnects = 0; qb_ipcs_connection_t *c = NULL; cib_shutdown_flag = TRUE; c = qb_ipcs_connection_first_get(ipcs_rw); while (c != NULL) { qb_ipcs_connection_t *last = c; c = qb_ipcs_connection_next_get(ipcs_rw, last); crm_debug("Disconnecting r/w client %p...", last); qb_ipcs_disconnect(last); qb_ipcs_connection_unref(last); disconnects++; } c = qb_ipcs_connection_first_get(ipcs_ro); while (c != NULL) { qb_ipcs_connection_t *last = c; c = qb_ipcs_connection_next_get(ipcs_ro, last); crm_debug("Disconnecting r/o client %p...", last); qb_ipcs_disconnect(last); qb_ipcs_connection_unref(last); disconnects++; } c = qb_ipcs_connection_first_get(ipcs_shm); while (c != NULL) { qb_ipcs_connection_t *last = c; c = qb_ipcs_connection_next_get(ipcs_shm, last); crm_debug("Disconnecting non-blocking r/w client %p...", last); qb_ipcs_disconnect(last); qb_ipcs_connection_unref(last); disconnects++; } disconnects += pcmk__ipc_client_count(); crm_debug("Disconnecting %d remote clients", pcmk__ipc_client_count()); pcmk__foreach_ipc_client(disconnect_remote_client, NULL); crm_info("Disconnected %d clients", disconnects); } qb_ipcs_stats_get(ipcs_rw, &srv_stats, QB_FALSE); if (pcmk__ipc_client_count() == 0) { crm_info("All clients disconnected (%d)", srv_stats.active_connections); initiate_exit(); } else { crm_info("Waiting on %d clients to disconnect (%d)", pcmk__ipc_client_count(), srv_stats.active_connections); } } extern int remote_fd; extern int remote_tls_fd; /*! * \internal * \brief Close remote sockets, free the global CIB and quit * * \param[in] caller Name of calling function (for log message) * \param[in] fast If -1, skip disconnect; if positive, exit that */ void terminate_cib(const char *caller, int fast) { crm_info("%s: Exiting%s...", caller, (fast > 0)? " fast" : mainloop ? " from mainloop" : ""); if (remote_fd > 0) { close(remote_fd); remote_fd = 0; } if (remote_tls_fd > 0) { close(remote_tls_fd); remote_tls_fd = 0; } uninitializeCib(); if (logger_out != NULL) { logger_out->finish(logger_out, CRM_EX_OK, true, NULL); pcmk__output_free(logger_out); logger_out = NULL; } if (fast > 0) { /* Quit fast on error */ pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); crm_exit(fast); } else if ((mainloop != NULL) && g_main_loop_is_running(mainloop)) { /* Quit via returning from the main loop. If fast == -1, we skip the * disconnect here, and it will be done when the main loop returns * (this allows the peer status callback to avoid messing with the * peer caches). */ if (fast == 0) { crm_cluster_disconnect(crm_cluster); } g_main_loop_quit(mainloop); } else { /* Quit via clean exit. Even the peer status callback can disconnect * here, because we're not returning control to the caller. */ crm_cluster_disconnect(crm_cluster); pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); crm_exit(CRM_EX_OK); } } diff --git a/daemons/based/based_common.c b/daemons/based/based_common.c index a263b7f06a..0bac0e016d 100644 --- a/daemons/based/based_common.c +++ b/daemons/based/based_common.c @@ -1,263 +1,294 @@ /* * Copyright 2008-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include gboolean stand_alone = FALSE; extern int cib_perform_command(xmlNode * request, xmlNode ** reply, xmlNode ** cib_diff, gboolean privileged); static xmlNode * cib_prepare_common(xmlNode * root, const char *section) { xmlNode *data = NULL; /* extract the CIB from the fragment */ if (root == NULL) { return NULL; } else if (pcmk__strcase_any_of(crm_element_name(root), XML_TAG_FRAGMENT, F_CRM_DATA, F_CIB_CALLDATA, NULL)) { data = first_named_child(root, XML_TAG_CIB); } else { data = root; } /* grab the section specified for the command */ if (section != NULL && data != NULL && pcmk__str_eq(crm_element_name(data), XML_TAG_CIB, pcmk__str_none)) { data = pcmk_find_cib_element(data, section); } /* crm_log_xml_trace(root, "cib:input"); */ return data; } static int cib_prepare_none(xmlNode * request, xmlNode ** data, const char **section) { *data = NULL; *section = crm_element_value(request, F_CIB_SECTION); return pcmk_ok; } static int cib_prepare_data(xmlNode * request, xmlNode ** data, const char **section) { xmlNode *input_fragment = get_message_xml(request, F_CIB_CALLDATA); *section = crm_element_value(request, F_CIB_SECTION); *data = cib_prepare_common(input_fragment, *section); /* crm_log_xml_debug(*data, "data"); */ return pcmk_ok; } static int cib_prepare_sync(xmlNode * request, xmlNode ** data, const char **section) { *data = NULL; *section = crm_element_value(request, F_CIB_SECTION); return pcmk_ok; } static int cib_prepare_diff(xmlNode * request, xmlNode ** data, const char **section) { xmlNode *input_fragment = NULL; *data = NULL; *section = NULL; if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) { input_fragment = get_message_xml(request, F_CIB_UPDATE_DIFF); } else { input_fragment = get_message_xml(request, F_CIB_CALLDATA); } CRM_CHECK(input_fragment != NULL, crm_log_xml_warn(request, "no input")); *data = cib_prepare_common(input_fragment, NULL); return pcmk_ok; } static int cib_cleanup_query(int options, xmlNode ** data, xmlNode ** output) { CRM_LOG_ASSERT(*data == NULL); if (*output != the_cib) { free_xml(*output); } return pcmk_ok; } static int cib_cleanup_data(int options, xmlNode ** data, xmlNode ** output) { free_xml(*output); *data = NULL; return pcmk_ok; } static int cib_cleanup_output(int options, xmlNode ** data, xmlNode ** output) { free_xml(*output); return pcmk_ok; } static int cib_cleanup_none(int options, xmlNode ** data, xmlNode ** output) { CRM_LOG_ASSERT(*data == NULL); CRM_LOG_ASSERT(*output == NULL); return pcmk_ok; } static const cib_operation_t cib_server_ops[] = { { PCMK__CIB_REQUEST_QUERY, cib_op_attr_none, cib_prepare_none, cib_cleanup_query, cib_process_query }, { PCMK__CIB_REQUEST_MODIFY, - cib_op_attr_modifies|cib_op_attr_privileged, + cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_transaction, cib_prepare_data, cib_cleanup_data, cib_process_modify }, { PCMK__CIB_REQUEST_APPLY_PATCH, - cib_op_attr_modifies|cib_op_attr_privileged, + cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_transaction, cib_prepare_diff, cib_cleanup_data, cib_server_process_diff }, { PCMK__CIB_REQUEST_REPLACE, cib_op_attr_modifies |cib_op_attr_privileged |cib_op_attr_replaces - |cib_op_attr_writes_through, + |cib_op_attr_writes_through + |cib_op_attr_transaction, cib_prepare_data, cib_cleanup_data, cib_process_replace_svr }, { PCMK__CIB_REQUEST_CREATE, - cib_op_attr_modifies|cib_op_attr_privileged, + cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_transaction, cib_prepare_data, cib_cleanup_data, cib_process_create }, { PCMK__CIB_REQUEST_DELETE, - cib_op_attr_modifies|cib_op_attr_privileged, + cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_transaction, cib_prepare_data, cib_cleanup_data, cib_process_delete }, { PCMK__CIB_REQUEST_SYNC_TO_ALL, cib_op_attr_privileged, cib_prepare_sync, cib_cleanup_none, cib_process_sync }, { PCMK__CIB_REQUEST_BUMP, - cib_op_attr_modifies|cib_op_attr_privileged, + cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_transaction, cib_prepare_none, cib_cleanup_output, cib_process_bump }, { PCMK__CIB_REQUEST_ERASE, - cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_replaces, + cib_op_attr_modifies + |cib_op_attr_privileged + |cib_op_attr_replaces + |cib_op_attr_transaction, cib_prepare_none, cib_cleanup_output, cib_process_erase }, { PCMK__CIB_REQUEST_NOOP, cib_op_attr_none, cib_prepare_none, cib_cleanup_none, cib_process_noop }, { PCMK__CIB_REQUEST_ABS_DELETE, cib_op_attr_modifies|cib_op_attr_privileged, cib_prepare_data, cib_cleanup_data, cib_process_delete_absolute }, { PCMK__CIB_REQUEST_UPGRADE, - cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_writes_through, + cib_op_attr_modifies + |cib_op_attr_privileged + |cib_op_attr_writes_through + |cib_op_attr_transaction, cib_prepare_none, cib_cleanup_output, cib_process_upgrade_server }, { PCMK__CIB_REQUEST_SECONDARY, cib_op_attr_privileged|cib_op_attr_local, cib_prepare_none, cib_cleanup_none, cib_process_readwrite }, { PCMK__CIB_REQUEST_SYNC_TO_ONE, cib_op_attr_privileged, cib_prepare_sync, cib_cleanup_none, cib_process_sync_one }, { // @COMPAT: Drop cib_op_attr_modifies when we drop legacy mode support PCMK__CIB_REQUEST_PRIMARY, cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_local, cib_prepare_data, cib_cleanup_data, cib_process_readwrite }, { PCMK__CIB_REQUEST_IS_PRIMARY, cib_op_attr_privileged, cib_prepare_none, cib_cleanup_none, cib_process_readwrite }, { PCMK__CIB_REQUEST_SHUTDOWN, cib_op_attr_privileged, cib_prepare_sync, cib_cleanup_none, cib_process_shutdown_req }, { CRM_OP_PING, cib_op_attr_none, cib_prepare_none, cib_cleanup_output, cib_process_ping }, + + /* PCMK__CIB_REQUEST_*_TRANSACT requests must be processed locally because + * they depend on the client table. Requests that manage transactions on + * other nodes would likely be problematic in many other ways as well. + */ + { + PCMK__CIB_REQUEST_INIT_TRANSACT, + cib_op_attr_privileged|cib_op_attr_local, + cib_prepare_none, cib_cleanup_none, cib_process_init_transaction, + }, + { + PCMK__CIB_REQUEST_COMMIT_TRANSACT, + cib_op_attr_modifies + |cib_op_attr_privileged + |cib_op_attr_local + |cib_op_attr_replaces + |cib_op_attr_writes_through, + cib_prepare_none, cib_cleanup_none, cib_process_commit_transaction, + }, + { + PCMK__CIB_REQUEST_DISCARD_TRANSACT, + cib_op_attr_privileged|cib_op_attr_local, + cib_prepare_none, cib_cleanup_none, cib_process_discard_transaction, + }, }; int cib_get_operation(const char *op, const cib_operation_t **operation) { static GHashTable *operation_hash = NULL; CRM_ASSERT((op != NULL) && (operation != NULL)); if (operation_hash == NULL) { operation_hash = pcmk__strkey_table(NULL, NULL); for (int lpc = 0; lpc < PCMK__NELEM(cib_server_ops); lpc++) { const cib_operation_t *oper = &(cib_server_ops[lpc]); g_hash_table_insert(operation_hash, (gpointer) oper->name, (gpointer) oper); } } *operation = g_hash_table_lookup(operation_hash, op); if (*operation == NULL) { crm_err("Operation %s is not valid", op); return -EINVAL; } return pcmk_ok; } diff --git a/daemons/based/based_messages.c b/daemons/based/based_messages.c index c7f693509f..3c9e68aad3 100644 --- a/daemons/based/based_messages.c +++ b/daemons/based/based_messages.c @@ -1,451 +1,582 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Maximum number of diffs to ignore while waiting for a resync */ #define MAX_DIFF_RETRY 5 bool based_is_primary = false; xmlNode *the_cib = NULL; int cib_process_shutdown_req(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { const char *host = crm_element_value(req, F_ORIG); *answer = NULL; if (crm_element_value(req, F_CIB_ISREPLY) == NULL) { crm_info("Peer %s is requesting to shut down", host); return pcmk_ok; } if (cib_shutdown_flag == FALSE) { crm_err("Peer %s mistakenly thinks we wanted to shut down", host); return -EINVAL; } crm_info("Peer %s has acknowledged our shutdown request", host); terminate_cib(__func__, 0); return pcmk_ok; } // @COMPAT: Remove when PCMK__CIB_REQUEST_NOOP is removed int cib_process_noop(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer) { crm_trace("Processing \"%s\" event", op); *answer = NULL; return pcmk_ok; } int cib_process_readwrite(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int result = pcmk_ok; crm_trace("Processing \"%s\" event", op); if (pcmk__str_eq(op, PCMK__CIB_REQUEST_IS_PRIMARY, pcmk__str_none)) { if (based_is_primary) { result = pcmk_ok; } else { result = -EPERM; } return result; } if (pcmk__str_eq(op, PCMK__CIB_REQUEST_PRIMARY, pcmk__str_none)) { if (!based_is_primary) { crm_info("We are now in R/W mode"); based_is_primary = true; } else { crm_debug("We are still in R/W mode"); } } else if (based_is_primary) { crm_info("We are now in R/O mode"); based_is_primary = false; } return result; } /* Set to 1 when a sync is requested, incremented when a diff is ignored, * reset to 0 when a sync is received */ static int sync_in_progress = 0; void send_sync_request(const char *host) { xmlNode *sync_me = create_xml_node(NULL, "sync-me"); crm_info("Requesting re-sync from %s", (host? host : "all peers")); sync_in_progress = 1; crm_xml_add(sync_me, F_TYPE, "cib"); crm_xml_add(sync_me, F_CIB_OPERATION, PCMK__CIB_REQUEST_SYNC_TO_ONE); crm_xml_add(sync_me, F_CIB_DELEGATED, stand_alone? "localhost" : crm_cluster->uname); send_cluster_message(host ? crm_get_peer(0, host) : NULL, crm_msg_cib, sync_me, FALSE); free_xml(sync_me); } int cib_process_ping(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { const char *host = crm_element_value(req, F_ORIG); const char *seq = crm_element_value(req, F_CIB_PING_ID); char *digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, CRM_FEATURE_SET); crm_trace("Processing \"%s\" event %s from %s", op, seq, host); *answer = create_xml_node(NULL, XML_CRM_TAG_PING); crm_xml_add(*answer, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); crm_xml_add(*answer, XML_ATTR_DIGEST, digest); crm_xml_add(*answer, F_CIB_PING_ID, seq); pcmk__if_tracing( { // Append additional detail so the receiver can log the differences add_message_xml(*answer, F_CIB_CALLDATA, the_cib); }, { // Always include at least the version details const char *tag = TYPE(the_cib); xmlNode *shallow = create_xml_node(NULL, tag); copy_in_properties(shallow, the_cib); add_message_xml(*answer, F_CIB_CALLDATA, shallow); free_xml(shallow); } ); crm_info("Reporting our current digest to %s: %s for %s.%s.%s", host, digest, crm_element_value(existing_cib, XML_ATTR_GENERATION_ADMIN), crm_element_value(existing_cib, XML_ATTR_GENERATION), crm_element_value(existing_cib, XML_ATTR_NUMUPDATES)); free(digest); return pcmk_ok; } int cib_process_sync(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { return sync_our_cib(req, TRUE); } int cib_process_upgrade_server(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int rc = pcmk_ok; *answer = NULL; if(crm_element_value(req, F_CIB_SCHEMA_MAX)) { /* The originator of an upgrade request sends it to the DC, without * F_CIB_SCHEMA_MAX. If an upgrade is needed, the DC re-broadcasts the * request with F_CIB_SCHEMA_MAX, and each node performs the upgrade * (and notifies its local clients) here. */ return cib_process_upgrade( op, options, section, req, input, existing_cib, result_cib, answer); } else { int new_version = 0; int current_version = 0; xmlNode *scratch = copy_xml(existing_cib); const char *host = crm_element_value(req, F_ORIG); const char *value = crm_element_value(existing_cib, XML_ATTR_VALIDATION); const char *client_id = crm_element_value(req, F_CIB_CLIENTID); const char *call_opts = crm_element_value(req, F_CIB_CALLOPTS); const char *call_id = crm_element_value(req, F_CIB_CALLID); crm_trace("Processing \"%s\" event", op); if (value != NULL) { current_version = get_schema_version(value); } rc = update_validation(&scratch, &new_version, 0, TRUE, TRUE); if (new_version > current_version) { xmlNode *up = create_xml_node(NULL, __func__); rc = pcmk_ok; crm_notice("Upgrade request from %s verified", host); crm_xml_add(up, F_TYPE, "cib"); crm_xml_add(up, F_CIB_OPERATION, PCMK__CIB_REQUEST_UPGRADE); crm_xml_add(up, F_CIB_SCHEMA_MAX, get_schema_name(new_version)); crm_xml_add(up, F_CIB_DELEGATED, host); crm_xml_add(up, F_CIB_CLIENTID, client_id); crm_xml_add(up, F_CIB_CALLOPTS, call_opts); crm_xml_add(up, F_CIB_CALLID, call_id); if (cib_legacy_mode() && based_is_primary) { rc = cib_process_upgrade( op, options, section, up, input, existing_cib, result_cib, answer); } else { send_cluster_message(NULL, crm_msg_cib, up, FALSE); } free_xml(up); } else if(rc == pcmk_ok) { rc = -pcmk_err_schema_unchanged; } if (rc != pcmk_ok) { // Notify originating peer so it can notify its local clients crm_node_t *origin = pcmk__search_cluster_node_cache(0, host); crm_info("Rejecting upgrade request from %s: %s " CRM_XS " rc=%d peer=%s", host, pcmk_strerror(rc), rc, (origin? origin->uname : "lost")); if (origin) { xmlNode *up = create_xml_node(NULL, __func__); crm_xml_add(up, F_TYPE, "cib"); crm_xml_add(up, F_CIB_OPERATION, PCMK__CIB_REQUEST_UPGRADE); crm_xml_add(up, F_CIB_DELEGATED, host); crm_xml_add(up, F_CIB_ISREPLY, host); crm_xml_add(up, F_CIB_CLIENTID, client_id); crm_xml_add(up, F_CIB_CALLOPTS, call_opts); crm_xml_add(up, F_CIB_CALLID, call_id); crm_xml_add_int(up, F_CIB_UPGRADE_RC, rc); if (send_cluster_message(origin, crm_msg_cib, up, TRUE) == FALSE) { crm_warn("Could not send CIB upgrade result to %s", host); } free_xml(up); } } free_xml(scratch); } return rc; } int cib_process_sync_one(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { return sync_our_cib(req, FALSE); } int cib_server_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int rc = pcmk_ok; if (sync_in_progress > MAX_DIFF_RETRY) { /* Don't ignore diffs forever; the last request may have been lost. * If the diff fails, we'll ask for another full resync. */ sync_in_progress = 0; } // The primary instance should never ignore a diff if (sync_in_progress && !based_is_primary) { int diff_add_updates = 0; int diff_add_epoch = 0; int diff_add_admin_epoch = 0; int diff_del_updates = 0; int diff_del_epoch = 0; int diff_del_admin_epoch = 0; cib_diff_version_details(input, &diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates, &diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates); sync_in_progress++; crm_notice("Not applying diff %d.%d.%d -> %d.%d.%d (sync in progress)", diff_del_admin_epoch, diff_del_epoch, diff_del_updates, diff_add_admin_epoch, diff_add_epoch, diff_add_updates); return -pcmk_err_diff_resync; } rc = cib_process_diff(op, options, section, req, input, existing_cib, result_cib, answer); crm_trace("result: %s (%d), %s", pcmk_strerror(rc), rc, (based_is_primary? "primary": "secondary")); if ((rc == -pcmk_err_diff_resync) && !based_is_primary) { free_xml(*result_cib); *result_cib = NULL; send_sync_request(NULL); } else if (rc == -pcmk_err_diff_resync) { rc = -pcmk_err_diff_failed; if (options & cib_force_diff) { crm_warn("Not requesting full refresh in R/W mode"); } } else if ((rc != pcmk_ok) && !based_is_primary && cib_legacy_mode()) { crm_warn("Requesting full CIB refresh because update failed: %s" CRM_XS " rc=%d", pcmk_strerror(rc), rc); pcmk__output_set_log_level(logger_out, LOG_INFO); logger_out->message(logger_out, "xml-patchset", input); free_xml(*result_cib); *result_cib = NULL; send_sync_request(NULL); } return rc; } int cib_process_replace_svr(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { const char *tag = crm_element_name(input); int rc = cib_process_replace(op, options, section, req, input, existing_cib, result_cib, answer); if (rc == pcmk_ok && pcmk__str_eq(tag, XML_TAG_CIB, pcmk__str_casei)) { sync_in_progress = 0; } return rc; } // @COMPAT: Remove when PCMK__CIB_REQUEST_ABS_DELETE is removed int cib_process_delete_absolute(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { return -EINVAL; } static xmlNode * cib_msg_copy(xmlNode *msg) { static const char *field_list[] = { F_XML_TAGNAME, F_TYPE, F_CIB_CLIENTID, F_CIB_CALLOPTS, F_CIB_CALLID, F_CIB_OPERATION, F_CIB_ISREPLY, F_CIB_SECTION, F_CIB_HOST, F_CIB_RC, F_CIB_DELEGATED, F_CIB_OBJID, F_CIB_OBJTYPE, F_CIB_EXISTING, F_CIB_SEENCOUNT, F_CIB_TIMEOUT, F_CIB_GLOBAL_UPDATE, F_CIB_CLIENTNAME, F_CIB_USER, F_CIB_NOTIFY_TYPE, F_CIB_NOTIFY_ACTIVATE }; xmlNode *copy = create_xml_node(NULL, "copy"); CRM_ASSERT(copy != NULL); for (int lpc = 0; lpc < PCMK__NELEM(field_list); lpc++) { const char *field = field_list[lpc]; const char *value = crm_element_value(msg, field); if (value != NULL) { crm_xml_add(copy, field, value); } } return copy; } int sync_our_cib(xmlNode * request, gboolean all) { int result = pcmk_ok; char *digest = NULL; const char *host = crm_element_value(request, F_ORIG); const char *op = crm_element_value(request, F_CIB_OPERATION); xmlNode *replace_request = NULL; CRM_CHECK(the_cib != NULL, return -EINVAL); CRM_CHECK(all || (host != NULL), return -EINVAL); crm_debug("Syncing CIB to %s", all ? "all peers" : host); replace_request = cib_msg_copy(request); if (host != NULL) { crm_xml_add(replace_request, F_CIB_ISREPLY, host); } if (all) { xml_remove_prop(replace_request, F_CIB_HOST); } crm_xml_add(replace_request, F_CIB_OPERATION, PCMK__CIB_REQUEST_REPLACE); crm_xml_add(replace_request, "original_" F_CIB_OPERATION, op); pcmk__xe_set_bool_attr(replace_request, F_CIB_GLOBAL_UPDATE, true); crm_xml_add(replace_request, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, CRM_FEATURE_SET); crm_xml_add(replace_request, XML_ATTR_DIGEST, digest); add_message_xml(replace_request, F_CIB_CALLDATA, the_cib); if (send_cluster_message (all ? NULL : crm_get_peer(0, host), crm_msg_cib, replace_request, FALSE) == FALSE) { result = -ENOTCONN; } free_xml(replace_request); free(digest); return result; } + +int +cib_process_init_transaction(const char *op, int options, const char *section, + xmlNode *req, xmlNode *input, + xmlNode *existing_cib, xmlNode **result_cib, + xmlNode **answer) +{ + const char *client_id = crm_element_value(req, F_CIB_CLIENTID); + pcmk__client_t *client = NULL; + + const char *reason = NULL; + int rc = pcmk_ok; + + if (client_id == NULL) { + reason = "No client ID"; + rc = -EINVAL; + goto done; + } + + client = pcmk__find_client_by_id(client_id); + if (client == NULL) { + reason = "Client not found"; + rc = -ENXIO; + goto done; + } + + rc = based_init_transaction(client); + + if (rc != pcmk_rc_ok) { + reason = pcmk_rc_str(rc); + } + rc = pcmk_rc2legacy(rc); + +done: + if (rc != pcmk_ok) { + const char *client_name = crm_element_value(req, F_CIB_CLIENTNAME); + + crm_err("Could not initiate transaction for client %s (%s): %s", + pcmk__s(client_name, "unspecified"), + pcmk__s(client_id, "unknown"), + pcmk__s(reason, "unknown reason (bug?)")); + } + return rc; +} + +int +cib_process_commit_transaction(const char *op, int options, const char *section, + xmlNode *req, xmlNode *input, + xmlNode *existing_cib, xmlNode **result_cib, + xmlNode **answer) +{ + /* On success, our caller will activate *result_cib locally, trigger a + * replace notification if appropriate, and sync *result_cib to all nodes. + * On failure, our caller will free *result_cib. + */ + + const char *client_id = crm_element_value(req, F_CIB_CLIENTID); + pcmk__client_t *client = NULL; + + const char *reason = NULL; + int rc = pcmk_ok; + + if (client_id == NULL) { + reason = "No client ID"; + rc = -EINVAL; + goto done; + } + + client = pcmk__find_client_by_id(client_id); + if (client == NULL) { + reason = "Client not found"; + rc = -ENXIO; + goto done; + } + + rc = based_commit_transaction(client, result_cib); + if (rc != pcmk_rc_ok) { + reason = pcmk_rc_str(rc); + rc = pcmk_rc2legacy(rc); + } + +done: + if (rc != pcmk_ok) { + const char *client_name = crm_element_value(req, F_CIB_CLIENTNAME); + + crm_err("Could not commit transaction for client %s (%s): %s", + pcmk__s(client_name, "unspecified"), + pcmk__s(client_id, "unknown"), + pcmk__s(reason, "unknown reason (bug?)")); + } + return rc; +} + +int +cib_process_discard_transaction(const char *op, int options, + const char *section, xmlNode *req, + xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer) +{ + const char *client_id = crm_element_value(req, F_CIB_CLIENTID); + pcmk__client_t *client = NULL; + + const char *reason = NULL; + int rc = pcmk_ok; + + if (client_id == NULL) { + reason = "No client ID"; + rc = -EINVAL; + goto done; + } + + client = pcmk__find_client_by_id(client_id); + if (client == NULL) { + reason = "Client not found"; + rc = -ENXIO; + goto done; + } + + based_discard_transaction(client); + +done: + if (rc != pcmk_ok) { + const char *client_name = crm_element_value(req, F_CIB_CLIENTNAME); + + crm_err("Could not discard transaction for client %s (%s): %s", + pcmk__s(client_name, "unspecified"), + pcmk__s(client_id, "unknown"), + pcmk__s(reason, "unknown reason (bug?)")); + } + return rc; +} diff --git a/daemons/based/based_remote.c b/daemons/based/based_remote.c index 38136d2d95..bba31e9ad4 100644 --- a/daemons/based/based_remote.c +++ b/daemons/based/based_remote.c @@ -1,680 +1,681 @@ /* * Copyright 2004-2021 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 // PRIx64 #include #include #include #include #include #include #include #include #include #include #include #include #include "pacemaker-based.h" /* #undef HAVE_PAM_PAM_APPL_H */ /* #undef HAVE_GNUTLS_GNUTLS_H */ #ifdef HAVE_GNUTLS_GNUTLS_H # include #endif #include #include #if HAVE_SECURITY_PAM_APPL_H # include # define HAVE_PAM 1 #else # if HAVE_PAM_PAM_APPL_H # include # define HAVE_PAM 1 # endif #endif extern int remote_tls_fd; extern gboolean cib_shutdown_flag; int init_remote_listener(int port, gboolean encrypted); void cib_remote_connection_destroy(gpointer user_data); #ifdef HAVE_GNUTLS_GNUTLS_H gnutls_dh_params_t dh_params; gnutls_anon_server_credentials_t anon_cred_s; static void debug_log(int level, const char *str) { fputs(str, stderr); } #endif #define REMOTE_AUTH_TIMEOUT 10000 int num_clients; int authenticate_user(const char *user, const char *passwd); static int cib_remote_listen(gpointer data); static int cib_remote_msg(gpointer data); static void remote_connection_destroy(gpointer user_data) { crm_info("No longer listening for remote connections"); return; } int init_remote_listener(int port, gboolean encrypted) { int rc; int *ssock = NULL; struct sockaddr_in saddr; int optval; static struct mainloop_fd_callbacks remote_listen_fd_callbacks = { .dispatch = cib_remote_listen, .destroy = remote_connection_destroy, }; if (port <= 0) { /* don't start it */ return 0; } if (encrypted) { #ifndef HAVE_GNUTLS_GNUTLS_H crm_warn("TLS support is not available"); return 0; #else crm_notice("Starting TLS listener on port %d", port); crm_gnutls_global_init(); /* gnutls_global_set_log_level (10); */ gnutls_global_set_log_function(debug_log); if (pcmk__init_tls_dh(&dh_params) != pcmk_rc_ok) { return -1; } gnutls_anon_allocate_server_credentials(&anon_cred_s); gnutls_anon_set_server_dh_params(anon_cred_s, dh_params); #endif } else { crm_warn("Starting plain-text listener on port %d", port); } #ifndef HAVE_PAM crm_warn("PAM is _not_ enabled!"); #endif /* create server socket */ ssock = malloc(sizeof(int)); if(ssock == NULL) { crm_perror(LOG_ERR, "Listener socket allocation failed"); return -1; } *ssock = socket(AF_INET, SOCK_STREAM, 0); if (*ssock == -1) { crm_perror(LOG_ERR, "Listener socket creation failed"); free(ssock); return -1; } /* reuse address */ optval = 1; rc = setsockopt(*ssock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); if (rc < 0) { crm_perror(LOG_WARNING, "Local address reuse not allowed on listener socket"); } /* bind server socket */ memset(&saddr, '\0', sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = htons(port); if (bind(*ssock, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) { crm_perror(LOG_ERR, "Cannot bind to listener socket"); close(*ssock); free(ssock); return -2; } if (listen(*ssock, 10) == -1) { crm_perror(LOG_ERR, "Cannot listen on socket"); close(*ssock); free(ssock); return -3; } mainloop_add_fd("cib-remote", G_PRIORITY_DEFAULT, *ssock, ssock, &remote_listen_fd_callbacks); crm_debug("Started listener on port %d", port); return *ssock; } static int check_group_membership(const char *usr, const char *grp) { int index = 0; struct passwd *pwd = NULL; struct group *group = NULL; CRM_CHECK(usr != NULL, return FALSE); CRM_CHECK(grp != NULL, return FALSE); pwd = getpwnam(usr); if (pwd == NULL) { crm_err("No user named '%s' exists!", usr); return FALSE; } group = getgrgid(pwd->pw_gid); if (group != NULL && pcmk__str_eq(grp, group->gr_name, pcmk__str_none)) { return TRUE; } group = getgrnam(grp); if (group == NULL) { crm_err("No group named '%s' exists!", grp); return FALSE; } while (TRUE) { char *member = group->gr_mem[index++]; if (member == NULL) { break; } else if (pcmk__str_eq(usr, member, pcmk__str_none)) { return TRUE; } }; return FALSE; } static gboolean cib_remote_auth(xmlNode * login) { const char *user = NULL; const char *pass = NULL; const char *tmp = NULL; crm_log_xml_info(login, "Login: "); if (login == NULL) { return FALSE; } tmp = crm_element_name(login); if (!pcmk__str_eq(tmp, "cib_command", pcmk__str_casei)) { crm_err("Wrong tag: %s", tmp); return FALSE; } tmp = crm_element_value(login, "op"); if (!pcmk__str_eq(tmp, "authenticate", pcmk__str_casei)) { crm_err("Wrong operation: %s", tmp); return FALSE; } user = crm_element_value(login, "user"); pass = crm_element_value(login, "password"); if (!user || !pass) { crm_err("missing auth credentials"); return FALSE; } /* Non-root daemons can only validate the password of the * user they're running as */ if (check_group_membership(user, CRM_DAEMON_GROUP) == FALSE) { crm_err("User is not a member of the required group"); return FALSE; } else if (authenticate_user(user, pass) == FALSE) { crm_err("PAM auth failed"); return FALSE; } return TRUE; } static gboolean remote_auth_timeout_cb(gpointer data) { pcmk__client_t *client = data; client->remote->auth_timeout = 0; if (pcmk_is_set(client->flags, pcmk__client_authenticated)) { return FALSE; } mainloop_del_fd(client->remote->source); crm_err("Remote client authentication timed out"); return FALSE; } static int cib_remote_listen(gpointer data) { int csock = 0; unsigned laddr; struct sockaddr_storage addr; char ipstr[INET6_ADDRSTRLEN]; int ssock = *(int *)data; int rc; pcmk__client_t *new_client = NULL; static struct mainloop_fd_callbacks remote_client_fd_callbacks = { .dispatch = cib_remote_msg, .destroy = cib_remote_connection_destroy, }; /* accept the connection */ laddr = sizeof(addr); memset(&addr, 0, sizeof(addr)); csock = accept(ssock, (struct sockaddr *)&addr, &laddr); if (csock == -1) { crm_perror(LOG_ERR, "Could not accept socket connection"); return TRUE; } pcmk__sockaddr2str(&addr, ipstr); crm_debug("New %s connection from %s", ((ssock == remote_tls_fd)? "secure" : "clear-text"), ipstr); rc = pcmk__set_nonblocking(csock); if (rc != pcmk_rc_ok) { crm_err("Could not set socket non-blocking: %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); close(csock); return TRUE; } num_clients++; new_client = pcmk__new_unauth_client(NULL); new_client->remote = calloc(1, sizeof(pcmk__remote_t)); if (ssock == remote_tls_fd) { #ifdef HAVE_GNUTLS_GNUTLS_H pcmk__set_client_flags(new_client, pcmk__client_tls); /* create gnutls session for the server socket */ new_client->remote->tls_session = pcmk__new_tls_session(csock, GNUTLS_SERVER, GNUTLS_CRD_ANON, anon_cred_s); if (new_client->remote->tls_session == NULL) { close(csock); return TRUE; } #endif } else { pcmk__set_client_flags(new_client, pcmk__client_tcp); new_client->remote->tcp_socket = csock; } // Require the client to authenticate within this time new_client->remote->auth_timeout = g_timeout_add(REMOTE_AUTH_TIMEOUT, remote_auth_timeout_cb, new_client); crm_info("Remote CIB client pending authentication " CRM_XS " %p id: %s", new_client, new_client->id); new_client->remote->source = mainloop_add_fd("cib-remote-client", G_PRIORITY_DEFAULT, csock, new_client, &remote_client_fd_callbacks); return TRUE; } void cib_remote_connection_destroy(gpointer user_data) { pcmk__client_t *client = user_data; int csock = 0; if (client == NULL) { return; } crm_trace("Cleaning up after client %s disconnect", pcmk__client_name(client)); num_clients--; crm_trace("Num unfree'd clients: %d", num_clients); switch (PCMK__CLIENT_TYPE(client)) { case pcmk__client_tcp: csock = client->remote->tcp_socket; break; #ifdef HAVE_GNUTLS_GNUTLS_H case pcmk__client_tls: if (client->remote->tls_session) { void *sock_ptr = gnutls_transport_get_ptr(*client->remote->tls_session); csock = GPOINTER_TO_INT(sock_ptr); if (pcmk_is_set(client->flags, pcmk__client_tls_handshake_complete)) { gnutls_bye(*client->remote->tls_session, GNUTLS_SHUT_WR); } gnutls_deinit(*client->remote->tls_session); gnutls_free(client->remote->tls_session); client->remote->tls_session = NULL; } break; #endif default: crm_warn("Unknown transport for client %s " CRM_XS " flags=%#016" PRIx64, pcmk__client_name(client), client->flags); } if (csock > 0) { close(csock); } + based_discard_transaction(client); pcmk__free_client(client); crm_trace("Freed the cib client"); if (cib_shutdown_flag) { cib_shutdown(0); } return; } static void cib_handle_remote_msg(pcmk__client_t *client, xmlNode *command) { const char *value = NULL; value = crm_element_name(command); if (!pcmk__str_eq(value, "cib_command", pcmk__str_casei)) { crm_log_xml_trace(command, "Bad command: "); return; } if (client->name == NULL) { value = crm_element_value(command, F_CLIENTNAME); if (value == NULL) { client->name = strdup(client->id); } else { client->name = strdup(value); } } /* unset dangerous options */ xml_remove_prop(command, F_ORIG); xml_remove_prop(command, F_CIB_HOST); xml_remove_prop(command, F_CIB_GLOBAL_UPDATE); crm_xml_add(command, F_TYPE, T_CIB); crm_xml_add(command, F_CIB_CLIENTID, client->id); crm_xml_add(command, F_CIB_CLIENTNAME, client->name); crm_xml_add(command, F_CIB_USER, client->user); if (crm_element_value(command, F_CIB_CALLID) == NULL) { char *call_uuid = crm_generate_uuid(); /* fix the command */ crm_xml_add(command, F_CIB_CALLID, call_uuid); free(call_uuid); } if (crm_element_value(command, F_CIB_CALLOPTS) == NULL) { crm_xml_add_int(command, F_CIB_CALLOPTS, 0); } crm_log_xml_trace(command, "Remote command: "); cib_common_callback_worker(0, 0, command, client, TRUE); } static int cib_remote_msg(gpointer data) { xmlNode *command = NULL; pcmk__client_t *client = data; int rc; int timeout = 1000; if (pcmk_is_set(client->flags, pcmk__client_authenticated)) { timeout = -1; } crm_trace("Remote %s message received for client %s", pcmk__client_type_str(PCMK__CLIENT_TYPE(client)), pcmk__client_name(client)); #ifdef HAVE_GNUTLS_GNUTLS_H if ((PCMK__CLIENT_TYPE(client) == pcmk__client_tls) && !pcmk_is_set(client->flags, pcmk__client_tls_handshake_complete)) { int rc = pcmk__read_handshake_data(client); if (rc == EAGAIN) { /* No more data is available at the moment. Just return for now; * we'll get invoked again once the client sends more. */ return 0; } else if (rc != pcmk_rc_ok) { return -1; } crm_debug("TLS handshake with remote CIB client completed"); pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete); if (client->remote->auth_timeout) { g_source_remove(client->remote->auth_timeout); } // Require the client to authenticate within this time client->remote->auth_timeout = g_timeout_add(REMOTE_AUTH_TIMEOUT, remote_auth_timeout_cb, client); return 0; } #endif rc = pcmk__read_remote_message(client->remote, timeout); /* must pass auth before we will process anything else */ if (!pcmk_is_set(client->flags, pcmk__client_authenticated)) { xmlNode *reg; const char *user = NULL; command = pcmk__remote_message_xml(client->remote); if (cib_remote_auth(command) == FALSE) { free_xml(command); return -1; } crm_notice("Remote CIB client connection accepted"); pcmk__set_client_flags(client, pcmk__client_authenticated); g_source_remove(client->remote->auth_timeout); client->remote->auth_timeout = 0; client->name = crm_element_value_copy(command, "name"); user = crm_element_value(command, "user"); if (user) { client->user = strdup(user); } /* send ACK */ reg = create_xml_node(NULL, "cib_result"); crm_xml_add(reg, F_CIB_OPERATION, CRM_OP_REGISTER); crm_xml_add(reg, F_CIB_CLIENTID, client->id); pcmk__remote_send_xml(client->remote, reg); free_xml(reg); free_xml(command); } command = pcmk__remote_message_xml(client->remote); while (command) { crm_trace("Remote client message received"); cib_handle_remote_msg(client, command); free_xml(command); command = pcmk__remote_message_xml(client->remote); } if (rc == ENOTCONN) { crm_trace("Remote CIB client disconnected while reading from it"); return -1; } return 0; } #ifdef HAVE_PAM static int construct_pam_passwd(int num_msg, const struct pam_message **msg, struct pam_response **response, void *data) { int count = 0; struct pam_response *reply; char *string = (char *)data; CRM_CHECK(data, return PAM_CONV_ERR); CRM_CHECK(num_msg == 1, return PAM_CONV_ERR); /* We only want to handle one message */ reply = calloc(1, sizeof(struct pam_response)); CRM_ASSERT(reply != NULL); for (count = 0; count < num_msg; ++count) { switch (msg[count]->msg_style) { case PAM_TEXT_INFO: crm_info("PAM: %s", msg[count]->msg); break; case PAM_PROMPT_ECHO_OFF: case PAM_PROMPT_ECHO_ON: reply[count].resp_retcode = 0; reply[count].resp = string; /* We already made a copy */ break; case PAM_ERROR_MSG: /* In theory we'd want to print this, but then * we see the password prompt in the logs */ /* crm_err("PAM error: %s", msg[count]->msg); */ break; default: crm_err("Unhandled conversation type: %d", msg[count]->msg_style); goto bail; } } *response = reply; reply = NULL; return PAM_SUCCESS; bail: for (count = 0; count < num_msg; ++count) { if (reply[count].resp != NULL) { switch (msg[count]->msg_style) { case PAM_PROMPT_ECHO_ON: case PAM_PROMPT_ECHO_OFF: /* Erase the data - it contained a password */ while (*(reply[count].resp)) { *(reply[count].resp)++ = '\0'; } free(reply[count].resp); break; } reply[count].resp = NULL; } } free(reply); reply = NULL; return PAM_CONV_ERR; } #endif int authenticate_user(const char *user, const char *passwd) { #ifndef HAVE_PAM gboolean pass = TRUE; #else int rc = 0; gboolean pass = FALSE; const void *p_user = NULL; struct pam_conv p_conv; struct pam_handle *pam_h = NULL; static const char *pam_name = NULL; if (pam_name == NULL) { pam_name = getenv("CIB_pam_service"); } if (pam_name == NULL) { pam_name = "login"; } p_conv.conv = construct_pam_passwd; p_conv.appdata_ptr = strdup(passwd); rc = pam_start(pam_name, user, &p_conv, &pam_h); if (rc != PAM_SUCCESS) { crm_err("Could not initialize PAM: %s (%d)", pam_strerror(pam_h, rc), rc); goto bail; } rc = pam_authenticate(pam_h, 0); if (rc != PAM_SUCCESS) { crm_err("Authentication failed for %s: %s (%d)", user, pam_strerror(pam_h, rc), rc); goto bail; } /* Make sure we authenticated the user we wanted to authenticate. * Since we also run as non-root, it might be worth pre-checking * the user has the same EID as us, since that the only user we * can authenticate. */ rc = pam_get_item(pam_h, PAM_USER, &p_user); if (rc != PAM_SUCCESS) { crm_err("Internal PAM error: %s (%d)", pam_strerror(pam_h, rc), rc); goto bail; } else if (p_user == NULL) { crm_err("Unknown user authenticated."); goto bail; } else if (!pcmk__str_eq(p_user, user, pcmk__str_casei)) { crm_err("User mismatch: %s vs. %s.", (const char *)p_user, (const char *)user); goto bail; } rc = pam_acct_mgmt(pam_h, 0); if (rc != PAM_SUCCESS) { crm_err("Access denied: %s (%d)", pam_strerror(pam_h, rc), rc); goto bail; } pass = TRUE; bail: pam_end(pam_h, rc); #endif return pass; } diff --git a/daemons/based/based_transaction.c b/daemons/based/based_transaction.c new file mode 100644 index 0000000000..d90d1b4420 --- /dev/null +++ b/daemons/based/based_transaction.c @@ -0,0 +1,371 @@ +/* + * Copyright 2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include + +#include +#include + +#include "pacemaker-based.h" + +struct request_data { + pcmk__client_t *client; + xmlNodePtr request; + bool privileged; +}; + +/* Table of uncommitted CIB transactions + * key: client ID (const char *), value: GQueue of struct request_data + */ +static GHashTable *transactions = NULL; + +/*! + * \internal + * \brief Create a new a CIB request data object + * + * \param[in] client CIB client + * \param[in] request CIB request + * \param[in] privileged If \p true, the request has write privileges + */ +static struct request_data * +create_request_data(pcmk__client_t *client, xmlNodePtr request, + bool privileged) +{ + struct request_data *data = calloc(1, sizeof(struct request_data)); + + if (data == NULL) { + return NULL; + } + + /* Caller owns client and request. The client's transaction is always + * discarded before the client is freed, so we can safely use it when + * processing requests in the transaction. The request is freed before we + * use it, so make a copy. + */ + data->client = client; + data->request = copy_xml(request); + data->privileged = privileged; + + return data; +} + +/*! + * \internal + * \brief Free a CIB request data object + * + * \param[in,out] data Request data object to free + */ +static void +free_request_data(gpointer data) +{ + struct request_data *req_data = (struct request_data *) data; + + if (req_data != NULL) { + // req_data doesn't own req_data->client + free_xml(req_data->request); + free(req_data); + } +} + +/*! + * \internal + * \brief Free a CIB transaction and the requests it contains + * + * \param[in,out] data Transaction to free + */ +static inline void +free_transaction(gpointer data) +{ + g_queue_free_full((GQueue *) data, (GDestroyNotify) free_request_data); +} + +/*! + * \internal + * \brief Get a CIB client's uncommitted transaction, if any + * + * \param[in] client Client to look up + * + * \return The client's uncommitted transaction, if any + * + * \note The caller must not free the return value directly. Transactions must + * be freed by \p based_discard_transaction() or by + * \p based_free_transaction_table(). + */ +static inline GQueue * +get_transaction(const pcmk__client_t *client) +{ + if (transactions == NULL) { + return NULL; + } + return g_hash_table_lookup(transactions, client->id); +} + +/*! + * \internal + * \brief Create a new transaction for a given CIB client + * + * \param[in] client Client to initiate a transaction for + * + * \return Standard Pacemaker return code + */ +int +based_init_transaction(const pcmk__client_t *client) +{ + CRM_ASSERT(client != NULL); + + if (client->id == NULL) { + crm_warn("Can't initiate transaction for client without id"); + return EINVAL; + } + + // A client can have at most one transaction at a time + if (get_transaction(client) != NULL) { + return pcmk_rc_already; + } + + crm_trace("Initiating transaction for client %s (%s)", + pcmk__client_name(client), client->id); + + if (transactions == NULL) { + transactions = pcmk__strkey_table(NULL, free_transaction); + } + g_hash_table_insert(transactions, client->id, g_queue_new()); + return pcmk_rc_ok; +} + +/*! + * \internal + * \brief Validate that a CIB request is supported in a transaction + * + * \param[in] request CIB request + * + * \return Standard Pacemaker return code + */ +static int +validate_transaction_request(const xmlNode *request) +{ + const char *op = crm_element_value(request, F_CIB_OPERATION); + const char *host = crm_element_value(request, F_CIB_HOST); + + const cib_operation_t *operation = NULL; + int rc = cib_get_operation(op, &operation); + + rc = pcmk_legacy2rc(rc); + if (rc != pcmk_rc_ok) { + // cib_get_operation() logs error + return rc; + } + + if (!pcmk_is_set(operation->flags, cib_op_attr_transaction)) { + crm_err("Operation '%s' is not supported in CIB transaction", op); + return EOPNOTSUPP; + } + + if (!pcmk__str_eq(host, OUR_NODENAME, + pcmk__str_casei|pcmk__str_null_matches)) { + crm_err("Operation targeting another node (%s) is not supported in CIB " + "transaction", + host); + return EOPNOTSUPP; + } + + return pcmk_rc_ok; +} + +/*! + * \internal + * \brief Add a new CIB request to an existing transaction + * + * \param[in] client Client whose transaction to extend + * \param[in] request CIB request + * \param[in] privileged If \p true, the request has write privileges + * + * \return Standard Pacemaker return code + */ +int +based_extend_transaction(pcmk__client_t *client, xmlNodePtr request, + bool privileged) +{ + struct request_data *data = NULL; + GQueue *transaction = NULL; + int rc = pcmk_rc_ok; + + CRM_ASSERT((client != NULL) && (request != NULL)); + + transaction = get_transaction(client); + if (transaction == NULL) { + return pcmk_rc_no_transaction; + } + + rc = validate_transaction_request(request); + if (rc != pcmk_rc_ok) { + return rc; + } + + data = create_request_data(client, request, privileged); + if (data == NULL) { + return ENOMEM; + } + + g_queue_push_tail(transaction, data); + return pcmk_rc_ok; +} + +/*! + * \internal + * \brief Free a CIB client's transaction (if any) and its requests + * + * \param[in] client Client whose transaction to discard + */ +void +based_discard_transaction(const pcmk__client_t *client) +{ + bool found = false; + + CRM_ASSERT(client != NULL); + + if (transactions != NULL) { + found = g_hash_table_remove(transactions, client->id); + } + + crm_trace("%s for client %s (%s)", + (found? "Found and removed transaction" : "No transaction found"), + pcmk__client_name(client), + pcmk__s(client->id, "unidentified")); +} + +/*! + * \internal + * \brief Process requests in a transaction + * + * Stop when a request fails or when all requests have been processed. + * + * \param[in,out] transaction Transaction to process + * \param[in] client_name Client name (for logging only) + * \param[in] client_id Client ID (for logging only) + * + * \return Standard Pacemaker return code + */ +static int +process_transaction_requests(GQueue *transaction, const char *client_name, + const char *client_id) +{ + for (struct request_data *data = g_queue_pop_head(transaction); + data != NULL; data = g_queue_pop_head(transaction)) { + + const char *op = crm_element_value(data->request, F_CIB_OPERATION); + + int rc = cib_process_request(data->request, data->privileged, + data->client); + + rc = pcmk_legacy2rc(rc); + if (rc != pcmk_rc_ok) { + crm_err("Aborting CIB transaction for client %s (%s) due to failed " + "%s request: %s", + client_name, client_id, op, pcmk_rc_str(rc)); + crm_log_xml_info(data->request, "Failed request"); + free_request_data(data); + return rc; + } + + crm_trace("Applied %s request to transaction working CIB for client %s " + "(%s)", + op, client_name, client_id); + crm_log_xml_trace(data->request, "Successful request"); + free_request_data(data); + } + + return pcmk_rc_ok; +} + +/*! + * \internal + * \brief Commit a given CIB client's transaction to a working CIB copy + * + * \param[in] client Client whose transaction to commit + * \param[in,out] result_cib Where to store result CIB + * + * \return Standard Pacemaker return code + * + * \note This function is expected to be called only by + * \p cib_process_commit_transaction(). + * \note \p result_cib is expected to be a copy of the current CIB as created by + * \p cib_perform_op(). + * \note The caller is responsible for activating and syncing \p result_cib on + * success, and for freeing it on failure. + */ +int +based_commit_transaction(const pcmk__client_t *client, xmlNodePtr *result_cib) +{ + GQueue *transaction = NULL; + const char *client_name = NULL; + const char *client_id = NULL; + xmlNodePtr saved_cib = the_cib; + int rc = pcmk_rc_ok; + + CRM_ASSERT((client != NULL) && (result_cib != NULL)); + + /* *result_cib should be a copy of the_cib (created by cib_perform_op()). If + * not, make a copy now. Change tracking isn't strictly required here + * because: + * * Each request in the transaction will have changes tracked and ACLs + * checked if appropriate. + * * cib_perform_op() will infer changes for the commit request at the end. + */ + CRM_CHECK((*result_cib != NULL) && (*result_cib != the_cib), + *result_cib = copy_xml(the_cib)); + + transaction = get_transaction(client); + if (transaction == NULL) { + return pcmk_rc_no_transaction; + } + + client_name = pcmk__client_name(client); + client_id = pcmk__s(client->id, "unidentified"); + + crm_trace("Committing transaction for client %s (%s) to working CIB", + client_name, client_id); + + // Apply all changes to a working copy of the CIB + the_cib = *result_cib; + + rc = process_transaction_requests(transaction, client_name, client_id); + + crm_trace("Transaction commit %s for client %s (%s); discarding queue", + ((rc != pcmk_rc_ok)? "succeeded" : "failed"), client_name, + client_id); + + // Free the transaction and (if aborted) free any remaining requests + based_discard_transaction(client); + + /* Some request types (for example, erase) may have freed the_cib (the + * working copy) and pointed it at a new XML object. In that case, it + * follows that *result_cib (the working copy) was freed. + * + * Point *result_cib at the updated working copy stored in the_cib. + */ + *result_cib = the_cib; + + // Point the_cib back to the unchanged original copy + the_cib = saved_cib; + + return rc; +} + +/*! + * \internal + * \brief Free the transaction table and any uncommitted transactions + */ +void +based_free_transaction_table(void) +{ + if (transactions != NULL) { + g_hash_table_destroy(transactions); + } +} diff --git a/daemons/based/based_transaction.h b/daemons/based/based_transaction.h new file mode 100644 index 0000000000..8412abf809 --- /dev/null +++ b/daemons/based/based_transaction.h @@ -0,0 +1,25 @@ +/* + * Copyright 2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#ifndef BASED_TRANSACTION__H +#define BASED_TRANSACTION__H + +#include + +#include + +int based_init_transaction(const pcmk__client_t *client); +int based_extend_transaction(pcmk__client_t *client, xmlNodePtr request, + bool privileged); +void based_discard_transaction(const pcmk__client_t *client); +int based_commit_transaction(const pcmk__client_t *client, + xmlNodePtr *result_cib); +void based_free_transaction_table(void); + +#endif // BASED_TRANSACTION__H diff --git a/daemons/based/pacemaker-based.c b/daemons/based/pacemaker-based.c index 129997ec3e..86e53e3acb 100644 --- a/daemons/based/pacemaker-based.c +++ b/daemons/based/pacemaker-based.c @@ -1,442 +1,443 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SUMMARY "daemon for managing the configuration of a Pacemaker cluster" extern int init_remote_listener(int port, gboolean encrypted); gboolean cib_shutdown_flag = FALSE; int cib_status = pcmk_ok; crm_cluster_t *crm_cluster = NULL; GMainLoop *mainloop = NULL; gchar *cib_root = NULL; static gboolean preserve_status = FALSE; gboolean cib_writes_enabled = TRUE; int remote_fd = 0; int remote_tls_fd = 0; GHashTable *config_hash = NULL; GHashTable *local_notify_queue = NULL; pcmk__output_t *logger_out = NULL; static void cib_init(void); void cib_shutdown(int nsig); static bool startCib(const char *filename); extern int write_cib_contents(gpointer p); static crm_exit_t exit_code = CRM_EX_OK; static void cib_enable_writes(int nsig) { crm_info("(Re)enabling disk writes"); cib_writes_enabled = TRUE; } /*! * \internal * \brief Set up options, users, and groups for stand-alone mode * * \param[out] error GLib error object * * \return Standard Pacemaker return code */ static int setup_stand_alone(GError **error) { int rc = 0; struct passwd *pwentry = NULL; preserve_status = TRUE; cib_writes_enabled = FALSE; errno = 0; pwentry = getpwnam(CRM_DAEMON_USER); if (pwentry == NULL) { exit_code = CRM_EX_FATAL; if (errno != 0) { g_set_error(error, PCMK__EXITC_ERROR, exit_code, "Error getting password DB entry for %s: %s", CRM_DAEMON_USER, strerror(errno)); return errno; } g_set_error(error, PCMK__EXITC_ERROR, exit_code, "Password DB entry for '%s' not found", CRM_DAEMON_USER); return ENXIO; } rc = setgid(pwentry->pw_gid); if (rc < 0) { exit_code = CRM_EX_FATAL; g_set_error(error, PCMK__EXITC_ERROR, exit_code, "Could not set group to %d: %s", pwentry->pw_gid, strerror(errno)); return errno; } rc = initgroups(CRM_DAEMON_USER, pwentry->pw_gid); if (rc < 0) { exit_code = CRM_EX_FATAL; g_set_error(error, PCMK__EXITC_ERROR, exit_code, "Could not setup groups for user %d: %s", pwentry->pw_uid, strerror(errno)); return errno; } rc = setuid(pwentry->pw_uid); if (rc < 0) { exit_code = CRM_EX_FATAL; g_set_error(error, PCMK__EXITC_ERROR, exit_code, "Could not set user to %d: %s", pwentry->pw_uid, strerror(errno)); return errno; } return pcmk_rc_ok; } static GOptionEntry entries[] = { { "stand-alone", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &stand_alone, "(Advanced use only) Run in stand-alone mode", NULL }, { "disk-writes", 'w', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &cib_writes_enabled, "(Advanced use only) Enable disk writes (enabled by default unless in " "stand-alone mode)", NULL }, { "cib-root", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &cib_root, "(Advanced use only) Directory where the CIB XML file should be located " "(default: " CRM_CONFIG_DIR ")", NULL }, { NULL } }; static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, 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_ipc_t *old_instance = NULL; pcmk__output_t *out = NULL; GError *error = NULL; GOptionGroup *output_group = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "r"); 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; } rc = pcmk__log_output_new(&logger_out); if (rc != pcmk_rc_ok) { exit_code = CRM_EX_ERROR; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format log: %s", pcmk_rc_str(rc)); goto done; } pcmk__output_set_log_level(logger_out, LOG_TRACE); mainloop_add_signal(SIGTERM, cib_shutdown); mainloop_add_signal(SIGPIPE, cib_enable_writes); cib_writer = mainloop_add_trigger(G_PRIORITY_LOW, write_cib_contents, NULL); if ((g_strv_length(processed_args) >= 2) && pcmk__str_eq(processed_args[1], "metadata", pcmk__str_none)) { cib_metadata(); goto done; } pcmk__cli_init_logging("pacemaker-based", args->verbosity); crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); crm_notice("Starting Pacemaker CIB manager"); old_instance = crm_ipc_new(PCMK__SERVER_BASED_RO, 0); if (old_instance == NULL) { /* crm_ipc_new() will have already logged an error message with * crm_err() */ exit_code = CRM_EX_FATAL; goto done; } if (crm_ipc_connect(old_instance)) { /* IPC end-point already up */ crm_ipc_close(old_instance); crm_ipc_destroy(old_instance); crm_err("pacemaker-based 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; } if (stand_alone) { rc = setup_stand_alone(&error); if (rc != pcmk_rc_ok) { goto done; } } if (cib_root == NULL) { cib_root = g_strdup(CRM_CONFIG_DIR); } else { crm_notice("Using custom config location: %s", cib_root); } if (!pcmk__daemon_can_write(cib_root, NULL)) { exit_code = CRM_EX_FATAL; crm_err("Terminating due to bad permissions on %s", cib_root); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Bad permissions on %s (see logs for details)", cib_root); goto done; } crm_peer_init(); // Read initial CIB, connect to cluster, and start IPC servers cib_init(); // Run the main loop mainloop = g_main_loop_new(NULL, FALSE); crm_notice("Pacemaker CIB manager successfully started and accepting connections"); g_main_loop_run(mainloop); /* If main loop returned, clean up and exit. We disconnect in case * terminate_cib() was called with fast=-1. */ crm_cluster_disconnect(crm_cluster); pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); done: g_strfreev(processed_args); pcmk__free_arg_context(context); crm_peer_destroy(); if (local_notify_queue != NULL) { g_hash_table_destroy(local_notify_queue); } if (config_hash != NULL) { g_hash_table_destroy(config_hash); } + based_free_transaction_table(); pcmk__client_cleanup(); pcmk_cluster_free(crm_cluster); g_free(cib_root); 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); } #if SUPPORT_COROSYNC static void cib_cs_dispatch(cpg_handle_t handle, const struct cpg_name *groupName, uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len) { uint32_t kind = 0; xmlNode *xml = NULL; const char *from = NULL; char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from); if(data == NULL) { return; } if (kind == crm_class_cluster) { xml = string2xml(data); if (xml == NULL) { crm_err("Invalid XML: '%.120s'", data); free(data); return; } crm_xml_add(xml, F_ORIG, from); /* crm_xml_add_int(xml, F_SEQ, wrapper->id); */ cib_peer_callback(xml, NULL); } free_xml(xml); free(data); } static void cib_cs_destroy(gpointer user_data) { if (cib_shutdown_flag) { crm_info("Corosync disconnection complete"); } else { crm_crit("Lost connection to cluster layer, shutting down"); terminate_cib(__func__, CRM_EX_DISCONNECT); } } #endif static void cib_peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *data) { switch (type) { case crm_status_processes: if (cib_legacy_mode() && !pcmk_is_set(node->processes, crm_get_cluster_proc())) { uint32_t old = data? *(const uint32_t *)data : 0; if ((node->processes ^ old) & crm_proc_cpg) { crm_info("Attempting to disable legacy mode after %s left the cluster", node->uname); legacy_mode = FALSE; } } break; case crm_status_uname: case crm_status_nstate: if (cib_shutdown_flag && (crm_active_peers() < 2) && (pcmk__ipc_client_count() == 0)) { crm_info("No more peers"); terminate_cib(__func__, -1); } break; } } static void cib_init(void) { crm_cluster = pcmk_cluster_new(); #if SUPPORT_COROSYNC if (is_corosync_cluster()) { crm_cluster->destroy = cib_cs_destroy; crm_cluster->cpg.cpg_deliver_fn = cib_cs_dispatch; crm_cluster->cpg.cpg_confchg_fn = pcmk_cpg_membership; } #endif // SUPPORT_COROSYNC config_hash = pcmk__strkey_table(free, free); if (startCib("cib.xml") == FALSE) { crm_crit("Cannot start CIB... terminating"); crm_exit(CRM_EX_NOINPUT); } if (!stand_alone) { crm_set_status_callback(&cib_peer_update_callback); if (!crm_cluster_connect(crm_cluster)) { crm_crit("Cannot sign in to the cluster... terminating"); crm_exit(CRM_EX_FATAL); } } pcmk__serve_based_ipc(&ipcs_ro, &ipcs_rw, &ipcs_shm, &ipc_ro_callbacks, &ipc_rw_callbacks); if (stand_alone) { based_is_primary = true; } } static bool startCib(const char *filename) { gboolean active = FALSE; xmlNode *cib = readCibXmlFile(cib_root, filename, !preserve_status); if (activateCibXml(cib, TRUE, "start") == 0) { int port = 0; active = TRUE; cib_read_config(config_hash, cib); pcmk__scan_port(crm_element_value(cib, "remote-tls-port"), &port); if (port >= 0) { remote_tls_fd = init_remote_listener(port, TRUE); } pcmk__scan_port(crm_element_value(cib, "remote-clear-port"), &port); if (port >= 0) { remote_fd = init_remote_listener(port, FALSE); } } return active; } diff --git a/daemons/based/pacemaker-based.h b/daemons/based/pacemaker-based.h index 8b2571479c..923af2909c 100644 --- a/daemons/based/pacemaker-based.h +++ b/daemons/based/pacemaker-based.h @@ -1,158 +1,173 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PACEMAKER_BASED__H # define PACEMAKER_BASED__H #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include "based_transaction.h" + #ifdef HAVE_GNUTLS_GNUTLS_H # include #endif #define OUR_NODENAME (stand_alone? "localhost" : crm_cluster->uname) // CIB-specific client flags enum cib_client_flags { // Notifications cib_notify_pre = (UINT64_C(1) << 0), cib_notify_post = (UINT64_C(1) << 1), cib_notify_replace = (UINT64_C(1) << 2), cib_notify_confirm = (UINT64_C(1) << 3), cib_notify_diff = (UINT64_C(1) << 4), // Whether client is another cluster daemon cib_is_daemon = (UINT64_C(1) << 12), }; /*! * \internal * \enum cib_op_attr * \brief Bit flags for CIB operation attributes */ enum cib_op_attr { cib_op_attr_none = 0, //!< No special attributes cib_op_attr_modifies = (1 << 1), //!< Modifies CIB cib_op_attr_privileged = (1 << 2), //!< Requires privileges cib_op_attr_local = (1 << 3), //!< Must only be processed locally cib_op_attr_replaces = (1 << 4), //!< Replaces CIB cib_op_attr_writes_through = (1 << 5), //!< Writes to disk on success + cib_op_attr_transaction = (1 << 6), //!< Supported in a transaction }; typedef struct cib_operation_s { const char *name; uint32_t flags; //!< Group of enum cib_op_attr flags int (*prepare) (xmlNode *, xmlNode **, const char **); int (*cleanup) (int, xmlNode **, xmlNode **); cib_op_t fn; } cib_operation_t; extern bool based_is_primary; extern GHashTable *config_hash; extern xmlNode *the_cib; extern crm_trigger_t *cib_writer; extern gboolean cib_writes_enabled; extern GMainLoop *mainloop; extern crm_cluster_t *crm_cluster; extern GHashTable *local_notify_queue; extern gboolean legacy_mode; extern gboolean stand_alone; extern gboolean cib_shutdown_flag; extern gchar *cib_root; extern int cib_status; extern pcmk__output_t *logger_out; extern struct qb_ipcs_service_handlers ipc_ro_callbacks; extern struct qb_ipcs_service_handlers ipc_rw_callbacks; extern qb_ipcs_service_t *ipcs_ro; extern qb_ipcs_service_t *ipcs_rw; extern qb_ipcs_service_t *ipcs_shm; void cib_peer_callback(xmlNode *msg, void *private_data); void cib_common_callback_worker(uint32_t id, uint32_t flags, xmlNode *op_request, pcmk__client_t *cib_client, gboolean privileged); int cib_process_request(xmlNode *request, gboolean privileged, const pcmk__client_t *cib_client); void cib_shutdown(int nsig); void terminate_cib(const char *caller, int fast); gboolean cib_legacy_mode(void); gboolean uninitializeCib(void); xmlNode *readCibXmlFile(const char *dir, const char *file, gboolean discard_status); int activateCibXml(xmlNode *doc, gboolean to_disk, const char *op); int cib_process_shutdown_req(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_noop(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_ping(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_readwrite(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_replace_svr(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_server_process_diff(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_sync(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_sync_one(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_delete_absolute(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_upgrade_server(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); +int cib_process_init_transaction(const char *op, int options, + const char *section, xmlNode *req, + xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer); +int cib_process_commit_transaction(const char *op, int options, + const char *section, xmlNode *req, + xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer); +int cib_process_discard_transaction(const char *op, int options, + const char *section, xmlNode *req, + xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer); void send_sync_request(const char *host); int sync_our_cib(xmlNode *request, gboolean all); int cib_get_operation(const char *op, const cib_operation_t **operation); void cib_diff_notify(const char *op, int result, const char *call_id, const char *client_id, const char *client_name, const char *origin, xmlNode *update, xmlNode *diff); void cib_replace_notify(const char *op, int result, const char *call_id, const char *client_id, const char *client_name, const char *origin, xmlNode *update, xmlNode *diff, uint32_t change_section); static inline const char * cib_config_lookup(const char *opt) { return g_hash_table_lookup(config_hash, opt); } #endif // PACEMAKER_BASED__H diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h index 20e365b318..6ed8ffd32a 100644 --- a/include/crm/cib/internal.h +++ b/include/crm/cib/internal.h @@ -1,276 +1,279 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef CIB_INTERNAL__H # define CIB_INTERNAL__H # include # include # include // Request types for CIB manager IPC/CPG #define PCMK__CIB_REQUEST_SECONDARY "cib_slave" #define PCMK__CIB_REQUEST_PRIMARY "cib_master" #define PCMK__CIB_REQUEST_SYNC_TO_ALL "cib_sync" #define PCMK__CIB_REQUEST_SYNC_TO_ONE "cib_sync_one" #define PCMK__CIB_REQUEST_IS_PRIMARY "cib_ismaster" #define PCMK__CIB_REQUEST_BUMP "cib_bump" #define PCMK__CIB_REQUEST_QUERY "cib_query" #define PCMK__CIB_REQUEST_CREATE "cib_create" #define PCMK__CIB_REQUEST_MODIFY "cib_modify" #define PCMK__CIB_REQUEST_DELETE "cib_delete" #define PCMK__CIB_REQUEST_ERASE "cib_erase" #define PCMK__CIB_REQUEST_REPLACE "cib_replace" #define PCMK__CIB_REQUEST_APPLY_PATCH "cib_apply_diff" #define PCMK__CIB_REQUEST_UPGRADE "cib_upgrade" #define PCMK__CIB_REQUEST_ABS_DELETE "cib_delete_alt" #define PCMK__CIB_REQUEST_NOOP "noop" #define PCMK__CIB_REQUEST_SHUTDOWN "cib_shutdown_req" +#define PCMK__CIB_REQUEST_INIT_TRANSACT "cib_init_transact" +#define PCMK__CIB_REQUEST_COMMIT_TRANSACT "cib_commit_transact" +#define PCMK__CIB_REQUEST_DISCARD_TRANSACT "cib_discard_transact" # define F_CIB_CLIENTID "cib_clientid" # define F_CIB_CALLOPTS "cib_callopt" # define F_CIB_CALLID "cib_callid" # define F_CIB_CALLDATA "cib_calldata" # define F_CIB_OPERATION "cib_op" # define F_CIB_ISREPLY "cib_isreplyto" # define F_CIB_SECTION "cib_section" # define F_CIB_HOST "cib_host" # define F_CIB_RC "cib_rc" # define F_CIB_UPGRADE_RC "cib_upgrade_rc" # define F_CIB_DELEGATED "cib_delegated_from" # define F_CIB_OBJID "cib_object" # define F_CIB_OBJTYPE "cib_object_type" # define F_CIB_EXISTING "cib_existing_object" # define F_CIB_SEENCOUNT "cib_seen" # define F_CIB_TIMEOUT "cib_timeout" # define F_CIB_UPDATE "cib_update" # define F_CIB_GLOBAL_UPDATE "cib_update" # define F_CIB_UPDATE_RESULT "cib_update_result" # define F_CIB_CLIENTNAME "cib_clientname" # define F_CIB_NOTIFY_TYPE "cib_notify_type" # define F_CIB_NOTIFY_ACTIVATE "cib_notify_activate" # define F_CIB_UPDATE_DIFF "cib_update_diff" # define F_CIB_USER "cib_user" # define F_CIB_LOCAL_NOTIFY_ID "cib_local_notify_id" # define F_CIB_PING_ID "cib_ping_id" # define F_CIB_SCHEMA_MAX "cib_schema_max" # define F_CIB_CHANGE_SECTION "cib_change_section" # define T_CIB "cib" # define T_CIB_NOTIFY "cib_notify" /* notify sub-types */ # define T_CIB_PRE_NOTIFY "cib_pre_notify" # define T_CIB_POST_NOTIFY "cib_post_notify" # define T_CIB_UPDATE_CONFIRM "cib_update_confirmation" # define T_CIB_REPLACE_NOTIFY "cib_refresh_notify" /*! * \internal * \enum cib_change_section_info * \brief Flags to indicate which sections of the CIB have changed */ enum cib_change_section_info { cib_change_section_none = 0, //!< No sections have changed cib_change_section_nodes = (1 << 0), //!< The nodes section has changed cib_change_section_alerts = (1 << 1), //!< The alerts section has changed cib_change_section_status = (1 << 2), //!< The status section has changed }; /*! * \internal * \brief Set given enum cib_change_section_info flags * * \param[in,out] flags_orig Group of flags to update * \param[in] flags_to_set Flags to clear from \p flags_orig */ #define pcmk__set_change_section(flags_orig, flags_to_set) do { \ flags_orig = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \ "CIB change section", \ "change_section", flags_orig, \ flags_to_set, #flags_to_set); \ } while (0) gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, int *_admin_epoch, int *_epoch, int *_updates); gboolean cib_read_config(GHashTable * options, xmlNode * current_cib); typedef struct cib_notify_client_s { const char *event; const char *obj_id; /* implement one day */ const char *obj_type; /* implement one day */ void (*callback) (const char *event, xmlNode * msg); } cib_notify_client_t; typedef struct cib_callback_client_s { void (*callback) (xmlNode *, int, int, xmlNode *, void *); const char *id; void *user_data; gboolean only_success; struct timer_rec_s *timer; void (*free_func)(void *); } cib_callback_client_t; struct timer_rec_s { int call_id; int timeout; guint ref; cib_t *cib; }; #define cib__set_call_options(cib_call_opts, call_for, flags_to_set) do { \ cib_call_opts = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "CIB call", (call_for), (cib_call_opts), \ (flags_to_set), #flags_to_set); \ } while (0) #define cib__clear_call_options(cib_call_opts, call_for, flags_to_clear) do { \ cib_call_opts = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "CIB call", (call_for), (cib_call_opts), \ (flags_to_clear), #flags_to_clear); \ } while (0) typedef int (*cib_op_t) (const char *, int, const char *, xmlNode *, xmlNode *, xmlNode *, xmlNode **, xmlNode **); cib_t *cib_new_variant(void); int cib_perform_op(const char *op, int call_options, cib_op_t fn, gboolean is_query, const char *section, xmlNode *req, xmlNode *input, gboolean manage_counters, gboolean *config_changed, xmlNode **current_cib, xmlNode **result_cib, xmlNode **diff, xmlNode **output); xmlNode *cib_create_op(int call_id, const char *op, const char *host, const char *section, xmlNode * data, int call_options, const char *user_name); void cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc); void cib_native_notify(gpointer data, gpointer user_data); int cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_erase(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_bump(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_replace(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); /*! * \internal * \brief Query or modify a CIB * * \param[in] op PCMK__CIB_REQUEST_* operation to be performed * \param[in] options Flag set of \c cib_call_options * \param[in] section XPath to query or modify * \param[in] req unused * \param[in] input Portion of CIB to modify (used with * PCMK__CIB_REQUEST_CREATE, * PCMK__CIB_REQUEST_MODIFY, and * PCMK__CIB_REQUEST_REPLACE) * \param[in,out] existing_cib Input CIB (used with PCMK__CIB_REQUEST_QUERY) * \param[in,out] result_cib CIB copy to make changes in (used with * PCMK__CIB_REQUEST_CREATE, * PCMK__CIB_REQUEST_MODIFY, * PCMK__CIB_REQUEST_DELETE, and * PCMK__CIB_REQUEST_REPLACE) * \param[out] answer Query result (used with PCMK__CIB_REQUEST_QUERY) * * \return Legacy Pacemaker return code */ int cib_process_xpath(const char *op, int options, const char *section, const xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode ** answer); bool cib__config_changed_v1(xmlNode *last, xmlNode *next, xmlNode **diff); int cib_internal_op(cib_t * cib, const char *op, const char *host, const char *section, xmlNode * data, xmlNode ** output_data, int call_options, const char *user_name); int cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root); int cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, const char *cib_filename); void cib__set_output(cib_t *cib, pcmk__output_t *out); cib_callback_client_t* cib__lookup_id (int call_id); /*! * \internal * \brief Connect to, query, and optionally disconnect from the CIB * * Open a read-write connection to the CIB manager if an already connected * client is not passed in. Then query the CIB and store the resulting XML. * Finally, disconnect if the CIB connection isn't being returned to the caller. * * \param[in,out] out Output object (may be \p NULL) * \param[in,out] cib If not \p NULL, where to store CIB connection * \param[out] cib_object Where to store query result * * \return Standard Pacemaker return code * * \note If \p cib is not \p NULL, the caller is responsible for freeing \p *cib * using \p cib_delete(). * \note If \p *cib points to an existing \p cib_t object, this function will * reuse it instead of creating a new one. If the existing client is * already connected, the connection will be reused, even if it's * read-only. */ int cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object); int cib__clean_up_connection(cib_t **cib); int cib__update_node_attr(pcmk__output_t *out, cib_t *cib, int call_options, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value, const char *user_name, const char *node_type); int cib__get_node_attrs(pcmk__output_t *out, cib_t *cib, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *user_name, xmlNode **result); int cib__delete_node_attr(pcmk__output_t *out, cib_t *cib, int options, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value, const char *user_name); #endif