| /* |
| * Copyright 2004-2019 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 <crm_internal.h> |
| |
| #include <sys/param.h> |
| |
| #include <crm/crm.h> |
| |
| #include <crm/msg_xml.h> |
| |
| #include <crm/pengine/rules.h> |
| #include <crm/cluster/internal.h> |
| #include <crm/cluster/election.h> |
| #include <crm/common/ipcs.h> |
| |
| #include <crmd.h> |
| #include <crmd_fsa.h> |
| #include <fsa_proto.h> |
| #include <crmd_messages.h> |
| #include <crmd_callbacks.h> |
| #include <crmd_lrm.h> |
| #include <crmd_alerts.h> |
| #include <crmd_metadata.h> |
| #include <tengine.h> |
| #include <throttle.h> |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| |
| #ifdef RHEL7_COMPAT |
| // for pe_enable_legacy_alerts() |
| #include <crm/pengine/rules_internal.h> |
| #endif |
| |
| qb_ipcs_service_t *ipcs = NULL; |
| |
| extern gboolean crm_connect_corosync(crm_cluster_t * cluster); |
| extern void crmd_ha_connection_destroy(gpointer user_data); |
| |
| void crm_shutdown(int nsig); |
| gboolean crm_read_options(gpointer user_data); |
| |
| gboolean fsa_has_quorum = FALSE; |
| crm_trigger_t *fsa_source = NULL; |
| crm_trigger_t *config_read = NULL; |
| bool no_quorum_suicide_escalation = FALSE; |
| |
| /* A_HA_CONNECT */ |
| void |
| do_ha_control(long long action, |
| enum crmd_fsa_cause cause, |
| enum crmd_fsa_state cur_state, |
| enum crmd_fsa_input current_input, fsa_data_t * msg_data) |
| { |
| gboolean registered = FALSE; |
| static crm_cluster_t *cluster = NULL; |
| |
| if (cluster == NULL) { |
| cluster = calloc(1, sizeof(crm_cluster_t)); |
| } |
| |
| if (action & A_HA_DISCONNECT) { |
| crm_cluster_disconnect(cluster); |
| crm_info("Disconnected from the cluster"); |
| |
| set_bit(fsa_input_register, R_HA_DISCONNECTED); |
| } |
| |
| if (action & A_HA_CONNECT) { |
| crm_set_status_callback(&peer_update_callback); |
| crm_set_autoreap(FALSE); |
| |
| if (is_openais_cluster()) { |
| #if SUPPORT_COROSYNC |
| registered = crm_connect_corosync(cluster); |
| #endif |
| } else if (is_heartbeat_cluster()) { |
| #if SUPPORT_HEARTBEAT |
| cluster->destroy = crmd_ha_connection_destroy; |
| cluster->hb_dispatch = crmd_ha_msg_callback; |
| |
| registered = crm_cluster_connect(cluster); |
| fsa_cluster_conn = cluster->hb_conn; |
| |
| crm_trace("Be informed of Node Status changes"); |
| if (registered && |
| fsa_cluster_conn->llc_ops->set_nstatus_callback(fsa_cluster_conn, |
| crmd_ha_status_callback, |
| fsa_cluster_conn) != HA_OK) { |
| |
| crm_err("Cannot set nstatus callback: %s", |
| fsa_cluster_conn->llc_ops->errmsg(fsa_cluster_conn)); |
| registered = FALSE; |
| } |
| |
| crm_trace("Be informed of CRM Client Status changes"); |
| if (registered && |
| fsa_cluster_conn->llc_ops->set_cstatus_callback(fsa_cluster_conn, |
| crmd_client_status_callback, |
| fsa_cluster_conn) != HA_OK) { |
| |
| crm_err("Cannot set cstatus callback: %s", |
| fsa_cluster_conn->llc_ops->errmsg(fsa_cluster_conn)); |
| registered = FALSE; |
| } |
| |
| if (registered) { |
| crm_trace("Requesting an initial dump of CRMD client_status"); |
| fsa_cluster_conn->llc_ops->client_status(fsa_cluster_conn, NULL, CRM_SYSTEM_CRMD, |
| -1); |
| } |
| #endif |
| } |
| |
| if (registered == TRUE) { |
| controld_election_init(cluster->uname); |
| fsa_our_uname = cluster->uname; |
| fsa_our_uuid = cluster->uuid; |
| if(cluster->uuid == NULL) { |
| crm_err("Could not obtain local uuid"); |
| registered = FALSE; |
| } |
| } |
| |
| if (registered == FALSE) { |
| set_bit(fsa_input_register, R_HA_DISCONNECTED); |
| register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); |
| return; |
| } |
| |
| populate_cib_nodes(node_update_none, __FUNCTION__); |
| clear_bit(fsa_input_register, R_HA_DISCONNECTED); |
| crm_info("Connected to the cluster"); |
| } |
| |
| if (action & ~(A_HA_CONNECT | A_HA_DISCONNECT)) { |
| crm_err("Unexpected action %s in %s", fsa_action2string(action), __FUNCTION__); |
| } |
| } |
| |
| static bool |
| need_spawn_pengine_from_crmd(void) |
| { |
| static int result = -1; |
| |
| if (result != -1) |
| return result; |
| if (!is_heartbeat_cluster()) { |
| result = 0; |
| return result; |
| } |
| |
| /* NULL, or "strange" value: rather spawn from here. */ |
| result = TRUE; |
| crm_str_to_boolean(daemon_option("crmd_spawns_pengine"), &result); |
| return result; |
| } |
| |
| /* A_SHUTDOWN */ |
| void |
| do_shutdown(long long action, |
| enum crmd_fsa_cause cause, |
| enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) |
| { |
| /* just in case */ |
| set_bit(fsa_input_register, R_SHUTDOWN); |
| |
| if (need_spawn_pengine_from_crmd()) { |
| if (is_set(fsa_input_register, pe_subsystem->flag_connected)) { |
| crm_info("Terminating the %s", pe_subsystem->name); |
| if (stop_subsystem(pe_subsystem, TRUE) == FALSE) { |
| /* It's gone ... */ |
| crm_err("Faking %s exit", pe_subsystem->name); |
| clear_bit(fsa_input_register, pe_subsystem->flag_connected); |
| } else { |
| crm_info("Waiting for subsystems to exit"); |
| crmd_fsa_stall(FALSE); |
| } |
| } |
| crm_info("All subsystems stopped, continuing"); |
| } |
| |
| if (stonith_api) { |
| /* Prevent it from coming up again */ |
| clear_bit(fsa_input_register, R_ST_REQUIRED); |
| |
| crm_info("Disconnecting STONITH..."); |
| if (stonith_api->state != stonith_disconnected) { |
| stonith_api->cmds->disconnect(stonith_api); |
| } |
| stonith_api->cmds->remove_notification(stonith_api, T_STONITH_NOTIFY_DISCONNECT); |
| stonith_api->cmds->remove_notification(stonith_api, T_STONITH_NOTIFY_FENCE); |
| stonith_api->cmds->remove_notification(stonith_api, T_STONITH_NOTIFY_HISTORY_SYNCED); |
| } |
| } |
| |
| /* A_SHUTDOWN_REQ */ |
| void |
| do_shutdown_req(long long action, |
| enum crmd_fsa_cause cause, |
| enum crmd_fsa_state cur_state, |
| enum crmd_fsa_input current_input, fsa_data_t * msg_data) |
| { |
| xmlNode *msg = NULL; |
| |
| set_bit(fsa_input_register, R_SHUTDOWN); |
| crm_info("Sending shutdown request to all peers (DC is %s)", |
| (fsa_our_dc? fsa_our_dc : "not set")); |
| msg = create_request(CRM_OP_SHUTDOWN_REQ, NULL, NULL, CRM_SYSTEM_CRMD, CRM_SYSTEM_CRMD, NULL); |
| |
| /* set_bit(fsa_input_register, R_STAYDOWN); */ |
| if (send_cluster_message(NULL, crm_msg_crmd, msg, TRUE) == FALSE) { |
| register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); |
| } |
| free_xml(msg); |
| } |
| |
| extern crm_ipc_t *attrd_ipc; |
| extern char *max_generation_from; |
| extern xmlNode *max_generation_xml; |
| extern GHashTable *resource_history; |
| extern GHashTable *voted; |
| extern char *te_client_id; |
| |
| void log_connected_client(gpointer key, gpointer value, gpointer user_data); |
| |
| void |
| log_connected_client(gpointer key, gpointer value, gpointer user_data) |
| { |
| crm_client_t *client = value; |
| |
| crm_err("%s is still connected at exit", crm_client_name(client)); |
| } |
| |
| int |
| crmd_fast_exit(int rc) |
| { |
| if (is_set(fsa_input_register, R_STAYDOWN)) { |
| crm_warn("Inhibiting respawn "CRM_XS" remapping exit code %d to %d", |
| rc, DAEMON_RESPAWN_STOP); |
| rc = DAEMON_RESPAWN_STOP; |
| } |
| |
| if (rc == pcmk_ok && is_set(fsa_input_register, R_IN_RECOVERY)) { |
| crm_err("Could not recover from internal error"); |
| rc = pcmk_err_generic; |
| } |
| return crm_exit(rc); |
| } |
| |
| int |
| crmd_exit(int rc) |
| { |
| GListPtr gIter = NULL; |
| GMainLoop *mloop = crmd_mainloop; |
| |
| static bool in_progress = FALSE; |
| |
| if(in_progress && rc == 0) { |
| crm_debug("Exit is already in progress"); |
| return rc; |
| |
| } else if(in_progress) { |
| crm_notice("Error during shutdown process, terminating now with status %d: %s", |
| rc, pcmk_strerror(rc)); |
| crm_write_blackbox(SIGTRAP, NULL); |
| crmd_fast_exit(rc); |
| } |
| |
| in_progress = TRUE; |
| crm_trace("Preparing to exit: %d", rc); |
| |
| /* Suppress secondary errors resulting from us disconnecting everything */ |
| set_bit(fsa_input_register, R_HA_DISCONNECTED); |
| |
| /* Close all IPC servers and clients to ensure any and all shared memory files are cleaned up */ |
| |
| if(ipcs) { |
| crm_trace("Closing IPC server"); |
| mainloop_del_ipc_server(ipcs); |
| ipcs = NULL; |
| } |
| |
| if (attrd_ipc) { |
| crm_trace("Closing attrd connection"); |
| crm_ipc_close(attrd_ipc); |
| crm_ipc_destroy(attrd_ipc); |
| attrd_ipc = NULL; |
| } |
| |
| if (pe_subsystem && pe_subsystem->client && pe_subsystem->client->ipcs) { |
| crm_trace("Disconnecting Policy Engine"); |
| |
| // If we aren't connected to the scheduler, we can't expect a reply |
| controld_expect_sched_reply(NULL); |
| |
| qb_ipcs_disconnect(pe_subsystem->client->ipcs); |
| } |
| |
| if(stonith_api) { |
| crm_trace("Disconnecting fencing API"); |
| clear_bit(fsa_input_register, R_ST_REQUIRED); |
| stonith_api->cmds->free(stonith_api); stonith_api = NULL; |
| } |
| |
| if (rc == pcmk_ok && crmd_mainloop == NULL) { |
| crm_debug("No mainloop detected"); |
| rc = EPROTO; |
| } |
| |
| /* On an error, just get out. |
| * |
| * Otherwise, make the effort to have mainloop exit gracefully so |
| * that it (mostly) cleans up after itself and valgrind has less |
| * to report on - allowing real errors stand out |
| */ |
| if(rc != pcmk_ok) { |
| crm_notice("Forcing immediate exit with status %d: %s", |
| rc, pcmk_strerror(rc)); |
| crm_write_blackbox(SIGTRAP, NULL); |
| return crmd_fast_exit(rc); |
| } |
| |
| /* Clean up as much memory as possible for valgrind */ |
| |
| for (gIter = fsa_message_queue; gIter != NULL; gIter = gIter->next) { |
| fsa_data_t *fsa_data = gIter->data; |
| |
| crm_info("Dropping %s: [ state=%s cause=%s origin=%s ]", |
| fsa_input2string(fsa_data->fsa_input), |
| fsa_state2string(fsa_state), |
| fsa_cause2string(fsa_data->fsa_cause), fsa_data->origin); |
| delete_fsa_input(fsa_data); |
| } |
| |
| clear_bit(fsa_input_register, R_MEMBERSHIP); |
| g_list_free(fsa_message_queue); fsa_message_queue = NULL; |
| |
| free(pe_subsystem); pe_subsystem = NULL; |
| free(te_subsystem); te_subsystem = NULL; |
| free(cib_subsystem); cib_subsystem = NULL; |
| |
| metadata_cache_fini(); |
| controld_election_fini(); |
| |
| /* Tear down the CIB connection, but don't free it yet -- it could be used |
| * when we drain the mainloop later. |
| */ |
| cib_free_callbacks(fsa_cib_conn); |
| fsa_cib_conn->cmds->signoff(fsa_cib_conn); |
| |
| verify_stopped(fsa_state, LOG_WARNING); |
| clear_bit(fsa_input_register, R_LRM_CONNECTED); |
| lrm_state_destroy_all(); |
| |
| /* This basically will not work, since mainloop has a reference to it */ |
| mainloop_destroy_trigger(fsa_source); fsa_source = NULL; |
| |
| mainloop_destroy_trigger(config_read); config_read = NULL; |
| mainloop_destroy_trigger(stonith_reconnect); stonith_reconnect = NULL; |
| mainloop_destroy_trigger(transition_trigger); transition_trigger = NULL; |
| |
| crm_client_cleanup(); |
| crm_peer_destroy(); |
| |
| crm_timer_stop(transition_timer); |
| crm_timer_stop(integration_timer); |
| crm_timer_stop(finalization_timer); |
| crm_timer_stop(election_trigger); |
| crm_timer_stop(shutdown_escalation_timer); |
| crm_timer_stop(wait_timer); |
| crm_timer_stop(recheck_timer); |
| |
| te_cleanup_stonith_history_sync(NULL, TRUE); |
| controld_free_sched_timer(); |
| |
| free(transition_timer); transition_timer = NULL; |
| free(integration_timer); integration_timer = NULL; |
| free(finalization_timer); finalization_timer = NULL; |
| free(election_trigger); election_trigger = NULL; |
| free(shutdown_escalation_timer); shutdown_escalation_timer = NULL; |
| free(wait_timer); wait_timer = NULL; |
| free(recheck_timer); recheck_timer = NULL; |
| |
| free(fsa_our_dc_version); fsa_our_dc_version = NULL; |
| free(fsa_our_uname); fsa_our_uname = NULL; |
| free(fsa_our_uuid); fsa_our_uuid = NULL; |
| free(fsa_our_dc); fsa_our_dc = NULL; |
| |
| free(fsa_cluster_name); fsa_cluster_name = NULL; |
| |
| free(te_uuid); te_uuid = NULL; |
| free(te_client_id); te_client_id = NULL; |
| free(failed_stop_offset); failed_stop_offset = NULL; |
| free(failed_start_offset); failed_start_offset = NULL; |
| |
| free(max_generation_from); max_generation_from = NULL; |
| free_xml(max_generation_xml); max_generation_xml = NULL; |
| |
| mainloop_destroy_signal(SIGPIPE); |
| mainloop_destroy_signal(SIGUSR1); |
| mainloop_destroy_signal(SIGTERM); |
| mainloop_destroy_signal(SIGTRAP); |
| /* leave SIGCHLD engaged as we might still want to drain some service-actions */ |
| |
| if (mloop) { |
| GMainContext *ctx = g_main_loop_get_context(crmd_mainloop); |
| |
| /* Don't re-enter this block */ |
| crmd_mainloop = NULL; |
| |
| /* no signals on final draining anymore */ |
| mainloop_destroy_signal(SIGCHLD); |
| |
| crm_trace("Draining mainloop %d %d", g_main_loop_is_running(mloop), g_main_context_pending(ctx)); |
| |
| { |
| int lpc = 0; |
| |
| while((g_main_context_pending(ctx) && lpc < 10)) { |
| lpc++; |
| crm_trace("Iteration %d", lpc); |
| g_main_context_dispatch(ctx); |
| } |
| } |
| |
| crm_trace("Closing mainloop %d %d", g_main_loop_is_running(mloop), g_main_context_pending(ctx)); |
| g_main_loop_quit(mloop); |
| |
| #if SUPPORT_HEARTBEAT |
| /* Do this only after g_main_loop_quit(). |
| * |
| * This interface was broken (incomplete) since it was introduced. |
| * ->delete() does cleanup and free most of it, but it does not |
| * actually remove and destroy the corresponding GSource, so the next |
| * prepare/check iteratioin would find a corrupt (because partially |
| * freed) GSource, and segfault. |
| * |
| * Apparently one was supposed to store the GSource as returned by |
| * G_main_add_ll_cluster(), and g_source_destroy() that "by hand". |
| * |
| * But no-one ever did this, not even in the old hb code when this was |
| * introduced. |
| * |
| * Note that fsa_cluster_conn was set as an "alias" to cluster->hb_conn |
| * in do_ha_control() right after crm_cluster_connect(), and only |
| * happens to still point at that object, because do_ha_control() does |
| * not reset it to NULL after crm_cluster_disconnect() above does |
| * reset cluster->hb_conn to NULL. |
| * Not sure if that's something to cleanup, too. |
| * |
| * I'll try to fix this up in heartbeat proper, so ->delete |
| * will actually remove, and destroy, and unref, and free this thing. |
| * Doing so after g_main_loop_quit() is valid with both old, |
| * and eventually fixed heartbeat. |
| * |
| * If we introduce the "by hand" destroy/remove/unref, |
| * this may break again once heartbeat is fixed :-( |
| * |
| * -- Lars Ellenberg |
| */ |
| if (fsa_cluster_conn) { |
| crm_trace("Deleting heartbeat api object"); |
| fsa_cluster_conn->llc_ops->delete(fsa_cluster_conn); |
| fsa_cluster_conn = NULL; |
| } |
| #endif |
| |
| /* Won't do anything yet, since we're inside it now */ |
| g_main_loop_unref(mloop); |
| |
| crm_trace("Done %d", rc); |
| } else { |
| mainloop_destroy_signal(SIGCHLD); |
| } |
| |
| cib_delete(fsa_cib_conn); |
| fsa_cib_conn = NULL; |
| |
| throttle_fini(); |
| |
| /* Graceful */ |
| return rc; |
| } |
| |
| /* A_EXIT_0, A_EXIT_1 */ |
| void |
| do_exit(long long action, |
| enum crmd_fsa_cause cause, |
| enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) |
| { |
| int exit_code = pcmk_ok; |
| int log_level = LOG_INFO; |
| const char *exit_type = "gracefully"; |
| |
| if (action & A_EXIT_1) { |
| /* exit_code = pcmk_err_generic; */ |
| log_level = LOG_ERR; |
| exit_type = "forcefully"; |
| exit_code = pcmk_err_generic; |
| } |
| |
| verify_stopped(cur_state, LOG_ERR); |
| do_crm_log(log_level, "Performing %s - %s exiting the CRMd", |
| fsa_action2string(action), exit_type); |
| |
| crm_info("[%s] stopped (%d)", crm_system_name, exit_code); |
| crmd_exit(exit_code); |
| } |
| |
| static void sigpipe_ignore(int nsig) { return; } |
| |
| /* A_STARTUP */ |
| void |
| do_startup(long long action, |
| enum crmd_fsa_cause cause, |
| enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) |
| { |
| int was_error = 0; |
| |
| crm_debug("Registering Signal Handlers"); |
| mainloop_add_signal(SIGTERM, crm_shutdown); |
| mainloop_add_signal(SIGPIPE, sigpipe_ignore); |
| |
| fsa_source = mainloop_add_trigger(G_PRIORITY_HIGH, crm_fsa_trigger, NULL); |
| config_read = mainloop_add_trigger(G_PRIORITY_HIGH, crm_read_options, NULL); |
| transition_trigger = mainloop_add_trigger(G_PRIORITY_LOW, te_graph_trigger, NULL); |
| |
| crm_debug("Creating CIB and LRM objects"); |
| fsa_cib_conn = cib_new(); |
| |
| lrm_state_init_local(); |
| |
| /* set up the timers */ |
| transition_timer = calloc(1, sizeof(fsa_timer_t)); |
| integration_timer = calloc(1, sizeof(fsa_timer_t)); |
| finalization_timer = calloc(1, sizeof(fsa_timer_t)); |
| election_trigger = calloc(1, sizeof(fsa_timer_t)); |
| shutdown_escalation_timer = calloc(1, sizeof(fsa_timer_t)); |
| wait_timer = calloc(1, sizeof(fsa_timer_t)); |
| recheck_timer = calloc(1, sizeof(fsa_timer_t)); |
| |
| if (election_trigger != NULL) { |
| election_trigger->source_id = 0; |
| election_trigger->period_ms = -1; |
| election_trigger->fsa_input = I_DC_TIMEOUT; |
| election_trigger->callback = crm_timer_popped; |
| election_trigger->repeat = FALSE; |
| } else { |
| was_error = TRUE; |
| } |
| |
| if (transition_timer != NULL) { |
| transition_timer->source_id = 0; |
| transition_timer->period_ms = -1; |
| transition_timer->fsa_input = I_PE_CALC; |
| transition_timer->callback = crm_timer_popped; |
| transition_timer->repeat = FALSE; |
| } else { |
| was_error = TRUE; |
| } |
| |
| if (integration_timer != NULL) { |
| integration_timer->source_id = 0; |
| integration_timer->period_ms = -1; |
| integration_timer->fsa_input = I_INTEGRATED; |
| integration_timer->callback = crm_timer_popped; |
| integration_timer->repeat = FALSE; |
| } else { |
| was_error = TRUE; |
| } |
| |
| if (finalization_timer != NULL) { |
| finalization_timer->source_id = 0; |
| finalization_timer->period_ms = -1; |
| finalization_timer->fsa_input = I_FINALIZED; |
| finalization_timer->callback = crm_timer_popped; |
| finalization_timer->repeat = FALSE; |
| /* for possible enabling... a bug in the join protocol left |
| * a slave in S_PENDING while we think it's in S_NOT_DC |
| * |
| * raising I_FINALIZED put us into a transition loop which is |
| * never resolved. |
| * in this loop we continually send probes which the node |
| * NACK's because it's in S_PENDING |
| * |
| * if we have nodes where heartbeat is active but the |
| * CRM is not... then this will be handled in the |
| * integration phase |
| */ |
| finalization_timer->fsa_input = I_ELECTION; |
| |
| } else { |
| was_error = TRUE; |
| } |
| |
| if (shutdown_escalation_timer != NULL) { |
| shutdown_escalation_timer->source_id = 0; |
| shutdown_escalation_timer->period_ms = -1; |
| shutdown_escalation_timer->fsa_input = I_STOP; |
| shutdown_escalation_timer->callback = crm_timer_popped; |
| shutdown_escalation_timer->repeat = FALSE; |
| } else { |
| was_error = TRUE; |
| } |
| |
| if (wait_timer != NULL) { |
| wait_timer->source_id = 0; |
| wait_timer->period_ms = 2000; |
| wait_timer->fsa_input = I_NULL; |
| wait_timer->callback = crm_timer_popped; |
| wait_timer->repeat = FALSE; |
| } else { |
| was_error = TRUE; |
| } |
| |
| if (recheck_timer != NULL) { |
| recheck_timer->source_id = 0; |
| recheck_timer->period_ms = -1; |
| recheck_timer->fsa_input = I_PE_CALC; |
| recheck_timer->callback = crm_timer_popped; |
| recheck_timer->repeat = FALSE; |
| } else { |
| was_error = TRUE; |
| } |
| |
| /* set up the sub systems */ |
| cib_subsystem = calloc(1, sizeof(struct crm_subsystem_s)); |
| te_subsystem = calloc(1, sizeof(struct crm_subsystem_s)); |
| pe_subsystem = calloc(1, sizeof(struct crm_subsystem_s)); |
| |
| if (cib_subsystem != NULL) { |
| cib_subsystem->pid = -1; |
| cib_subsystem->name = CRM_SYSTEM_CIB; |
| cib_subsystem->flag_connected = R_CIB_CONNECTED; |
| cib_subsystem->flag_required = R_CIB_REQUIRED; |
| |
| } else { |
| was_error = TRUE; |
| } |
| |
| if (te_subsystem != NULL) { |
| te_subsystem->pid = -1; |
| te_subsystem->name = CRM_SYSTEM_TENGINE; |
| te_subsystem->flag_connected = R_TE_CONNECTED; |
| te_subsystem->flag_required = R_TE_REQUIRED; |
| |
| } else { |
| was_error = TRUE; |
| } |
| |
| if (pe_subsystem != NULL) { |
| pe_subsystem->pid = -1; |
| pe_subsystem->path = CRM_DAEMON_DIR; |
| pe_subsystem->name = CRM_SYSTEM_PENGINE; |
| pe_subsystem->command = CRM_DAEMON_DIR "/" CRM_SYSTEM_PENGINE; |
| pe_subsystem->args = NULL; |
| pe_subsystem->flag_connected = R_PE_CONNECTED; |
| pe_subsystem->flag_required = R_PE_REQUIRED; |
| |
| } else { |
| was_error = TRUE; |
| } |
| |
| if (was_error == FALSE && need_spawn_pengine_from_crmd()) { |
| if (start_subsystem(pe_subsystem) == FALSE) { |
| was_error = TRUE; |
| } |
| } |
| |
| if (was_error) { |
| register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); |
| } |
| |
| } |
| |
| static int32_t |
| crmd_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) |
| { |
| crm_trace("Connection %p", c); |
| if (crm_client_new(c, uid, gid) == NULL) { |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| static void |
| crmd_ipc_created(qb_ipcs_connection_t * c) |
| { |
| crm_trace("Connection %p", c); |
| } |
| |
| static int32_t |
| crmd_ipc_dispatch(qb_ipcs_connection_t * c, void *data, size_t size) |
| { |
| uint32_t id = 0; |
| uint32_t flags = 0; |
| crm_client_t *client = crm_client_get(c); |
| |
| xmlNode *msg = crm_ipcs_recv(client, data, size, &id, &flags); |
| |
| crm_trace("Invoked: %s", crm_client_name(client)); |
| crm_ipcs_send_ack(client, id, flags, "ack", __FUNCTION__, __LINE__); |
| |
| if (msg == NULL) { |
| return 0; |
| } |
| |
| #if ENABLE_ACL |
| CRM_ASSERT(client->user != NULL); |
| crm_acl_get_set_user(msg, F_CRM_USER, client->user); |
| #endif |
| |
| crm_trace("Processing msg from %s", crm_client_name(client)); |
| crm_log_xml_trace(msg, "CRMd[inbound]"); |
| |
| crm_xml_add(msg, F_CRM_SYS_FROM, client->id); |
| if (crmd_authorize_message(msg, client, NULL)) { |
| route_message(C_IPC_MESSAGE, msg); |
| } |
| |
| trigger_fsa(fsa_source); |
| free_xml(msg); |
| return 0; |
| } |
| |
| static int32_t |
| crmd_ipc_closed(qb_ipcs_connection_t * c) |
| { |
| crm_client_t *client = crm_client_get(c); |
| struct crm_subsystem_s *the_subsystem = NULL; |
| |
| if (client == NULL) { |
| return 0; |
| } |
| |
| crm_trace("Connection %p", c); |
| |
| if (client->userdata == NULL) { |
| crm_trace("Client hadn't registered with us yet"); |
| |
| } else if (strcasecmp(CRM_SYSTEM_PENGINE, client->userdata) == 0) { |
| the_subsystem = pe_subsystem; |
| |
| } else if (strcasecmp(CRM_SYSTEM_TENGINE, client->userdata) == 0) { |
| the_subsystem = te_subsystem; |
| |
| } else if (strcasecmp(CRM_SYSTEM_CIB, client->userdata) == 0) { |
| the_subsystem = cib_subsystem; |
| } |
| |
| if (the_subsystem != NULL) { |
| the_subsystem->source = NULL; |
| the_subsystem->client = NULL; |
| crm_info("Received HUP from %s:[%d]", the_subsystem->name, the_subsystem->pid); |
| |
| } else { |
| /* else that was a transient client */ |
| crm_trace("Received HUP from transient client"); |
| } |
| |
| crm_trace("Disconnecting client %s (%p)", crm_client_name(client), client); |
| free(client->userdata); |
| crm_client_destroy(client); |
| |
| trigger_fsa(fsa_source); |
| return 0; |
| } |
| |
| static void |
| crmd_ipc_destroy(qb_ipcs_connection_t * c) |
| { |
| crm_trace("Connection %p", c); |
| crmd_ipc_closed(c); |
| } |
| |
| /* A_STOP */ |
| void |
| do_stop(long long action, |
| enum crmd_fsa_cause cause, |
| enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) |
| { |
| crm_trace("Closing IPC server"); |
| mainloop_del_ipc_server(ipcs); ipcs = NULL; |
| register_fsa_input(C_FSA_INTERNAL, I_TERMINATE, NULL); |
| } |
| |
| /* A_STARTED */ |
| void |
| do_started(long long action, |
| enum crmd_fsa_cause cause, |
| enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) |
| { |
| static struct qb_ipcs_service_handlers crmd_callbacks = { |
| .connection_accept = crmd_ipc_accept, |
| .connection_created = crmd_ipc_created, |
| .msg_process = crmd_ipc_dispatch, |
| .connection_closed = crmd_ipc_closed, |
| .connection_destroyed = crmd_ipc_destroy |
| }; |
| |
| if (cur_state != S_STARTING) { |
| crm_err("Start cancelled... %s", fsa_state2string(cur_state)); |
| return; |
| |
| } else if (is_set(fsa_input_register, R_MEMBERSHIP) == FALSE) { |
| crm_info("Delaying start, no membership data (%.16llx)", R_MEMBERSHIP); |
| |
| crmd_fsa_stall(TRUE); |
| return; |
| |
| } else if (is_set(fsa_input_register, R_LRM_CONNECTED) == FALSE) { |
| crm_info("Delaying start, LRM not connected (%.16llx)", R_LRM_CONNECTED); |
| |
| crmd_fsa_stall(TRUE); |
| return; |
| |
| } else if (is_set(fsa_input_register, R_CIB_CONNECTED) == FALSE) { |
| crm_info("Delaying start, CIB not connected (%.16llx)", R_CIB_CONNECTED); |
| |
| crmd_fsa_stall(TRUE); |
| return; |
| |
| } else if (is_set(fsa_input_register, R_READ_CONFIG) == FALSE) { |
| crm_info("Delaying start, Config not read (%.16llx)", R_READ_CONFIG); |
| |
| crmd_fsa_stall(TRUE); |
| return; |
| |
| } else if (is_set(fsa_input_register, R_PEER_DATA) == FALSE) { |
| |
| /* try reading from HA */ |
| crm_info("Delaying start, No peer data (%.16llx)", R_PEER_DATA); |
| |
| #if SUPPORT_HEARTBEAT |
| if (is_heartbeat_cluster()) { |
| HA_Message *msg = NULL; |
| |
| crm_trace("Looking for a HA message"); |
| msg = fsa_cluster_conn->llc_ops->readmsg(fsa_cluster_conn, 0); |
| if (msg != NULL) { |
| crm_trace("There was a HA message"); |
| ha_msg_del(msg); |
| } |
| } |
| #endif |
| crmd_fsa_stall(TRUE); |
| return; |
| } |
| |
| crm_debug("Init server comms"); |
| ipcs = crmd_ipc_server_init(&crmd_callbacks); |
| if (ipcs == NULL) { |
| crm_err("Failed to create IPC server: shutting down and inhibiting respawn"); |
| register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); |
| } |
| |
| if (stonith_reconnect == NULL) { |
| int dummy; |
| |
| stonith_reconnect = mainloop_add_trigger(G_PRIORITY_LOW, te_connect_stonith, &dummy); |
| } |
| set_bit(fsa_input_register, R_ST_REQUIRED); |
| mainloop_set_trigger(stonith_reconnect); |
| |
| crm_notice("The local CRM is operational"); |
| clear_bit(fsa_input_register, R_STARTING); |
| register_fsa_input(msg_data->fsa_cause, I_PENDING, NULL); |
| } |
| |
| /* A_RECOVER */ |
| void |
| do_recover(long long action, |
| enum crmd_fsa_cause cause, |
| enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) |
| { |
| set_bit(fsa_input_register, R_IN_RECOVERY); |
| crm_warn("Fast-tracking shutdown in response to errors"); |
| |
| register_fsa_input(C_FSA_INTERNAL, I_TERMINATE, NULL); |
| } |
| |
| /* *INDENT-OFF* */ |
| pe_cluster_option crmd_opts[] = { |
| /* name, old-name, validate, values, default, short description, long description */ |
| { "dc-version", NULL, "string", NULL, "none", NULL, |
| "Version of Pacemaker on the cluster's DC.", |
| "Includes the hash which identifies the exact changeset it was built from. Used for diagnostic purposes." |
| }, |
| { "cluster-infrastructure", NULL, "string", NULL, "heartbeat", NULL, |
| "The messaging stack on which Pacemaker is currently running.", |
| "Used for informational and diagnostic purposes." }, |
| { "cluster-name", NULL, "string", NULL, NULL, NULL, |
| "An arbitrary name for the cluster", |
| "This optional value is mostly for users' convenience as desired " |
| "in administration, but may also be used in Pacemaker configuration " |
| "rules via the #cluster-name node attribute, and by higher-level tools " |
| "and resource agents." |
| }, |
| { XML_CONFIG_ATTR_DC_DEADTIME, "dc_deadtime", "time", NULL, "20s", &check_time, |
| "How long to wait for a response from other nodes during startup.", |
| "The \"correct\" value will depend on the speed/load of your network and the type of switches used." |
| }, |
| { XML_CONFIG_ATTR_RECHECK, "cluster_recheck_interval", "time", |
| "Zero disables polling. Positive values are an interval in seconds (unless other SI units are specified. eg. 5min)", |
| "15min", &check_timer, |
| "Polling interval for time based changes to options, resource parameters and constraints.", |
| "The Cluster is primarily event driven, however the configuration can have elements that change based on time." |
| " To ensure these changes take effect, we can optionally poll the cluster's status for changes." |
| }, |
| |
| #ifdef RHEL7_COMPAT |
| /* These options were superseded by the alerts feature and now are just an |
| * alternate interface to it. It was never released upstream, but was |
| * released in RHEL 7, so we allow it to be enabled at compile-time by |
| * defining RHEL7_COMPAT. |
| */ |
| { "notification-agent", NULL, "string", NULL, "/dev/null", &check_script, |
| "Deprecated", |
| "Use alert path in alerts section instead" |
| }, |
| { "notification-recipient", NULL, "string", NULL, "", NULL, |
| "Deprecated", |
| "Use recipient value in alerts section instead" |
| }, |
| #endif |
| |
| { "load-threshold", NULL, "percentage", NULL, "80%", &check_utilization, |
| "The maximum amount of system resources that should be used by nodes in the cluster", |
| "The cluster will slow down its recovery process when the amount of system resources used" |
| " (currently CPU) approaches this limit", |
| }, |
| { "node-action-limit", NULL, "integer", NULL, "0", &check_number, |
| "The maximum number of jobs that can be scheduled per node. Defaults to 2x cores"}, |
| { XML_CONFIG_ATTR_FENCE_REACTION, NULL, "string", NULL, "stop", NULL, |
| "How a cluster node should react if notified of its own fencing", |
| "A cluster node may receive notification of its own fencing if fencing " |
| "is misconfigured, or if fabric fencing is in use that doesn't cut " |
| "cluster communication. Allowed values are \"stop\" to attempt to " |
| "immediately stop pacemaker and stay stopped, or \"panic\" to attempt " |
| "to immediately reboot the local node, falling back to stop on failure." |
| }, |
| { XML_CONFIG_ATTR_ELECTION_FAIL, "election_timeout", "time", NULL, "2min", &check_timer, |
| "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug." |
| }, |
| { XML_CONFIG_ATTR_FORCE_QUIT, "shutdown_escalation", "time", NULL, "20min", &check_timer, |
| "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug." |
| }, |
| { "crmd-integration-timeout", NULL, "time", NULL, "3min", &check_timer, |
| "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug." |
| }, |
| { "crmd-finalization-timeout", NULL, "time", NULL, "30min", &check_timer, |
| "*** Advanced Use Only ***.", "If you need to adjust this value, it probably indicates the presence of a bug." |
| }, |
| { "crmd-transition-delay", NULL, "time", NULL, "0s", &check_timer, |
| "*** Advanced Use Only ***\n" |
| "Enabling this option will slow down cluster recovery under all conditions", |
| "Delay cluster recovery for the configured interval to allow for additional/related events to occur.\n" |
| "Useful if your configuration is sensitive to the order in which ping updates arrive." |
| }, |
| { "stonith-watchdog-timeout", NULL, "time", NULL, NULL, &check_sbd_timeout, |
| "How long to wait before we can assume nodes are safely down", NULL |
| }, |
| { "stonith-max-attempts",NULL,"integer",NULL,"10",&check_positive_number, |
| "How many times stonith can fail before it will no longer be attempted on a target" |
| }, |
| { "no-quorum-policy", "no_quorum_policy", "enum", "stop, freeze, ignore, suicide", "stop", &check_quorum, NULL, NULL }, |
| |
| #if SUPPORT_PLUGIN |
| { XML_ATTR_EXPECTED_VOTES, NULL, "integer", NULL, "2", &check_number, "The number of nodes expected to be in the cluster", "Used to calculate quorum in openais based clusters." }, |
| #endif |
| }; |
| /* *INDENT-ON* */ |
| |
| void |
| crmd_metadata(void) |
| { |
| config_metadata("CRM Daemon", "1.0", |
| "CRM Daemon Options", |
| "This is a fake resource that details the options that can be configured for the CRM Daemon.", |
| crmd_opts, DIMOF(crmd_opts)); |
| } |
| |
| static void |
| verify_crmd_options(GHashTable * options) |
| { |
| verify_all_options(options, crmd_opts, DIMOF(crmd_opts)); |
| } |
| |
| static const char * |
| crmd_pref(GHashTable * options, const char *name) |
| { |
| return get_cluster_pref(options, crmd_opts, DIMOF(crmd_opts), name); |
| } |
| |
| static void |
| config_query_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data) |
| { |
| const char *value = NULL; |
| GHashTable *config_hash = NULL; |
| crm_time_t *now = crm_time_new(NULL); |
| xmlNode *crmconfig = NULL; |
| xmlNode *alerts = NULL; |
| |
| if (rc != pcmk_ok) { |
| fsa_data_t *msg_data = NULL; |
| |
| crm_err("Local CIB query resulted in an error: %s", pcmk_strerror(rc)); |
| register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); |
| |
| if (rc == -EACCES || rc == -pcmk_err_schema_validation) { |
| crm_err("The cluster is mis-configured - shutting down and staying down"); |
| set_bit(fsa_input_register, R_STAYDOWN); |
| } |
| goto bail; |
| } |
| |
| crmconfig = output; |
| if ((crmconfig) && |
| (crm_element_name(crmconfig)) && |
| (strcmp(crm_element_name(crmconfig), XML_CIB_TAG_CRMCONFIG) != 0)) { |
| crmconfig = first_named_child(crmconfig, XML_CIB_TAG_CRMCONFIG); |
| } |
| if (!crmconfig) { |
| fsa_data_t *msg_data = NULL; |
| |
| crm_err("Local CIB query for " XML_CIB_TAG_CRMCONFIG " section failed"); |
| register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); |
| goto bail; |
| } |
| |
| crm_debug("Call %d : Parsing CIB options", call_id); |
| config_hash = crm_str_table_new(); |
| unpack_instance_attributes(crmconfig, crmconfig, XML_CIB_TAG_PROPSET, NULL, config_hash, |
| CIB_OPTIONS_FIRST, FALSE, now); |
| |
| verify_crmd_options(config_hash); |
| |
| #ifdef RHEL7_COMPAT |
| { |
| const char *script = crmd_pref(config_hash, "notification-agent"); |
| const char *recip = crmd_pref(config_hash, "notification-recipient"); |
| |
| pe_enable_legacy_alerts(script, recip); |
| } |
| #endif |
| |
| value = crmd_pref(config_hash, XML_CONFIG_ATTR_DC_DEADTIME); |
| election_trigger->period_ms = crm_get_msec(value); |
| |
| value = crmd_pref(config_hash, "node-action-limit"); /* Also checks migration-limit */ |
| throttle_update_job_max(value); |
| |
| value = crmd_pref(config_hash, "load-threshold"); |
| if(value) { |
| throttle_set_load_target(strtof(value, NULL) / 100.0); |
| } |
| |
| value = crmd_pref(config_hash, "no-quorum-policy"); |
| if (safe_str_eq(value, "suicide") && pcmk_locate_sbd()) { |
| no_quorum_suicide_escalation = TRUE; |
| } |
| |
| set_fence_reaction(crmd_pref(config_hash, XML_CONFIG_ATTR_FENCE_REACTION)); |
| |
| value = crmd_pref(config_hash,"stonith-max-attempts"); |
| update_stonith_max_attempts(value); |
| |
| value = crmd_pref(config_hash, XML_CONFIG_ATTR_FORCE_QUIT); |
| shutdown_escalation_timer->period_ms = crm_get_msec(value); |
| /* How long to declare an election over - even if not everyone voted */ |
| crm_debug("Shutdown escalation occurs after: %dms", shutdown_escalation_timer->period_ms); |
| |
| value = crmd_pref(config_hash, XML_CONFIG_ATTR_ELECTION_FAIL); |
| controld_set_election_period(value); |
| |
| value = crmd_pref(config_hash, XML_CONFIG_ATTR_RECHECK); |
| recheck_timer->period_ms = crm_get_msec(value); |
| crm_debug("Checking for expired actions every %dms", recheck_timer->period_ms); |
| |
| value = crmd_pref(config_hash, "crmd-transition-delay"); |
| transition_timer->period_ms = crm_get_msec(value); |
| |
| value = crmd_pref(config_hash, "crmd-integration-timeout"); |
| integration_timer->period_ms = crm_get_msec(value); |
| |
| value = crmd_pref(config_hash, "crmd-finalization-timeout"); |
| finalization_timer->period_ms = crm_get_msec(value); |
| |
| #if SUPPORT_COROSYNC |
| if (is_classic_ais_cluster()) { |
| value = crmd_pref(config_hash, XML_ATTR_EXPECTED_VOTES); |
| crm_debug("Sending expected-votes=%s to corosync", value); |
| send_cluster_text(crm_class_quorum, value, TRUE, NULL, crm_msg_ais); |
| } |
| #endif |
| |
| free(fsa_cluster_name); |
| fsa_cluster_name = NULL; |
| |
| value = g_hash_table_lookup(config_hash, "cluster-name"); |
| if (value) { |
| fsa_cluster_name = strdup(value); |
| } |
| |
| alerts = first_named_child(output, XML_CIB_TAG_ALERTS); |
| crmd_unpack_alerts(alerts); |
| |
| set_bit(fsa_input_register, R_READ_CONFIG); |
| crm_trace("Triggering FSA: %s", __FUNCTION__); |
| mainloop_set_trigger(fsa_source); |
| |
| g_hash_table_destroy(config_hash); |
| bail: |
| crm_time_free(now); |
| } |
| |
| gboolean |
| crm_read_options(gpointer user_data) |
| { |
| int call_id = |
| fsa_cib_conn->cmds->query(fsa_cib_conn, |
| "//" XML_CIB_TAG_CRMCONFIG " | //" XML_CIB_TAG_ALERTS, |
| NULL, cib_xpath | cib_scope_local); |
| |
| fsa_register_cib_callback(call_id, FALSE, NULL, config_query_callback); |
| crm_trace("Querying the CIB... call %d", call_id); |
| return TRUE; |
| } |
| |
| /* A_READCONFIG */ |
| void |
| do_read_config(long long action, |
| enum crmd_fsa_cause cause, |
| enum crmd_fsa_state cur_state, |
| enum crmd_fsa_input current_input, fsa_data_t * msg_data) |
| { |
| throttle_init(); |
| mainloop_set_trigger(config_read); |
| } |
| |
| void |
| crm_shutdown(int nsig) |
| { |
| if (crmd_mainloop != NULL && g_main_is_running(crmd_mainloop)) { |
| if (is_set(fsa_input_register, R_SHUTDOWN)) { |
| crm_err("Escalating the shutdown"); |
| register_fsa_input_before(C_SHUTDOWN, I_ERROR, NULL); |
| |
| } else { |
| set_bit(fsa_input_register, R_SHUTDOWN); |
| register_fsa_input(C_SHUTDOWN, I_SHUTDOWN, NULL); |
| |
| if (shutdown_escalation_timer->period_ms < 1) { |
| const char *value = crmd_pref(NULL, XML_CONFIG_ATTR_FORCE_QUIT); |
| int msec = crm_get_msec(value); |
| |
| crm_debug("Using default shutdown escalation: %dms", msec); |
| shutdown_escalation_timer->period_ms = msec; |
| } |
| |
| /* can't rely on this... */ |
| crm_notice("Shutting down cluster resource manager " CRM_XS |
| " limit=%dms", shutdown_escalation_timer->period_ms); |
| crm_timer_start(shutdown_escalation_timer); |
| } |
| |
| } else { |
| crm_info("exit from shutdown"); |
| crmd_exit(pcmk_ok); |
| } |
| } |