diff --git a/cib/callbacks.c b/cib/callbacks.c index c7de518c78..c79def6cd2 100644 --- a/cib/callbacks.c +++ b/cib/callbacks.c @@ -1,1378 +1,1378 @@ /* * Copyright (C) 2004 Andrew Beekhof * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #if ENABLE_ACL #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "common.h" extern GMainLoop* mainloop; extern gboolean cib_shutdown_flag; extern gboolean stand_alone; extern const char* cib_root; #if SUPPORT_HEARTBEAT extern ll_cluster_t *hb_conn; #endif extern void cib_ha_connection_destroy(gpointer user_data); extern enum cib_errors cib_update_counter( xmlNode *xml_obj, const char *field, gboolean reset); extern void GHFunc_count_peers( gpointer key, gpointer value, gpointer user_data); void initiate_exit(void); void terminate_cib(const char *caller); gint cib_GCompareFunc(gconstpointer a, gconstpointer b); gboolean can_write(int flags); void send_cib_replace(const xmlNode *sync_request, const char *host); void cib_process_request( xmlNode *request, gboolean privileged, gboolean force_synchronous, gboolean from_peer, cib_client_t *cib_client); void cib_common_callback_worker(xmlNode *op_request, cib_client_t *cib_client, gboolean force_synchronous, gboolean privileged); extern GHashTable *client_list; int next_client_id = 0; extern const char *cib_our_uname; extern unsigned long cib_num_ops, cib_num_local, cib_num_updates, cib_num_fail; extern unsigned long cib_bad_connects, cib_num_timeouts; extern longclock_t cib_call_time; extern enum cib_errors cib_status; int send_via_callback_channel(xmlNode *msg, const char *token); enum cib_errors cib_process_command( xmlNode *request, xmlNode **reply, xmlNode **cib_diff, gboolean privileged); gboolean cib_common_callback(IPC_Channel *channel, cib_client_t *cib_client, gboolean force_synchronous, gboolean privileged); gboolean cib_process_disconnect(IPC_Channel *channel, cib_client_t *cib_client); int num_clients = 0; static void cib_ipc_connection_destroy(gpointer user_data) { cib_client_t *cib_client = user_data; /* cib_process_disconnect */ if(cib_client == NULL) { crm_debug_4("Destroying %p", user_data); return; } if(cib_client->source != NULL) { crm_debug_4("Deleting %s (%p) from mainloop", cib_client->name, cib_client->source); G_main_del_IPC_Channel(cib_client->source); cib_client->source = NULL; } crm_debug_3("Destroying %s (%p)", cib_client->name, user_data); num_clients--; crm_debug_2("Num unfree'd clients: %d", num_clients); crm_free(cib_client->name); crm_free(cib_client->callback_id); crm_free(cib_client->id); crm_free(cib_client); crm_debug_4("Freed the cib client"); return; } gboolean cib_client_connect(IPC_Channel *channel, gpointer user_data) { cl_uuid_t client_id; xmlNode *reg_msg = NULL; cib_client_t *new_client = NULL; char uuid_str[UU_UNPARSE_SIZEOF]; const char *channel_name = user_data; gboolean (*callback)(IPC_Channel *channel, gpointer user_data); crm_debug_3("Connecting channel"); if (channel == NULL) { crm_err("Channel was NULL"); cib_bad_connects++; return FALSE; } else if (channel->ch_status != IPC_CONNECT) { crm_err("Channel was disconnected"); cib_bad_connects++; return FALSE; } else if(channel_name == NULL) { crm_err("user_data must contain channel name"); cib_bad_connects++; return FALSE; } else if(cib_shutdown_flag) { crm_info("Ignoring new client [%d] during shutdown", channel->farside_pid); return FALSE; } callback = cib_ro_callback; if(safe_str_eq(channel_name, cib_channel_rw)) { callback = cib_rw_callback; } crm_malloc0(new_client, sizeof(cib_client_t)); num_clients++; new_client->channel = channel; new_client->channel_name = channel_name; crm_debug_3("Created channel %p for channel %s", new_client, new_client->channel_name); channel->ops->set_recv_qlen(channel, 1024); channel->ops->set_send_qlen(channel, 1024); new_client->source = G_main_add_IPC_Channel( G_PRIORITY_DEFAULT, channel, FALSE, callback, new_client, cib_ipc_connection_destroy); crm_debug_3("Channel %s connected for client %s", new_client->channel_name, new_client->id); cl_uuid_generate(&client_id); cl_uuid_unparse(&client_id, uuid_str); CRM_CHECK(new_client->id == NULL, crm_free(new_client->id)); new_client->id = crm_strdup(uuid_str); /* make sure we can find ourselves later for sync calls * redirected to the master instance */ g_hash_table_insert(client_list, new_client->id, new_client); reg_msg = create_xml_node(NULL, "callback"); crm_xml_add(reg_msg, F_CIB_OPERATION, CRM_OP_REGISTER); crm_xml_add(reg_msg, F_CIB_CLIENTID, new_client->id); send_ipc_message(channel, reg_msg); free_xml(reg_msg); return TRUE; } gboolean cib_rw_callback(IPC_Channel *channel, gpointer user_data) { gboolean result = FALSE; result = cib_common_callback(channel, user_data, FALSE, TRUE); return result; } gboolean cib_ro_callback(IPC_Channel *channel, gpointer user_data) { gboolean result = FALSE; result = cib_common_callback(channel, user_data, FALSE, FALSE); return result; } void cib_common_callback_worker(xmlNode *op_request, cib_client_t *cib_client, gboolean force_synchronous, gboolean privileged) { longclock_t call_stop = 0; longclock_t call_start = 0; const char *op = crm_element_value(op_request, F_CIB_OPERATION); if(crm_str_eq(op, CRM_OP_REGISTER, TRUE)) { return; } else if(crm_str_eq(op, T_CIB_NOTIFY, TRUE)) { /* Update the notify filters for this client */ int on_off = 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 for %s (%s): %s", type, cib_client->name, cib_client->id, on_off?"on":"off"); if(safe_str_eq(type, T_CIB_POST_NOTIFY)) { cib_client->post_notify = on_off; } else if(safe_str_eq(type, T_CIB_PRE_NOTIFY)) { cib_client->pre_notify = on_off; } else if(safe_str_eq(type, T_CIB_UPDATE_CONFIRM)) { cib_client->confirmations = on_off; } else if(safe_str_eq(type, T_CIB_DIFF_NOTIFY)) { cib_client->diffs = on_off; } else if(safe_str_eq(type, T_CIB_REPLACE_NOTIFY)) { cib_client->replace = on_off; } return; } cib_client->num_calls++; call_start = time_longclock(); cib_process_request( op_request, force_synchronous, privileged, FALSE, cib_client); call_stop = time_longclock(); cib_call_time += (call_stop - call_start); } gboolean cib_common_callback(IPC_Channel *channel, cib_client_t *cib_client, gboolean force_synchronous, gboolean privileged) { int lpc = 0; const char *value = NULL; xmlNode *op_request = NULL; gboolean keep_channel = TRUE; CRM_CHECK(cib_client != NULL, crm_err("Invalid client"); return FALSE); CRM_CHECK(cib_client->id != NULL, crm_err("Invalid client: %p", cib_client); return FALSE); /* * Do enough work to make entering worthwhile * But don't allow a single client to monopolize the CIB */ while(lpc < 5 && IPC_ISRCONN(channel) && channel->ops->is_message_pending(channel)) { lpc++; op_request = xmlfromIPC(channel, MAX_IPC_DELAY); if (op_request == NULL) { break; } if(cib_client->name == NULL) { value = crm_element_value(op_request, F_CIB_CLIENTNAME); if(value == NULL) { cib_client->name = crm_itoa(channel->farside_pid); } else { cib_client->name = crm_strdup(value); } } #if ENABLE_ACL if(cib_client->user == NULL) { struct passwd *pwent = NULL; pwent = getpwuid(channel->farside_uid); if (pwent == NULL) { crm_perror(LOG_ERR, "Cannot get password entry of uid: %d", channel->farside_uid); } else { cib_client->user = crm_strdup(pwent->pw_name); } } #endif crm_xml_add(op_request, F_CIB_CLIENTID, cib_client->id); crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name); #if ENABLE_ACL crm_xml_add(op_request, F_CIB_USER, cib_client->user); #endif /* crm_log_xml(LOG_MSG, "Client[inbound]", op_request); */ if(cib_client->callback_id == NULL) { value = crm_element_value(op_request, F_CIB_CALLBACK_TOKEN); if(value != NULL) { cib_client->callback_id = crm_strdup(value); } else { cib_client->callback_id = crm_strdup(cib_client->id); } } cib_common_callback_worker( op_request, cib_client, force_synchronous, privileged); free_xml(op_request); } if(channel->ch_status != IPC_CONNECT) { crm_debug_2("Client disconnected"); keep_channel = cib_process_disconnect(channel, cib_client); } return keep_channel; } static void do_local_notify(xmlNode *notify_src, const char *client_id, gboolean sync_reply, gboolean from_peer) { /* send callback to originating child */ cib_client_t *client_obj = NULL; enum cib_errors local_rc = cib_ok; if(client_id != NULL) { client_obj = g_hash_table_lookup(client_list, client_id); } else { crm_trace("No client to sent the response to. F_CIB_CLIENTID not set."); } if(client_obj == NULL) { local_rc = cib_reply_failed; } else { const char *client_id = client_obj->callback_id; crm_trace("Sending %ssync response to %s %s", sync_reply?"":"an a-", client_obj->name, from_peer?"(originator of delegated request)":""); if(sync_reply) { client_id = client_obj->id; } local_rc = send_via_callback_channel(notify_src, client_id); } if(local_rc != cib_ok && client_obj != NULL) { crm_warn("%sSync reply to %s failed: %s", sync_reply?"":"A-", client_obj?client_obj->name:"", cib_error2string(local_rc)); } } static void parse_local_options( cib_client_t *cib_client, int call_type, int call_options, const char *host, const char *op, gboolean *local_notify, gboolean *needs_reply, gboolean *process, gboolean *needs_forward) { if(cib_op_modifies(call_type) && !(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_debug_2("Processing locally scoped %s op from %s", op, cib_client->name); *local_notify = TRUE; } else if(host == NULL && cib_is_master) { crm_debug_2("Processing master %s op locally from %s", op, cib_client->name); *local_notify = TRUE; } else if(safe_str_eq(host, cib_our_uname)) { crm_debug_2("Processing locally addressed %s op from %s", op, cib_client->name); *local_notify = TRUE; } else if(stand_alone) { *needs_forward = FALSE; *local_notify = TRUE; *process = TRUE; } else { crm_debug_2("%s op from %s needs to be forwarded to %s", op, cib_client->name, host?host:"the master instance"); *needs_forward = TRUE; *process = FALSE; } } static gboolean parse_peer_options( int call_type, xmlNode *request, gboolean *local_notify, gboolean *needs_reply, gboolean *process, gboolean *needs_forward) { 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); const char *update = crm_element_value(request, F_CIB_GLOBAL_UPDATE); gboolean is_reply = safe_str_eq(reply_to, cib_our_uname); if(crm_is_true(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; } host = crm_element_value(request, F_CIB_HOST); if(host != NULL && safe_str_eq(host, cib_our_uname)) { crm_trace("Processing request sent to us from %s", originator); return TRUE; } else if(host == NULL && cib_is_master == TRUE) { crm_trace("Processing request sent to master instance from %s", originator); return TRUE; } if(is_reply) { crm_trace("Forward reply sent from %s to local clients", originator); *process = FALSE; *needs_reply = FALSE; *local_notify = TRUE; return TRUE; } op = crm_element_value(request, F_CIB_OPERATION); delegated = crm_element_value(request, F_CIB_DELEGATED); if(delegated != NULL) { crm_debug_2("Ignoring msg for master instance"); } else if(host != NULL) { /* this is for a specific instance and we're not it */ crm_debug_2("Ignoring msg for instance on %s", crm_str(host)); } else if(reply_to == NULL && cib_is_master == FALSE) { /* this is for the master instance and we're not it */ crm_debug_2("Ignoring reply to %s", crm_str(reply_to)); } else if(safe_str_eq(op, "cib_shutdown_req")) { if(reply_to != NULL) { crm_debug("Processing %s from %s", op, host); *needs_reply = FALSE; } else { crm_debug("Processing %s reply from %s", op, host); } return TRUE; } else { crm_err("Nothing for us to do?"); crm_log_xml(LOG_ERR, "Peer[inbound]", request); } return FALSE; } static void forward_request(xmlNode *request, cib_client_t *cib_client, int call_options) { const char *op = crm_element_value(request, F_CIB_OPERATION); const char *host = crm_element_value(request, F_CIB_HOST); crm_xml_add(request, F_CIB_DELEGATED, cib_our_uname); if(host != NULL) { crm_debug_2("Forwarding %s op to %s", op, host); send_cluster_message(host, crm_msg_cib, request, FALSE); } else { crm_debug_2("Forwarding %s op to master instance", op); send_cluster_message(NULL, crm_msg_cib, request, FALSE); } /* Return the request to its original state */ xml_remove_prop(request, F_CIB_DELEGATED); if(call_options & cib_discard_reply) { crm_debug_2("Client not interested in reply"); } } static void send_peer_reply( xmlNode *msg, xmlNode *result_diff, const char *originator, gboolean broadcast) { CRM_ASSERT(msg != NULL); if(broadcast) { /* this (successful) call modified the CIB _and_ the * change needs to be broadcast... * send via HA to other 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; char *digest = NULL; 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_debug_2("Sending update diff %d.%d.%d -> %d.%d.%d", diff_del_admin_epoch,diff_del_epoch,diff_del_updates, diff_add_admin_epoch,diff_add_epoch,diff_add_updates); crm_xml_add(msg, F_CIB_ISREPLY, originator); crm_xml_add(msg, F_CIB_GLOBAL_UPDATE, XML_BOOLEAN_TRUE); crm_xml_add(msg, F_CIB_OPERATION, CIB_OP_APPLY_DIFF); /* Its safe to always use the latest version since the election * ensures the software on this node is the oldest node in the cluster */ digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, CRM_FEATURE_SET); crm_xml_add(result_diff, XML_ATTR_DIGEST, digest); crm_log_xml_trace(the_cib, digest); crm_free(digest); add_message_xml(msg, F_CIB_UPDATE_DIFF, result_diff); crm_log_xml(LOG_DEBUG_3, "copy", msg); send_cluster_message(NULL, crm_msg_cib, msg, TRUE); } else if(originator != NULL) { /* send reply via HA to originating node */ crm_debug_2("Sending request result to originator only"); crm_xml_add(msg, F_CIB_ISREPLY, originator); send_cluster_message(originator, crm_msg_cib, msg, FALSE); } } void cib_process_request( xmlNode *request, gboolean force_synchronous, gboolean privileged, gboolean from_peer, cib_client_t *cib_client) { int call_type = 0; int call_options = 0; gboolean process = TRUE; gboolean is_update = TRUE; gboolean needs_reply = TRUE; gboolean local_notify = FALSE; gboolean needs_forward = FALSE; gboolean global_update = crm_is_true(crm_element_value(request, F_CIB_GLOBAL_UPDATE)); xmlNode *op_reply = NULL; xmlNode *result_diff = NULL; enum cib_errors rc = cib_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); crm_debug_4("%s Processing msg %s", cib_our_uname, crm_element_value(request, F_SEQ)); cib_num_ops++; if(cib_num_ops == 0) { cib_num_fail = 0; cib_num_local = 0; cib_num_updates = 0; crm_info("Stats wrapped around"); } if(host != NULL && strlen(host) == 0) { host = NULL; } crm_element_value_int(request, F_CIB_CALLOPTS, &call_options); if(force_synchronous) { call_options |= cib_sync_call; } crm_debug_2("Processing %s message (%s) for %s...", from_peer?"peer":"local", from_peer?originator:cib_our_uname, host?host:"master"); rc = cib_get_operation_id(op, &call_type); if(rc != cib_ok) { /* TODO: construct error reply? */ crm_err("Pre-processing of command failed: %s", cib_error2string(rc)); return; } is_update = cib_op_modifies(call_type); if(is_update) { cib_num_updates++; } if(from_peer == FALSE) { parse_local_options(cib_client, call_type, call_options, host, op, &local_notify, &needs_reply, &process, &needs_forward); } else if(parse_peer_options(call_type, request, &local_notify, &needs_reply, &process, &needs_forward) == FALSE) { return; } crm_debug_3("Finished determining processing actions"); if(call_options & cib_discard_reply) { needs_reply = is_update; local_notify = FALSE; } if(needs_forward) { forward_request(request, cib_client, call_options); return; } if(cib_status != cib_ok) { rc = cib_status; crm_err("Operation ignored, cluster configuration is invalid." " Please repair and restart: %s", cib_error2string(cib_status)); op_reply = cib_construct_reply(request, the_cib, cib_status); } else if(process) { int level = LOG_INFO; const char *section = crm_element_value(request, F_CIB_SECTION); cib_num_local++; rc = cib_process_command( request, &op_reply, &result_diff, privileged); if(global_update) { switch(rc) { case cib_ok: case cib_old_data: case cib_diff_resync: case cib_diff_failed: level = LOG_DEBUG_2; break; default: level = LOG_ERR; } } else if(safe_str_eq(op, CIB_OP_QUERY)) { level = LOG_DEBUG_2; } else if(rc != cib_ok) { cib_num_fail++; level = LOG_WARNING; } else if(safe_str_eq(op, CIB_OP_SLAVE)) { level = LOG_DEBUG_2; } else if(safe_str_eq(section, XML_CIB_TAG_STATUS)) { level = LOG_DEBUG_2; } if(crm_log_level >= level) { /* Avoid all the xml lookups if we're not going to print the results */ do_crm_log(level, "Operation complete: op %s for section %s (origin=%s/%s/%s, version=%s.%s.%s): %s (rc=%d)", op, section?section:"'all'", originator?originator:"local", crm_element_value(request, F_CIB_CLIENTNAME), crm_element_value(request, F_CIB_CALLID), 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", cib_error2string(rc), rc); } if(op_reply == NULL && (needs_reply || local_notify)) { crm_err("Unexpected NULL reply to message"); crm_log_xml(LOG_ERR, "null reply", request); needs_reply = FALSE; local_notify = FALSE; } } crm_debug_3("processing response cases"); if(local_notify) { const char *client_id = crm_element_value(request, F_CIB_CLIENTID); if(client_id && process == FALSE) { do_local_notify(request, client_id, call_options & cib_sync_call, from_peer); } else if(client_id) { do_local_notify(op_reply, client_id, call_options & cib_sync_call, from_peer); } } /* from now on we are the server */ if(needs_reply == FALSE || stand_alone) { /* nothing more to do... * this was a non-originating slave update */ crm_debug_2("Completed slave update"); } else if(rc == cib_ok && result_diff != NULL && !(call_options & cib_inhibit_bcast)) { send_peer_reply(request, result_diff, originator, TRUE); } else if(call_options & cib_discard_reply) { crm_debug_4("Caller isn't interested in reply"); } else if (from_peer) { if(is_update == FALSE || result_diff == NULL) { crm_debug_3("Request not broadcast: R/O call"); } else if(call_options & cib_inhibit_bcast) { crm_debug_3("Request not broadcast: inhibited"); } else if(rc != cib_ok) { crm_debug_3("Request not broadcast: call failed: %s", cib_error2string(rc)); } else { crm_debug_2("Directing reply to %s", originator); } send_peer_reply(op_reply, result_diff, originator, FALSE); } free_xml(op_reply); free_xml(result_diff); return; } xmlNode * cib_construct_reply(xmlNode *request, xmlNode *output, int rc) { int lpc = 0; xmlNode *reply = NULL; const char *name = NULL; const char *value = NULL; const char *names[] = { F_CIB_OPERATION, F_CIB_CALLID, F_CIB_CLIENTID, F_CIB_CALLOPTS }; static int max = DIMOF(names); crm_debug_4("Creating a basic reply"); reply = create_xml_node(NULL, "cib-reply"); crm_xml_add(reply, F_TYPE, T_CIB); for(lpc = 0; lpc < max; lpc++) { name = names[lpc]; value = crm_element_value(request, name); crm_xml_add(reply, name, value); } crm_xml_add_int(reply, F_CIB_RC, rc); if(output != NULL) { crm_debug_4("Attaching reply output"); add_message_xml(reply, F_CIB_CALLDATA, output); } return reply; } enum cib_errors cib_process_command(xmlNode *request, xmlNode **reply, xmlNode **cib_diff, gboolean privileged) { xmlNode *input = NULL; xmlNode *output = NULL; xmlNode *result_cib = NULL; xmlNode *current_cib = NULL; #if ENABLE_ACL xmlNode *filtered_current_cib = NULL; #endif int call_type = 0; int call_options = 0; int log_level = LOG_DEBUG_4; const char *op = NULL; const char *section = NULL; enum cib_errors rc = cib_ok; enum cib_errors rc2 = cib_ok; gboolean send_r_notify = FALSE; gboolean global_update = FALSE; gboolean config_changed = FALSE; gboolean manage_counters = TRUE; CRM_ASSERT(cib_status == cib_ok); *reply = NULL; *cib_diff = NULL; current_cib = the_cib; /* Start processing the request... */ op = crm_element_value(request, F_CIB_OPERATION); crm_element_value_int(request, F_CIB_CALLOPTS, &call_options); rc = cib_get_operation_id(op, &call_type); if(rc == cib_ok && privileged == FALSE) { rc = cib_op_can_run(call_type, call_options, privileged, global_update); } rc2 = cib_op_prepare(call_type, request, &input, §ion); if(rc == cib_ok) { rc = rc2; } if(rc != cib_ok) { crm_debug_2("Call setup failed: %s", cib_error2string(rc)); goto done; } else if(cib_op_modifies(call_type) == FALSE) { #if ENABLE_ACL if (acl_enabled(config_hash) == FALSE || acl_filter_cib(request, current_cib, current_cib, &filtered_current_cib) == FALSE) { rc = cib_perform_op(op, call_options, cib_op_func(call_type), TRUE, section, request, input, FALSE, &config_changed, current_cib, &result_cib, NULL, &output); } else if (filtered_current_cib == NULL) { crm_debug("Pre-filtered the entire cib"); rc = cib_permission_denied; } else { crm_debug("Pre-filtered the queried cib according to the ACLs"); rc = cib_perform_op(op, call_options, cib_op_func(call_type), TRUE, section, request, input, FALSE, &config_changed, filtered_current_cib, &result_cib, NULL, &output); } #else rc = cib_perform_op(op, call_options, cib_op_func(call_type), TRUE, section, request, input, FALSE, &config_changed, current_cib, &result_cib, NULL, &output); #endif CRM_CHECK(result_cib == NULL, free_xml(result_cib)); goto done; } /* Handle a valid write action */ global_update = crm_is_true(crm_element_value(request, F_CIB_GLOBAL_UPDATE)); if(global_update) { manage_counters = FALSE; call_options |= cib_force_diff; CRM_CHECK(call_type == 3 || call_type == 4, crm_err("Call type: %d", call_type); crm_log_xml(LOG_ERR, "bad op", request)); } #ifdef SUPPORT_PRENOTIFY if((call_options & cib_inhibit_notify) == 0) { cib_pre_notify(call_options, op, the_cib, input); } #endif if(rc == cib_ok) { if(call_options & cib_inhibit_bcast) { /* skip */ crm_debug_2("Skipping update: inhibit broadcast"); manage_counters = FALSE; } rc = cib_perform_op(op, call_options, cib_op_func(call_type), FALSE, section, request, input, manage_counters, &config_changed, current_cib, &result_cib, cib_diff, &output); #if ENABLE_ACL if (acl_enabled(config_hash) == TRUE && acl_check_diff(request, current_cib, result_cib, *cib_diff) == FALSE) { rc = cib_permission_denied; } #endif if(manage_counters == FALSE) { config_changed = cib_config_changed(current_cib, result_cib, cib_diff); } /* Always write to disk for replace ops, * this negates the need to detect ordering changes */ if(config_changed == FALSE && crm_str_eq(CIB_OP_REPLACE, op, TRUE)) { config_changed = TRUE; } } if(rc == cib_ok) { rc = activateCibXml(result_cib, config_changed, op); - if (rc == cib_ok && crm_config_changed(*cib_diff)) { + if (rc == cib_ok && cib_internal_config_changed(*cib_diff)) { cib_read_config(config_hash, result_cib); } if(crm_str_eq(CIB_OP_REPLACE, op, TRUE)) { if(section == NULL) { send_r_notify = TRUE; } else if(safe_str_eq(section, XML_TAG_CIB)) { send_r_notify = TRUE; } else if(safe_str_eq(section, XML_CIB_TAG_NODES)) { send_r_notify = TRUE; } else if(safe_str_eq(section, XML_CIB_TAG_STATUS)) { send_r_notify = TRUE; } } else if(crm_str_eq(CIB_OP_ERASE, op, TRUE)) { send_r_notify = TRUE; } } else if(rc == cib_dtd_validation) { if(output != NULL) { crm_log_xml_info(output, "cib:output"); free_xml(output); } #if ENABLE_ACL { xmlNode *filtered_result_cib = NULL; if (acl_enabled(config_hash) == FALSE || acl_filter_cib(request, current_cib, result_cib, &filtered_result_cib) == FALSE) { output = result_cib; } else { crm_debug("Filtered the result cib for output according to the ACLs"); output = filtered_result_cib; if (result_cib != NULL) { free_xml(result_cib); } } } #else output = result_cib; #endif } else { free_xml(result_cib); } if((call_options & cib_inhibit_notify) == 0) { const char *call_id = crm_element_value(request, F_CIB_CALLID); const char *client = crm_element_value(request, F_CIB_CLIENTNAME); #ifdef SUPPORT_POSTNOTIFY cib_post_notify(call_options, op, input, rc, the_cib); #endif cib_diff_notify(call_options, client, call_id, op, input, rc, *cib_diff); } if(send_r_notify) { const char *origin = crm_element_value(request, F_ORIG); cib_replace_notify(origin, the_cib, rc, *cib_diff); } if(rc != cib_ok) { log_level = LOG_DEBUG_4; if(rc == cib_dtd_validation && global_update) { log_level = LOG_WARNING; crm_log_xml_info(input, "cib:global_update"); } } else if(config_changed) { log_level = LOG_DEBUG_3; if(cib_is_master) { log_level = LOG_INFO; } } else if(cib_is_master) { log_level = LOG_DEBUG_2; } log_xml_diff(log_level, *cib_diff, "cib:diff"); done: if((call_options & cib_discard_reply) == 0) { *reply = cib_construct_reply(request, output, rc); /* crm_log_xml_info(*reply, "cib:reply"); */ } #if ENABLE_ACL if (filtered_current_cib != NULL) { free_xml(filtered_current_cib); } #endif if(call_type >= 0) { cib_op_cleanup(call_type, call_options, &input, &output); } return rc; } int send_via_callback_channel(xmlNode *msg, const char *token) { cib_client_t *hash_client = NULL; enum cib_errors rc = cib_ok; crm_debug_3("Delivering msg %p to client %s", msg, token); if(token == NULL) { crm_err("No client id token, cant send message"); if(rc == cib_ok) { rc = cib_missing; } } else if(msg == NULL) { crm_err("No message to send"); rc = cib_reply_failed; } else { /* A client that left before we could reply is not really * _our_ error. Warn instead. */ hash_client = g_hash_table_lookup(client_list, token); if(hash_client == NULL) { crm_warn("Cannot find client for token %s", token); rc = cib_client_gone; } else if (crm_str_eq(hash_client->channel_name, "remote", FALSE)) { /* just hope it's alive */ } else if(hash_client->channel == NULL) { crm_err("Cannot find channel for client %s", token); rc = cib_client_corrupt; } } if(rc == cib_ok) { crm_debug_3("Delivering reply to client %s (%s)", token, hash_client->channel_name); if (crm_str_eq(hash_client->channel_name, "remote", FALSE)) { cib_send_remote_msg(hash_client->channel, msg, hash_client->encrypted); } else if(send_ipc_message(hash_client->channel, msg) == FALSE) { crm_warn("Delivery of reply to client %s/%s failed", hash_client->name, token); rc = cib_reply_failed; } } return rc; } gint cib_GCompareFunc(gconstpointer a, gconstpointer b) { const xmlNode *a_msg = a; const xmlNode *b_msg = b; int msg_a_id = 0; int msg_b_id = 0; const char *value = NULL; value = crm_element_value_const(a_msg, F_CIB_CALLID); msg_a_id = crm_parse_int(value, NULL); value = crm_element_value_const(b_msg, F_CIB_CALLID); msg_b_id = crm_parse_int(value, NULL); if(msg_a_id == msg_b_id) { return 0; } else if(msg_a_id < msg_b_id) { return -1; } return 1; } gboolean cib_process_disconnect(IPC_Channel *channel, cib_client_t *cib_client) { if (channel == NULL) { CRM_LOG_ASSERT(cib_client == NULL); } else if (cib_client == NULL) { crm_err("No client"); } else { CRM_LOG_ASSERT(channel->ch_status != IPC_CONNECT); crm_debug_2("Cleaning up after client disconnect: %s/%s/%s", crm_str(cib_client->name), cib_client->channel_name, cib_client->id); if(cib_client->id != NULL) { if(!g_hash_table_remove(client_list, cib_client->id)) { crm_err("Client %s not found in the hashtable", cib_client->name); } } } if(cib_shutdown_flag && g_hash_table_size(client_list) == 0) { crm_info("All clients disconnected..."); initiate_exit(); } return FALSE; } void cib_ha_peer_callback(HA_Message * msg, void* private_data) { xmlNode *xml = convert_ha_message(NULL, msg, __FUNCTION__); cib_peer_callback(xml, private_data); free_xml(xml); } void cib_peer_callback(xmlNode * msg, void* private_data) { crm_node_t *node = NULL; const char *reason = NULL; const char *originator = crm_element_value(msg, F_ORIG); if(originator == NULL || crm_str_eq(originator, cib_our_uname, TRUE)) { /* message is from ourselves */ return; } else if(crm_peer_cache == NULL) { reason = "membership not established"; goto bail; } node = crm_get_peer(0, originator); if(node == NULL || (node->processes & crm_proc_cib) == 0) { reason = "not in our membership"; goto bail; } if(crm_element_value(msg, F_CIB_CLIENTNAME) == NULL) { crm_xml_add(msg, F_CIB_CLIENTNAME, originator); } /* crm_log_xml(LOG_MSG, "Peer[inbound]", msg); */ cib_process_request(msg, FALSE, TRUE, 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); } } void cib_client_status_callback(const char * node, const char * client, const char * status, void * private) { crm_node_t *member = NULL; if(safe_str_eq(client, CRM_SYSTEM_CIB)) { crm_info("Status update: Client %s/%s now has status [%s]", node, client, status); if(safe_str_eq(status, JOINSTATUS)){ status = ONLINESTATUS; } else if(safe_str_eq(status, LEAVESTATUS)){ status = OFFLINESTATUS; } member = crm_get_peer(0, node); if(member == NULL) { /* Make sure it gets created */ const char *uuid = get_uuid(node); member = crm_update_peer(0, 0, 0, -1, 0, uuid, node, NULL, NULL); } crm_update_peer_proc(node, crm_proc_cib, status); } return; } #if SUPPORT_HEARTBEAT extern oc_ev_t *cib_ev_token; gboolean cib_ccm_dispatch(int fd, gpointer user_data) { int rc = 0; oc_ev_t *ccm_token = (oc_ev_t*)user_data; crm_debug_2("received callback"); rc = oc_ev_handle_event(ccm_token); if(0 == rc) { return TRUE; } crm_err("CCM connection appears to have failed: rc=%d.", rc); /* eventually it might be nice to recover and reconnect... but until then... */ crm_err("Exiting to recover from CCM connection failure"); exit(2); return FALSE; } int current_instance = 0; void cib_ccm_msg_callback( oc_ed_t event, void *cookie, size_t size, const void *data) { gboolean update_id = FALSE; const oc_ev_membership_t *membership = data; CRM_ASSERT(membership != NULL); crm_info("Processing CCM event=%s (id=%d)", ccm_event_name(event), membership->m_instance); if(current_instance > membership->m_instance) { crm_err("Membership instance ID went backwards! %d->%d", current_instance, membership->m_instance); CRM_ASSERT(current_instance <= membership->m_instance); } switch(event) { case OC_EV_MS_NEW_MEMBERSHIP: case OC_EV_MS_INVALID: update_id = TRUE; break; case OC_EV_MS_PRIMARY_RESTORED: update_id = TRUE; break; case OC_EV_MS_NOT_PRIMARY: crm_debug_2("Ignoring transitional CCM event: %s", ccm_event_name(event)); break; case OC_EV_MS_EVICTED: crm_err("Evicted from CCM: %s", ccm_event_name(event)); break; default: crm_err("Unknown CCM event: %d", event); } if(update_id) { unsigned int lpc = 0; CRM_CHECK(membership != NULL, return); current_instance = membership->m_instance; for(lpc=0; lpc < membership->m_n_out; lpc++) { crm_update_ccm_node( membership, lpc+membership->m_out_idx, CRM_NODE_LOST, current_instance); } for(lpc=0; lpc < membership->m_n_member; lpc++) { crm_update_ccm_node( membership, lpc+membership->m_memb_idx,CRM_NODE_ACTIVE, current_instance); } } oc_ev_callback_done(cookie); return; } #endif gboolean can_write(int flags) { return TRUE; } static gboolean cib_force_exit(gpointer data) { crm_notice("Forcing exit!"); terminate_cib(__FUNCTION__); return FALSE; } void initiate_exit(void) { int active = 0; xmlNode *leaving = NULL; active = crm_active_peers(crm_proc_cib); if(active < 2) { terminate_cib(__FUNCTION__); 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, "cib_shutdown_req"); send_cluster_message(NULL, crm_msg_cib, leaving, TRUE); free_xml(leaving); g_timeout_add(crm_get_msec("5s"), cib_force_exit, NULL); } extern int remote_fd; extern int remote_tls_fd; void terminate_cib(const char *caller) { if(remote_fd > 0) { close(remote_fd); } if(remote_tls_fd > 0) { close(remote_tls_fd); } #if SUPPORT_COROSYNC if(is_openais_cluster()) { cib_ha_connection_destroy(NULL); return; } #endif #if SUPPORT_HEARTBEAT if(hb_conn != NULL) { crm_info("%s: Disconnecting heartbeat", caller); hb_conn->llc_ops->signoff(hb_conn, FALSE); } else { crm_err("%s: No heartbeat connection", caller); } #endif uninitializeCib(); crm_info("Exiting..."); if (mainloop != NULL && g_main_is_running(mainloop)) { g_main_quit(mainloop); } else { exit(LSB_EXIT_OK); } } diff --git a/include/crm/cib_util.h b/include/crm/cib_util.h index e892837fc1..18969bb524 100644 --- a/include/crm/cib_util.h +++ b/include/crm/cib_util.h @@ -1,94 +1,94 @@ /* * Copyright (C) 2004 Andrew Beekhof * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef CIB_UTIL__H #define CIB_UTIL__H /* Utility functions */ extern const char *get_object_path(const char *object_type); extern const char *get_object_parent(const char *object_type); extern xmlNode *get_object_root(const char *object_type,xmlNode *the_root); extern xmlNode *create_cib_fragment_adv( xmlNode *update, const char *section, const char *source); /* Error Interpretation*/ extern const char *cib_error2string(enum cib_errors); extern xmlNode *createEmptyCib(void); extern gboolean verifyCibXml(xmlNode *cib); extern int cib_section2enum(const char *a_section); #define create_cib_fragment(update,cib_section) create_cib_fragment_adv(update, cib_section, __FUNCTION__) extern xmlNode *diff_cib_object( xmlNode *old, xmlNode *new,gboolean suppress); extern gboolean apply_cib_diff( xmlNode *old, xmlNode *diff, xmlNode **new); extern void log_cib_diff(int log_level, xmlNode *diff, const char *function); extern gboolean cib_diff_version_details( xmlNode *diff, int *admin_epoch, int *epoch, int *updates, int *_admin_epoch, int *_epoch, int *_updates); extern gboolean cib_version_details( xmlNode *cib, int *admin_epoch, int *epoch, int *updates); extern enum cib_errors update_attr( cib_t *the_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, gboolean to_console); extern enum cib_errors find_nvpair_attr( cib_t *the_cib, const char *attr, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, gboolean to_console, char **value); extern enum cib_errors read_attr( cib_t *the_cib, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, char **attr_value, gboolean to_console); extern enum cib_errors delete_attr( cib_t *the_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, gboolean to_console); extern enum cib_errors query_node_uuid( cib_t *the_cib, const char *uname, char **uuid); extern enum cib_errors query_node_uname( cib_t *the_cib, const char *uuid, char **uname); extern enum cib_errors set_standby( cib_t *the_cib, const char *uuid, const char *scope, const char *standby_value); extern const char *feature_set(xmlNode *xml_obj); extern gboolean startCib(const char *filename); extern xmlNode *get_cib_copy(cib_t *cib); extern xmlNode *cib_get_generation(cib_t *cib); extern int cib_compare_generation(xmlNode *left, xmlNode *right); extern gboolean determine_host(cib_t *cib_conn, char **node_uname, char **node_uuid); extern void cib_metadata(void); extern void verify_cib_options(GHashTable *options); extern const char *cib_pref(GHashTable *options, const char *name); extern gboolean cib_read_config(GHashTable *options, xmlNode *current_cib); -extern gboolean crm_config_changed(xmlNode *diff); +extern gboolean cib_internal_config_changed(xmlNode *diff); #endif diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index 21b977fad2..f3f8e400f9 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.c @@ -1,1013 +1,1013 @@ /* * Copyright (c) 2004 International Business Machines * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct config_root_s { const char *name; const char *parent; const char *path; }; /* * "//crm_config" will also work in place of "/cib/configuration/crm_config" * The / prefix means find starting from the root, whereas the // prefix means * find anywhere and risks multiple matches */ struct config_root_s known_paths[] = { { NULL, NULL, "//cib" }, { XML_TAG_CIB, NULL, "//cib" }, { XML_CIB_TAG_STATUS, "/cib", "//cib/status" }, { XML_CIB_TAG_CONFIGURATION,"/cib", "//cib/configuration" }, { XML_CIB_TAG_CRMCONFIG, "/cib/configuration", "//cib/configuration/crm_config" }, { XML_CIB_TAG_NODES, "/cib/configuration", "//cib/configuration/nodes" }, { XML_CIB_TAG_DOMAINS, "/cib/configuration", "//cib/configuration/domains" }, { XML_CIB_TAG_RESOURCES, "/cib/configuration", "//cib/configuration/resources" }, { XML_CIB_TAG_CONSTRAINTS, "/cib/configuration", "//cib/configuration/constraints" }, { XML_CIB_TAG_OPCONFIG, "/cib/configuration", "//cib/configuration/op_defaults" }, { XML_CIB_TAG_RSCCONFIG, "/cib/configuration", "//cib/configuration/rsc_defaults" }, { XML_CIB_TAG_ACLS, "/cib/configuration", "//cib/configuration/acls" }, { XML_CIB_TAG_SECTION_ALL, NULL, "//cib" }, }; const char * cib_error2string(enum cib_errors return_code) { const char *error_msg = NULL; switch(return_code) { case cib_bad_permissions: error_msg = "bad permissions for the on-disk configuration. shutdown heartbeat and repair."; break; case cib_bad_digest: error_msg = "the on-disk configuration was manually altered. shutdown heartbeat and repair."; break; case cib_bad_config: error_msg = "the on-disk configuration is not valid"; break; case cib_msg_field_add: error_msg = "failed adding field to cib message"; break; case cib_id_check: error_msg = "missing id or id-collision detected"; break; case cib_operation: error_msg = "invalid operation"; break; case cib_create_msg: error_msg = "couldnt create cib message"; break; case cib_client_gone: error_msg = "client left before we could send reply"; break; case cib_not_connected: error_msg = "not connected"; break; case cib_not_authorized: error_msg = "not authorized"; break; case cib_send_failed: error_msg = "send failed"; break; case cib_reply_failed: error_msg = "reply failed"; break; case cib_return_code: error_msg = "no return code"; break; case cib_output_ptr: error_msg = "nowhere to store output"; break; case cib_output_data: error_msg = "corrupt output data"; break; case cib_connection: error_msg = "connection failed"; break; case cib_callback_register: error_msg = "couldnt register callback channel"; break; case cib_authentication: error_msg = ""; break; case cib_registration_msg: error_msg = "invalid registration msg"; break; case cib_callback_token: error_msg = "callback token not found"; break; case cib_missing: error_msg = "cib object missing"; break; case cib_variant: error_msg = "unknown/corrupt cib variant"; break; case CIBRES_MISSING_ID: error_msg = "The id field is missing"; break; case CIBRES_MISSING_TYPE: error_msg = "The type field is missing"; break; case CIBRES_MISSING_FIELD: error_msg = "A required field is missing"; break; case CIBRES_OBJTYPE_MISMATCH: error_msg = "CIBRES_OBJTYPE_MISMATCH"; break; case cib_EXISTS: error_msg = "The object already exists"; break; case cib_NOTEXISTS: error_msg = "The object/attribute does not exist"; break; case CIBRES_CORRUPT: error_msg = "The CIB is corrupt"; break; case cib_NOOBJECT: error_msg = "The update was empty"; break; case cib_NOPARENT: error_msg = "The parent object does not exist"; break; case cib_NODECOPY: error_msg = "Failed while copying update"; break; case CIBRES_OTHER: error_msg = "CIBRES_OTHER"; break; case cib_ok: error_msg = "ok"; break; case cib_unknown: error_msg = "Unknown error"; break; case cib_STALE: error_msg = "Discarded old update"; break; case cib_ACTIVATION: error_msg = "Activation Failed"; break; case cib_NOSECTION: error_msg = "Required section was missing"; break; case cib_NOTSUPPORTED: error_msg = "The action/feature is not supported"; break; case cib_not_master: error_msg = "Local service is not the master instance"; break; case cib_client_corrupt: error_msg = "Service client not valid"; break; case cib_remote_timeout: error_msg = "Remote node did not respond"; break; case cib_master_timeout: error_msg = "No master service is currently active"; break; case cib_revision_unsupported: error_msg = "The required CIB revision number is not supported"; break; case cib_revision_unknown: error_msg = "The CIB revision number could not be determined"; break; case cib_missing_data: error_msg = "Required data for this CIB API call not found"; break; case cib_no_quorum: error_msg = "Write requires quorum"; break; case cib_diff_failed: error_msg = "Application of an update diff failed"; break; case cib_diff_resync: error_msg = "Application of an update diff failed, requesting a full refresh"; break; case cib_bad_section: error_msg = "Invalid CIB section specified"; break; case cib_old_data: error_msg = "Update was older than existing configuration"; break; case cib_dtd_validation: error_msg = "Update does not conform to the configured schema/DTD"; break; case cib_invalid_argument: error_msg = "Invalid argument"; break; case cib_transform_failed: error_msg = "Schema transform failed"; break; case cib_permission_denied: error_msg = "Permission Denied"; break; } if(error_msg == NULL) { crm_err("Unknown CIB Error Code: %d", return_code); error_msg = ""; } return error_msg; } int cib_section2enum(const char *a_section) { if(a_section == NULL || strcasecmp(a_section, "all") == 0) { return cib_section_all; } else if(strcasecmp(a_section, XML_CIB_TAG_NODES) == 0) { return cib_section_nodes; } else if(strcasecmp(a_section, XML_CIB_TAG_STATUS) == 0) { return cib_section_status; } else if(strcasecmp(a_section, XML_CIB_TAG_CONSTRAINTS) == 0) { return cib_section_constraints; } else if(strcasecmp(a_section, XML_CIB_TAG_RESOURCES) == 0) { return cib_section_resources; } else if(strcasecmp(a_section, XML_CIB_TAG_CRMCONFIG) == 0) { return cib_section_crmconfig; } crm_err("Unknown CIB section: %s", a_section); return cib_section_none; } int cib_compare_generation(xmlNode *left, xmlNode *right) { int lpc = 0; const char *attributes[] = { XML_ATTR_GENERATION_ADMIN, XML_ATTR_GENERATION, XML_ATTR_NUMUPDATES, }; crm_log_xml_debug_3(left, "left"); crm_log_xml_debug_3(right, "right"); for(lpc = 0; lpc < DIMOF(attributes); lpc++) { int int_elem_l = -1; int int_elem_r = -1; const char *elem_r = NULL; const char *elem_l = crm_element_value(left, attributes[lpc]); if(right != NULL) { elem_r = crm_element_value(right, attributes[lpc]); } if(elem_l != NULL) { int_elem_l = crm_parse_int(elem_l, NULL); } if(elem_r != NULL) { int_elem_r = crm_parse_int(elem_r, NULL); } if(int_elem_l < int_elem_r) { crm_debug_2("%s (%s < %s)", attributes[lpc], crm_str(elem_l), crm_str(elem_r)); return -1; } else if(int_elem_l > int_elem_r) { crm_debug_2("%s (%s > %s)", attributes[lpc], crm_str(elem_l), crm_str(elem_r)); return 1; } } return 0; } xmlNode* get_cib_copy(cib_t *cib) { xmlNode *xml_cib; int options = cib_scope_local|cib_sync_call; if(cib->cmds->query(cib, NULL, &xml_cib, options) != cib_ok) { crm_err("Couldnt retrieve the CIB"); return NULL; } else if(xml_cib == NULL) { crm_err("The CIB result was empty"); return NULL; } if(safe_str_eq(crm_element_name(xml_cib), XML_TAG_CIB)) { return xml_cib; } free_xml(xml_cib); return NULL; } xmlNode* cib_get_generation(cib_t *cib) { xmlNode *the_cib = get_cib_copy(cib); xmlNode *generation = create_xml_node( NULL, XML_CIB_TAG_GENERATION_TUPPLE); if(the_cib != NULL) { copy_in_properties(generation, the_cib); free_xml(the_cib); } return generation; } void log_cib_diff(int log_level, xmlNode *diff, const char *function) { int add_updates = 0; int add_epoch = 0; int add_admin_epoch = 0; int del_updates = 0; int del_epoch = 0; int del_admin_epoch = 0; if(diff == NULL) { return; } cib_diff_version_details( diff, &add_admin_epoch, &add_epoch, &add_updates, &del_admin_epoch, &del_epoch, &del_updates); if(add_updates != del_updates) { do_crm_log(log_level, "%s: Diff: --- %d.%d.%d", function, del_admin_epoch, del_epoch, del_updates); do_crm_log(log_level, "%s: Diff: +++ %d.%d.%d", function, add_admin_epoch, add_epoch, add_updates); } else if(diff != NULL) { do_crm_log(log_level, "%s: Local-only Change: %d.%d.%d", function, add_admin_epoch, add_epoch, add_updates); } log_xml_diff(log_level, diff, function); } gboolean cib_version_details( xmlNode *cib, int *admin_epoch, int *epoch, int *updates) { *epoch = -1; *updates = -1; *admin_epoch = -1; if(cib == NULL) { return FALSE; } else { crm_element_value_int(cib, XML_ATTR_GENERATION, epoch); crm_element_value_int(cib, XML_ATTR_NUMUPDATES, updates); crm_element_value_int(cib, XML_ATTR_GENERATION_ADMIN, admin_epoch); } return TRUE; } gboolean cib_diff_version_details( xmlNode *diff, int *admin_epoch, int *epoch, int *updates, int *_admin_epoch, int *_epoch, int *_updates) { xmlNode *tmp = NULL; tmp = find_xml_node(diff, "diff-added", FALSE); tmp = find_xml_node(tmp, XML_TAG_CIB, FALSE); cib_version_details(tmp, admin_epoch, epoch, updates); tmp = find_xml_node(diff, "diff-removed", FALSE); cib_version_details(tmp, _admin_epoch, _epoch, _updates); return TRUE; } /* * The caller should never free the return value */ const char *get_object_path(const char *object_type) { int lpc = 0; int max = DIMOF(known_paths); for(; lpc < max; lpc++) { if((object_type == NULL && known_paths[lpc].name == NULL) || safe_str_eq(object_type, known_paths[lpc].name)) { return known_paths[lpc].path; } } return NULL; } const char *get_object_parent(const char *object_type) { int lpc = 0; int max = DIMOF(known_paths); for(; lpc < max; lpc++) { if(safe_str_eq(object_type, known_paths[lpc].name)) { return known_paths[lpc].parent; } } return NULL; } xmlNode* get_object_root(const char *object_type, xmlNode *the_root) { const char *xpath = get_object_path(object_type); if(xpath == NULL) { return the_root; /* or return NULL? */ } return get_xpath_object(xpath, the_root, LOG_DEBUG_4); } xmlNode* create_cib_fragment_adv( xmlNode *update, const char *update_section, const char *source) { xmlNode *cib = NULL; gboolean whole_cib = FALSE; xmlNode *object_root = NULL; char *local_section = NULL; /* crm_debug("Creating a blank fragment: %s", update_section); */ if(update == NULL && update_section == NULL) { crm_debug_3("Creating a blank fragment"); update = createEmptyCib(); crm_xml_add(cib, XML_ATTR_ORIGIN, source); return update; } else if(update == NULL) { crm_err("No update to create a fragment for"); return NULL; } CRM_CHECK(update_section != NULL, return NULL); if(safe_str_eq(crm_element_name(update), XML_TAG_CIB)) { whole_cib = TRUE; } if(whole_cib == FALSE) { cib = createEmptyCib(); crm_xml_add(cib, XML_ATTR_ORIGIN, source); object_root = get_object_root(update_section, cib); add_node_copy(object_root, update); } else { cib = copy_xml(update); crm_xml_add(cib, XML_ATTR_ORIGIN, source); } crm_free(local_section); crm_debug_3("Verifying created fragment"); return cib; } /* * It is the callers responsibility to free both the new CIB (output) * and the new CIB (input) */ xmlNode* createEmptyCib(void) { xmlNode *cib_root = NULL, *config = NULL; cib_root = create_xml_node(NULL, XML_TAG_CIB); config = create_xml_node(cib_root, XML_CIB_TAG_CONFIGURATION); create_xml_node(cib_root, XML_CIB_TAG_STATUS); /* crm_xml_add(cib_root, "version", "1"); */ create_xml_node(config, XML_CIB_TAG_CRMCONFIG); create_xml_node(config, XML_CIB_TAG_NODES); create_xml_node(config, XML_CIB_TAG_RESOURCES); create_xml_node(config, XML_CIB_TAG_CONSTRAINTS); return cib_root; } static unsigned int dtd_throttle = 0; enum cib_errors 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) { int rc = cib_ok; gboolean check_dtd = TRUE; xmlNode *scratch = NULL; xmlNode *local_diff = NULL; const char *current_dtd = "unknown"; CRM_CHECK(output != NULL, return cib_output_data); CRM_CHECK(result_cib != NULL, return cib_output_data); CRM_CHECK(config_changed != NULL, return cib_output_data); *output = NULL; *result_cib = NULL; *config_changed = FALSE; if(fn == NULL) { return cib_operation; } if(is_query) { rc = (*fn)(op, call_options, section, req, input, current_cib, result_cib, output); return rc; } scratch = copy_xml(current_cib); rc = (*fn)(op, call_options, section, req, input, current_cib, &scratch, output); CRM_CHECK(current_cib != scratch, return cib_unknown); if(rc == cib_ok && scratch == NULL) { rc = cib_unknown; } if(rc == cib_ok && scratch) { const char *new_version = crm_element_value(scratch, XML_ATTR_CRM_VERSION); if(new_version && compare_version(new_version, CRM_FEATURE_SET) > 0) { crm_err("Discarding update with feature set '%s' greater than our own '%s'", new_version, CRM_FEATURE_SET); rc = cib_NOTSUPPORTED; } } if(rc == cib_ok && current_cib) { int old = 0; int new = 0; crm_element_value_int(scratch, XML_ATTR_GENERATION_ADMIN, &new); crm_element_value_int(current_cib, XML_ATTR_GENERATION_ADMIN, &old); if(old > new) { crm_err("%s went backwards: %d -> %d (Opts: 0x%x)", XML_ATTR_GENERATION_ADMIN, old, new, call_options); crm_log_xml_warn(req, "Bad Op"); crm_log_xml_warn(input, "Bad Data"); rc = cib_old_data; } else if(old == new) { crm_element_value_int(scratch, XML_ATTR_GENERATION, &new); crm_element_value_int(current_cib, XML_ATTR_GENERATION, &old); if(old > new) { crm_err("%s went backwards: %d -> %d (Opts: 0x%x)", XML_ATTR_GENERATION, old, new, call_options); crm_log_xml_warn(req, "Bad Op"); crm_log_xml_warn(input, "Bad Data"); rc = cib_old_data; } } } if(rc == cib_ok) { fix_plus_plus_recursive(scratch); current_dtd = crm_element_value(scratch, XML_ATTR_VALIDATION); if(manage_counters) { if(is_set(call_options, cib_inhibit_bcast) && safe_str_eq(section, XML_CIB_TAG_STATUS)) { /* Fast-track changes connections which wont be broadcasting anywhere */ cib_update_counter(scratch, XML_ATTR_NUMUPDATES, FALSE); goto done; } /* The diff calculation in cib_config_changed() accounts for 25% of the * CIB's total CPU usage on the DC * * RNG validation on the otherhand, accounts for only 9%... */ *config_changed = cib_config_changed(current_cib, scratch, &local_diff); if(*config_changed) { cib_update_counter(scratch, XML_ATTR_NUMUPDATES, TRUE); cib_update_counter(scratch, XML_ATTR_GENERATION, FALSE); } else { /* Previously we only did this if the diff detected a change * * But we replies are still sent, even if nothing changes so we * don't save any network traffic and means we need to jump * through expensive hoops to detect ordering changes - see below */ cib_update_counter(scratch, XML_ATTR_NUMUPDATES, FALSE); if(local_diff == NULL) { /* Nothing to check */ check_dtd = FALSE; /* Create a fake diff so that notifications, which include a _digest_, * will be sent to our peers * * This is the cheapest way to detect changes to group/set ordering * * Previously we compared the old and new digest in cib_config_changed(), * but that accounted for 15% of the CIB's total CPU usage on the DC */ local_diff = create_xml_node(NULL, "diff"); crm_xml_add(local_diff, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); create_xml_node(local_diff, "diff-removed"); create_xml_node(local_diff, "diff-added"); /* Usually these are attrd re-updates */ crm_log_xml_trace(req, "Non-change"); } else if(dtd_throttle++ % 20) { /* Throttle the amount of costly validation we perform due to status updates * a) we don't really care whats in the status section * b) we don't validate any of it's contents at the moment anyway */ check_dtd = FALSE; } } } } if(diff != NULL && local_diff != NULL) { /* Only fix the diff if we'll return it... */ xmlNode *cib = NULL; xmlNode *diff_child = NULL; const char *tag = NULL; const char *value = NULL; tag = "diff-removed"; diff_child = find_xml_node(local_diff, tag, FALSE); if(diff_child == NULL) { diff_child = create_xml_node(local_diff, tag); } tag = XML_TAG_CIB; cib = find_xml_node(diff_child, tag, FALSE); if(cib == NULL) { cib = create_xml_node(diff_child, tag); } tag = XML_ATTR_GENERATION_ADMIN; value = crm_element_value(current_cib, tag); crm_xml_add(diff_child, tag, value); if(*config_changed) { crm_xml_add(cib, tag, value); } tag = XML_ATTR_GENERATION; value = crm_element_value(current_cib, tag); crm_xml_add(diff_child, tag, value); if(*config_changed) { crm_xml_add(cib, tag, value); } tag = XML_ATTR_NUMUPDATES; value = crm_element_value(current_cib, tag); crm_xml_add(cib, tag, value); crm_xml_add(diff_child, tag, value); tag = "diff-added"; diff_child = find_xml_node(local_diff, tag, FALSE); if(diff_child == NULL) { diff_child = create_xml_node(local_diff, tag); } tag = XML_TAG_CIB; cib = find_xml_node(diff_child, tag, FALSE); if(cib == NULL) { cib = create_xml_node(diff_child, tag); } xml_prop_iter(scratch, p_name, p_value, xmlSetProp(cib, (const xmlChar*)p_name, (const xmlChar*)p_value)); crm_log_xml_trace(local_diff, "Repaired-diff"); *diff = local_diff; local_diff = NULL; } done: if(rc == cib_ok && check_dtd && validate_xml(scratch, NULL, TRUE) == FALSE) { crm_warn("Updated CIB does not validate against %s schema/dtd", crm_str(current_dtd)); rc = cib_dtd_validation; } *result_cib = scratch; free_xml(local_diff); return rc; } int get_channel_token(IPC_Channel *ch, char **token) { int rc = cib_ok; xmlNode *reg_msg = NULL; const char *msg_type = NULL; const char *tmp_ticket = NULL; CRM_CHECK(ch != NULL, return cib_missing); CRM_CHECK(token != NULL, return cib_output_ptr); crm_debug_4("Waiting for msg on command channel"); reg_msg = xmlfromIPC(ch, MAX_IPC_DELAY); if(ch->ops->get_chan_status(ch) != IPC_CONNECT) { crm_err("No reply message - disconnected"); free_xml(reg_msg); return cib_not_connected; } else if(reg_msg == NULL) { crm_err("No reply message - empty"); return cib_reply_failed; } msg_type = crm_element_value(reg_msg, F_CIB_OPERATION); tmp_ticket = crm_element_value(reg_msg, F_CIB_CLIENTID); if(safe_str_neq(msg_type, CRM_OP_REGISTER) ) { crm_err("Invalid registration message: %s", msg_type); rc = cib_registration_msg; } else if(tmp_ticket == NULL) { rc = cib_callback_token; } else { *token = crm_strdup(tmp_ticket); } free_xml(reg_msg); return rc; } xmlNode * cib_create_op( int call_id, const char *token, const char *op, const char *host, const char *section, xmlNode *data, int call_options) { int rc = HA_OK; xmlNode *op_msg = create_xml_node(NULL, "cib_command"); CRM_CHECK(op_msg != NULL, return NULL); CRM_CHECK(token != NULL, return NULL); crm_xml_add(op_msg, F_XML_TAGNAME, "cib_command"); crm_xml_add(op_msg, F_TYPE, T_CIB); crm_xml_add(op_msg, F_CIB_CALLBACK_TOKEN, token); crm_xml_add(op_msg, F_CIB_OPERATION, op); crm_xml_add(op_msg, F_CIB_HOST, host); crm_xml_add(op_msg, F_CIB_SECTION, section); crm_xml_add_int(op_msg, F_CIB_CALLID, call_id); crm_debug_4("Sending call options: %.8lx, %d", (long)call_options, call_options); crm_xml_add_int(op_msg, F_CIB_CALLOPTS, call_options); if(data != NULL) { add_message_xml(op_msg, F_CIB_CALLDATA, data); } if (rc != HA_OK) { crm_err("Failed to create CIB operation message"); crm_log_xml(LOG_ERR, "op", op_msg); free_xml(op_msg); return NULL; } if(call_options & cib_inhibit_bcast) { CRM_CHECK((call_options & cib_scope_local), return NULL); } return op_msg; } void cib_native_callback(cib_t *cib, xmlNode *msg, int call_id, int rc) { xmlNode *output = NULL; cib_callback_client_t *blob = NULL; cib_callback_client_t local_blob; local_blob.id = NULL; local_blob.callback = NULL; local_blob.user_data = NULL; local_blob.only_success = FALSE; if(msg != NULL) { crm_element_value_int(msg, F_CIB_RC, &rc); crm_element_value_int(msg, F_CIB_CALLID, &call_id); output = get_message_xml(msg, F_CIB_CALLDATA); } blob = g_hash_table_lookup( cib_op_callback_table, GINT_TO_POINTER(call_id)); if(blob != NULL) { local_blob = *blob; blob = NULL; remove_cib_op_callback(call_id, FALSE); } else { crm_debug_2("No callback found for call %d", call_id); local_blob.callback = NULL; } if(cib == NULL) { crm_debug("No cib object supplied"); } if(rc == cib_diff_resync) { /* This is an internal value that clients do not and should not care about */ rc = cib_ok; } if(local_blob.callback != NULL && (rc == cib_ok || local_blob.only_success == FALSE)) { crm_debug_2("Invoking callback %s for call %d", crm_str(local_blob.id), call_id); local_blob.callback(msg, call_id, rc, output, local_blob.user_data); } else if(cib && cib->op_callback == NULL && rc != cib_ok) { crm_warn("CIB command failed: %s", cib_error2string(rc)); crm_log_xml(LOG_DEBUG, "Failed CIB Update", msg); } if(cib && cib->op_callback != NULL) { crm_debug_2("Invoking global callback for call %d", call_id); cib->op_callback(msg, call_id, rc, output); } crm_debug_4("OP callback activated."); } void cib_native_notify(gpointer data, gpointer user_data) { xmlNode *msg = user_data; cib_notify_client_t *entry = data; const char *event = NULL; if(msg == NULL) { crm_warn("Skipping callback - NULL message"); return; } event = crm_element_value(msg, F_SUBTYPE); if(entry == NULL) { crm_warn("Skipping callback - NULL callback client"); return; } else if(entry->callback == NULL) { crm_warn("Skipping callback - NULL callback"); return; } else if(safe_str_neq(entry->event, event)) { crm_debug_4("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event); return; } crm_debug_4("Invoking callback for %p/%s event...", entry, event); entry->callback(event, msg); crm_debug_4("Callback invoked..."); } gboolean determine_host(cib_t *cib_conn, char **node_uname, char **node_uuid) { CRM_CHECK(node_uname != NULL, return FALSE); if(*node_uname == NULL) { struct utsname name; if(uname(&name) < 0) { crm_perror(LOG_ERR,"uname(2) call failed"); return FALSE; } *node_uname = crm_strdup(name.nodename); crm_info("Detected uname: %s", *node_uname); } if(cib_conn && *node_uname != NULL && node_uuid != NULL && *node_uuid == NULL) { int rc = query_node_uuid(cib_conn, *node_uname, node_uuid); if(rc != cib_ok) { fprintf(stderr,"Could not map uname=%s to a UUID: %s\n", *node_uname, cib_error2string(rc)); return FALSE; } crm_info("Mapped %s to %s", *node_uname, crm_str(*node_uuid)); } return TRUE; } pe_cluster_option cib_opts[] = { /* name, old-name, validate, default, description */ { "enable-acl", NULL, "boolean", NULL, "false", &check_boolean, "Enable CIB ACL", NULL }, }; void cib_metadata(void) { config_metadata("Cluster Information Base", "1.0", "Cluster Information Base Options", "This is a fake resource that details the options that can be configured for the Cluster Information Base.", cib_opts, DIMOF(cib_opts)); } void verify_cib_options(GHashTable *options) { verify_all_options(options, cib_opts, DIMOF(cib_opts)); } const char * cib_pref(GHashTable *options, const char *name) { return get_cluster_pref(options, cib_opts, DIMOF(cib_opts), name); } gboolean cib_read_config(GHashTable *options, xmlNode *current_cib) { xmlNode *config = NULL; ha_time_t *now = NULL; if (options == NULL || current_cib == NULL) { return FALSE; } now = new_ha_date(TRUE); g_hash_table_remove_all(options); config = get_object_root(XML_CIB_TAG_CRMCONFIG, current_cib); if (config) { unpack_instance_attributes( current_cib, config, XML_CIB_TAG_PROPSET, NULL, options, CIB_OPTIONS_FIRST, FALSE, now); } verify_cib_options(options); free_ha_date(now); return TRUE; } gboolean -crm_config_changed(xmlNode *diff) +cib_internal_config_changed(xmlNode *diff) { gboolean changed = FALSE; const char *config_xpath = "//"XML_TAG_CIB"/"XML_CIB_TAG_CONFIGURATION"/"XML_CIB_TAG_CRMCONFIG; xmlXPathObject *xpathObj = NULL; if (diff == NULL) { return FALSE; } xpathObj = xpath_search(diff, config_xpath); if (xpathObj && xpathObj->nodesetval->nodeNr > 0) { changed = TRUE; } if (xpathObj) { xmlXPathFreeObject(xpathObj); } return changed; }