diff --git a/crm/crm-1.0.dtd b/crm/crm-1.0.dtd index ed87b5b1da..7619ca8266 100644 --- a/crm/crm-1.0.dtd +++ b/crm/crm-1.0.dtd @@ -1,835 +1,837 @@ diff --git a/crm/crmd/lrm.c b/crm/crmd/lrm.c index 667ff0c6d3..765c0f32ee 100644 --- a/crm/crmd/lrm.c +++ b/crm/crmd/lrm.c @@ -1,1594 +1,1740 @@ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include /* for access */ #include #include #include #include #include #include #include #include #include #include #include #include char *make_stop_id(const char *rsc, int call_id); gboolean verify_stopped(gboolean force, int log_level); gboolean resource_stopped(gpointer key, gpointer value, gpointer user_data); gboolean build_operation_update( crm_data_t *rsc_list, lrm_op_t *op, const char *src, int lpc); gboolean build_active_RAs(crm_data_t *rsc_list); gboolean is_rsc_active(const char *rsc_id); void do_update_resource(lrm_op_t *op); gboolean process_lrm_event(lrm_op_t *op); enum crmd_fsa_input do_lrm_rsc_op( lrm_rsc_t *rsc, const char *operation, crm_data_t *msg, HA_Message *request); enum crmd_fsa_input do_fake_lrm_op(gpointer data); void stop_recurring_action( gpointer key, gpointer value, gpointer user_data); gboolean remove_recurring_action( gpointer key, gpointer value, gpointer user_data); lrm_op_t *construct_op( crm_data_t *rsc_op, const char *rsc_id, const char *operation); void send_direct_ack(const char *to_host, const char *to_sys, lrm_op_t* op, const char *rsc_id); void free_recurring_op(gpointer value); GHashTable *monitors = NULL; GHashTable *resources = NULL; GHashTable *shutdown_ops = NULL; int num_lrm_register_fails = 0; int max_lrm_register_fails = 30; enum crmd_rscstate { crmd_rscstate_NULL, crmd_rscstate_START, crmd_rscstate_START_PENDING, crmd_rscstate_START_OK, crmd_rscstate_START_FAIL, crmd_rscstate_STOP, crmd_rscstate_STOP_PENDING, crmd_rscstate_STOP_OK, crmd_rscstate_STOP_FAIL, crmd_rscstate_MON, crmd_rscstate_MON_PENDING, crmd_rscstate_MON_OK, crmd_rscstate_MON_FAIL, crmd_rscstate_GENERIC_PENDING, crmd_rscstate_GENERIC_OK, crmd_rscstate_GENERIC_FAIL }; const char *crmd_rscstate2string(enum crmd_rscstate state); const char * crmd_rscstate2string(enum crmd_rscstate state) { switch(state) { case crmd_rscstate_NULL: return NULL; case crmd_rscstate_START: return CRMD_ACTION_START; case crmd_rscstate_START_PENDING: return CRMD_ACTION_START_PENDING; case crmd_rscstate_START_OK: return CRMD_ACTION_STARTED; case crmd_rscstate_START_FAIL: return CRMD_ACTION_START_FAIL; case crmd_rscstate_STOP: return CRMD_ACTION_STOP; case crmd_rscstate_STOP_PENDING: return CRMD_ACTION_STOP_PENDING; case crmd_rscstate_STOP_OK: return CRMD_ACTION_STOPPED; case crmd_rscstate_STOP_FAIL: return CRMD_ACTION_STOP_FAIL; case crmd_rscstate_MON: return CRMD_ACTION_MON; case crmd_rscstate_MON_PENDING: return CRMD_ACTION_MON_PENDING; case crmd_rscstate_MON_OK: return CRMD_ACTION_MON_OK; case crmd_rscstate_MON_FAIL: return CRMD_ACTION_MON_FAIL; case crmd_rscstate_GENERIC_PENDING: return CRMD_ACTION_GENERIC_PENDING; case crmd_rscstate_GENERIC_OK: return CRMD_ACTION_GENERIC_OK; case crmd_rscstate_GENERIC_FAIL: return CRMD_ACTION_GENERIC_FAIL; } return ""; } GCHSource *lrm_source = NULL; /* A_LRM_CONNECT */ enum crmd_fsa_input do_lrm_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) { int ret = HA_OK; if(action & A_LRM_DISCONNECT) { verify_stopped(TRUE, LOG_ERR); if(lrm_source) { crm_debug("Removing LRM connection from MainLoop"); if(G_main_del_IPC_Channel(lrm_source) == FALSE) { crm_err("Could not remove LRM connection" " from MainLoop"); } lrm_source = NULL; } if(fsa_lrm_conn) { fsa_lrm_conn->lrm_ops->signoff(fsa_lrm_conn); crm_info("Disconnected from the LRM"); clear_bit_inplace(fsa_input_register, R_LRM_CONNECTED); fsa_lrm_conn = NULL; } /* TODO: Clean up the hashtable */ } if(action & A_LRM_CONNECT) { ret = HA_OK; monitors = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, free_recurring_op); resources = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); shutdown_ops = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); fsa_lrm_conn = ll_lrm_new(XML_CIB_TAG_LRM); if(NULL == fsa_lrm_conn) { register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); ret = HA_FAIL; } if(ret == HA_OK) { crm_debug("Connecting to the LRM"); ret = fsa_lrm_conn->lrm_ops->signon( fsa_lrm_conn, CRM_SYSTEM_CRMD); } if(ret != HA_OK) { if(++num_lrm_register_fails < max_lrm_register_fails) { crm_warn("Failed to sign on to the LRM %d" " (%d max) times", num_lrm_register_fails, max_lrm_register_fails); crm_timer_start(wait_timer); crmd_fsa_stall(NULL); return I_NULL; } } if(ret == HA_OK) { crm_debug_4("LRM: set_lrm_callback..."); ret = fsa_lrm_conn->lrm_ops->set_lrm_callback( fsa_lrm_conn, lrm_op_callback); if(ret != HA_OK) { crm_err("Failed to set LRM callbacks"); } } if(ret != HA_OK) { crm_err("Failed to sign on to the LRM %d" " (max) times", num_lrm_register_fails); register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); return I_NULL; } /* TODO: create a destroy handler that causes * some recovery to happen */ lrm_source = G_main_add_IPC_Channel( G_PRIORITY_LOW, fsa_lrm_conn->lrm_ops->ipcchan(fsa_lrm_conn), FALSE, lrm_dispatch, fsa_lrm_conn, default_ipc_connection_destroy); set_bit_inplace(fsa_input_register, R_LRM_CONNECTED); crm_debug("LRM connection established"); } if(action & ~(A_LRM_CONNECT|A_LRM_DISCONNECT)) { crm_err("Unexpected action %s in %s", fsa_action2string(action), __FUNCTION__); } return I_NULL; } static void ghash_print_pending(gpointer key, gpointer value, gpointer user_data) { const char *action = key; int *log_level = user_data; do_crm_log(*log_level, "Pending action: %s", action); } gboolean verify_stopped(gboolean force, int log_level) { GListPtr lrm_list = NULL; crm_info("Checking for active resources before exit"); if(fsa_lrm_conn == NULL) { crm_err("Exiting with no LRM connection..." " resources may be active!"); return TRUE; } if(g_hash_table_size(shutdown_ops) > 0) { do_crm_log(log_level, "%d pending LRM operations at shutdown%s", g_hash_table_size(shutdown_ops), force?"":"... waiting"); if(force || !is_set(fsa_input_register, R_SENT_RSC_STOP)) { g_hash_table_foreach( shutdown_ops, ghash_print_pending, &log_level); } if(force == FALSE) { return FALSE; } } lrm_list = fsa_lrm_conn->lrm_ops->get_all_rscs(fsa_lrm_conn); slist_iter( rsc_id, char, lrm_list, lpc, if(is_rsc_active(rsc_id) == FALSE) { continue; } crm_err("Resource %s was active at shutdown." " You may ignore this error if it is unmanaged.", rsc_id); ); set_bit_inplace(fsa_input_register, R_SENT_RSC_STOP); register_fsa_input(C_FSA_INTERNAL, I_TERMINATE, NULL); return TRUE; } +static const char * +get_rsc_metadata(const char *type, const char *class, const char *provider) +{ + int len = 0; + char *key = NULL; + char *metadata = NULL; + static GHashTable *meta_hash = NULL; + if(meta_hash == NULL) { + meta_hash = g_hash_table_new_full( + g_str_hash, g_str_equal, + g_hash_destroy_str, g_hash_destroy_str); + } + + CRM_CHECK(type != NULL, return NULL); + CRM_CHECK(class != NULL, return NULL); + if(provider == NULL) { + provider = "heartbeat"; + } + + len = strlen(type) + strlen(class) + strlen(provider) + 4; + + crm_malloc0(key, len); + sprintf(key, "%s::%s:%s", type, class, provider); + key[len-1] = 0; + + metadata = g_hash_table_lookup(meta_hash, key); + if(metadata) { + crm_debug_2("Returning cached metadata for %s", key); + crm_free(key); + goto out; + } + + crm_debug("Retreiving metadata for %s", key); + metadata = fsa_lrm_conn->lrm_ops->get_rsc_type_metadata( + fsa_lrm_conn, class, type, provider); + + if(metadata) { + g_hash_table_insert(meta_hash, key, metadata); + } + + out: + if(metadata == NULL) { + crm_warn("No metadata found for %s::%s:%s", + class, type, provider); + } + + return metadata; +} + +static GListPtr +get_rsc_restart_list(lrm_rsc_t *rsc, lrm_op_t *op) +{ + gboolean supported = FALSE; + GListPtr restart_list = NULL; + + const char *value = NULL; + const char *metadata_str = get_rsc_metadata( + rsc->type, rsc->class, rsc->provider); + + crm_data_t *params = NULL; + crm_data_t *actions = NULL; + crm_data_t *metadata = NULL; + + if(metadata_str == NULL) { + return NULL; + } + + metadata = string2xml(metadata_str); + actions = find_xml_node(metadata, "actions", TRUE); + + xml_child_iter_filter( + actions, action, "action", + value = crm_element_value(action, "name"); + if(safe_str_eq("reload", value)) { + supported = TRUE; + break; + } + ); + + if(supported == FALSE) { + return NULL; + } + + params = find_xml_node(metadata, "parameters", TRUE); + xml_child_iter_filter( + params, param, "parameter", + value = crm_element_value(param, "unique"); + if(crm_is_true(value)) { + value = crm_element_value(param, "name"); + crm_debug("Attr %s is not reloadable", value); + restart_list = g_list_append( + restart_list, crm_strdup(value)); + } + ); + + free_xml(metadata); + return restart_list; +} + +static void +append_restart_list(crm_data_t *update, lrm_op_t *op, const char *version) +{ + int len = 0; + char *list = NULL; + char *digest = NULL; + lrm_rsc_t *rsc = NULL; + const char *value = NULL; + crm_data_t *restart = NULL; + GListPtr restart_list = NULL; + + if(op->interval > 0) { + /* monitors are not reloadable */ + return; + + } else if(safe_str_neq(CRMD_ACTION_START, op->op_type)) { + /* only starts are potentially reloadable */ + return; + + } else if(compare_version("1.0.8", version) > 0) { + crm_debug("Caller version %s does not support reloads", version); + return; + } + + rsc = fsa_lrm_conn->lrm_ops->get_rsc(fsa_lrm_conn, op->rsc_id); + if(rsc == NULL) { + crm_info("Resource %s no longer in the LRM", op->rsc_id); + return; + } + + restart = create_xml_node(NULL, "restart"); + restart_list = get_rsc_restart_list(rsc, op); + if(restart_list == NULL) { + crm_info("Resource %s does not support reloads", op->rsc_id); + return; + } + + slist_iter(param, const char, restart_list, lpc, + int start = len; + value = g_hash_table_lookup(op->params, param); + crm_xml_add(restart, param, value); + + if(list == NULL) { + len = strlen(param); + crm_malloc0(list, len+1); + sprintf(list, "%s", param); + } else { + len += strlen(param)+1; + crm_realloc(list, len+1); + sprintf(list+start, " %s", param); + } + ); + + digest = calculate_xml_digest(restart, TRUE); + crm_xml_add(update, XML_LRM_ATTR_OP_RESTART, list); + crm_xml_add(update, XML_LRM_ATTR_RESTART_DIGEST, digest); + + crm_debug("%s : %s", digest, list); + free_xml(restart); + crm_free(digest); + crm_free(list); +} + gboolean build_operation_update( crm_data_t *xml_rsc, lrm_op_t *op, const char *src, int lpc) { char *fail_state = NULL; const char *state = NULL; crm_data_t *xml_op = NULL; char *op_id = NULL; const char *caller_version = NULL; char *digest = NULL; crm_data_t *args_xml = NULL; crm_data_t *args_parent = NULL; CRM_DEV_ASSERT(op != NULL); if(crm_assert_failed) { return FALSE; } crm_debug_2("%s: Updating resouce %s after %s %s op", - src, op->rsc_id, op_status2text(op->op_status), op->op_type); + src, op->rsc_id, op_status2text(op->op_status), op->op_type); if(op->op_status == LRM_OP_CANCELLED) { crm_debug_3("Ignoring cancelled op"); return TRUE; } if(AM_I_DC) { caller_version = CRM_FEATURE_SET; } else if(fsa_our_dc_version != NULL) { caller_version = fsa_our_dc_version; } else { /* there is a small risk in formerly mixed clusters that * it will be sub-optimal. * however with our upgrade policy, the update we send * should still be completely supported anyway */ caller_version = g_hash_table_lookup( op->params, XML_ATTR_CRM_VERSION); crm_warn("Falling back to operation originator version: %s", caller_version); } crm_debug_3("DC version: %s", caller_version); if(safe_str_eq(op->op_type, CRMD_ACTION_NOTIFY)) { const char *n_type = g_hash_table_lookup( op->params, crm_meta_name("notify_type")); const char *n_task = g_hash_table_lookup( op->params, crm_meta_name("notify_operation")); #if CRM_DEPRECATED_SINCE_2_0_5 if(n_type == NULL) { n_type = g_hash_table_lookup(op->params, "notify_type"); } if(n_task == NULL) { n_task = g_hash_table_lookup(op->params, "notify_operation"); } #endif CRM_DEV_ASSERT(n_type != NULL); CRM_DEV_ASSERT(n_task != NULL); op_id = generate_notify_key(op->rsc_id, n_type, n_task); /* these are not yet allowed to fail */ op->op_status = LRM_OP_DONE; op->rc = 0; } else { op_id = generate_op_key(op->rsc_id, op->op_type, op->interval); } /* Handle recurring ops - infer last op_status */ if(op->op_status == LRM_OP_PENDING && op->interval > 0) { if(op->rc == 0) { crm_debug("Mapping pending operation to DONE"); op->op_status = LRM_OP_DONE; } else { crm_debug("Mapping pending operation to ERROR"); op->op_status = LRM_OP_ERROR; } } xml_op = find_entity(xml_rsc, XML_LRM_TAG_RSC_OP, op_id); if(xml_op != NULL) { const char *old_status_s = crm_element_value( xml_op, XML_LRM_ATTR_OPSTATUS); int old_status = crm_parse_int(old_status_s, "-2"); int log_level = LOG_ERR; if(old_status_s == NULL) { crm_err("No value for "XML_LRM_ATTR_OPSTATUS); } else if(old_status == op->op_status) { /* safe to mask */ log_level = LOG_WARNING; } else if(old_status == LRM_OP_PENDING){ /* ??safe to mask?? */ /* log_level = LOG_WARNING; */ } do_crm_log(log_level, "Duplicate %s operations in get_cur_state()", op_id); do_crm_log(log_level+2, "New entry: %s %s (call=%d, status=%s)", op_id, op->user_data, op->call_id, op_status2text(op->op_status)); crm_log_xml(log_level+2, "Existing entry", xml_op); crm_free(op_id); return FALSE; } else { xml_op = create_xml_node(xml_rsc, XML_LRM_TAG_RSC_OP); } crm_xml_add(xml_op, XML_ATTR_ID, op_id); crm_free(op_id); crm_xml_add(xml_op, XML_LRM_ATTR_TASK, op->op_type); crm_xml_add(xml_op, XML_ATTR_ORIGIN, src); if(op->user_data == NULL) { - op->user_data = generate_transition_key(-1, fsa_our_uname); + op->user_data = generate_transition_key(-1, 0, fsa_our_uname); } -#if CRM_DEPRECATED_SINCE_2_0_3 if(compare_version("1.0.3", caller_version) > 0) { - CRM_CHECK(FALSE, ; ); fail_state = generate_transition_magic_v202( op->user_data, op->op_status); + } else { fail_state = generate_transition_magic( op->user_data, op->op_status, op->rc); } -#else - fail_state = generate_transition_magic( - op->user_data, op->op_status, op->rc); -#endif crm_xml_add(xml_op, XML_ATTR_TRANSITION_KEY, op->user_data); crm_xml_add(xml_op, XML_ATTR_TRANSITION_MAGIC, fail_state); crm_free(fail_state); switch(op->op_status) { case LRM_OP_PENDING: break; case LRM_OP_CANCELLED: crm_err("What to do here"); break; case LRM_OP_ERROR: case LRM_OP_TIMEOUT: case LRM_OP_NOTSUPPORTED: crm_debug("Resource action %s/%s %s: %d", op->rsc_id, op->op_type, op_status2text(op->op_status), op->rc); break; case LRM_OP_DONE: if(safe_str_eq(CRMD_ACTION_START, op->op_type)) { state = CRMD_ACTION_STARTED; } else if(safe_str_eq(CRMD_ACTION_STOP, op->op_type)) { state = CRMD_ACTION_STOPPED; } else if(safe_str_eq(CRMD_ACTION_MON, op->op_type)) { state = CRMD_ACTION_STARTED; } else { crm_debug("Using status \"%s\" for op \"%s\"", CRMD_ACTION_GENERIC_OK, op->op_type); state = CRMD_ACTION_GENERIC_OK; } break; } crm_xml_add_int(xml_op, XML_LRM_ATTR_CALLID, op->call_id); /* set these on 'xml_rsc' too to make life easy for the PE */ crm_xml_add(xml_op, XML_ATTR_CRM_VERSION, caller_version); crm_xml_add_int(xml_op, XML_LRM_ATTR_RC, op->rc); crm_xml_add_int(xml_op, XML_LRM_ATTR_OPSTATUS, op->op_status); crm_xml_add_int(xml_op, XML_LRM_ATTR_INTERVAL, op->interval); /* this will enable us to later determin that the * resource's parameters have changed and we should force * a restart */ args_parent = NULL; #if CRM_DEPRECATED_SINCE_2_0_4 if(compare_version("1.0.4", caller_version) > 0) { args_parent = xml_op; } #endif args_xml = create_xml_node(args_parent, XML_TAG_PARAMS); g_hash_table_foreach(op->params, hash2field, args_xml); filter_action_parameters(args_xml, caller_version); digest = calculate_xml_digest(args_xml, TRUE); crm_xml_add(xml_op, XML_LRM_ATTR_OP_DIGEST, digest); crm_free(digest); if(args_parent == NULL) { free_xml(args_xml); } + + append_restart_list(xml_op, op, caller_version); return TRUE; } gboolean is_rsc_active(const char *rsc_id) { GList *op_list = NULL; gboolean active = FALSE; lrm_rsc_t *the_rsc = NULL; state_flag_t cur_state = 0; int max_call_id = -1; if(fsa_lrm_conn == NULL) { return FALSE; } the_rsc = fsa_lrm_conn->lrm_ops->get_rsc(fsa_lrm_conn, rsc_id); crm_debug_2("Processing lrm_rsc_t entry %s", rsc_id); if(the_rsc == NULL) { crm_err("NULL resource returned from the LRM"); return FALSE; } op_list = the_rsc->ops->get_cur_state(the_rsc, &cur_state); crm_debug_3("\tcurrent state:%s",cur_state==LRM_RSC_IDLE?"Idle":"Busy"); slist_iter( op, lrm_op_t, op_list, llpc, crm_debug("Processing op %s_%d (%d) for %s (status=%d, rc=%d)", op->op_type, op->interval, op->call_id, the_rsc->id, op->op_status, op->rc); CRM_ASSERT(max_call_id <= op->call_id); if(safe_str_eq(op->op_type, CRMD_ACTION_STOP)) { active = FALSE; } else if(op->rc == EXECRA_NOT_RUNNING) { active = FALSE; } else { active = TRUE; } max_call_id = op->call_id; lrm_free_op(op); ); g_list_free(op_list); lrm_free_rsc(the_rsc); return active; } gboolean build_active_RAs(crm_data_t *rsc_list) { GList *op_list = NULL; GList *lrm_list = NULL; gboolean found_op = FALSE; state_flag_t cur_state = 0; if(fsa_lrm_conn == NULL) { return FALSE; } lrm_list = fsa_lrm_conn->lrm_ops->get_all_rscs(fsa_lrm_conn); slist_iter( rid, char, lrm_list, lpc, lrm_rsc_t *the_rsc = fsa_lrm_conn->lrm_ops->get_rsc(fsa_lrm_conn, rid); crm_data_t *xml_rsc = create_xml_node( rsc_list, XML_LRM_TAG_RESOURCE); int max_call_id = -1; crm_debug("Processing lrm_rsc_t entry %s", rid); if(the_rsc == NULL) { crm_err("NULL resource returned from the LRM"); continue; } crm_xml_add(xml_rsc, XML_ATTR_ID, the_rsc->id); crm_xml_add(xml_rsc, XML_ATTR_TYPE, the_rsc->type); crm_xml_add(xml_rsc, XML_AGENT_ATTR_CLASS, the_rsc->class); crm_xml_add(xml_rsc, XML_AGENT_ATTR_PROVIDER,the_rsc->provider); op_list = the_rsc->ops->get_cur_state(the_rsc, &cur_state); crm_debug_2("\tcurrent state:%s", cur_state==LRM_RSC_IDLE?"Idle":"Busy"); slist_iter( op, lrm_op_t, op_list, llpc, crm_debug_2("Processing op %s for %s (status=%d, rc=%d)", op->op_type, the_rsc->id, op->op_status, op->rc); if(max_call_id < op->call_id) { build_operation_update( xml_rsc, op, __FUNCTION__, llpc); } else if(max_call_id > op->call_id) { crm_err("Bad call_id in list=%d. Previous call_id=%d", op->call_id, max_call_id); } else { crm_warn("lrm->get_cur_state() returned" " duplicate entries for call_id=%d", op->call_id); } max_call_id = op->call_id; found_op = TRUE; lrm_free_op(op); ); if(found_op == FALSE && g_list_length(op_list) != 0) { crm_err("Could not properly determin last op" " for %s from %d entries", the_rsc->id, g_list_length(op_list)); } g_list_free(op_list); lrm_free_rsc(the_rsc); ); g_list_free(lrm_list); return TRUE; } crm_data_t* do_lrm_query(gboolean is_replace) { gboolean shut_down = FALSE; crm_data_t *xml_result= NULL; crm_data_t *xml_state = NULL; crm_data_t *xml_data = NULL; crm_data_t *rsc_list = NULL; const char *exp_state = CRMD_JOINSTATE_MEMBER; if(is_set(fsa_input_register, R_SHUTDOWN)) { exp_state = CRMD_STATE_INACTIVE; shut_down = TRUE; } xml_state = create_node_state( fsa_our_uname, ACTIVESTATUS, XML_BOOLEAN_TRUE, ONLINESTATUS, CRMD_JOINSTATE_MEMBER, exp_state, !shut_down, __FUNCTION__); xml_data = create_xml_node(xml_state, XML_CIB_TAG_LRM); crm_xml_add(xml_data, XML_ATTR_ID, fsa_our_uuid); rsc_list = create_xml_node(xml_data, XML_LRM_TAG_RESOURCES); /* Build a list of active (not always running) resources */ build_active_RAs(rsc_list); if(is_replace) { crm_xml_add(xml_state, XML_CIB_ATTR_REPLACE, XML_CIB_TAG_LRM); } xml_result = create_cib_fragment(xml_state, XML_CIB_TAG_STATUS); crm_log_xml_debug_3(xml_state, "Current state of the LRM"); return xml_result; } struct recurring_op_s { char *rsc_id; int call_id; }; static void cancel_monitor(lrm_rsc_t *rsc, const char *key) { struct recurring_op_s *existing_op = NULL; if(rsc == NULL) { crm_err("No resource to cancel and operation for"); return; } else if(key == NULL) { crm_err("No operation to cancel"); return; } existing_op = g_hash_table_lookup(monitors, key); if(existing_op != NULL) { crm_debug("Cancelling previous invocation of %s (%d)", key, existing_op->call_id); /* cancel it so we can then restart it without conflict */ if(rsc->ops->cancel_op(rsc, existing_op->call_id) != HA_OK) { crm_info("Couldn't cancel %s (%d)", key, existing_op->call_id); } else { g_hash_table_remove(monitors, key); } } else { crm_debug("No previous invocation of %s", key); } } static lrm_rsc_t * get_lrm_resource(crm_data_t *resource, crm_data_t *op_msg, gboolean do_create) { char rid[64]; lrm_rsc_t *rsc = NULL; const char *short_id = ID(resource); const char *long_id = crm_element_value(resource, XML_ATTR_ID_LONG); crm_debug_2("Retrieving %s from the LRM.", short_id); CRM_CHECK(short_id != NULL, return NULL); if(rsc == NULL) { /* check if its already there (short name) */ strncpy(rid, short_id, 64); rid[63] = 0; rsc = fsa_lrm_conn->lrm_ops->get_rsc(fsa_lrm_conn, rid); } if(rsc == NULL && long_id != NULL) { /* try the long name instead */ strncpy(rid, long_id, 64); rid[63] = 0; rsc = fsa_lrm_conn->lrm_ops->get_rsc(fsa_lrm_conn, rid); } if(rsc == NULL && do_create) { /* add it to the LRM */ const char *type = crm_element_value(resource, XML_ATTR_TYPE); const char *class = crm_element_value(resource, XML_AGENT_ATTR_CLASS); const char *provider = crm_element_value(resource, XML_AGENT_ATTR_PROVIDER); GHashTable *params = xml2list(op_msg); CRM_CHECK(class != NULL, return NULL); CRM_CHECK(type != NULL, return NULL); crm_debug("Adding rsc %s before operation", short_id); strncpy(rid, short_id, 64); rid[63] = 0; #if CRM_DEPRECATED_SINCE_2_0_3 if(op_msg != NULL) { if(g_hash_table_lookup( params, XML_ATTR_CRM_VERSION) == NULL) { g_hash_table_destroy(params); params = xml2list_202(op_msg); } } #endif if(g_hash_table_size(params) == 0) { crm_log_xml_warn(op_msg, "EmptyParams"); } fsa_lrm_conn->lrm_ops->add_rsc( fsa_lrm_conn, rid, class, type, provider, params); rsc = fsa_lrm_conn->lrm_ops->get_rsc(fsa_lrm_conn, rid); g_hash_table_destroy(params); if(rsc == NULL) { fsa_data_t *msg_data = NULL; crm_err("Could not add resource %s to LRM", rid); register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL); } } return rsc; } /* A_LRM_INVOKE */ enum crmd_fsa_input do_lrm_invoke(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) { const char *crm_op = NULL; const char *from_sys = NULL; const char *from_host = NULL; const char *operation = NULL; enum crmd_fsa_input next_input = I_NULL; ha_msg_input_t *input = fsa_typed_data(fsa_dt_ha_msg); crm_op = cl_get_string(input->msg, F_CRM_TASK); from_sys = cl_get_string(input->msg, F_CRM_SYS_FROM); if(safe_str_neq(from_sys, CRM_SYSTEM_TENGINE)) { from_host = cl_get_string(input->msg, F_CRM_HOST_FROM); } crm_debug_2("LRM command from: %s", from_sys); if(safe_str_eq(crm_op, CRM_OP_LRM_DELETE)) { operation = CRMD_ACTION_DELETE; } else if(safe_str_eq(operation, CRM_OP_LRM_REFRESH)) { crm_op = CRM_OP_LRM_REFRESH; } else if(input->xml != NULL) { operation = crm_element_value(input->xml, XML_LRM_ATTR_TASK); } if(crm_op != NULL && safe_str_eq(crm_op, CRM_OP_LRM_REFRESH)) { enum cib_errors rc = cib_ok; crm_data_t *fragment = do_lrm_query(TRUE); crm_info("Forcing a local LRM refresh"); fsa_cib_update(XML_CIB_TAG_STATUS, fragment, cib_quorum_override, rc); free_xml(fragment); } else if(crm_op != NULL && safe_str_eq(crm_op, CRM_OP_LRM_QUERY)) { crm_data_t *data = do_lrm_query(FALSE); HA_Message *reply = create_reply(input->msg, data); if(relay_message(reply, TRUE) == FALSE) { crm_err("Unable to route reply"); crm_log_message(LOG_ERR, reply); crm_msg_del(reply); } free_xml(data); } else if(safe_str_eq(operation, CRM_OP_PROBED) || safe_str_eq(crm_op, CRM_OP_REPROBE)) { const char *probed = XML_BOOLEAN_TRUE; if(safe_str_eq(crm_op, CRM_OP_REPROBE)) { probed = XML_BOOLEAN_FALSE; } update_attr(fsa_cib_conn, cib_none, XML_CIB_TAG_STATUS, fsa_our_uuid, NULL, NULL, CRM_OP_PROBED, probed); } else if(operation != NULL) { lrm_rsc_t *rsc = NULL; crm_data_t *params = NULL; gboolean create_rsc = TRUE; crm_data_t *xml_rsc = find_xml_node( input->xml, XML_CIB_TAG_RESOURCE, TRUE); CRM_CHECK(xml_rsc != NULL, return I_NULL); /* only the first 16 chars are used by the LRM */ params = find_xml_node(input->xml, XML_TAG_ATTRS,TRUE); if(safe_str_eq(operation, CRMD_ACTION_STOP)) { create_rsc = FALSE; } rsc = get_lrm_resource(xml_rsc, input->xml, create_rsc); if(rsc == NULL && create_rsc) { crm_err("Invalid resource definition"); crm_log_xml_warn(input->xml, "Bad command"); } else if(rsc == NULL) { lrm_op_t* op = NULL; crm_err("Not creating resource for a stop event"); crm_log_xml_warn(input->xml, "Bad command"); op = construct_op(input->xml, ID(xml_rsc), operation); op->op_status = LRM_OP_DONE; op->rc = 0; CRM_ASSERT(op != NULL); send_direct_ack(from_host, from_sys, op, ID(xml_rsc)); free_lrm_op(op); } else if(safe_str_eq(operation, CRMD_ACTION_CANCEL)) { lrm_op_t* op = NULL; char *op_key = NULL; const char *op_task = NULL; const char *op_interval = NULL; CRM_CHECK(params != NULL, crm_log_xml_warn(input->xml, "Bad command"); return I_NULL); op_task = crm_element_value(params, crm_meta_name(XML_LRM_ATTR_TASK)); op_interval = crm_element_value(params, crm_meta_name("interval")); #if CRM_DEPRECATED_SINCE_2_0_5 if(op_interval == NULL) { op_interval = crm_element_value(params, "interval"); } if(op_task == NULL) { op_task = crm_element_value(params, XML_LRM_ATTR_TASK); if(op_task == NULL) { op_task = crm_element_value(params, "task"); } } #endif CRM_CHECK(op_task != NULL, crm_log_xml_warn(input->xml, "Bad command"); return I_NULL); CRM_CHECK(op_interval != NULL, crm_log_xml_warn(input->xml, "Bad command"); return I_NULL); op = construct_op(input->xml, rsc->id, op_task); CRM_ASSERT(op != NULL); op_key = generate_op_key( rsc->id,op_task,crm_parse_int(op_interval,"0")); cancel_monitor(rsc, op_key); op->op_status = LRM_OP_DONE; op->rc = EXECRA_OK; send_direct_ack(from_host, from_sys, op, rsc->id); crm_free(op_key); free_lrm_op(op); } else if(safe_str_eq(operation, CRMD_ACTION_DELETE)) { int rc = HA_OK; lrm_op_t* op = NULL; op = construct_op(input->xml, rsc->id, operation); CRM_ASSERT(op != NULL); op->op_status = LRM_OP_DONE; op->rc = EXECRA_OK; if(rsc == NULL) { crm_debug("Resource %s was already removed", rsc->id); } else { crm_info("Removing resource %s from the LRM", rsc->id); rc = fsa_lrm_conn->lrm_ops->delete_rsc( fsa_lrm_conn, rsc->id); if(rc != HA_OK) { crm_err("Failed to remove resource %s", rsc->id); op->op_status = LRM_OP_ERROR; op->rc = EXECRA_UNKNOWN_ERROR; } } send_direct_ack(from_host, from_sys, op, rsc->id); free_lrm_op(op); } else if(rsc != NULL) { next_input = do_lrm_rsc_op( rsc, operation, input->xml, input->msg); } lrm_free_rsc(rsc); } else { crm_err("Operation was neither a lrm_query, nor a rsc op. %s", crm_str(crm_op)); register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL); } return next_input; } lrm_op_t * construct_op(crm_data_t *rsc_op, const char *rsc_id, const char *operation) { lrm_op_t *op = NULL; const char *op_delay = NULL; const char *op_timeout = NULL; const char *op_interval = NULL; const char *transition = NULL; CRM_DEV_ASSERT(rsc_id != NULL); crm_malloc0(op, sizeof(lrm_op_t)); op->op_type = crm_strdup(operation); op->op_status = LRM_OP_PENDING; op->rc = -1; op->rsc_id = crm_strdup(rsc_id); op->interval = 0; op->timeout = 0; op->start_delay = 0; op->app_name = crm_strdup(CRM_SYSTEM_CRMD); if(rsc_op == NULL) { CRM_DEV_ASSERT(safe_str_eq(CRMD_ACTION_STOP, operation)); op->user_data = NULL; op->user_data_len = 0; /* the stop_all_resources() case * by definition there is no DC (or they'd be shutting * us down). * So we should put our version here. */ op->params = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); g_hash_table_insert(op->params, crm_strdup(XML_ATTR_CRM_VERSION), crm_strdup(CRM_FEATURE_SET)); crm_debug_2("Constructed %s op for %s", operation, rsc_id); return op; } op->params = xml2list(rsc_op); #if CRM_DEPRECATED_SINCE_2_0_3 if(g_hash_table_lookup(op->params, XML_ATTR_CRM_VERSION) == NULL) { g_hash_table_destroy(op->params); op->params = xml2list_202(rsc_op); } #endif if(op->params == NULL) { CRM_DEV_ASSERT(safe_str_eq(CRMD_ACTION_STOP, operation)); } op_delay = g_hash_table_lookup(op->params, crm_meta_name("start_delay")); op_timeout = g_hash_table_lookup(op->params, crm_meta_name("timeout")); op_interval = g_hash_table_lookup(op->params, crm_meta_name("interval")); #if CRM_DEPRECATED_SINCE_2_0_5 if(op_delay == NULL) { op_delay = g_hash_table_lookup(op->params, "start_delay"); } if(op_timeout == NULL) { op_timeout = g_hash_table_lookup(op->params, "timeout"); } if(op_interval == NULL) { op_interval = g_hash_table_lookup(op->params, "interval"); } #endif op->interval = crm_parse_int(op_interval, "0"); op->timeout = crm_parse_int(op_timeout, "0"); op->start_delay = crm_parse_int(op_delay, "0"); /* sanity */ if(op->interval < 0) { op->interval = 0; } if(op->timeout < 0) { op->timeout = 0; } if(op->start_delay < 0) { op->start_delay = 0; } transition = crm_element_value(rsc_op, XML_ATTR_TRANSITION_KEY); CRM_CHECK(transition != NULL, return op); op->user_data = crm_strdup(transition); op->user_data_len = 1+strlen(op->user_data); if(op->interval != 0) { if(safe_str_eq(operation, CRMD_ACTION_START) || safe_str_eq(operation, CRMD_ACTION_STOP)) { crm_err("Start and Stop actions cannot have an interval"); op->interval = 0; } } crm_debug_2("Constructed %s op for %s: interval=%d", operation, rsc_id, op->interval); return op; } void send_direct_ack(const char *to_host, const char *to_sys, lrm_op_t* op, const char *rsc_id) { HA_Message *reply = NULL; crm_data_t *update, *iter; crm_data_t *fragment; CRM_DEV_ASSERT(op != NULL); if(crm_assert_failed) { return; } if(op->rsc_id == NULL) { CRM_DEV_ASSERT(rsc_id != NULL); op->rsc_id = crm_strdup(rsc_id); } if(to_sys == NULL) { to_sys = CRM_SYSTEM_TENGINE; } crm_info("ACK'ing resource op: %s for %s", op->op_type, op->rsc_id); update = create_node_state( fsa_our_uname, NULL, NULL, NULL, NULL, NULL, FALSE, __FUNCTION__); iter = create_xml_node(update, XML_CIB_TAG_LRM); crm_xml_add(iter, XML_ATTR_ID, fsa_our_uuid); iter = create_xml_node(iter, XML_LRM_TAG_RESOURCES); iter = create_xml_node(iter, XML_LRM_TAG_RESOURCE); crm_xml_add(iter, XML_ATTR_ID, op->rsc_id); build_operation_update(iter, op, __FUNCTION__, 0); fragment = create_cib_fragment(update, XML_CIB_TAG_STATUS); reply = create_request(CRM_OP_INVOKE_LRM, fragment, to_host, to_sys, CRM_SYSTEM_LRMD, NULL); crm_debug("Sending ACK: %s", cl_get_string(reply, XML_ATTR_REFERENCE)); crm_log_xml_debug_2(update, "ACK Update"); crm_log_message_adv(LOG_DEBUG_3, "ACK Reply", reply); if(relay_message(reply, TRUE) == FALSE) { crm_log_message_adv(LOG_ERR, "Unable to route reply", reply); crm_msg_del(reply); } free_xml(fragment); free_xml(update); } enum crmd_fsa_input do_lrm_rsc_op(lrm_rsc_t *rsc, const char *operation, crm_data_t *msg, HA_Message *request) { int call_id = 0; char *op_id = NULL; lrm_op_t* op = NULL; fsa_data_t *msg_data = NULL; const char *transition = NULL; CRM_CHECK(rsc != NULL, return I_NULL); if(msg != NULL) { transition = crm_element_value(msg, XML_ATTR_TRANSITION_KEY); if(transition == NULL) { crm_err("Missing transition"); crm_log_message(LOG_ERR, msg); } } /* stop the monitor before stopping the resource */ if(safe_str_eq(operation, CRMD_ACTION_STOP)) { g_hash_table_foreach(monitors, stop_recurring_action, rsc); g_hash_table_foreach_remove( monitors, remove_recurring_action, rsc); } /* now do the op */ op = construct_op(msg, rsc->id, operation); crm_info("Performing op=%s_%s_%d key=%s)", rsc->id, operation, op->interval, transition); if((AM_I_DC == FALSE && fsa_state != S_NOT_DC) || (AM_I_DC && fsa_state != S_TRANSITION_ENGINE)) { if(safe_str_neq(operation, CRMD_ACTION_STOP)) { crm_info("Discarding attempt to perform action %s on %s" " in state %s", operation, rsc->id, fsa_state2string(fsa_state)); op->rc = 99; op->op_status = LRM_OP_ERROR; send_direct_ack(NULL, NULL, op, rsc->id); free_lrm_op(op); crm_free(op_id); return I_NULL; } } op_id = generate_op_key(rsc->id, op->op_type, op->interval); if(op->interval > 0) { cancel_monitor(rsc, op_id); op->target_rc = CHANGED; } else { op->target_rc = EVERYTIME; } g_hash_table_replace(resources,crm_strdup(rsc->id), crm_strdup(op_id)); call_id = rsc->ops->perform_op(rsc, op); if(call_id <= 0) { crm_err("Operation %s on %s failed: %d", operation, rsc->id, call_id); register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL); } else { /* record all operations so we can wait * for them to complete during shutdown */ char *call_id_s = make_stop_id(rsc->id, call_id); g_hash_table_replace( shutdown_ops, call_id_s, crm_strdup(rsc->id)); crm_debug_2("Recording pending op: %s/%s %s", rsc->id, operation, call_id_s); if(op->interval > 0) { struct recurring_op_s *op = NULL; crm_malloc0(op, sizeof(struct recurring_op_s)); crm_debug_2("Adding recurring %s op for %s (%d)", op_id, rsc->id, call_id); op->call_id = call_id; op->rsc_id = crm_strdup(rsc->id); g_hash_table_insert(monitors, op_id, op); op_id = NULL; } } crm_free(op_id); free_lrm_op(op); return I_NULL; } void stop_recurring_action(gpointer key, gpointer value, gpointer user_data) { lrm_rsc_t *rsc = user_data; struct recurring_op_s *op = (struct recurring_op_s*)value; if(safe_str_eq(op->rsc_id, rsc->id)) { if(op->call_id > 0) { crm_debug("Stopping recurring op %d for %s (%s)", op->call_id, rsc->id, (char*)key); rsc->ops->cancel_op(rsc, op->call_id); } else { crm_err("Invalid call_id %d for %s", op->call_id, rsc->id); } } } gboolean remove_recurring_action(gpointer key, gpointer value, gpointer user_data) { lrm_rsc_t *rsc = user_data; struct recurring_op_s *op = (struct recurring_op_s*)value; if(safe_str_eq(op->rsc_id, rsc->id)) { return TRUE; } return FALSE; } void free_recurring_op(gpointer value) { struct recurring_op_s *op = (struct recurring_op_s*)value; crm_free(op->rsc_id); crm_free(op); } void free_lrm_op(lrm_op_t *op) { g_hash_table_destroy(op->params); crm_free(op->user_data); crm_free(op->output); crm_free(op->rsc_id); crm_free(op->op_type); crm_free(op->app_name); crm_free(op); } static void dup_attr(gpointer key, gpointer value, gpointer user_data) { g_hash_table_replace(user_data, crm_strdup(key), crm_strdup(value)); } lrm_op_t * copy_lrm_op(const lrm_op_t *op) { lrm_op_t *op_copy = NULL; CRM_DEV_ASSERT(op != NULL); if(crm_assert_failed) { return NULL; } CRM_ASSERT(op->rsc_id != NULL); crm_malloc0(op_copy, sizeof(lrm_op_t)); op_copy->op_type = crm_strdup(op->op_type); /* input fields */ op_copy->params = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); if(op->params != NULL) { g_hash_table_foreach(op->params, dup_attr, op_copy->params); } op_copy->timeout = op->timeout; op_copy->interval = op->interval; op_copy->target_rc = op->target_rc; /* in the CRM, this is always a string */ if(op->user_data != NULL) { op_copy->user_data = crm_strdup(op->user_data); } /* output fields */ op_copy->op_status = op->op_status; op_copy->rc = op->rc; op_copy->call_id = op->call_id; op_copy->output = NULL; op_copy->rsc_id = crm_strdup(op->rsc_id); if(op->app_name != NULL) { op_copy->app_name = crm_strdup(op->app_name); } if(op->output != NULL) { op_copy->output = crm_strdup(op->output); } return op_copy; } lrm_rsc_t * copy_lrm_rsc(const lrm_rsc_t *rsc) { lrm_rsc_t *rsc_copy = NULL; if(rsc == NULL) { return NULL; } crm_malloc0(rsc_copy, sizeof(lrm_rsc_t)); rsc_copy->id = crm_strdup(rsc->id); rsc_copy->type = crm_strdup(rsc->type); rsc_copy->class = NULL; rsc_copy->provider = NULL; if(rsc->class != NULL) { rsc_copy->class = crm_strdup(rsc->class); } if(rsc->provider != NULL) { rsc_copy->provider = crm_strdup(rsc->provider); } /* GHashTable* params; */ rsc_copy->params = NULL; rsc_copy->ops = NULL; return rsc_copy; } static void cib_rsc_callback(const HA_Message *msg, int call_id, int rc, crm_data_t *output, void *user_data) { if(rc != cib_ok) { crm_err("Resource update %d failed: %s", call_id, cib_error2string(rc)); } else { crm_debug("Resource update %d complete", call_id); } } void do_update_resource(lrm_op_t* op) { /* */ int rc = cib_ok; lrm_rsc_t *rsc = NULL; crm_data_t *update, *iter; CRM_CHECK(op != NULL, return); update = create_node_state( fsa_our_uname, NULL, NULL, NULL, NULL, NULL, FALSE, __FUNCTION__); iter = create_xml_node(update, XML_CIB_TAG_LRM); crm_xml_add(iter, XML_ATTR_ID, fsa_our_uuid); iter = create_xml_node(iter, XML_LRM_TAG_RESOURCES); iter = create_xml_node(iter, XML_LRM_TAG_RESOURCE); crm_xml_add(iter, XML_ATTR_ID, op->rsc_id); rsc = fsa_lrm_conn->lrm_ops->get_rsc(fsa_lrm_conn, op->rsc_id); crm_xml_add(iter, XML_ATTR_TYPE, rsc->type); crm_xml_add(iter, XML_AGENT_ATTR_CLASS, rsc->class); crm_xml_add(iter, XML_AGENT_ATTR_PROVIDER,rsc->provider); lrm_free_rsc(rsc); build_operation_update(iter, op, __FUNCTION__, 0); /* make it an asyncronous call and be done with it * * Best case: * the resource state will be discovered during * the next signup or election. * * Bad case: * we are shutting down and there is no DC at the time, * but then why were we shutting down then anyway? * (probably because of an internal error) * * Worst case: * we get shot for having resources "running" when the really weren't * * the alternative however means blocking here for too long, which * isnt acceptable */ fsa_cib_update(XML_CIB_TAG_STATUS, update, cib_quorum_override, rc); if(rc > 0) { /* the return code is a call number, not an error code */ crm_debug("Sent resource state update message: %d", rc); add_cib_op_callback(rc, FALSE, NULL, cib_rsc_callback); } else { crm_err("Resource state update failed: %s", cib_error2string(rc)); } free_xml(update); } enum crmd_fsa_input do_lrm_event(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input cur_input, fsa_data_t *msg_data) { lrm_op_t *op = NULL; CRM_CHECK(msg_data->fsa_cause == C_LRM_OP_CALLBACK, return I_NULL); op = fsa_typed_data(fsa_dt_lrm); process_lrm_event(op); return I_NULL; } gboolean process_lrm_event(lrm_op_t *op) { + char *op_id = NULL; + int log_level = LOG_ERR; CRM_CHECK(op != NULL, return I_NULL); CRM_CHECK(op->rsc_id != NULL, return I_NULL); if(op->rc == 8 || op->rc == 7) { /* Leave it up to the TE/PE to decide if this is an error */ op->op_status = LRM_OP_DONE; } switch(op->op_status) { - case LRM_OP_PENDING: - /* this really shouldnt happen */ - crm_err("LRM operation (%d) %s_%d on %s %s: %s", - op->call_id, op->op_type, - op->interval, - crm_str(op->rsc_id), - op_status2text(op->op_status), - execra_code2string(op->rc)); - break; case LRM_OP_ERROR: - crm_err("LRM operation (%d) %s_%d on %s %s: (%d) %s", - op->call_id, op->op_type, - op->interval, - crm_str(op->rsc_id), - op_status2text(op->op_status), - op->rc, execra_code2string(op->rc)); - if(op->output != NULL) { - crm_debug("Result: %s", op->output); - } + case LRM_OP_PENDING: + case LRM_OP_NOTSUPPORTED: break; case LRM_OP_CANCELLED: - crm_warn("LRM operation (%d) %s_%d on %s %s", - op->call_id, op->op_type, - op->interval, - crm_str(op->rsc_id), - op_status2text(op->op_status)); - break; - case LRM_OP_TIMEOUT: - crm_err("LRM operation (%d) %s_%d on %s %s (timeout=%dms)", - op->call_id, op->op_type, op->interval, - crm_str(op->rsc_id), - op_status2text(op->op_status), op->timeout); - break; - case LRM_OP_NOTSUPPORTED: - crm_err("LRM operation (%d) %s_%d on %s %s", - op->call_id, op->op_type, - op->interval, - crm_str(op->rsc_id), - op_status2text(op->op_status)); + log_level = LOG_WARNING; break; case LRM_OP_DONE: - crm_info("LRM operation (%d) %s_%d on %s %s", - op->call_id, op->op_type, - op->interval, - crm_str(op->rsc_id), - op_status2text(op->op_status)); + log_level = LOG_INFO; break; + case LRM_OP_TIMEOUT: + log_level = LOG_DEBUG_3; + crm_err("LRM operation %s_%s_%d (%d) %s (timeout=%dms)", + crm_str(op->rsc_id), op->op_type, op->interval, + op->call_id, op_status2text(op->op_status), + op->timeout); + /* set op->rc because the lrm doesn't bother */ + op->rc = -1; + break; + default: + crm_err("Mapping unknown status (%d) to ERROR", + op->op_status); + op->op_status = LRM_OP_ERROR; + } + + do_crm_log(log_level, "LRM operation %s_%s_%d (call=%d, rc=%d) %s %s", + crm_str(op->rsc_id), op->op_type, op->interval, + op->call_id, op->rc, op_status2text(op->op_status), + op->op_status==LRM_OP_ERROR?execra_code2string(op->rc):""); + + if(op->op_status == LRM_OP_ERROR && op->output != NULL) { + crm_info("Result: %s", op->output); } if(op->op_status != LRM_OP_CANCELLED) { do_update_resource(op); if(op->interval > 0) { - crm_debug("Op %d %s_%s_%d returned", - op->call_id, op->rsc_id, op->op_type, - op->interval); + /* dont remove active recurring ops from + * the shutdown list + */ return TRUE; } } else if(op->interval == 0) { - crm_err("No update sent for cancelled op %d: %s_%s_%d", - op->call_id, op->rsc_id, op->op_type, op->interval); + crm_err("Op %s_%s_%d (call=%d): cancelled!", + op->rsc_id, op->op_type, op->interval, op->call_id); } - if(g_hash_table_size(shutdown_ops) > 0) { - char *op_id = make_stop_id(op->rsc_id, op->call_id); - if(g_hash_table_remove(shutdown_ops, op_id)) { - crm_debug_2("Op %d %s_%s_%d is confirmed", - op->call_id, op->rsc_id, op->op_type, - op->interval); - crm_free(op_id); - return TRUE; - } - crm_free(op_id); + op_id = make_stop_id(op->rsc_id, op->call_id); + if(g_hash_table_remove(shutdown_ops, op_id)) { + crm_debug("Op %s_%s_%d (call=%d): confirmed", + op->rsc_id, op->op_type, op->interval, op->call_id); + goto out; } + + /* most likely scenario is that it previously timed out */ crm_err("Op %d %s_%s_%d not matched", op->call_id, op->rsc_id, op->op_type, op->interval); + + out: + crm_free(op_id); return TRUE; } char * make_stop_id(const char *rsc, int call_id) { char *op_id = NULL; crm_malloc0(op_id, strlen(rsc) + 34); if(op_id != NULL) { snprintf(op_id, strlen(rsc) + 34, "%s:%d", rsc, call_id); } return op_id; } gboolean resource_stopped(gpointer key, gpointer value, gpointer user_data) { const char *this_rsc = value; const char *target_rsc = user_data; if(safe_str_eq(this_rsc, target_rsc)) { return TRUE; } return FALSE; } diff --git a/crm/pengine/allocate.c b/crm/pengine/allocate.c index a18f6c4870..c3280d9254 100644 --- a/crm/pengine/allocate.c +++ b/crm/pengine/allocate.c @@ -1,1354 +1,1330 @@ /* $Id: allocate.c,v 1.12 2006/08/14 09:06:31 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include void set_alloc_actions(pe_working_set_t *data_set); resource_alloc_functions_t resource_class_alloc_functions[] = { { native_set_cmds, native_num_allowed_nodes, native_color, native_create_actions, native_create_probe, native_internal_constraints, native_agent_constraints, native_rsc_colocation_lh, native_rsc_colocation_rh, native_rsc_order_lh, native_rsc_order_rh, native_rsc_location, native_expand, native_stonith_ordering, native_create_notify_element, }, { group_set_cmds, group_num_allowed_nodes, group_color, group_create_actions, group_create_probe, group_internal_constraints, group_agent_constraints, group_rsc_colocation_lh, group_rsc_colocation_rh, group_rsc_order_lh, group_rsc_order_rh, group_rsc_location, group_expand, group_stonith_ordering, group_create_notify_element, }, { clone_set_cmds, clone_num_allowed_nodes, clone_color, clone_create_actions, clone_create_probe, clone_internal_constraints, clone_agent_constraints, clone_rsc_colocation_lh, clone_rsc_colocation_rh, clone_rsc_order_lh, clone_rsc_order_rh, clone_rsc_location, clone_expand, clone_stonith_ordering, clone_create_notify_element, }, { clone_set_cmds, clone_num_allowed_nodes, master_color, master_create_actions, clone_create_probe, master_internal_constraints, clone_agent_constraints, clone_rsc_colocation_lh, master_rsc_colocation_rh, clone_rsc_order_lh, clone_rsc_order_rh, clone_rsc_location, clone_expand, clone_stonith_ordering, clone_create_notify_element, } }; static gboolean check_rsc_parameters(resource_t *rsc, node_t *node, crm_data_t *rsc_entry, pe_working_set_t *data_set) { int attr_lpc = 0; gboolean force_restart = FALSE; gboolean delete_resource = FALSE; const char *value = NULL; const char *old_value = NULL; const char *attr_list[] = { XML_ATTR_TYPE, XML_AGENT_ATTR_CLASS, XML_AGENT_ATTR_PROVIDER }; for(; attr_lpc < DIMOF(attr_list); attr_lpc++) { value = crm_element_value(rsc->xml, attr_list[attr_lpc]); old_value = crm_element_value(rsc_entry, attr_list[attr_lpc]); if(safe_str_eq(value, old_value)) { continue; } force_restart = TRUE; crm_notice("Forcing restart of %s on %s, %s changed: %s -> %s", rsc->id, node->details->uname, attr_list[attr_lpc], crm_str(old_value), crm_str(value)); } if(force_restart) { /* make sure the restart happens */ stop_action(rsc, node, FALSE); rsc->start_pending = TRUE; delete_resource = TRUE; } return delete_resource; } static gboolean check_action_definition(resource_t *rsc, node_t *active_node, crm_data_t *xml_op, pe_working_set_t *data_set) { char *key = NULL; int interval = 0; const char *interval_s = NULL; gboolean did_change = FALSE; crm_data_t *pnow = NULL; GHashTable *local_rsc_params = NULL; char *pnow_digest = NULL; const char *param_digest = NULL; char *local_param_digest = NULL; -#if CRM_DEPRECATED_SINCE_2_0_4 - crm_data_t *params = NULL; -#endif - action_t *action = NULL; const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK); const char *op_version = crm_element_value(xml_op, XML_ATTR_CRM_VERSION); CRM_CHECK(active_node != NULL, return FALSE); - interval_s = get_interval(xml_op); + interval_s = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL); interval = crm_parse_int(interval_s, "0"); key = generate_op_key(rsc->id, task, interval); if(interval > 0) { crm_data_t *op_match = NULL; crm_debug_2("Checking parameters for %s %s", key, task); op_match = find_rsc_op_entry(rsc, key); if(op_match == NULL && data_set->stop_action_orphans) { /* create a cancel action */ action_t *cancel = NULL; char *cancel_key = NULL; crm_info("Orphan action will be stopped: %s on %s", key, active_node->details->uname); cancel_key = generate_op_key(rsc->id, CRMD_ACTION_CANCEL, interval); cancel = custom_action( rsc, cancel_key, CRMD_ACTION_CANCEL, active_node, FALSE, TRUE, data_set); add_hash_param(cancel->meta, XML_LRM_ATTR_TASK, task); add_hash_param(cancel->meta, XML_LRM_ATTR_INTERVAL, interval_s); custom_action_order( rsc, NULL, cancel, rsc, stop_key(rsc), NULL, pe_ordering_optional, data_set); } if(op_match == NULL) { crm_debug("Orphan action detected: %s on %s", key, active_node->details->uname); crm_free(key); key = NULL; return TRUE; } } action = custom_action(rsc, key, task, active_node, TRUE, FALSE, data_set); local_rsc_params = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); unpack_instance_attributes( rsc->xml, XML_TAG_ATTR_SETS, active_node->details->attrs, local_rsc_params, NULL, data_set->now); pnow = create_xml_node(NULL, XML_TAG_PARAMS); g_hash_table_foreach(action->extra, hash2field, pnow); g_hash_table_foreach(rsc->parameters, hash2field, pnow); g_hash_table_foreach(local_rsc_params, hash2field, pnow); filter_action_parameters(pnow, op_version); pnow_digest = calculate_xml_digest(pnow, TRUE); param_digest = crm_element_value(xml_op, XML_LRM_ATTR_OP_DIGEST); -#if CRM_DEPRECATED_SINCE_2_0_4 - if(param_digest == NULL) { - params = find_xml_node(xml_op, XML_TAG_PARAMS, TRUE); - } - if(params != NULL) { - crm_data_t *local_params = copy_xml(params); - - crm_warn("Faking parameter digest creation for %s", ID(xml_op)); - filter_action_parameters(local_params, op_version); - xml_remove_prop(local_params, "interval"); - xml_remove_prop(local_params, "timeout"); - crm_log_xml_warn(local_params, "params:used"); - - local_param_digest = calculate_xml_digest(local_params, TRUE); - param_digest = local_param_digest; - - free_xml(local_params); - } -#endif - if(safe_str_neq(pnow_digest, param_digest)) { did_change = TRUE; crm_log_xml_info(pnow, "params:calc"); crm_warn("Parameters to %s on %s changed: recorded %s vs. calculated %s", ID(xml_op), active_node->details->uname, crm_str(param_digest), pnow_digest); key = generate_op_key(rsc->id, task, interval); custom_action(rsc, key, task, NULL, FALSE, TRUE, data_set); } free_xml(pnow); crm_free(pnow_digest); crm_free(local_param_digest); g_hash_table_destroy(local_rsc_params); pe_free_action(action); return did_change; } extern gboolean DeleteRsc(resource_t *rsc, node_t *node, pe_working_set_t *data_set); static void check_actions_for(crm_data_t *rsc_entry, node_t *node, pe_working_set_t *data_set) { const char *id = NULL; const char *task = NULL; int interval = 0; const char *interval_s = NULL; GListPtr op_list = NULL; GListPtr sorted_op_list = NULL; const char *rsc_id = ID(rsc_entry); gboolean is_probe = FALSE; resource_t *rsc = pe_find_resource(data_set->resources, rsc_id); CRM_CHECK(rsc_id != NULL, return); if(rsc == NULL) { crm_warn("Skipping param check for resource with no actions"); return; } else if(rsc->orphan) { crm_debug_2("Skipping param check for orphan: %s %s", rsc->id, task); return; } crm_debug_3("Processing %s on %s", rsc->id, node->details->uname); if(check_rsc_parameters(rsc, node, rsc_entry, data_set)) { DeleteRsc(rsc, node, data_set); } xml_child_iter_filter( rsc_entry, rsc_op, XML_LRM_TAG_RSC_OP, op_list = g_list_append(op_list, rsc_op); ); sorted_op_list = g_list_sort(op_list, sort_op_by_callid); slist_iter( rsc_op, crm_data_t, sorted_op_list, lpc, id = ID(rsc_op); is_probe = FALSE; task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK); - interval_s = get_interval(rsc_op); + interval_s = crm_element_value(rsc_op, XML_LRM_ATTR_INTERVAL); interval = crm_parse_int(interval_s, "0"); if(interval == 0 && safe_str_eq(task, CRMD_ACTION_STATUS)) { is_probe = TRUE; } if(is_probe || safe_str_eq(task, CRMD_ACTION_START) || interval > 0) { check_action_definition(rsc, node, rsc_op, data_set); } ); g_list_free(sorted_op_list); } static void check_actions(pe_working_set_t *data_set) { const char *id = NULL; node_t *node = NULL; crm_data_t *lrm_rscs = NULL; crm_data_t *status = get_object_root(XML_CIB_TAG_STATUS, data_set->input); xml_child_iter_filter( status, node_state, XML_CIB_TAG_STATE, id = crm_element_value(node_state, XML_ATTR_ID); lrm_rscs = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE); lrm_rscs = find_xml_node(lrm_rscs, XML_LRM_TAG_RESOURCES, FALSE); node = pe_find_node_id(data_set->nodes, id); if(node == NULL) { continue; } crm_debug_2("Processing node %s", node->details->uname); if(node->details->online || data_set->stonith_enabled) { xml_child_iter_filter( lrm_rscs, rsc_entry, XML_LRM_TAG_RESOURCE, check_actions_for(rsc_entry, node, data_set); ); } ); } static gboolean apply_placement_constraints(pe_working_set_t *data_set) { crm_debug_3("Applying constraints..."); slist_iter( cons, rsc_to_node_t, data_set->placement_constraints, lpc, cons->rsc_lh->cmds->rsc_location(cons->rsc_lh, cons); ); return TRUE; } void set_alloc_actions(pe_working_set_t *data_set) { slist_iter( rsc, resource_t, data_set->resources, lpc, rsc->cmds = &resource_class_alloc_functions[rsc->variant]; rsc->cmds->set_cmds(rsc); ); } gboolean stage0(pe_working_set_t *data_set) { crm_data_t * cib_constraints = get_object_root( XML_CIB_TAG_CONSTRAINTS, data_set->input); if(data_set->input == NULL) { return FALSE; } cluster_status(data_set); set_alloc_actions(data_set); unpack_constraints(cib_constraints, data_set); return TRUE; } /* * Check nodes for resources started outside of the LRM */ gboolean stage1(pe_working_set_t *data_set) { action_t *probe_complete = NULL; action_t *probe_node_complete = NULL; slist_iter( node, node_t, data_set->nodes, lpc, gboolean force_probe = FALSE; const char *probed = g_hash_table_lookup( node->details->attrs, CRM_OP_PROBED); crm_debug_2("%s probed: %s", node->details->uname, probed); if(node->details->online == FALSE) { continue; } else if(node->details->unclean) { continue; } else if(probe_complete == NULL) { probe_complete = custom_action( NULL, crm_strdup(CRM_OP_PROBED), CRM_OP_PROBED, NULL, FALSE, TRUE, data_set); probe_complete->pseudo = TRUE; probe_complete->optional = TRUE; } if(probed != NULL && crm_is_true(probed) == FALSE) { force_probe = TRUE; } probe_node_complete = custom_action( NULL, crm_strdup(CRM_OP_PROBED), CRM_OP_PROBED, node, FALSE, TRUE, data_set); probe_node_complete->optional = crm_is_true(probed); probe_node_complete->priority = INFINITY; add_hash_param(probe_node_complete->meta, XML_ATTR_TE_NOWAIT, XML_BOOLEAN_TRUE); custom_action_order(NULL, NULL, probe_node_complete, NULL, NULL, probe_complete, pe_ordering_optional, data_set); slist_iter( rsc, resource_t, data_set->resources, lpc2, if(rsc->cmds->create_probe( rsc, node, probe_node_complete, force_probe, data_set)) { probe_complete->optional = FALSE; probe_node_complete->optional = FALSE; custom_action_order( NULL, NULL, probe_complete, rsc, start_key(rsc), NULL, pe_ordering_manditory, data_set); } ); ); return TRUE; } /* * Count how many valid nodes we have (so we know the maximum number of * colors we can resolve). * * Apply node constraints (ie. filter the "allowed_nodes" part of resources */ gboolean stage2(pe_working_set_t *data_set) { crm_debug_3("Applying placement constraints"); slist_iter( node, node_t, data_set->nodes, lpc, if(node == NULL) { /* error */ } else if(node->weight >= 0.0 /* global weight */ && node->details->online && node->details->type == node_member) { data_set->max_valid_nodes++; } ); apply_placement_constraints(data_set); return TRUE; } /* * Create internal resource constraints before allocation */ gboolean stage3(pe_working_set_t *data_set) { slist_iter( rsc, resource_t, data_set->resources, lpc, rsc->cmds->internal_constraints(rsc, data_set); ); return TRUE; } /* * Check for orphaned or redefined actions */ gboolean stage4(pe_working_set_t *data_set) { check_actions(data_set); return TRUE; } gboolean stage5(pe_working_set_t *data_set) { /* Take (next) highest resource, assign it and create its actions */ slist_iter( rsc, resource_t, data_set->resources, lpc, rsc->cmds->color(rsc, data_set); rsc->cmds->create_actions(rsc, data_set); ); return TRUE; } /* * Create dependacies for stonith and shutdown operations */ gboolean stage6(pe_working_set_t *data_set) { action_t *dc_down = NULL; action_t *stonith_op = NULL; action_t *last_stonith = NULL; gboolean integrity_lost = FALSE; crm_debug_3("Processing fencing and shutdown cases"); slist_iter( node, node_t, data_set->nodes, lpc, stonith_op = NULL; if(node->details->unclean && data_set->stonith_enabled && (data_set->have_quorum || data_set->no_quorum_policy == no_quorum_ignore)) { pe_warn("Scheduling Node %s for STONITH", node->details->uname); stonith_op = custom_action( NULL, crm_strdup(CRM_OP_FENCE), CRM_OP_FENCE, node, FALSE, TRUE, data_set); add_hash_param( stonith_op->meta, XML_LRM_ATTR_TARGET, node->details->uname); add_hash_param( stonith_op->meta, XML_LRM_ATTR_TARGET_UUID, node->details->id); add_hash_param( stonith_op->meta, "stonith_action", data_set->stonith_action); stonith_constraints(node, stonith_op, data_set); if(node->details->is_dc) { dc_down = stonith_op; } else { if(last_stonith) { order_actions(last_stonith, stonith_op, pe_ordering_manditory); } last_stonith = stonith_op; } } else if(node->details->online && node->details->shutdown) { action_t *down_op = NULL; crm_info("Scheduling Node %s for shutdown", node->details->uname); down_op = custom_action( NULL, crm_strdup(CRM_OP_SHUTDOWN), CRM_OP_SHUTDOWN, node, FALSE, TRUE, data_set); shutdown_constraints(node, down_op, data_set); if(node->details->is_dc) { dc_down = down_op; } } if(node->details->unclean && stonith_op == NULL) { integrity_lost = TRUE; pe_warn("Node %s is unclean!", node->details->uname); } ); if(integrity_lost) { if(data_set->have_quorum == FALSE) { crm_notice("Cannot fence unclean nodes until quorum is" " attained (or no_quorum_policy is set to ignore)"); } else if(data_set->stonith_enabled == FALSE) { pe_warn("YOUR RESOURCES ARE NOW LIKELY COMPROMISED"); pe_err("ENABLE STONITH TO KEEP YOUR RESOURCES SAFE"); } } if(dc_down != NULL) { GListPtr shutdown_matches = find_actions( data_set->actions, CRM_OP_SHUTDOWN, NULL); crm_debug_2("Ordering shutdowns before %s on %s (DC)", dc_down->task, dc_down->node->details->uname); add_hash_param(dc_down->meta, XML_ATTR_TE_NOWAIT, XML_BOOLEAN_TRUE); slist_iter( node_stop, action_t, shutdown_matches, lpc, if(node_stop->node->details->is_dc) { continue; } crm_debug("Ordering shutdown on %s before %s on %s", node_stop->node->details->uname, dc_down->task, dc_down->node->details->uname); order_actions(node_stop, dc_down, pe_ordering_manditory); ); if(last_stonith && dc_down != last_stonith) { order_actions(last_stonith, dc_down, pe_ordering_manditory); } } return TRUE; } /* * Determin the sets of independant actions and the correct order for the * actions in each set. * * Mark dependencies of un-runnable actions un-runnable * */ gboolean stage7(pe_working_set_t *data_set) { crm_debug_4("Applying ordering constraints"); slist_iter( order, order_constraint_t, data_set->ordering_constraints, lpc, resource_t *rsc = order->lh_rsc; crm_debug_3("Applying ordering constraint: %d", order->id); if(rsc != NULL) { crm_debug_4("rsc_action-to-*"); rsc->cmds->rsc_order_lh(rsc, order); continue; } rsc = order->rh_rsc; if(rsc != NULL) { crm_debug_4("action-to-rsc_action"); rsc->cmds->rsc_order_rh(order->lh_action, rsc, order); } else { crm_debug_4("action-to-action"); order_actions( order->lh_action, order->rh_action, order->type); } ); update_action_states(data_set->actions); return TRUE; } int transition_id = -1; /* * Create a dependency graph to send to the transitioner (via the CRMd) */ gboolean stage8(pe_working_set_t *data_set) { const char *value = NULL; char *transition_id_s = NULL; transition_id++; transition_id_s = crm_itoa(transition_id); value = pe_pref(data_set->config_hash, "network-delay"); crm_debug_2("Creating transition graph %d.", transition_id); data_set->graph = create_xml_node(NULL, XML_TAG_GRAPH); crm_xml_add(data_set->graph, "network-delay", value); crm_xml_add(data_set->graph, "transition_id", transition_id_s); crm_free(transition_id_s); /* errors... slist_iter(action, action_t, action_list, lpc, if(action->optional == FALSE && action->runnable == FALSE) { print_action("Ignoring", action, TRUE); } ); */ slist_iter( rsc, resource_t, data_set->resources, lpc, crm_debug_4("processing actions for rsc=%s", rsc->id); rsc->cmds->expand(rsc, data_set); ); crm_log_xml_debug_3( data_set->graph, "created resource-driven action list"); /* catch any non-resource specific actions */ crm_debug_4("processing non-resource actions"); slist_iter( action, action_t, data_set->actions, lpc, graph_element_from_action(action, data_set); ); crm_log_xml_debug_3(data_set->graph, "created generic action list"); crm_debug_2("Created transition graph %d.", transition_id); return TRUE; } void cleanup_alloc_calculations(pe_working_set_t *data_set) { if(data_set == NULL) { return; } crm_debug_3("deleting order cons: %p", data_set->ordering_constraints); pe_free_ordering(data_set->ordering_constraints); data_set->ordering_constraints = NULL; crm_debug_3("deleting node cons: %p", data_set->placement_constraints); pe_free_rsc_to_node(data_set->placement_constraints); data_set->placement_constraints = NULL; cleanup_calculations(data_set); } gboolean unpack_constraints(crm_data_t * xml_constraints, pe_working_set_t *data_set) { crm_data_t *lifetime = NULL; xml_child_iter( xml_constraints, xml_obj, const char *id = crm_element_value(xml_obj, XML_ATTR_ID); if(id == NULL) { crm_config_err("Constraint <%s...> must have an id", crm_element_name(xml_obj)); continue; } crm_debug_3("Processing constraint %s %s", crm_element_name(xml_obj),id); lifetime = cl_get_struct(xml_obj, "lifetime"); if(test_ruleset(lifetime, NULL, data_set->now) == FALSE) { crm_info("Constraint %s %s is not active", crm_element_name(xml_obj), id); } else if(safe_str_eq(XML_CONS_TAG_RSC_ORDER, crm_element_name(xml_obj))) { unpack_rsc_order(xml_obj, data_set); } else if(safe_str_eq(XML_CONS_TAG_RSC_DEPEND, crm_element_name(xml_obj))) { unpack_rsc_colocation(xml_obj, data_set); } else if(safe_str_eq(XML_CONS_TAG_RSC_LOCATION, crm_element_name(xml_obj))) { unpack_rsc_location(xml_obj, data_set); } else { pe_err("Unsupported constraint type: %s", crm_element_name(xml_obj)); } ); return TRUE; } static const char * invert_action(const char *action) { if(safe_str_eq(action, CRMD_ACTION_START)) { return CRMD_ACTION_STOP; } else if(safe_str_eq(action, CRMD_ACTION_STOP)) { return CRMD_ACTION_START; } else if(safe_str_eq(action, CRMD_ACTION_PROMOTE)) { return CRMD_ACTION_DEMOTE; } else if(safe_str_eq(action, CRMD_ACTION_DEMOTE)) { return CRMD_ACTION_PROMOTE; } else if(safe_str_eq(action, CRMD_ACTION_PROMOTED)) { return CRMD_ACTION_DEMOTED; } else if(safe_str_eq(action, CRMD_ACTION_DEMOTED)) { return CRMD_ACTION_PROMOTED; } else if(safe_str_eq(action, CRMD_ACTION_STARTED)) { return CRMD_ACTION_STOPPED; } else if(safe_str_eq(action, CRMD_ACTION_STOPPED)) { return CRMD_ACTION_STARTED; } crm_config_warn("Unknown action: %s", action); return NULL; } gboolean unpack_rsc_order(crm_data_t * xml_obj, pe_working_set_t *data_set) { resource_t *rsc_lh = NULL; resource_t *rsc_rh = NULL; gboolean symmetrical_bool = TRUE; enum pe_ordering cons_weight = pe_ordering_optional; const char *id_rh = NULL; const char *id_lh = NULL; const char *action = NULL; const char *action_rh = NULL; const char *id = crm_element_value(xml_obj, XML_ATTR_ID); const char *type = crm_element_value(xml_obj, XML_ATTR_TYPE); const char *score = crm_element_value(xml_obj, XML_RULE_ATTR_SCORE); const char *symmetrical = crm_element_value( xml_obj, XML_CONS_ATTR_SYMMETRICAL); cl_str_to_boolean(symmetrical, &symmetrical_bool); if(xml_obj == NULL) { crm_config_err("No constraint object to process."); return FALSE; } else if(id == NULL) { crm_config_err("%s constraint must have an id", crm_element_name(xml_obj)); return FALSE; } if(safe_str_eq(type, "before")) { id_lh = crm_element_value(xml_obj, XML_CONS_ATTR_TO); id_rh = crm_element_value(xml_obj, XML_CONS_ATTR_FROM); action = crm_element_value(xml_obj, XML_CONS_ATTR_ACTION); action_rh = crm_element_value(xml_obj, XML_CONS_ATTR_TOACTION); } else { type="after"; id_rh = crm_element_value(xml_obj, XML_CONS_ATTR_TO); id_lh = crm_element_value(xml_obj, XML_CONS_ATTR_FROM); action = crm_element_value(xml_obj, XML_CONS_ATTR_TOACTION); action_rh = crm_element_value(xml_obj, XML_CONS_ATTR_ACTION); if(action == NULL) { action = action_rh; } } if(id_lh == NULL || id_rh == NULL) { crm_config_err("Constraint %s needs two sides lh: %s rh: %s", id, crm_str(id_lh), crm_str(id_rh)); return FALSE; } if(action == NULL) { action = CRMD_ACTION_START; } if(action_rh == NULL) { action_rh = action; } rsc_lh = pe_find_resource(data_set->resources, id_rh); rsc_rh = pe_find_resource(data_set->resources, id_lh); crm_debug("%s: %s.%s %s %s.%s%s", id, rsc_lh->id, action, type, rsc_rh->id, action_rh, symmetrical_bool?" (symmetrical)":""); if(rsc_lh == NULL) { crm_config_err("Constraint %s: no resource found for LHS of %s", id, id_lh); return FALSE; } else if(rsc_rh == NULL) { crm_config_err("Constraint %s: no resource found for RHS of %s", id, id_rh); return FALSE; } if(char2score(score) > 0) { /* the name seems weird but the effect is correct */ cons_weight = pe_ordering_restart; } custom_action_order( rsc_lh, generate_op_key(rsc_lh->id, action, 0), NULL, rsc_rh, generate_op_key(rsc_rh->id, action_rh, 0), NULL, cons_weight, data_set); if(rsc_rh->restart_type == pe_restart_restart && safe_str_eq(action, action_rh)) { if(safe_str_eq(action, CRMD_ACTION_START)) { crm_debug_2("Recover %s.%s-%s.%s", rsc_lh->id, action, rsc_rh->id, action_rh); /* order_start_start(rsc_lh, rsc_rh, pe_ordering_recover); */ custom_action_order( rsc_lh, generate_op_key(rsc_lh->id, action, 0), NULL, rsc_rh, generate_op_key(rsc_rh->id, action_rh, 0), NULL, pe_ordering_recover, data_set); } else if(safe_str_eq(action, CRMD_ACTION_STOP)) { crm_debug_2("Recover %s.%s-%s.%s", rsc_rh->id, action_rh, rsc_lh->id, action); /* order_stop_stop(rsc_rh, rsc_lh, pe_ordering_recover); */ custom_action_order( rsc_rh, generate_op_key(rsc_rh->id, action_rh, 0), NULL, rsc_lh, generate_op_key(rsc_lh->id, action, 0), NULL, pe_ordering_recover, data_set); } } if(symmetrical_bool == FALSE) { return TRUE; } action = invert_action(action); action_rh = invert_action(action_rh); if(action == NULL || action_rh == NULL) { crm_config_err("Cannot invert rsc_order constraint %s." " Please specify the inverse manually.", id); return TRUE; } custom_action_order( rsc_rh, generate_op_key(rsc_rh->id, action_rh, 0), NULL, rsc_lh, generate_op_key(rsc_lh->id, action, 0), NULL, cons_weight, data_set); if(rsc_lh->restart_type == pe_restart_restart && safe_str_eq(action, action_rh)) { if(safe_str_eq(action, CRMD_ACTION_START)) { crm_debug_2("Recover start-start (2): %s-%s", rsc_lh->id, rsc_rh->id); /* order_start_start(rsc_lh, rsc_rh, pe_ordering_recover); */ custom_action_order( rsc_lh, generate_op_key(rsc_lh->id, action, 0), NULL, rsc_rh, generate_op_key(rsc_rh->id, action_rh, 0), NULL, pe_ordering_recover, data_set); } else if(safe_str_eq(action, CRMD_ACTION_STOP)) { crm_debug_2("Recover stop-stop (2): %s-%s", rsc_rh->id, rsc_lh->id); /* order_stop_stop(rsc_rh, rsc_lh, pe_ordering_recover); */ custom_action_order( rsc_rh, generate_op_key(rsc_rh->id, action_rh, 0), NULL, rsc_lh, generate_op_key(rsc_lh->id, action, 0), NULL, pe_ordering_recover, data_set); } } return TRUE; } gboolean unpack_rsc_location(crm_data_t * xml_obj, pe_working_set_t *data_set) { gboolean empty = TRUE; const char *id_lh = crm_element_value(xml_obj, "rsc"); const char *id = crm_element_value(xml_obj, XML_ATTR_ID); resource_t *rsc_lh = pe_find_resource(data_set->resources, id_lh); if(rsc_lh == NULL) { /* only a warn as BSC adds the constraint then the resource */ crm_config_warn("No resource (con=%s, rsc=%s)", id, id_lh); return FALSE; } else if(rsc_lh->is_managed == FALSE) { crm_debug_2("Ignoring constraint %s: resource %s not managed", id, id_lh); return FALSE; } xml_child_iter_filter( xml_obj, rule_xml, XML_TAG_RULE, empty = FALSE; crm_debug_2("Unpacking %s/%s", id, ID(rule_xml)); generate_location_rule(rsc_lh, rule_xml, data_set); ); if(empty) { crm_config_err("Invalid location constraint %s:" " rsc_location must contain at least one rule", ID(xml_obj)); } return TRUE; } static int get_node_score(const char *rule, const char *score, gboolean raw, node_t *node) { int score_f = 0; if(score == NULL) { pe_err("Rule %s: no score specified. Assuming 0.", rule); } else if(raw) { score_f = char2score(score); } else { const char *attr_score = g_hash_table_lookup( node->details->attrs, score); if(attr_score == NULL) { crm_debug("Rule %s: node %s did not have a value for %s", rule, node->details->uname, score); score_f = -INFINITY; } else { crm_debug("Rule %s: node %s had value %s for %s", rule, node->details->uname, attr_score, score); score_f = char2score(attr_score); } } return score_f; } rsc_to_node_t * generate_location_rule( resource_t *rsc, crm_data_t *rule_xml, pe_working_set_t *data_set) { const char *rule_id = NULL; const char *score = NULL; const char *boolean = NULL; const char *role = NULL; GListPtr match_L = NULL; int score_f = 0; gboolean do_and = TRUE; gboolean accept = TRUE; gboolean raw_score = TRUE; rsc_to_node_t *location_rule = NULL; rule_id = crm_element_value(rule_xml, XML_ATTR_ID); boolean = crm_element_value(rule_xml, XML_RULE_ATTR_BOOLEAN_OP); role = crm_element_value(rule_xml, XML_RULE_ATTR_ROLE); crm_debug_2("Processing rule: %s", rule_id); if(role != NULL && text2role(role) == RSC_ROLE_UNKNOWN) { pe_err("Bad role specified for %s: %s", rule_id, role); return NULL; } score = crm_element_value(rule_xml, XML_RULE_ATTR_SCORE); if(score != NULL) { score_f = char2score(score); } else { score = crm_element_value( rule_xml, XML_RULE_ATTR_SCORE_ATTRIBUTE); if(score == NULL) { score = crm_element_value( rule_xml, XML_RULE_ATTR_SCORE_MANGLED); } if(score != NULL) { raw_score = FALSE; } } if(safe_str_eq(boolean, "or")) { do_and = FALSE; } location_rule = rsc2node_new(rule_id, rsc, 0, NULL, data_set); if(location_rule == NULL) { return NULL; } if(role != NULL) { crm_debug_2("Setting role filter: %s", role); location_rule->role_filter = text2role(role); } if(do_and) { match_L = node_list_dup(data_set->nodes, TRUE, FALSE); slist_iter( node, node_t, match_L, lpc, node->weight = get_node_score(rule_id, score, raw_score, node); ); } xml_child_iter( rule_xml, expr, enum expression_type type = find_expression_type(expr); crm_debug_2("Processing expression: %s", ID(expr)); if(type == not_expr) { pe_err("Expression <%s id=%s...> is not valid", crm_element_name(expr), crm_str(ID(expr))); continue; } slist_iter( node, node_t, data_set->nodes, lpc, if(type == nested_rule) { accept = test_rule( expr, node->details->attrs, RSC_ROLE_UNKNOWN, data_set->now); } else { accept = test_expression( expr, node->details->attrs, RSC_ROLE_UNKNOWN, data_set->now); } score_f = get_node_score(rule_id, score, raw_score, node); /* if(accept && score_f == -INFINITY) { */ /* accept = FALSE; */ /* } */ if(accept) { node_t *local = pe_find_node_id( match_L, node->details->id); if(local == NULL && do_and) { continue; } else if(local == NULL) { local = node_copy(node); match_L = g_list_append(match_L, local); } if(do_and == FALSE) { local->weight = merge_weights( local->weight, score_f); } crm_debug_2("node %s now has weight %d", node->details->uname, local->weight); } else if(do_and && !accept) { /* remove it */ node_t *delete = pe_find_node_id( match_L, node->details->id); if(delete != NULL) { match_L = g_list_remove(match_L,delete); crm_debug_5("node %s did not match", node->details->uname); } crm_free(delete); } ); ); location_rule->node_list_rh = match_L; if(location_rule->node_list_rh == NULL) { crm_debug_2("No matching nodes for rule %s", rule_id); return NULL; } crm_debug_3("%s: %d nodes matched", rule_id, g_list_length(location_rule->node_list_rh)); return location_rule; } gboolean rsc_colocation_new(const char *id, const char *node_attr, int score, resource_t *rsc_lh, resource_t *rsc_rh, const char *state_lh, const char *state_rh) { rsc_colocation_t *new_con = NULL; if(rsc_lh == NULL){ crm_config_err("No resource found for LHS %s", id); return FALSE; } else if(rsc_rh == NULL){ crm_config_err("No resource found for RHS of %s", id); return FALSE; } crm_malloc0(new_con, sizeof(rsc_colocation_t)); if(new_con == NULL) { return FALSE; } if(state_lh == NULL || safe_str_eq(state_lh, RSC_ROLE_STARTED_S)) { state_lh = RSC_ROLE_UNKNOWN_S; } if(state_rh == NULL || safe_str_eq(state_rh, RSC_ROLE_STARTED_S)) { state_rh = RSC_ROLE_UNKNOWN_S; } new_con->id = id; new_con->rsc_lh = rsc_lh; new_con->rsc_rh = rsc_rh; new_con->score = score; new_con->role_lh = text2role(state_lh); new_con->role_rh = text2role(state_rh); new_con->node_attribute = node_attr; crm_debug_4("Adding constraint %s (%p) to %s", new_con->id, new_con, rsc_lh->id); rsc_lh->rsc_cons = g_list_insert_sorted( rsc_lh->rsc_cons, new_con, sort_cons_strength); return TRUE; } /* LHS before RHS */ gboolean custom_action_order( resource_t *lh_rsc, char *lh_action_task, action_t *lh_action, resource_t *rh_rsc, char *rh_action_task, action_t *rh_action, enum pe_ordering type, pe_working_set_t *data_set) { order_constraint_t *order = NULL; if(lh_rsc == NULL && lh_action) { lh_rsc = lh_action->rsc; } if(rh_rsc == NULL && rh_action) { rh_rsc = rh_action->rsc; } if((lh_action == NULL && lh_rsc == NULL) || (rh_action == NULL && rh_rsc == NULL)){ crm_config_err("Invalid inputs %p.%p %p.%p", lh_rsc, lh_action, rh_rsc, rh_action); crm_free(lh_action_task); crm_free(rh_action_task); return FALSE; } crm_malloc0(order, sizeof(order_constraint_t)); if(order == NULL) { return FALSE; } crm_debug_3("Creating ordering constraint %d", data_set->order_id); order->id = data_set->order_id++; order->type = type; order->lh_rsc = lh_rsc; order->rh_rsc = rh_rsc; order->lh_action = lh_action; order->rh_action = rh_action; order->lh_action_task = lh_action_task; order->rh_action_task = rh_action_task; data_set->ordering_constraints = g_list_append( data_set->ordering_constraints, order); if(lh_rsc != NULL && rh_rsc != NULL) { crm_debug_4("Created ordering constraint %d (%s):" " %s/%s before %s/%s", order->id, ordering_type2text(order->type), lh_rsc->id, lh_action_task, rh_rsc->id, rh_action_task); } else if(lh_rsc != NULL) { crm_debug_4("Created ordering constraint %d (%s):" " %s/%s before action %d (%s)", order->id, ordering_type2text(order->type), lh_rsc->id, lh_action_task, rh_action->id, rh_action_task); } else if(rh_rsc != NULL) { crm_debug_4("Created ordering constraint %d (%s):" " action %d (%s) before %s/%s", order->id, ordering_type2text(order->type), lh_action->id, lh_action_task, rh_rsc->id, rh_action_task); } else { crm_debug_4("Created ordering constraint %d (%s):" " action %d (%s) before action %d (%s)", order->id, ordering_type2text(order->type), lh_action->id, lh_action_task, rh_action->id, rh_action_task); } return TRUE; } gboolean unpack_rsc_colocation(crm_data_t * xml_obj, pe_working_set_t *data_set) { int score_i = 0; const char *id = crm_element_value(xml_obj, XML_ATTR_ID); const char *id_rh = crm_element_value(xml_obj, XML_CONS_ATTR_TO); const char *id_lh = crm_element_value(xml_obj, XML_CONS_ATTR_FROM); const char *score = crm_element_value(xml_obj, XML_RULE_ATTR_SCORE); const char *state_lh = crm_element_value(xml_obj, XML_RULE_ATTR_FROMSTATE); const char *state_rh = crm_element_value(xml_obj, XML_RULE_ATTR_TOSTATE); const char *attr = crm_element_value(xml_obj, "node_attribute"); resource_t *rsc_lh = pe_find_resource(data_set->resources, id_lh); resource_t *rsc_rh = pe_find_resource(data_set->resources, id_rh); if(rsc_lh == NULL) { crm_config_err("No resource (con=%s, rsc=%s)", id, id_lh); return FALSE; } else if(rsc_rh == NULL) { crm_config_err("No resource (con=%s, rsc=%s)", id, id_rh); return FALSE; } if(score) { score_i = char2score(score); } return rsc_colocation_new( id, attr, score_i, rsc_lh, rsc_rh, state_lh, state_rh); } gboolean is_active(rsc_to_node_t *cons) { return TRUE; } diff --git a/crm/tengine/actions.c b/crm/tengine/actions.c index 2a9b53cfcf..3122a328f7 100644 --- a/crm/tengine/actions.c +++ b/crm/tengine/actions.c @@ -1,495 +1,495 @@ /* $Id: actions.c,v 1.37 2006/08/14 09:14:45 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include char *te_uuid = NULL; IPC_Channel *crm_ch = NULL; void send_rsc_command(crm_action_t *action); extern crm_action_timer_t *transition_timer; static void te_start_action_timer(crm_action_t *action) { crm_malloc0(action->timer, sizeof(crm_action_timer_t)); action->timer->timeout = action->timeout; action->timer->reason = timeout_action_warn; action->timer->action = action; action->timer->source_id = Gmain_timeout_add( action->timer->timeout, action_timer_callback, (void*)action->timer); CRM_ASSERT(action->timer->source_id != 0); } static gboolean te_pseudo_action(crm_graph_t *graph, crm_action_t *pseudo) { crm_info("Pseudo action %d fired and confirmed", pseudo->id); pseudo->confirmed = TRUE; update_graph(graph, pseudo); trigger_graph(); return TRUE; } void send_stonith_update(stonith_ops_t * op) { enum cib_errors rc = cib_ok; const char *target = op->node_name; const char *uuid = op->node_uuid; /* zero out the node-status & remove all LRM status info */ crm_data_t *node_state = create_xml_node(NULL, XML_CIB_TAG_STATE); CRM_CHECK(op->node_name != NULL, return); CRM_CHECK(op->node_uuid != NULL, return); crm_xml_add(node_state, XML_ATTR_UUID, uuid); crm_xml_add(node_state, XML_ATTR_UNAME, target); crm_xml_add(node_state, XML_CIB_ATTR_HASTATE, DEADSTATUS); crm_xml_add(node_state, XML_CIB_ATTR_INCCM, XML_BOOLEAN_NO); crm_xml_add(node_state, XML_CIB_ATTR_CRMDSTATE, OFFLINESTATUS); crm_xml_add(node_state, XML_CIB_ATTR_JOINSTATE, CRMD_JOINSTATE_DOWN); crm_xml_add(node_state, XML_CIB_ATTR_EXPSTATE, CRMD_JOINSTATE_DOWN); crm_xml_add(node_state, XML_CIB_ATTR_REPLACE, XML_CIB_TAG_LRM); crm_xml_add(node_state, XML_ATTR_ORIGIN, __FUNCTION__); rc = te_cib_conn->cmds->update( te_cib_conn, XML_CIB_TAG_STATUS, node_state, NULL, cib_quorum_override|cib_scope_local); if(rc < cib_ok) { crm_err("CIB update failed: %s", cib_error2string(rc)); abort_transition( INFINITY, tg_shutdown, "CIB update failed", node_state); } else { /* delay processing the trigger until the update completes */ add_cib_op_callback(rc, FALSE, NULL, cib_fencing_updated); } free_xml(node_state); return; } static gboolean te_fence_node(crm_graph_t *graph, crm_action_t *action) { - char *key = NULL; const char *id = NULL; const char *uuid = NULL; const char *target = NULL; const char *type = NULL; stonith_ops_t * st_op = NULL; id = ID(action->xml); target = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); uuid = crm_element_value(action->xml, XML_LRM_ATTR_TARGET_UUID); type = g_hash_table_lookup(action->params, crm_meta_name("stonith_action")); CRM_CHECK(id != NULL, crm_log_xml_warn(action->xml, "BadAction"); return FALSE); CRM_CHECK(uuid != NULL, crm_log_xml_warn(action->xml, "BadAction"); return FALSE); CRM_CHECK(type != NULL, crm_log_xml_warn(action->xml, "BadAction"); return FALSE); CRM_CHECK(target != NULL, crm_log_xml_warn(action->xml, "BadAction"); return FALSE); te_log_action(LOG_INFO, "Executing %s fencing operation (%s) on %s (timeout=%d)", type, id, target, transition_graph->transition_timeout / 2); crm_malloc0(st_op, sizeof(stonith_ops_t)); if(safe_str_eq(type, "poweroff")) { st_op->optype = POWEROFF; } else { st_op->optype = RESET; } st_op->timeout = transition_graph->transition_timeout / 2; st_op->node_name = crm_strdup(target); st_op->node_uuid = crm_strdup(uuid); - key = generate_transition_key(transition_graph->id, te_uuid); - st_op->private_data = crm_concat(id, key, ';'); - crm_free(key); + st_op->private_data = generate_transition_key( + transition_graph->id, action->id, te_uuid); CRM_ASSERT(stonithd_input_IPC_channel() != NULL); if (ST_OK != stonithd_node_fence( st_op )) { crm_err("Cannot fence %s: stonithd_node_fence() call failed ", target); } return TRUE; } static gboolean te_crm_command(crm_graph_t *graph, crm_action_t *action) { char *value = NULL; char *counter = NULL; HA_Message *cmd = NULL; const char *id = NULL; const char *task = NULL; const char *on_node = NULL; gboolean ret = TRUE; id = ID(action->xml); task = crm_element_value(action->xml, XML_LRM_ATTR_TASK); on_node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); CRM_CHECK(on_node != NULL && strlen(on_node) != 0, te_log_action(LOG_ERR, "Corrupted command (id=%s) %s: no node", crm_str(id), crm_str(task)); return FALSE); te_log_action(LOG_INFO, "Executing crm-event (%s): %s on %s", crm_str(id), crm_str(task), on_node); cmd = create_request(task, NULL, on_node, CRM_SYSTEM_CRMD, CRM_SYSTEM_TENGINE, NULL); - counter = generate_transition_key(transition_graph->id, te_uuid); + counter = generate_transition_key( + transition_graph->id, action->id, te_uuid); crm_xml_add(cmd, XML_ATTR_TRANSITION_KEY, counter); ret = send_ipc_message(crm_ch, cmd); crm_free(counter); crm_msg_del(cmd); value = g_hash_table_lookup(action->params, crm_meta_name(XML_ATTR_TE_NOWAIT)); if(ret == FALSE) { crm_err("Action %d failed: send", action->id); return FALSE; } else if(crm_is_true(value)) { crm_info("Skipping wait for %d", action->id); action->confirmed = TRUE; update_graph(graph, action); trigger_graph(); } else if(ret && action->timeout > 0) { crm_debug("Setting timer for action %d",action->id); action->timer->reason = timeout_action_warn; te_start_action_timer(action); } return TRUE; } static gboolean te_rsc_command(crm_graph_t *graph, crm_action_t *action) { /* never overwrite stop actions in the CIB with * anything other than completed results * * Writing pending stops makes it look like the * resource is running again */ const char *task = NULL; const char *on_node = NULL; action->executed = FALSE; on_node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); CRM_CHECK(on_node != NULL && strlen(on_node) != 0, te_log_action(LOG_ERR, "Corrupted command(id=%s) %s: no node", ID(action->xml), crm_str(task)); return FALSE); send_rsc_command(action); return TRUE; } gboolean cib_action_update(crm_action_t *action, int status) { char *code = NULL; char *digest = NULL; crm_data_t *params = NULL; crm_data_t *state = NULL; crm_data_t *rsc = NULL; crm_data_t *xml_op = NULL; crm_data_t *action_rsc = NULL; char *op_id = NULL; enum cib_errors rc = cib_ok; const char *name = NULL; const char *value = NULL; const char *rsc_id = NULL; const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK); const char *target = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); const char *task_uuid = crm_element_value( action->xml, XML_LRM_ATTR_TASK_KEY); const char *target_uuid = crm_element_value( action->xml, XML_LRM_ATTR_TARGET_UUID); int call_options = cib_quorum_override|cib_scope_local; crm_warn("%s %d: %s on %s timed out", crm_element_name(action->xml), action->id, task_uuid, target); action_rsc = find_xml_node(action->xml, XML_CIB_TAG_RESOURCE, TRUE); if(action_rsc == NULL) { return FALSE; } rsc_id = ID(action_rsc); CRM_CHECK(rsc_id != NULL, crm_log_xml_err(action->xml, "Bad:action"); return FALSE); code = crm_itoa(status); /* update the CIB */ state = create_xml_node(NULL, XML_CIB_TAG_STATE); crm_xml_add(state, XML_ATTR_UUID, target_uuid); crm_xml_add(state, XML_ATTR_UNAME, target); rsc = create_xml_node(state, XML_CIB_TAG_LRM); crm_xml_add(rsc, XML_ATTR_ID, target_uuid); rsc = create_xml_node(rsc, XML_LRM_TAG_RESOURCES); rsc = create_xml_node(rsc, XML_LRM_TAG_RESOURCE); crm_xml_add(rsc, XML_ATTR_ID, rsc_id); name = XML_ATTR_TYPE; value = crm_element_value(action_rsc, name); crm_xml_add(rsc, name, value); name = XML_AGENT_ATTR_CLASS; value = crm_element_value(action_rsc, name); crm_xml_add(rsc, name, value); name = XML_AGENT_ATTR_PROVIDER; value = crm_element_value(action_rsc, name); crm_xml_add(rsc, name, value); xml_op = create_xml_node(rsc, XML_LRM_TAG_RSC_OP); crm_xml_add(xml_op, XML_ATTR_ID, task); op_id = generate_op_key(rsc_id, task, action->interval); crm_xml_add(xml_op, XML_ATTR_ID, op_id); crm_free(op_id); crm_xml_add(xml_op, XML_LRM_ATTR_TASK, task); crm_xml_add(xml_op, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); crm_xml_add(xml_op, XML_LRM_ATTR_OPSTATUS, code); crm_xml_add(xml_op, XML_LRM_ATTR_CALLID, "-1"); crm_xml_add_int(xml_op, XML_LRM_ATTR_INTERVAL, action->interval); crm_xml_add(xml_op, XML_LRM_ATTR_RC, code); crm_xml_add(xml_op, XML_ATTR_ORIGIN, __FUNCTION__); crm_free(code); - code = generate_transition_key(transition_graph->id, te_uuid); + code = generate_transition_key(transition_graph->id, action->id,te_uuid); crm_xml_add(xml_op, XML_ATTR_TRANSITION_KEY, code); crm_free(code); code = generate_transition_magic( crm_element_value(xml_op, XML_ATTR_TRANSITION_KEY), status, status); crm_xml_add(xml_op, XML_ATTR_TRANSITION_MAGIC, code); crm_free(code); params = find_xml_node(action->xml, "attributes", TRUE); params = copy_xml(params); filter_action_parameters(params, CRM_FEATURE_SET); digest = calculate_xml_digest(params, TRUE); crm_xml_add(xml_op, XML_LRM_ATTR_OP_DIGEST, digest); crm_free(digest); free_xml(params); crm_debug_3("Updating CIB with \"%s\" (%s): %s %s on %s", status<0?"new action":XML_ATTR_TIMEOUT, crm_element_name(action->xml), crm_str(task), rsc_id, target); rc = te_cib_conn->cmds->update( te_cib_conn, XML_CIB_TAG_STATUS, state, NULL, call_options); crm_debug("Updating CIB with %s action %d: %s %s on %s (call_id=%d)", op_status2text(status), action->id, task_uuid, rsc_id, target, rc); add_cib_op_callback(rc, FALSE, NULL, cib_action_updated); free_xml(state); action->sent_update = TRUE; if(rc < cib_ok) { return FALSE; } return TRUE; } void send_rsc_command(crm_action_t *action) { HA_Message *cmd = NULL; crm_data_t *rsc_op = NULL; char *counter = NULL; const char *task = NULL; const char *value = NULL; const char *on_node = NULL; const char *task_uuid = NULL; CRM_ASSERT(action != NULL); CRM_ASSERT(action->xml != NULL); rsc_op = action->xml; task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK); task_uuid = crm_element_value(action->xml, XML_LRM_ATTR_TASK_KEY); on_node = crm_element_value(rsc_op, XML_LRM_ATTR_TARGET); - counter = generate_transition_key(transition_graph->id, te_uuid); + counter = generate_transition_key( + transition_graph->id, action->id, te_uuid); crm_xml_add(rsc_op, XML_ATTR_TRANSITION_KEY, counter); crm_info("Initiating action %d: %s on %s", action->id, task_uuid, on_node); crm_free(counter); if(rsc_op != NULL) { crm_log_xml_debug_2(rsc_op, "Performing"); } cmd = create_request(CRM_OP_INVOKE_LRM, rsc_op, on_node, CRM_SYSTEM_LRMD, CRM_SYSTEM_TENGINE, NULL); #if 1 send_ipc_message(crm_ch, cmd); #else /* test the TE timer/recovery code */ if((action->id % 11) == 0) { crm_err("Faking lost action %d: %s", action->id, task_uuid); } else { send_ipc_message(crm_ch, cmd); } #endif crm_msg_del(cmd); action->executed = TRUE; value = g_hash_table_lookup(action->params, crm_meta_name(XML_ATTR_TE_NOWAIT)); if(crm_is_true(value)) { crm_debug("Skipping wait for %d", action->id); action->confirmed = TRUE; update_graph(transition_graph, action); trigger_graph(); } else if(action->timeout > 0) { int action_timeout = 2 * action->timeout + transition_graph->network_delay; crm_debug_3("Setting timer for action %s", task_uuid); if(transition_graph->transition_timeout < action_timeout) { crm_debug("Action %d:" " Increasing transition %d timeout to %d", action->id, transition_graph->id, transition_graph->transition_timeout); transition_graph->transition_timeout = action_timeout; } te_start_action_timer(action); } } crm_graph_functions_t te_graph_fns = { te_pseudo_action, te_rsc_command, te_crm_command, te_fence_node }; void notify_crmd(crm_graph_t *graph) { HA_Message *cmd = NULL; int log_level = LOG_DEBUG; const char *op = CRM_OP_TEABORT; int pending_callbacks = num_cib_op_callbacks(); stop_te_timer(transition_timer); if(pending_callbacks != 0) { crm_warn("Delaying completion until all CIB updates complete"); return; } CRM_CHECK(graph->complete, graph->complete = TRUE); switch(graph->completion_action) { case tg_stop: op = CRM_OP_TECOMPLETE; log_level = LOG_INFO; break; case tg_abort: case tg_restart: op = CRM_OP_TEABORT; break; case tg_shutdown: crm_info("Exiting after transition"); exit(LSB_EXIT_OK); } te_log_action(log_level, "Transition %d status: %s - %s", graph->id, op, crm_str(graph->abort_reason)); print_graph(LOG_DEBUG_3, graph); cmd = create_request( op, NULL, NULL, CRM_SYSTEM_DC, CRM_SYSTEM_TENGINE, NULL); if(graph->abort_reason != NULL) { ha_msg_add(cmd, "message", graph->abort_reason); } send_ipc_message(crm_ch, cmd); crm_msg_del(cmd); graph->abort_reason = NULL; graph->completion_action = tg_restart; } diff --git a/crm/tengine/callbacks.c b/crm/tengine/callbacks.c index 6b2ce58afa..50fc8e8091 100644 --- a/crm/tengine/callbacks.c +++ b/crm/tengine/callbacks.c @@ -1,584 +1,575 @@ /* $Id: callbacks.c,v 1.89 2006/08/14 09:14:45 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include void te_update_confirm(const char *event, HA_Message *msg); void te_update_diff(const char *event, HA_Message *msg); crm_data_t *need_abort(crm_data_t *update); void cib_fencing_updated(const HA_Message *msg, int call_id, int rc, crm_data_t *output, void *user_data); extern char *te_uuid; gboolean shuttingdown = FALSE; crm_graph_t *transition_graph; GTRIGSource *transition_trigger = NULL; crm_action_timer_t *transition_timer = NULL; static gboolean start_global_timer(crm_action_timer_t *timer, int timeout) { CRM_ASSERT(timer != NULL); CRM_CHECK(timer > 0, return FALSE); CRM_CHECK(timer->source_id == 0, return FALSE); if(timeout <= 0) { crm_err("Tried to start timer with period: %d", timeout); } else if(timer->source_id == 0) { crm_debug_2("Starting abort timer: %dms", timeout); timer->timeout = timeout; timer->source_id = Gmain_timeout_add( timeout, global_timer_callback, (void*)timer); CRM_ASSERT(timer->source_id != 0); return TRUE; } else { crm_err("Timer is already active with period: %d", timer->timeout); } return FALSE; } void te_update_diff(const char *event, HA_Message *msg) { int rc = -1; const char *op = NULL; crm_data_t *diff = NULL; crm_data_t *aborted = NULL; const char *set_name = NULL; 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; if(msg == NULL) { crm_err("NULL update"); return; } ha_msg_value_int(msg, F_CIB_RC, &rc); op = cl_get_string(msg, F_CIB_OPERATION); if(rc < cib_ok) { crm_debug_2("Ignoring failed %s operation: %s", op, cib_error2string(rc)); return; } diff = get_message_xml(msg, F_CIB_UPDATE_RESULT); cib_diff_version_details( diff, &diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates, &diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates); crm_info("Processing diff (%s): %d.%d.%d -> %d.%d.%d", op, diff_del_admin_epoch,diff_del_epoch,diff_del_updates, diff_add_admin_epoch,diff_add_epoch,diff_add_updates); log_cib_diff(LOG_DEBUG_2, diff, op); set_name = "diff-added"; if(diff != NULL) { crm_data_t *section = NULL; crm_data_t *change_set = find_xml_node(diff, set_name, FALSE); change_set = find_xml_node(change_set, XML_TAG_CIB, FALSE); if(change_set != NULL) { crm_debug_2("Checking status changes"); section=get_object_root(XML_CIB_TAG_STATUS,change_set); } if(section != NULL) { extract_event(section); } crm_debug_2("Checking change set: %s", set_name); aborted = need_abort(change_set); } set_name = "diff-removed"; if(diff != NULL && aborted == NULL) { crm_data_t *attrs = NULL; crm_data_t *status = NULL; crm_data_t *change_set = find_xml_node(diff, set_name, FALSE); change_set = find_xml_node(change_set, XML_TAG_CIB, FALSE); crm_debug_2("Checking change set: %s", set_name); aborted = need_abort(change_set); if(aborted == NULL && change_set != NULL) { status = get_object_root(XML_CIB_TAG_STATUS, change_set); xml_child_iter_filter( status, node_state, XML_CIB_TAG_STATE, attrs = find_xml_node( node_state, XML_TAG_TRANSIENT_NODEATTRS, FALSE); if(attrs != NULL) { crm_info("Aborting on "XML_TAG_TRANSIENT_NODEATTRS" deletions"); abort_transition(INFINITY, tg_restart, XML_TAG_TRANSIENT_NODEATTRS, attrs); } ); } } if(aborted != NULL) { abort_transition( INFINITY, tg_restart, "Non-status change", NULL); } free_xml(diff); return; } gboolean process_te_message(HA_Message *msg, crm_data_t *xml_data, IPC_Channel *sender) { crm_data_t *xml_obj = NULL; const char *from = cl_get_string(msg, F_ORIG); const char *sys_to = cl_get_string(msg, F_CRM_SYS_TO); const char *sys_from = cl_get_string(msg, F_CRM_SYS_FROM); const char *ref = cl_get_string(msg, XML_ATTR_REFERENCE); const char *op = cl_get_string(msg, F_CRM_TASK); const char *type = cl_get_string(msg, F_CRM_MSG_TYPE); crm_debug_2("Processing %s (%s) message", op, ref); crm_log_message(LOG_DEBUG_3, msg); if(op == NULL){ /* error */ } else if(strcasecmp(op, CRM_OP_HELLO) == 0) { /* ignore */ } else if(sys_to == NULL || strcasecmp(sys_to, CRM_SYSTEM_TENGINE) != 0) { crm_debug_2("Bad sys-to %s", crm_str(sys_to)); return FALSE; } else if(safe_str_eq(op, CRM_OP_INVOKE_LRM) && safe_str_eq(sys_from, CRM_SYSTEM_LRMD) /* && safe_str_eq(type, XML_ATTR_RESPONSE) */ ){ #if CRM_DEPRECATED_SINCE_2_0_4 if(safe_str_eq(crm_element_name(xml_data), XML_TAG_CIB)) { xml_obj = xml_data; } else { xml_obj = find_xml_node(xml_data, XML_TAG_CIB, TRUE); } #else xml_obj = xml_data; CRM_CHECK(xml_obj != NULL, crm_log_message_adv(LOG_ERR, "Invalid (N)ACK", msg); return FALSE); #endif CRM_CHECK(xml_obj != NULL, crm_log_message_adv(LOG_ERR, "Invalid (N)ACK", msg); return FALSE); xml_obj = get_object_root(XML_CIB_TAG_STATUS, xml_obj); CRM_CHECK(xml_obj != NULL, crm_log_message_adv(LOG_ERR, "Invalid (N)ACK", msg); return FALSE); crm_log_message_adv(LOG_DEBUG_2, "Processing (N)ACK", msg); crm_debug("Processing (N)ACK from %s", from); extract_event(xml_obj); } else if(safe_str_eq(type, XML_ATTR_RESPONSE)) { crm_err("Message was a response not a request. Discarding"); return TRUE; } else if(strcasecmp(op, CRM_OP_TRANSITION) == 0) { const char *graph_file = cl_get_string(msg, F_CRM_TGRAPH); const char *graph_input = cl_get_string(msg, F_CRM_TGRAPH_INPUT); CRM_CHECK(graph_file != NULL || xml_data != NULL, crm_err("No graph provided"); crm_log_message(LOG_WARNING, msg); return TRUE); if(transition_graph->complete == FALSE) { crm_info("Another transition is already active"); abort_transition( INFINITY, tg_restart, "Transition Active", NULL); } else { destroy_graph(transition_graph); crm_debug("Processing graph derived from %s", graph_input); if(graph_file == NULL) { transition_graph = unpack_graph(xml_data); } else { FILE *graph_fd = fopen(graph_file, "r"); crm_data_t *graph_data = file2xml(graph_fd, FALSE); CRM_CHECK(graph_fd != NULL, crm_err("Could not open graph filename: %s", graph_file); return TRUE); transition_graph = unpack_graph(graph_data); fclose(graph_fd); free_xml(graph_data); unlink(graph_file); } start_global_timer(transition_timer, transition_graph->transition_timeout); trigger_graph(); print_graph(LOG_DEBUG_2, transition_graph); } } else if(strcasecmp(op, CRM_OP_TE_HALT) == 0) { abort_transition(INFINITY, tg_stop, "Peer Halt", NULL); } else if(strcasecmp(op, CRM_OP_TEABORT) == 0) { abort_transition(INFINITY, tg_restart, "Peer Cancelled", NULL); } else { crm_err("Unknown command: %s::%s from %s", type, op, sys_from); } crm_debug_3("finished processing message"); return TRUE; } void tengine_stonith_callback(stonith_ops_t * op) { const char *allow_fail = NULL; int stonith_id = -1; + int transition_id = -1; + char *uuid = NULL; crm_action_t *stonith_action = NULL; - char *op_key = NULL; - char *call_id = NULL; if(op == NULL) { crm_err("Called with a NULL op!"); return; } crm_info("call=%d, optype=%d, node_name=%s, result=%d, node_list=%s, action=%s", op->call_id, op->optype, op->node_name, op->op_result, (char *)op->node_list, op->private_data); /* this will mark the event complete if a match is found */ CRM_CHECK(op->private_data != NULL, return); /* filter out old STONITH actions */ - decodeNVpair(op->private_data, ';', &call_id, &op_key); - if(op_key != NULL) { - char *key = generate_transition_key( - transition_graph->id, te_uuid); - gboolean key_matched = safe_str_eq(key, op_key); - crm_free(key); - if(key_matched == FALSE) { - crm_info("Ignoring old STONITH op: %s", - op->private_data); - return; - } - } -#if 1 - stonith_id = crm_parse_int(call_id, "-1"); - if(stonith_id < 0) { - crm_err("Stonith action not matched: %s (%s)", - call_id, op->private_data); - return; - } -#endif + CRM_CHECK(decode_transition_key( + op->private_data, &uuid, &transition_id, &stonith_id), + crm_err("Invalid event detected"); + ); - stonith_action = match_down_event( - stonith_id, op->node_uuid, CRM_OP_FENCE); + if(transition_graph->complete + || stonith_id < 0 + || safe_str_neq(uuid, te_uuid) + || transition_graph->id != transition_id) { + crm_info("Ignoring STONITH action initiated outside" + " of the current transition"); + } + + stonith_action = get_action(stonith_id, TRUE); if(stonith_action == NULL) { crm_err("Stonith action not matched"); return; } switch(op->op_result) { case STONITH_SUCCEEDED: send_stonith_update(op); break; case STONITH_CANNOT: case STONITH_TIMEOUT: case STONITH_GENERIC: stonith_action->failed = TRUE; allow_fail = g_hash_table_lookup( stonith_action->params, crm_meta_name(XML_ATTR_TE_ALLOWFAIL)); if(FALSE == crm_is_true(allow_fail)) { crm_err("Stonith of %s failed (%d)..." " aborting transition.", op->node_name, op->op_result); abort_transition(INFINITY, tg_restart, "Stonith failed", NULL); } break; default: crm_err("Unsupported action result: %d", op->op_result); abort_transition(INFINITY, tg_restart, "Unsupport Stonith result", NULL); } update_graph(transition_graph, stonith_action); trigger_graph(); return; } void tengine_stonith_connection_destroy(gpointer user_data) { #if 0 crm_err("Fencing daemon has left us: Shutting down...NOW"); /* shutdown properly later */ CRM_CHECK(FALSE/* fencing daemon died */); #else crm_err("Fencing daemon has left us"); #endif return; } gboolean tengine_stonith_dispatch(IPC_Channel *sender, void *user_data) { int lpc = 0; while(stonithd_op_result_ready()) { if (sender->ch_status == IPC_DISCONNECT) { /* The message which was pending for us is that * the IPC status is now IPC_DISCONNECT */ break; } if(ST_FAIL == stonithd_receive_ops_result(FALSE)) { crm_err("stonithd_receive_ops_result() failed"); } else { lpc++; } } crm_debug_2("Processed %d messages", lpc); if (sender->ch_status == IPC_DISCONNECT) { return FALSE; } return TRUE; } void cib_fencing_updated(const HA_Message *msg, int call_id, int rc, crm_data_t *output, void *user_data) { trigger_graph(); if(rc < cib_ok) { crm_err("CIB update failed: %s", cib_error2string(rc)); crm_log_xml_warn(msg, "[Failed Update]"); } } void cib_action_updated(const HA_Message *msg, int call_id, int rc, crm_data_t *output, void *user_data) { trigger_graph(); if(rc < cib_ok) { crm_err("Update %d FAILED: %s", call_id, cib_error2string(rc)); } } gboolean action_timer_callback(gpointer data) { crm_action_timer_t *timer = NULL; if(data == NULL) { crm_err("Timer popped with no data"); return FALSE; } timer = (crm_action_timer_t*)data; stop_te_timer(timer); crm_warn("Timer popped (abort_level=%d, complete=%s)", transition_graph->abort_priority, transition_graph->complete?"true":"false"); CRM_CHECK(timer->action != NULL, return FALSE); if(transition_graph->complete) { crm_err("Ignoring timeout while not in transition"); } else if(timer->reason == timeout_action_warn) { print_action( LOG_WARNING,"Action missed its timeout", timer->action); } else { /* fail the action */ cib_action_update(timer->action, LRM_OP_TIMEOUT); } return FALSE; } static int unconfirmed_actions(gboolean send_updates) { int unconfirmed = 0; const char *task = NULL; crm_debug_2("Unconfirmed actions..."); slist_iter( synapse, synapse_t, transition_graph->synapses, lpc, /* lookup event */ slist_iter( action, crm_action_t, synapse->actions, lpc2, if(action->executed == FALSE) { continue; } else if(action->confirmed) { continue; } unconfirmed++; task = crm_element_value(action->xml,XML_LRM_ATTR_TASK); if(action->type != action_type_rsc) { continue; } else if(send_updates == FALSE) { continue; } else if(safe_str_eq(task, "cancel")) { /* we dont need to update the CIB with these */ continue; } else if(safe_str_eq(task, "stop")) { /* *never* update the CIB with these */ continue; } crm_err("Action %d unconfirmed from peer", action->id); cib_action_update(action, LRM_OP_PENDING); ); ); if(unconfirmed > 0) { crm_info("Waiting on %d unconfirmed actions", unconfirmed); } return unconfirmed; } gboolean global_timer_callback(gpointer data) { crm_action_timer_t *timer = NULL; if(data == NULL) { crm_err("Timer popped with no data"); return FALSE; } timer = (crm_action_timer_t*)data; stop_te_timer(timer); crm_warn("Timer popped (abort_level=%d, complete=%s)", transition_graph->abort_priority, transition_graph->complete?"true":"false"); CRM_CHECK(timer->action == NULL, return FALSE); if(transition_graph->complete) { crm_err("Ignoring timeout while not in transition"); } else if(timer->reason == timeout_abort) { int unconfirmed = unconfirmed_actions(FALSE); crm_warn("Transition abort timeout reached..." " marking transition complete."); transition_graph->complete = TRUE; abort_transition(INFINITY, tg_restart, "Global Timeout", NULL); if(unconfirmed != 0) { crm_warn("Writing %d unconfirmed actions to the CIB", unconfirmed); unconfirmed_actions(TRUE); } } return FALSE; } gboolean te_graph_trigger(gpointer user_data) { int timeout = 0; enum transition_status graph_rc = -1; if(transition_graph->complete) { notify_crmd(transition_graph); return TRUE; } graph_rc = run_graph(transition_graph); timeout = transition_graph->transition_timeout; print_graph(LOG_DEBUG_3, transition_graph); if(graph_rc == transition_active) { crm_debug_3("Transition not yet complete"); stop_te_timer(transition_timer); start_global_timer(transition_timer, timeout); return TRUE; } else if(graph_rc == transition_pending) { timeout = transition_timer->timeout; crm_debug_3("Transition not yet complete - no actions fired"); return TRUE; } if(graph_rc != transition_complete) { crm_err("Transition failed: %s", transition_status(graph_rc)); print_graph(LOG_WARNING, transition_graph); } transition_graph->complete = TRUE; notify_crmd(transition_graph); return TRUE; } diff --git a/crm/tengine/events.c b/crm/tengine/events.c index 0fd0cc7698..8ccbb0bea6 100644 --- a/crm/tengine/events.c +++ b/crm/tengine/events.c @@ -1,552 +1,511 @@ /* $Id: events.c,v 1.23 2006/08/14 09:14:45 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include crm_data_t *need_abort(crm_data_t *update); void process_graph_event(crm_data_t *event, const char *event_node); -int match_graph_event( - crm_action_t *action, crm_data_t *event, const char *event_node); +int match_graph_event(int action_id, crm_data_t *event, const char *event_node, + int op_status, int op_rc); crm_data_t * need_abort(crm_data_t *update) { crm_data_t *section_xml = NULL; const char *section = NULL; if(update == NULL) { return NULL; } section = XML_CIB_TAG_NODES; section_xml = get_object_root(section, update); xml_child_iter(section_xml, child, return section_xml; ); section = XML_CIB_TAG_RESOURCES; section_xml = get_object_root(section, update); xml_child_iter(section_xml, child, return section_xml; ); section = XML_CIB_TAG_CONSTRAINTS; section_xml = get_object_root(section, update); xml_child_iter(section_xml, child, return section_xml; ); section = XML_CIB_TAG_CRMCONFIG; section_xml = get_object_root(section, update); xml_child_iter(section_xml, child, return section_xml; ); return NULL; } static gboolean fail_incompletable_actions(crm_graph_t *graph, const char *down_node) { const char *target = NULL; crm_data_t *last_action = NULL; slist_iter( synapse, synapse_t, graph->synapses, lpc, if (synapse->confirmed) { continue; } slist_iter( action, crm_action_t, synapse->actions, lpc, if(action->type == action_type_pseudo || action->confirmed) { continue; } target = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); if(safe_str_eq(target, down_node)) { action->failed = TRUE; last_action = action->xml; update_graph(graph, action); crm_notice("Action %d (%s) is scheduled for %s (offline)", action->id, ID(action->xml), down_node); } ); ); if(last_action != NULL) { crm_warn("Node %s shutdown resulted in un-runnable actions", down_node); abort_transition(INFINITY, tg_restart, "Node failure", last_action); return TRUE; } return FALSE; } gboolean extract_event(crm_data_t *msg) { int shutdown = 0; const char *event_node = NULL; /* [cib fragment] ... */ crm_debug_4("Extracting event from %s", crm_element_name(msg)); xml_child_iter_filter( msg, node_state, XML_CIB_TAG_STATE, crm_data_t *attrs = NULL; crm_data_t *resources = NULL; const char *ccm_state = crm_element_value( node_state, XML_CIB_ATTR_INCCM); const char *crmd_state = crm_element_value( node_state, XML_CIB_ATTR_CRMDSTATE); /* Transient node attribute changes... */ event_node = crm_element_value(node_state, XML_ATTR_ID); crm_debug_2("Processing state update from %s", event_node); crm_log_xml_debug_3(node_state, "Processing"); attrs = find_xml_node( node_state, XML_TAG_TRANSIENT_NODEATTRS, FALSE); if(attrs != NULL) { crm_info("Aborting on "XML_TAG_TRANSIENT_NODEATTRS" changes for %s", event_node); abort_transition(INFINITY, tg_restart, XML_TAG_TRANSIENT_NODEATTRS, attrs); } resources = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE); resources = find_xml_node( resources, XML_LRM_TAG_RESOURCES, FALSE); /* LRM resource update... */ xml_child_iter( resources, rsc, xml_child_iter( rsc, rsc_op, crm_log_xml_debug_3(rsc_op, "Processing resource update"); process_graph_event(rsc_op, event_node); ); ); /* * node state update... possibly from a shutdown we requested */ if(safe_str_eq(ccm_state, XML_BOOLEAN_FALSE) || safe_str_eq(crmd_state, CRMD_JOINSTATE_DOWN)) { crm_action_t *shutdown = NULL; shutdown = match_down_event(0, event_node, NULL); if(shutdown != NULL) { update_graph(transition_graph, shutdown); trigger_graph(); } else { crm_info("Stonith/shutdown of %s not matched", event_node); abort_transition(INFINITY, tg_restart, "Node failure", node_state); } fail_incompletable_actions(transition_graph, event_node); } shutdown = 0; ha_msg_value_int(node_state, XML_CIB_ATTR_SHUTDOWN, &shutdown); if(shutdown != 0) { crm_info("Aborting on "XML_CIB_ATTR_SHUTDOWN" attribute for %s", event_node); abort_transition(INFINITY, tg_restart, "Shutdown request", node_state); } ); return TRUE; } static void update_failcount(crm_data_t *event, const char *event_node, int rc) { char *attr_name = NULL; char *task = NULL; char *rsc_id = NULL; const char *on_node = event_node; const char *on_uuid = event_node; int interval = 0; if(rc == 99) { /* this is an internal code for "we're busy, try again" */ return; } CRM_CHECK(on_uuid != NULL, return); CRM_CHECK(parse_op_key(ID(event), &rsc_id, &task, &interval), crm_err("Couldn't parse: %s", ID(event)); return); CRM_CHECK(task != NULL, crm_free(rsc_id); return); CRM_CHECK(rsc_id != NULL, crm_free(task); return); /* CRM_CHECK(on_node != NULL, return); */ if(interval > 0) { attr_name = crm_concat("fail-count", rsc_id, '-'); crm_warn("Updating failcount for %s on %s after failed %s: rc=%d", rsc_id, on_node, task, rc); update_attr(te_cib_conn, cib_none, XML_CIB_TAG_STATUS, on_uuid, NULL,NULL, attr_name, XML_NVPAIR_ATTR_VALUE"++"); crm_free(attr_name); } crm_free(rsc_id); crm_free(task); } static int status_from_rc(crm_action_t *action, int orig_status, int rc) { int status = orig_status; const char *target_rc_s = g_hash_table_lookup( action->params, crm_meta_name(XML_ATTR_TE_TARGET_RC)); if(target_rc_s != NULL) { int target_rc = 0; crm_debug_2("Target rc: %s vs. %d", target_rc_s, rc); target_rc = crm_parse_int(target_rc_s, NULL); if(target_rc == rc) { crm_debug_2("Target rc: == %d", rc); if(status != LRM_OP_DONE) { crm_debug_2("Re-mapping op status to" " LRM_OP_DONE for rc=%d", rc); status = LRM_OP_DONE; } } else { crm_debug_2("Target rc: != %d", rc); if(status != LRM_OP_ERROR) { crm_info("Re-mapping op status to" " LRM_OP_ERROR for rc=%d", rc); status = LRM_OP_ERROR; } } } /* 99 is the code we use for direct nack's */ if(rc != 99 && status != LRM_OP_DONE) { const char *task, *uname; task = crm_element_value(action->xml, XML_LRM_ATTR_TASK); uname = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); crm_warn("Action %s on %s failed (target: %s vs. rc: %d): %s", task, uname, target_rc_s, rc, op_status2text(status)); } return status; } /* * returns the ID of the action if a match is found * returns -1 if a match was not found * returns -2 if a match was found but the action failed (and was * not allowed to) */ int -match_graph_event( - crm_action_t *action, crm_data_t *event, const char *event_node) +match_graph_event(int action_id, crm_data_t *event, const char *event_node, + int op_status, int op_rc) { - const char *allow_fail = NULL; - const char *this_action = NULL; - const char *this_node = NULL; - const char *this_uname = NULL; - const char *magic = NULL; - - const char *this_event; - char *update_te_uuid = NULL; - const char *update_event; - - int op_status_i = -3; - int op_rc_i = -3; - int transition_i = -1; + const char *allow_fail = NULL; + const char *this_event = ID(event); + crm_action_t *action = NULL; - CRM_CHECK(event != NULL, return -1); - - crm_debug_3("Processing \"%s\" change", crm_element_name(event)); - update_event = crm_element_value(event, XML_ATTR_ID); - magic = crm_element_value(event, XML_ATTR_TRANSITION_MAGIC); - - CRM_CHECK(magic != NULL, return -2); - - this_action = crm_element_value(action->xml, XML_LRM_ATTR_TASK); - this_uname = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); - this_event = crm_element_value(action->xml, XML_LRM_ATTR_TASK_KEY); - this_node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET_UUID); - - CRM_CHECK(this_event != NULL, return -2); - - if(safe_str_neq(this_event, update_event)) { - crm_debug_2("Action %d : Event mismatch %s vs. %s", - action->id, this_event, update_event); - return -1; - - } else if(safe_str_neq(this_node, event_node)) { - crm_debug_2("Action %d : Node mismatch %s (%s) vs. %s", - action->id, this_node, this_uname, event_node); + action = get_action(action_id, FALSE); + if(action == NULL) { return -1; } - crm_debug_2("Matched action (%d) %s", action->id, this_event); - - CRM_CHECK(decode_transition_magic( - magic, &update_te_uuid, - &transition_i, &op_status_i, &op_rc_i), return -2); - - op_status_i = status_from_rc(action, op_status_i, op_rc_i); - if(op_status_i != LRM_OP_DONE) { - update_failcount(event, event_node, op_rc_i); - } - - if(transition_i == -1) { - /* we never expect these - recompute */ - crm_err("Detected action %s initiated outside of a transition", - this_event); - crm_log_message(LOG_ERR, event); - crm_free(update_te_uuid); - return -2; - - } else if(safe_str_neq(update_te_uuid, te_uuid)) { - crm_info("Detected action %s from a different transitioner:" - " %s vs. %s", this_event, update_te_uuid, te_uuid); - crm_log_message(LOG_INFO, event); - crm_free(update_te_uuid); - return -3; - - } else if(transition_graph->id != transition_i) { - crm_warn("Detected an action %s from a different transition:" - " %d vs. %d", this_event, transition_i, - transition_graph->id); - crm_log_message(LOG_INFO, event); - crm_free(update_te_uuid); - return -4; + op_status = status_from_rc(action, op_status, op_rc); + if(op_status != LRM_OP_DONE) { + update_failcount(event, event_node, op_rc); } - - crm_free(update_te_uuid); - - /* stop this event's timer if it had one */ - stop_te_timer(action->timer); - action->confirmed = TRUE; /* Process OP status */ - switch(op_status_i) { - case -3: - crm_err("Action returned the same as last time..." - " whatever that was!"); - crm_log_message(LOG_ERR, event); - break; + switch(op_status) { case LRM_OP_PENDING: crm_debug("Ignoring pending operation"); - return -5; + return action->id; break; case LRM_OP_DONE: break; case LRM_OP_ERROR: case LRM_OP_TIMEOUT: case LRM_OP_NOTSUPPORTED: action->failed = TRUE; break; case LRM_OP_CANCELLED: /* do nothing?? */ crm_err("Dont know what to do for cancelled ops yet"); break; default: action->failed = TRUE; - crm_err("Unsupported action result: %d", op_status_i); + crm_err("Unsupported action result: %d", op_status); } + /* stop this event's timer if it had one */ + stop_te_timer(action->timer); + action->confirmed = TRUE; + update_graph(transition_graph, action); trigger_graph(); if(action->failed) { allow_fail = g_hash_table_lookup( action->params, crm_meta_name(XML_ATTR_TE_ALLOWFAIL)); if(crm_is_true(allow_fail)) { action->failed = FALSE; } } if(action->failed) { abort_transition(action->synapse->priority+1, tg_restart, "Event failed", event); - - } else if(transition_graph->complete) { - abort_transition(INFINITY, tg_restart,"No active graph", event); } - te_log_action(LOG_INFO, "Action %s (%d) confirmed", - this_event, action->id); + te_log_action(LOG_INFO, "Action %s (%d) confirmed on %s", + this_event, action->id, event_node); return action->id; } +crm_action_t * +get_action(int id, gboolean confirmed) +{ + slist_iter( + synapse, synapse_t, transition_graph->synapses, lpc, + + slist_iter( + action, crm_action_t, synapse->actions, lpc2, + + if(action->id == id) { + if(confirmed) { + stop_te_timer(action->timer); + action->confirmed = TRUE; + } + return action; + } + ) + ); + return NULL; +} + + crm_action_t * match_down_event(int id, const char *target, const char *filter) { const char *this_action = NULL; const char *this_node = NULL; crm_action_t *match = NULL; - + slist_iter( synapse, synapse_t, transition_graph->synapses, lpc, /* lookup event */ slist_iter( action, crm_action_t, synapse->actions, lpc2, if(id > 0 && action->id == id) { match = action; break; } this_action = crm_element_value( action->xml, XML_LRM_ATTR_TASK); if(action->type != action_type_crm) { continue; } else if(safe_str_eq(this_action, CRM_OP_LRM_REFRESH)){ continue; } else if(filter != NULL && safe_str_neq(this_action, filter)) { continue; } this_node = crm_element_value( action->xml, XML_LRM_ATTR_TARGET_UUID); if(this_node == NULL) { crm_log_xml_err(action->xml, "No node uuid"); } if(safe_str_neq(this_node, target)) { crm_debug("Action %d : Node mismatch: %s", action->id, this_node); continue; } match = action; break; ); if(match != NULL) { /* stop this event's timer if it had one */ break; } ); if(match != NULL) { /* stop this event's timer if it had one */ crm_debug("Match found for action %d: %s on %s", id, crm_element_value(match->xml, XML_LRM_ATTR_TASK_KEY), target); stop_te_timer(match->timer); match->confirmed = TRUE; } else if(id > 0) { crm_err("No match for action %d", id); } else { crm_warn("No match for shutdown action on %s", target); } return match; } void process_graph_event(crm_data_t *event, const char *event_node) { - int rc = -1; - const char *magic = NULL; - const char *rsc_id = NULL; + int rc = -1; + int status = -1; + int action = -1; + int transition_num = -1; + char *update_te_uuid = NULL; + + gboolean passed = FALSE; + const char *id = NULL; + const char *magic = NULL; + CRM_ASSERT(event != NULL); - rsc_id = crm_element_value(event, XML_ATTR_ID); - magic = crm_element_value(event, XML_ATTR_TRANSITION_MAGIC); + + id = ID(event); + magic = crm_element_value(event, XML_ATTR_TRANSITION_MAGIC); if(magic == NULL) { - crm_log_xml_debug_2(event, "Skipping \"non-change\""); + /* non-change */ return; - - } else { - crm_debug_2("Processing CIB update: %s on %s: %s", - rsc_id, event_node, magic); } - slist_iter( - synapse, synapse_t, transition_graph->synapses, lpc, - - /* lookup event */ - slist_iter( - action, crm_action_t, synapse->actions, lpc2, - - rc = match_graph_event(action, event, event_node); - if(rc >= 0) { - crm_log_xml_debug_2(event, "match:found"); + CRM_CHECK(decode_transition_magic( + magic, &update_te_uuid, &transition_num, &action, + &status, &rc), + crm_err("Invalid event %s detected", id); + abort_transition(INFINITY, tg_restart,"Bad event", event); + ); + + if(transition_num == -1) { + crm_err("Action %s initiated outside of a transition", id); + abort_transition(INFINITY, tg_restart,"Unexpected event",event); - } else if(rc == -5) { - crm_log_xml_debug_2(event, "match:pending"); + } else if(action < 0 || safe_str_neq(update_te_uuid, te_uuid)) { + crm_info("Action %s initiated by a different transitioner", id); + abort_transition(INFINITY, tg_restart,"Foreign event", event); + + } else if(transition_graph->id != transition_num) { + crm_info("Detected action %s from a different transition:" + " %d vs. %d", id, transition_num, transition_graph->id); + abort_transition(INFINITY, tg_restart,"Old event", event); + + } else if(transition_graph->complete) { + crm_info("Action %s arrived after a completed transition", id); + abort_transition(INFINITY, tg_restart, "Inactive graph", event); - } else if(rc != -1) { - crm_warn("Search for %s terminated: %d", - ID(event), rc); - abort_transition(INFINITY, tg_restart, - "Unexpected event", event); - } + } else if(match_graph_event( + action, event, event_node, status, rc) < 0) { + crm_err("Unknown graph action %s", id); + abort_transition(INFINITY, tg_restart, "Unknown event", event); - if(rc != -1) { - return; - } - ); - ); + } else { + passed = TRUE; + crm_debug("Processed update to %s: %s", id, magic); + } - /* unexpected event, trigger a pe-recompute */ - /* possibly do this only for certain types of actions */ - crm_warn("Event not found."); - if(rc != EXECRA_OK) { + if(passed == FALSE && rc != EXECRA_OK) { update_failcount(event, event_node, rc); } - crm_log_xml_info(event, "match:not-found"); - abort_transition(INFINITY, tg_restart, "Unexpected event", event); + + crm_free(update_te_uuid); return; } diff --git a/crm/tengine/main.c b/crm/tengine/main.c index 68cf69a207..d2eac6cffe 100644 --- a/crm/tengine/main.c +++ b/crm/tengine/main.c @@ -1,250 +1,251 @@ /* $Id: main.c,v 1.32 2006/02/27 09:55:57 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SYS_NAME CRM_SYSTEM_TENGINE #define OPTARGS "hVc" GMainLoop* mainloop = NULL; const char* crm_system_name = SYS_NAME; cib_t *te_cib_conn = NULL; extern GTRIGSource *transition_trigger; extern crm_action_timer_t *transition_timer; void usage(const char* cmd, int exit_status); int init_start(void); gboolean tengine_shutdown(int nsig, gpointer unused); extern void te_update_confirm(const char *event, HA_Message *msg); extern void te_update_diff(const char *event, HA_Message *msg); extern crm_graph_functions_t te_graph_fns; int main(int argc, char ** argv) { gboolean allow_cores = TRUE; int argerr = 0; int flag; crm_log_init(crm_system_name); G_main_add_SignalHandler( G_PRIORITY_HIGH, SIGTERM, tengine_shutdown, NULL, NULL); transition_trigger = G_main_add_TriggerHandler( G_PRIORITY_LOW, te_graph_trigger, NULL, NULL); crm_debug_3("Begining option processing"); while ((flag = getopt(argc, argv, OPTARGS)) != EOF) { switch(flag) { case 'V': alter_debug(DEBUG_INC); break; case 'h': /* Help message */ usage(crm_system_name, LSB_EXIT_OK); break; case 'c': allow_cores = TRUE; break; default: ++argerr; break; } } crm_debug_3("Option processing complete"); if (optind > argc) { ++argerr; } if (argerr) { usage(crm_system_name,LSB_EXIT_GENERIC); } /* read local config file */ crm_debug_3("Starting..."); return init_start(); } int init_start(void) { int init_ok = TRUE; init_client_ipc_comms( CRM_SYSTEM_CRMD, subsystem_msg_dispatch, (void*)process_te_message, &crm_ch); if(crm_ch != NULL) { send_hello_message(crm_ch, "1234", CRM_SYSTEM_TENGINE, "0", "1"); } else { init_ok = FALSE; crm_err("Could not connect to the CRMd"); } if(init_ok) { crm_debug_4("Creating CIB connection"); te_cib_conn = cib_new(); if(te_cib_conn == NULL) { init_ok = FALSE; } } if(init_ok) { crm_debug_4("Connecting to the CIB"); if(cib_ok != te_cib_conn->cmds->signon( te_cib_conn, crm_system_name, cib_command)) { crm_err("Could not connect to the CIB"); init_ok = FALSE; } } if(init_ok) { crm_debug_4("Setting CIB notification callback"); if(cib_ok != te_cib_conn->cmds->add_notify_callback( te_cib_conn, T_CIB_DIFF_NOTIFY, te_update_diff)) { crm_err("Could not set CIB notification callback"); init_ok = FALSE; } } if(init_ok && ST_OK != stonithd_signon(crm_system_name)) { crm_err("Could not sign up to stonithd"); /* init_ok = FALSE; */ } if(init_ok && ST_OK != stonithd_set_stonith_ops_callback( tengine_stonith_callback)) { crm_err("Could not set stonith callback"); stonithd_signoff(); /* init_ok = FALSE; */ } if(init_ok) { IPC_Channel *fence_ch = stonithd_input_IPC_channel(); if(fence_ch == NULL) { } else if(NULL == G_main_add_IPC_Channel( G_PRIORITY_LOW, fence_ch, FALSE, tengine_stonith_dispatch, NULL, tengine_stonith_connection_destroy)) { crm_err("Failed to add Fencing channel to our mainloop"); init_ok = FALSE; } } if(init_ok) { cl_uuid_t new_uuid; char uuid_str[UU_UNPARSE_SIZEOF]; cl_uuid_generate(&new_uuid); cl_uuid_unparse(&new_uuid, uuid_str); te_uuid = crm_strdup(uuid_str); crm_info("Registering TE UUID: %s", te_uuid); set_graph_functions(&te_graph_fns); /* create a blank one */ transition_graph = unpack_graph(NULL); transition_graph->complete = TRUE; + transition_graph->abort_reason = "DC Takeover"; transition_graph->completion_action = tg_restart; crm_malloc0(transition_timer, sizeof(crm_action_timer_t)); transition_timer->source_id = 0; transition_timer->reason = timeout_abort; transition_timer->action = NULL; } if(init_ok) { /* Create the mainloop and run it... */ crm_info("Starting %s", crm_system_name); mainloop = g_main_new(FALSE); g_main_run(mainloop); return_to_orig_privs(); crm_info("Exiting %s", crm_system_name); } else { crm_warn("Initialization errors, %s not starting.", crm_system_name); } if(init_ok) { return 0; } return 1; } void usage(const char* cmd, int exit_status) { FILE* stream; stream = exit_status ? stderr : stdout; fprintf(stream, "usage: %s [-srkh]" "[-c configure file]\n", cmd); /* fprintf(stream, "\t-d\tsets debug level\n"); */ /* fprintf(stream, "\t-s\tgets daemon status\n"); */ /* fprintf(stream, "\t-r\trestarts daemon\n"); */ /* fprintf(stream, "\t-k\tstops daemon\n"); */ /* fprintf(stream, "\t-h\thelp message\n"); */ fflush(stream); exit(exit_status); } gboolean shuttingdown; gboolean tengine_shutdown(int nsig, gpointer unused) { shuttingdown = TRUE; abort_transition(INFINITY, tg_shutdown, "Shutdown", NULL); return TRUE; } diff --git a/crm/tengine/tengine.h b/crm/tengine/tengine.h index 43c7003966..4df15c56b0 100644 --- a/crm/tengine/tengine.h +++ b/crm/tengine/tengine.h @@ -1,65 +1,66 @@ /* $Id: tengine.h,v 1.36 2006/02/27 09:55:57 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef TENGINE__H #define TENGINE__H #include #include #include extern IPC_Channel *crm_ch; extern GMainLoop* mainloop; /* tengine */ extern crm_action_t *match_down_event( int rc, const char *target, const char *filter); extern void send_stonith_update(stonith_ops_t * op); extern gboolean cib_action_update(crm_action_t *action, int status); /* utils */ +extern crm_action_t *get_action(int id, gboolean confirmed); extern gboolean stop_te_timer(crm_action_timer_t *timer); extern const char *get_rsc_state(const char *task, op_status_t status); /* unpack */ extern gboolean extract_event(crm_data_t *msg); extern gboolean process_te_message( HA_Message * msg, crm_data_t *xml_data, IPC_Channel *sender); extern crm_graph_t *transition_graph; extern GTRIGSource *transition_trigger; extern char *te_uuid; extern cib_t *te_cib_conn; extern void notify_crmd(crm_graph_t *graph); #include extern void trigger_graph_processing(const char *fn, int line); extern void abort_transition_graph( int abort_priority, enum transition_action abort_action, const char *abort_text, crm_data_t *reason, const char *fn, int line); #define trigger_graph() trigger_graph_processing(__FUNCTION__, __LINE__) #define abort_transition(pri, action, text, reason) \ abort_transition_graph(pri, action, text, reason,__FUNCTION__,__LINE__); #endif diff --git a/crm/tengine/utils.c b/crm/tengine/utils.c index f875c3b8e1..e87e980692 100644 --- a/crm/tengine/utils.c +++ b/crm/tengine/utils.c @@ -1,94 +1,101 @@ /* $Id: utils.c,v 1.60 2006/08/14 08:52:30 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include extern cib_t *te_cib_conn; gboolean stop_te_timer(crm_action_timer_t *timer) { const char *timer_desc = "action timer"; if(timer == NULL) { return FALSE; } if(timer->reason == timeout_abort) { timer_desc = "global timer"; } if(timer->source_id != 0) { crm_debug_2("Stopping %s", timer_desc); Gmain_timeout_remove(timer->source_id); timer->source_id = 0; } else { return FALSE; } return TRUE; } void trigger_graph_processing(const char *fn, int line) { G_main_set_trigger(transition_trigger); crm_debug_2("%s:%d - Triggered graph processing", fn, line); } void abort_transition_graph( int abort_priority, enum transition_action abort_action, const char *abort_text, crm_data_t *reason, const char *fn, int line) { int log_level = LOG_DEBUG; /* if(abort_priority >= INFINITY) { log_level = LOG_INFO; } */ update_abort_priority( transition_graph, abort_priority, abort_action, abort_text); do_crm_log(log_level, "%s:%d - Triggered graph processing : %s", fn, line, abort_text); if(reason != NULL) { - crm_log_xml(log_level, "Cause", reason); + const char *magic = crm_element_value( + reason, XML_ATTR_TRANSITION_MAGIC); + if(magic) { + do_crm_log(log_level, "Caused by update to %s: %s", + ID(reason), magic); + } else { + crm_log_xml(log_level, "Cause", reason); + } } if(transition_graph->complete) { notify_crmd(transition_graph); } else { G_main_set_trigger(transition_trigger); } } diff --git a/cts/CM_LinuxHAv2.py.in b/cts/CM_LinuxHAv2.py.in index e70bf43ba8..929c68703e 100755 --- a/cts/CM_LinuxHAv2.py.in +++ b/cts/CM_LinuxHAv2.py.in @@ -1,624 +1,577 @@ #!@PYTHON@ '''CTS: Cluster Testing System: LinuxHA v2 dependent modules... ''' __copyright__=''' Author: Huang Zhen Copyright (C) 2004 International Business Machines Additional Audits, Revised Start action, Default Configuration: 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 program 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 program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import os,sys,CTS,CTSaudits,CTStests, warnings from CTS import * from CM_hb import HeartbeatCM from xml.dom.minidom import * from CTSaudits import ClusterAudit from CTStests import * from CIB import * ####################################################################### # # LinuxHA v2 dependent modules # ####################################################################### class LinuxHAv2(HeartbeatCM): ''' The linux-ha version 2 cluster manager class. It implements the things we need to talk to and manipulate linux-ha version 2 clusters ''' def __init__(self, Environment, randseed=None): HeartbeatCM.__init__(self, Environment, randseed=randseed) self.clear_cache = 0 self.cib_installed = 0 self.config = None self.cluster_monitor = 0 self.use_short_names = 1 self.update({ "Name" : "linux-ha-v2", "DeadTime" : 300, "StartTime" : 300, # Max time to start up "StableTime" : 30, "StartCmd" : "@INITDIR@/heartbeat@INIT_EXT@ start > /dev/null 2>&1", "StopCmd" : "@INITDIR@/heartbeat@INIT_EXT@ stop > /dev/null 2>&1", "ElectionCmd" : "@sbindir@/crmadmin -E %s", "StatusCmd" : "@sbindir@/crmadmin -S %s 2>/dev/null", "EpocheCmd" : "@sbindir@/ccm_tool -e", "QuorumCmd" : "@sbindir@/ccm_tool -q", "CibQuery" : "@sbindir@/cibadmin -Ql", "ParitionCmd" : "@sbindir@/ccm_tool -p", "IsRscRunning" : "@libdir@/heartbeat/lrmadmin -E %s monitor 0 0 EVERYTIME 2>/dev/null|grep return", "ExecuteRscOp" : "@libdir@/heartbeat/lrmadmin -E %s %s 0 0 EVERYTIME 2>/dev/null", "CIBfile" : "%s:@HA_VARLIBDIR@/heartbeat/crm/cib.xml", "TmpDir" : "/tmp", "BreakCommCmd2" : "@libdir@/heartbeat/TestHeartbeatComm break-communication %s>/dev/null 2>&1", "IsIPAddrRscRunning" : "", "StandbyCmd" : "@sbindir@/crm_standby -U %s -v %s 2>/dev/null", "UUIDQueryCmd" : "@sbindir@/crmadmin -N", "StandbyQueryCmd" : "@sbindir@/crm_standby -GQ -U %s 2>/dev/null", # Patterns to look for in the log files for various occasions... "Pat:DC_IDLE" : "crmd.*State transition.*-> S_IDLE", # This wont work if we have multiple partitions # Use: "Pat:They_started" : "%s crmd:.*State transition.*-> S_NOT_DC", "Pat:They_started" : "Updating node state to member for %s", "Pat:We_started" : "crmd.*%s: State transition.*-> S_IDLE", "Pat:We_stopped" : "heartbeat.*%s.*Heartbeat shutdown complete", "Pat:Logd_stopped" : "%s logd:.*Exiting write process", "Pat:They_stopped" : "%s crmd:.*LOST:.* %s ", "Pat:All_stopped" : "heartbeat.*%s.*Heartbeat shutdown complete", "Pat:They_dead" : "node %s.*: is dead", "Pat:TransitionComplete" : "Transition status: Complete: complete", # Bad news Regexes. Should never occur. "BadRegexes" : ( r"ERROR:", r"CRIT:", r"Shutting down\.", r"Forcing shutdown\.", r"Timer I_TERMINATE just popped", r"input=I_ERROR", r"input=I_FAIL", r"input=I_INTEGRATED cause=C_TIMER_POPPED", r"input=I_FINALIZED cause=C_TIMER_POPPED", r"input=I_ERROR", r", exiting\.", r"WARN.*Ignoring HA message.*vote.*not in our membership list", r"pengine.*Attempting recovery of resource", r"pengine.*Handling failed ", r"tengine.*is taking more than 2x its timeout", r"Confirm not received from", r"Welcome reply not received from", r"Attempting to schedule .* after a stop", r"Resource .* was active at shutdown", r"duplicate entries for call_id", r"Search terminated:", r"No need to invoke the TE", r":global_timer_callback", r"Updating failcount for ", r"Faking parameter digest creation", r"Parameters to .* action changed:", ), }) del self["Standby"] if self.Env["DoBSC"]: del self["Pat:They_stopped"] del self["Pat:Logd_stopped"] self.check_transitions = 0 self.check_elections = 0 self.CIBsync = {} self.default_cts_cib=CIB(self).cib() self.debug(self.default_cts_cib) def errorstoignore(self): # At some point implement a more elegant solution that # also produces a report at the end '''Return list of errors which are known and very noisey should be ignored''' if 1: return [ "crmadmin:" ] return [] def install_config(self, node): + if not self.ns.WaitForNodeToComeUp(node): + self.log("Node %s is not up." % node) + return None + if not self.CIBsync.has_key(node) and self.Env["ClobberCIB"] == 1: self.CIBsync[node] = 1 self.rsh.remote_py(node, "os", "system", "rm -f @HA_VARLIBDIR@/heartbeat/crm/cib.xml") self.rsh.remote_py(node, "os", "system", "rm -f @HA_VARLIBDIR@/heartbeat/crm/cib.xml.sig") # Only install the CIB on the first node, all the other ones will pick it up from there if self.cib_installed == 1: return None self.cib_installed = 1 if self.Env["CIBfilename"] == None: self.debug("Installing Generated CIB on node %s" %(node)) warnings.filterwarnings("ignore") cib_file=os.tmpnam() warnings.resetwarnings() os.system("rm -f "+cib_file) self.debug("Creating new CIB for " + node + " in: " + cib_file) os.system("echo \'" + self.default_cts_cib + "\' > " + cib_file) if 0!=self.rsh.echo_cp(None, cib_file, node, "@HA_VARLIBDIR@/heartbeat/crm/cib.xml"): raise ValueError("Can not create CIB on %s "%node) os.system("rm -f "+cib_file) else: self.debug("Installing CIB (%s) on node %s" %(self.Env["CIBfilename"], node)) if 0!=self.rsh.cp(self.Env["CIBfilename"], "root@" + (self["CIBfile"]%node)): raise ValueError("Can not scp file to %s "%node) self.rsh.remote_py(node, "os", "system", "chown @HA_CCMUSER@ @HA_VARLIBDIR@/heartbeat/crm/cib.xml") def prepare(self): '''Finish the Initialization process. Prepare to test...''' for node in self.Env["nodes"]: self.ShouldBeStatus[node] = "" self.StataCM(node) def test_node_CM(self, node): '''Report the status of the cluster manager on a given node''' - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp failed, node %s set to down" % node) - self.ShouldBeStatus[node] == self["down"] - return 3 - watchpats = [ ] watchpats.append("Current ping state: (S_IDLE|S_NOT_DC)") watchpats.append(self["Pat:They_started"]%node) idle_watch = CTS.LogWatcher(self["LogFileName"], watchpats) idle_watch.setwatch() out=self.rsh.readaline(node, self["StatusCmd"]%node) ret= (string.find(out, 'ok') != -1) self.debug("Node %s status: %s" %(node, out)) if not ret: if self.ShouldBeStatus[node] == self["up"]: self.log( "Node status for %s is %s but we think it should be %s" %(node, self["down"], self.ShouldBeStatus[node])) self.ShouldBeStatus[node]=self["down"] return 0 if self.ShouldBeStatus[node] == self["down"]: self.log( "Node status for %s is %s but we think it should be %s: %s" %(node, self["up"], self.ShouldBeStatus[node], out)) self.ShouldBeStatus[node]=self["up"] # check the output first - because syslog-ng looses messages if string.find(out, 'S_NOT_DC') != -1: # Up and stable return 2 if string.find(out, 'S_IDLE') != -1: # Up and stable return 2 # fall back to syslog-ng and wait if not idle_watch.look(): # just up self.debug("Warn: Node %s is unstable: %s" %(node, out)) return 1 # Up and stable return 2 # Is the node up or is the node down def StataCM(self, node): '''Report the status of the cluster manager on a given node''' if self.test_node_CM(node) > 0: return 1 return None # Being up and being stable is not the same question... def node_stable(self, node): '''Report the status of the cluster manager on a given node''' if self.test_node_CM(node) == 2: return 1 self.log("Warn: Node %s not stable" %(node)) return None def cluster_stable(self, timeout=None): watchpats = [ ] watchpats.append("Current ping state: S_IDLE") watchpats.append(self["Pat:DC_IDLE"]) if timeout == None: timeout = self["DeadTime"] idle_watch = CTS.LogWatcher(self["LogFileName"], watchpats, timeout) idle_watch.setwatch() - if not self.ns.WaitForAllNodesToComeUp(self.Env["nodes"]): - self.log("WaitForAllNodesToComeUp failed.") - return None - any_up = 0 for node in self.Env["nodes"]: # have each node dump its current state if self.ShouldBeStatus[node] == self["up"]: self.rsh.readaline(node, (self["StatusCmd"] %node) ) any_up = 1 if any_up == 0 or idle_watch.look(): return 1 self.log("Warn: Cluster Master not IDLE") return None def is_node_dc(self, node, status_line=None): rc = 0 - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s failed." % node) - return rc - if not status_line: status_line = self.rsh.readaline(node, self["StatusCmd"]%node) if not status_line: rc = 0 elif string.find(status_line, 'S_IDLE') != -1: rc = 1 elif string.find(status_line, 'S_INTEGRATION') != -1: rc = 1 elif string.find(status_line, 'S_FINALIZE_JOIN') != -1: rc = 1 elif string.find(status_line, 'S_POLICY_ENGINE') != -1: rc = 1 elif string.find(status_line, 'S_TRANSITION_ENGINE') != -1: rc = 1 if rc == 1: self.debug("%s _is_ the DC" % node) return rc def isolate_node(self, node, allowlist): '''isolate the communication between the nodes''' - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s failed." % node) - return None - rc = self.rsh(node, self["BreakCommCmd2"]%allowlist) if rc == 0: return 1 else: self.log("Could not break the communication from node: %s",node) return None def Configuration(self): if self.config: return self.config.getElementsByTagName('configuration')[0] warnings.filterwarnings("ignore") cib_file=os.tmpnam() warnings.resetwarnings() os.system("rm -f "+cib_file) if self.Env["ClobberCIB"] == 1: if self.Env["CIBfilename"] == None: self.debug("Creating new CIB in: " + cib_file) os.system("echo \'"+ self.default_cts_cib +"\' > "+ cib_file) else: os.system("cp "+self.Env["CIBfilename"]+" "+cib_file) else: if 0 != self.rsh.echo_cp( self.Env["nodes"][0], "@HA_VARLIBDIR@/heartbeat/crm/cib.xml", None, cib_file): raise ValueError("Can not copy file to %s, maybe permission denied"%cib_file) self.config = parse(cib_file) os.remove(cib_file) return self.config.getElementsByTagName('configuration')[0] def Resources(self): ResourceList = [] #read resources in cib configuration = self.Configuration() resources = configuration.getElementsByTagName('resources')[0] rscs = configuration.getElementsByTagName('primitive') incs = configuration.getElementsByTagName('clone') groups = configuration.getElementsByTagName('group') for rsc in rscs: if rsc in resources.childNodes: ResourceList.append(HAResource(self,rsc)) for grp in groups: for rsc in rscs: if rsc in grp.childNodes: if self.use_short_names: resource = HAResource(self,rsc) else: resource = HAResource(self,rsc,grp.getAttribute('id')) ResourceList.append(resource) for inc in incs: max = 0 inc_name = inc.getAttribute("id") instance_attributes = inc.getElementsByTagName('instance_attributes')[0] attributes = instance_attributes.getElementsByTagName('attributes')[0] nvpairs = attributes.getElementsByTagName('nvpair') for nvpair in nvpairs: if nvpair.getAttribute("name") == "clone_max": max = int(nvpair.getAttribute("value")) inc_rsc = inc.getElementsByTagName('primitive')[0] for i in range(0,max): rsc = HAResource(self,inc_rsc) rsc.inc_no = i rsc.inc_name = inc_name rsc.inc_max = max if self.use_short_names: rsc.rid = rsc.rid + ":%d"%i else: rsc.rid = inc_name+":"+rsc.rid + ":%d"%i rsc.Instance = rsc.rid ResourceList.append(rsc) return ResourceList def ResourceGroups(self): GroupList = [] #read resources in cib configuration = self.Configuration() groups = configuration.getElementsByTagName('group') rscs = configuration.getElementsByTagName('primitive') for grp in groups: group = [] GroupList.append(group) for rsc in rscs: if rsc in grp.childNodes: if self.use_short_names: resource = HAResource(self,rsc) else: resource = HAResource(self,rsc,grp.getAttribute('id')) group.append(resource) return GroupList def Dependencies(self): DependencyList = [] #read dependency in cib configuration=self.Configuration() constraints=configuration.getElementsByTagName('constraints')[0] rsc_to_rscs=configuration.getElementsByTagName('rsc_to_rsc') for node in rsc_to_rscs: dependency = {} dependency["id"]=node.getAttribute('id') dependency["from"]=node.getAttribute('from') dependency["to"]=node.getAttribute('to') dependency["type"]=node.getAttribute('type') dependency["strength"]=node.getAttribute('strength') DependencyList.append(dependency) return DependencyList def find_partitions(self): ccm_partitions = [] - if not self.ns.WaitForAllNodesToComeUp(self.Env["nodes"]): - self.log("WaitForAllNodesToComeUp failed.") - return None - for node in self.Env["nodes"]: self.debug("Retrieving partition details for %s" %node) if self.ShouldBeStatus[node] == self["up"]: partition = self.rsh.readaline(node, self["ParitionCmd"]) if not partition: self.log("no partition details for %s" %node) elif len(partition) > 2: partition = partition[:-1] - self.debug("partition details for %s: %s" %(node, partition)) found=0 for a_partition in ccm_partitions: if partition == a_partition: found = 1 if found == 0: - self.debug("Adding partition") + self.debug("Adding partition from %s: %s" %(node, partition)) ccm_partitions.append(partition) else: self.log("bad partition details for %s" %node) return ccm_partitions def HasQuorum(self, node_list): # If we are auditing a partition, then one side will # have quorum and the other not. # So the caller needs to tell us which we are checking # If no value for node_list is specified... assume all nodes if not node_list: node_list = self.Env["nodes"] - if not self.ns.WaitForAllNodesToComeUp(node_list): - self.log("WaitForAllNodesToComeUp failed in HasQuorum.") - return 0 - for node in node_list: if self.ShouldBeStatus[node] == self["up"]: quorum = self.rsh.readaline(node, self["QuorumCmd"]) if string.find(quorum, "1") != -1: return 1 elif string.find(quorum, "0") != -1: return 0 else: self.log("WARN: Unexpected quorum test result from "+ node +":"+ quorum) return 0 def Components(self): complist = [Process("lrmd",self),Process("crmd",self)] if self.Env["DoFencing"] == 1 : complist.append(Process("stonithd",self)) complist.append(Process("heartbeat",self)) return complist def NodeUUID(self, node): - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s failed in NodeUUID." % node) - return "" - lines = self.rsh.readlines(node, self["UUIDQueryCmd"]) for line in lines: self.debug("UUIDLine:"+ line) m = re.search(r'%s.+\((.+)\)' % node, line) if m: return m.group(1) return "" def StandbyStatus(self, node): - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s failed in StandbyStatus." % node) - return "" - out=self.rsh.readaline(node, self["StandbyQueryCmd"]%node) if not out: return "off" out = out[:-1] self.debug("Standby result: "+out) return out # status == "on" : Enter Standby mode # status == "off": Enter Active mode def SetStandbyMode(self, node, status): - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s failed in SetStandbyMode." % node) - return True - current_status = self.StandbyStatus(node) cmd = self["StandbyCmd"] % (node, status) ret = self.rsh(node, cmd) return True class HAResource(Resource): def __init__(self, cm, node, group=None): ''' Get information from xml node ''' if group == None : self.rid = str(node.getAttribute('id')) else : self.rid = group + ":" + str(node.getAttribute('id')) self.rclass = str(node.getAttribute('class')) self.rtype = str(node.getAttribute('type')) self.inc_name = None self.inc_no = -1 self.inc_max = -1 self.rparameters = {} nvpairs = [] list = node.getElementsByTagName('instance_attributes') if len(list) > 0: attributes = list[0] list = attributes.getElementsByTagName('attributes') if len(list) > 0: parameters = list[0] nvpairs = parameters.getElementsByTagName('nvpair') for nvpair in nvpairs: name=nvpair.getAttribute('name') value=nvpair.getAttribute('value') self.rparameters[name]=value # This should normally be called first... FIXME! Resource.__init__(self, cm, self.rtype, self.rid) # resources that dont need quorum will have: # ops = node.getElementsByTagName('op') for op in ops: if op.getAttribute('name') == "start" and op.getAttribute('prereq') == "nothing": self.needs_quorum = 0 - if self.needs_quorum == 0: - self.CM.debug("Resource %s does not need quorum" % self.rid) -# else: -# self.CM.debug("Resource %s DOES need quorum" % self.rid) - def IsRunningOn(self, nodename): ''' This member function returns true if our resource is running on the given node in the cluster. We call the status operation for the resource script. ''' - if not self.CM.ns.WaitForNodeToComeUp(nodename): - self.CM.log("WaitForNodeToComeUp %s failed in IsRunningOn." % node) - return 0 - out=self.CM.rsh.readaline(nodename, self.CM["IsRscRunning"]%self.rid) return re.search("0",out) def RunningNodes(self): ResourceNodes = [] for node in self.CM.Env["nodes"]: if self.CM.ShouldBeStatus[node] == self.CM["up"]: if self.IsRunningOn(node): ResourceNodes.append(node) return ResourceNodes def _ResourceOperation(self, operation, nodename): ''' Execute an operation on the resource ''' - if not self.CM.ns.WaitForNodeToComeUp(nodename): - self.CM.log("WaitForNodeToComeUp %s failed in _ResourceOperation." % node) - return 0 - self.CM.rsh.readaline(nodename, self.CM["ExecuteRscOp"]%(self.rid,operation)) return self.CM.rsh.lastrc == 0 def Start(self, nodename): ''' This member function starts or activates the resource. ''' return self._ResourceOperation("start", nodename) def Stop(self, nodename): ''' This member function stops or deactivates the resource. ''' return self._ResourceOperation("stop", nodename) def IsWorkingCorrectly(self, nodename): return self._ResourceOperation("monitor", nodename) ####################################################################### # # A little test code... # # Which you are advised to completely ignore... # ####################################################################### if __name__ == '__main__': pass diff --git a/cts/CTS.py.in b/cts/CTS.py.in index 97219b53bd..9d044b65f2 100755 --- a/cts/CTS.py.in +++ b/cts/CTS.py.in @@ -1,1236 +1,1185 @@ #!@PYTHON@ '''CTS: Cluster Testing System: Main module Classes related to testing high-availability clusters... Lots of things are implemented. Lots of things are not implemented. We have many more ideas of what to do than we've implemented. ''' __copyright__=''' Copyright (C) 2000, 2001 Alan Robertson Licensed under the GNU GPL. ''' # # 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 program 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 program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import types, string, select, sys, time, re, os, struct, os, signal import base64, pickle, binascii from UserDict import UserDict from syslog import * from popen2 import Popen3 class RemoteExec: '''This is an abstract remote execution class. It runs a command on another machine - somehow. The somehow is up to us. This particular class uses ssh. Most of the work is done by fork/exec of ssh or scp. ''' def __init__(self): # -n: no stdin, -x: no X11 self.Command = "@SSH@ -l root -n -x" # -f: ssh to background self.CommandnoBlock = "@SSH@ -f -l root -n -x" # -B: batch mode, -q: no stats (quiet) self.CpCommand = "@SCP@ -B -q" self.OurNode=string.lower(os.uname()[1]) def setcmd(self, rshcommand): '''Set the name of the remote shell command''' self.Command = rshcommand def _fixcmd(self, cmd): return re.sub("\'", "'\\''", cmd) def _cmd(self, *args): '''Compute the string that will run the given command on the given remote system''' args= args[0] sysname = args[0] command = args[1] #print "sysname: %s, us: %s" % (sysname, self.OurNode) if sysname == None or string.lower(sysname) == self.OurNode or sysname == "localhost": ret = command else: ret = self.Command + " " + sysname + " '" + self._fixcmd(command) + "'" #print ("About to run %s\n" % ret) return ret def _cmd_noblock(self, *args): '''Compute the string that will run the given command on the given remote system''' args= args[0] sysname = args[0] command = args[1] #print "sysname: %s, us: %s" % (sysname, self.OurNode) if sysname == None or string.lower(sysname) == self.OurNode or sysname == "localhost": ret = command + " &" else: ret = self.CommandnoBlock + " " + sysname + " '" + self._fixcmd(command) + "'" #print ("About to run %s\n" % ret) return ret def __call__(self, *args): '''Run the given command on the given remote system If you call this class like a function, this is the function that gets called. It just runs it roughly as though it were a system() call on the remote machine. The first argument is name of the machine to run it on. ''' count=0; rc = 0; while count < 3: rc = os.system(self._cmd(args)) if rc == 0: return rc print "Retrying command %s" % self._cmd(args) count=count+1 return rc def popen(self, *args): '''popen the given remote command on the remote system. As in __call__, the first argument is name of the machine to run it on. ''' #print "Now running %s\n" % self._cmd(args) return Popen3(self._cmd(args), None) def readaline(self, *args): '''Run a command on the remote machine and capture 1 line of stdout from the given remote command As in __call__, the first argument is name of the machine to run it on. ''' p = self.popen(args[0], args[1]) p.tochild.close() result = p.fromchild.readline() p.fromchild.close() self.lastrc = p.wait() return result def readlines(self, *args): p = self.popen(args[0], args[1]) p.tochild.close() result = p.fromchild.readlines() p.fromchild.close() self.lastrc = p.wait() return result def cp(self, *args): '''Perform a remote copy''' cpstring=self.CpCommand for arg in args: cpstring = cpstring + " \'" + arg + "\'" count=0; rc = 0; for i in range(3): rc = os.system(cpstring) if rc == 0: return rc print "Retrying command %s" % cpstring return rc def echo_cp(self, src_host, src_file, dest_host, dest_file): '''Perform a remote copy via echo''' (rc, lines) = self.remote_py(src_host, "os", "system", "cat %s" % src_file) if rc != 0: print "Copy of %s:%s failed" % (src_host, src_file) elif dest_host == None: fd = open(dest_file, "w") fd.writelines(lines) fd.close() else: big_line="" for line in lines: big_line = big_line + line (rc, lines) = self.remote_py(dest_host, "os", "system", "echo '%s' > %s" % (big_line, dest_file)) return rc def noBlock(self, *args): '''Perform a remote execution without waiting for it to finish''' sshnoBlock = self._cmd_noblock(args) count=0; rc = 0; for i in range(3): rc = os.system(sshnoBlock) if rc == 0: return rc print "Retrying command %s" % sshnoBlock return rc def remote_py(self, node, module, func, *args): '''Execute a remote python function If the call success, lastrc == 0 and return result. If the call fail, lastrc == 1 and return the reason (string) ''' encode_args = binascii.b2a_base64(pickle.dumps(args)) encode_cmd = string.join(["@hb_libdir@/cts/CTSproxy.py",module,func,encode_args]) #print "%s: %s.%s %s" % (node, module, func, repr(args)) result = self.readlines(node, encode_cmd) if result != None: result.pop() if self.lastrc == 0: last_line="" if result != None: array_len = len(result) if array_len > 0: last_line=result.pop() #print "result: %s" % repr(last_line) return pickle.loads(binascii.a2b_base64(last_line)), result return -1, result class LogWatcher: '''This class watches logs for messages that fit certain regular expressions. Watching logs for events isn't the ideal way to do business, but it's better than nothing :-) On the other hand, this class is really pretty cool ;-) The way you use this class is as follows: Construct a LogWatcher object Call setwatch() when you want to start watching the log Call look() to scan the log looking for the patterns ''' def __init__(self, log, regexes, timeout=10, debug=None): '''This is the constructor for the LogWatcher class. It takes a log name to watch, and a list of regular expressions to watch for." ''' # Validate our arguments. Better sooner than later ;-) for regex in regexes: assert re.compile(regex) self.regexes = regexes self.filename = log self.debug=debug self.whichmatch = -1 self.unmatched = None if self.debug: print "Debug now on for for log", log self.Timeout = int(timeout) self.returnonlymatch = None if not os.access(log, os.R_OK): raise ValueError("File [" + log + "] not accessible (r)") def setwatch(self, frombeginning=None): '''Mark the place to start watching the log from. ''' self.file = open(self.filename, "r") self.size = os.path.getsize(self.filename) if not frombeginning: self.file.seek(0,2) def ReturnOnlyMatch(self, onlymatch=1): '''Mark the place to start watching the log from. ''' self.returnonlymatch = onlymatch def look(self, timeout=None): '''Examine the log looking for the given patterns. It starts looking from the place marked by setwatch(). This function looks in the file in the fashion of tail -f. It properly recovers from log file truncation, but not from removing and recreating the log. It would be nice if it recovered from this as well :-) We return the first line which matches any of our patterns. ''' last_line=None first_line=None if timeout == None: timeout = self.Timeout done=time.time()+timeout+1 if self.debug: print "starting search: timeout=%d" % timeout for regex in self.regexes: print "Looking for regex: ", regex while (timeout <= 0 or time.time() <= done): newsize=os.path.getsize(self.filename) if self.debug > 4: print "newsize = %d" % newsize if newsize < self.size: # Somebody truncated the log! if self.debug: print "Log truncated!" self.setwatch(frombeginning=1) continue if newsize > self.file.tell(): line=self.file.readline() if self.debug > 2: print "Looking at line:", line if line: last_line=line if not first_line: first_line=line if self.debug: print "First line: "+ line which=-1 for regex in self.regexes: which=which+1 if self.debug > 3: print "Comparing line to ", regex #matchobj = re.search(string.lower(regex), string.lower(line)) matchobj = re.search(regex, line) if matchobj: self.whichmatch=which if self.returnonlymatch: return matchobj.group(self.returnonlymatch) else: if self.debug: print "Returning line" return line newsize=os.path.getsize(self.filename) if self.file.tell() == newsize: if timeout > 0: time.sleep(0.025) else: if self.debug: print "End of file" if self.debug: print "Last line: "+last_line return None if self.debug: print "Timeout" if self.debug: print "Last line: "+last_line return None def lookforall(self, timeout=None): '''Examine the log looking for ALL of the given patterns. It starts looking from the place marked by setwatch(). We return when the timeout is reached, or when we have found ALL of the regexes that were part of the watch ''' if timeout == None: timeout = self.Timeout save_regexes = self.regexes returnresult = [] while (len(self.regexes) > 0): oneresult = self.look(timeout) if not oneresult: self.unmatched = self.regexes self.regexes = save_regexes return None returnresult.append(oneresult) del self.regexes[self.whichmatch] self.unmatched = None self.regexes = save_regexes return returnresult # In case we ever want multiple regexes to match a single line... #- del self.regexes[self.whichmatch] #+ tmp_regexes = self.regexes #+ self.regexes = [] #+ which = 0 #+ for regex in tmp_regexes: #+ matchobj = re.search(regex, oneresult) #+ if not matchobj: #+ self.regexes.append(regex) class NodeStatus: def __init__(self, Env): self.Env = Env self.rsh = RemoteExec() def IsNodeBooted(self, node): - '''Return TRUE if the given node is booted (responds to pings''' + '''Return TRUE if the given node is booted (responds to pings)''' return os.system("@PING@ -nq -c1 @PING_TIMEOUT_OPT@ %s >/dev/null 2>&1" % node) == 0 def IsSshdUp(self, node): return self.rsh(node, "true") == 0; def WaitForNodeToComeUp(self, node, Timeout=300): '''Return TRUE when given node comes up, or None/FALSE if timeout''' timeout=Timeout anytimeouts=0 while timeout > 0: if self.IsNodeBooted(node) and self.IsSshdUp(node): if anytimeouts: # Fudge to wait for the system to finish coming up time.sleep(30) self.Env.log("Node %s now up" % node) return 1 time.sleep(1) if (not anytimeouts): self.Env.log("Waiting for node %s to come up" % node) anytimeouts=1 timeout = timeout - 1 self.Env.log("%s did not come up within %d tries" % (node, Timeout)) def WaitForAllNodesToComeUp(self, nodes, timeout=300): '''Return TRUE when all nodes come up, or FALSE if timeout''' for node in nodes: if not self.WaitForNodeToComeUp(node, timeout): return None return 1 class ClusterManager(UserDict): '''The Cluster Manager class. This is an subclass of the Python dictionary class. (this is because it contains lots of {name,value} pairs, not because it's behavior is that terribly similar to a dictionary in other ways.) This is an abstract class which class implements high-level operations on the cluster and/or its cluster managers. Actual cluster managers classes are subclassed from this type. One of the things we do is track the state we think every node should be in. ''' def __InitialConditions(self): #if os.geteuid() != 0: # raise ValueError("Must Be Root!") None def _finalConditions(self): for key in self.keys(): if self[key] == None: raise ValueError("Improper derivation: self[" + key + "] must be overridden by subclass.") def __init__(self, Environment, randseed=None): self.Env = Environment self.__InitialConditions() self.clear_cache = 0 self.data = { "up" : "up", # Status meaning up "down" : "down", # Status meaning down "StonithCmd" : "@sbindir@/stonith -t baytech -p '10.10.10.100 admin admin' %s", "DeadTime" : 30, # Max time to detect dead node... "StartTime" : 90, # Max time to start up # # These next values need to be overridden in the derived class. # "Name" : None, "StartCmd" : None, "StopCmd" : None, "StatusCmd" : None, "RereadCmd" : None, "StartDRBDCmd" : None, "StopDRBDCmd" : None, "StatusDRBDCmd" : None, "DRBDCheckconf" : None, "BreakCommCmd" : None, "FixCommCmd" : None, "TestConfigDir" : None, "LogFileName" : None, "Pat:We_started" : None, "Pat:They_started" : None, "Pat:We_stopped" : None, "Pat:They_stopped" : None, "BadRegexes" : None, # A set of "bad news" regexes # to apply to the log } self.rsh = RemoteExec() self.ShouldBeStatus={} self.OurNode=string.lower(os.uname()[1]) self.ShouldBeStatus={} self.ns = NodeStatus(self.Env) def errorstoignore(self): '''Return list of errors which are 'normal' and should be ignored''' return [] def log(self, args): self.Env.log(args) def debug(self, args): self.Env.debug(args) def prepare(self): '''Finish the Initialization process. Prepare to test...''' for node in self.Env["nodes"]: if self.StataCM(node): self.ShouldBeStatus[node]=self["up"] else: self.ShouldBeStatus[node]=self["down"] def upcount(self): '''How many nodes are up?''' count=0 for node in self.Env["nodes"]: if self.ShouldBeStatus[node]==self["up"]: count=count+1 return count def TruncLogs(self): '''Truncate the log for the cluster manager so we can start clean''' if self["LogFileName"] != None: os.system("cp /dev/null " + self["LogFileName"]) def install_config(self, node): return None def clear_all_caches(self): if self.clear_cache: for node in self.Env["nodes"]: if self.ShouldBeStatus[node] == self["down"]: self.debug("Removing cache file on: "+node) self.rsh.remote_py(node, "os", "system", "rm -f @HA_VARLIBDIR@/heartbeat/hostcache") else: self.debug("NOT Removing cache file on: "+node) def StartaCM(self, node): '''Start up the cluster manager on a given node''' self.debug("Starting %s on node %s" %(self["Name"], node)) if not self.ShouldBeStatus.has_key(node): self.ShouldBeStatus[node] = self["down"] if self.ShouldBeStatus[node] != self["down"]: return 1 - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s failed" % node) - return 0 - patterns = [] # Technically we should always be able to notice ourselves starting if self.upcount() == 0: patterns.append(self["Pat:We_started"] % node) else: patterns.append(self["Pat:They_started"] % node) watch = LogWatcher( self["LogFileName"], patterns, timeout=self["StartTime"]+10) watch.setwatch() self.install_config(node) - if not self.ns.WaitForNodeToComeUp(node): - self.log("Second WaitForNodeToComeUp %s failed" % node) - return 0 - self.ShouldBeStatus[node] = "any" if self.StataCM(node) and self.cluster_stable(self["DeadTime"]): self.log ("%s was already started" %(node)) return 1 # Clear out the host cache so autojoin can be exercised if self.clear_cache: self.debug("Removing cache file on: "+node) self.rsh.remote_py(node, "os", "system", "rm -f @HA_VARLIBDIR@/heartbeat/hostcache") if self.rsh(node, self["StartCmd"]) != 0: self.log ("Warn: Start command failed on node %s" %(node)) return None self.ShouldBeStatus[node]=self["up"] watch_result = watch.lookforall() if watch.unmatched: for regex in watch.unmatched: self.log ("Warn: Startup pattern not found: %s" %(regex)) if watch_result: #self.debug("Found match: "+ repr(watch_result)) self.cluster_stable(self["DeadTime"]) return 1 if self.StataCM(node) and self.cluster_stable(self["DeadTime"]): return 1 self.log ("Warn: Start failed for node %s" %(node)) return None def StartaCMnoBlock(self, node): '''Start up the cluster manager on a given node with none-block mode''' self.debug("Starting %s on node %s" %(self["Name"], node)) - if not self.ns.IsNodeBooted(node): - self.log("node %s not booted, StartaCMnoBlock failed." % node) - return 0 - # Clear out the host cache so autojoin can be exercised if self.clear_cache: self.debug("Removing cache file on: "+node) self.rsh.remote_py(node, "os", "system", "rm -f @HA_VARLIBDIR@/heartbeat/hostcache") self.rsh.noBlock(node, self["StartCmd"]) self.ShouldBeStatus[node]=self["up"] return 1 def StopaCM(self, node): '''Stop the cluster manager on a given node''' self.debug("Stopping %s on node %s" %(self["Name"], node)) if self.ShouldBeStatus[node] != self["up"]: return 1 - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s filed" % node) - return 0 - if self.rsh(node, self["StopCmd"]) == 0: self.ShouldBeStatus[node]=self["down"] self.cluster_stable(self["DeadTime"]) return 1 else: self.log ("Could not stop %s on node %s" %(self["Name"], node)) return None def StopaCMnoBlock(self, node): '''Stop the cluster manager on a given node with none-block mode''' self.debug("Stopping %s on node %s" %(self["Name"], node)) - if not self.ns.IsNodeBooted(node): - self.log("node %s not booted, StopaCNnoBlock failed." % node) - return 0 - self.rsh.noBlock(node, self["StopCmd"]) self.ShouldBeStatus[node]=self["down"] return 1 def cluster_stable(self, timeout = None): time.sleep(self["StableTime"]) return 1 def node_stable(self, node): return 1 def RereadCM(self, node): '''Force the cluster manager on a given node to reread its config This may be a no-op on certain cluster managers. ''' - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s filed" % node) - return 0 - rc=self.rsh(node, self["RereadCmd"]) if rc == 0: return 1 else: self.log ("Could not force %s on node %s to reread its config" % (self["Name"], node)) return None def StataCM(self, node): '''Report the status of the cluster manager on a given node''' out=self.rsh.readaline(node, self["StatusCmd"]) ret= (string.find(out, 'stopped') == -1) try: if ret: if self.ShouldBeStatus[node] == self["down"]: self.log( "Node status for %s is %s but we think it should be %s" % (node, self["up"], self.ShouldBeStatus[node])) else: if self.ShouldBeStatus[node] == self["up"]: self.log( "Node status for %s is %s but we think it should be %s" % (node, self["down"], self.ShouldBeStatus[node])) except KeyError: pass if ret: self.ShouldBeStatus[node]=self["up"] else: self.ShouldBeStatus[node]=self["down"] return ret def startall(self, nodelist=None): '''Start the cluster manager on every node in the cluster. We can do it on a subset of the cluster if nodelist is not None. ''' ret = 1 map = {} if not nodelist: nodelist=self.Env["nodes"] for node in nodelist: if self.ShouldBeStatus[node] == self["down"]: if not self.StartaCM(node): ret = 0 return ret def stopall(self, nodelist=None): '''Stop the cluster managers on every node in the cluster. We can do it on a subset of the cluster if nodelist is not None. ''' ret = 1 map = {} if not nodelist: nodelist=self.Env["nodes"] for node in self.Env["nodes"]: if self.ShouldBeStatus[node] == self["up"]: if not self.StopaCM(node): ret = 0 return ret def rereadall(self, nodelist=None): '''Force the cluster managers on every node in the cluster to reread their config files. We can do it on a subset of the cluster if nodelist is not None. ''' map = {} if not nodelist: nodelist=self.Env["nodes"] for node in self.Env["nodes"]: if self.ShouldBeStatus[node] == self["up"]: self.RereadCM(node) def statall(self, nodelist=None): '''Return the status of the cluster managers in the cluster. We can do it on a subset of the cluster if nodelist is not None. ''' result={} if not nodelist: nodelist=self.Env["nodes"] for node in nodelist: if self.StataCM(node): result[node] = self["up"] else: result[node] = self["down"] return result def isolate_node(self, node): - '''isolate the communication between the nodes''' - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s filed in isolate_node" % node) - return None - rc = self.rsh(node, self["BreakCommCmd"]) if rc == 0: return 1 else: self.log("Could not break the communication between the nodes frome node: %s" % node) return None def unisolate_node(self, node): - '''fix the communication between the nodes''' - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s filed in unisolate_node" % node) - return None - rc = self.rsh(node, self["FixCommCmd"]) if rc == 0: return 1 else: self.log("Could not fix the communication between the nodes from node: %s" % node) return None def reducecomm_node(self,node): '''reduce the communication between the nodes''' - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s filed in reducecomm_node" % node) - return None - rc = self.rsh(node, self["ReduceCommCmd"]%(self.Env["XmitLoss"],self.Env["RecvLoss"])) if rc == 0: return 1 else: self.log("Could not reduce the communication between the nodes from node: %s" % node) return None def savecomm_node(self,node): '''save current the communication between the nodes''' rc = 0 - - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s filed in savecomm_node" % node) - return None - if float(self.Env["XmitLoss"])!=0 or float(self.Env["RecvLoss"])!=0 : rc = self.rsh(node, self["SaveFileCmd"]); if rc == 0: return 1 else: self.log("Could not save the communication between the nodes from node: %s" % node) return None def restorecomm_node(self,node): '''restore the saved communication between the nodes''' rc = 0 - if not self.ns.WaitForNodeToComeUp(node): - self.log("WaitForNodeToComeUp %s filed in restoreomm_node" % node) - return None - if float(self.Env["XmitLoss"])!=0 or float(self.Env["RecvLoss"])!=0 : rc = self.rsh(node, self["RestoreCommCmd"]); if rc == 0: return 1 else: self.log("Could not restore the communication between the nodes from node: %s" % node) return None def SyncTestConfigs(self): '''Synchronize test configurations throughout the cluster. This one's a no-op for FailSafe, since it does that by itself. ''' fromdir=self["TestConfigDir"] if not os.access(fromdir, os.F_OK | os.R_OK | os.W_OK): raise ValueError("Directory [" + fromdir + "] not accessible (rwx)") for node in self.Env["nodes"]: if node == self.OurNode: continue self.log("Syncing test configurations on " + node) # Perhaps I ought to use rsync... self.rsh.cp("-r", fromdir, node + ":" + fromdir) def SetClusterConfig(self, configpath="default", nodelist=None): '''Activate the named test configuration throughout the cluster. It would be useful to implement this :-) ''' pass return 1 def ResourceGroups(self): "Return a list of resource type/instance pairs for the cluster" raise ValueError("Abstract Class member (ResourceGroups)") def InternalCommConfig(self): "Return a list of paths: each patch consists of a tuple" raise ValueError("Abstract Class member (InternalCommConfig)") def HasQuorum(self, node_list): "Return TRUE if the cluster currently has quorum" # If we are auditing a partition, then one side will # have quorum and the other not. # So the caller needs to tell us which we are checking # If no value for node_list is specified... assume all nodes raise ValueError("Abstract Class member (HasQuorum)") def Components(self): raise ValueError("Abstract Class member (Components)") def TestLogging(self): patterns= [] prefix="Test message from " for node in self.Env["nodes"]: patterns.append(prefix + node) watch = LogWatcher(self["LogFileName"], patterns, 30 + len(self.Env["nodes"])) watch.setwatch() logpri = self.Env["logfacility"] + ".info" for node in self.Env["nodes"]: cmd="logger -p %s %s%s" % (logpri, prefix, node) if self.rsh.noBlock(node, cmd) != 0: self.log ("ERROR: Cannot execute remote command [%s] on %s" % (cmd, node)) watch_result = watch.lookforall() if watch.unmatched: self.log("ERROR: Remote logging is not working. Please fix before continuing.") for regex in watch.unmatched: self.log ("ERROR: Test message [%s] not found in logs." % (regex)) return None return 1 def CheckDf(self): dfcmd="df -k /var/log | tail -1 | tr -s ' ' | cut -d' ' -f2" dfmin=500000 result=1 for node in self.Env["nodes"]: dfout=self.rsh.readaline(node, dfcmd) if not dfout: self.log ("ERROR: Cannot execute remote df command [%s] on %s" % (dfcmd, node)) else: try: idfout = int(dfout) except (ValueError, TypeError): self.log("Warning: df output from %s was invalid [%s]" % (node, dfout)) else: if idfout == 0: self.log("CRIT: Completely out of log disk space on %s" % node) result=None elif idfout <= 500: self.log("WARN: Low on log disk space (%d Mbytes) on %s" % (idfout, node)) return result class Resource: ''' This is an HA resource (not a resource group). A resource group is just an ordered list of Resource objects. ''' def __init__(self, cm, rsctype=None, instance=None): self.CM = cm self.ResourceType = rsctype self.Instance = instance self.needs_quorum = 1 def Type(self): return self.ResourceType def Instance(self, nodename): return self.Instance def IsRunningOn(self, nodename): ''' This member function returns true if our resource is running on the given node in the cluster. It is analagous to the "status" operation on SystemV init scripts and heartbeat scripts. FailSafe calls it the "exclusive" operation. ''' raise ValueError("Abstract Class member (IsRunningOn)") return None def IsWorkingCorrectly(self, nodename): ''' This member function returns true if our resource is operating correctly on the given node in the cluster. Heartbeat does not require this operation, but it might be called the Monitor operation, which is what FailSafe calls it. For remotely monitorable resources (like IP addresses), they *should* be monitored remotely for testing. ''' raise ValueError("Abstract Class member (IsWorkingCorrectly)") return None def Start(self, nodename): ''' This member function starts or activates the resource. ''' raise ValueError("Abstract Class member (Start)") return None def Stop(self, nodename): ''' This member function stops or deactivates the resource. ''' raise ValueError("Abstract Class member (Stop)") return None def __repr__(self): if (self.Instance and len(self.Instance) > 1): return "{" + self.ResourceType + "::" + self.Instance + "}" else: return "{" + self.ResourceType + "}" class Component: def kill(self, node): None class Process(Component): def __init__(self, name, cm): self.name = str(name) self.CM = cm self.KillCmd = "killall -9 " + self.name def kill(self, node): - if not self.CM.ns.WaitForNodeToComeUp(node): - self.CM.log("WaitForNodeToComeUp %s filed in kill" % node) - return None - if self.CM.rsh(node, self.KillCmd) != 0: self.log ("Warn: Kill %s failed on node %s" %(name,node)) return None return 1 class ScenarioComponent: def __init__(self, Env): self.Env = Env def IsApplicable(self): '''Return TRUE if the current ScenarioComponent is applicable in the given LabEnvironment given to the constructor. ''' raise ValueError("Abstract Class member (IsApplicable)") def SetUp(self, CM): '''Set up the given ScenarioComponent''' raise ValueError("Abstract Class member (Setup)") def TearDown(self, CM): '''Tear down (undo) the given ScenarioComponent''' raise ValueError("Abstract Class member (Setup)") class Scenario: ( '''The basic idea of a scenario is that of an ordered list of ScenarioComponent objects. Each ScenarioComponent is SetUp() in turn, and then after the tests have been run, they are torn down using TearDown() (in reverse order). A Scenario is applicable to a particular cluster manager iff each ScenarioComponent is applicable. A partially set up scenario is torn down if it fails during setup. ''') def __init__(self, Components): "Initialize the Scenario from the list of ScenarioComponents" for comp in Components: if not issubclass(comp.__class__, ScenarioComponent): raise ValueError("Init value must be subclass of" " ScenarioComponent") self.Components = Components def IsApplicable(self): ( '''A Scenario IsApplicable() iff each of its ScenarioComponents IsApplicable() ''' ) for comp in self.Components: if not comp.IsApplicable(): return None return 1 def SetUp(self, CM): '''Set up the Scenario. Return TRUE on success.''' j=0 while j < len(self.Components): if not self.Components[j].SetUp(CM): # OOPS! We failed. Tear partial setups down. CM.log("Tearing down partial setup") self.TearDown(CM, j) return None j=j+1 return 1 def TearDown(self, CM, max=None): '''Tear Down the Scenario - in reverse order.''' if max == None: max = len(self.Components)-1 j=max while j >= 0: self.Components[j].TearDown(CM) j=j-1 class InitClusterManager(ScenarioComponent): ( '''InitClusterManager is the most basic of ScenarioComponents. This ScenarioComponent simply starts the cluster manager on all the nodes. It is fairly robust as it waits for all nodes to come up before starting as they might have been rebooted or crashed for some reason beforehand. ''') def __init__(self, Env): pass def IsApplicable(self): '''InitClusterManager is so generic it is always Applicable''' return 1 def SetUp(self, CM): '''Basic Cluster Manager startup. Start everything''' CM.prepare() # Clear out the cobwebs ;-) self.TearDown(CM) for node in CM.Env["nodes"]: CM.rsh(node, CM["DelFileCommCmd"]+ "; true") # Now start the Cluster Manager on all the nodes. CM.log("Starting Cluster Manager on all nodes.") return CM.startall() def TearDown(self, CM): '''Set up the given ScenarioComponent''' # Stop the cluster manager everywhere CM.log("Stopping Cluster Manager on all nodes") return CM.stopall() class PingFest(ScenarioComponent): ( '''PingFest does a flood ping to each node in the cluster from the test machine. If the LabEnvironment Parameter PingSize is set, it will be used as the size of ping packet requested (via the -s option). If it is not set, it defaults to 1024 bytes. According to the manual page for ping: Outputs packets as fast as they come back or one hundred times per second, whichever is more. For every ECHO_REQUEST sent a period ``.'' is printed, while for every ECHO_REPLY received a backspace is printed. This provides a rapid display of how many packets are being dropped. Only the super-user may use this option. This can be very hard on a net- work and should be used with caution. ''' ) def __init__(self, Env): self.Env = Env def IsApplicable(self): '''PingFests are always applicable ;-) ''' return 1 def SetUp(self, CM): '''Start the PingFest!''' self.PingSize=1024 if CM.Env.has_key("PingSize"): self.PingSize=CM.Env["PingSize"] CM.log("Starting %d byte flood pings" % self.PingSize) self.PingPids=[] for node in CM.Env["nodes"]: self.PingPids.append(self._pingchild(node)) CM.log("Ping PIDs: " + repr(self.PingPids)) return 1 def TearDown(self, CM): '''Stop it right now! My ears are pinging!!''' for pid in self.PingPids: if pid != None: CM.log("Stopping ping process %d" % pid) os.kill(pid, signal.SIGKILL) def _pingchild(self, node): Args = ["ping", "-qfn", "-s", str(self.PingSize), node] sys.stdin.flush() sys.stdout.flush() sys.stderr.flush() pid = os.fork() if pid < 0: self.Env.log("Cannot fork ping child") return None if pid > 0: return pid # Otherwise, we're the child process. os.execvp("ping", Args) self.Env.log("Cannot execvp ping: " + repr(Args)) sys.exit(1) class PacketLoss(ScenarioComponent): ( ''' It would be useful to do some testing of CTS with a modest amount of packet loss enabled - so we could see that everything runs like it should with a certain amount of packet loss present. ''') def IsApplicable(self): '''always Applicable''' return 1 def SetUp(self, CM): '''Reduce the reliability of communications''' if float(CM.Env["XmitLoss"]) == 0 and float(CM.Env["RecvLoss"]) == 0 : return 1 for node in CM.Env["nodes"]: CM.reducecomm_node(node) CM.log("Reduce the reliability of communications") return 1 def TearDown(self, CM): '''Fix the reliability of communications''' if float(CM.Env["XmitLoss"]) == 0 and float(CM.Env["RecvLoss"]) == 0 : return 1 for node in CM.Env["nodes"]: CM.unisolate_node(node) CM.log("Fix the reliability of communications") class BasicSanityCheck(ScenarioComponent): ( ''' ''') def IsApplicable(self): return self.Env["DoBSC"] def SetUp(self, CM): CM.prepare() # Clear out the cobwebs self.TearDown(CM) # Now start the Cluster Manager on all the nodes. CM.log("Starting Cluster Manager on BSC node(s).") return CM.startall() def TearDown(self, CM): CM.log("Stopping Cluster Manager on BSC node(s).") return CM.stopall() diff --git a/cts/CTSaudits.py.in b/cts/CTSaudits.py.in index abfaab8497..8cc78ce40f 100755 --- a/cts/CTSaudits.py.in +++ b/cts/CTSaudits.py.in @@ -1,698 +1,698 @@ #!@PYTHON@ '''CTS: Cluster Testing System: Audit module ''' __copyright__=''' Copyright (C) 2000, 2001,2005 Alan Robertson Licensed under the GNU GPL. ''' # # 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 program 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 program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import time, os, popen2, string, re import CTS import os import popen2 class ClusterAudit: def __init__(self, cm): self.CM = cm def __call__(self): raise ValueError("Abstract Class member (__call__)") def is_applicable(self): '''Return TRUE if we are applicable in the current test configuration''' raise ValueError("Abstract Class member (is_applicable)") return 1 def name(self): raise ValueError("Abstract Class member (name)") AllAuditClasses = [ ] class ResourceAudit(ClusterAudit): def name(self): return "ResourceAudit" def _doauditRsc(self, resource): ResourceNodes = [] for node in self.CM.Env["nodes"]: if self.CM.ShouldBeStatus[node] == self.CM["up"]: if resource.IsRunningOn(node): ResourceNodes.append(node) return ResourceNodes def _doaudit(self): '''Check to see if all resources are running in exactly one place in the cluster. We also verify that the members of a resource group are all running on the same node in the cluster, and we monitor that they are all running "properly". ''' Fatal = 0 result = [] # Thought: use self.CM.find_partitions() and make this audit # aware of partitions. Since in a split cluster one # partition may have quorum (and permission to run resources) # and the other not. Groups = self.CM.ResourceGroups() for group in Groups: GrpServedBy = None lastResource = None for resource in group: # # _doauditRsc returns the set of nodes serving # the given resource. This is normally a single node. # ResourceNodes = self._doauditRsc(resource) # Is the resource served without quorum present? if not self.CM.HasQuorum(None) and len(ResourceNodes) != 0 and resource.needs_quorum: result.append("Resource " + repr(resource) + " active without Quorum: " + repr(ResourceNodes)) # Is the resource served at all? elif len(ResourceNodes) == 0 and self.CM.HasQuorum(None): result.append("Resource " + repr(resource) + " not served anywhere.") # Is the resource served too many times? elif len(ResourceNodes) > 1: result.append("Resource " + repr(resource) + " served too many times: " + repr(ResourceNodes)) self.CM.log("Resource " + repr(resource) + " served too many times: " + repr(ResourceNodes)) Fatal = 1 elif GrpServedBy == None: GrpServedBy = ResourceNodes # Are all the members of the Rsc Grp served by the same node? elif GrpServedBy != ResourceNodes: result.append("Resource group resources" + repr(resource) + " running on different nodes: " + repr(ResourceNodes)+" vs "+repr(GrpServedBy) + "(otherRsc = " + repr(lastResource) + ")") self.CM.log("Resource group resources" + repr(resource) + " running on different nodes: " + repr(ResourceNodes)+" vs "+repr(GrpServedBy) + "(otherRsc = " + repr(lastResource) + ")") Fatal = 1 if self.CM.Env.has_key("SuppressMonitoring") and \ self.CM.Env["SuppressMonitoring"]: continue # Is the resource working correctly ? if not Fatal and len(ResourceNodes) == 1: beforearpchild = popen2.Popen3("date;/sbin/arp -n|cut -c1-15,26-50,75-" , None) beforearpchild.tochild.close() # /dev/null if not resource.IsWorkingCorrectly(ResourceNodes[0]): afterarpchild = popen2.Popen3("/sbin/arp -n|cut -c1-15,26-50,75-" , None) afterarpchild.tochild.close() # /dev/null result.append("Resource " + repr(resource) + " not operating properly." + " Resource is running on " + ResourceNodes[0]); Fatal = 1 self.CM.log("ARP table before failure ========"); for line in beforearpchild.fromchild.readlines(): self.CM.log(line) self.CM.log("ARP table after failure ========"); for line in afterarpchild.fromchild.readlines(): self.CM.log(line) self.CM.log("End of ARP tables ========"); try: beforearpchild.wait() afterarpchild.wait() except OSError: pass afterarpchild.fromchild.close() beforearpchild.fromchild.close() lastResource = resource if (Fatal): result.insert(0, "FATAL") # Kludgy. return result def __call__(self): # # Audit the resources. Since heartbeat doesn't really # know when resource acquisition is complete, we will # poll until things get stable. # # Having a resource duplicately implemented is a Fatal Error # with no tolerance granted. # audresult = self._doaudit() # # Probably the constant below should be a CM parameter. # Then it could be 0 for FailSafe. # Of course, it really depends on what resources # you have in the test suite, and how long it takes # for them to settle. # Recently, we've changed heartbeat so we know better when # resource acquisition is done. # audcount=5; while(audcount > 0): audresult = self._doaudit() if (len(audresult) <= 0 or audresult[0] == "FATAL"): audcount=0 else: audcount = audcount - 1 if (audcount > 0): time.sleep(1) if (len(audresult) > 0): self.CM.log("Fatal Audit error: " + repr(audresult)) return (len(audresult) == 0) def is_applicable(self): if self.CM["Name"] == "heartbeat": return 1 return 0 class HAResourceAudit(ClusterAudit): def __init__(self, cm): self.CM = cm def _RscRunningNodes(self, resource): ResourceNodes = [] for node in self.CM.Env["nodes"]: if self.CM.ShouldBeStatus[node] == self.CM["up"]: if resource.IsRunningOn(node): ResourceNodes.append(node) return ResourceNodes def __call__(self): passed = 1 NodeofRsc = {} NumofInc = {} MaxofInc = {} self.CM.debug("Do Audit HAResourceAudit") #Calculate the count of active nodes up_count = 0; for node in self.CM.Env["nodes"]: if self.CM.ShouldBeStatus[node] == self.CM["up"]: up_count += 1 #Make sure the resouces are running on one and only one node Resources = self.CM.Resources() for resource in Resources : RunningNodes = self._RscRunningNodes(resource) NodeofRsc[resource.rid]=RunningNodes if resource.inc_name == None: #Is the resource served without quorum present? if not self.CM.HasQuorum(None) and len(RunningNodes) != 0 and resource.needs_quorum: self.CM.log("Resource " + repr(resource) + " active without Quorum: " + repr(RunningNodes)) passed = 0 #Is the resource served at all? elif len(RunningNodes) == 0 and self.CM.HasQuorum(None): self.CM.log("Resource " + repr(resource) + " not served anywhere.") passed = 0 # Is the resource served too many times? elif len(RunningNodes) > 1: self.CM.log("Resource " + repr(resource) + " served too many times: " + repr(RunningNodes)) passed = 0 else: if not NumofInc.has_key(resource.inc_name): NumofInc[resource.inc_name]=0 MaxofInc[resource.inc_name]=resource.inc_max running = 1 #Is the resource served without quorum present? if not self.CM.HasQuorum(None) and len(RunningNodes) != 0 and resource.needs_quorum == 1: self.CM.log("Resource " + repr(resource) + " active without Quorum: " + repr(RunningNodes)) passed = 0 #Is the resource served at all? elif len(RunningNodes) == 0 : running = 0 # Is the resource served too many times? elif len(RunningNodes) > 1: self.CM.log("Resource " + repr(resource) + " served too many times: " + repr(RunningNodes)) passed = 0 if running: NumofInc[resource.inc_name] += 1 if self.CM.HasQuorum(None): for inc_name in NumofInc.keys(): if NumofInc[inc_name] != min(up_count, MaxofInc[inc_name]): passed = 0 self.CM.log("Cloned resource "+ str(inc_name) +" has "+ str(NumofInc[inc_name]) +" active instances (max: " + str(MaxofInc[inc_name]) +", active nodes: "+ str(up_count) + ")") Groups = self.CM.ResourceGroups() for group in Groups : group_printed = 0 first_rsc = group[0].rid RunningNodes = NodeofRsc[first_rsc] for rsc in group : if RunningNodes != NodeofRsc[rsc.rid]: passed = 0 if group_printed == 0: group_printed = 1 self.CM.log("Group audit failed for: %s" % repr(group)) if not NodeofRsc[first_rsc] or len(NodeofRsc[first_rsc]) == 0: self.CM.log("* %s not running" % first_rsc) else: self.CM.log("* %s running on %s" %(first_rsc, repr(NodeofRsc[first_rsc]))) if not NodeofRsc[rsc.rid] or len(NodeofRsc[rsc.rid]) == 0: self.CM.log("* %s not running" % rsc.rid) else: self.CM.log("* %s running on %s" %(rsc.rid, repr(NodeofRsc[rsc.rid]))) # Make sure the resouces with "must","placement" constraint # are running on the same node Dependancies = self.CM.Dependencies() for dependency in Dependancies: if dependency["type"] == "placement" and dependency["strength"] == "must": if NodeofRsc[dependency["from"]] != NodeofRsc[dependency["to"]]: print dependency["from"] + " and " + dependency["to"] + " should be run on same node" passed = 0 return passed def is_applicable(self): if self.CM["Name"] == "linux-ha-v2" and self.CM.Env["ResCanStop"] == 0: return 1 return 0 def name(self): return "HAResourceAudit" class CrmdStateAudit(ClusterAudit): def __init__(self, cm): self.CM = cm self.Stats = {"calls":0 , "success":0 , "failure":0 , "skipped":0 , "auditfail":0} def has_key(self, key): return self.Stats.has_key(key) def __setitem__(self, key, value): self.Stats[key] = value def __getitem__(self, key): return self.Stats[key] def incr(self, name): '''Increment (or initialize) the value associated with the given name''' if not self.Stats.has_key(name): self.Stats[name]=0 self.Stats[name] = self.Stats[name]+1 def __call__(self): passed = 1 up_are_down = 0 down_are_up = 0 unstable_list = [] self.CM.debug("Do Audit %s"%self.name()) for node in self.CM.Env["nodes"]: should_be = self.CM.ShouldBeStatus[node] - rc = self.CM.StataCM(node) - if rc: + rc = self.CM.test_node_CM(node) + if rc > 0: if should_be == self.CM["down"]: down_are_up = down_are_up + 1 - if not self.CM.node_stable(node): + if rc == 1: unstable_list.append(node) elif should_be == self.CM["up"]: up_are_down = up_are_down + 1 if len(unstable_list) > 0: passed = 0 self.CM.log("Cluster is not stable: %d (of %d): %s" %(len(unstable_list), self.CM.upcount(), repr(unstable_list))) if up_are_down > 0: passed = 0 self.CM.log("%d (of %d) nodes expected to be up were down." %(up_are_down, len(self.CM.Env["nodes"]))) if down_are_up > 0: passed = 0 self.CM.log("%d (of %d) nodes expected to be down were up." %(down_are_up, len(self.CM.Env["nodes"]))) return passed def name(self): return "CrmdStateAudit" def is_applicable(self): if self.CM["Name"] == "linux-ha-v2": return 1 return 0 class CIBAudit(ClusterAudit): def __init__(self, cm): self.CM = cm self.Stats = {"calls":0 , "success":0 , "failure":0 , "skipped":0 , "auditfail":0} def has_key(self, key): return self.Stats.has_key(key) def __setitem__(self, key, value): self.Stats[key] = value def __getitem__(self, key): return self.Stats[key] def incr(self, name): '''Increment (or initialize) the value associated with the given name''' if not self.Stats.has_key(name): self.Stats[name]=0 self.Stats[name] = self.Stats[name]+1 def __call__(self): self.CM.debug("Do Audit %s"%self.name()) passed = 1 ccm_partitions = self.CM.find_partitions() if len(ccm_partitions) == 0: self.CM.debug("\tNo partitions to audit") return 1 for partition in ccm_partitions: self.CM.debug("\tAuditing CIB consistency for: %s" %partition) partition_passed = 0 if self.audit_cib_contents(partition) == 0: passed = 0 return passed def audit_cib_contents(self, hostlist): passed = 1 first_host = None first_host_xml = "" partition_hosts = hostlist.split() for a_host in partition_hosts: if first_host == None: first_host = a_host first_host_xml = self.store_remote_cib(a_host) #self.CM.debug("Retrieved CIB: %s" % first_host_xml) else: a_host_xml = self.store_remote_cib(a_host) diff_cmd="@sbindir@/crm_diff -c -VV -f -N \'%s\' -O '%s'" % (a_host_xml, first_host_xml) infile, outfile, errfile = os.popen3(diff_cmd) diff_lines = outfile.readlines() for line in diff_lines: if not re.search("", line): passed = 0 self.CM.log("CibDiff[%s-%s]: %s" % (first_host, a_host, line)) else: self.CM.debug("CibDiff[%s-%s] Ignoring: %s" % (first_host, a_host, line)) diff_lines = errfile.readlines() for line in diff_lines: passed = 0 self.CM.log("CibDiff[%s-%s] ERROR: %s" % (first_host, a_host, line)) return passed def store_remote_cib(self, node): combined = "" first_line = 1 extra_debug = 0 #self.CM.debug("\tRetrieving CIB from: %s" % node) lines = self.CM.rsh.readlines(node, self.CM["CibQuery"]) if extra_debug: self.CM.debug("Start Cib[%s]" % node) for line in lines: combined = combined + line[:-1] if first_line: self.CM.debug("[Cib]" + line) first_line = 0 elif extra_debug: self.CM.debug("[Cib]" + line) if extra_debug: self.CM.debug("End Cib[%s]" % node) #self.CM.debug("Complete CIB: %s" % combined) return combined def name(self): return "CibAudit" def is_applicable(self): if self.CM["Name"] == "linux-ha-v2": return 1 return 0 class PartitionAudit(ClusterAudit): def __init__(self, cm): self.CM = cm self.Stats = {"calls":0 , "success":0 , "failure":0 , "skipped":0 , "auditfail":0} self.NodeEpoche={} self.NodeState={} self.NodeQuorum={} def has_key(self, key): return self.Stats.has_key(key) def __setitem__(self, key, value): self.Stats[key] = value def __getitem__(self, key): return self.Stats[key] def incr(self, name): '''Increment (or initialize) the value associated with the given name''' if not self.Stats.has_key(name): self.Stats[name]=0 self.Stats[name] = self.Stats[name]+1 def __call__(self): self.CM.debug("Do Audit %s"%self.name()) passed = 1 ccm_partitions = self.CM.find_partitions() if ccm_partitions == None or len(ccm_partitions) == 0: return 1 if len(ccm_partitions) > 1: self.CM.log("Warn: %d cluster partitions detected:" %len(ccm_partitions)) for partition in ccm_partitions: self.CM.log("\t %s" %partition) for partition in ccm_partitions: partition_passed = 0 if self.audit_partition(partition) == 0: passed = 0 return passed def trim_string(self, avalue): if not avalue: return None if len(avalue) > 1: return avalue[:-1] def trim2int(self, avalue): if not avalue: return None if len(avalue) > 1: return int(avalue[:-1]) def audit_partition(self, partition): passed = 1 dc_found = [] dc_allowed_list = [] lowest_epoche = None node_list = partition.split() self.CM.debug("Auditing partition: %s" %(partition)) for node in node_list: if self.CM.ShouldBeStatus[node] != self.CM["up"]: self.CM.log("Warn: Node %s appeared out of nowhere" %(node)) self.CM.ShouldBeStatus[node] = self.CM["up"] # not in itself a reason to fail the audit (not what we're # checking for in this audit) self.NodeState[node] = self.CM.rsh.readaline( node, self.CM["StatusCmd"]%node) self.NodeEpoche[node] = self.CM.rsh.readaline( node, self.CM["EpocheCmd"]) self.NodeQuorum[node] = self.CM.rsh.readaline( node, self.CM["QuorumCmd"]) self.NodeState[node] = self.trim_string(self.NodeState[node]) self.NodeEpoche[node] = self.trim2int(self.NodeEpoche[node]) self.NodeQuorum[node] = self.trim_string(self.NodeQuorum[node]) if not self.NodeEpoche[node]: self.CM.log("Warn: Node %s dissappeared: cant determin epoche" %(node)) self.CM.ShouldBeStatus[node] = self.CM["down"] # not in itself a reason to fail the audit (not what we're # checking for in this audit) elif lowest_epoche == None or self.NodeEpoche[node] < lowest_epoche: lowest_epoche = self.NodeEpoche[node] if not lowest_epoche: self.CM.log("Lowest epoche not determined in %s" % (partition)) passed = 0 for node in node_list: if self.CM.ShouldBeStatus[node] == self.CM["up"]: if self.CM.is_node_dc(node, self.NodeState[node]): dc_found.append(node) if self.NodeEpoche[node] == lowest_epoche: self.CM.debug("%s: OK" % node) elif not self.NodeEpoche[node]: self.CM.debug("Check on %s ignored: no node epoche" % node) elif not lowest_epoche: self.CM.debug("Check on %s ignored: no lowest epoche" % node) else: self.CM.log("DC %s is not the oldest node (%d vs. %d)" %(node, self.NodeEpoche[node], lowest_epoche)) passed = 0 if len(dc_found) == 0: self.CM.log("DC not found on any of the %d allowed nodes: %s (of %s)" %(len(dc_allowed_list), str(dc_allowed_list), str(node_list))) elif len(dc_found) > 1: self.CM.log("%d DCs (%s) found in cluster partition: %s" %(len(dc_found), str(dc_found), str(node_list))) passed = 0 elif self.CM.Env["CIBResource"] == 1 and self.NodeQuorum[dc_found[0]]: if self.audit_dc_resources(node_list, dc_found) == 0: passed = 0 Resources = self.CM.Resources() for node in node_list: if self.CM.ShouldBeStatus[node] == self.CM["up"]: for resource in Resources: if resource.rid == "rsc_"+node: if resource.IsRunningOn(node) == 0: self.CM.log("Node %s is not running its own resource" %(node)) passed = 0 elif self.CM.Env["CIBResource"] == 1: # no quorum means no resource management self.CM.debug("Not auditing resources - no quorum") if passed == 0: for node in node_list: if self.CM.ShouldBeStatus[node] == self.CM["up"]: self.CM.log("epoche %s : %s" %(self.NodeEpoche[node], self.NodeState[node])) return passed def audit_dc_resources(self, node_list, dc_list): passed = 1 Resources = self.CM.Resources() for resource in Resources: if resource.rid == "DcIPaddr": self.CM.debug("Auditing resource: %s" %(resource)) # All DCs are running the resource for dc in dc_list: if self.NodeQuorum[dc]: if resource.IsRunningOn(dc) == 0: self.CM.log("Resource %s not running on DC: %s" %(resource, dc)) passed = 0 # All nodes running the resource are DCs for node in node_list: if resource.IsRunningOn(node): if self.CM.is_node_dc(node, self.NodeState[node]) == 0: self.CM.log("Resource %s is running on non-DC node %s" %("DcIPaddr", node)) passed = 0 return passed def name(self): return "PartitionAudit" def is_applicable(self): if self.CM["Name"] == "linux-ha-v2": return 1 return 0 AllAuditClasses.append(CrmdStateAudit) AllAuditClasses.append(PartitionAudit) AllAuditClasses.append(ResourceAudit) AllAuditClasses.append(HAResourceAudit) AllAuditClasses.append(CIBAudit) def AuditList(cm): result = [] for auditclass in AllAuditClasses: result.append(auditclass(cm)) return result diff --git a/cts/CTSlab.py.in b/cts/CTSlab.py.in index 0cba94249f..34029af285 100755 --- a/cts/CTSlab.py.in +++ b/cts/CTSlab.py.in @@ -1,794 +1,804 @@ #!@PYTHON@ '''CTS: Cluster Testing System: Lab environment module ''' __copyright__=''' Copyright (C) 2001,2005 Alan Robertson Licensed under the GNU GPL. ''' # # 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 program 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 program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. from UserDict import UserDict import sys, time, types, string, syslog, random, os, string, signal, traceback from CTS import ClusterManager from CM_hb import HeartbeatCM from CTStests import BSC_AddResource from socket import gethostbyname_ex tests = None cm = None old_handler = None def sig_handler(signum, frame) : if cm != None: cm.log("Interrupted by signal %d"%signum) if signum == 10 and tests != None : tests.summarize() if signum == 15 : sys.exit(1) class ResetMechanism: def reset(self, node): raise ValueError("Abstract class member (reset)") class Stonith(ResetMechanism): def __init__(self, sttype="external/ssh", parm=None , path="@sbindir@/stonith"): self.pathname=path self.configstring=parm self.stonithtype=sttype def reset(self, node): if self.configstring == None : config=node else: config=self.configstring cmdstring = "%s -t '%s' -p '%s' '%s' 2>/dev/null" % (self.pathname , self.stonithtype, config, node) return (os.system(cmdstring) == 0) class Stonithd(ResetMechanism): def __init__(self, nodes, sttype = 'external/ssh'): self.sttype = sttype self.nodes = nodes self.query_cmd_pat = '@libdir@/heartbeat/stonithdtest/apitest 0 %s 20000 0' self.reset_cmd_pat = '@libdir@/heartbeat/stonithdtest/apitest 1 %s 20000 0' self.poweron_cmd_pat = '@libdir@/heartbeat/stonithdtest/apitest 2 %s 20000 0' self.poweroff_cmd_pat= '@libdir@/heartbeat/stonithdtest/apitest 3 %s 20000 0' self.lrmd_add_pat = '@libdir@/heartbeat/lrmadmin -A %s stonith ' + sttype + ' NULL hostlist=%s' self.lrmd_start_pat = '@libdir@/heartbeat/lrmadmin -E %s start 0 0 EVERYTIME' self.lrmd_stop_pat = '@libdir@/heartbeat/lrmadmin -E %s stop 0 0 EVERYTIME' self.lrmd_del_pat = '@libdir@/heartbeat/lrmadmin -D %s' self.rsc_id = 'my_stonithd_id' self.command = "@SSH@ -l root -n -x" self.command_noblock = "@SSH@ -f -l root -n -x" self.stonithd_started_nodes = [] self.fail_reason = '' def _remote_exec(self, node, cmnd): return (os.system("%s %s %s > /dev/null" % (self.command, node, cmnd)) == 0) def _remote_readlines(self, node, cmnd): f = os.popen("%s %s %s" % (self.command, node, cmnd)) return f.readlines() def _stonithd_started(self, node): return node in self.stonithd_started_nodes def _start_stonithd(self, node, hosts): hostlist = string.join(hosts, ',') lrmd_add_cmd = self.lrmd_add_pat % (self.rsc_id, hostlist) ret = self._remote_exec(node, lrmd_add_cmd) if not ret:return ret lrmd_start_cmd = self.lrmd_start_pat % self.rsc_id ret = self._remote_exec(node, lrmd_start_cmd) if not ret:return ret self.stonithd_started_nodes.append(node) return 1 def _stop_stonithd(self, node): lrmd_stop_cmd = self.lrmd_stop_pat % self.rsc_id ret = self._remote_exec(node, lrmd_stop_cmd) if not ret:return ret lrmd_del_cmd = self.lrmd_del_pat % self.rsc_id ret = self._remote_exec(node, lrmd_del_cmd) if not ret:return ret self.stonithd_started_nodes.remove(node) return 1 def _do_stonith(self, init_node, target_node, action): stonithd_started = self._stonithd_started(init_node) if not stonithd_started: ret = self._start_stonithd(init_node, [target_node]) if not ret: self.fail_reason = "failed to start stonithd on node %s" % init_node return ret command = "" if action == "RESET": command = self.reset_cmd_pat % target_node elif action == "POWEROFF": command = self.poweroff_cmd_pat % target_node elif action == "POWERON": command = self.poweron_cmd_pat % target_node else: self.fail_reason = "unknown opration type %s" % action return 0 lines = self._remote_readlines(init_node, command) result = "".join(lines) if not stonithd_started: self._stop_stonithd(init_node) index = result.find("result=0") if index == -1: self.fail_reason = "unexpected stonithd status: %s" % result return 0 return 1 # Should we randomly choose a node as init_node here if init_node not specified? def reset(self, init_node, target_node): return self._do_stonith(init_node, target_node, "RESET") def poweron(self, init_node, target_node): return self._do_stonith(init_node, target_node, "POWERON") def poweroff(self, init_node, target_node): return self._do_stonith(init_node, target_node, "POWEROFF") class Logger: TimeFormat = "%b %d %H:%M:%S\t" def __call__(self, lines): raise ValueError("Abstract class member (__call__)") def write(self, line): return self(line.rstrip()) def writelines(self, lines): for s in lines: self.write(s) return 1 def flush(self): return 1 def isatty(self): return None class SysLog(Logger): # http://docs.python.org/lib/module-syslog.html defaultsource="CTS" defaultfacility= syslog.LOG_LOCAL7 map = { "kernel": syslog.LOG_KERN, "user": syslog.LOG_USER, "mail": syslog.LOG_MAIL, "daemon": syslog.LOG_MAIL, "auth": syslog.LOG_AUTH, "lpr": syslog.LOG_LPR, "news": syslog.LOG_NEWS, "uucp": syslog.LOG_UUCP, "cron": syslog.LOG_CRON, "local0": syslog.LOG_LOCAL0, "local1": syslog.LOG_LOCAL1, "local2": syslog.LOG_LOCAL2, "local3": syslog.LOG_LOCAL3, "local4": syslog.LOG_LOCAL4, "local5": syslog.LOG_LOCAL5, "local6": syslog.LOG_LOCAL6, "local7": syslog.LOG_LOCAL7, } def __init__(self, labinfo): if labinfo.has_key("syslogsource"): self.source=labinfo["syslogsource"] else: self.source=SysLog.defaultsource if labinfo.has_key("SyslogFacility"): self.facility=labinfo["SyslogFacility"] if SysLog.map.has_key(self.facility): self.facility=SysLog.map[self.facility] else: self.facility=SysLog.defaultfacility syslog.openlog(self.source, 0, self.facility) def setfacility(self, facility): self.facility = facility if SysLog.map.has_key(self.facility): self.facility=SysLog.map[self.facility] syslog.closelog() syslog.openlog(self.source, 0, self.facility) def __call__(self, lines): if isinstance(lines, types.StringType): syslog.syslog(lines) else: for line in lines: syslog.syslog(line) def name(self): return "Syslog" class StdErrLog(Logger): def __init__(self, labinfo): pass def __call__(self, lines): t = time.strftime(Logger.TimeFormat, time.localtime(time.time())) if isinstance(lines, types.StringType): sys.__stderr__.writelines([t, lines, "\n"]) else: for line in lines: sys.__stderr__.writelines([t, line, "\n"]) sys.__stderr__.flush() def name(self): return "StdErrLog" class FileLog(Logger): def __init__(self, labinfo, filename=None): if filename == None: filename=labinfo["LogFileName"] self.logfile=filename import os self.hostname = os.uname()[1]+" " self.source = "CTS: " def __call__(self, lines): fd = open(self.logfile, "a") t = time.strftime(Logger.TimeFormat, time.localtime(time.time())) if isinstance(lines, types.StringType): fd.writelines([t, self.hostname, self.source, lines, "\n"]) else: for line in lines: fd.writelines([t, self.hostname, self.source, line, "\n"]) fd.close() def name(self): return "FileLog" class CtsLab(UserDict): '''This class defines the Lab Environment for the Cluster Test System. It defines those things which are expected to change from test environment to test environment for the same cluster manager. It is where you define the set of nodes that are in your test lab what kind of reset mechanism you use, etc. This class is derived from a UserDict because we hold many different parameters of different kinds, and this provides provide a uniform and extensible interface useful for any kind of communication between the user/administrator/tester and CTS. At this point in time, it is the intent of this class to model static configuration and/or environmental data about the environment which doesn't change as the tests proceed. Well-known names (keys) are an important concept in this class. The HasMinimalKeys member function knows the minimal set of well-known names for the class. The following names are standard (well-known) at this time: nodes An array of the nodes in the cluster reset A ResetMechanism object logger An array of objects that log strings... CMclass The type of ClusterManager we are running (This is a class object, not a class instance) RandSeed Random seed. It is a triple of bytes. (optional) HAdir Base directory for HA installation The CTS code ignores names it doesn't know about/need. The individual tests have access to this information, and it is perfectly acceptable to provide hints, tweaks, fine-tuning directions or other information to the tests through this mechanism. ''' def __init__(self, nodes): self.data = {} self["nodes"] = nodes self.MinimalKeys=["nodes", "reset", "logger", "CMclass", "HAdir"] def HasMinimalKeys(self): 'Return TRUE if our object has the minimal set of keys/values in it' result = 1 for key in self.MinimalKeys: if not self.has_key(key): result = None return result def SupplyDefaults(self): if not self.has_key("logger"): self["logger"] = (SysLog(self), StdErrLog(self)) if not self.has_key("reset"): self["reset"] = Stonith() if not self.has_key("CMclass"): self["CMclass"] = HeartbeatCM if not self.has_key("HAdir"): self["HAdir"] = "@sysconfdir@/ha.d" if not self.has_key("LogFileName"): self["LogFileName"] = "/var/log/ha-log" if not self.has_key("logfacility"): LogFacility = "local7" # # Now set up our random number generator... # self.RandomGen = random.Random() # Get a random seed for the random number generator. if self.has_key("RandSeed"): randseed = self["RandSeed"] self.log("Random seed is: " + str(randseed)) self.RandomGen.seed(str(randseed)) else: randseed = int(time.time()) self.log("Random seed is: " + str(randseed)) self.RandomGen.seed(str(randseed)) def log(self, args): "Log using each of the supplied logging methods" for logfcn in self._logfunctions: logfcn(string.strip(args)) def debug(self, args): "Log using each of the supplied logging methods" for logfcn in self._logfunctions: if logfcn.name() != "StdErrLog": logfcn(string.strip(args)) def __setitem__(self, key, value): '''Since this function gets called whenever we modify the dictionary (object), we can (and do) validate those keys that we know how to validate. For the most part, we know how to validate the "MinimalKeys" elements. ''' # # List of nodes in the system # if key == "nodes": self.Nodes = {} for node in value: # I don't think I need the IP address, etc. but this validates # the node name against /etc/hosts and/or DNS, so it's a # GoodThing(tm). self.Nodes[node] = gethostbyname_ex(node) # # Reset Mechanism # elif key == "reset": if not issubclass(value.__class__, ResetMechanism): raise ValueError("'reset' Value must be a subclass" " of ResetMechanism") # # List of Logging Mechanism(s) # elif key == "logger": if len(value) < 1: raise ValueError("Must have at least one logging mechanism") for logger in value: if not callable(logger): raise ValueError("'logger' elements must be callable") self._logfunctions = value # # Cluster Manager Class # elif key == "CMclass": if not issubclass(value, ClusterManager): raise ValueError("'CMclass' must be a subclass of" " ClusterManager") # # Initial Random seed... # #elif key == "RandSeed": # if len(value) != 3: # raise ValueError("'Randseed' must be a 3-element list/tuple") # for elem in value: # if not isinstance(elem, types.IntType): # raise ValueError("'Randseed' list must all be ints") self.data[key] = value def IsValidNode(self, node): 'Return TRUE if the given node is valid' return self.Nodes.has_key(node) def __CheckNode(self, node): "Raise a ValueError if the given node isn't valid" if not self.IsValidNode(node): raise ValueError("Invalid node [%s] in CheckNode" % node) def RandomNode(self): '''Choose a random node from the cluster''' return self.RandomGen.choice(self["nodes"]) def ResetNode(self, node): "Reset a node, (normally) using a hardware mechanism" self.__CheckNode(node) return self["reset"].reset(node) def ResetNode2(self, init_node, target_node, reasons): self.__CheckNode(target_node) stonithd = Stonithd(self["nodes"]) ret = stonithd.reset(init_node, target_node) if not ret: reasons.append(stonithd.fail_reason) return ret def usage(arg): print "Illegal argument " + arg print "usage: " + sys.argv[0] \ + " --directory config-directory" \ + " -D config-directory" \ + " --logfile system-logfile-name" \ + " --trunc (truncate logfile before starting)" \ + " -L system-logfile-name" \ + " --limit-nodes maxnumnodes" \ + " --xmit-loss lost-rate(0.0-1.0)" \ + " --recv-loss lost-rate(0.0-1.0)" \ + " --suppressmonitoring" \ + " --syslog-facility syslog-facility" \ + " --facility syslog-facility" \ + " --choose testcase-name" \ + " --test-ip-base ip" \ + + " --oprofile \"whitespace separated list of nodes to oprofile\"" \ + " (-2 |"\ + " -v2 |"\ + " --crm |"\ + " --classic)"\ + " --resource-can-stop" \ + " --stonith (1 | 0 | yes | no)" \ + " --standby (1 | 0 | yes | no)" \ + " --fencing (1 | 0 | yes | no)" \ + " --suppress_cib_writes (1 | 0 | yes | no)" \ + " -lstests" \ + " --seed" \ + " [number-of-iterations]" sys.exit(1) # # A little test code... # if __name__ == '__main__': from CTSaudits import AuditList from CTStests import TestList,RandomTests from CTS import Scenario, InitClusterManager, PingFest, PacketLoss, BasicSanityCheck import CM_hb HAdir = "/etc/ha.d" LogFile = "/var/log/ha-log-local7" DoStonith = 1 DoStandby = 1 DoFencing = 1 NumIter = 500 SuppressMonitoring = None Version = 1 CIBfilename = None CIBResource = 0 ClobberCIB = 0 LimitNodes = 0 TestCase = None LogFacility = None TruncateLog = 0 ResCanStop = 0 XmitLoss = "0.0" RecvLoss = "0.0" IPBase = "127.0.0.10" SuppressCib = 1 DoBSC = 0 ListTests = 0 HaveSeed = 0 + oprofile = None + # # The values of the rest of the parameters are now properly derived from # the configuration files. # # Stonith is configurable because it's slow, I have a few machines which # don't reboot very reliably, and it can mild damage to your machine if # you're using a real power switch. # # Standby is configurable because the test is very heartbeat specific # and I haven't written the code to set it properly yet. Patches are # being accepted... # Set the signal handler signal.signal(15, sig_handler) signal.signal(10, sig_handler) # Process arguments... skipthis=None args=sys.argv[1:] for i in range(0, len(args)): if skipthis: skipthis=None continue elif args[i] == "-D" or args[i] == "--directory": skipthis=1 HAdir = args[i+1] elif args[i] == "-l" or args[i] == "--limit-nodes": skipthis=1 LimitNodes = int(args[i+1]) elif args[i] == "-r": CIBResource = 1 elif args[i] == "-L" or args[i] == "--logfile": skipthis=1 LogFile = args[i+1] elif args[i] == "--test-ip-base": skipthis=1 IPBase = args[i+1] + elif args[i] == "--oprofile": + skipthis=1 + oprofile = args[i+1].split(' ') elif args[i] == "--trunc": TruncateLog=1 elif args[i] == "-v2": Version=2 elif args[i] == "-lstests": ListTests=1 elif args[i] == "--stonith": skipthis=1 if args[i+1] == "1" or args[i+1] == "yes": DoStonith=1 elif args[i+1] == "0" or args[i+1] == "no": DoStonith=0 else: usage(args[i+1]) elif args[i] == "--suppress-cib-writes": skipthis=1 if args[i+1] == "1" or args[i+1] == "yes": SuppressCib=1 elif args[i+1] == "0" or args[i+1] == "no": SuppressCib=0 else: usage(args[i+1]) elif args[i] == "--bsc": DoBSC=1 elif args[i] == "--standby": skipthis=1 if args[i+1] == "1" or args[i+1] == "yes": DoStandby=1 elif args[i+1] == "0" or args[i+1] == "no": DoStandby=0 else: usage(args[i+1]) elif args[i] == "--fencing": skipthis=1 if args[i+1] == "1" or args[i+1] == "yes": DoFencing=1 elif args[i+1] == "0" or args[i+1] == "no": DoFencing=0 else: usage(args[i+1]) elif args[i] == "--suppressmonitoring": SuppressMonitoring = 1 elif args[i] == "--resource-can-stop": ResCanStop = 1 elif args[i] == "-2" or args[i] == "--crm": Version = 2 elif args[i] == "-1" or args[i] == "--classic": Version = 1 elif args[i] == "--clobber-cib" or args[i] == "-c": ClobberCIB = 1 elif args[i] == "--cib-filename": skipthis=1 CIBfilename = args[i+1] elif args[i] == "--xmit-loss": try: float(args[i+1]) except ValueError: print ("--xmit-loss parameter should be float") usage(args[i+1]) skipthis=1 XmitLoss = args[i+1] elif args[i] == "--recv-loss": try: float(args[i+1]) except ValueError: print ("--recv-loss parameter should be float") usage(args[i+1]) skipthis=1 RecvLoss = args[i+1] elif args[i] == "--choose": skipthis=1 TestCase = args[i+1] elif args[i] == "--syslog-facility" or args[i] == "--facility": skipthis=1 LogFacility = args[i+1] elif args[i] == "--seed": skipthis=1 Seed=args[i+1] HaveSeed = 1 else: NumIter=int(args[i]) - + + if not oprofile: + oprofile = [] + # # This reading of HBconfig here is ugly, and I suppose ought to # be done by the Cluster manager. This would probably mean moving the # list of cluster nodes into the ClusterManager class. A good thought # for our Copious Spare Time in the future... # config = CM_hb.HBConfig(HAdir) node_list = config.Parameters["node"] if DoBSC: NumIter = 2 Version = 2 LimitNodes = 1 if LogFacility == None: if config.Parameters.has_key("logfacility"): LogFacility = config.Parameters["logfacility"][0] else: LogFacility = "local7" if LimitNodes > 0: if len(node_list) > LimitNodes: print("Limiting the number of nodes configured=%d (max=%d)" %(len(node_list), LimitNodes)) while len(node_list) > LimitNodes: node_list.pop(len(node_list)-1) # alt_list = [] # for node in node_list: # alt_list.append(string.lower(node)) # node_list = alt_list Environment = CtsLab(node_list) Environment["HAdir"] = HAdir Environment["ClobberCIB"] = ClobberCIB Environment["CIBfilename"] = CIBfilename Environment["CIBResource"] = CIBResource Environment["LogFileName"] = LogFile Environment["DoStonith"] = DoStonith Environment["SyslogFacility"] = LogFacility Environment["DoStandby"] = DoStandby Environment["DoFencing"] = DoFencing Environment["ResCanStop"] = ResCanStop Environment["SuppressMonitoring"] = SuppressMonitoring Environment["XmitLoss"] = XmitLoss Environment["RecvLoss"] = RecvLoss Environment["IPBase"] = IPBase Environment["SuppressCib"] = SuppressCib Environment["DoBSC"] = 0 Environment["use_logd"] = 0 Environment["logfacility"] = LogFacility + Environment["oprofile"] = oprofile if config.Parameters.has_key("use_logd"): Environment["use_logd"] = 1 if Version == 2: from CM_LinuxHAv2 import LinuxHAv2 Environment['CMclass']=LinuxHAv2 if HaveSeed: Environment["RandSeed"] = Seed Environment["reset"] = Stonith(sttype="external/ssh", parm=string.join(node_list, " ")) if DoBSC: Environment["DoBSC"] = 1 Environment["ClobberCIB"] = 1 Environment["CIBResource"] = 0 Environment["logger"] = (FileLog(Environment), StdErrLog(Environment)) scenario = Scenario([ BasicSanityCheck(Environment) ]) else: scenario = Scenario( [ InitClusterManager(Environment), PacketLoss(Environment)]) Environment.SupplyDefaults() # Your basic start up the world type of test scenario... #scenario = Scenario( #[ InitClusterManager(Environment) #, PingFest(Environment)]) # Create the Cluster Manager object cm = Environment['CMclass'](Environment) if TruncateLog: cm.log("Truncating %s" % LogFile) lf = open(LogFile, "w"); if lf != None: lf.truncate(0) lf.close() cm.log(">>>>>>>>>>>>>>>> BEGINNING " + repr(NumIter) + " TESTS ") cm.log("HA configuration directory: " + Environment["HAdir"]) cm.log("System log files: " + Environment["LogFileName"]) cm.log("Enable Stonith: " + ("%d" % Environment["DoStonith"])) cm.log("Enable Fencing: " + ("%d" % Environment["DoFencing"])) cm.log("Enable Standby: " + ("%d" % Environment["DoStandby"])) if Environment.has_key("SuppressMonitoring") \ and Environment["SuppressMonitoring"]: cm.log("Resource Monitoring is disabled") cm.log("Cluster nodes: ") for node in config.Parameters["node"]: (rc, lines) = cm.rsh.remote_py(node, "os", "system", "@sbindir@/crm_uuid") if not lines: cm.log(" * %s: __undefined_uuid__" % node) else: out=lines[0] out = out[:-1] cm.log(" * %s: %s" % (node, out)) Audits = AuditList(cm) Tests = [] if Environment["DoBSC"]: test = BSC_AddResource(cm) Tests.append(test) elif TestCase != None: for test in TestList(cm): if test.name == TestCase: Tests.append(test) if Tests == []: usage("--choose: No applicable/valid tests chosen") else: Tests = TestList(cm) if ListTests == 1 : cm.log("Total %d tests"%len(Tests)) for test in Tests : cm.log(str(test.name)); sys.exit(0) tests = RandomTests(scenario, cm, Tests, Audits) Environment.RandomTests = tests try : overall, detailed = tests.run(NumIter) except : cm.Env.log("Exception by %s" % sys.exc_info()[0]) for logmethod in Environment["logger"]: traceback.print_exc(50, logmethod) tests.summarize() if tests.Stats["failure"] > 0: sys.exit(tests.Stats["failure"]) elif tests.Stats["success"] != NumIter: cm.Env.log("No failure count but success != requested iterations") sys.exit(1) diff --git a/cts/CTStests.py.in b/cts/CTStests.py.in index e2542b737e..b78f3de0d6 100644 --- a/cts/CTStests.py.in +++ b/cts/CTStests.py.in @@ -1,2364 +1,2372 @@ #!@PYTHON@ '''CTS: Cluster Testing System: Tests module There are a few things we want to do here: ''' __copyright__=''' Copyright (C) 2000, 2001 Alan Robertson Licensed under the GNU GPL. Add RecourceRecover testcase Zhao Kai ''' # # 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 program 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 program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # SPECIAL NOTE: # # Tests may NOT implement any cluster-manager-specific code in them. # EXTEND the ClusterManager object to provide the base capabilities # the test needs if you need to do something that the current CM classes # do not. Otherwise you screw up the whole point of the object structure # in CTS. # # Thank you. # import CTS from CM_hb import HBConfig import CTSaudits import time, os, re, types, string, tempfile, sys from CTSaudits import * from stat import * from xml.dom.minidom import * # List of all class objects for tests which we ought to # consider running. class RandomTests: ''' A collection of tests which are run at random. ''' def __init__(self, scenario, cm, tests, Audits): self.CM = cm self.Env = cm.Env self.Scenario = scenario self.Tests = [] self.Audits = [] self.ns=CTS.NodeStatus(self.Env) for test in tests: if not issubclass(test.__class__, CTSTest): raise ValueError("Init value must be a subclass of CTSTest") if test.is_applicable(): self.Tests.append(test) if not scenario.IsApplicable(): raise ValueError("Scenario not applicable in" " given Environment") self.Stats = {"success":0, "failure":0, "BadNews":0} self.IndividualStats= {} for audit in Audits: if not issubclass(audit.__class__, ClusterAudit): raise ValueError("Init value must be a subclass of ClusterAudit") if audit.is_applicable(): self.Audits.append(audit) def incr(self, name): '''Increment (or initialize) the value associated with the given name''' if not self.Stats.has_key(name): self.Stats[name]=0 self.Stats[name] = self.Stats[name]+1 def audit(self, BadNews, test): errcount=0 BadNewsDebug=0 #BadNews.debug=1 while errcount < 1000: if BadNewsDebug: print "Looking for BadNews" match=BadNews.look(0) if match: if BadNewsDebug: print "BadNews found: "+match add_err = 1 ignorelist = [] if test: ignorelist=test.errorstoignore() ignorelist.append(" CTS: ") ignorelist.append("BadNews:") for ignore in ignorelist: if re.search(ignore, match): if BadNewsDebug: print "Ignoring:"+match+" (pattern: "+ignore+")" add_err = 0 if add_err == 1: ignorelist=self.CM.errorstoignore() for ignore in ignorelist: if re.search(ignore, match): if BadNewsDebug: print "Ignoring:"+match+" (pattern: "+ignore+")" add_err = 0 if add_err == 1: self.CM.log("BadNews: " + match) self.incr("BadNews") errcount=errcount+1 else: break else: self.CM.log("Big problems. Shutting down.") self.CM.stopall() self.summarize() raise ValueError("Looks like we hit the jackpot! :-)") for audit in self.Audits: if not audit(): self.CM.log("Audit " + audit.name() + " FAILED.") self.incr("auditfail") if test: test.incr("auditfail") def summarize(self): self.CM.log("****************") self.CM.log("Overall Results:" + repr(self.Stats)) self.CM.log("****************") self.CM.log("Detailed Results") for test in self.Tests: self.CM.log("Test %s: \t%s" %(test.name, repr(test.Stats))) self.CM.log("<<<<<<<<<<<<<<<< TESTS COMPLETED") def run(self, max=1): ( ''' Set up the given scenario, then run the selected tests at random for the selected number of iterations. ''') BadNews=CTS.LogWatcher(self.CM["LogFileName"], self.CM["BadRegexes"] , timeout=0) BadNews.setwatch() + self.CM.ns.WaitForAllNodesToComeUp(self.CM.Env["nodes"]) + + for node in self.CM.Env["nodes"]: + if node in self.CM.Env["oprofile"]: + self.CM.log("Enabling oprofile on %s" % node) + self.CM.rsh.remote_py(node, "os", "system", "opcontrol --init") + self.CM.rsh.remote_py(node, "os", "system", "opcontrol --start") + if not self.Scenario.SetUp(self.CM): return None - self.CM.ns.WaitForAllNodesToComeUp(self.CM.Env["nodes"]) + for node in self.CM.Env["nodes"]: + if node in self.CM.Env["oprofile"]: + self.CM.rsh.remote_py( + node, "os", "system", "opcontrol --save=cts.setup") + testcount=1 time.sleep(30) # This makes sure everything is stabilized before starting... self.audit(BadNews, None) while testcount <= max: test = self.Env.RandomGen.choice(self.Tests) # Some tests want a node as an argument. nodechoice = self.Env.RandomNode() #logsize = os.stat(self.CM["LogFileName"])[ST_SIZE] #self.CM.log("Running test %s (%s) \t[%d : %d]" # % (test.name, nodechoice, testcount, logsize)) self.CM.log("Running test %s (%s) \t[%d]" % (test.name, nodechoice, testcount)) testcount = testcount + 1 starttime=time.time() test.starttime=starttime ret=test(nodechoice) + for node in self.CM.Env["nodes"]: + if node in self.CM.Env["oprofile"]: + self.CM.rsh.remote_py( + node, "os", "system", + "opcontrol --save=cts.%d" % (testcount-1)) + if ret: self.incr("success") else: self.incr("failure") self.CM.log("Test %s (%s) \t[FAILED]" %(test.name,nodechoice)) # Better get the current info from the cluster... self.CM.statall() # Make sure logging is working and we have enough disk space... if not self.CM.Env["DoBSC"]: - self.CM.ns.WaitForAllNodesToComeUp(self.CM.Env["nodes"]) if not self.CM.TestLogging(): sys.exit(1) if not self.CM.CheckDf(): sys.exit(1) stoptime=time.time() elapsed_time = stoptime - starttime test_time = stoptime - test.starttime if not test.has_key("min_time"): test["elapsed_time"] = elapsed_time test["min_time"] = test_time test["max_time"] = test_time else: test["elapsed_time"] = test["elapsed_time"] + elapsed_time if test_time < test["min_time"]: test["min_time"] = test_time if test_time > test["max_time"]: test["max_time"] = test_time self.audit(BadNews, test) self.Scenario.TearDown(self.CM) + for node in self.CM.Env["nodes"]: + if node in self.CM.Env["oprofile"]: + self.CM.log("Disabling oprofile on %s" % node) + self.CM.rsh.remote_py(node, "os", "system", "opcontrol --shutdown") + self.audit(BadNews, None) for test in self.Tests: self.IndividualStats[test.name] = test.Stats return self.Stats, self.IndividualStats AllTestClasses = [ ] class CTSTest: ''' A Cluster test. We implement the basic set of properties and behaviors for a generic cluster test. Cluster tests track their own statistics. We keep each of the kinds of counts we track as separate {name,value} pairs. ''' def __init__(self, cm): #self.name="the unnamed test" self.Stats = {"calls":0 , "success":0 , "failure":0 , "skipped":0 , "auditfail":0} # if not issubclass(cm.__class__, ClusterManager): # raise ValueError("Must be a ClusterManager object") self.CM = cm self.timeout=120 self.starttime=0 def has_key(self, key): return self.Stats.has_key(key) def __setitem__(self, key, value): self.Stats[key] = value def __getitem__(self, key): return self.Stats[key] def incr(self, name): '''Increment (or initialize) the value associated with the given name''' if not self.Stats.has_key(name): self.Stats[name]=0 self.Stats[name] = self.Stats[name]+1 def failure(self, reason="none"): '''Increment the failure count''' self.incr("failure") self.CM.log("Test " + self.name + " failed [reason:" + reason + "]") return None def success(self): '''Increment the success count''' self.incr("success") return 1 def skipped(self): '''Increment the skipped count''' self.incr("skipped") return 1 def __call__(self, node): '''Perform the given test''' raise ValueError("Abstract Class member (__call__)") self.incr("calls") return self.failure() def is_applicable(self): '''Return TRUE if we are applicable in the current test configuration''' raise ValueError("Abstract Class member (is_applicable)") return 1 def canrunnow(self): '''Return TRUE if we can meaningfully run right now''' return 1 def errorstoignore(self): '''Return list of errors which are 'normal' and should be ignored''' return [] ################################################################### class StopTest(CTSTest): ################################################################### '''Stop (deactivate) the cluster manager on a node''' def __init__(self, cm): CTSTest.__init__(self, cm) self.name="Stop" self.uspat = self.CM["Pat:We_stopped"] self.thempat = self.CM["Pat:They_stopped"] def __call__(self, node): '''Perform the 'stop' test. ''' self.incr("calls") if self.CM.ShouldBeStatus[node] != self.CM["up"]: return self.skipped() patterns = [] # Technically we should always be able to notice ourselves stopping patterns.append(self.CM["Pat:We_stopped"] % node) if self.CM.Env["use_logd"]: patterns.append(self.CM["Pat:Logd_stopped"] % node) # Any active node needs to notice this one left # NOTE: This wont work if we have multiple partitions for other in self.CM.Env["nodes"]: if self.CM.ShouldBeStatus[other] == self.CM["up"] and other != node: patterns.append(self.CM["Pat:They_stopped"] %(other, node)) #self.debug("Checking %s will notice %s left"%(other, node)) watch = CTS.LogWatcher( self.CM["LogFileName"], patterns, self.CM["DeadTime"]) watch.setwatch() if node == self.CM.OurNode: self.incr("us") else: if self.CM.upcount() <= 1: self.incr("all") else: self.incr("them") self.CM.StopaCM(node) watch_result = watch.lookforall() UnmatchedList = "||" if watch.unmatched: for regex in watch.unmatched: self.CM.log ("Warn: Shutdown pattern not found: %s" % (regex)) UnmatchedList += regex + "||"; self.CM.cluster_stable(self.CM["DeadTime"]) # because syslog looses so many messages we can only really fail # the stop if _none_ of the CCM peers notice the node leave if not watch.unmatched or self.CM.upcount() == 0: return self.success() elif len(watch.unmatched) >= self.CM.upcount(): return self.failure("no match against (%s)" % UnmatchedList) return self.success() # # We don't register StopTest because it's better when called by # another test... # ################################################################### class StartTest(CTSTest): ################################################################### '''Start (activate) the cluster manager on a node''' def __init__(self, cm, debug=None): CTSTest.__init__(self,cm) self.name="start" self.debug = debug self.uspat = self.CM["Pat:We_started"] self.thempat = self.CM["Pat:They_started"] def __call__(self, node): '''Perform the 'start' test. ''' self.incr("calls") if self.CM.upcount() == 0: self.incr("us") else: self.incr("them") if self.CM.ShouldBeStatus[node] != self.CM["down"]: return self.skipped() elif self.CM.StartaCM(node): return self.success() else: return self.failure("Startup %s on node %s failed" %(self.CM["Name"], node)) def is_applicable(self): '''StartTest is always applicable''' return 1 # # We don't register StartTest because it's better when called by # another test... # ################################################################### class FlipTest(CTSTest): ################################################################### '''If it's running, stop it. If it's stopped start it. Overthrow the status quo... ''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="Flip" self.start = StartTest(cm) self.stop = StopTest(cm) def __call__(self, node): '''Perform the 'Flip' test. ''' self.incr("calls") if self.CM.ShouldBeStatus[node] == self.CM["up"]: self.incr("stopped") ret = self.stop(node) type="up->down" # Give the cluster time to recognize it's gone... time.sleep(self.CM["StableTime"]) elif self.CM.ShouldBeStatus[node] == self.CM["down"]: self.incr("started") ret = self.start(node) type="down->up" else: return self.skipped() self.incr(type) if ret: return self.success() else: return self.failure("%s failure" % type) def is_applicable(self): '''FlipTest is always applicable''' return 1 # Register FlipTest as a good test to run AllTestClasses.append(FlipTest) ################################################################### class RestartTest(CTSTest): ################################################################### '''Stop and restart a node''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="Restart" self.start = StartTest(cm) self.stop = StopTest(cm) def __call__(self, node): '''Perform the 'restart' test. ''' self.incr("calls") self.incr("node:" + node) ret1 = 1 if self.CM.StataCM(node): self.incr("WasStopped") if not self.start(node): return self.failure("start (setup) failure: "+node) self.starttime=time.time() if not self.stop(node): return self.failure("stop failure: "+node) if not self.start(node): return self.failure("start failure: "+node) return self.success() def is_applicable(self): '''RestartTest is always applicable''' return 1 # Register RestartTest as a good test to run AllTestClasses.append(RestartTest) ################################################################### class StonithTest(CTSTest): ################################################################### '''Reboot a node by whacking it with stonith.''' def __init__(self, cm, timeout=900): CTSTest.__init__(self,cm) self.name="Stonith" self.theystopped = self.CM["Pat:They_dead"] self.allstopped = self.CM["Pat:All_stopped"] self.usstart = self.CM["Pat:We_started"] self.themstart = self.CM["Pat:They_started"] self.timeout = timeout def _reset(self, node): StonithWorked=False for tries in 1,2,3,4,5: if self.CM.Env.ResetNode(node): StonithWorked=True break return StonithWorked def setup(self, target_node): # nothing to do return 1 def __call__(self, node): '''Perform the 'stonith' test. (whack the node)''' self.incr("calls") stopwatch = 0 rc = 0 if not self.setup(node): return self.failure("Setup failed") # Figure out what log message to look for when/if it goes down # # Any active node needs to notice this one left # NOTE: This wont work if we have multiple partitions stop_patterns = [] for other in self.CM.Env["nodes"]: if self.CM.ShouldBeStatus[other] == self.CM["up"] and other != node: stop_patterns.append(self.CM["Pat:They_stopped"] %(other, node)) stopwatch = 1 #self.debug("Checking %s will notice %s left"%(other, node)) if self.CM.ShouldBeStatus[node] == self.CM["down"]: # actually no-one will notice this node die since HA isnt running stopwatch = 0 # Figure out what log message to look for when it comes up if self.CM.upcount() == 1 and self.CM.ShouldBeStatus[node] == self.CM["up"]: uppat = (self.usstart % node) else: uppat = (self.themstart % node) upwatch = CTS.LogWatcher(self.CM["LogFileName"], [uppat] , timeout=self.timeout) if stopwatch == 1: watch = CTS.LogWatcher(self.CM["LogFileName"], stop_patterns , timeout=self.CM["DeadTime"]+10) watch.setwatch() # Reset (stonith) the node StonithWorked = self._reset(node) if not StonithWorked: return self.failure("Stonith didn't work") upwatch.setwatch() # Look() and see if the machine went down if stopwatch == 0: # Allow time for the node to die time.sleep(self.CM["DeadTime"]+10) elif not watch.lookforall(): if watch.unmatched: for regex in watch.unmatched: self.CM.log("Warn: STONITH pattern not found: %s"%regex) # !!no-one!! saw this node die if len(watch.unmatched) == len(stop_patterns): return self.failure("No-one saw %s die" %node) # else: syslog* lost a message # Alas I dont think this check is plausable (beekhof) # # Check it really stopped... #self.CM.ShouldBeStatus[node] = self.CM["down"] #if self.CM.StataCM(node) == 1: # ret1=0 # Look() and see if the machine came back up rc=0 if upwatch.look(): self.CM.debug("Startup pattern found: %s" %uppat) rc=1 else: self.CM.log("Warn: Startup pattern not found: %s" %uppat) # Check it really started... self.CM.ShouldBeStatus[node] = self.CM["up"] if rc == 0 and self.CM.StataCM(node) == 1: rc=1 # wait for the cluster to stabilize self.CM.cluster_stable() # return case processing if rc == 0: return self.failure("Node %s did not restart" %node) else: return self.success() def is_applicable(self): '''StonithTest is applicable unless suppressed by CM.Env["DoStonith"] == FALSE''' # for v2, stonithd test is a better test to run. if self.CM["Name"] == "linux-ha-v2": return None if self.CM.Env.has_key("DoStonith"): return self.CM.Env["DoStonith"] return 1 # Register StonithTest as a good test to run AllTestClasses.append(StonithTest) ################################################################### class StonithdTest(StonithTest): ################################################################### def __init__(self, cm, timeout=600): StonithTest.__init__(self, cm, timeout=600) self.name="Stonithd" self.startall = SimulStartLite(cm) self.start = StartTest(cm) self.stop = StopTest(cm) self.init_node = None def _reset(self, target_node): if len(self.CM.Env["nodes"]) < 2: return self.skipped() StonithWorked = False for tries in range(1,5): fail_reasons = [] if self.CM.Env.ResetNode2(self.init_node, target_node, fail_reasons): StonithWorked = True break for reason in fail_reasons: self.CM.log(reason) return StonithWorked def setup(self, target_node): if len(self.CM.Env["nodes"]) < 2: return 1 self.init_node = self.CM.Env.RandomNode() while self.init_node == target_node: self.init_node = self.CM.Env.RandomNode() if not self.startall(None): return self.failure("Test setup failed") return 1 def is_applicable(self): if not self.CM["Name"] == "linux-ha-v2": return 0 if self.CM.Env.has_key("DoStonith"): return self.CM.Env["DoStonith"] return 1 AllTestClasses.append(StonithdTest) ################################################################### class IPaddrtest(CTSTest): ################################################################### '''Find the machine supporting a particular IP address, and knock it down. [Hint: This code isn't finished yet...] ''' def __init__(self, cm, IPaddrs): CTSTest.__init__(self,cm) self.name="IPaddrtest" self.IPaddrs = IPaddrs self.start = StartTest(cm) self.stop = StopTest(cm) def __call__(self, IPaddr): ''' Perform the IPaddr test... ''' self.incr("calls") node = self.CM.Env.RandomNode() self.incr("node:" + node) if self.CM.ShouldBeStatus[node] == self.CM["down"]: self.incr("WasStopped") self.start(node) ret1 = self.stop(node) # Give the cluster time to recognize we're gone... time.sleep(self.CM["StableTime"]) ret2 = self.start(node) if not ret1: return self.failure("Could not stop") if not ret2: return self.failure("Could not start") return self.success() def is_applicable(self): '''IPaddrtest is always applicable (but shouldn't be)''' return 1 ################################################################### class StartOnebyOne(CTSTest): ################################################################### '''Start all the nodes ~ one by one''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="StartOnebyOne" self.stopall = SimulStopLite(cm) self.start = StartTest(cm) self.ns=CTS.NodeStatus(cm.Env) def __call__(self, dummy): '''Perform the 'StartOnebyOne' test. ''' self.incr("calls") # We ignore the "node" parameter... # Shut down all the nodes... ret = self.stopall(None) if not ret: return self.failure("Test setup failed") failed=[] self.starttime=time.time() for node in self.CM.Env["nodes"]: if not self.start(node): failed.append(node) if len(failed) > 0: return self.failure("Some node failed to start: " + repr(failed)) return self.success() def errorstoignore(self): '''Return list of errors which should be ignored''' return [] def is_applicable(self): '''StartOnebyOne is always applicable''' return 1 # Register StartOnebyOne as a good test to run AllTestClasses.append(StartOnebyOne) ################################################################### class SimulStart(CTSTest): ################################################################### '''Start all the nodes ~ simultaneously''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="SimulStart" self.stopall = SimulStopLite(cm) self.startall = SimulStartLite(cm) def __call__(self, dummy): '''Perform the 'SimulStart' test. ''' self.incr("calls") # We ignore the "node" parameter... # Shut down all the nodes... ret = self.stopall(None) if not ret: return self.failure("Setup failed") self.CM.clear_all_caches() if not self.startall(None): return self.failure("Startall failed") return self.success() def errorstoignore(self): '''Return list of errors which should be ignored''' return [] def is_applicable(self): '''SimulStart is always applicable''' return 1 # Register SimulStart as a good test to run AllTestClasses.append(SimulStart) class SimulStop(CTSTest): ################################################################### '''Stop all the nodes ~ simultaneously''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="SimulStop" self.startall = SimulStartLite(cm) self.stopall = SimulStopLite(cm) def __call__(self, dummy): '''Perform the 'SimulStop' test. ''' self.incr("calls") # We ignore the "node" parameter... # Start up all the nodes... ret = self.startall(None) if not ret: return self.failure("Setup failed") if not self.stopall(None): return self.failure("Stopall failed") return self.success() def errorstoignore(self): '''Return list of errors which should be ignored''' return [] def is_applicable(self): '''SimulStop is always applicable''' return 1 # Register SimulStop as a good test to run AllTestClasses.append(SimulStop) class StopOnebyOne(CTSTest): ################################################################### '''Stop all the nodes in order''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="StopOnebyOne" self.startall = SimulStartLite(cm) self.stop = StopTest(cm) def __call__(self, dummy): '''Perform the 'StopOnebyOne' test. ''' self.incr("calls") # We ignore the "node" parameter... # Start up all the nodes... ret = self.startall(None) if not ret: return self.failure("Setup failed") failed=[] self.starttime=time.time() for node in self.CM.Env["nodes"]: if not self.stop(node): failed.append(node) if len(failed) > 0: return self.failure("Some node failed to stop: " + repr(failed)) self.CM.clear_all_caches() return self.success() def is_applicable(self): '''StopOnebyOne is always applicable''' return 1 # Register StopOnebyOne as a good test to run AllTestClasses.append(StopOnebyOne) class RestartOnebyOne(CTSTest): ################################################################### '''Stop all the nodes in order''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="RestartOnebyOne" self.startall = SimulStartLite(cm) def __call__(self, dummy): '''Perform the 'RestartOnebyOne' test. ''' self.incr("calls") # We ignore the "node" parameter... # Start up all the nodes... ret = self.startall(None) if not ret: return self.failure("Setup failed") did_fail=[] self.starttime=time.time() self.restart = RestartTest(self.CM) for node in self.CM.Env["nodes"]: if not self.restart(node): did_fail.append(node) if did_fail: return self.failure("Could not restart %d nodes: %s" %(len(did_fail), repr(did_fail))) return self.success() def is_applicable(self): '''RestartOnebyOne is always applicable''' return 1 # Register StopOnebyOne as a good test to run AllTestClasses.append(RestartOnebyOne) ################################################################### class StandbyTest(CTSTest): ################################################################### '''Put a node in standby mode''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="standby" self.successpat = self.CM["Pat:StandbyOK"] self.nostandbypat = self.CM["Pat:StandbyNONE"] self.transient = self.CM["Pat:StandbyTRANSIENT"] def __call__(self, node): '''Perform the 'standby' test. ''' self.incr("calls") if self.CM.ShouldBeStatus[node] == self.CM["down"]: return self.skipped() if self.CM.upcount() < 2: self.incr("nostandby") pat = self.nostandbypat else: self.incr("standby") pat = self.successpat # # You could make a good argument that the cluster manager # ought to give us good clues on when its a bad time to # switch over to the other side, but heartbeat doesn't... # It could also queue the request. But, heartbeat # doesn't do that either :-) # retrycount=0 while (retrycount < 10): watch = CTS.LogWatcher(self.CM["LogFileName"] , [pat, self.transient] , timeout=self.CM["DeadTime"]+10) watch.setwatch() self.CM.rsh(node, self.CM["Standby"]) match = watch.look() if match: if re.search(self.transient, match): self.incr("retries") time.sleep(2) retrycount=retrycount+1 else: return self.success() else: break # No point in retrying... return self.failure("did not find pattern " + pat) def is_applicable(self): '''StandbyTest is applicable when the CM has a Standby command''' if not self.CM.has_key("Standby"): return None else: #if self.CM.Env.has_key("DoStandby"): #flag=self.CM.Env["DoStandby"] #if type(flag) == types.IntType: #return flag #if not re.match("[yt]", flag, re.I): #return None # # We need to strip off everything after the first blank # cmd=self.CM["Standby"] cmd = cmd.split()[0] if not os.access(cmd, os.X_OK): return None cf = self.CM.cf if not cf.Parameters.has_key("auto_failback"): return None elif cf.Parameters["auto_failback"][0] == "legacy": return None return 1 # Register StandbyTest as a good test to run AllTestClasses.append(StandbyTest) ####################################################################### class StandbyTest2(CTSTest): ####################################################################### '''Standby with CRM of HA release 2''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="standby2" self.start = StartTest(cm) self.startall = SimulStartLite(cm) # make sure the node is active # set the node to standby mode # check resources, none resource should be running on the node # set the node to active mode # check resouces, resources should have been migrated back (SHOULD THEY?) def __call__(self, node): self.incr("calls") ret=self.startall(None) if not ret: return self.failure("Start all nodes failed") self.CM.debug("Make sure node %s is active" % node) if self.CM.StandbyStatus(node) != "off": if not self.CM.SetStandbyMode(node, "off"): return self.failure("can't set node %s to active mode" % node) self.CM.cluster_stable() status = self.CM.StandbyStatus(node) if status != "off": return self.failure("standby status of %s is [%s] but we expect [off]" % (node, status)) self.CM.debug("Getting resources running on node %s" % node) rsc_on_node = [] for rsc in self.CM.Resources(): if rsc.IsRunningOn(node): rsc_on_node.append(rsc) self.CM.debug("Setting node %s to standby mode" % node) if not self.CM.SetStandbyMode(node, "on"): return self.failure("can't set node %s to standby mode" % node) time.sleep(30) # Allow time for the update to be applied and cause something self.CM.cluster_stable() status = self.CM.StandbyStatus(node) if status != "on": return self.failure("standby status of %s is [%s] but we expect [on]" % (node, status)) self.CM.debug("Checking resources") for rsc in self.CM.Resources(): if rsc.IsRunningOn(node): return self.failure("%s set to standby, %s is still running on it" % (node, rsc.rid)) self.CM.debug("Setting node %s to active mode" % node) if not self.CM.SetStandbyMode(node, "off"): return self.failure("can't set node %s to active mode" % node) time.sleep(30) # Allow time for the update to be applied and cause something self.CM.cluster_stable() status = self.CM.StandbyStatus(node) if status != "off": return self.failure("standby status of %s is [%s] but we expect [off]" % (node, status)) self.CM.debug("Checking resources") for rsc in rsc_on_node: if not rsc.IsRunningOn(node): return self.failure("%s set to active but %s is NOT back" % (node, rsc.rid)) return self.success() def is_applicable(self): if self.CM["Name"] == "linux-ha-v2": return 1 return 0 AllTestClasses.append(StandbyTest2) ####################################################################### class Fastdetection(CTSTest): ####################################################################### '''Test the time which one node find out the other node is killed very quickly''' def __init__(self,cm,timeout=60): CTSTest.__init__(self, cm) self.name = "DetectionTime" self.they_stopped = self.CM["Pat:They_stopped"] self.timeout = timeout self.start = StartTest(cm) self.startall = SimulStartLite(cm) self.standby = StandbyTest(cm) self.__setitem__("min", 0) self.__setitem__("max", 0) self.__setitem__("totaltime", 0) def __call__(self, node): '''Perform the fastfailureDetection test''' self.incr("calls") ret=self.startall(None) if not ret: return self.failure("Test setup failed") if self.CM.upcount() < 2: return self.skipped() # Make sure they're not holding any resources ret = self.standby(node) if not ret: return ret stoppat = (self.they_stopped % ("", node)) stopwatch = CTS.LogWatcher(self.CM["LogFileName"], [stoppat], timeout=self.timeout) stopwatch.setwatch() # # This test is CM-specific - FIXME!! # if self.CM.rsh(node, "killall -9 heartbeat")==0: Starttime = os.times()[4] if stopwatch.look(): Stoptime = os.times()[4] # This test is CM-specific - FIXME!! self.CM.rsh(node, "killall -9 @libdir@/heartbeat/ccm @libdir@/heartbeat/ipfail >/dev/null 2>&1; true") Detectiontime = Stoptime-Starttime detectms = int(Detectiontime*1000+0.5) self.CM.log("...failure detection time: %d ms" % detectms) self.Stats["totaltime"] = self.Stats["totaltime"] + Detectiontime if self.Stats["min"] == 0: self.Stats["min"] = Detectiontime if Detectiontime > self.Stats["max"]: self.Stats["max"] = Detectiontime if Detectiontime < self.Stats["min"]: self.Stats["min"] = Detectiontime self.CM.ShouldBeStatus[node] = self.CM["down"] self.start(node) return self.success() else: # This test is CM-specific - FIXME!! self.CM.rsh(node, "killall -9 @libdir@/heartbeat/ccm @libdir@/heartbeat/ipfail >/dev/null 2>&1; true") self.CM.ShouldBeStatus[node] = self.CM["down"] ret=self.start(node) return self.failure("Didn't find the log message") else: return self.failure("Couldn't kill cluster manager") def is_applicable(self): '''This test is applicable when auto_failback != legacy''' return self.standby.is_applicable() # This test is CM-specific - FIXME!! def errorstoignore(self): '''Return list of errors which are 'normal' and should be ignored''' return [ "ccm.*ERROR: ccm_control_process:failure to send protoversion request" , "ccm.*ERROR: Lost connection to heartbeat service. Need to bail out" ] AllTestClasses.append(Fastdetection) ############################################################################## class BandwidthTest(CTSTest): ############################################################################## # Tests should not be cluster-manager-specific # If you need to find out cluster manager configuration to do this, then # it should be added to the generic cluster manager API. '''Test the bandwidth which heartbeat uses''' def __init__(self, cm): CTSTest.__init__(self, cm) self.name = "Bandwidth" self.start = StartTest(cm) self.__setitem__("min",0) self.__setitem__("max",0) self.__setitem__("totalbandwidth",0) self.tempfile = tempfile.mktemp(".cts") self.startall = SimulStartLite(cm) def __call__(self, node): '''Perform the Bandwidth test''' self.incr("calls") if self.CM.upcount()<1: return self.skipped() Path = self.CM.InternalCommConfig() if "ip" not in Path["mediatype"]: return self.skipped() port = Path["port"][0] port = int(port) ret = self.startall(None) if not ret: return self.failure("Test setup failed") time.sleep(5) # We get extra messages right after startup. fstmpfile = "/var/run/band_estimate" dumpcmd = "tcpdump -p -n -c 102 -i any udp port %d > %s 2>&1" \ % (port, fstmpfile) rc = self.CM.rsh(node, dumpcmd) if rc == 0: farfile = "root@%s:%s" % (node, fstmpfile) self.CM.rsh.cp(farfile, self.tempfile) Bandwidth = self.countbandwidth(self.tempfile) if not Bandwidth: self.CM.log("Could not compute bandwidth.") return self.success() intband = int(Bandwidth + 0.5) self.CM.log("...bandwidth: %d bits/sec" % intband) self.Stats["totalbandwidth"] = self.Stats["totalbandwidth"] + Bandwidth if self.Stats["min"] == 0: self.Stats["min"] = Bandwidth if Bandwidth > self.Stats["max"]: self.Stats["max"] = Bandwidth if Bandwidth < self.Stats["min"]: self.Stats["min"] = Bandwidth self.CM.rsh(node, "rm -f %s" % fstmpfile) os.unlink(self.tempfile) return self.success() else: return self.failure("no response from tcpdump command [%d]!" % rc) def countbandwidth(self, file): fp = open(file, "r") fp.seek(0) count = 0 sum = 0 while 1: line = fp.readline() if not line: return None if re.search("udp",line) or re.search("UDP,", line): count=count+1 linesplit = string.split(line," ") for j in range(len(linesplit)-1): if linesplit[j]=="udp": break if linesplit[j]=="length:": break try: sum = sum + int(linesplit[j+1]) except ValueError: self.CM.log("Invalid tcpdump line: %s" % line) return None T1 = linesplit[0] timesplit = string.split(T1,":") time2split = string.split(timesplit[2],".") time1 = (long(timesplit[0])*60+long(timesplit[1]))*60+long(time2split[0])+long(time2split[1])*0.000001 break while count < 100: line = fp.readline() if not line: return None if re.search("udp",line) or re.search("UDP,", line): count = count+1 linessplit = string.split(line," ") for j in range(len(linessplit)-1): if linessplit[j] =="udp": break if linesplit[j]=="length:": break try: sum=int(linessplit[j+1])+sum except ValueError: self.CM.log("Invalid tcpdump line: %s" % line) return None T2 = linessplit[0] timesplit = string.split(T2,":") time2split = string.split(timesplit[2],".") time2 = (long(timesplit[0])*60+long(timesplit[1]))*60+long(time2split[0])+long(time2split[1])*0.000001 time = time2-time1 if (time <= 0): return 0 return (sum*8)/time def is_applicable(self): '''BandwidthTest is always applicable''' return 0 AllTestClasses.append(BandwidthTest) ########################################################################## class RedundantpathTest(CTSTest): ########################################################################## '''In heartbeat, it has redundant path to communicate between the cluster''' # # Tests should not be cluster-manager specific # One needs to isolate what you need from the cluster manager and then # add a (new) API to do it. # def __init__(self,cm,timeout=60): CTSTest.__init__(self,cm) self.name = "RedundantpathTest" self.timeout = timeout def PathCount(self): '''Return number of communication paths''' Path = self.CM.InternalCommConfig() cf = self.CM.cf eths = [] serials = [] num = 0 for interface in Path["interface"]: if re.search("eth",interface): eths.append(interface) num = num + 1 if re.search("/dev",interface): serials.append(interface) num = num + 1 return (num, eths, serials) def __call__(self,node): '''Perform redundant path test''' self.incr("calls") if self.CM.ShouldBeStatus[node]!=self.CM["up"]: return self.skipped() (num, eths, serials) = self.PathCount() for eth in eths: if self.CM.rsh(node,"ifconfig %s down" % eth)==0: PathDown = "OK" break if PathDown != "OK": for serial in serials: if self.CM.rsh(node,"setserial %s uart none" % serial)==0: PathDown = "OK" break if PathDown != "OK": return self.failure("Cannot break the path") time.sleep(self.timeout) for audit in CTSaudits.AuditList(self.CM): if not audit(): for eth in eths: self.CM.rsh(node,"ifconfig %s up" % eth) for serial in serials: self.CM.rsh(node,"setserial %s uart 16550" % serial) return self.failure("Redundant path fail") for eth in eths: self.CM.rsh(node,"ifconfig %s up" % eth) for serial in serials: self.CM.rsh(node,"setserial %s uart 16550" % serial) return self.success() def is_applicable(self): '''It is applicable when you have more than one connection''' return self.PathCount()[0] > 1 # FIXME!! Why is this one commented out? #AllTestClasses.append(RedundantpathTest) ########################################################################## class DRBDTest(CTSTest): ########################################################################## '''In heartbeat, it provides replicated storage.''' def __init__(self,cm, timeout=10): CTSTest.__init__(self,cm) self.name = "DRBD" self.timeout = timeout def __call__(self, dummy): '''Perform the 'DRBD' test.''' self.incr("calls") for node in self.CM.Env["nodes"]: if self.CM.ShouldBeStatus[node] == self.CM["down"]: return self.skipped() # Note: All these special cases with Start/Stop/StatusDRBD # should be reworked to use resource objects instead of # being hardwired to bypass the objects here. for node in self.CM.Env["nodes"]: done=time.time()+self.timeout+1 while (time.time()done: return self.failure("Can't start drbd, please check it") device={} for node in self.CM.Env["nodes"]: device[node]=self.getdevice(node) node = self.CM.Env["nodes"][0] done=time.time()+self.timeout+1 while 1: if (time.time()>done): return self.failure("the drbd could't sync") self.CM.rsh(node,"cp /proc/drbd /var/run >/dev/null 2>&1") if self.CM.rsh.cp("%s:/var/run/drbd" % node,"/var/run"): line = open("/tmp/var/run").readlines()[2] p = line.find("Primary") s1 = line.find("Secondary") s2 = line.rfind("Secondary") if s1!=s2: if self.CM.rsh(node,"drbdsetup %s primary" % device[node]): pass if p!=-1: if p mem_allow: failed = 1 messages.append("%s size grew by %dkB (%dkB)" %(memory_error[index], mem_diff, mem_after)) elif mem_diff < 0: messages.append("%s size shrank by %dkB (%dkB)" %(memory_error[index], mem_diff, mem_after)) if len(messages) > 0: self.CM.log("Process %s[%s] on %s: %s" %(before_tokens[0], before_tokens[1], node, repr(messages))) self.CM.debug("%s Before: %s[%s] (%s%%):\tcode=%skB, data=%skB, resident=%skB, total=%skB" %(node, before_tokens[0], before_tokens[1], before_tokens[2], before_tokens[3], before_tokens[4], before_tokens[5], before_tokens[6])) self.CM.debug("%s After: %s[%s] (%s%%):\tcode=%skB, data=%skB, resident=%skB, total=%skB" %(node, after_tokens[0], after_tokens[1], after_tokens[2], after_tokens[3], after_tokens[4], after_tokens[5], after_tokens[6])) if failed == 1: failed_nodes.append(node) if len(failed_nodes) > 0: return self.failure("Memory leaked on: " + repr(failed_nodes)) return self.success() def errorstoignore(self): '''Return list of errors which should be ignored''' return [ """ERROR: .* LRM operation.*monitor on .*: not running""", """pengine:.*Handling failed """] def is_applicable(self): if self.CM["Name"] == "linux-ha-v2": return 1 return 0 #AllTestClasses.append(MemoryTest) #################################################################### class ElectionMemoryTest(CTSTest): #################################################################### '''Check to see if anyone is leaking memory''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="Election" def __call__(self, node): self.rsh.readaline(node, self.CM["ElectionCmd"]%node) if self.CM.cluster_stable(): return self.success() return self.failure("Cluster not stable") def errorstoignore(self): '''Return list of errors which should be ignored''' return [] def is_applicable(self): '''Never applicable, only for use by the memory test''' return 0 AllTestClasses.append(ElectionMemoryTest) #################################################################### class SpecialTest1(CTSTest): #################################################################### '''Set up a custom test to cause quorum failure issues for Andrew''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="SpecialTest1" self.startall = SimulStartLite(cm) self.restart1 = RestartTest(cm) self.stopall = SimulStopLite(cm) def __call__(self, node): '''Perform the 'SpecialTest1' test for Andrew. ''' self.incr("calls") # Shut down all the nodes... ret = self.stopall(None) if not ret: return ret # Start the selected node ret = self.restart1(node) if not ret: return ret # Start all remaining nodes ret = self.startall(None) return ret def errorstoignore(self): '''Return list of errors which should be ignored''' return [] def is_applicable(self): return 1 AllTestClasses.append(SpecialTest1) ################################################################### class NearQuorumPointTest(CTSTest): ################################################################### ''' This test brings larger clusters near the quorum point (50%). In addition, it will test doing starts and stops at the same time. Here is how I think it should work: - loop over the nodes and decide randomly which will be up and which will be down Use a 50% probability for each of up/down. - figure out what to do to get into that state from the current state - in parallel, bring up those going up and bring those going down. ''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="NearQuorumPoint" def __call__(self, dummy): '''Perform the 'NearQuorumPoint' test. ''' self.incr("calls") startset = [] stopset = [] #decide what to do with each node for node in self.CM.Env["nodes"]: action = self.CM.Env.RandomGen.choice(["start","stop"]) #action = self.CM.Env.RandomGen.choice(["start","stop","no change"]) if action == "start" : startset.append(node) elif action == "stop" : stopset.append(node) self.CM.debug("start nodes:" + repr(startset)) self.CM.debug("stop nodes:" + repr(stopset)) - if not self.CM.ns.WaitForAllNodesToComeUp(self.CM.Env["nodes"]): - self.log("WaitForAllNodesToComeUp filed") - return self.skipped() - #add search patterns watchpats = [ ] for node in stopset: if self.CM.ShouldBeStatus[node] == self.CM["up"]: watchpats.append(self.CM["Pat:We_stopped"] % node) for node in startset: if self.CM.ShouldBeStatus[node] == self.CM["down"]: watchpats.append(self.CM["Pat:They_started"] % node) if len(watchpats) == 0: return self.skipped() watch = CTS.LogWatcher(self.CM["LogFileName"], watchpats , timeout=self.CM["DeadTime"]+10) watch.setwatch() #begin actions for node in stopset: if self.CM.ShouldBeStatus[node] == self.CM["up"]: self.CM.StopaCMnoBlock(node) for node in startset: if self.CM.ShouldBeStatus[node] == self.CM["down"]: self.CM.StartaCMnoBlock(node) #get the result if watch.lookforall(): self.CM.cluster_stable() return self.success() self.CM.log("Warn: Patterns not found: " + repr(watch.unmatched)) #get the "bad" nodes upnodes = [] for node in stopset: if self.CM.StataCM(node) == 1: upnodes.append(node) downnodes = [] for node in startset: if self.CM.StataCM(node) == 0: downnodes.append(node) if upnodes == [] and downnodes == []: self.CM.cluster_stable() return self.success() if len(upnodes) > 0: self.CM.log("Warn: Unstoppable nodes: " + repr(upnodes)) if len(downnodes) > 0: self.CM.log("Warn: Unstartable nodes: " + repr(downnodes)) return self.failure() def errorstoignore(self): '''Return list of errors which should be ignored''' return [] def is_applicable(self): if self.CM["Name"] == "linux-ha-v2": return 1 return 0 AllTestClasses.append(NearQuorumPointTest) ################################################################### class BSC_AddResource(CTSTest): ################################################################### '''Add a resource to the cluster''' def __init__(self, cm): CTSTest.__init__(self, cm) self.name="AddResource" self.resource_offset = 0 self.cib_cmd="""@sbindir@/cibadmin -C -o %s -X '%s' """ def __call__(self, node): self.resource_offset = self.resource_offset + 1 r_id = "bsc-rsc-%s-%d" % (node, self.resource_offset) - start_pat = "crmd.*start_0 on %s complete" + start_pat = "crmd.*%s_start_0.*complete" patterns = [] patterns.append(start_pat % r_id) watch = CTS.LogWatcher( self.CM["LogFileName"], patterns, self.CM["DeadTime"]) watch.setwatch() fields = string.split(self.CM.Env["IPBase"], '.') fields[3] = str(int(fields[3])+1) ip = string.join(fields, '.') self.CM.Env["IPBase"] = ip if not self.make_ip_resource(node, r_id, "ocf", "IPaddr", ip): return self.failure("Make resource %s failed" % r_id) failed = 0 watch_result = watch.lookforall() if watch.unmatched: for regex in watch.unmatched: self.CM.log ("Warn: Pattern not found: %s" % (regex)) failed = 1 if failed: return self.failure("Resource pattern(s) not found") if not self.CM.cluster_stable(self.CM["DeadTime"]): return self.failure("Unstable cluster") return self.success() def make_ip_resource(self, node, id, rclass, type, ip): self.CM.log("Creating %s::%s:%s (%s) on %s" % (rclass,type,id,ip,node)) rsc_xml=""" """ % (id, rclass, type, id, id, ip) node_constraint=""" """ % (id, id, id, id, node) rc = 0 (rc, lines) = self.CM.rsh.remote_py(node, "os", "system", self.cib_cmd % ("constraints", node_constraint)) if rc != 0: self.CM.log("Constraint creation failed: %d" % rc) return None (rc, lines) = self.CM.rsh.remote_py(node, "os", "system", self.cib_cmd % ("resources", rsc_xml)) if rc != 0: self.CM.log("Resource creation failed: %d" % rc) return None return 1 def is_applicable(self): if self.CM["Name"] == "linux-ha-v2" and self.CM.Env["DoBSC"]: return 1 return None def TestList(cm): result = [] for testclass in AllTestClasses: bound_test = testclass(cm) if bound_test.is_applicable(): result.append(bound_test) return result class SimulStopLite(CTSTest): ################################################################### '''Stop any active nodes ~ simultaneously''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="SimulStopLite" def __call__(self, dummy): '''Perform the 'SimulStopLite' setup work. ''' self.incr("calls") - if not self.CM.ns.WaitForAllNodesToComeUp(self.CM.Env["nodes"]): - self.log("WaitForAllNodesToComeUp filed") - return self.skipped() - self.CM.debug("Setup: " + self.name) # We ignore the "node" parameter... watchpats = [ ] for node in self.CM.Env["nodes"]: if self.CM.ShouldBeStatus[node] == self.CM["up"]: self.incr("WasStarted") watchpats.append(self.CM["Pat:All_stopped"] % node) if self.CM.Env["use_logd"]: watchpats.append(self.CM["Pat:Logd_stopped"] % node) if len(watchpats) == 0: self.CM.clear_all_caches() return self.skipped() # Stop all the nodes - at about the same time... watch = CTS.LogWatcher(self.CM["LogFileName"], watchpats , timeout=self.CM["DeadTime"]+10) watch.setwatch() self.starttime=time.time() for node in self.CM.Env["nodes"]: if self.CM.ShouldBeStatus[node] == self.CM["up"]: self.CM.StopaCMnoBlock(node) if watch.lookforall(): self.CM.clear_all_caches() return self.success() did_fail=0 up_nodes = [] for node in self.CM.Env["nodes"]: if self.CM.StataCM(node) == 1: did_fail=1 up_nodes.append(node) if did_fail: return self.failure("Active nodes exist: " + repr(up_nodes)) self.CM.log("Warn: All nodes stopped but CTS didnt detect: " + repr(watch.unmatched)) self.CM.clear_all_caches() return self.success() def is_applicable(self): '''SimulStopLite is a setup test and never applicable''' return 0 ################################################################### class SimulStartLite(CTSTest): ################################################################### '''Start any stopped nodes ~ simultaneously''' def __init__(self, cm): CTSTest.__init__(self,cm) self.name="SimulStartLite" def __call__(self, dummy): '''Perform the 'SimulStartList' setup work. ''' self.incr("calls") self.CM.debug("Setup: " + self.name) - if not self.CM.ns.WaitForAllNodesToComeUp(self.CM.Env["nodes"]): - self.log("WaitForAllNodesToComeUp filed") - return self.skipped() - # We ignore the "node" parameter... watchpats = [ ] for node in self.CM.Env["nodes"]: if self.CM.ShouldBeStatus[node] == self.CM["down"]: self.incr("WasStopped") watchpats.append(self.CM["Pat:They_started"] % node) if len(watchpats) == 0: return self.skipped() # Start all the nodes - at about the same time... watch = CTS.LogWatcher(self.CM["LogFileName"], watchpats , timeout=self.CM["DeadTime"]+10) watch.setwatch() self.starttime=time.time() for node in self.CM.Env["nodes"]: if self.CM.ShouldBeStatus[node] == self.CM["down"]: self.CM.StartaCMnoBlock(node) if watch.lookforall(): for attempt in (1, 2, 3, 4, 5): if self.CM.cluster_stable(): return self.success() return self.failure("Cluster did not stabilize") did_fail=0 unstable = [] for node in self.CM.Env["nodes"]: if self.CM.StataCM(node) == 0: did_fail=1 unstable.append(node) if did_fail: return self.failure("Unstarted nodes exist: " + repr(unstable)) unstable = [] for node in self.CM.Env["nodes"]: if not self.CM.node_stable(node): did_fail=1 unstable.append(node) if did_fail: return self.failure("Unstable cluster nodes exist: " + repr(unstable)) self.CM.log("Warn: All nodes started but CTS didnt detect: " + repr(watch.unmatched)) return self.success() def is_applicable(self): '''SimulStartLite is a setup test and never applicable''' return 0 ################################################################### class LoggingTest(CTSTest): ################################################################### def __init__(self, cm): CTSTest.__init__(self,cm) self.name="Logging" def __call__(self, dummy): '''Perform the 'Logging' test. ''' self.incr("calls") - self.CM.ns.WaitForAllNodesToComeUp(self.CM.Env["nodes"]) - # Make sure logging is working and we have enough disk space... if not self.CM.TestLogging(): sys.exit(1) if not self.CM.CheckDf(): sys.exit(1) def is_applicable(self): '''ResourceRecover is applicable only when there are resources running on our cluster and environment is linux-ha-v2''' return self.CM.Env["DoBSC"] def errorstoignore(self): '''Return list of errors which should be ignored''' return [] #AllTestClasses.append(LoggingTest) diff --git a/include/crm/common/util.h b/include/crm/common/util.h index 08d6343f20..8e371fd3f2 100644 --- a/include/crm/common/util.h +++ b/include/crm/common/util.h @@ -1,192 +1,192 @@ /* $Id: util.h,v 1.39 2006/08/14 09:06:31 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef CRM_COMMON_UTIL__H #define CRM_COMMON_UTIL__H #include #include #include #include #include #define DEBUG_INC SIGUSR1 #define DEBUG_DEC SIGUSR2 extern unsigned int crm_log_level; extern gboolean crm_config_error; extern gboolean crm_config_warning; #define crm_config_err(fmt...) { crm_config_error = TRUE; crm_err(fmt); } #define crm_config_warn(fmt...) { crm_config_warning = TRUE; crm_warn(fmt); } extern gboolean crm_log_init(const char *entity); /* returns the old value */ extern unsigned int set_crm_log_level(unsigned int level); extern unsigned int get_crm_log_level(void); extern char *crm_itoa(int an_int); extern char *crm_strdup(const char *a); extern char *generate_hash_key(const char *crm_msg_reference, const char *sys); extern char *generate_hash_value(const char *src_node, const char *src_subsys); extern gboolean decode_hash_value(gpointer value, char **node, char **subsys); extern gboolean decodeNVpair(const char *srcstring, char separator, char **name, char **value); extern int compare_version(const char *version1, const char *version2); extern char *generateReference(const char *custom1, const char *custom2); extern void alter_debug(int nsig); extern void g_hash_destroy_str(gpointer data); extern const char *get_uuid(ll_cluster_t *hb, const char *uname); extern const char *get_uname(ll_cluster_t *hb, const char *uuid); extern void unget_uuid(const char *uname); extern void set_uuid( ll_cluster_t* hb, crm_data_t *node, const char *attr, const char *uname); extern gboolean crm_is_true(const char * s); extern int crm_str_to_boolean(const char * s, int * ret); extern long crm_get_msec(const char * input); extern gboolean ccm_have_quorum(oc_ed_t event); extern const char *ccm_event_name(oc_ed_t event); extern const char *op_status2text(op_status_t status); extern char *generate_op_key( const char *rsc_id, const char *op_type, int interval); extern gboolean parse_op_key( const char *key, char **rsc_id, char **op_type, int *interval); extern char *generate_notify_key( const char *rsc_id, const char *notify_type, const char *op_type); extern gboolean crm_mem_stats(volatile cl_mem_stats_t *mem_stats); extern void crm_zero_mem_stats(volatile cl_mem_stats_t *stats); extern void crm_save_mem_stats(const char *location, cl_mem_stats_t *saved_stats); extern gboolean crm_diff_mem_stats( int log_level_up, int log_level_down, const char *location, volatile cl_mem_stats_t *stats, volatile cl_mem_stats_t *saved_stats); extern void crm_xml_nbytes(crm_data_t *xml, long *bytes, long *allocs, long *frees); extern void crm_adjust_mem_stats( volatile cl_mem_stats_t *stats, long bytes, long allocs, long frees); extern char *generate_transition_magic_v202( const char *transition_key, int op_status); extern char *generate_transition_magic( const char *transition_key, int op_status, int op_rc); extern gboolean decode_transition_magic( const char *magic, char **uuid, - int *transition_id, int *op_status, int *op_rc); + int *transition_id, int *action_id, int *op_status, int *op_rc); -extern char *generate_transition_key(int transition_id, const char *node); +extern char *generate_transition_key(int action, int transition_id, const char *node); extern gboolean decode_transition_key( - const char *key, char **uuid, int *transition_id); + const char *key, char **uuid, int *action, int *transition_id); extern char *crm_concat(const char *prefix, const char *suffix, char join); extern gboolean decode_op_key( const char *key, char **rsc_id, char **op_type, int *interval); extern void filter_action_parameters(crm_data_t *param_set, const char *version); extern gboolean safe_str_eq(const char *a, const char *b); extern gboolean safe_str_neq(const char *a, const char *b); extern int crm_parse_int(const char *text, const char *default_text); extern int crm_int_helper(const char *text, char **end_text); #define crm_atoi(text, default_text) crm_parse_int(text, default_text) extern void crm_abort(const char *file, const char *function, int line, const char *condition, gboolean do_fork); extern char *generate_series_filename( const char *directory, const char *series, int sequence, gboolean bzip); extern int get_last_sequence(const char *directory, const char *series); extern void write_last_sequence( const char *directory, const char *series, int sequence, int max); extern void crm_make_daemon( const char *name, gboolean daemonize, const char *pidfile); extern cl_mem_stats_t *crm_running_stats; typedef struct pe_cluster_option_s { const char *name; const char *alt_name; const char *type; const char *values; const char *default_value; gboolean (*is_valid)(const char *); const char *description_short; const char *description_long; } pe_cluster_option; extern const char *cluster_option( GHashTable* options, gboolean(*validate)(const char*), const char *name, const char *old_name, const char *def_value); extern const char *get_cluster_pref( GHashTable *options, pe_cluster_option *option_list, int len, const char *name); extern void config_metadata( const char *name, const char *version, const char *desc_short, const char *desc_long, pe_cluster_option *option_list, int len); extern void verify_all_options(GHashTable *options, pe_cluster_option *option_list, int len); extern gboolean check_time(const char *value); extern gboolean check_timer(const char *value); extern gboolean check_boolean(const char *value); extern gboolean check_number(const char *value); extern int char2score(const char *score); extern char *score2char(int score); extern gboolean crm_is_writable( const char *dir, const char *file, const char *user, const char *group, gboolean need_both); #endif diff --git a/include/crm/crm.h b/include/crm/crm.h index 001e799a41..0e6b9ca94a 100644 --- a/include/crm/crm.h +++ b/include/crm/crm.h @@ -1,348 +1,348 @@ /* $Id: crm.h,v 1.98 2006/06/16 10:07:16 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef CRM__H #define CRM__H #include #include #include #undef MIN #undef MAX #include #include #include #include #ifdef MCHECK #include #endif #include #ifndef CRM_DEV_BUILD # define CRM_DEV_BUILD 0 #endif #define CRM_DEPRECATED_SINCE_2_0_1 0 #define CRM_DEPRECATED_SINCE_2_0_2 0 #define CRM_DEPRECATED_SINCE_2_0_3 0 #define CRM_DEPRECATED_SINCE_2_0_4 1 #define CRM_DEPRECATED_SINCE_2_0_5 1 #define CRM_DEPRECATED_SINCE_2_0_6 1 #define CRM_DEPRECATED_SINCE_2_0_7 1 #define CRM_DEPRECATED_SINCE_2_0_8 1 #define CRM_DEPRECATED_SINCE_2_1_0 1 #define CRM_META "CRM_meta" #define crm_meta_name(field) CRM_META"_"field #define ipc_call_diff_max_ms 5000 #define action_diff_warn_ms 5000 #define action_diff_max_ms 20000 #define fsa_diff_warn_ms 10000 #define fsa_diff_max_ms 30000 #include #define CRM_ASSERT(expr) if((expr) == FALSE) { \ crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, #expr, FALSE); \ } extern gboolean crm_assert_failed; #define CRM_DEV_ASSERT(expr) \ crm_assert_failed = FALSE; \ if((expr) == FALSE) { \ crm_assert_failed = TRUE; \ crm_abort(__FILE__,__PRETTY_FUNCTION__,__LINE__, #expr, TRUE); \ } #define CRM_CHECK(expr, failure_action) if((expr) == FALSE) { \ crm_abort(__FILE__,__PRETTY_FUNCTION__,__LINE__, #expr, TRUE); \ failure_action; \ } /* Clean these up at some point, some probably should be runtime options */ #define WORKING_DIR HA_VARLIBDIR"/heartbeat/crm" #define CRM_SOCK_DIR HA_VARRUNDIR"/heartbeat/crm" #define BIN_DIR HA_LIBDIR"/heartbeat" #define SOCKET_LEN 1024 #define APPNAME_LEN 256 #define MAX_IPC_FAIL 5 #define CIB_FILENAME WORKING_DIR"/cib.xml" #define CIB_BACKUP WORKING_DIR"/cib_backup.xml" #define CRM_FEATURE_SET "1.0.7" #define MSG_LOG 1 #define DOT_FSA_ACTIONS 1 #define DOT_ALL_FSA_INPUTS 1 /* #define FSA_TRACE 1 */ #define INFINITY_S "INFINITY" #define MINUS_INFINITY_S "-INFINITY" #define INFINITY 1000000 /* Sub-systems */ #define CRM_SYSTEM_DC "dc" #define CRM_SYSTEM_DCIB "dcib" /* The master CIB */ #define CRM_SYSTEM_CIB "cib" #define CRM_SYSTEM_CRMD "crmd" #define CRM_SYSTEM_LRMD "lrmd" #define CRM_SYSTEM_PENGINE "pengine" #define CRM_SYSTEM_TENGINE "tengine" /* Valid operations */ #define CRM_OP_NOOP "noop" #define CRM_OP_JOIN_ANNOUNCE "join_announce" #define CRM_OP_JOIN_OFFER "join_offer" #define CRM_OP_JOIN_REQUEST "join_request" #define CRM_OP_JOIN_ACKNAK "join_ack_nack" #define CRM_OP_JOIN_CONFIRM "join_confirm" #define CRM_OP_DIE "die_no_respawn" #define CRM_OP_RETRIVE_CIB "retrieve_cib" #define CRM_OP_PING "ping" #define CRM_OP_VOTE "vote" #define CRM_OP_NOVOTE "no-vote" #define CRM_OP_HELLO "hello" #define CRM_OP_HBEAT "dc_beat" #define CRM_OP_PECALC "pe_calc" #define CRM_OP_ABORT "abort" #define CRM_OP_QUIT "quit" #define CRM_OP_LOCAL_SHUTDOWN "start_shutdown" #define CRM_OP_SHUTDOWN_REQ "req_shutdown" #define CRM_OP_SHUTDOWN "do_shutdown" #define CRM_OP_FENCE "stonith" #define CRM_OP_EVENTCC "event_cc" #define CRM_OP_TEABORT "te_abort" #define CRM_OP_TEABORTED "te_abort_confirmed" /* we asked */ #define CRM_OP_TE_HALT "te_halt" #define CRM_OP_TECOMPLETE "te_complete" #define CRM_OP_TETIMEOUT "te_timeout" #define CRM_OP_TRANSITION "transition" #define CRM_OP_REGISTER "register" #define CRM_OP_DEBUG_UP "debug_inc" #define CRM_OP_DEBUG_DOWN "debug_dec" #define CRM_OP_INVOKE_LRM "lrm_invoke" #define CRM_OP_LRM_REFRESH "lrm_refresh" #define CRM_OP_LRM_QUERY "lrm_query" #define CRM_OP_LRM_DELETE "lrm_delete" #define CRM_OP_PROBED "probe_complete" #define CRM_OP_REPROBE "probe_again" #define CRMD_STATE_ACTIVE "member" #define CRMD_STATE_INACTIVE "down" #define CRMD_JOINSTATE_DOWN CRMD_STATE_INACTIVE #define CRMD_JOINSTATE_PENDING "pending" #define CRMD_JOINSTATE_MEMBER CRMD_STATE_ACTIVE #define CRMD_ACTION_DELETE "delete" #define CRMD_ACTION_CANCEL "cancel" #define CRMD_ACTION_START "start" #define CRMD_ACTION_STARTED "running" #define CRMD_ACTION_START_FAIL "start_failed" #define CRMD_ACTION_START_PENDING "starting" #define CRMD_ACTION_STOP "stop" #define CRMD_ACTION_STOPPED "stopped" #define CRMD_ACTION_STOP_FAIL "stop_failed" #define CRMD_ACTION_STOP_PENDING "stopping" #define CRMD_ACTION_PROMOTE "promote" #define CRMD_ACTION_PROMOTED "promoted" #define CRMD_ACTION_DEMOTE "demote" #define CRMD_ACTION_DEMOTED "demoted" #define CRMD_ACTION_NOTIFY "notify" #define CRMD_ACTION_NOTIFIED "notified" #define CRMD_ACTION_STATUS "monitor" #define CRMD_ACTION_MON "monitor" #define CRMD_ACTION_MON_PENDING CRMD_ACTION_STARTED #define CRMD_ACTION_MON_OK CRMD_ACTION_STARTED #define CRMD_ACTION_MON_FAIL "monitor_failed" /* #define CRMD_ACTION_GENERIC "pending" */ #define CRMD_ACTION_GENERIC_PENDING "pending" #define CRMD_ACTION_GENERIC_OK "complete" #define CRMD_ACTION_GENERIC_FAIL "pending_failed" typedef GList* GListPtr; #define slist_iter(child, child_type, parent, counter, a) \ { \ GListPtr __crm_iter_head = parent; \ child_type *child = NULL; \ int counter = 0; \ for(; __crm_iter_head != NULL; counter++) { \ child = __crm_iter_head->data; \ __crm_iter_head = __crm_iter_head->next; \ { a; } \ } \ } #define LOG_DEBUG_2 LOG_DEBUG+1 #define LOG_DEBUG_3 LOG_DEBUG+2 #define LOG_DEBUG_4 LOG_DEBUG+3 #define LOG_DEBUG_5 LOG_DEBUG+4 #define LOG_DEBUG_6 LOG_DEBUG+5 #define LOG_MSG LOG_DEBUG_3 #define do_crm_log(level, fmt, args...) do { \ if(crm_log_level < (level)) { \ continue; \ - } else if(level > LOG_DEBUG) { \ + } else if((level) > LOG_DEBUG) { \ cl_log(LOG_DEBUG, "debug%d: %s: " fmt, \ level-LOG_INFO, __PRETTY_FUNCTION__, ##args); \ } else { \ cl_log(level, "%s: " fmt, \ __PRETTY_FUNCTION__, ##args); \ } \ } while(0) #define crm_crit(fmt, args...) do_crm_log(LOG_CRIT, fmt, ##args) #define crm_err(fmt, args...) do_crm_log(LOG_ERR, fmt, ##args) #define crm_warn(fmt, args...) do_crm_log(LOG_WARNING, fmt, ##args) #define crm_notice(fmt, args...) do_crm_log(LOG_NOTICE, fmt, ##args) #define crm_info(fmt, args...) do_crm_log(LOG_INFO, fmt, ##args) #define crm_debug(fmt, args...) do_crm_log(LOG_DEBUG, fmt, ##args) #define crm_debug_2(fmt, args...) do_crm_log(LOG_DEBUG_2, fmt, ##args) #define crm_debug_3(fmt, args...) do_crm_log(LOG_DEBUG_3, fmt, ##args) #define crm_debug_4(fmt, args...) do_crm_log(LOG_DEBUG_4, fmt, ##args) #define crm_debug_5(fmt, args...) do_crm_log(LOG_DEBUG_5, fmt, ##args) #define crm_debug_6(fmt, args...) do_crm_log(LOG_DEBUG_6, fmt, ##args) extern void crm_log_message_adv( int level, const char *alt_debugfile, const HA_Message *msg); -#define crm_log_message(level, msg) if(crm_log_level >= level) { \ +#define crm_log_message(level, msg) if(crm_log_level >= (level)) { \ crm_log_message_adv(level, NULL, msg); \ } -#define crm_log_xml(level, text, xml) if(crm_log_level >= level) { \ +#define crm_log_xml(level, text, xml) if(crm_log_level >= (level)) { \ print_xml_formatted(level, __PRETTY_FUNCTION__, xml, text); \ } #define crm_log_xml_crit(xml, text) crm_log_xml(LOG_CRIT, text, xml) #define crm_log_xml_err(xml, text) crm_log_xml(LOG_ERR, text, xml) #define crm_log_xml_warn(xml, text) crm_log_xml(LOG_WARNING, text, xml) #define crm_log_xml_notice(xml, text) crm_log_xml(LOG_NOTICE, text, xml) #define crm_log_xml_info(xml, text) crm_log_xml(LOG_INFO, text, xml) #define crm_log_xml_debug(xml, text) crm_log_xml(LOG_DEBUG, text, xml) #define crm_log_xml_debug_2(xml, text) crm_log_xml(LOG_DEBUG_2, text, xml) #define crm_log_xml_debug_3(xml, text) crm_log_xml(LOG_DEBUG_3, text, xml) #define crm_log_xml_debug_4(xml, text) crm_log_xml(LOG_DEBUG_4, text, xml) #define crm_log_xml_debug_5(xml, text) crm_log_xml(LOG_DEBUG_5, text, xml) #define crm_str(x) (const char*)(x?x:"") #if CRM_USE_MALLOC # define crm_malloc0(new_obj,length) \ { \ new_obj = malloc(length); \ if(new_obj == NULL) { \ crm_crit("Out of memory... exiting"); \ cl_flush_logs(); \ exit(1); \ } \ memset(new_obj, 0, length); \ } # define crm_realloc(new_obj,length) \ { \ new_obj = realloc(new_obj, length); \ if(new_obj == NULL) { \ crm_crit("Out of memory... exiting"); \ cl_flush_logs(); \ exit(1); \ } \ } # define crm_free(x) if(x) { free(x); x=NULL; } #else # if CRM_DEV_BUILD # define crm_malloc0(new_obj,length) \ { \ if(new_obj) { \ crm_err("Potential memory leak:" \ " %s at %s:%d not NULL before alloc.", \ #new_obj, __FILE__, __LINE__); \ cl_flush_logs(); \ abort(); \ } \ new_obj = cl_malloc(length); \ if(new_obj == NULL) { \ crm_crit("Out of memory... exiting"); \ cl_flush_logs(); \ abort(); \ } \ memset(new_obj, 0, length); \ } /* it's not a memory leak to already have an object to realloc, that's * the usual case, however if it does have a value, it must have been * allocated by the same allocator! */ # define crm_realloc(new_obj,length) \ { \ if (new_obj != NULL) { \ CRM_ASSERT(cl_is_allocated(new_obj) == 1); \ } \ new_obj = cl_realloc(new_obj, length); \ if(new_obj == NULL) { \ crm_crit("Out of memory... exiting"); \ cl_flush_logs(); \ abort(); \ } \ } # define crm_free(x) if(x) { \ CRM_ASSERT(cl_is_allocated(x) == 1); \ cl_free(x); \ x=NULL; \ } # else # define crm_malloc0(new_obj,length) \ { \ new_obj = cl_malloc(length); \ if(new_obj == NULL) { \ crm_crit("Out of memory... exiting"); \ cl_flush_logs(); \ abort(); \ } \ memset(new_obj, 0, length); \ } # define crm_realloc(new_obj,length) \ { \ new_obj = cl_realloc(new_obj, length); \ if(new_obj == NULL) { \ crm_crit("Out of memory... exiting"); \ cl_flush_logs(); \ abort(); \ } \ } # define crm_free(x) if(x) { \ cl_free(x); \ x=NULL; \ } # endif #endif #define crm_msg_del(msg) if(msg != NULL) { ha_msg_del(msg); msg = NULL; } #endif diff --git a/include/crm/msg_xml.h b/include/crm/msg_xml.h index e3a6863fe1..f78f9ee0b4 100644 --- a/include/crm/msg_xml.h +++ b/include/crm/msg_xml.h @@ -1,260 +1,262 @@ /* $Id: msg_xml.h,v 1.56 2006/06/23 08:33:53 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef XML_TAGS__H #define XML_TAGS__H #define F_CRM_TASK "crm_task" #define F_CRM_HOST_TO "crm_host_to" #define F_CRM_MSG_TYPE F_SUBTYPE #define F_CRM_SYS_TO "crm_sys_to" #define F_CRM_SYS_FROM "crm_sys_from" #define F_CRM_HOST_FROM F_ORIG #define F_CRM_REFERENCE XML_ATTR_REFERENCE #define F_CRM_VERSION XML_ATTR_VERSION #define F_CRM_ORIGIN "origin" #define F_CRM_JOIN_ID "join_id" #define F_CRM_ELECTION_ID "election-id" #define F_CRM_ELECTION_OWNER "election-owner" #define F_CRM_TGRAPH "crm-tgraph" #define F_CRM_TGRAPH_INPUT "crm-tgraph-in" /*---- Common tags/attrs */ #define XML_DIFF_MARKER "__crm_diff_marker__" #define XML_ATTR_TAGNAME F_XML_TAGNAME #define XML_ATTR_PARENT F_XML_PARENT #define XML_TAG_CIB "cib" #define XML_TAG_FAILED "failed" #define XML_ATTR_CRM_VERSION "crm_feature_set" #define XML_ATTR_NUMPEERS "num_peers" #define XML_ATTR_HAVE_QUORUM "have_quorum" #define XML_ATTR_CCM_TRANSITION "ccm_transition" #define XML_ATTR_GENERATION "epoch" #define XML_ATTR_GENERATION_ADMIN "admin_epoch" #define XML_ATTR_NUMUPDATES "num_updates" #define XML_ATTR_TIMEOUT "timeout" #define XML_ATTR_ORIGIN "crm-debug-origin" #define XML_ATTR_TSTAMP "crm-timestamp" #define XML_CIB_ATTR_WRITTEN "cib-last-written" #define XML_ATTR_VERSION "version" #define XML_ATTR_DESC "description" #define XML_ATTR_ID "id" #define XML_ATTR_ID_LONG "long-id" #define XML_ATTR_TYPE "type" #define XML_ATTR_FILTER_TYPE "type_filter" #define XML_ATTR_FILTER_ID "id_filter" #define XML_ATTR_FILTER_PRIORITY "priority_filter" #define XML_ATTR_VERBOSE "verbose" #define XML_ATTR_OP "op" #define XML_ATTR_DC "is_dc" #define XML_ATTR_DC_UUID "dc_uuid" #define XML_ATTR_CIB_REVISION "cib_feature_revision" #define XML_ATTR_CIB_REVISION_MAX "cib_feature_revision_max" #define XML_BOOLEAN_TRUE "true" #define XML_BOOLEAN_FALSE "false" #define XML_BOOLEAN_YES XML_BOOLEAN_TRUE #define XML_BOOLEAN_NO XML_BOOLEAN_FALSE #define XML_TAG_OPTIONS "options" /*---- top level tags/attrs */ #define XML_MSG_TAG "crm_message" #define XML_MSG_TAG_DATA "msg_data" #define XML_ATTR_REQUEST "request" #define XML_ATTR_RESPONSE "response" #define XML_ATTR_UNAME "uname" #define XML_ATTR_UUID "id" #define XML_ATTR_REFERENCE "reference" #define XML_FAIL_TAG_RESOURCE "failed_resource" #define XML_FAILRES_ATTR_RESID "resource_id" #define XML_FAILRES_ATTR_REASON "reason" #define XML_FAILRES_ATTR_RESSTATUS "resource_status" #define XML_CRM_TAG_PING "ping_response" #define XML_PING_ATTR_STATUS "result" #define XML_PING_ATTR_SYSFROM "crm_subsystem" #define XML_TAG_FRAGMENT "cib_fragment" #define XML_ATTR_RESULT "result" #define XML_ATTR_SECTION "section" #define XML_FAIL_TAG_CIB "failed_update" #define XML_FAILCIB_ATTR_ID "id" #define XML_FAILCIB_ATTR_OBJTYPE "object_type" #define XML_FAILCIB_ATTR_OP "operation" #define XML_FAILCIB_ATTR_REASON "reason" /*---- CIB specific tags/attrs */ #define XML_CIB_TAG_SECTION_ALL "all" #define XML_CIB_TAG_CONFIGURATION "configuration" #define XML_CIB_TAG_STATUS "status" #define XML_CIB_TAG_RESOURCES "resources" #define XML_CIB_TAG_NODES "nodes" #define XML_CIB_TAG_CONSTRAINTS "constraints" #define XML_CIB_TAG_CRMCONFIG "crm_config" #define XML_CIB_TAG_STATE "node_state" #define XML_CIB_TAG_NODE "node" #define XML_CIB_TAG_CONSTRAINT "constraint" #define XML_CIB_TAG_NVPAIR "nvpair" #define XML_CIB_TAG_PROPSET "cluster_property_set" #define XML_TAG_ATTR_SETS "instance_attributes" #define XML_TAG_META_SETS "meta_attributes" #define XML_TAG_ATTRS "attributes" #define XML_TAG_PARAMS "parameters" #define XML_CIB_TAG_RESOURCE "primitive" #define XML_CIB_TAG_GROUP "group" #define XML_CIB_TAG_INCARNATION "clone" #define XML_CIB_TAG_MASTER "master_slave" #define XML_RSC_ATTR_STOPFAIL "on_stopfail" #define XML_RSC_ATTR_RESTART "restart_type" #define XML_RSC_ATTR_START_TIMEOUT "start_timeout" #define XML_RSC_ATTR_STOP_TIMEOUT "stop_timeout" #define XML_RSC_ATTR_ORDERED "ordered" #define XML_RSC_ATTR_INTERLEAVE "interleave" #define XML_RSC_ATTR_INCARNATION "clone" #define XML_RSC_ATTR_INCARNATION_MAX "clone_max" #define XML_RSC_ATTR_INCARNATION_NODEMAX "clone_node_max" #define XML_RSC_ATTR_MASTER_MAX "master_max" #define XML_RSC_ATTR_MASTER_NODEMAX "master_node_max" #define XML_RSC_ATTR_STATE "clone_state" #define XML_RSC_ATTR_MANAGED "is_managed" #define XML_RSC_ATTR_TARGET_ROLE "target_role" #define XML_RSC_ATTR_UNIQUE "globally_unique" #define XML_RSC_ATTR_NOTIFY "notify" #define XML_RSC_ATTR_STICKINESS "resource_stickiness" #define XML_RSC_ATTR_FAIL_STICKINESS "resource_failure_stickiness" #define XML_RSC_ATTR_MULTIPLE "multiple_active" #define XML_RSC_ATTR_START "start_prereq" #define XML_RSC_ATTR_PRIORITY "priority" #define XML_CIB_TAG_LRM "lrm" #define XML_LRM_TAG_RESOURCES "lrm_resources" #define XML_LRM_TAG_RESOURCE "lrm_resource" #define XML_LRM_TAG_AGENTS "lrm_agents" #define XML_LRM_TAG_AGENT "lrm_agent" #define XML_LRM_TAG_RSC_OP "lrm_rsc_op" #define XML_AGENT_ATTR_CLASS "class" #define XML_AGENT_ATTR_PROVIDER "provider" #define XML_LRM_TAG_ATTRIBUTES "attributes" #define XML_CIB_ATTR_REPLACE "replace" #define XML_CIB_ATTR_SOURCE "source" #define XML_CIB_ATTR_HEALTH "health" #define XML_CIB_ATTR_WEIGHT "weight" #define XML_CIB_ATTR_PRIORITY "priority" #define XML_CIB_ATTR_CLEAR "clear_on" #define XML_CIB_ATTR_SOURCE "source" #define XML_CIB_ATTR_JOINSTATE "join" #define XML_CIB_ATTR_EXPSTATE "expected" #define XML_CIB_ATTR_INCCM "in_ccm" #define XML_CIB_ATTR_CRMDSTATE "crmd" #define XML_CIB_ATTR_HASTATE "ha" #define XML_CIB_ATTR_SHUTDOWN "shutdown" #define XML_CIB_ATTR_STONITH "stonith" #define XML_LRM_ATTR_INTERVAL "interval" #define XML_LRM_ATTR_TASK "operation" #define XML_LRM_ATTR_TASK_KEY "operation_key" #define XML_LRM_ATTR_TARGET "on_node" #define XML_LRM_ATTR_TARGET_UUID "on_node_uuid" #define XML_LRM_ATTR_RSCID "rsc_id" #define XML_LRM_ATTR_OPSTATUS "op_status" #define XML_LRM_ATTR_RC "rc_code" #define XML_LRM_ATTR_CALLID "call_id" #define XML_LRM_ATTR_OP_DIGEST "op_digest" +#define XML_LRM_ATTR_OP_RESTART "op_force_restart" +#define XML_LRM_ATTR_RESTART_DIGEST "op_restart_digest" #define XML_TAG_GRAPH "transition_graph" #define XML_GRAPH_TAG_RSC_OP "rsc_op" #define XML_GRAPH_TAG_PSEUDO_EVENT "pseudo_event" #define XML_GRAPH_TAG_CRM_EVENT "crm_event" #define XML_TAG_RULE "rule" #define XML_RULE_ATTR_SCORE "score" #define XML_RULE_ATTR_SCORE_ATTRIBUTE "score_attribute" #define XML_RULE_ATTR_SCORE_MANGLED "score_attribute_mangled" #define XML_RULE_ATTR_ROLE "role" #define XML_RULE_ATTR_RESULT "result" #define XML_RULE_ATTR_BOOLEAN_OP "boolean_op" #define XML_RULE_ATTR_FROMSTATE "from_role" #define XML_RULE_ATTR_TOSTATE "to_role" #define XML_TAG_EXPRESSION "expression" #define XML_EXPR_ATTR_ATTRIBUTE "attribute" #define XML_EXPR_ATTR_OPERATION "operation" #define XML_EXPR_ATTR_VALUE "value" #define XML_EXPR_ATTR_TYPE "type" #define XML_CONS_TAG_RSC_DEPEND "rsc_colocation" #define XML_CONS_TAG_RSC_ORDER "rsc_order" #define XML_CONS_TAG_RSC_LOCATION "rsc_location" #define XML_CONS_ATTR_FROM "from" #define XML_CONS_ATTR_TO "to" #define XML_CONS_ATTR_ACTION "action" #define XML_CONS_ATTR_TOACTION "to_action" #define XML_CONS_ATTR_SYMMETRICAL "symmetrical" #define XML_NVPAIR_ATTR_NAME "name" #define XML_NVPAIR_ATTR_VALUE "value" #define XML_STRENGTH_VAL_MUST "must" #define XML_STRENGTH_VAL_SHOULD "should" #define XML_STRENGTH_VAL_SHOULDNOT "should_not" #define XML_STRENGTH_VAL_MUSTNOT "must_not" #define XML_NODE_ATTR_STATE "state" #define XML_CONFIG_ATTR_DC_DEADTIME "dc_deadtime" #define XML_CONFIG_ATTR_ELECTION_FAIL "election_timeout" #define XML_CONFIG_ATTR_FORCE_QUIT "shutdown_escalation" #define XML_CONFIG_ATTR_REANNOUNCE "join_reannouce" #define XML_CONFIG_ATTR_RECHECK "cluster_recheck_interval" #define XML_CIB_TAG_GENERATION_TUPPLE "generation_tuple" #define XML_ATTR_TRANSITION_MAGIC "transition_magic" #define XML_ATTR_TRANSITION_KEY "transition_key" #define XML_ATTR_TE_NOWAIT "op_no_wait" #define XML_ATTR_TE_TARGET_RC "op_target_rc" #define XML_ATTR_TE_ALLOWFAIL "op_allow_fail" #define XML_ATTR_LRM_PROBE "lrm-is-probe" #define XML_TAG_TRANSIENT_NODEATTRS "transient_attributes" #include #define ID(x) crm_element_value(x, XML_ATTR_ID) #define INSTANCE(x) crm_element_value(x, XML_CIB_ATTR_INSTANCE) #define TSTAMP(x) crm_element_value(x, XML_ATTR_TSTAMP) #define TYPE(x) crm_element_name(x) #endif diff --git a/include/crm/pengine/common.h b/include/crm/pengine/common.h index 6d0201b100..582752d8c1 100644 --- a/include/crm/pengine/common.h +++ b/include/crm/pengine/common.h @@ -1,154 +1,153 @@ /* $Id: common.h,v 1.4 2006/08/14 09:06:31 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef PE_COMMON__H #define PE_COMMON__H /* * The man pages for both curses and ncurses suggest inclusion of "curses.h". * We believe the following to be acceptable and portable. */ #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBCURSES) #if defined(HAVE_NCURSES_H) && !defined(HAVE_INCOMPATIBLE_PRINTW) # include # define CURSES_ENABLED 1 #elif defined(HAVE_NCURSES_NCURSES_H) && !defined(HAVE_INCOMPATIBLE_PRINTW) # include # define CURSES_ENABLED 1 #elif defined(HAVE_CURSES_H) && !defined(HAVE_INCOMPATIBLE_PRINTW) # include # define CURSES_ENABLED 1 #elif defined(HAVE_CURSES_CURSES_H) && !defined(HAVE_INCOMPATIBLE_PRINTW) # include # define CURSES_ENABLED 1 #else # define CURSES_ENABLED 0 #endif #else # define CURSES_ENABLED 0 #endif extern gboolean was_processing_error; extern gboolean was_processing_warning; extern unsigned int pengine_input_loglevel; /* order is significant here * items listed in order of accending severeness * more severe actions take precedent over lower ones */ enum action_fail_response { action_fail_ignore, action_fail_recover, action_fail_migrate, action_fail_block, /* action_fail_stop, */ action_fail_fence }; /* the "done" action must be the "pre" action +1 */ enum action_tasks { no_action, monitor_rsc, stop_rsc, stopped_rsc, start_rsc, started_rsc, action_notify, action_notified, action_promote, action_promoted, action_demote, action_demoted, shutdown_crm, stonith_node }; enum rsc_recovery_type { recovery_stop_start, recovery_stop_only, recovery_block }; enum rsc_start_requirement { rsc_req_nothing, rsc_req_quorum, rsc_req_stonith }; enum pe_ordering { pe_ordering_manditory, pe_ordering_restart, pe_ordering_recover, pe_ordering_postnotify, pe_ordering_optional }; enum rsc_role_e { RSC_ROLE_UNKNOWN, RSC_ROLE_STOPPED, RSC_ROLE_STARTED, RSC_ROLE_SLAVE, RSC_ROLE_MASTER, }; #define RSC_ROLE_MAX RSC_ROLE_MASTER+1 #define RSC_ROLE_UNKNOWN_S "Unknown" #define RSC_ROLE_STOPPED_S "Stopped" #define RSC_ROLE_STARTED_S "Started" #define RSC_ROLE_SLAVE_S "Slave" #define RSC_ROLE_MASTER_S "Master" enum pe_print_options { pe_print_log = 0x0001, pe_print_html = 0x0002, pe_print_ncurses = 0x0004, pe_print_printf = 0x0008, pe_print_dev = 0x0010, pe_print_details = 0x0020, pe_print_max_details = 0x0040, pe_print_rsconly = 0x0080, }; extern int merge_weights(int w1, int w2); extern const char *task2text(enum action_tasks task); extern enum action_tasks text2task(const char *task); extern enum rsc_role_e text2role(const char *role); extern const char *role2text(enum rsc_role_e role); extern const char *fail2text(enum action_fail_response fail); extern void add_hash_param(GHashTable *hash, const char *name, const char *value); extern void pe_metadata(void); extern void verify_pe_options(GHashTable *options); extern const char *pe_pref(GHashTable *options, const char *name); - /* Helper macros to avoid NULL pointers */ #define safe_val3(def, t,u,v) (t?t->u?t->u->v:def:def) #define safe_val5(def, t,u,v,w,x) (t?t->u?t->u->v?t->u->v->w?t->u->v->w->x:def:def:def:def) #define pe_err(fmt...) { was_processing_error = TRUE; crm_config_error = TRUE; crm_err(fmt); } #define pe_warn(fmt...) { was_processing_warning = TRUE; crm_config_warning = TRUE; crm_warn(fmt); } #define pe_proc_err(fmt...) { was_processing_error = TRUE; crm_err(fmt); } #define pe_proc_warn(fmt...) { was_processing_warning = TRUE; crm_warn(fmt); } #endif diff --git a/lib/crm/cib/cib_attrs.c b/lib/crm/cib/cib_attrs.c index 310acb8af4..118a38ef2d 100644 --- a/lib/crm/cib/cib_attrs.c +++ b/lib/crm/cib/cib_attrs.c @@ -1,634 +1,634 @@ /* $Id: cib_attrs.c,v 1.24 2006/04/18 11:28:56 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define attr_common_setup(section) \ gboolean is_crm_config = FALSE; \ gboolean is_node_transient = FALSE; \ char *local_set_name = NULL; \ if(attr_id == NULL && attr_name == NULL) { \ return cib_missing; \ \ } else if(safe_str_eq(section, XML_CIB_TAG_CRMCONFIG)) { \ tag = NULL; \ is_crm_config = TRUE; \ if(set_name == NULL) { \ set_name = CIB_OPTIONS_FIRST; \ } \ \ } else if(safe_str_eq(section, XML_CIB_TAG_NODES)) { \ tag = XML_CIB_TAG_NODE; \ if(node_uuid == NULL) { \ return cib_missing; \ } \ if(set_name == NULL) { \ local_set_name = crm_concat(section, node_uuid, '-'); \ set_name = local_set_name; \ } \ \ } else if(safe_str_eq(section, XML_CIB_TAG_STATUS)) { \ is_node_transient = TRUE; \ tag = XML_TAG_TRANSIENT_NODEATTRS; \ if(set_name == NULL) { \ local_set_name = crm_concat(section, node_uuid, '-'); \ set_name = local_set_name; \ } \ \ } else { \ return cib_bad_section; \ } \ \ if(attr_id == NULL) { \ local_attr_id = crm_concat(set_name, attr_name, '-'); \ attr_id = local_attr_id; \ \ } else if(attr_name == NULL) { \ attr_name = attr_id; \ } \ crm_data_t * find_attr_details(crm_data_t *xml_search, const char *node_uuid, const char *set_name, const char *attr_id, const char *attr_name) { int matches = 0; crm_data_t *nv_children = NULL; crm_data_t *set_children = NULL; const char *set_type = XML_TAG_ATTR_SETS; if(node_uuid != NULL) { set_type = XML_CIB_TAG_PROPSET; /* filter by node */ matches = find_xml_children( &set_children, xml_search, NULL, XML_ATTR_ID, node_uuid, FALSE); - crm_log_xml_debug(set_children, "search by node:"); + crm_log_xml_debug_2(set_children, "search by node:"); if(matches == 0) { crm_info("No node matching id=%s in %s", node_uuid, TYPE(xml_search)); return NULL; } } /* filter by set name */ if(set_name != NULL) { matches = find_xml_children( &set_children, set_children?set_children:xml_search, XML_TAG_ATTR_SETS, XML_ATTR_ID, set_name, FALSE); - crm_log_xml_debug(set_children, "search by set:"); + crm_log_xml_debug_2(set_children, "search by set:"); if(matches == 0) { crm_info("No set matching id=%s in %s", set_name, TYPE(xml_search)); return NULL; } } matches = 0; if(attr_id == NULL) { matches = find_xml_children( &nv_children, set_children?set_children:xml_search, XML_CIB_TAG_NVPAIR, XML_NVPAIR_ATTR_NAME, attr_name, FALSE); - crm_log_xml_debug(nv_children, "search by name:"); + crm_log_xml_debug_2(nv_children, "search by name:"); } else if(attr_id != NULL) { matches = find_xml_children( &nv_children, set_children?set_children:xml_search, XML_CIB_TAG_NVPAIR, XML_ATTR_ID, attr_id, FALSE); crm_log_xml_debug(nv_children, "search by id:"); } if(matches == 1) { crm_data_t *single_match = NULL; xml_child_iter(nv_children, child, single_match = copy_xml(child); break; ); free_xml(nv_children); return single_match; } else if(matches > 1) { crm_err("Multiple attributes match name=%s in %s:\n", attr_name, TYPE(xml_search)); if(set_children == NULL) { free_xml(set_children); set_children = NULL; find_xml_children( &set_children, xml_search, XML_TAG_ATTR_SETS, NULL, NULL, FALSE); xml_child_iter( set_children, set, free_xml(nv_children); nv_children = NULL; find_xml_children( &nv_children, set, XML_CIB_TAG_NVPAIR, XML_NVPAIR_ATTR_NAME, attr_name, FALSE); xml_child_iter( nv_children, child, crm_info(" Set: %s,\tValue: %s,\tID: %s\n", ID(set), crm_element_value(child, XML_NVPAIR_ATTR_VALUE), ID(child)); ); ); } else { xml_child_iter( nv_children, child, crm_info(" ID: %s, Value: %s\n", ID(child), crm_element_value(child, XML_NVPAIR_ATTR_VALUE)); ); } } return NULL; } enum cib_errors update_attr(cib_t *the_cib, int call_options, const char *section, const char *node_uuid, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value) { const char *tag = NULL; enum cib_errors rc = cib_ok; crm_data_t *xml_top = NULL; crm_data_t *xml_obj = NULL; crm_data_t *xml_search = NULL; char *local_attr_id = NULL; CRM_CHECK(section != NULL, return cib_missing); CRM_CHECK(attr_name != NULL || attr_id != NULL, return cib_missing); if(safe_str_eq(section, XML_CIB_TAG_NODES)) { CRM_CHECK(node_uuid != NULL, return cib_NOTEXISTS); } else if(safe_str_eq(section, XML_CIB_TAG_STATUS)) { CRM_CHECK(node_uuid != NULL, return cib_NOTEXISTS); } rc = the_cib->cmds->query(the_cib, section, &xml_search, cib_sync_call|cib_scope_local); if(rc != cib_ok) { crm_err("Query failed for attribute %s (section=%s, node=%s, set=%s): %s", attr_name, section, crm_str(set_name), crm_str(node_uuid), cib_error2string(rc)); return rc; } xml_obj = find_attr_details( xml_search, node_uuid, set_name, attr_id, attr_name); free_xml(xml_search); if(xml_obj != NULL) { local_attr_id = crm_strdup(ID(xml_obj)); attr_id = local_attr_id; } if(attr_id == NULL || xml_obj == NULL) { attr_common_setup(section); CRM_CHECK(attr_id != NULL, return cib_missing); CRM_CHECK(set_name != NULL, return cib_missing); if(attr_value == NULL) { return cib_missing_data; } if(is_node_transient) { xml_obj = create_xml_node(xml_obj, XML_CIB_TAG_STATE); crm_xml_add(xml_obj, XML_ATTR_ID, node_uuid); if(xml_top == NULL) { xml_top = xml_obj; } } crm_debug_2("Creating %s/%s", section, tag); if(tag != NULL) { xml_obj = create_xml_node(xml_obj, tag); crm_xml_add(xml_obj, XML_ATTR_ID, node_uuid); if(xml_top == NULL) { xml_top = xml_obj; } } if(node_uuid == NULL) { xml_obj = create_xml_node(xml_obj, XML_CIB_TAG_PROPSET); } else { xml_obj = create_xml_node(xml_obj, XML_TAG_ATTR_SETS); } crm_xml_add(xml_obj, XML_ATTR_ID, set_name); if(xml_top == NULL) { xml_top = xml_obj; } xml_obj = create_xml_node(xml_obj, XML_TAG_ATTRS); crm_free(local_set_name); } else { xml_obj = NULL; } xml_obj = create_xml_node(xml_obj, XML_CIB_TAG_NVPAIR); if(xml_top == NULL) { xml_top = xml_obj; } crm_xml_add(xml_obj, XML_ATTR_ID, attr_id); crm_xml_add(xml_obj, XML_NVPAIR_ATTR_NAME, attr_name); crm_xml_add(xml_obj, XML_NVPAIR_ATTR_VALUE, attr_value); crm_log_xml_debug_2(xml_top, "update_attr"); rc = the_cib->cmds->modify(the_cib, section, xml_top, NULL, call_options|cib_quorum_override); if(rc == cib_diff_resync) { /* this is an internal matter - the update succeeded */ rc = cib_ok; } if(rc < cib_ok) { crm_err("Error setting %s=%s (section=%s, set=%s): %s", attr_name, attr_value, section, crm_str(set_name), cib_error2string(rc)); crm_log_xml_info(xml_top, "Update"); } crm_free(local_attr_id); free_xml(xml_top); return rc; } enum cib_errors read_attr(cib_t *the_cib, const char *section, const char *node_uuid, const char *set_name, const char *attr_id, const char *attr_name, char **attr_value) { enum cib_errors rc = cib_ok; crm_data_t *xml_obj = NULL; crm_data_t *xml_next = NULL; crm_data_t *fragment = NULL; CRM_CHECK(section != NULL, return cib_missing); CRM_CHECK(attr_name != NULL || attr_id != NULL, return cib_missing); if(safe_str_eq(section, XML_CIB_TAG_NODES)) { CRM_CHECK(node_uuid != NULL, return cib_NOTEXISTS); } else if(safe_str_eq(section, XML_CIB_TAG_STATUS)) { CRM_CHECK(node_uuid != NULL, return cib_NOTEXISTS); } CRM_ASSERT(attr_value != NULL); *attr_value = NULL; crm_debug("Searching for attribute %s (section=%s, node=%s, set=%s)", attr_name, section, crm_str(node_uuid), crm_str(set_name)); rc = the_cib->cmds->query( the_cib, section, &fragment, cib_sync_call); if(rc != cib_ok) { crm_err("Query failed for attribute %s (section=%s, node=%s, set=%s): %s", attr_name, section, crm_str(set_name), crm_str(node_uuid), cib_error2string(rc)); return rc; } #if CRM_DEPRECATED_SINCE_2_0_4 if(safe_str_eq(crm_element_name(fragment), section)) { xml_obj = fragment; } else { crm_data_t *a_node = NULL; a_node = find_xml_node(fragment, XML_TAG_CIB, TRUE); xml_obj = get_object_root(section, a_node); } #else xml_obj = fragment; CRM_CHECK(safe_str_eq(crm_element_name(xml_obj), section), return cib_output_data); #endif CRM_ASSERT(xml_obj != NULL); crm_log_xml_debug_2(xml_obj, "Result section"); xml_next = find_attr_details( xml_obj, node_uuid, set_name, attr_id, attr_name); if(xml_next != NULL) { *attr_value = crm_element_value_copy( xml_next, XML_NVPAIR_ATTR_VALUE); } free_xml(fragment); return xml_next == NULL?cib_NOTEXISTS:cib_ok; } enum cib_errors delete_attr(cib_t *the_cib, int options, const char *section, const char *node_uuid, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value) { enum cib_errors rc = cib_ok; crm_data_t *xml_obj = NULL; crm_data_t *xml_search = NULL; char *local_attr_id = NULL; CRM_CHECK(section != NULL, return cib_missing); CRM_CHECK(attr_name != NULL || attr_id != NULL, return cib_missing); if(safe_str_eq(section, XML_CIB_TAG_NODES)) { CRM_CHECK(node_uuid != NULL, return cib_NOTEXISTS); } else if(safe_str_eq(section, XML_CIB_TAG_STATUS)) { CRM_CHECK(node_uuid != NULL, return cib_NOTEXISTS); } if(attr_id == NULL || attr_value != NULL) { rc = the_cib->cmds->query(the_cib, section, &xml_search, cib_sync_call|cib_scope_local); if(rc != cib_ok) { crm_err("Query failed for section=%s of the CIB: %s", section, cib_error2string(rc)); return rc; } xml_obj = find_attr_details( xml_search, node_uuid, set_name, attr_id, attr_name); free_xml(xml_search); if(xml_obj != NULL) { if(attr_value != NULL) { const char *current = crm_element_value(xml_obj, XML_NVPAIR_ATTR_VALUE); if(safe_str_neq(attr_value, current)) { return cib_NOTEXISTS; } } local_attr_id = crm_strdup(ID(xml_obj)); attr_id = local_attr_id; xml_obj = NULL; } } if(attr_id == NULL) { return cib_NOTEXISTS; } xml_obj = create_xml_node(NULL, XML_CIB_TAG_NVPAIR); crm_xml_add(xml_obj, XML_ATTR_ID, attr_id); crm_xml_add(xml_obj, XML_NVPAIR_ATTR_NAME, attr_name); crm_xml_add(xml_obj, XML_NVPAIR_ATTR_VALUE, attr_value); rc = the_cib->cmds->delete( the_cib, section, xml_obj, NULL, options|cib_quorum_override); crm_free(local_attr_id); free_xml(xml_obj); return rc; } enum cib_errors query_node_uuid(cib_t *the_cib, const char *uname, char **uuid) { enum cib_errors rc = cib_ok; crm_data_t *xml_obj = NULL; crm_data_t *fragment = NULL; const char *child_name = NULL; CRM_ASSERT(uname != NULL); CRM_ASSERT(uuid != NULL); rc = the_cib->cmds->query(the_cib, XML_CIB_TAG_NODES, &fragment, cib_sync_call|cib_scope_local); if(rc != cib_ok) { return rc; } #if CRM_DEPRECATED_SINCE_2_0_4 if(safe_str_eq(crm_element_name(fragment), XML_CIB_TAG_NODES)) { xml_obj = fragment; } else { xml_obj = find_xml_node(fragment, XML_TAG_CIB, TRUE); xml_obj = get_object_root(XML_CIB_TAG_NODES, xml_obj); } #else xml_obj = fragment; CRM_CHECK(safe_str_eq(crm_element_name(xml_obj), XML_CIB_TAG_NODES), return cib_output_data); #endif CRM_ASSERT(xml_obj != NULL); crm_log_xml_debug(xml_obj, "Result section"); rc = cib_NOTEXISTS; *uuid = NULL; xml_child_iter_filter( xml_obj, a_child, XML_CIB_TAG_NODE, child_name = crm_element_value(a_child, XML_ATTR_UNAME); if(safe_str_eq(uname, child_name)) { child_name = ID(a_child); if(child_name != NULL) { *uuid = crm_strdup(child_name); rc = cib_ok; } break; } ); free_xml(fragment); return rc; } enum cib_errors query_node_uname(cib_t *the_cib, const char *uuid, char **uname) { enum cib_errors rc = cib_ok; crm_data_t *xml_obj = NULL; crm_data_t *fragment = NULL; const char *child_name = NULL; CRM_ASSERT(uname != NULL); CRM_ASSERT(uuid != NULL); rc = the_cib->cmds->query(the_cib, XML_CIB_TAG_NODES, &fragment, cib_sync_call|cib_scope_local); if(rc != cib_ok) { return rc; } #if CRM_DEPRECATED_SINCE_2_0_4 if(safe_str_eq(crm_element_name(fragment), XML_CIB_TAG_NODES)) { xml_obj = fragment; } else { xml_obj = find_xml_node(fragment, XML_TAG_CIB, TRUE); xml_obj = get_object_root(XML_CIB_TAG_NODES, xml_obj); } #else xml_obj = fragment; CRM_CHECK(safe_str_eq(crm_element_name(xml_obj), XML_CIB_TAG_NODES), return cib_output_data); #endif CRM_ASSERT(xml_obj != NULL); crm_log_xml_debug_2(xml_obj, "Result section"); rc = cib_NOTEXISTS; *uname = NULL; xml_child_iter_filter( xml_obj, a_child, XML_CIB_TAG_NODE, child_name = ID(a_child); if(safe_str_eq(uuid, child_name)) { child_name = crm_element_value(a_child, XML_ATTR_UNAME); if(child_name != NULL) { *uname = crm_strdup(child_name); rc = cib_ok; } break; } ); free_xml(fragment); return rc; } #define standby_common char *attr_id = NULL; \ int str_length = 3; \ char *set_name = NULL; \ const char *attr_name = "standby"; \ \ CRM_CHECK(uuid != NULL, return cib_missing_data); \ str_length += strlen(attr_name); \ str_length += strlen(uuid); \ if(safe_str_eq(type, "reboot") \ || safe_str_eq(type, XML_CIB_TAG_STATUS)) { \ const char *extra = "transient"; \ type = XML_CIB_TAG_STATUS; \ str_length += strlen(extra); \ crm_malloc0(attr_id, str_length); \ sprintf(attr_id, "%s-%s-%s", extra, attr_name, uuid); \ \ } else { \ crm_malloc0(attr_id, str_length); \ sprintf(attr_id, "%s-%s", attr_name, uuid); \ } enum cib_errors query_standby(cib_t *the_cib, const char *uuid, char **scope, char **standby_value) { enum cib_errors rc = cib_ok; CRM_CHECK(standby_value != NULL, return cib_missing_data); CRM_CHECK(scope != NULL, return cib_missing_data); if(*scope != NULL) { const char *type = *scope; standby_common; rc = read_attr(the_cib, type, uuid, set_name, attr_id, attr_name, standby_value); crm_free(attr_id); crm_free(set_name); } else { *scope = crm_strdup(XML_CIB_TAG_NODES); rc = query_standby(the_cib, uuid, scope, standby_value); if(rc == cib_NOTEXISTS) { crm_free(*scope); *scope = crm_strdup(XML_CIB_TAG_STATUS); crm_debug("No standby value found with " "lifetime=forever, checking lifetime=reboot"); rc = query_standby(the_cib, uuid, scope, standby_value); } } return rc; } enum cib_errors set_standby(cib_t *the_cib, const char *uuid, const char *scope, const char *standby_value) { enum cib_errors rc = cib_ok; CRM_CHECK(standby_value != NULL, return cib_missing_data); if(scope != NULL) { const char *type = scope; standby_common; rc = update_attr(the_cib, cib_sync_call, type, uuid, set_name, attr_id, attr_name, standby_value); crm_free(attr_id); crm_free(set_name); } else { rc = set_standby(the_cib, uuid, XML_CIB_TAG_NODES, standby_value); } return rc; } enum cib_errors delete_standby(cib_t *the_cib, const char *uuid, const char *scope, const char *standby_value) { enum cib_errors rc = cib_ok; if(scope != NULL) { const char *type = scope; standby_common; rc = delete_attr(the_cib, cib_sync_call, type, uuid, set_name, attr_id, attr_name, standby_value); crm_free(attr_id); crm_free(set_name); } else { rc = delete_standby( the_cib, uuid, XML_CIB_TAG_STATUS, standby_value); rc = delete_standby( the_cib, uuid, XML_CIB_TAG_NODES, standby_value); } return rc; } diff --git a/lib/crm/common/utils.c b/lib/crm/common/utils.c index be9284030a..7ef6a30f38 100644 --- a/lib/crm/common/utils.c +++ b/lib/crm/common/utils.c @@ -1,1802 +1,1823 @@ /* $Id: utils.c,v 1.64 2006/08/14 09:06:31 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef MAXLINE # define MAXLINE 512 #endif static uint ref_counter = 0; gboolean crm_assert_failed = FALSE; unsigned int crm_log_level = LOG_INFO; gboolean crm_config_error = FALSE; gboolean crm_config_warning = FALSE; void crm_set_env_options(void); gboolean check_time(const char *value) { if(crm_get_msec(value) < 5000) { return FALSE; } return TRUE; } gboolean check_timer(const char *value) { if(crm_get_msec(value) < 0) { return FALSE; } return TRUE; } gboolean check_boolean(const char *value) { int tmp = FALSE; if(crm_str_to_boolean(value, &tmp) != 1) { return FALSE; } return TRUE; } gboolean check_number(const char *value) { errno = 0; if(value == NULL) { return FALSE; } else if(safe_str_eq(value, MINUS_INFINITY_S)) { } else if(safe_str_eq(value, INFINITY_S)) { } else { crm_int_helper(value, NULL); } if(errno != 0) { return FALSE; } return TRUE; } int char2score(const char *score) { int score_f = 0; if(score == NULL) { } else if(safe_str_eq(score, MINUS_INFINITY_S)) { score_f = -INFINITY; } else if(safe_str_eq(score, INFINITY_S)) { score_f = INFINITY; } else if(safe_str_eq(score, "+"INFINITY_S)) { score_f = INFINITY; } else { score_f = crm_parse_int(score, NULL); if(score_f > 0 && score_f > INFINITY) { score_f = INFINITY; } else if(score_f < 0 && score_f < -INFINITY) { score_f = -INFINITY; } } return score_f; } char * score2char(int score) { if(score >= INFINITY) { return crm_strdup("+"INFINITY_S); } else if(score <= -INFINITY) { return crm_strdup("-"INFINITY_S); } return crm_itoa(score); } const char * cluster_option(GHashTable* options, gboolean(*validate)(const char*), const char *name, const char *old_name, const char *def_value) { const char *value = NULL; CRM_ASSERT(name != NULL); CRM_ASSERT(options != NULL); value = g_hash_table_lookup(options, name); if(value == NULL && old_name) { value = g_hash_table_lookup(options, old_name); if(value != NULL) { crm_config_warn("Using deprecated name '%s' for" " cluster option '%s'", old_name, name); g_hash_table_insert( options, crm_strdup(name), crm_strdup(value)); value = g_hash_table_lookup(options, old_name); } } if(value == NULL) { g_hash_table_insert( options, crm_strdup(name), crm_strdup(def_value)); value = g_hash_table_lookup(options, name); crm_notice("Using default value '%s' for cluster option '%s'", value, name); } if(validate && validate(value) == FALSE) { crm_config_err("Value '%s' for cluster option '%s' is invalid." " Defaulting to %s", value, name, def_value); g_hash_table_replace(options, crm_strdup(name), crm_strdup(def_value)); value = g_hash_table_lookup(options, name); } return value; } const char * get_cluster_pref(GHashTable *options, pe_cluster_option *option_list, int len, const char *name) { int lpc = 0; const char *value = NULL; gboolean found = FALSE; for(lpc = 0; lpc < len; lpc++) { if(safe_str_eq(name, option_list[lpc].name)) { found = TRUE; value = cluster_option(options, option_list[lpc].is_valid, option_list[lpc].name, option_list[lpc].alt_name, option_list[lpc].default_value); } } CRM_CHECK(found, crm_err("No option named: %s", name)); CRM_ASSERT(value != NULL); return value; } void config_metadata(const char *name, const char *version, const char *desc_short, const char *desc_long, pe_cluster_option *option_list, int len) { int lpc = 0; fprintf(stdout, "" "\n" "\n" " %s\n" " %s\n" " %s\n" " \n", name, version, desc_long, desc_short); for(lpc = 0; lpc < len; lpc++) { if(option_list[lpc].description_long == NULL && option_list[lpc].description_short == NULL) { continue; } fprintf(stdout, " \n" " %s\n" " \n" " %s%s%s\n" " \n", option_list[lpc].name, option_list[lpc].description_short, option_list[lpc].type, option_list[lpc].default_value, option_list[lpc].description_long?option_list[lpc].description_long:option_list[lpc].description_short, option_list[lpc].values?" Allowed values: ":"", option_list[lpc].values?option_list[lpc].values:""); } fprintf(stdout, " \n\n"); } void verify_all_options(GHashTable *options, pe_cluster_option *option_list, int len) { int lpc = 0; for(lpc = 0; lpc < len; lpc++) { cluster_option(options, option_list[lpc].is_valid, option_list[lpc].name, option_list[lpc].alt_name, option_list[lpc].default_value); } } char * generateReference(const char *custom1, const char *custom2) { const char *local_cust1 = custom1; const char *local_cust2 = custom2; int reference_len = 4; char *since_epoch = NULL; reference_len += 20; /* too big */ reference_len += 40; /* too big */ if(local_cust1 == NULL) { local_cust1 = "_empty_"; } reference_len += strlen(local_cust1); if(local_cust2 == NULL) { local_cust2 = "_empty_"; } reference_len += strlen(local_cust2); crm_malloc0(since_epoch, reference_len); if(since_epoch != NULL) { sprintf(since_epoch, "%s-%s-%ld-%u", local_cust1, local_cust2, (unsigned long)time(NULL), ref_counter++); } return since_epoch; } gboolean decodeNVpair(const char *srcstring, char separator, char **name, char **value) { int lpc = 0; int len = 0; const char *temp = NULL; CRM_ASSERT(name != NULL && value != NULL); *name = NULL; *value = NULL; crm_debug_4("Attempting to decode: [%s]", srcstring); if (srcstring != NULL) { len = strlen(srcstring); while(lpc <= len) { if (srcstring[lpc] == separator) { crm_malloc0(*name, lpc+1); if(*name == NULL) { break; /* and return FALSE */ } strncpy(*name, srcstring, lpc); (*name)[lpc] = '\0'; /* this sucks but as the strtok manpage says.. * it *is* a bug */ len = len-lpc; len--; if(len <= 0) { *value = NULL; } else { crm_malloc0(*value, len+1); if(*value == NULL) { crm_free(*name); break; /* and return FALSE */ } temp = srcstring+lpc+1; strncpy(*value, temp, len); (*value)[len] = '\0'; } return TRUE; } lpc++; } } if(*name != NULL) { crm_free(*name); } *name = NULL; *value = NULL; return FALSE; } char * crm_concat(const char *prefix, const char *suffix, char join) { int len = 0; char *new_str = NULL; CRM_ASSERT(prefix != NULL); CRM_ASSERT(suffix != NULL); len = strlen(prefix) + strlen(suffix) + 2; crm_malloc0(new_str, (len)); sprintf(new_str, "%s%c%s", prefix, join, suffix); new_str[len-1] = 0; return new_str; } char * generate_hash_key(const char *crm_msg_reference, const char *sys) { char *hash_key = crm_concat(sys?sys:"none", crm_msg_reference, '_'); crm_debug_3("created hash key: (%s)", hash_key); return hash_key; } char * generate_hash_value(const char *src_node, const char *src_subsys) { char *hash_value = NULL; if (src_node == NULL || src_subsys == NULL) { return NULL; } if (strcasecmp(CRM_SYSTEM_DC, src_subsys) == 0) { hash_value = crm_strdup(src_subsys); if (!hash_value) { crm_err("memory allocation failed in " "generate_hash_value()"); } return hash_value; } hash_value = crm_concat(src_node, src_subsys, '_'); crm_info("created hash value: (%s)", hash_value); return hash_value; } gboolean decode_hash_value(gpointer value, char **node, char **subsys) { char *char_value = (char*)value; int value_len = strlen(char_value); CRM_CHECK(value != NULL, return FALSE); CRM_CHECK(node != NULL, return FALSE); CRM_CHECK(subsys != NULL, return FALSE); *node = NULL; *subsys = NULL; crm_info("Decoding hash value: (%s:%d)", char_value, value_len); if (strcasecmp(CRM_SYSTEM_DC, (char*)value) == 0) { *node = NULL; *subsys = (char*)crm_strdup(char_value); CRM_CHECK(*subsys != NULL, return FALSE); crm_info("Decoded value: (%s:%d)", *subsys, (int)strlen(*subsys)); return TRUE; } else if (decodeNVpair(char_value, '_', node, subsys)) { return TRUE; } crm_free(*node); crm_free(*subsys); *node = NULL; *subsys = NULL; return FALSE; } char * crm_itoa(int an_int) { int len = 32; char *buffer = NULL; crm_malloc0(buffer, (len+1)); if(buffer != NULL) { snprintf(buffer, len, "%d", an_int); } return buffer; } extern int LogToLoggingDaemon(int priority, const char * buf, int bstrlen, gboolean use_pri_str); gboolean crm_log_init(const char *entity) { /* const char *test = "Testing log daemon connection"; */ /* Redirect messages from glib functions to our handler */ /* cl_malloc_forced_for_glib(); */ g_log_set_handler(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_RECURSION | G_LOG_FLAG_FATAL, cl_glib_msg_handler, NULL); /* and for good measure... - this enum is a bit field (!) */ g_log_set_always_fatal((GLogLevelFlags)0); /*value out of range*/ cl_log_set_entity(entity); cl_log_set_facility(LOG_LOCAL7); cl_set_corerootdir(HA_COREDIR); cl_cdtocoredir(); crm_set_env_options(); CL_SIGNAL(DEBUG_INC, alter_debug); CL_SIGNAL(DEBUG_DEC, alter_debug); return TRUE; } /* returns the old value */ unsigned int set_crm_log_level(unsigned int level) { unsigned int old = crm_log_level; while(crm_log_level < 100 && crm_log_level < level) { alter_debug(DEBUG_INC); } while(crm_log_level > 0 && crm_log_level > level) { alter_debug(DEBUG_DEC); } return old; } unsigned int get_crm_log_level(void) { return crm_log_level; } void crm_log_message_adv(int level, const char *prefix, const HA_Message *msg) { if((int)crm_log_level >= level) { do_crm_log(level, "#========= %s message start ==========#", prefix?prefix:""); if(level > LOG_DEBUG) { cl_log_message(LOG_DEBUG, msg); } else { cl_log_message(level, msg); } } } int compare_version(const char *version1, const char *version2) { int lpc = 0; char *step1 = NULL, *step2 = NULL; char *rest1 = NULL, *rest2 = NULL; if(version1 == NULL && version2 == NULL) { return 0; } else if(version1 == NULL) { return -1; } else if(version2 == NULL) { return 1; } rest1 = crm_strdup(version1); rest2 = crm_strdup(version2); while(1) { int cmp = 0; int step1_i = 0; int step2_i = 0; char *tmp1 = NULL, *tmp2 = NULL; decodeNVpair(rest1, '.', &step1, &tmp1); decodeNVpair(rest2, '.', &step2, &tmp2); if(step1 != NULL) { step1_i = crm_parse_int(step1, NULL); } if(step2 != NULL) { step2_i = crm_parse_int(step2, NULL); } if(step1_i < step2_i){ cmp = -1; } else if (step1_i > step2_i){ cmp = 1; } crm_debug_4("compare[%d (%d)]: %d(%s) %d(%s)", lpc++, cmp, step1_i, crm_str(step1), step2_i, crm_str(step2)); crm_free(rest1); crm_free(rest2); rest1 = tmp1; rest2 = tmp2; if(step1 == NULL && step2 == NULL) { break; } crm_free(step1); crm_free(step2); if(cmp < 0) { crm_debug_3("%s < %s", version1, version2); return -1; } else if(cmp > 0) { crm_debug_3("%s > %s", version1, version2); return 1; } } crm_debug_3("%s == %s", version1, version2); return 0; } gboolean do_stderr = FALSE; void alter_debug(int nsig) { CL_SIGNAL(DEBUG_INC, alter_debug); CL_SIGNAL(DEBUG_DEC, alter_debug); switch(nsig) { case DEBUG_INC: if (crm_log_level < 100) { crm_log_level++; } break; case DEBUG_DEC: if (crm_log_level > 0) { crm_log_level--; } break; default: fprintf(stderr, "Unknown signal %d\n", nsig); cl_log(LOG_ERR, "Unknown signal %d", nsig); break; } } void g_hash_destroy_str(gpointer data) { crm_free(data); } int crm_int_helper(const char *text, char **end_text) { int atoi_result = -1; char *local_end_text = NULL; errno = 0; if(text != NULL) { if(end_text != NULL) { atoi_result = (int)strtol(text, end_text, 10); } else { atoi_result = (int)strtol(text, &local_end_text, 10); } /* CRM_CHECK(errno != EINVAL); */ if(errno == EINVAL) { crm_err("Conversion of %s failed", text); atoi_result = -1; } else { if(errno == ERANGE) { crm_err("Conversion of %s was clipped", text); } if(end_text == NULL && local_end_text[0] != '\0') { crm_err("Characters left over after parsing " "\"%s\": \"%s\"", text, local_end_text); } } } return atoi_result; } int crm_parse_int(const char *text, const char *default_text) { int atoi_result = -1; if(text != NULL) { atoi_result = crm_int_helper(text, NULL); if(errno == 0) { return atoi_result; } } if(default_text != NULL) { atoi_result = crm_int_helper(default_text, NULL); if(errno == 0) { return atoi_result; } } else { crm_err("No default conversion value supplied"); } return -1; } gboolean safe_str_eq(const char *a, const char *b) { if(a == b) { return TRUE; } else if(a == NULL || b == NULL) { return FALSE; } else if(strcasecmp(a, b) == 0) { return TRUE; } return FALSE; } gboolean safe_str_neq(const char *a, const char *b) { if(a == b) { return FALSE; } else if(a==NULL || b==NULL) { return TRUE; } else if(strcasecmp(a, b) == 0) { return FALSE; } return TRUE; } char * crm_strdup(const char *a) { CRM_CHECK(a != NULL, return NULL); return cl_strdup(a); } static GHashTable *crm_uuid_cache = NULL; static GHashTable *crm_uname_cache = NULL; void unget_uuid(const char *uname) { if(crm_uuid_cache == NULL) { return; } g_hash_table_remove(crm_uuid_cache, uname); } const char * get_uuid(ll_cluster_t *hb, const char *uname) { cl_uuid_t uuid_raw; char *uuid_calc = NULL; const char *unknown = "00000000-0000-0000-0000-000000000000"; if(crm_uuid_cache == NULL) { crm_uuid_cache = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); } CRM_CHECK(uname != NULL, return NULL); /* avoid blocking calls where possible */ uuid_calc = g_hash_table_lookup(crm_uuid_cache, uname); if(uuid_calc != NULL) { return uuid_calc; } if(hb->llc_ops->get_uuid_by_name(hb, uname, &uuid_raw) == HA_FAIL) { crm_err("get_uuid_by_name() call failed for host %s", uname); crm_free(uuid_calc); return NULL; } crm_malloc0(uuid_calc, 50); if(uuid_calc == NULL) { return NULL; } cl_uuid_unparse(&uuid_raw, uuid_calc); if(safe_str_eq(uuid_calc, unknown)) { crm_warn("Could not calculate UUID for %s", uname); crm_free(uuid_calc); return NULL; } g_hash_table_insert(crm_uuid_cache, crm_strdup(uname), uuid_calc); uuid_calc = g_hash_table_lookup(crm_uuid_cache, uname); return uuid_calc; } const char * get_uname(ll_cluster_t *hb, const char *uuid) { char *uname = NULL; if(crm_uuid_cache == NULL) { crm_uname_cache = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); } CRM_CHECK(uuid != NULL, return NULL); /* avoid blocking calls where possible */ uname = g_hash_table_lookup(crm_uname_cache, uuid); if(uname != NULL) { return uname; } if(uuid != NULL) { cl_uuid_t uuid_raw; char *uuid_copy = crm_strdup(uuid); cl_uuid_parse(uuid_copy, &uuid_raw); if(hb->llc_ops->get_name_by_uuid( hb, &uuid_raw, uname, 256) == HA_FAIL) { crm_err("Could not calculate UUID for %s", uname); uname = NULL; crm_free(uuid_copy); } else { g_hash_table_insert( crm_uuid_cache, uuid_copy, crm_strdup(uname)); uname = g_hash_table_lookup(crm_uname_cache, uuid); } return uname; } return NULL; } void set_uuid(ll_cluster_t *hb,crm_data_t *node,const char *attr,const char *uname) { const char *uuid_calc = get_uuid(hb, uname); crm_xml_add(node, attr, uuid_calc); return; } #define ENV_PREFIX "HA_" void crm_set_env_options(void) { char *param_val = NULL; const char *param_name = NULL; /* apparently we're not allowed to free the result of getenv */ param_name = ENV_PREFIX "" KEY_DEBUGLEVEL; param_val = getenv(param_name); if(param_val != NULL) { int debug_level = crm_parse_int(param_val, NULL); if(debug_level > 0 && (debug_level+LOG_INFO) > (int)crm_log_level) { set_crm_log_level(LOG_INFO + debug_level); } crm_debug("%s = %s", param_name, param_val); param_val = NULL; } param_name = ENV_PREFIX "" KEY_FACILITY; param_val = getenv(param_name); crm_debug("%s = %s", param_name, param_val); if(param_val != NULL) { int facility = cl_syslogfac_str2int(param_val); if(facility > 0) { cl_log_set_facility(facility); } param_val = NULL; } param_name = ENV_PREFIX "" KEY_LOGFILE; param_val = getenv(param_name); crm_debug("%s = %s", param_name, param_val); if(param_val != NULL) { if(safe_str_eq("/dev/null", param_val)) { param_val = NULL; } cl_log_set_logfile(param_val); param_val = NULL; } param_name = ENV_PREFIX "" KEY_DBGFILE; param_val = getenv(param_name); crm_debug("%s = %s", param_name, param_val); if(param_val != NULL) { if(safe_str_eq("/dev/null", param_val)) { param_val = NULL; } cl_log_set_debugfile(param_val); param_val = NULL; } param_name = ENV_PREFIX "" KEY_LOGDAEMON; param_val = getenv(param_name); crm_debug("%s = %s", param_name, param_val); if(param_val != NULL) { int uselogd; cl_str_to_boolean(param_val, &uselogd); cl_log_set_uselogd(uselogd); if(uselogd) { cl_set_logging_wqueue_maxlen(500); cl_log_set_logd_channel_source(NULL, NULL); } param_val = NULL; } param_name = ENV_PREFIX "" KEY_CONNINTVAL; param_val = getenv(param_name); crm_debug("%s = %s", param_name, param_val); if(param_val != NULL) { int logdtime; logdtime = crm_get_msec(param_val); cl_log_set_logdtime(logdtime); param_val = NULL; } inherit_compress(); } gboolean crm_is_true(const char * s) { gboolean ret = FALSE; if(s != NULL) { cl_str_to_boolean(s, &ret); } return ret; } int crm_str_to_boolean(const char * s, int * ret) { if(s == NULL) { return -1; } else if (strcasecmp(s, "true") == 0 || strcasecmp(s, "on") == 0 || strcasecmp(s, "yes") == 0 || strcasecmp(s, "y") == 0 || strcasecmp(s, "1") == 0){ *ret = TRUE; return 1; } else if (strcasecmp(s, "false") == 0 || strcasecmp(s, "off") == 0 || strcasecmp(s, "no") == 0 || strcasecmp(s, "n") == 0 || strcasecmp(s, "0") == 0){ *ret = FALSE; return 1; } return -1; } #ifndef NUMCHARS # define NUMCHARS "0123456789." #endif #ifndef WHITESPACE # define WHITESPACE " \t\n\r\f" #endif long crm_get_msec(const char * input) { const char * cp = input; const char * units; long multiplier = 1000; long divisor = 1; long ret = -1; double dret; if(input == NULL) { return 0; } cp += strspn(cp, WHITESPACE); units = cp + strspn(cp, NUMCHARS); units += strspn(units, WHITESPACE); if (strchr(NUMCHARS, *cp) == NULL) { return ret; } if (strncasecmp(units, "ms", 2) == 0 || strncasecmp(units, "msec", 4) == 0) { multiplier = 1; divisor = 1; }else if (strncasecmp(units, "us", 2) == 0 || strncasecmp(units, "usec", 4) == 0) { multiplier = 1; divisor = 1000; }else if (strncasecmp(units, "s", 1) == 0 || strncasecmp(units, "sec", 3) == 0) { multiplier = 1000; divisor = 1; }else if (strncasecmp(units, "m", 1) == 0 || strncasecmp(units, "min", 3) == 0) { multiplier = 60*1000; divisor = 1; }else if (strncasecmp(units, "h", 1) == 0 || strncasecmp(units, "hr", 2) == 0) { multiplier = 60*60*1000; divisor = 1; }else if (*units != EOS && *units != '\n' && *units != '\r') { return ret; } dret = atof(cp); dret *= (double)multiplier; dret /= (double)divisor; dret += 0.5; ret = (long)dret; return(ret); } gboolean ccm_have_quorum(oc_ed_t event) { if(event==OC_EV_MS_NEW_MEMBERSHIP) { return TRUE; } return FALSE; } const char * ccm_event_name(oc_ed_t event) { if(event==OC_EV_MS_NEW_MEMBERSHIP) { return "NEW MEMBERSHIP"; } else if(event==OC_EV_MS_NOT_PRIMARY) { return "NOT PRIMARY"; } else if(event==OC_EV_MS_PRIMARY_RESTORED) { return "PRIMARY RESTORED"; } else if(event==OC_EV_MS_EVICTED) { return "EVICTED"; } else if(event==OC_EV_MS_INVALID) { return "INVALID"; } return "NO QUORUM MEMBERSHIP"; } const char * op_status2text(op_status_t status) { switch(status) { case LRM_OP_PENDING: return "pending"; break; case LRM_OP_DONE: return "complete"; break; case LRM_OP_ERROR: return "Error"; break; case LRM_OP_TIMEOUT: return "Timed Out"; break; case LRM_OP_NOTSUPPORTED: return "NOT SUPPORTED"; break; case LRM_OP_CANCELLED: return "Cancelled"; break; } CRM_CHECK(status >= LRM_OP_PENDING && status <= LRM_OP_CANCELLED, crm_err("Unknown status: %d", status)); return "UNKNOWN!"; } char * generate_op_key(const char *rsc_id, const char *op_type, int interval) { int len = 35; char *op_id = NULL; CRM_CHECK(rsc_id != NULL, return NULL); CRM_CHECK(op_type != NULL, return NULL); len += strlen(op_type); len += strlen(rsc_id); crm_malloc0(op_id, len); CRM_CHECK(op_id != NULL, return NULL); sprintf(op_id, "%s_%s_%d", rsc_id, op_type, interval); return op_id; } gboolean parse_op_key(const char *key, char **rsc_id, char **op_type, int *interval) { char *mutable_key = NULL; char *mutable_key_ptr = NULL; int len = 0, offset = 0, ch = 0; CRM_CHECK(key != NULL, return FALSE); *interval = 0; len = strlen(key); offset = len-1; crm_debug_3("Source: %s", key); while(offset > 0 && isdigit(key[offset])) { int digits = len-offset; ch = key[offset] - '0'; CRM_CHECK(ch < 10, return FALSE); CRM_CHECK(ch >= 0, return FALSE); while(digits > 1) { digits--; ch = ch * 10; } *interval += ch; offset--; } crm_debug_3(" Interval: %d", *interval); CRM_CHECK(key[offset] == '_', return FALSE); mutable_key = crm_strdup(key); mutable_key_ptr = mutable_key_ptr; mutable_key[offset] = 0; offset--; while(offset > 0 && key[offset] != '_') { offset--; } CRM_CHECK(key[offset] == '_', crm_free(mutable_key); return FALSE); mutable_key_ptr = mutable_key+offset+1; crm_debug_3(" Action: %s", mutable_key_ptr); *op_type = crm_strdup(mutable_key_ptr); mutable_key[offset] = 0; offset--; CRM_CHECK(mutable_key != mutable_key_ptr, crm_free(mutable_key); return FALSE); crm_debug_3(" Resource: %s", mutable_key); *rsc_id = crm_strdup(mutable_key); crm_free(mutable_key); return TRUE; } char * generate_notify_key(const char *rsc_id, const char *notify_type, const char *op_type) { int len = 12; char *op_id = NULL; CRM_CHECK(rsc_id != NULL, return NULL); CRM_CHECK(op_type != NULL, return NULL); CRM_CHECK(notify_type != NULL, return NULL); len += strlen(op_type); len += strlen(rsc_id); len += strlen(notify_type); crm_malloc0(op_id, len); if(op_id != NULL) { sprintf(op_id, "%s_%s_notify_%s_0", rsc_id, notify_type, op_type); } return op_id; } char * generate_transition_magic_v202(const char *transition_key, int op_status) { int len = 80; char *fail_state = NULL; CRM_CHECK(transition_key != NULL, return NULL); len += strlen(transition_key); crm_malloc0(fail_state, len); if(fail_state != NULL) { snprintf(fail_state, len, "%d:%s", op_status,transition_key); } return fail_state; } char * generate_transition_magic(const char *transition_key, int op_status, int op_rc) { int len = 80; char *fail_state = NULL; CRM_CHECK(transition_key != NULL, return NULL); len += strlen(transition_key); crm_malloc0(fail_state, len); if(fail_state != NULL) { snprintf(fail_state, len, "%d:%d;%s", op_status, op_rc, transition_key); } return fail_state; } gboolean decode_transition_magic( - const char *magic, char **uuid, int *transition_id, + const char *magic, char **uuid, int *transition_id, int *action_id, int *op_status, int *op_rc) { char *rc = NULL; char *key = NULL; char *magic2 = NULL; char *status = NULL; if(decodeNVpair(magic, ':', &status, &magic2) == FALSE) { crm_err("Couldn't find ':' in: %s", magic); return FALSE; } if(decodeNVpair(magic2, ';', &rc, &key) == FALSE) { crm_err("Couldn't find ';' in: %s", magic2); return FALSE; } - CRM_CHECK(decode_transition_key(key, uuid, transition_id), return FALSE); + CRM_CHECK(decode_transition_key(key, uuid, transition_id, action_id), + return FALSE); *op_rc = crm_parse_int(rc, NULL); *op_status = crm_parse_int(status, NULL); crm_free(rc); crm_free(key); crm_free(magic2); crm_free(status); return TRUE; } char * -generate_transition_key(int transition_id, const char *node) +generate_transition_key(int transition_id, int action_id, const char *node) { int len = 40; char *fail_state = NULL; CRM_CHECK(node != NULL, return NULL); len += strlen(node); crm_malloc0(fail_state, len); if(fail_state != NULL) { - snprintf(fail_state, len, "%d:%s", transition_id, node); + snprintf(fail_state, len, "%d:%d:%s", + action_id, transition_id, node); } return fail_state; } gboolean -decode_transition_key(const char *key, char **uuid, int *transition_id) +decode_transition_key( + const char *key, char **uuid, int *transition_id, int *action_id) { + char *tmp = NULL; + char *action = NULL; char *transition = NULL; + + *uuid = NULL; + *action_id = -1; + *transition_id = -1; - if(decodeNVpair(key, ':', &transition, uuid) == FALSE) { + if(decodeNVpair(key, ':', &action, &tmp) == FALSE) { crm_err("Couldn't find ':' in: %s", key); return FALSE; } - - *transition_id = crm_parse_int(transition, NULL); - crm_free(transition); - + *action_id = crm_parse_int(action, NULL); + crm_free(action); + + if(decodeNVpair(tmp, ':', &transition, uuid) == FALSE) { + /* this would be an error but some versions dont + * have the action + */ + *transition_id = *action_id; + *action_id = -1; + *uuid = tmp; + + } else { + *transition_id = crm_parse_int(transition, NULL); + crm_free(transition); + crm_free(tmp); + } return TRUE; } void filter_action_parameters(crm_data_t *param_set, const char *version) { #if CRM_DEPRECATED_SINCE_2_0_5 const char *filter_205[] = { XML_ATTR_TE_TARGET_RC, XML_ATTR_LRM_PROBE, XML_RSC_ATTR_START, XML_RSC_ATTR_NOTIFY, XML_RSC_ATTR_UNIQUE, XML_RSC_ATTR_MANAGED, XML_RSC_ATTR_PRIORITY, XML_RSC_ATTR_MULTIPLE, XML_RSC_ATTR_STICKINESS, XML_RSC_ATTR_FAIL_STICKINESS, XML_RSC_ATTR_TARGET_ROLE, /* ignore clone fields */ XML_RSC_ATTR_INCARNATION, XML_RSC_ATTR_INCARNATION_MAX, XML_RSC_ATTR_INCARNATION_NODEMAX, XML_RSC_ATTR_MASTER_MAX, XML_RSC_ATTR_MASTER_NODEMAX, /* old field names */ "role", "crm_role", "te-target-rc", /* ignore notify fields */ "notify_stop_resource", "notify_stop_uname", "notify_start_resource", "notify_start_uname", "notify_active_resource", "notify_active_uname", "notify_inactive_resource", "notify_inactive_uname", "notify_promote_resource", "notify_promote_uname", "notify_demote_resource", "notify_demote_uname", "notify_master_resource", "notify_master_uname", "notify_slave_resource", "notify_slave_uname" }; #endif const char *attr_filter[] = { XML_ATTR_ID, XML_ATTR_CRM_VERSION, XML_LRM_ATTR_OP_DIGEST, }; gboolean do_delete = FALSE; int lpc = 0; static int meta_len = 0; if(meta_len == 0) { meta_len = strlen(CRM_META); } if(param_set == NULL) { return; } #if CRM_DEPRECATED_SINCE_2_0_5 if(version == NULL || compare_version("1.0.5", version)) { for(lpc = 0; lpc < DIMOF(filter_205); lpc++) { xml_remove_prop(param_set, filter_205[lpc]); } } #endif for(lpc = 0; lpc < DIMOF(attr_filter); lpc++) { xml_remove_prop(param_set, attr_filter[lpc]); } xml_prop_iter(param_set, prop_name, prop_value, do_delete = FALSE; if(strncasecmp(prop_name, CRM_META, meta_len) == 0) { do_delete = TRUE; } if(do_delete) { /* remove it */ xml_remove_prop(param_set, prop_name); /* unwind the counetr */ __counter--; } ); } gboolean crm_mem_stats(volatile cl_mem_stats_t *mem_stats) { volatile cl_mem_stats_t *active_stats = mem_stats; if(active_stats == NULL) { active_stats = cl_malloc_getstats(); } CRM_CHECK(active_stats != NULL, ;); #ifndef CRM_USE_MALLOC if(active_stats->numalloc > active_stats->numfree) { crm_warn("Potential memory leak detected:" " %lu alloc's vs. %lu free's (%lu)" " (%lu bytes not freed: req=%lu, alloc'd=%lu)", active_stats->numalloc, active_stats->numfree, active_stats->numalloc - active_stats->numfree, active_stats->nbytes_alloc, active_stats->nbytes_req, active_stats->mallocbytes); return TRUE; } else if(active_stats->numalloc < active_stats->numfree) { crm_debug("Process shrank: %lu alloc's vs. %lu free's (%lu)", active_stats->numalloc, active_stats->numfree, active_stats->numalloc - active_stats->numfree); } #endif return FALSE; } void crm_zero_mem_stats(volatile cl_mem_stats_t *stats) { if(stats == NULL) { crm_debug("Resetting global memory stats"); stats = cl_malloc_getstats(); } stats->numalloc = 0; stats->numfree = 0; stats->numrealloc = 0; stats->nbytes_req = 0; stats->nbytes_alloc = 0; stats->mallocbytes = 0; stats->arena = 0; } void crm_save_mem_stats(const char *location, cl_mem_stats_t *saved_stats) { volatile cl_mem_stats_t *stats = cl_malloc_getstats(); if(saved_stats == NULL) { return; } crm_debug_2("Saving memory stats: %s", location); *saved_stats = *stats; } void crm_xml_nbytes(crm_data_t *xml, long *bytes, long *allocs, long *frees) { crm_data_t *xml_copy = NULL; volatile cl_mem_stats_t *stats = cl_malloc_getstats(); if(xml == NULL) { *bytes = 0; *allocs = 0; *frees = 0; return; } *bytes = 0 - stats->nbytes_alloc; *allocs = 0 - stats->numalloc; *frees = 0 - stats->numfree; xml_copy = copy_xml(xml); *bytes += stats->nbytes_alloc; *allocs += stats->numalloc; *frees += stats->numfree; crm_debug_3("XML size: %ld bytes, %ld allocs, %ld frees", *bytes, *allocs, *frees); free_xml(xml_copy); } void crm_adjust_mem_stats(volatile cl_mem_stats_t *stats, long bytes, long allocs, long frees) { if(bytes == 0&& allocs == 0 && frees == 0) { return; } if(stats == NULL) { stats = cl_malloc_getstats(); } stats->nbytes_alloc -= bytes; stats->numalloc -= allocs; stats->numfree -= frees; crm_debug("Adjusted CIB Memory usage by: %10ld bytes, %5ld allocs, %5ld frees", bytes, allocs, frees); } cl_mem_stats_t *crm_running_stats = NULL; gboolean crm_diff_mem_stats(int log_level_up, int log_level_down, const char *location, volatile cl_mem_stats_t *stats, volatile cl_mem_stats_t *saved_stats) { long delta_allocs = 0; long delta_frees = 0; long delta_bytes = 0; long delta_req = 0; gboolean increase = TRUE; gboolean reset_on_change = (log_level_up == LOG_DEBUG); /* long delta_malloc = stats->mallocbytes - saved_stats->mallocbytes; */ return FALSE; if(stats == NULL && saved_stats == NULL) { crm_err("Comparision doesnt make sense"); return FALSE; } else if(stats == NULL) { stats = cl_malloc_getstats(); } else if(saved_stats == NULL) { saved_stats = cl_malloc_getstats(); } delta_allocs = stats->numalloc - saved_stats->numalloc; delta_frees = stats->numfree - saved_stats->numfree; delta_bytes = stats->nbytes_alloc - saved_stats->nbytes_alloc; delta_req = stats->nbytes_req - saved_stats->nbytes_req; if(delta_bytes == 0) { crm_debug_2("Memory usage constant at %s: %ld alloc's %ld free's", location, delta_allocs, delta_frees); return FALSE; } if(delta_bytes < 0) { increase = FALSE; reset_on_change = (log_level_down == LOG_DEBUG); } do_crm_log(increase?log_level_up:log_level_down, "Memory usage %s detected at %s:\t" " %10ld alloc's vs. %10ld free's (%5ld change" " %10ld bytes leaked)", increase?"increase":"decrease", location, delta_allocs, delta_frees, delta_allocs - delta_frees, delta_bytes); if(reset_on_change) { crm_info("Resetting %s stats", location); *stats = *saved_stats; if(crm_running_stats) { crm_adjust_mem_stats(crm_running_stats, delta_bytes, delta_allocs, delta_frees); } } return TRUE; } void crm_abort(const char *file, const char *function, int line, const char *assert_condition, gboolean do_fork) { int pid = 0; if(do_fork == FALSE) { do_crm_log(LOG_ERR, "%s: Triggered fatal assert at %s:%d : %s", function, file, line, assert_condition); } else if(crm_log_level < LOG_DEBUG) { do_crm_log(LOG_ERR, "%s: Triggered non-fatal assert at %s:%d : %s", function, file, line, assert_condition); return; } else { pid=fork(); } switch(pid) { case -1: crm_err("Cannot fork!"); return; default: /* Parent */ do_crm_log(LOG_ERR, "%s: Forked child %d to record non-fatal assert at %s:%d : %s", function, pid, file, line, assert_condition); return; case 0: /* Child */ abort(); break; } } char * generate_series_filename( const char *directory, const char *series, int sequence, gboolean bzip) { int len = 40; char *filename = NULL; const char *ext = "raw"; CRM_CHECK(directory != NULL, return NULL); CRM_CHECK(series != NULL, return NULL); len += strlen(directory); len += strlen(series); crm_malloc0(filename, len); CRM_CHECK(filename != NULL, return NULL); if(bzip) { ext = "bz2"; } sprintf(filename, "%s/%s-%d.%s", directory, series, sequence, ext); return filename; } int get_last_sequence(const char *directory, const char *series) { FILE *file_strm = NULL; int start = 0, length = 0, read_len = 0; char *series_file = NULL; char *buffer = NULL; int seq = 0; int len = 36; CRM_CHECK(directory != NULL, return 0); CRM_CHECK(series != NULL, return 0); len += strlen(directory); len += strlen(series); crm_malloc0(series_file, len); CRM_CHECK(series_file != NULL, return 0); sprintf(series_file, "%s/%s.last", directory, series); file_strm = fopen(series_file, "r"); if(file_strm == NULL) { crm_debug("%s does not exist", series_file); crm_free(series_file); return 0; } /* see how big the file is */ start = ftell(file_strm); fseek(file_strm, 0L, SEEK_END); length = ftell(file_strm); fseek(file_strm, 0L, start); CRM_ASSERT(start == ftell(file_strm)); crm_debug_3("Reading %d bytes from file", length); crm_malloc0(buffer, (length+1)); read_len = fread(buffer, 1, length, file_strm); if(read_len != length) { crm_err("Calculated and read bytes differ: %d vs. %d", length, read_len); crm_free(buffer); buffer = NULL; } else if(length <= 0) { crm_info("%s was not valid", series_file); crm_free(buffer); buffer = NULL; } crm_free(series_file); seq = crm_parse_int(buffer, "0"); crm_free(buffer); fclose(file_strm); return seq; } void write_last_sequence( const char *directory, const char *series, int sequence, int max) { int rc = 0; int len = 36; char *buffer = NULL; FILE *file_strm = NULL; char *series_file = NULL; CRM_CHECK(directory != NULL, return); CRM_CHECK(series != NULL, return); if(max == 0) { return; } while(max > 0 && sequence > max) { sequence -= max; } buffer = crm_itoa(sequence); len += strlen(directory); len += strlen(series); crm_malloc0(series_file, len); CRM_CHECK(series_file != NULL, return); sprintf(series_file, "%s/%s.last", directory, series); file_strm = fopen(series_file, "w"); if(file_strm == NULL) { crm_err("%s does not exist", series_file); crm_free(series_file); return; } rc = fprintf(file_strm, "%s", buffer); if(rc < 0) { cl_perror("Cannot write output to %s", series_file); } fflush(file_strm); fclose(file_strm); crm_free(series_file); crm_free(buffer); } void crm_make_daemon(const char *name, gboolean daemonize, const char *pidfile) { long pid; const char *devnull = "/dev/null"; if(daemonize == FALSE) { return; } pid = fork(); if (pid < 0) { fprintf(stderr, "%s: could not start daemon\n", name); cl_perror("fork"); exit(LSB_EXIT_GENERIC); } else if (pid > 0) { exit(LSB_EXIT_OK); } if (cl_lock_pidfile(pidfile) < 0 ) { pid = cl_read_pidfile_no_checking(pidfile); crm_warn("%s: already running [pid %ld] (%s).\n", name, pid, pidfile); exit(LSB_EXIT_OK); } umask(022); close(FD_STDIN); (void)open(devnull, O_RDONLY); /* Stdin: fd 0 */ close(FD_STDOUT); (void)open(devnull, O_WRONLY); /* Stdout: fd 1 */ close(FD_STDERR); (void)open(devnull, O_WRONLY); /* Stderr: fd 2 */ } gboolean crm_is_writable(const char *dir, const char *file, const char *user, const char *group, gboolean need_both) { int s_res = -1; struct stat buf; char *full_file = NULL; const char *target = NULL; gboolean pass = TRUE; gboolean readwritable = FALSE; CRM_ASSERT(dir != NULL); if(file != NULL) { full_file = crm_concat(dir, file, '/'); target = full_file; s_res = stat(full_file, &buf); if( s_res == 0 && S_ISREG(buf.st_mode) == FALSE ) { crm_err("%s must be a regular file", target); pass = FALSE; goto out; } } if (s_res != 0) { target = dir; s_res = stat(dir, &buf); if(s_res != 0) { crm_err("%s must exist and be a directory", dir); pass = FALSE; goto out; } else if( S_ISDIR(buf.st_mode) == FALSE ) { crm_err("%s must be a directory", dir); pass = FALSE; } } if(user) { struct passwd *sys_user = NULL; sys_user = getpwnam(user); readwritable = (sys_user != NULL && buf.st_uid == sys_user->pw_uid && (buf.st_mode & (S_IRUSR|S_IWUSR))); if(readwritable == FALSE) { crm_err("%s must be owned and r/w by user %s", target, user); if(need_both) { pass = FALSE; } } } if(group) { struct group *sys_grp = getgrnam(group); readwritable = ( sys_grp != NULL && buf.st_gid == sys_grp->gr_gid && (buf.st_mode & (S_IRGRP|S_IWGRP))); if(readwritable == FALSE) { if(need_both || user == NULL) { pass = FALSE; crm_err("%s must be owned and r/w by group %s", target, group); } else { crm_warn("%s should be owned and r/w by group %s", target, group); } } } out: crm_free(full_file); return pass; } diff --git a/lib/crm/common/xml.c b/lib/crm/common/xml.c index 3d20592ec3..0cddcd3bb0 100644 --- a/lib/crm/common/xml.c +++ b/lib/crm/common/xml.c @@ -1,2626 +1,2630 @@ /* $Id: xml.c,v 1.104 2006/08/17 07:17:16 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_BZLIB_H # include #endif #include #define XML_BUFFER_SIZE 4096 int is_comment_start(const char *input); int is_comment_end(const char *input); gboolean drop_comments(const char *input, int *offset); void dump_array( int log_level, const char *message, const char **array, int depth); int print_spaces(char *buffer, int spaces); int log_data_element(const char *function, const char *prefix, int log_level, int depth, const crm_data_t *data, gboolean formatted); int dump_data_element( int depth, char **buffer, const crm_data_t *data, gboolean formatted); crm_data_t *parse_xml(const char *input, int *offset); int get_tag_name(const char *input); int get_attr_name(const char *input); int get_attr_value(const char *input); gboolean can_prune_leaf(crm_data_t *xml_node); void diff_filter_context(int context, int upper_bound, int lower_bound, crm_data_t *xml_node, crm_data_t *parent); int in_upper_context(int depth, int context, crm_data_t *xml_node); crm_data_t * find_xml_node(crm_data_t *root, const char * search_path, gboolean must_find) { if(must_find || root != NULL) { crm_validate_data(root); } if(search_path == NULL) { crm_warn("Will never find "); return NULL; } xml_child_iter_filter( root, a_child, search_path, /* crm_debug_5("returning node (%s).", crm_element_name(a_child)); */ crm_log_xml(LOG_DEBUG_5, "found:", a_child); crm_log_xml(LOG_DEBUG_6, "in:", root); crm_validate_data(a_child); return a_child; ); if(must_find) { crm_warn("Could not find %s in %s.", search_path, crm_element_name(root)); } else if(root != NULL) { crm_debug_3("Could not find %s in %s.", search_path, crm_element_name(root)); } else { crm_debug_3("Could not find %s in .", search_path); } return NULL; } crm_data_t* find_xml_node_nested(crm_data_t *root, const char **search_path, int len) { int j; gboolean is_found = TRUE; crm_data_t *match = NULL; crm_data_t *lastMatch = root; crm_validate_data(root); if(search_path == NULL || search_path[0] == NULL) { crm_warn("Will never find NULL"); return NULL; } dump_array(LOG_DEBUG_5, "Looking for.", search_path, len); for (j=0; j < len; ++j) { if (search_path[j] == NULL) { /* a NULL also means stop searching */ break; } match = find_xml_node(lastMatch, search_path[j], FALSE); if(match == NULL) { is_found = FALSE; break; } else { lastMatch = match; } } if (is_found) { crm_debug_5("returning node (%s).", crm_element_name(lastMatch)); crm_log_xml_debug_5(lastMatch, "found\t%s"); crm_log_xml_debug_5(root, "in \t%s"); crm_validate_data(lastMatch); return lastMatch; } dump_array(LOG_DEBUG_2, "Could not find the full path to the node you specified.", search_path, len); crm_debug_2("Closest point was node (%s) starting from %s.", crm_element_name(lastMatch), crm_element_name(root)); return NULL; } const char * get_xml_attr_nested(crm_data_t *parent, const char **node_path, int length, const char *attr_name, gboolean error) { const char *attr_value = NULL; crm_data_t *attr_parent = NULL; if(error || parent != NULL) { crm_validate_data(parent); } if(parent == NULL) { crm_debug_3("Can not find attribute %s in NULL parent",attr_name); return NULL; } if(attr_name == NULL || strlen(attr_name) == 0) { crm_err("Can not find attribute with no name in %s", crm_element_name(parent)); return NULL; } if(length == 0) { attr_parent = parent; } else { attr_parent = find_xml_node_nested(parent, node_path, length); if(attr_parent == NULL && error) { crm_err("No node at the path you specified."); return NULL; } } attr_value = crm_element_value(attr_parent, attr_name); if((attr_value == NULL || strlen(attr_value) == 0) && error) { crm_err("No value present for %s at %s", attr_name, crm_element_name(attr_parent)); return NULL; } return attr_value; } crm_data_t* find_entity(crm_data_t *parent, const char *node_name, const char *id) { crm_validate_data(parent); xml_child_iter_filter( parent, a_child, node_name, if(id == NULL || safe_str_eq(id, ID(a_child))) { crm_debug_4("returning node (%s).", crm_element_name(a_child)); return a_child; } ); crm_debug_3("node <%s id=%s> not found in %s.", node_name, id, crm_element_name(parent)); return NULL; } void copy_in_properties(crm_data_t* target, const crm_data_t *src) { int value_len = 0; char *incr_value = NULL; char *new_value = NULL; crm_validate_data(src); crm_validate_data(target); if(src == NULL) { crm_warn("No node to copy properties from"); } else if (target == NULL) { crm_err("No node to copy properties into"); } else { xml_prop_iter( src, local_prop_name, local_prop_value, /* if the value is name followed by "++" we need * to increment the existing value */ new_value = NULL; incr_value = NULL; /* do a quick check to decide if its worth * constructing the various strings and * performing string comparisions */ value_len = strlen(local_prop_value); if(value_len > 2 && local_prop_value[0] == local_prop_value[0] && local_prop_value[value_len-1] == '+' && local_prop_value[value_len-2] == '+') { int old_int = 0; const char *old_value = NULL; incr_value=crm_concat(local_prop_name,"+",'+'); if(safe_str_eq(local_prop_value, incr_value)) { old_value = crm_element_value( target, local_prop_name); old_int = crm_parse_int(old_value, "0"); new_value = crm_itoa(old_int + 1); local_prop_value = new_value; } } crm_xml_add(target, local_prop_name, local_prop_value); crm_free(incr_value); crm_free(new_value); ); crm_validate_data(target); } return; } crm_data_t* add_node_copy(crm_data_t *parent, const crm_data_t *src_node) { const char *name = NULL; crm_data_t *child = NULL; CRM_CHECK(src_node != NULL, return NULL); crm_validate_data(src_node); name = crm_element_name(src_node); CRM_CHECK(name != NULL, return NULL); child = create_xml_node(parent, name); copy_in_properties(child, src_node); xml_child_iter(src_node, src_child, add_node_copy(child, src_child); ); crm_set_element_parent(child, parent); return child; } const char * crm_xml_add(crm_data_t* node, const char *name, const char *value) { const char *parent_name = NULL; if(node != NULL) { parent_name = crm_element_name(node); } crm_debug_5("[%s] Setting %s to %s", crm_str(parent_name), name, value); if (name == NULL || strlen(name) <= 0) { } else if(node == NULL) { } else if(parent_name == NULL && strcasecmp(name, F_XML_TAGNAME) != 0) { } else if (value == NULL || strlen(value) <= 0) { xml_remove_prop(node, name); return NULL; } else { const char *new_value = NULL; crm_validate_data(node); ha_msg_mod(node, name, value); new_value = crm_element_value(node, name); return new_value; } return NULL; } const char * crm_xml_add_int(crm_data_t* node, const char *name, int value) { const char *parent_name = NULL; if(node != NULL) { parent_name = crm_element_name(node); } crm_debug_5("[%s] Setting %s to %d", crm_str(parent_name), name, value); if (name == NULL || strlen(name) <= 0) { } else if(node == NULL) { } else if(parent_name == NULL && strcasecmp(name, F_XML_TAGNAME) != 0) { } else { crm_validate_data(node); ha_msg_mod_int(node, name, value); return crm_element_value(node, name); } return NULL; } crm_data_t* create_xml_node(crm_data_t *parent, const char *name) { const char *local_name = NULL; const char *parent_name = NULL; crm_data_t *ret_value = NULL; if (name == NULL || strlen(name) < 1) { ret_value = NULL; } else { local_name = name; ret_value = ha_msg_new(3); CRM_CHECK(ret_value != NULL, return NULL); crm_xml_add(ret_value, F_XML_TAGNAME, name); crm_set_element_parent(ret_value, parent); crm_validate_data(ret_value); if(parent) { crm_validate_data(parent); parent_name = crm_element_name(parent); crm_debug_5("Attaching %s to parent %s", local_name, parent_name); CRM_CHECK(HA_OK == ha_msg_addstruct( parent, name, ret_value), return NULL); crm_msg_del(ret_value); crm_validate_data(parent); ret_value = parent->values[parent->nfields-1]; } } crm_debug_5("Created node [%s [%s]]", crm_str(parent_name), crm_str(local_name)); return ret_value; } void free_xml_from_parent(crm_data_t *parent, crm_data_t *a_node) { CRM_CHECK(parent != NULL, return); CRM_CHECK(a_node != NULL, return); crm_validate_data(parent); cl_msg_remove_value(parent, a_node); crm_validate_data(parent); } void free_xml_fn(crm_data_t *a_node) { if(a_node == NULL) { ; /* nothing to do */ } else { int has_parent = 0; crm_validate_data(a_node); ha_msg_value_int(a_node, F_XML_PARENT, &has_parent); /* there is no way in hell we should be deleting anything * with a parent and without the parent knowning */ CRM_CHECK(has_parent == 0, return); crm_validate_data(a_node); ha_msg_del(a_node); } return; } void add_xml_tstamp(crm_data_t *a_node) { char *since_epoch = NULL; time_t a_time = time(NULL); crm_validate_data(a_node); if(a_time == (time_t)-1) { cl_perror("set_node_tstamp(): Invalid time returned"); return; } crm_malloc0(since_epoch, 128); if(since_epoch != NULL) { sprintf(since_epoch, "%ld", (unsigned long)a_time); ha_msg_mod(a_node, XML_ATTR_TSTAMP, since_epoch); crm_validate_data(a_node); crm_free(since_epoch); } } crm_data_t* copy_xml(const crm_data_t *src_node) { return add_node_copy(NULL, src_node); } crm_data_t* string2xml(const char *input) { crm_data_t *output = parse_xml(input, NULL); if(output != NULL) { crm_update_parents(output); crm_validate_data(output); } return output; } crm_data_t * stdin2xml(void) { size_t data_length = 0; size_t read_chars = 0; char *xml_buffer = NULL; crm_data_t *xml_obj = NULL; do { crm_realloc(xml_buffer, XML_BUFFER_SIZE + data_length + 1); read_chars = fread(xml_buffer + data_length, 1, XML_BUFFER_SIZE, stdin); data_length += read_chars; } while (read_chars > 0); xml_buffer[data_length] = '\0'; xml_obj = string2xml(xml_buffer); crm_free(xml_buffer); crm_log_xml_debug_3(xml_obj, "Created fragment"); return xml_obj; } crm_data_t* file2xml(FILE *input, gboolean compressed) { char *buffer = NULL; gboolean work_done = FALSE; crm_data_t *new_obj = NULL; size_t length = 0, read_len = 0; if(input == NULL) { crm_err("No file to read"); return NULL; } if(compressed) { #if HAVE_BZLIB_H int rc = 0; BZFILE *bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0); if ( rc != BZ_OK ) { BZ2_bzReadClose ( &rc, bz_file); return NULL; } rc = BZ_OK; while ( rc == BZ_OK ) { crm_realloc(buffer, XML_BUFFER_SIZE + length + 1); read_len = BZ2_bzRead ( &rc, bz_file, buffer + length, XML_BUFFER_SIZE); crm_debug_5("Read %ld bytes from file: %d", (long)read_len, rc); if ( rc == BZ_OK || rc == BZ_STREAM_END) { length += read_len; } } buffer[length] = '\0'; read_len = length; if ( rc != BZ_STREAM_END ) { crm_err("Couldnt read compressed xml from file"); crm_free(buffer); } BZ2_bzReadClose (&rc, bz_file); if(buffer == NULL) { return NULL; } work_done = TRUE; #else crm_err("Cannot read compressed files:" " bzlib was not available at compile time"); #endif } if(work_done == FALSE) { int start = 0; start = ftell(input); fseek(input, 0L, SEEK_END); length = ftell(input); fseek(input, 0L, start); CRM_ASSERT(start == ftell(input)); crm_debug_3("Reading %ld bytes from file", (long)length); crm_malloc0(buffer, (length+1)); read_len = fread(buffer, 1, length, input); } /* see how big the file is */ if(read_len != length) { crm_err("Calculated and read bytes differ: %ld vs. %ld", (long)length, (long)read_len); } else if(length > 0) { new_obj = string2xml(buffer); } else { crm_warn("File contained no XML"); } crm_free(buffer); return new_obj; } void dump_array(int log_level, const char *message, const char **array, int depth) { int j; if(message != NULL) { do_crm_log(log_level, "%s", message); } do_crm_log(log_level, "Contents of the array:"); if(array == NULL || array[0] == NULL || depth == 0) { do_crm_log(log_level, "\t"); return; } for (j=0; j < depth && array[j] != NULL; j++) { if (array[j] == NULL) { break; } do_crm_log(log_level, "\t--> (%s).", array[j]); } } int write_xml_file(crm_data_t *xml_node, const char *filename, gboolean compress) { int res = 0; time_t now; char *buffer = NULL; char *now_str = NULL; gboolean is_done = FALSE; FILE *file_output_strm = NULL; static mode_t cib_mode = S_IRUSR|S_IWUSR; CRM_CHECK(filename != NULL, return -1); crm_debug_3("Writing XML out to %s", filename); crm_validate_data(xml_node); if (xml_node == NULL) { crm_err("Cannot write NULL to %s", filename); return -1; } crm_validate_data(xml_node); crm_log_xml_debug_4(xml_node, "Writing out"); crm_validate_data(xml_node); now = time(NULL); now_str = ctime(&now); now_str[24] = EOS; /* replace the newline */ crm_xml_add(xml_node, XML_CIB_ATTR_WRITTEN, now_str); crm_validate_data(xml_node); buffer = dump_xml_formatted(xml_node); CRM_CHECK(buffer != NULL && strlen(buffer) > 0, return -1); /* establish the file with correct permissions */ file_output_strm = fopen(filename, "w"); fclose(file_output_strm); chmod(filename, cib_mode); /* now write it */ file_output_strm = fopen(filename, "w"); if(file_output_strm == NULL) { crm_free(buffer); cl_perror("Cannot write to %s", filename); return -1; } if(compress) { #if HAVE_BZLIB_H int rc = BZ_OK; BZFILE *bz_file = NULL; unsigned int in = 0, out = 0; is_done = TRUE; bz_file = BZ2_bzWriteOpen(&rc, file_output_strm, 5,0,0); if(rc != BZ_OK) { is_done = FALSE; crm_err("bzWriteOpen failed: %d", rc); } if(is_done) { BZ2_bzWrite(&rc,bz_file,buffer,strlen(buffer)); if(rc != BZ_OK) { crm_err("bzWrite() failed: %d", rc); is_done = FALSE; } } if(is_done) { BZ2_bzWriteClose(&rc, bz_file, 0, &in, &out); if(rc != BZ_OK) { crm_err("bzWriteClose() failed: %d",rc); is_done = FALSE; } else { crm_debug_2("%s: In: %d, out: %d", filename, in, out); } } #else crm_err("Cannot write compressed files:" " bzlib was not available at compile time"); #endif } if(is_done == FALSE) { res = fprintf(file_output_strm, "%s", buffer); if(res < 0) { cl_perror("Cannot write output to %s",filename); } fflush(file_output_strm); } fclose(file_output_strm); crm_free(buffer); crm_debug_3("Saved %d bytes to the Cib as XML", res); return res; } void print_xml_formatted(int log_level, const char *function, const crm_data_t *msg, const char *text) { if(msg == NULL) { do_crm_log(log_level, "%s: %s: NULL", function, crm_str(text)); return; } crm_validate_data(msg); log_data_element(function, text, log_level, 0, msg, TRUE); return; } crm_data_t * get_message_xml(HA_Message *msg, const char *field) { crm_data_t *xml_node = NULL; crm_data_t *tmp_node = NULL; crm_validate_data(msg); tmp_node = cl_get_struct(msg, field); if(tmp_node != NULL) { xml_node = copy_xml(tmp_node); } return xml_node; } gboolean add_message_xml(HA_Message *msg, const char *field, const crm_data_t *xml) { crm_validate_data(xml); crm_validate_data(msg); CRM_CHECK(field != NULL, return FALSE); ha_msg_addstruct_compress(msg, field, xml); crm_update_parents(msg); return TRUE; } char * dump_xml_formatted(const crm_data_t *an_xml_node) { char *buffer = NULL; char *mutable_ptr = NULL; if(an_xml_node == NULL) { return NULL; } crm_malloc0(buffer, 3*get_stringlen(an_xml_node)); mutable_ptr = buffer; crm_validate_data(an_xml_node); CRM_CHECK(dump_data_element( 0, &mutable_ptr, an_xml_node, TRUE) >= 0, crm_crit("Could not dump the whole message")); crm_debug_4("Dumped: %s", buffer); return buffer; } char * dump_xml_unformatted(const crm_data_t *an_xml_node) { char *buffer = NULL; char *mutable_ptr = NULL; crm_malloc0(buffer, 2*get_stringlen(an_xml_node)); mutable_ptr = buffer; crm_validate_data(an_xml_node); CRM_CHECK(dump_data_element( 0, &mutable_ptr, an_xml_node, TRUE) >= 0, crm_crit("Could not dump the whole message")); crm_debug_4("Dumped: %s", buffer); return buffer; } #define update_buffer_head(buffer, len) if(len < 0) { \ (*buffer) = EOS; return -1; \ } else { \ buffer += len; \ } int print_spaces(char *buffer, int depth) { int lpc = 0; int spaces = 2*depth; /* <= so that we always print 1 space - prevents problems with syslog */ for(lpc = 0; lpc <= spaces; lpc++) { if(sprintf(buffer, "%c", ' ') < 1) { return -1; } buffer += 1; } return lpc; } int log_data_element( const char *function, const char *prefix, int log_level, int depth, const crm_data_t *data, gboolean formatted) { int printed = 0; int child_result = 0; int has_children = 0; char print_buffer[1000]; char *buffer = print_buffer; const char *name = crm_element_name(data); const char *hidden = NULL; crm_debug_5("Dumping %s...", name); crm_validate_data(data); if(data == NULL) { crm_warn("No data to dump as XML"); return 0; } else if(name == NULL && depth == 0) { xml_child_iter( data, a_child, child_result = log_data_element( function, prefix, log_level, depth, a_child, formatted); if(child_result < 0) { return child_result; } ); return 0; } else if(name == NULL) { crm_err("Cannot dump NULL element at depth %d", depth); return -1; } if(formatted) { printed = print_spaces(buffer, depth); update_buffer_head(buffer, printed); } printed = sprintf(buffer, "<%s", name); update_buffer_head(buffer, printed); hidden = crm_element_value(data, "hidden"); xml_prop_iter( data, prop_name, prop_value, if(prop_name == NULL) { CRM_ASSERT(prop_name != NULL); } else if(safe_str_eq(F_XML_TAGNAME, prop_name)) { continue; } else if(safe_str_eq(F_XML_PARENT, prop_name)) { continue; } else if(hidden != NULL && strlen(prop_name) > 0 && strstr(hidden, prop_name) != NULL) { prop_value = "*****"; } crm_debug_5("Dumping <%s %s=\"%s\"...", name, prop_name, prop_value); printed = sprintf(buffer, " %s=\"%s\"", prop_name, prop_value); update_buffer_head(buffer, printed); ); xml_child_iter( data, child, if(child != NULL) { has_children++; break; } ); printed = sprintf(buffer, "%s>", has_children==0?"/":""); update_buffer_head(buffer, printed); do_crm_log(log_level, "%s: %s%s", function, prefix?prefix:"", print_buffer); buffer = print_buffer; if(has_children == 0) { return 0; } xml_child_iter( data, a_child, child_result = log_data_element( function, prefix, log_level, depth+1, a_child, formatted); if(child_result < 0) { return -1; } ); if(formatted) { printed = print_spaces(buffer, depth); update_buffer_head(buffer, printed); } do_crm_log(log_level, "%s: %s%s", function, prefix?prefix:"", print_buffer, name); crm_debug_5("Dumped %s...", name); return has_children; } int dump_data_element( int depth, char **buffer, const crm_data_t *data, gboolean formatted) { int printed = 0; int child_result = 0; int has_children = 0; const char *name = crm_element_name(data); crm_debug_5("Dumping %s...", name); crm_validate_data(data); if(buffer == NULL || *buffer == NULL) { crm_err("No buffer supplied to dump XML into"); return -1; } else if(data == NULL) { crm_warn("No data to dump as XML"); (*buffer)[0] = EOS; return 0; } else if(name == NULL && depth == 0) { xml_child_iter( data, a_child, child_result = dump_data_element( depth, buffer, a_child, formatted); if(child_result < 0) { return child_result; } ); return 0; } else if(name == NULL) { crm_err("Cannot dump NULL element at depth %d", depth); return -1; } if(formatted) { printed = print_spaces(*buffer, depth); update_buffer_head(*buffer, printed); } printed = sprintf(*buffer, "<%s", name); update_buffer_head(*buffer, printed); xml_prop_iter(data, prop_name, prop_value, if(safe_str_eq(F_XML_TAGNAME, prop_name)) { continue; } else if(safe_str_eq(F_XML_PARENT, prop_name)) { continue; } crm_debug_5("Dumping <%s %s=\"%s\"...", name, prop_name, prop_value); printed = sprintf(*buffer, " %s=\"%s\"", prop_name, prop_value); update_buffer_head(*buffer, printed); ); xml_child_iter( data, child, if(child != NULL) { has_children++; break; } ); printed = sprintf(*buffer, "%s>%s", has_children==0?"/":"", formatted?"\n":""); update_buffer_head(*buffer, printed); if(has_children == 0) { return 0; } xml_child_iter( data, child, child_result = dump_data_element( depth+1, buffer, child, formatted); if(child_result < 0) { return -1; } ); if(formatted) { printed = print_spaces(*buffer, depth); update_buffer_head(*buffer, printed); } printed = sprintf(*buffer, "%s", name, formatted?"\n":""); update_buffer_head(*buffer, printed); crm_debug_5("Dumped %s...", name); return has_children; } gboolean xml_has_children(crm_data_t *xml_root) { crm_validate_data(xml_root); xml_child_iter( xml_root, a_child, return TRUE; ); return FALSE; } void xml_validate(const crm_data_t *xml_root) { int lpc = 0; CRM_ASSERT(xml_root != NULL); CRM_ASSERT(cl_is_allocated(xml_root) == 1); CRM_ASSERT(xml_root->nfields < 500); for (lpc = 0; lpc < xml_root->nfields; lpc++) { void *child = xml_root->values[lpc]; CRM_ASSERT(cl_is_allocated(xml_root->names[lpc]) == 1); if(child == NULL) { } else if(xml_root->types[lpc] == FT_STRUCT || xml_root->types[lpc] == FT_UNCOMPRESS) { crm_validate_data(child); } else if(xml_root->types[lpc] == FT_STRING) { CRM_ASSERT(cl_is_allocated(child) == 1); /* } else { */ /* CRM_CHECK(FALSE); */ } } } void crm_set_element_parent(crm_data_t *data, crm_data_t *parent) { crm_validate_data(data); if(parent != NULL) { ha_msg_mod_int(data, F_XML_PARENT, 1); } else { ha_msg_mod_int(data, F_XML_PARENT, 0); } } const char * crm_element_value(const crm_data_t *data, const char *name) { const char *value = NULL; crm_validate_data(data); value = cl_get_string(data, name); #if XML_PARANOIA_CHECKS CRM_CHECK(value == NULL || cl_is_allocated(value) == 1, return NULL); #endif return value; } char * crm_element_value_copy(const crm_data_t *data, const char *name) { const char *value = NULL; char *value_copy = NULL; crm_validate_data(data); value = cl_get_string(data, name); if(value != NULL) { #if XML_PARANOIA_CHECKS CRM_CHECK(cl_is_allocated(value) == 1, return NULL); #endif value_copy = crm_strdup(value); } return value_copy; } const char * crm_element_name(const crm_data_t *data) { #if CRM_DEV_BUILD crm_validate_data(data); #endif return cl_get_string(data, F_XML_TAGNAME); } void xml_remove_prop(crm_data_t *obj, const char *name) { if(crm_element_value(obj, name) != NULL) { cl_msg_remove(obj, name); } } void crm_update_parents(crm_data_t *xml_root) { crm_validate_data(xml_root); xml_child_iter( xml_root, a_child, crm_set_element_parent(a_child, xml_root); crm_update_parents(a_child); ); } int get_tag_name(const char *input) { int lpc = 0; char ch = 0; const char *error = NULL; gboolean do_special = FALSE; for(lpc = 0; error == NULL && lpc < (ssize_t)strlen(input); lpc++) { ch = input[lpc]; crm_debug_5("Processing char %c [%d]", ch, lpc); switch(ch) { case 0: error = "unexpected EOS"; break; case '?': if(lpc == 0) { /* weird xml tag that we dont care about */ do_special = TRUE; } else { return lpc; } break; case '/': case '>': case '\t': case '\n': case ' ': if(!do_special) { return lpc; } break; default: if(do_special) { } else if('a' <= ch && ch <= 'z') { } else if('A' <= ch && ch <= 'Z') { } else if(ch == '_') { } else if(ch == '-') { } else { error = "bad character, not in [a-zA-Z_-]"; } break; } } crm_err("Error parsing token near %.15s: %s", input, crm_str(error)); return -1; } int get_attr_name(const char *input) { int lpc = 0; char ch = 0; const char *error = NULL; for(lpc = 0; error == NULL && lpc < (ssize_t)strlen(input); lpc++) { ch = input[lpc]; crm_debug_5("Processing char %c[%d]", ch, lpc); switch(ch) { case 0: error = "unexpected EOS"; break; case '\t': case '\n': case ' ': error = "unexpected whitespace"; break; case '=': return lpc; default: if('a' <= ch && ch <= 'z') { } else if('A' <= ch && ch <= 'Z') { } else if(isdigit((int) ch)) { } else if(ch == '_') { } else if(ch == '-') { } else { error = "bad character, not in [a-zA-Z0-9_-]"; } break; } } crm_err("Error parsing token near %.15s: %s", input, crm_str(error)); return -1; } int get_attr_value(const char *input) { int lpc = 0; char ch = 0; const char *error = NULL; for(lpc = 0; error == NULL && lpc < (ssize_t)strlen(input); lpc++) { ch = input[lpc]; crm_debug_5("Processing char %c [%d]", ch, lpc); switch(ch) { case 0: error = "unexpected EOS"; break; case '\\': if(input[lpc+1] == '"') { /* skip over the next char */ lpc++; break; } /*fall through*/ case '"': return lpc; default: break; } } crm_err("Error parsing token near %.15s: %s", input, crm_str(error)); return -1; } int is_comment_start(const char *input) { CRM_CHECK(input != NULL, return 0); if(strlen(input) > 4 && input[0] == '<' && input[1] == '!' && input[2] == '-' && input[3] == '-') { crm_debug_6("Found comment start: "); return 3; } else if(strlen(input) > 1 && input[0] == '?' && input[1] == '>') { crm_debug_6("Found comment end: ?>"); return 2; } if(strlen(input) > 2) { crm_debug_6("Not comment end: %c%c%c", input[0], input[1], input[2]); } else { crm_debug_6("Not comment end"); } return 0; } static gboolean drop_whitespace(const char *input, int *offset) { char ch = 0; int len = 0, lpc = 0; gboolean more = TRUE; const char *our_input = input; if(input == NULL) { return FALSE; } if(offset != NULL) { our_input = input + (*offset); } len = strlen(our_input); while(lpc < len && more) { ch = our_input[lpc]; crm_debug_6("Processing char %c[%d]", ch, lpc); if(isspace(ch)) { lpc++; } else { more = FALSE; } } crm_debug_4("Finished processing whitespace"); if(offset != NULL) { (*offset) += lpc; } if(lpc > 0) { crm_debug_5("Skipped %d whitespace chars", lpc); return TRUE; } return FALSE; } gboolean drop_comments(const char *input, int *offset) { gboolean more = TRUE; gboolean in_directive = FALSE; int in_comment = FALSE; const char *our_input = input; int len = 0, lpc = 0; int tag_len = 0; char ch = 0; if(input == NULL) { return FALSE; } if(offset != NULL) { our_input = input + (*offset); } len = strlen(our_input); while(lpc < len && more) { ch = our_input[lpc]; crm_debug_6("Processing char %c[%d]", ch, lpc); switch(ch) { case 0: if(in_comment == FALSE) { more = FALSE; } else { crm_err("unexpected EOS"); crm_warn("Parsing error at or before: %s", our_input); } break; case '<': tag_len = is_comment_start(our_input + lpc); if(tag_len > 0) { if(in_comment) { crm_err("Nested XML comments are not supported!"); crm_warn("Parsing error at or before: %s", our_input); crm_warn("Netsed comment found at: %s", our_input + lpc + tag_len); } in_comment = TRUE; lpc+=tag_len; if(tag_len == 2) { #if 1 if(our_input[lpc-1] == '!') { in_directive = TRUE; } #else tag_len = get_tag_name(our_input + lpc); crm_debug_2("Tag length: %d", len); if(strncmp("DOCTYPE", our_input+lpc, tag_len) == 0) { in_directive = TRUE; } #endif } } else if(in_comment == FALSE){ more = FALSE; } else { lpc++; crm_debug_6("Skipping comment char %c", our_input[lpc]); } break; case '>': lpc++; if(in_directive) { in_directive = FALSE; in_comment = FALSE; } break; case '-': case '?': tag_len = is_comment_end(our_input + lpc); if(tag_len > 0) { lpc+=tag_len; in_comment = FALSE; } else { lpc++; crm_debug_6("Skipping comment char %c", our_input[lpc]); } break; case ' ': case '\t': case '\n': case '\r': lpc++; crm_debug_6("Skipping whitespace char %d", our_input[lpc]); break; default: lpc++; crm_debug_6("Skipping comment char %c", our_input[lpc]); break; } } crm_debug_4("Finished processing comments"); if(offset != NULL) { (*offset) += lpc; } if(lpc > 0) { crm_debug_5("Skipped %d comment chars", lpc); return TRUE; } return FALSE; } crm_data_t* parse_xml(const char *input, int *offset) { int len = 0, lpc = 0; char ch = 0; char *tag_name = NULL; char *attr_name = NULL; char *attr_value = NULL; gboolean more = TRUE; gboolean were_comments = TRUE; const char *error = NULL; const char *our_input = input; crm_data_t *new_obj = NULL; if(input == NULL) { return NULL; } if(offset != NULL) { our_input = input + (*offset); } len = strlen(our_input); while(lpc < len && were_comments) { were_comments = drop_comments(our_input, &lpc); } CRM_CHECK(our_input[lpc] == '<', return NULL); lpc++; len = get_tag_name(our_input + lpc); crm_debug_5("Tag length: %d", len); if(len < 0) { return NULL; } crm_malloc0(tag_name, len+1); strncpy(tag_name, our_input + lpc, len+1); tag_name[len] = EOS; crm_debug_4("Processing tag %s", tag_name); new_obj = ha_msg_new(1); ha_msg_add(new_obj, F_XML_TAGNAME, tag_name); lpc += len; for(; more && error == NULL && lpc < (ssize_t)strlen(input); lpc++) { ch = our_input[lpc]; crm_debug_5("Processing char %c[%d]", ch, lpc); switch(ch) { case 0: error = "unexpected EOS"; break; case '/': if(our_input[lpc+1] == '>') { more = FALSE; } break; case '<': if(our_input[lpc+1] != '/') { crm_data_t *child = NULL; gboolean any_comments = FALSE; do { were_comments = drop_comments(our_input, &lpc); any_comments = any_comments || were_comments; } while(lpc < len && were_comments); if(any_comments) { lpc--; break; } crm_debug_4("Start parsing child..."); child = parse_xml(our_input, &lpc); if(child == NULL) { error = "error parsing child"; } else { ha_msg_addstruct_compress( new_obj, crm_element_name(child), child); crm_debug_4("Finished parsing child: %s", crm_element_name(child)); ha_msg_del(child); if(our_input[lpc] == '<') { /* lpc is about to get incrimented * make sure we process the '<' that * we're currently looking at */ lpc--; } /* lpc++; /\* > *\/ */ } } else { lpc += 2; /* *\/ */ if(our_input[lpc] != '>') { error = "clase tag cannot contain attrs"; } crm_debug_4("Finished parsing ourselves: %s", crm_element_name(new_obj)); } else { error = "Mismatching close tag"; crm_err("Expected: %s", tag_name); } } break; case '=': lpc++; /* = */ /*fall through*/ case '"': lpc++; /* " */ len = get_attr_value(our_input+lpc); if(len < 0) { error = "couldnt find attr_value"; } else { crm_malloc0(attr_value, len+1); strncpy(attr_value, our_input+lpc, len+1); attr_value[len] = EOS; lpc += len; /* lpc++; /\* " *\/ */ crm_debug_4("creating nvpair: <%s %s=\"%s\"...", tag_name, crm_str(attr_name), crm_str(attr_value)); ha_msg_add(new_obj, attr_name, attr_value); crm_free(attr_name); crm_free(attr_value); } break; case '>': + while(our_input[lpc+1] != '<') { + lpc++; + } + break; case ' ': case '\t': case '\n': case '\r': break; default: len = get_attr_name(our_input+lpc); if(len < 0) { error = "couldnt find attr_name"; } else { crm_malloc0(attr_name, len+1); strncpy(attr_name, our_input+lpc, len+1); attr_name[len] = EOS; lpc += len; crm_debug_4("found attr name: %s", attr_name); lpc--; /* make sure the '=' is seen next time around */ } break; } } if(error) { crm_err("Error parsing token: %s", error); crm_err("Error at or before: %s", our_input+lpc-3); return NULL; } if(offset == NULL) { lpc++; drop_comments(our_input, &lpc); drop_whitespace(our_input, &lpc); if(lpc < (ssize_t)strlen(input)) { crm_err("Ignoring trailing characters in XML input."); crm_err("Parsed %d characters of a possible %d. Trailing text was: ...\'%20s\'", lpc, (int)strlen(input), input+lpc); } } crm_debug_4("Finished processing %s tag", tag_name); crm_free(tag_name); if(offset != NULL) { (*offset) += lpc; } return new_obj; } void log_xml_diff(unsigned int log_level, crm_data_t *diff, const char *function) { crm_data_t *added = find_xml_node(diff, "diff-added", FALSE); crm_data_t *removed = find_xml_node(diff, "diff-removed", FALSE); gboolean is_first = TRUE; if(crm_log_level < log_level) { /* nothing will ever be printed */ return; } xml_child_iter( removed, child, log_data_element(function, "-", log_level, 0, child, TRUE); if(is_first) { is_first = FALSE; } else { do_crm_log(log_level, " --- "); } ); is_first = TRUE; xml_child_iter( added, child, log_data_element(function, "+", log_level, 0, child, TRUE); if(is_first) { is_first = FALSE; } else { do_crm_log(log_level, " --- "); } ); } void purge_diff_markers(crm_data_t *a_node) { CRM_CHECK(a_node != NULL, return); xml_remove_prop(a_node, XML_DIFF_MARKER); xml_child_iter(a_node, child, purge_diff_markers(child); ); } gboolean apply_xml_diff(crm_data_t *old, crm_data_t *diff, crm_data_t **new) { gboolean result = TRUE; crm_data_t *added = find_xml_node(diff, "diff-added", FALSE); crm_data_t *removed = find_xml_node(diff, "diff-removed", FALSE); int root_nodes_seen = 0; CRM_CHECK(new != NULL, return FALSE); crm_debug_2("Substraction Phase"); xml_child_iter(removed, child_diff, CRM_CHECK(root_nodes_seen == 0, result = FALSE); if(root_nodes_seen == 0) { *new = subtract_xml_object(old, child_diff, NULL); } root_nodes_seen++; ); if(root_nodes_seen == 0) { *new = copy_xml(old); } else if(root_nodes_seen > 1) { crm_err("(-) Diffs cannot contain more than one change set..." " saw %d", root_nodes_seen); result = FALSE; } root_nodes_seen = 0; crm_debug_2("Addition Phase"); if(result) { xml_child_iter(added, child_diff, CRM_CHECK(root_nodes_seen == 0, result = FALSE); if(root_nodes_seen == 0) { add_xml_object(NULL, *new, child_diff); } root_nodes_seen++; ); } if(root_nodes_seen > 1) { crm_err("(+) Diffs cannot contain more than one change set..." " saw %d", root_nodes_seen); result = FALSE; } else if(result) { int lpc = 0; crm_data_t *intermediate = NULL; crm_data_t *diff_of_diff = NULL; crm_data_t *calc_added = NULL; crm_data_t *calc_removed = NULL; const char *value = NULL; const char *name = NULL; const char *version_attrs[] = { XML_ATTR_NUMUPDATES, XML_ATTR_GENERATION, XML_ATTR_GENERATION_ADMIN }; crm_debug_2("Verification Phase"); intermediate = diff_xml_object(old, *new, FALSE); calc_added = find_xml_node(intermediate, "diff-added", FALSE); calc_removed = find_xml_node(intermediate, "diff-removed", FALSE); /* add any version details to the diff so they match */ for(lpc = 0; lpc < DIMOF(version_attrs); lpc++) { name = version_attrs[lpc]; value = crm_element_value(added, name); crm_xml_add(calc_added, name, value); value = crm_element_value(removed, name); crm_xml_add(calc_removed, name, value); } diff_of_diff = diff_xml_object(intermediate, diff, TRUE); if(diff_of_diff != NULL) { crm_notice("Diff application failed!"); crm_log_xml_debug(old, "diff:old"); crm_log_xml_debug(*new, "diff:new"); log_xml_diff(LOG_DEBUG, diff_of_diff, "diff:diff_of_diff"); log_xml_diff(LOG_INFO, intermediate, "diff:actual_diff"); result = FALSE; } free_xml(diff_of_diff); free_xml(intermediate); diff_of_diff = NULL; intermediate = NULL; } if(result == FALSE) { log_xml_diff(LOG_INFO, diff, "diff:input_diff"); log_data_element("diff:input", NULL, LOG_DEBUG_2, 0, old, TRUE); /* CRM_CHECK(diff_of_diff != NULL); */ result = FALSE; } else { purge_diff_markers(*new); } return result; } crm_data_t * diff_xml_object(crm_data_t *old, crm_data_t *new, gboolean suppress) { crm_data_t *diff = NULL; crm_data_t *tmp1 = NULL; crm_data_t *added = NULL; crm_data_t *removed = NULL; tmp1 = subtract_xml_object(old, new, "removed:top"); if(tmp1 != NULL) { if(suppress && can_prune_leaf(tmp1)) { ha_msg_del(tmp1); tmp1 = NULL; } else { diff = create_xml_node(NULL, "diff"); removed = create_xml_node(diff, "diff-removed"); added = create_xml_node(diff, "diff-added"); add_node_copy(removed, tmp1); } free_xml(tmp1); } tmp1 = subtract_xml_object(new, old, "added:top"); if(tmp1 != NULL) { if(suppress && can_prune_leaf(tmp1)) { ha_msg_del(tmp1); return diff; } if(diff == NULL) { diff = create_xml_node(NULL, "diff"); } if(removed == NULL) { removed = create_xml_node(diff, "diff-removed"); } if(added == NULL) { added = create_xml_node(diff, "diff-added"); } add_node_copy(added, tmp1); free_xml(tmp1); } return diff; } gboolean can_prune_leaf(crm_data_t *xml_node) { gboolean can_prune = TRUE; /* return FALSE; */ xml_prop_iter(xml_node, prop_name, prop_value, if(safe_str_eq(prop_name, XML_ATTR_ID)) { continue; } can_prune = FALSE; ); xml_child_iter(xml_node, child, if(can_prune_leaf(child)) { cl_msg_remove_value(xml_node, child); __counter--; } else { can_prune = FALSE; } ); return can_prune; } void diff_filter_context(int context, int upper_bound, int lower_bound, crm_data_t *xml_node, crm_data_t *parent) { crm_data_t *us = NULL; crm_data_t *new_parent = parent; const char *name = crm_element_name(xml_node); CRM_CHECK(xml_node != NULL && name != NULL, return); us = create_xml_node(parent, name); xml_prop_iter(xml_node, prop_name, prop_value, lower_bound = context; crm_xml_add(us, prop_name, prop_value); ); if(lower_bound >= 0 || upper_bound >= 0) { crm_xml_add(us, XML_ATTR_ID, ID(xml_node)); new_parent = us; } else { upper_bound = in_upper_context(0, context, xml_node); if(upper_bound >= 0) { crm_xml_add(us, XML_ATTR_ID, ID(xml_node)); new_parent = us; } else { free_xml(us); us = NULL; } } xml_child_iter(us, child, diff_filter_context( context, upper_bound-1, lower_bound-1, child, new_parent); ); } int in_upper_context(int depth, int context, crm_data_t *xml_node) { gboolean has_attributes = FALSE; if(context == 0) { return 0; } xml_prop_iter(xml_node, prop_name, prop_value, has_attributes = TRUE; break; ); if(has_attributes) { return depth; } else if(depth < context) { xml_child_iter(xml_node, child, if(in_upper_context(depth+1, context, child)) { return depth; } ); } return 0; } crm_data_t * subtract_xml_object(crm_data_t *left, crm_data_t *right, const char *marker) { gboolean skip = FALSE; gboolean differences = FALSE; crm_data_t *diff = NULL; crm_data_t *child_diff = NULL; crm_data_t *right_child = NULL; const char *right_val = NULL; const char *name = NULL; const char *value = NULL; int lpc = 0; const char *filter[] = { XML_ATTR_ORIGIN, XML_DIFF_MARKER, XML_CIB_ATTR_WRITTEN, }; crm_log_xml(LOG_DEBUG_5, "left:", left); crm_log_xml(LOG_DEBUG_5, "right:", right); if(left == NULL) { return NULL; } else if(right == NULL) { crm_data_t *deleted = NULL; crm_debug_5("Processing <%s id=%s> (complete copy)", crm_element_name(left), ID(left)); deleted = copy_xml(left); crm_xml_add(deleted, XML_DIFF_MARKER, marker); return deleted; } name = crm_element_name(left); /* sanity checks */ CRM_CHECK(name != NULL, return NULL); CRM_CHECK(safe_str_eq(crm_element_name(left), crm_element_name(right)), return NULL); CRM_CHECK(safe_str_eq(ID(left), ID(right)), return NULL); diff = create_xml_node(NULL, name); /* changes to name/value pairs */ crm_debug_5("Processing <%s id=%s>", crm_str(name), ID(left)); xml_prop_iter(left, prop_name, left_value, if(safe_str_eq(prop_name, XML_ATTR_ID)) { continue; } skip = FALSE; for(lpc = 0; skip == FALSE && lpc < DIMOF(filter); lpc++){ if(safe_str_eq(prop_name, filter[lpc])) { skip = TRUE; } } if(skip) { continue; } right_val = crm_element_value(right, prop_name); if(right_val == NULL) { differences = TRUE; crm_xml_add(diff, prop_name, left_value); crm_debug_6("\t%s: %s", crm_str(prop_name), crm_str(left_value)); } else if(safe_str_eq(left_value, right_val)) { crm_debug_5("\t%s: %s (removed)", crm_str(prop_name), crm_str(left_value)); } else { differences = TRUE; crm_xml_add(diff, prop_name, left_value); crm_debug_5("\t%s: %s->%s", crm_str(prop_name), crm_str(left_value), right_val); } ); /* changes to child objects */ crm_debug_3("Processing children of <%s id=%s>",crm_str(name),ID(left)); xml_child_iter( left, left_child, right_child = find_entity( right, crm_element_name(left_child), ID(left_child)); child_diff = subtract_xml_object( left_child, right_child, marker); if(child_diff != NULL) { differences = TRUE; add_node_copy(diff, child_diff); free_xml(child_diff); } ); if(differences == FALSE) { /* check for XML_DIFF_MARKER in a child */ xml_child_iter( right, right_child, value = crm_element_value(right_child, XML_DIFF_MARKER); if(value != NULL && safe_str_eq(value, "removed:top")) { crm_debug("Found the root of the deletion: %s", name); crm_log_xml_debug(right_child, "deletion"); differences = TRUE; break; } ); } if(differences == FALSE) { free_xml(diff); crm_debug_5("\tNo changes to <%s id=%s>", crm_str(name), ID(left)); return NULL; } crm_xml_add(diff, XML_ATTR_ID, ID(left)); return diff; } int add_xml_object(crm_data_t *parent, crm_data_t *target, const crm_data_t *update) { const char *object_id = NULL; const char *object_name = NULL; crm_log_xml(LOG_DEBUG_5, "update:", update); crm_log_xml(LOG_DEBUG_5, "target:", target); CRM_CHECK(update != NULL, return 0); object_name = crm_element_name(update); object_id = ID(update); CRM_CHECK(object_name != NULL, return 0); if(target == NULL && object_id == NULL) { /* placeholder object */ target = find_xml_node(parent, object_name, FALSE); } else if(target == NULL) { target = find_entity(parent, object_name, object_id); } if(target == NULL) { target = create_xml_node(parent, object_name); CRM_CHECK(target != NULL, return 0); crm_debug_2("Added <%s%s%s/>", crm_str(object_name), object_id?" id=":"", object_id?object_id:""); } else { crm_debug_3("Found node <%s%s%s/> to update", crm_str(object_name), object_id?" id=":"", object_id?object_id:""); } copy_in_properties(target, update); xml_child_iter( update, a_child, crm_debug_4("Updating child <%s id=%s>", crm_element_name(a_child), ID(a_child)); add_xml_object(target, NULL, a_child); ); crm_debug_3("Finished with <%s id=%s>", crm_str(object_name), crm_str(object_id)); return 0; } gboolean update_xml_child(crm_data_t *child, crm_data_t *to_update) { gboolean can_update = TRUE; CRM_CHECK(child != NULL, return FALSE); CRM_CHECK(to_update != NULL, return FALSE); if(safe_str_neq(crm_element_name(to_update), crm_element_name(child))) { can_update = FALSE; } else if(safe_str_neq(ID(to_update), ID(child))) { can_update = FALSE; } else if(can_update) { crm_log_xml_debug_2(child, "Update match found..."); add_xml_object(NULL, child, to_update); } xml_child_iter( child, child_of_child, /* only update the first one */ if(can_update) { break; } can_update = update_xml_child(child_of_child, to_update); ); return can_update; } int find_xml_children(crm_data_t **children, crm_data_t *root, const char *tag, const char *field, const char *value, gboolean search_matches) { int match_found = 0; CRM_CHECK(root != NULL, return FALSE); CRM_CHECK(children != NULL, return FALSE); if(tag != NULL && safe_str_neq(tag, crm_element_name(root))) { } else if(value != NULL && safe_str_neq(value, crm_element_value(root, field))) { } else { if(*children == NULL) { *children = create_xml_node(NULL, __FUNCTION__); } add_node_copy(*children, root); match_found = 1; } if(search_matches || match_found == 0) { xml_child_iter( root, child, match_found += find_xml_children( children, child, tag, field, value, search_matches); ); } return match_found; } gboolean replace_xml_child(crm_data_t *parent, crm_data_t *child, crm_data_t *update, gboolean delete_only) { gboolean can_delete = FALSE; const char *right_val = NULL; CRM_CHECK(child != NULL, return FALSE); CRM_CHECK(update != NULL, return FALSE); if(safe_str_eq(ID(child), ID(update))) { can_delete = TRUE; } if(safe_str_neq(crm_element_name(update), crm_element_name(child))) { can_delete = FALSE; } if(can_delete && delete_only) { xml_prop_iter(update, prop_name, left_value, right_val = crm_element_value(child, prop_name); if(safe_str_neq(left_value, right_val)) { can_delete = FALSE; } ); } if(can_delete && parent != NULL) { crm_log_xml_debug_4(child, "Delete match found..."); if(delete_only) { cl_msg_remove_value(parent, child); } else { /* preserve the order */ cl_msg_replace_value(parent, child, update, sizeof(struct ha_msg), FT_STRUCT); } child = NULL; return TRUE; } else if(can_delete) { crm_log_xml_debug(child, "Cannot delete the search root"); can_delete = FALSE; } xml_child_iter( child, child_of_child, /* only delete the first one */ if(can_delete) { break; } can_delete = replace_xml_child(child, child_of_child, update, delete_only); ); return can_delete; } void hash2nvpair(gpointer key, gpointer value, gpointer user_data) { const char *name = key; const char *s_value = value; crm_data_t *xml_node = user_data; crm_data_t *xml_child = create_xml_node(xml_node, XML_CIB_TAG_NVPAIR); crm_xml_add(xml_child, XML_ATTR_ID, name); crm_xml_add(xml_child, XML_NVPAIR_ATTR_NAME, name); crm_xml_add(xml_child, XML_NVPAIR_ATTR_VALUE, s_value); crm_debug_3("dumped: name=%s value=%s", name, s_value); } void hash2field(gpointer key, gpointer value, gpointer user_data) { const char *name = key; const char *s_value = value; crm_data_t *xml_node = user_data; if(crm_element_value(xml_node, name) == NULL) { crm_xml_add(xml_node, name, s_value); crm_debug_3("dumped: %s=%s", name, s_value); } else { crm_debug_2("duplicate: %s=%s", name, s_value); } } void hash2metafield(gpointer key, gpointer value, gpointer user_data) { char *crm_name = NULL; if(key == NULL || value == NULL) { return; } crm_name = crm_concat(CRM_META, key, '_'); hash2field(crm_name, value, user_data); crm_free(crm_name); } #if CRM_DEPRECATED_SINCE_2_0_3 GHashTable * xml2list_202(crm_data_t *parent) { crm_data_t *nvpair_list = NULL; GHashTable *nvpair_hash = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); CRM_CHECK(parent != NULL, return nvpair_hash); nvpair_list = find_xml_node(parent, XML_TAG_ATTRS, FALSE); if(nvpair_list == NULL) { crm_debug("No attributes in %s", crm_element_name(parent)); crm_log_xml_debug_2( parent,"No attributes for resource op"); } xml_child_iter_filter( nvpair_list, node_iter, XML_CIB_TAG_NVPAIR, const char *key = crm_element_value( node_iter, XML_NVPAIR_ATTR_NAME); const char *value = crm_element_value( node_iter, XML_NVPAIR_ATTR_VALUE); crm_debug_2("Added %s=%s", key, value); g_hash_table_insert( nvpair_hash, crm_strdup(key), crm_strdup(value)); ); return nvpair_hash; } #endif GHashTable * xml2list(crm_data_t *parent) { crm_data_t *nvpair_list = NULL; GHashTable *nvpair_hash = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); CRM_CHECK(parent != NULL, return nvpair_hash); nvpair_list = find_xml_node(parent, XML_TAG_ATTRS, FALSE); if(nvpair_list == NULL) { crm_debug_2("No attributes in %s", crm_element_name(parent)); crm_log_xml_debug_2( parent,"No attributes for resource op"); } crm_log_xml_debug_3(nvpair_list, "Unpacking"); xml_prop_iter( nvpair_list, key, value, crm_debug_4("Added %s=%s", key, value); g_hash_table_insert( nvpair_hash, crm_strdup(key), crm_strdup(value)); ); return nvpair_hash; } static void assign_uuid(crm_data_t *xml_obj) { cl_uuid_t new_uuid; char *new_uuid_s = NULL; const char *tag_name = crm_element_name(xml_obj); const char *tag_id = ID(xml_obj); crm_malloc0(new_uuid_s, 38); cl_uuid_generate(&new_uuid); cl_uuid_unparse(&new_uuid, new_uuid_s); crm_warn("Updating object from <%s id=%s/> to <%s id=%s/>", tag_name, tag_id?tag_id:"__empty__", tag_name, new_uuid_s); crm_xml_add(xml_obj, XML_ATTR_ID, new_uuid_s); crm_log_xml_debug(xml_obj, "Updated object"); crm_free(new_uuid_s); } static gboolean tag_needs_id(const char *tag_name) { int lpc = 0; const char *allowed_list[] = { XML_TAG_CIB, XML_TAG_FRAGMENT, XML_CIB_TAG_NODES, XML_CIB_TAG_RESOURCES, XML_CIB_TAG_CONSTRAINTS, XML_CIB_TAG_STATUS, XML_LRM_TAG_RESOURCES, "configuration", "crm_config", "attributes", "operations", "diff", "diff-added", "diff-removed", }; for(lpc = 0; lpc < DIMOF(allowed_list); lpc++) { if(safe_str_eq(tag_name, allowed_list[lpc])) { /* this tag is never meant to have an ID */ return FALSE; } } return TRUE; } static gboolean non_unique_allowed(const char *tag_name) { int lpc = 0; const char *non_unique[] = { XML_LRM_TAG_RESOURCE, XML_LRM_TAG_RSC_OP, }; for(lpc = 0; lpc < DIMOF(non_unique); lpc++) { if(safe_str_eq(tag_name, non_unique[lpc])) { /* this tag can have a non-unique ID */ return TRUE; } } return FALSE; } gboolean do_id_check(crm_data_t *xml_obj, GHashTable *id_hash, gboolean silent_add, gboolean silent_rename) { char *lookup_id = NULL; gboolean modified = FALSE; char *old_id = NULL; const char *tag_id = NULL; const char *tag_name = NULL; const char *lookup_value = NULL; gboolean created_hash = FALSE; if(xml_obj == NULL) { return FALSE; } else if(id_hash == NULL) { created_hash = TRUE; id_hash = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); } xml_child_iter( xml_obj, xml_child, if(do_id_check(xml_child, id_hash, silent_add, silent_rename)) { modified = TRUE; } ); tag_id = ID(xml_obj); tag_name = TYPE(xml_obj); if(tag_needs_id(tag_name) == FALSE) { crm_debug_5("%s does not need an ID", tag_name); goto finish_id_check; } else if(tag_id != NULL && non_unique_allowed(tag_name)){ crm_debug_5("%s does not need top be unique", tag_name); goto finish_id_check; } lookup_id = NULL; if(tag_id != NULL) { lookup_id = crm_concat(tag_name, tag_id, '-'); lookup_value = g_hash_table_lookup(id_hash, lookup_id); if(lookup_value == NULL) { g_hash_table_insert(id_hash, lookup_id, crm_strdup(tag_id)); goto finish_id_check; } modified |= (!silent_rename); } else { modified |= (!silent_add); } if(tag_id != NULL) { old_id = crm_strdup(tag_id); } crm_free(lookup_id); assign_uuid(xml_obj); tag_id = ID(xml_obj); if(modified == FALSE) { /* nothing to report */ } else if(old_id != NULL && safe_str_neq(tag_id, old_id)) { crm_err("\"id\" collision detected... Multiple '%s' entries" " with id=\"%s\", assigned id=\"%s\"", tag_name, old_id, tag_id); } else if(old_id == NULL && tag_id != NULL) { crm_err("Detected <%s.../> object without an ID. Assigned: %s", tag_name, tag_id); } crm_free(old_id); finish_id_check: if(created_hash) { g_hash_table_destroy(id_hash); } return modified; } typedef struct name_value_s { const char *name; const void *value; } name_value_t; static gint sort_pairs(gconstpointer a, gconstpointer b) { const name_value_t *pair_a = a; const name_value_t *pair_b = b; if(a == NULL && b == NULL) { return 0; } else if(a == NULL) { return 1; } else if(b == NULL) { return -1; } if(pair_a->name == NULL && pair_b->name == NULL) { return 0; } else if(pair_a->name == NULL) { return 1; } else if(pair_b->name == NULL) { return -1; } return strcmp(pair_a->name, pair_b->name); } static void dump_pair(gpointer data, gpointer user_data) { name_value_t *pair = data; crm_data_t *parent = user_data; crm_xml_add(parent, pair->name, pair->value); } static void free_pair(gpointer data, gpointer user_data) { name_value_t *pair = data; crm_free(pair); } static crm_data_t * sorted_xml(const crm_data_t *input) { GListPtr sorted = NULL; GListPtr unsorted = NULL; name_value_t *pair = NULL; crm_data_t *result = NULL; const char *name = crm_element_name(input); CRM_CHECK(input != NULL, return NULL); name = crm_element_name(input); CRM_CHECK(name != NULL, return NULL); result = create_xml_node(NULL, name); xml_prop_iter(input, p_name, p_value, crm_malloc0(pair, sizeof(name_value_t)); pair->name = p_name; pair->value = p_value; unsorted = g_list_prepend(unsorted, pair); pair = NULL; ); sorted = g_list_sort(unsorted, sort_pairs); g_list_foreach(sorted, dump_pair, result); g_list_foreach(sorted, free_pair, NULL); g_list_free(sorted); return result; } /* "c048eae664dba840e1d2060f00299e9d" */ char * calculate_xml_digest(crm_data_t *input, gboolean sort) { int i = 0; int digest_len = 16; char *digest = NULL; unsigned char *raw_digest = NULL; crm_data_t *sorted = NULL; char *buffer = NULL; if(sort) { sorted = sorted_xml(input); } else { sorted = copy_xml(input); } buffer = dump_xml_formatted(sorted); CRM_CHECK(buffer != NULL && strlen(buffer) > 0, free_xml(sorted); return NULL); crm_malloc0(digest, (2 * digest_len + 1)); crm_malloc0(raw_digest, (digest_len + 1)); MD5((unsigned char *)buffer, strlen(buffer), raw_digest); for(i = 0; i < digest_len; i++) { sprintf(digest+(2*i), "%02x", raw_digest[i]); } crm_debug_2("Digest %s: %s\n", digest, buffer); crm_log_xml(LOG_DEBUG_3, "digest:source", sorted); crm_free(buffer); crm_free(raw_digest); free_xml(sorted); return digest; } #if HAVE_LIBXML2 # include # include #endif gboolean validate_with_dtd( crm_data_t *xml_blob, gboolean to_logs, const char *dtd_file) { gboolean valid = TRUE; #if HAVE_LIBXML2 char *buffer = NULL; xmlDocPtr doc = NULL; xmlDtdPtr dtd = NULL; xmlValidCtxtPtr cvp = NULL; CRM_CHECK(xml_blob != NULL, return FALSE); CRM_CHECK(dtd_file != NULL, return FALSE); buffer = dump_xml_formatted(xml_blob); CRM_CHECK(buffer != NULL, return FALSE); doc = xmlParseMemory(buffer, strlen(buffer)); CRM_CHECK(doc != NULL, crm_free(buffer); return FALSE); dtd = xmlParseDTD(NULL, (const xmlChar *)dtd_file); CRM_CHECK(dtd != NULL, crm_free(buffer); return TRUE); cvp = xmlNewValidCtxt(); CRM_CHECK(cvp != NULL, crm_free(buffer); return TRUE); if(to_logs) { cvp->userData = (void *) LOG_ERR; cvp->error = (xmlValidityErrorFunc) cl_log; cvp->warning = (xmlValidityWarningFunc) cl_log; } else { cvp->userData = (void *) stderr; cvp->error = (xmlValidityErrorFunc) fprintf; cvp->warning = (xmlValidityWarningFunc) fprintf; } if (!xmlValidateDtd(cvp, doc, dtd)) { crm_err("CIB does not validate against %s", dtd_file); valid = FALSE; crm_log_xml_debug(xml_blob, "invalid"); } xmlFreeValidCtxt(cvp); xmlFreeDtd(dtd); xmlFreeDoc(doc); crm_free(buffer); #endif return valid; } diff --git a/lib/crm/pengine/unpack.c b/lib/crm/pengine/unpack.c index 29029fbcf0..3ec9352469 100644 --- a/lib/crm/pengine/unpack.c +++ b/lib/crm/pengine/unpack.c @@ -1,1253 +1,1253 @@ /* $Id: unpack.c,v 1.18 2006/08/14 09:40:01 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include /* for ONLINESTATUS */ #include #include #include #include gboolean unpack_config(crm_data_t *config, pe_working_set_t *data_set) { const char *name = NULL; const char *value = NULL; GHashTable *config_hash = g_hash_table_new_full( g_str_hash,g_str_equal, g_hash_destroy_str,g_hash_destroy_str); data_set->config_hash = config_hash; unpack_instance_attributes( config, XML_CIB_TAG_PROPSET, NULL, config_hash, CIB_OPTIONS_FIRST, data_set->now); #if CRM_DEPRECATED_SINCE_2_0_1 xml_child_iter_filter( config, a_child, XML_CIB_TAG_NVPAIR, name = crm_element_value(a_child, XML_NVPAIR_ATTR_NAME); value = crm_element_value(a_child, XML_NVPAIR_ATTR_VALUE); if(g_hash_table_lookup(config_hash, name) == NULL) { g_hash_table_insert( config_hash,crm_strdup(name),crm_strdup(value)); } crm_config_err("Creating directly" "beneath has been depreciated since" " 2.0.1", ID(a_child), name); ); #else xml_child_iter_filter( config, a_child, XML_CIB_TAG_NVPAIR, name = crm_element_value(a_child, XML_NVPAIR_ATTR_NAME); crm_config_err("Creating directly" "beneath has been depreciated since" " 2.0.1 and is now disabled", ID(a_child), name); ); #endif verify_pe_options(data_set->config_hash); value = pe_pref(data_set->config_hash, "default-action-timeout"); data_set->transition_idle_timeout = crm_strdup(value); crm_debug("Default action timeout: %s", data_set->transition_idle_timeout); value = pe_pref(data_set->config_hash, "default-resource-stickiness"); data_set->default_resource_stickiness = char2score(value); crm_debug("Default stickiness: %d", data_set->default_resource_stickiness); value = pe_pref(data_set->config_hash, "default-resource-failure-stickiness"); data_set->default_resource_fail_stickiness = char2score(value); crm_debug("Default failure stickiness: %d", data_set->default_resource_fail_stickiness); value = pe_pref(data_set->config_hash, "stonith-enabled"); cl_str_to_boolean(value, &data_set->stonith_enabled); crm_debug("STONITH of failed nodes is %s", data_set->stonith_enabled?"enabled":"disabled"); data_set->stonith_action = pe_pref(data_set->config_hash, "stonith-action"); crm_debug_2("STONITH will %s nodes", data_set->stonith_action); value = pe_pref(data_set->config_hash, "symmetric-cluster"); cl_str_to_boolean(value, &data_set->symmetric_cluster); if(data_set->symmetric_cluster) { crm_debug("Cluster is symmetric" " - resources can run anywhere by default"); } value = pe_pref(data_set->config_hash, "no-quorum-policy"); if(safe_str_eq(value, "ignore")) { data_set->no_quorum_policy = no_quorum_ignore; } else if(safe_str_eq(value, "freeze")) { data_set->no_quorum_policy = no_quorum_freeze; } else { data_set->no_quorum_policy = no_quorum_stop; } switch (data_set->no_quorum_policy) { case no_quorum_freeze: crm_debug("On loss of CCM Quorum: Freeze resources"); break; case no_quorum_stop: crm_debug("On loss of CCM Quorum: Stop ALL resources"); break; case no_quorum_ignore: crm_notice("On loss of CCM Quorum: Ignore"); break; } value = pe_pref(data_set->config_hash, "stop-orphan-resources"); cl_str_to_boolean(value, &data_set->stop_rsc_orphans); crm_debug_2("Orphan resources are %s", data_set->stop_rsc_orphans?"stopped":"ignored"); value = pe_pref(data_set->config_hash, "stop-orphan-actions"); cl_str_to_boolean(value, &data_set->stop_action_orphans); crm_debug_2("Orphan resource actions are %s", data_set->stop_action_orphans?"stopped":"ignored"); value = pe_pref(data_set->config_hash, "remove-after-stop"); cl_str_to_boolean(value, &data_set->remove_after_stop); crm_debug_2("Stopped resources are removed from the status section: %s", data_set->remove_after_stop?"true":"false"); value = pe_pref(data_set->config_hash, "is-managed-default"); cl_str_to_boolean(value, &data_set->is_managed_default); crm_debug_2("By default resources are %smanaged", data_set->is_managed_default?"":"not "); return TRUE; } gboolean unpack_nodes(crm_data_t * xml_nodes, pe_working_set_t *data_set) { node_t *new_node = NULL; const char *id = NULL; const char *uname = NULL; const char *type = NULL; gboolean unseen_are_unclean = TRUE; const char *blind_faith = pe_pref( data_set->config_hash, "startup-fencing"); if(crm_is_true(blind_faith) == FALSE) { unseen_are_unclean = FALSE; crm_warn("Blind faith: not fencing unseen nodes"); } xml_child_iter_filter( xml_nodes, xml_obj, XML_CIB_TAG_NODE, new_node = NULL; id = crm_element_value(xml_obj, XML_ATTR_ID); uname = crm_element_value(xml_obj, XML_ATTR_UNAME); type = crm_element_value(xml_obj, XML_ATTR_TYPE); crm_debug_3("Processing node %s/%s", uname, id); if(id == NULL) { crm_config_err("Must specify id tag in "); continue; } if(type == NULL) { crm_config_err("Must specify type tag in "); continue; } crm_malloc0(new_node, sizeof(node_t)); if(new_node == NULL) { return FALSE; } new_node->weight = 0; new_node->fixed = FALSE; crm_malloc0(new_node->details, sizeof(struct node_shared_s)); if(new_node->details == NULL) { crm_free(new_node); return FALSE; } crm_debug_3("Creaing node for entry %s/%s", uname, id); new_node->details->id = id; new_node->details->uname = uname; new_node->details->type = node_ping; new_node->details->online = FALSE; new_node->details->shutdown = FALSE; new_node->details->running_rsc = NULL; new_node->details->attrs = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); /* if(data_set->have_quorum == FALSE */ /* && data_set->no_quorum_policy == no_quorum_stop) { */ /* /\* start shutting resources down *\/ */ /* new_node->weight = -INFINITY; */ /* } */ if(data_set->stonith_enabled == FALSE || unseen_are_unclean == FALSE) { /* blind faith... */ new_node->details->unclean = FALSE; } else { /* all nodes are unclean until we've seen their * status entry */ new_node->details->unclean = TRUE; } if(type == NULL || safe_str_eq(type, "member") || safe_str_eq(type, NORMALNODE)) { new_node->details->type = node_member; } add_node_attrs(xml_obj, new_node, data_set); if(crm_is_true(g_hash_table_lookup( new_node->details->attrs, "standby"))) { crm_info("Node %s is in standby-mode", new_node->details->uname); new_node->weight = -INFINITY; new_node->details->standby = TRUE; } data_set->nodes = g_list_append(data_set->nodes, new_node); crm_debug_3("Done with node %s", crm_element_value(xml_obj, XML_ATTR_UNAME)); ); return TRUE; } gboolean unpack_resources(crm_data_t * xml_resources, pe_working_set_t *data_set) { xml_child_iter( xml_resources, xml_obj, resource_t *new_rsc = NULL; crm_debug_3("Begining unpack... %s", xml_obj?crm_element_name(xml_obj):""); if(common_unpack(xml_obj, &new_rsc, NULL, data_set)) { data_set->resources = g_list_append( data_set->resources, new_rsc); print_resource(LOG_DEBUG_3, "Added", new_rsc, FALSE); } else { crm_config_err("Failed unpacking %s %s", crm_element_name(xml_obj), crm_element_value(xml_obj, XML_ATTR_ID)); if(new_rsc != NULL && new_rsc->fns != NULL) { new_rsc->fns->free(new_rsc); } } ); data_set->resources = g_list_sort( data_set->resources, sort_rsc_priority); return TRUE; } /* remove nodes that are down, stopping */ /* create +ve rsc_to_node constraints between resources and the nodes they are running on */ /* anything else? */ gboolean unpack_status(crm_data_t * status, pe_working_set_t *data_set) { const char *id = NULL; const char *uname = NULL; crm_data_t * lrm_rsc = NULL; crm_data_t * attrs = NULL; node_t *this_node = NULL; crm_debug_3("Begining unpack"); xml_child_iter_filter( status, node_state, XML_CIB_TAG_STATE, id = crm_element_value(node_state, XML_ATTR_ID); uname = crm_element_value(node_state, XML_ATTR_UNAME); attrs = find_xml_node( node_state, XML_TAG_TRANSIENT_NODEATTRS, FALSE); lrm_rsc = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE); lrm_rsc = find_xml_node(lrm_rsc, XML_LRM_TAG_RESOURCES, FALSE); crm_debug_3("Processing node %s", uname); this_node = pe_find_node_id(data_set->nodes, id); if(uname == NULL) { /* error */ continue; } else if(this_node == NULL) { crm_config_warn("Node %s in status section no longer exists", uname); continue; } /* Mark the node as provisionally clean * - at least we have seen it in the current cluster's lifetime */ this_node->details->unclean = FALSE; crm_debug_3("Adding runtime node attrs"); add_node_attrs(attrs, this_node, data_set); crm_debug_3("determining node state"); determine_online_status(node_state, this_node, data_set); if(this_node->details->online || data_set->stonith_enabled) { /* offline nodes run no resources... * unless stonith is enabled in which case we need to * make sure rsc start events happen after the stonith */ crm_debug_3("Processing lrm resource entries"); unpack_lrm_resources(this_node, lrm_rsc, data_set); } ); return TRUE; } static gboolean determine_online_status_no_fencing(crm_data_t * node_state, node_t *this_node) { gboolean online = FALSE; const char *join_state = crm_element_value(node_state, XML_CIB_ATTR_JOINSTATE); const char *crm_state = crm_element_value(node_state, XML_CIB_ATTR_CRMDSTATE); const char *ccm_state = crm_element_value(node_state, XML_CIB_ATTR_INCCM); const char *ha_state = crm_element_value(node_state, XML_CIB_ATTR_HASTATE); const char *exp_state = crm_element_value(node_state, XML_CIB_ATTR_EXPSTATE); if(ha_state == NULL) { ha_state = DEADSTATUS; } if(!crm_is_true(ccm_state) || safe_str_eq(ha_state, DEADSTATUS)){ crm_debug_2("Node is down: ha_state=%s, ccm_state=%s", crm_str(ha_state), crm_str(ccm_state)); } else if(!crm_is_true(ccm_state) || safe_str_eq(ha_state, DEADSTATUS)) { } else if(safe_str_eq(crm_state, ONLINESTATUS)) { if(safe_str_eq(join_state, CRMD_JOINSTATE_MEMBER)) { online = TRUE; } else { crm_debug("Node is not ready to run resources: %s", join_state); } } else if(this_node->details->expected_up == FALSE) { crm_debug_2("CRMd is down: ha_state=%s, ccm_state=%s", crm_str(ha_state), crm_str(ccm_state)); crm_debug_2("\tcrm_state=%s, join_state=%s, expected=%s", crm_str(crm_state), crm_str(join_state), crm_str(exp_state)); } else { /* mark it unclean */ this_node->details->unclean = TRUE; crm_warn("Node %s is partially & un-expectedly down", this_node->details->uname); crm_info("\tha_state=%s, ccm_state=%s," " crm_state=%s, join_state=%s, expected=%s", crm_str(ha_state), crm_str(ccm_state), crm_str(crm_state), crm_str(join_state), crm_str(exp_state)); } return online; } static gboolean determine_online_status_fencing(crm_data_t * node_state, node_t *this_node) { gboolean online = FALSE; const char *join_state = crm_element_value(node_state, XML_CIB_ATTR_JOINSTATE); const char *crm_state = crm_element_value(node_state, XML_CIB_ATTR_CRMDSTATE); const char *ccm_state = crm_element_value(node_state, XML_CIB_ATTR_INCCM); const char *ha_state = crm_element_value(node_state, XML_CIB_ATTR_HASTATE); const char *exp_state = crm_element_value(node_state, XML_CIB_ATTR_EXPSTATE); if(ha_state == NULL) { ha_state = DEADSTATUS; } if(crm_is_true(ccm_state) && safe_str_eq(ha_state, ACTIVESTATUS) && safe_str_eq(crm_state, ONLINESTATUS)) { online = TRUE; if(safe_str_neq(join_state, CRMD_JOINSTATE_MEMBER)) { crm_debug("Node is not ready to run resources: %s", join_state); this_node->details->standby = TRUE; } } else if(crm_is_true(ccm_state) == FALSE && safe_str_eq(ha_state, DEADSTATUS) && safe_str_eq(crm_state, OFFLINESTATUS) && this_node->details->expected_up == FALSE) { crm_debug("Node %s is down: join_state=%s, expected=%s", this_node->details->uname, crm_str(join_state), crm_str(exp_state)); } else if(this_node->details->expected_up) { /* mark it unclean */ this_node->details->unclean = TRUE; crm_warn("Node %s (%s) is un-expectedly down", this_node->details->uname, this_node->details->id); crm_info("\tha_state=%s, ccm_state=%s," " crm_state=%s, join_state=%s, expected=%s", crm_str(ha_state), crm_str(ccm_state), crm_str(crm_state), crm_str(join_state), crm_str(exp_state)); } else { crm_info("Node %s is comming up", this_node->details->uname); crm_debug("\tha_state=%s, ccm_state=%s," " crm_state=%s, join_state=%s, expected=%s", crm_str(ha_state), crm_str(ccm_state), crm_str(crm_state), crm_str(join_state), crm_str(exp_state)); } return online; } gboolean determine_online_status( crm_data_t * node_state, node_t *this_node, pe_working_set_t *data_set) { int shutdown = 0; gboolean online = FALSE; const char *exp_state = crm_element_value(node_state, XML_CIB_ATTR_EXPSTATE); if(this_node == NULL) { crm_config_err("No node to check"); return online; } ha_msg_value_int(node_state, XML_CIB_ATTR_SHUTDOWN, &shutdown); this_node->details->expected_up = FALSE; if(safe_str_eq(exp_state, CRMD_JOINSTATE_MEMBER)) { this_node->details->expected_up = TRUE; } this_node->details->shutdown = FALSE; if(shutdown != 0) { this_node->details->shutdown = TRUE; this_node->details->expected_up = FALSE; } if(data_set->stonith_enabled == FALSE) { online = determine_online_status_no_fencing( node_state, this_node); } else { online = determine_online_status_fencing( node_state, this_node); } if(online) { this_node->details->online = TRUE; } else { /* remove node from contention */ this_node->fixed = TRUE; this_node->weight = -INFINITY; } if(online && this_node->details->shutdown) { /* dont run resources here */ this_node->fixed = TRUE; this_node->weight = -INFINITY; } if(this_node->details->unclean) { pe_proc_warn("Node %s is unclean", this_node->details->uname); } else if(this_node->details->online) { crm_info("Node %s is %s", this_node->details->uname, this_node->details->shutdown?"shutting down":"online"); } else { crm_debug_2("Node %s is offline", this_node->details->uname); } return online; } #define set_char(x) last_rsc_id[len] = x; complete = TRUE; static void increment_clone(char *last_rsc_id) { gboolean complete = FALSE; int len = 0; CRM_CHECK(last_rsc_id != NULL, return); if(last_rsc_id != NULL) { len = strlen(last_rsc_id); } len--; while(complete == FALSE && len > 0) { switch (last_rsc_id[len]) { case 0: len--; break; case '0': set_char('1'); break; case '1': set_char('2'); break; case '2': set_char('3'); break; case '3': set_char('4'); break; case '4': set_char('5'); break; case '5': set_char('6'); break; case '6': set_char('7'); break; case '7': set_char('8'); break; case '8': set_char('9'); break; case '9': last_rsc_id[len] = '0'; len--; break; default: crm_err("Unexpected char: %c (%d)", last_rsc_id[len], len); break; } } } static resource_t * create_fake_resource(const char *rsc_id, crm_data_t *rsc_entry, pe_working_set_t *data_set) { resource_t *rsc = NULL; crm_data_t *xml_rsc = create_xml_node(NULL, XML_CIB_TAG_RESOURCE); crm_log_xml_info(rsc_entry, "Orphan resource"); copy_in_properties(xml_rsc, rsc_entry); crm_xml_add(xml_rsc, XML_ATTR_ID, rsc_id); common_unpack(xml_rsc, &rsc, NULL, data_set); rsc->orphan = TRUE; data_set->resources = g_list_append(data_set->resources, rsc); return rsc; } static resource_t * unpack_find_resource( pe_working_set_t *data_set, node_t *node, const char *rsc_id, crm_data_t *rsc_entry) { resource_t *rsc = NULL; gboolean is_duped_clone = FALSE; char *alt_rsc_id = crm_strdup(rsc_id); while(rsc == NULL) { crm_debug_3("looking for: %s", alt_rsc_id); rsc = pe_find_resource(data_set->resources, alt_rsc_id); /* no match */ if(rsc == NULL) { crm_debug_2("%s not found: %d", alt_rsc_id, is_duped_clone); if(is_duped_clone) { /* create one */ rsc = create_fake_resource(alt_rsc_id, rsc_entry, data_set); crm_info("Making sure orphan %s/%s is stopped on %s", rsc_id, rsc->id, node->details->uname); resource_location(rsc, NULL, -INFINITY, "__orphan_clone_dont_run__", data_set); } break; /* not running anywhere else */ } else if(rsc->running_on == NULL) { crm_debug_3("not active yet"); break; /* always unique */ } else if(rsc->globally_unique) { crm_debug_3("unique"); break; /* running somewhere already but we dont care * find another clone instead */ } else { crm_debug_2("find another one"); rsc = NULL; is_duped_clone = TRUE; increment_clone(alt_rsc_id); } } crm_free(alt_rsc_id); if(rsc != NULL) { crm_free(rsc->clone_name); rsc->clone_name = NULL; if(is_duped_clone) { crm_info("Internally renamed %s on %s to %s", rsc_id, node->details->uname, rsc->id); rsc->clone_name = crm_strdup(rsc_id); } } return rsc; } static resource_t * process_orphan_resource(crm_data_t *rsc_entry, node_t *node, pe_working_set_t *data_set) { resource_t *rsc = NULL; const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID); crm_log_xml_info(rsc_entry, "Orphan resource"); crm_config_warn("Nothing known about resource %s running on %s", rsc_id, node->details->uname); rsc = create_fake_resource(rsc_id, rsc_entry, data_set); if(data_set->stop_rsc_orphans == FALSE) { rsc->is_managed = FALSE; } else { crm_info("Making sure orphan %s is stopped", rsc_id); print_resource(LOG_DEBUG_3, "Added orphan", rsc, FALSE); CRM_CHECK(rsc != NULL, return NULL); resource_location(rsc, NULL, -INFINITY, "__orphan_dont_run__", data_set); } return rsc; } static void process_rsc_state(resource_t *rsc, node_t *node, enum action_fail_response on_fail, pe_working_set_t *data_set) { int fail_count = 0; char *fail_attr = NULL; const char *value = NULL; GHashTable *meta_hash = NULL; crm_debug_2("Resource %s is %s on %s", rsc->id, role2text(rsc->role), node->details->uname); meta_hash = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); get_meta_attributes(meta_hash, rsc, node, data_set); /* update resource preferences that relate to the current node */ value = g_hash_table_lookup(meta_hash, "resource_stickiness"); if(value != NULL && safe_str_neq("default", value)) { rsc->stickiness = char2score(value); } else { rsc->stickiness = data_set->default_resource_stickiness; } value = g_hash_table_lookup(meta_hash, XML_RSC_ATTR_FAIL_STICKINESS); if(value != NULL && safe_str_neq("default", value)) { rsc->fail_stickiness = char2score(value); } else { rsc->fail_stickiness = data_set->default_resource_fail_stickiness; } /* process failure stickiness */ fail_attr = crm_concat("fail-count", rsc->id, '-'); value = g_hash_table_lookup(node->details->attrs, fail_attr); if(value != NULL) { crm_debug("%s: %s", fail_attr, value); fail_count = crm_parse_int(value, "0"); } crm_free(fail_attr); if(fail_count > 0 && rsc->fail_stickiness != 0) { resource_location(rsc, node, fail_count * rsc->fail_stickiness, "fail_stickiness", data_set); crm_debug("Setting failure stickiness for %s on %s: %d", rsc->id, node->details->uname, fail_count * rsc->fail_stickiness); } /* process current state */ if(rsc->role != RSC_ROLE_UNKNOWN) { rsc->known_on = g_list_append(rsc->known_on, node); } if(rsc->role != RSC_ROLE_STOPPED && rsc->role != RSC_ROLE_UNKNOWN) { if(on_fail != action_fail_ignore) { rsc->failed = TRUE; crm_debug_2("Force stop"); } native_add_running(rsc, node, data_set); if(rsc->is_managed && rsc->stickiness != 0) { resource_location(rsc, node, rsc->stickiness, "stickiness", data_set); crm_debug("Resource %s: preferring current location" " (node=%s, weight=%d)", rsc->id, node->details->uname, rsc->stickiness); } if(on_fail == action_fail_ignore) { /* nothing to do */ } else if(node->details->unclean) { stop_action(rsc, node, FALSE); } else if(on_fail == action_fail_fence) { /* treat it as if it is still running * but also mark the node as unclean */ node->details->unclean = TRUE; stop_action(rsc, node, FALSE); } else if(on_fail == action_fail_block) { /* is_managed == FALSE will prevent any * actions being sent for the resource */ rsc->is_managed = FALSE; } else if(on_fail == action_fail_migrate) { stop_action(rsc, node, FALSE); /* make sure it comes up somewhere else * or not at all */ resource_location(rsc, node, -INFINITY, "__action_migration_auto__",data_set); } else { stop_action(rsc, node, FALSE); } } else if(rsc->clone_name) { crm_debug_2("Resetting clone_name %s for %s (stopped)", rsc->clone_name, rsc->id); crm_free(rsc->clone_name); rsc->clone_name = NULL; } else { char *key = stop_key(rsc); GListPtr possible_matches = find_actions(rsc->actions, key, node); slist_iter(stop, action_t, possible_matches, lpc, stop->optional = TRUE; ); crm_free(key); } g_hash_table_destroy(meta_hash); } /* create active recurring operations as optional */ static void process_recurring(node_t *node, resource_t *rsc, int start_index, int stop_index, GListPtr sorted_op_list, pe_working_set_t *data_set) { const char *task = NULL; const char *status = NULL; crm_debug_3("%s: Start index %d, stop index = %d", rsc->id, start_index, stop_index); slist_iter(rsc_op, crm_data_t, sorted_op_list, lpc, int interval = 0; char *key = NULL; const char *id = ID(rsc_op); const char *interval_s = NULL; if(node->details->online == FALSE) { crm_debug_4("Skipping %s/%s: node is offline", rsc->id, node->details->uname); break; } else if(start_index < stop_index) { crm_debug_4("Skipping %s/%s: not active", rsc->id, node->details->uname); break; } else if(lpc <= start_index) { crm_debug_4("Skipping %s/%s: old", id, node->details->uname); continue; } - - interval_s = get_interval(rsc_op); + + interval_s = crm_element_value(rsc_op,XML_LRM_ATTR_INTERVAL); interval = crm_parse_int(interval_s, "0"); if(interval == 0) { crm_debug_4("Skipping %s/%s: non-recurring", id, node->details->uname); continue; } status = crm_element_value(rsc_op, XML_LRM_ATTR_OPSTATUS); if(safe_str_eq(status, "-1")) { crm_debug_4("Skipping %s/%s: status", id, node->details->uname); continue; } task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK); /* create the action */ key = generate_op_key(rsc->id, task, interval); crm_debug_3("Creating %s/%s", key, node->details->uname); custom_action(rsc, key, task, node, TRUE, TRUE, data_set); ); } static void unpack_lrm_rsc_state( node_t *node, crm_data_t * rsc_entry, pe_working_set_t *data_set) { int stop_index = -1; int start_index = -1; int max_call_id = -1; const char *task = NULL; const char *status = NULL; const char *value = NULL; const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID); resource_t *rsc = NULL; GListPtr op_list = NULL; GListPtr sorted_op_list = NULL; enum action_fail_response on_fail = FALSE; enum rsc_role_e saved_role = RSC_ROLE_UNKNOWN; crm_debug_3("[%s] Processing %s on %s", crm_element_name(rsc_entry), rsc_id, node->details->uname); /* extract operations */ op_list = NULL; sorted_op_list = NULL; xml_child_iter_filter( rsc_entry, rsc_op, XML_LRM_TAG_RSC_OP, op_list = g_list_append(op_list, rsc_op); ); if(op_list == NULL) { /* if there are no operations, there is nothing to do */ return; } /* find the resource */ rsc = unpack_find_resource(data_set, node, rsc_id, rsc_entry); if(rsc == NULL) { rsc = process_orphan_resource(rsc_entry, node, data_set); } CRM_ASSERT(rsc != NULL); /* process operations */ max_call_id = -1; saved_role = rsc->role; on_fail = action_fail_ignore; rsc->role = RSC_ROLE_UNKNOWN; sorted_op_list = g_list_sort(op_list, sort_op_by_callid); slist_iter( rsc_op, crm_data_t, sorted_op_list, lpc, task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK); status = crm_element_value(rsc_op, XML_LRM_ATTR_OPSTATUS); if(safe_str_eq(task, CRMD_ACTION_STOP) && safe_str_eq(status, "0")) { stop_index = lpc; } else if(safe_str_eq(task, CRMD_ACTION_START)) { start_index = lpc; } else if(start_index <= stop_index && safe_str_eq(task, CRMD_ACTION_STATUS)) { const char *rc = crm_element_value(rsc_op, XML_LRM_ATTR_RC); if(safe_str_eq(rc, "0") || safe_str_eq(rc, "8")) { start_index = lpc; } } unpack_rsc_op(rsc, node, rsc_op, &max_call_id, &on_fail, data_set); ); /* create active recurring operations as optional */ process_recurring(node, rsc, start_index, stop_index, sorted_op_list, data_set); /* no need to free the contents */ g_list_free(sorted_op_list); process_rsc_state(rsc, node, on_fail, data_set); value = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); if(value != NULL && safe_str_neq("default", value)) { enum rsc_role_e req_role = text2role(value); if(req_role != RSC_ROLE_UNKNOWN && req_role != rsc->next_role){ if(rsc->next_role != RSC_ROLE_UNKNOWN) { crm_debug("%s: Overwriting calculated next role %s" " with requested next role %s", rsc->id, role2text(rsc->next_role), role2text(req_role)); } rsc->next_role = req_role; } } if(saved_role > rsc->role) { rsc->role = saved_role; } } gboolean unpack_lrm_resources(node_t *node, crm_data_t * lrm_rsc_list, pe_working_set_t *data_set) { CRM_CHECK(node != NULL, return FALSE); crm_debug_3("Unpacking resources on %s", node->details->uname); xml_child_iter_filter( lrm_rsc_list, rsc_entry, XML_LRM_TAG_RESOURCE, unpack_lrm_rsc_state(node, rsc_entry, data_set); ); return TRUE; } gboolean unpack_rsc_op(resource_t *rsc, node_t *node, crm_data_t *xml_op, int *max_call_id, enum action_fail_response *on_fail, pe_working_set_t *data_set) { const char *id = NULL; const char *task = NULL; const char *task_id = NULL; const char *actual_rc = NULL; /* const char *target_rc = NULL; */ const char *task_status = NULL; const char *interval_s = NULL; const char *op_digest = NULL; int interval = 0; int task_id_i = -1; int task_status_i = -2; int actual_rc_i = 0; action_t *action = NULL; gboolean is_probe = FALSE; gboolean is_stop_action = FALSE; CRM_CHECK(rsc != NULL, return FALSE); CRM_CHECK(node != NULL, return FALSE); CRM_CHECK(xml_op != NULL, return FALSE); id = ID(xml_op); task = crm_element_value(xml_op, XML_LRM_ATTR_TASK); task_id = crm_element_value(xml_op, XML_LRM_ATTR_CALLID); task_status = crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS); op_digest = crm_element_value(xml_op, XML_LRM_ATTR_OP_DIGEST); CRM_CHECK(id != NULL, return FALSE); CRM_CHECK(task != NULL, return FALSE); CRM_CHECK(task_status != NULL, return FALSE); task_status_i = crm_parse_int(task_status, NULL); CRM_CHECK(task_status_i <= LRM_OP_ERROR, return FALSE); CRM_CHECK(task_status_i >= LRM_OP_PENDING, return FALSE); if(safe_str_eq(task, CRMD_ACTION_NOTIFY)) { /* safe to ignore these */ return TRUE; } crm_debug_2("Unpacking task %s/%s (call_id=%s, status=%s) on %s (role=%s)", id, task, task_id, task_status, node->details->uname, role2text(rsc->role)); - interval_s = get_interval(xml_op); + interval_s = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL); interval = crm_parse_int(interval_s, "0"); if(interval == 0 && safe_str_eq(task, CRMD_ACTION_STATUS)) { is_probe = TRUE; } else if(interval > 0 && rsc->role < RSC_ROLE_STARTED) { crm_debug_3("Skipping recurring action %s for stopped resource", id); return FALSE; } if(safe_str_eq(task, CRMD_ACTION_STOP)) { is_stop_action = TRUE; } if(task_status_i != LRM_OP_PENDING) { task_id_i = crm_parse_int(task_id, "-1"); CRM_CHECK(task_id != NULL, return FALSE); CRM_CHECK(task_id_i >= 0, return FALSE); CRM_CHECK(task_id_i > *max_call_id, return FALSE); } if(*max_call_id < task_id_i) { *max_call_id = task_id_i; } if(node->details->unclean) { crm_debug_2("Node %s (where %s is running) is unclean." " Further action depends on the value of %s", node->details->uname, rsc->id, XML_RSC_ATTR_STOPFAIL); } actual_rc = crm_element_value(xml_op, XML_LRM_ATTR_RC); CRM_CHECK(actual_rc != NULL, return FALSE); actual_rc_i = crm_parse_int(actual_rc, NULL); if(EXECRA_NOT_INSTALLED == actual_rc_i) { if(is_probe) { /* treat these like stops */ is_stop_action = TRUE; task_status_i = LRM_OP_DONE; } else { task_status_i = LRM_OP_ERROR; } } else if(EXECRA_NOT_RUNNING == actual_rc_i) { if(is_probe) { /* treat these like stops */ is_stop_action = TRUE; } if(is_stop_action) { task_status_i = LRM_OP_DONE; } else { task_status_i = LRM_OP_ERROR; } } else if(EXECRA_RUNNING_MASTER == actual_rc_i) { if(is_probe || (rsc->role == RSC_ROLE_MASTER && safe_str_eq(task, CRMD_ACTION_STATUS))) { task_status_i = LRM_OP_DONE; } else { task_status_i = LRM_OP_ERROR; if(rsc->role != RSC_ROLE_MASTER) { crm_err("%s reported %s in master mode on %s", id, rsc->id, node->details->uname); } } rsc->role = RSC_ROLE_MASTER; } else if(EXECRA_FAILED_MASTER == actual_rc_i) { rsc->role = RSC_ROLE_MASTER; task_status_i = LRM_OP_ERROR; } else if(EXECRA_OK == actual_rc_i && interval > 0 && rsc->role == RSC_ROLE_MASTER) { /* catch status ops that return 0 instead of 8 while they * are supposed to be in master mode */ task_status_i = LRM_OP_ERROR; } if(task_status_i == LRM_OP_ERROR || task_status_i == LRM_OP_TIMEOUT || task_status_i == LRM_OP_NOTSUPPORTED) { action = custom_action(rsc, crm_strdup(id), task, NULL, TRUE, FALSE, data_set); if(action->on_fail == action_fail_ignore) { task_status_i = LRM_OP_DONE; } } switch(task_status_i) { case LRM_OP_PENDING: if(safe_str_eq(task, CRMD_ACTION_START)) { rsc->start_pending = TRUE; rsc->role = RSC_ROLE_STARTED; } else if(safe_str_eq(task, CRMD_ACTION_PROMOTE)) { rsc->role = RSC_ROLE_MASTER; } break; case LRM_OP_DONE: crm_debug_3("%s/%s completed on %s", rsc->id, task, node->details->uname); if(is_stop_action) { rsc->role = RSC_ROLE_STOPPED; /* clear any previous failure actions */ *on_fail = action_fail_ignore; rsc->next_role = RSC_ROLE_UNKNOWN; } else if(safe_str_eq(task, CRMD_ACTION_PROMOTE)) { rsc->role = RSC_ROLE_MASTER; } else if(safe_str_eq(task, CRMD_ACTION_DEMOTE)) { rsc->role = RSC_ROLE_SLAVE; } else if(rsc->role < RSC_ROLE_STARTED) { crm_debug_3("%s active on %s", rsc->id, node->details->uname); rsc->role = RSC_ROLE_STARTED; } break; case LRM_OP_ERROR: case LRM_OP_TIMEOUT: case LRM_OP_NOTSUPPORTED: crm_warn("Processing failed op (%s) for %s on %s", id, rsc->id, node->details->uname); if(*on_fail < action->on_fail) { *on_fail = action->on_fail; } if(task_status_i == LRM_OP_NOTSUPPORTED || is_stop_action || safe_str_eq(task, CRMD_ACTION_START) ) { crm_warn("Handling failed %s for %s on %s", task, rsc->id, node->details->uname); resource_location(rsc, node, -INFINITY, "__dont_run__failed_stopstart__", data_set); } if(safe_str_eq(task, CRMD_ACTION_PROMOTE)) { rsc->role = RSC_ROLE_MASTER; } else if(safe_str_eq(task, CRMD_ACTION_DEMOTE)) { rsc->role = RSC_ROLE_MASTER; } else if(rsc->role < RSC_ROLE_STARTED) { rsc->role = RSC_ROLE_STARTED; } crm_debug_2("Resource %s: role=%s, unclean=%s, on_fail=%s, fail_role=%s", rsc->id, role2text(rsc->role), node->details->unclean?"true":"false", fail2text(action->on_fail), role2text(action->fail_role)); if(action->fail_role != RSC_ROLE_STARTED && rsc->next_role < action->fail_role) { rsc->next_role = action->fail_role; } if(action->fail_role == RSC_ROLE_STOPPED) { crm_err("Making sure %s doesn't come up again", rsc->id); /* make sure it doesnt come up again */ pe_free_shallow_adv(rsc->allowed_nodes, TRUE); rsc->allowed_nodes = node_list_dup( data_set->nodes, FALSE, FALSE); slist_iter( node, node_t, rsc->allowed_nodes, lpc, node->weight = -INFINITY; ); } pe_free_action(action); action = NULL; break; case LRM_OP_CANCELLED: /* do nothing?? */ pe_err("Dont know what to do for cancelled ops yet"); break; } crm_debug_3("Resource %s after %s: role=%s", rsc->id, task, role2text(rsc->role)); pe_free_action(action); return TRUE; } gboolean add_node_attrs(crm_data_t *xml_obj, node_t *node, pe_working_set_t *data_set) { g_hash_table_insert(node->details->attrs, crm_strdup("#"XML_ATTR_UNAME), crm_strdup(node->details->uname)); g_hash_table_insert(node->details->attrs, crm_strdup("#"XML_ATTR_ID), crm_strdup(node->details->id)); if(safe_str_eq(node->details->id, data_set->dc_uuid)) { data_set->dc_node = node; node->details->is_dc = TRUE; g_hash_table_insert(node->details->attrs, crm_strdup("#"XML_ATTR_DC), crm_strdup(XML_BOOLEAN_TRUE)); } else { g_hash_table_insert(node->details->attrs, crm_strdup("#"XML_ATTR_DC), crm_strdup(XML_BOOLEAN_FALSE)); } unpack_instance_attributes( xml_obj, XML_TAG_ATTR_SETS, NULL, node->details->attrs, NULL, data_set->now); return TRUE; } diff --git a/lib/crm/pengine/utils.c b/lib/crm/pengine/utils.c index 89d910cf88..8af35a79b8 100644 --- a/lib/crm/pengine/utils.c +++ b/lib/crm/pengine/utils.c @@ -1,1295 +1,1277 @@ /* $Id: utils.c,v 1.11 2006/08/14 09:14:45 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include void print_str_str(gpointer key, gpointer value, gpointer user_data); gboolean ghash_free_str_str(gpointer key, gpointer value, gpointer user_data); void unpack_operation( action_t *action, crm_data_t *xml_obj, pe_working_set_t* data_set); void pe_free_shallow(GListPtr alist) { pe_free_shallow_adv(alist, TRUE); } void pe_free_shallow_adv(GListPtr alist, gboolean with_data) { GListPtr item; GListPtr item_next = alist; while(item_next != NULL) { item = item_next; item_next = item_next->next; if(with_data) { /* crm_debug_5("freeing %p", item->data); */ crm_free(item->data); } item->data = NULL; item->next = NULL; g_list_free(item); } } node_t * pe_find_node_id(GListPtr nodes, const char *id) { unsigned lpc = 0; node_t *node = NULL; for(lpc = 0; lpc < g_list_length(nodes); lpc++) { node = g_list_nth_data(nodes, lpc); if(safe_str_eq(node->details->id, id)) { return node; } } /* error */ return NULL; } node_t * node_copy(node_t *this_node) { node_t *new_node = NULL; CRM_CHECK(this_node != NULL, return NULL); crm_malloc0(new_node, sizeof(node_t)); CRM_CHECK(new_node != NULL, return NULL); crm_debug_5("Copying %p (%s) to %p", this_node, this_node->details->uname, new_node); new_node->weight = this_node->weight; new_node->fixed = this_node->fixed; new_node->details = this_node->details; return new_node; } /* are the contents of list1 and list2 equal * nodes with weight < 0 are ignored if filter == TRUE * * slow but linear * */ gboolean node_list_eq(GListPtr list1, GListPtr list2, gboolean filter) { node_t *other_node; GListPtr lhs = list1; GListPtr rhs = list2; slist_iter( node, node_t, lhs, lpc, if(node == NULL || (filter && node->weight < 0)) { continue; } other_node = (node_t*) pe_find_node_id(rhs, node->details->id); if(other_node == NULL || other_node->weight < 0) { return FALSE; } ); lhs = list2; rhs = list1; slist_iter( node, node_t, lhs, lpc, if(node == NULL || (filter && node->weight < 0)) { continue; } other_node = (node_t*) pe_find_node_id(rhs, node->details->id); if(other_node == NULL || other_node->weight < 0) { return FALSE; } ); return TRUE; } /* the intersection of list1 and list2 */ GListPtr node_list_and(GListPtr list1, GListPtr list2, gboolean filter) { GListPtr result = NULL; unsigned lpc = 0; for(lpc = 0; lpc < g_list_length(list1); lpc++) { node_t *node = (node_t*)g_list_nth_data(list1, lpc); node_t *other_node = pe_find_node_id(list2, node->details->id); node_t *new_node = NULL; if(other_node != NULL) { new_node = node_copy(node); } if(new_node != NULL) { crm_debug_4("%s: %d + %d", node->details->uname, other_node->weight, new_node->weight); new_node->weight = merge_weights( new_node->weight, other_node->weight); crm_debug_3("New node weight for %s: %d", new_node->details->uname, new_node->weight); if(filter && new_node->weight < 0) { crm_free(new_node); new_node = NULL; } } if(new_node != NULL) { result = g_list_append(result, new_node); } } return result; } /* list1 - list2 */ GListPtr node_list_minus(GListPtr list1, GListPtr list2, gboolean filter) { GListPtr result = NULL; slist_iter( node, node_t, list1, lpc, node_t *other_node = pe_find_node_id(list2, node->details->id); node_t *new_node = NULL; if(node == NULL || other_node != NULL || (filter && node->weight < 0)) { continue; } new_node = node_copy(node); result = g_list_append(result, new_node); ); crm_debug_3("Minus result len: %d", g_list_length(result)); return result; } /* list1 + list2 - (intersection of list1 and list2) */ GListPtr node_list_xor(GListPtr list1, GListPtr list2, gboolean filter) { GListPtr result = NULL; slist_iter( node, node_t, list1, lpc, node_t *new_node = NULL; node_t *other_node = (node_t*) pe_find_node_id(list2, node->details->id); if(node == NULL || other_node != NULL || (filter && node->weight < 0)) { continue; } new_node = node_copy(node); result = g_list_append(result, new_node); ); slist_iter( node, node_t, list2, lpc, node_t *new_node = NULL; node_t *other_node = (node_t*) pe_find_node_id(list1, node->details->id); if(node == NULL || other_node != NULL || (filter && node->weight < 0)) { continue; } new_node = node_copy(node); result = g_list_append(result, new_node); ); crm_debug_3("Xor result len: %d", g_list_length(result)); return result; } GListPtr node_list_or(GListPtr list1, GListPtr list2, gboolean filter) { node_t *other_node = NULL; GListPtr result = NULL; gboolean needs_filter = FALSE; result = node_list_dup(list1, FALSE, filter); slist_iter( node, node_t, list2, lpc, if(node == NULL) { continue; } other_node = (node_t*)pe_find_node_id( result, node->details->id); if(other_node != NULL) { crm_debug_4("%s + %s: %d + %d", node->details->uname, other_node->details->uname, node->weight, other_node->weight); other_node->weight = merge_weights( other_node->weight, node->weight); if(filter && node->weight < 0) { needs_filter = TRUE; } } else if(filter == FALSE || node->weight >= 0) { node_t *new_node = node_copy(node); result = g_list_append(result, new_node); } ); /* not the neatest way, but the most expedient for now */ if(filter && needs_filter) { GListPtr old_result = result; result = node_list_dup(old_result, FALSE, filter); pe_free_shallow_adv(old_result, TRUE); } return result; } GListPtr node_list_dup(GListPtr list1, gboolean reset, gboolean filter) { GListPtr result = NULL; slist_iter( this_node, node_t, list1, lpc, node_t *new_node = NULL; if(filter && this_node->weight < 0) { continue; } new_node = node_copy(this_node); if(reset) { new_node->weight = 0; } if(new_node != NULL) { result = g_list_append(result, new_node); } ); return result; } gint sort_rsc_priority(gconstpointer a, gconstpointer b) { const resource_t *resource1 = (const resource_t*)a; const resource_t *resource2 = (const resource_t*)b; if(a == NULL && b == NULL) { return 0; } if(a == NULL) { return 1; } if(b == NULL) { return -1; } if(resource1->priority > resource2->priority) { return -1; } if(resource1->priority < resource2->priority) { return 1; } return 0; } action_t * custom_action(resource_t *rsc, char *key, const char *task, node_t *on_node, gboolean optional, gboolean save_action, pe_working_set_t *data_set) { action_t *action = NULL; GListPtr possible_matches = NULL; CRM_CHECK(key != NULL, return NULL); CRM_CHECK(task != NULL, return NULL); if(save_action && rsc != NULL) { possible_matches = find_actions(rsc->actions, key, on_node); } if(possible_matches != NULL) { crm_free(key); if(g_list_length(possible_matches) > 1) { pe_warn("Action %s for %s on %s exists %d times", task, rsc?rsc->id:"", on_node?on_node->details->uname:"", g_list_length(possible_matches)); } action = g_list_nth_data(possible_matches, 0); crm_debug_4("Found existing action (%d) %s for %s on %s", action->id, task, rsc?rsc->id:"", on_node?on_node->details->uname:""); } if(action == NULL) { if(save_action) { crm_debug_2("Creating%s action %d: %s for %s on %s", optional?"":" manditory", data_set->action_id, key, rsc?rsc->id:"", on_node?on_node->details->uname:""); } crm_malloc0(action, sizeof(action_t)); if(action != NULL) { if(save_action) { action->id = data_set->action_id++; } else { action->id = 0; } action->rsc = rsc; action->task = task; action->node = on_node; action->actions_before = NULL; action->actions_after = NULL; action->failure_is_fatal = TRUE; action->pseudo = FALSE; action->dumped = FALSE; action->runnable = TRUE; action->processed = FALSE; action->optional = optional; action->seen_count = 0; action->extra = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); action->meta = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); if(save_action) { data_set->actions = g_list_append( data_set->actions, action); } action->uuid = key; if(rsc != NULL) { action->op_entry = find_rsc_op_entry(rsc, key); unpack_operation( action, action->op_entry, data_set); if(save_action) { rsc->actions = g_list_append( rsc->actions, action); } } if(save_action) { crm_debug_4("Action %d created", action->id); } } } if(optional == FALSE && action->optional) { crm_debug_2("Action %d (%s) marked manditory", action->id, action->uuid); action->optional = FALSE; } if(rsc != NULL) { enum action_tasks a_task = text2task(action->task); int warn_level = LOG_DEBUG_3; if(save_action) { warn_level = LOG_WARNING; } if(action->node != NULL && action->op_entry != NULL) { unpack_instance_attributes( action->op_entry, XML_TAG_ATTR_SETS, action->node->details->attrs, action->extra, NULL, data_set->now); } if(action->node == NULL) { action->runnable = FALSE; } else if(rsc->is_managed == FALSE) { do_crm_log(warn_level, "Action %s %s is for %s (unmanaged)", action->uuid, task, rsc->id); action->optional = TRUE; /* action->runnable = FALSE; */ #if 0 } else if(action->node->details->unclean) { do_crm_log(warn_level, "Action %s on %s is unrunnable (unclean)", action->uuid, action->node?action->node->details->uname:""); action->runnable = FALSE; #endif } else if(action->node->details->online == FALSE) { action->runnable = FALSE; do_crm_log(warn_level, "Action %s on %s is unrunnable (offline)", action->uuid, action->node->details->uname); if(action->rsc->is_managed && save_action && a_task == stop_rsc) { do_crm_log(warn_level, "Marking node %s unclean", action->node->details->uname); action->node->details->unclean = TRUE; } } else if(action->needs == rsc_req_nothing) { crm_debug_3("Action %s doesnt require anything", action->uuid); action->runnable = TRUE; #if 0 /* * No point checking this * - if we dont have quorum we cant stonith anyway */ } else if(action->needs == rsc_req_stonith) { crm_debug_3("Action %s requires only stonith", action->uuid); action->runnable = TRUE; #endif } else if(data_set->have_quorum == FALSE && data_set->no_quorum_policy == no_quorum_stop) { action->runnable = FALSE; crm_debug("%s\t%s %s (cancelled : quorum)", action->node->details->uname, action->task, rsc->id); } else if(data_set->have_quorum == FALSE && data_set->no_quorum_policy == no_quorum_freeze) { crm_debug_3("Check resource is already active"); if(rsc->fns->active(rsc, TRUE) == FALSE) { action->runnable = FALSE; crm_debug("%s\t%s %s (cancelled : quorum freeze)", action->node->details->uname, action->task, rsc->id); } } else { crm_debug_3("Action %s is runnable", action->uuid); action->runnable = TRUE; } if(save_action) { switch(a_task) { case stop_rsc: rsc->stopping = TRUE; break; case start_rsc: rsc->starting = FALSE; if(action->runnable) { rsc->starting = TRUE; } break; default: break; } } } return action; } void unpack_operation( action_t *action, crm_data_t *xml_obj, pe_working_set_t* data_set) { int value_i = 0; int start_delay = 0; char *value_ms = NULL; const char *class = NULL; const char *value = NULL; const char *field = NULL; CRM_CHECK(action->rsc != NULL, return); if(xml_obj != NULL) { value = crm_element_value(xml_obj, "prereq"); } if(value == NULL && safe_str_eq(action->task, CRMD_ACTION_START)) { value = g_hash_table_lookup(action->rsc->meta, "start_prereq"); } if(value == NULL && safe_str_neq(action->task, CRMD_ACTION_START)) { /* todo: integrate stop as an option? */ action->needs = rsc_req_nothing; value = "nothing (default)"; } else if(safe_str_eq(value, "nothing")) { action->needs = rsc_req_nothing; } else if(safe_str_eq(value, "quorum")) { action->needs = rsc_req_quorum; } else if(safe_str_eq(value, "fencing")) { action->needs = rsc_req_stonith; } else if(data_set->no_quorum_policy == no_quorum_ignore) { action->needs = rsc_req_nothing; value = "nothing (default)"; } else if(data_set->no_quorum_policy == no_quorum_freeze && data_set->stonith_enabled) { action->needs = rsc_req_stonith; value = "fencing (default)"; } else { action->needs = rsc_req_quorum; value = "quorum (default)"; } class = g_hash_table_lookup(action->rsc->meta, "class"); if(safe_str_eq(class, "stonith")) { if(action->needs == rsc_req_stonith) { crm_config_err("Stonith resources (eg. %s) cannot require" " fencing to start", action->rsc->id); } action->needs = rsc_req_nothing; value = "nothing (fencing override)"; } crm_debug_3("\tAction %s requires: %s", action->task, value); value = NULL; if(xml_obj != NULL) { value = crm_element_value(xml_obj, "on_fail"); } if(value == NULL && safe_str_eq(action->task, CRMD_ACTION_STOP)) { value = g_hash_table_lookup( action->rsc->meta, "on_stopfail"); if(value != NULL) { #if CRM_DEPRECATED_SINCE_2_0_2 crm_config_err("The \"on_stopfail\" attribute used in" " %s has been deprecated since 2.0.2", action->rsc->id); #else crm_config_err("The \"on_stopfail\" attribute used in" " %s has been deprecated since 2.0.2" " and is now disabled", action->rsc->id); value = NULL; #endif crm_config_err("Please use specify the \"on_fail\"" " attribute on the \"stop\" operation" " instead"); } } if(value == NULL) { } else if(safe_str_eq(value, "block")) { action->on_fail = action_fail_block; } else if(safe_str_eq(value, "fence")) { action->on_fail = action_fail_fence; value = "node fencing"; } else if(safe_str_eq(value, "ignore")) { action->on_fail = action_fail_ignore; value = "ignore"; } else if(safe_str_eq(value, "migrate")) { action->on_fail = action_fail_migrate; value = "force migration"; } else if(safe_str_eq(value, "stop")) { action->fail_role = RSC_ROLE_STOPPED; value = "stop resource"; } else if(safe_str_eq(value, "restart") || safe_str_eq(value, "nothing")) { action->on_fail = action_fail_recover; value = "restart (and possibly migrate)"; } else { pe_err("Resource %s: Unknown failure type (%s)", action->rsc->id, value); value = NULL; } /* defaults */ if(value == NULL && safe_str_eq(action->task, CRMD_ACTION_STOP)) { if(data_set->stonith_enabled) { action->on_fail = action_fail_fence; value = "resource fence (default)"; } else { action->on_fail = action_fail_block; value = "resource block (default)"; } } else if(value == NULL) { action->on_fail = action_fail_recover; value = "restart (and possibly migrate) (default)"; } crm_debug_3("\t%s failure handling: %s", action->task, value); value = NULL; if(xml_obj != NULL) { value = crm_element_value(xml_obj, "role_after_failure"); } if(value != NULL && action->fail_role == RSC_ROLE_UNKNOWN) { action->fail_role = text2role(value); } /* defaults */ if(action->fail_role == RSC_ROLE_UNKNOWN) { if(safe_str_eq(action->task, CRMD_ACTION_PROMOTE)) { action->fail_role = RSC_ROLE_SLAVE; } else { action->fail_role = RSC_ROLE_STARTED; } } crm_debug_3("\t%s failure results in: %s", action->task, role2text(action->fail_role)); if(xml_obj != NULL) { xml_prop_iter(xml_obj, p_name, p_value, if(p_value != NULL) { g_hash_table_insert(action->meta, crm_strdup(p_name), crm_strdup(p_value)); } ); unpack_instance_attributes(xml_obj, XML_TAG_META_SETS, NULL, action->meta, NULL, data_set->now); unpack_instance_attributes(xml_obj, XML_TAG_ATTR_SETS, NULL, action->meta, NULL, data_set->now); } field = XML_LRM_ATTR_INTERVAL; value = g_hash_table_lookup(action->meta, field); if(value != NULL) { value_i = crm_get_msec(value); if(value_i < 0) { value_i = 0; } value_ms = crm_itoa(value_i); g_hash_table_replace(action->meta, crm_strdup(field), value_ms); } field = "start_delay"; value = g_hash_table_lookup(action->meta, field); if(value != NULL) { value_i = crm_get_msec(value); if(value_i < 0) { value_i = 0; } start_delay = value_i; value_ms = crm_itoa(value_i); g_hash_table_replace(action->meta, crm_strdup(field), value_ms); } field = "timeout"; value = g_hash_table_lookup(action->meta, field); if(value == NULL) { value = pe_pref( data_set->config_hash, "default-action-timeout"); } value_i = crm_get_msec(value); if(value_i < 0) { value_i = 0; } value_i += start_delay; value_ms = crm_itoa(value_i); g_hash_table_replace(action->meta, crm_strdup(field), value_ms); } crm_data_t * find_rsc_op_entry(resource_t *rsc, const char *key) { const char *name = NULL; const char *value = NULL; const char *interval = NULL; char *match_key = NULL; crm_data_t *op = NULL; xml_child_iter_filter( rsc->ops_xml, operation, "op", name = crm_element_value(operation, "name"); interval = crm_element_value(operation, XML_LRM_ATTR_INTERVAL); value = crm_element_value(operation, "disabled"); if(crm_is_true(value)) { crm_debug_2("%s disabled", ID(operation)); continue; } match_key = generate_op_key( rsc->id, name, crm_get_msec(interval)); if(safe_str_eq(key, match_key)) { op = operation; } crm_free(match_key); if(op != NULL) { return op; } ); crm_debug_3("No match for %s", key); return op; } void print_node(const char *pre_text, node_t *node, gboolean details) { if(node == NULL) { crm_debug_4("%s%s: ", pre_text==NULL?"":pre_text, pre_text==NULL?"":": "); return; } crm_debug_4("%s%s%sNode %s: (weight=%d, fixed=%s)", pre_text==NULL?"":pre_text, pre_text==NULL?"":": ", node->details==NULL?"error ":node->details->online?"":"Unavailable/Unclean ", node->details->uname, node->weight, node->fixed?"True":"False"); if(details && node != NULL && node->details != NULL) { char *pe_mutable = crm_strdup("\t\t"); crm_debug_4("\t\t===Node Attributes"); g_hash_table_foreach(node->details->attrs, print_str_str, pe_mutable); crm_free(pe_mutable); crm_debug_4("\t\t=== Resources"); slist_iter( rsc, resource_t, node->details->running_rsc, lpc, print_resource(LOG_DEBUG_4, "\t\t", rsc, FALSE); ); } } /* * Used by the HashTable for-loop */ void print_str_str(gpointer key, gpointer value, gpointer user_data) { crm_debug_4("%s%s %s ==> %s", user_data==NULL?"":(char*)user_data, user_data==NULL?"":": ", (char*)key, (char*)value); } void print_resource( int log_level, const char *pre_text, resource_t *rsc, gboolean details) { long options = pe_print_log; if(rsc == NULL) { do_crm_log(log_level-1, "%s%s: ", pre_text==NULL?"":pre_text, pre_text==NULL?"":": "); return; } if(details) { options |= pe_print_details; } rsc->fns->print(rsc, pre_text, options, &log_level); } void log_action(unsigned int log_level, const char *pre_text, action_t *action, gboolean details) { const char *node_uname = NULL; const char *node_uuid = NULL; if(action == NULL) { do_crm_log(log_level, "%s%s: ", pre_text==NULL?"":pre_text, pre_text==NULL?"":": "); return; } if(action->pseudo) { node_uname = NULL; node_uuid = NULL; } else if(action->node != NULL) { node_uname = action->node->details->uname; node_uuid = action->node->details->id; } else { node_uname = ""; node_uuid = NULL; } switch(text2task(action->task)) { case stonith_node: case shutdown_crm: do_crm_log(log_level, "%s%s%sAction %d: %s%s%s%s%s%s", pre_text==NULL?"":pre_text, pre_text==NULL?"":": ", action->pseudo?"Pseduo ":action->optional?"Optional ":action->runnable?action->processed?"":"(Provisional) ":"!!Non-Startable!! ", action->id, action->uuid, node_uname?"\ton ":"", node_uname?node_uname:"", node_uuid?"\t\t(":"", node_uuid?node_uuid:"", node_uuid?")":""); break; default: do_crm_log(log_level, "%s%s%sAction %d: %s %s%s%s%s%s%s", pre_text==NULL?"":pre_text, pre_text==NULL?"":": ", action->optional?"Optional ":action->pseudo?"Pseduo ":action->runnable?action->processed?"":"(Provisional) ":"!!Non-Startable!! ", action->id, action->uuid, safe_val3("", action, rsc, id), node_uname?"\ton ":"", node_uname?node_uname:"", node_uuid?"\t\t(":"", node_uuid?node_uuid:"", node_uuid?")":""); break; } if(details) { do_crm_log(log_level+1, "\t\t====== Preceeding Actions"); slist_iter( other, action_wrapper_t, action->actions_before, lpc, log_action(log_level+1, "\t\t", other->action, FALSE); ); #if 1 do_crm_log(log_level+1, "\t\t====== Subsequent Actions"); slist_iter( other, action_wrapper_t, action->actions_after, lpc, log_action(log_level+1, "\t\t", other->action, FALSE); ); #endif do_crm_log(log_level+1, "\t\t====== End"); } else { do_crm_log(log_level, "\t\t(seen=%d, before=%d, after=%d)", action->seen_count, g_list_length(action->actions_before), g_list_length(action->actions_after)); } } void pe_free_action(action_t *action) { if(action == NULL) { return; } pe_free_shallow(action->actions_before);/* action_warpper_t* */ pe_free_shallow(action->actions_after); /* action_warpper_t* */ g_hash_table_destroy(action->extra); g_hash_table_destroy(action->meta); crm_free(action->uuid); crm_free(action); } GListPtr find_recurring_actions(GListPtr input, node_t *not_on_node) { const char *value = NULL; GListPtr result = NULL; CRM_CHECK(input != NULL, return NULL); slist_iter( action, action_t, input, lpc, value = g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL); if(value == NULL) { /* skip */ } else if(safe_str_eq(value, "0")) { /* skip */ } else if(safe_str_eq(CRMD_ACTION_CANCEL, action->task)) { /* skip */ } else if(not_on_node == NULL) { crm_debug_5("(null) Found: %s", action->uuid); result = g_list_append(result, action); } else if(action->node == NULL) { /* skip */ } else if(action->node->details != not_on_node->details) { crm_debug_5("Found: %s", action->uuid); result = g_list_append(result, action); } ); return result; } GListPtr find_actions(GListPtr input, const char *key, node_t *on_node) { GListPtr result = NULL; CRM_CHECK(key != NULL, return NULL); slist_iter( action, action_t, input, lpc, crm_debug_5("Matching %s against %s", key, action->uuid); if(safe_str_neq(key, action->uuid)) { continue; } else if(on_node == NULL) { result = g_list_append(result, action); } else if(action->node == NULL) { /* skip */ crm_debug_2("While looking for %s action on %s, " "found an unallocated one. Assigning" " it to the requested node...", key, on_node->details->uname); action->node = on_node; result = g_list_append(result, action); } else if(safe_str_eq(on_node->details->id, action->node->details->id)) { result = g_list_append(result, action); } ); return result; } GListPtr find_actions_exact(GListPtr input, const char *key, node_t *on_node) { GListPtr result = NULL; CRM_CHECK(key != NULL, return NULL); slist_iter( action, action_t, input, lpc, crm_debug_5("Matching %s against %s", key, action->uuid); if(safe_str_neq(key, action->uuid)) { crm_debug_3("Key mismatch: %s vs. %s", key, action->uuid); continue; } else if(on_node == NULL || action->node == NULL) { crm_debug_3("on_node=%p, action->node=%p", on_node, action->node); continue; } else if(safe_str_eq(on_node->details->id, action->node->details->id)) { result = g_list_append(result, action); } crm_debug_2("Node mismatch: %s vs. %s", on_node->details->id, action->node->details->id); ); return result; } void set_id(crm_data_t * xml_obj, const char *prefix, int child) { int id_len = 0; gboolean use_prefix = TRUE; gboolean use_child = TRUE; char *new_id = NULL; const char *id = crm_element_value(xml_obj, XML_ATTR_ID); id_len = 1 + strlen(id); if(child > 999) { pe_err("Are you insane?!?" " The CRM does not support > 1000 children per resource"); return; } else if(child < 0) { use_child = FALSE; } else { id_len += 4; /* child */ } if(prefix == NULL || safe_str_eq(id, prefix)) { use_prefix = FALSE; } else { id_len += (1 + strlen(prefix)); } crm_malloc0(new_id, id_len); if(use_child) { snprintf(new_id, id_len, "%s%s%s:%d", use_prefix?prefix:"", use_prefix?":":"", id, child); } else { snprintf(new_id, id_len, "%s%s%s", use_prefix?prefix:"", use_prefix?":":"", id); } crm_xml_add(xml_obj, XML_ATTR_ID, new_id); crm_free(new_id); } static void resource_node_score(resource_t *rsc, node_t *node, int score, const char *tag) { node_t *match = NULL; crm_debug_2("Setting %s for %s on %s: %d", tag, rsc->id, node->details->uname, score); match = pe_find_node_id(rsc->allowed_nodes, node->details->id); if(match == NULL) { match = node_copy(node); match->weight = 0; rsc->allowed_nodes = g_list_append(rsc->allowed_nodes, match); } match->weight = merge_weights(match->weight, score); } void resource_location(resource_t *rsc, node_t *node, int score, const char *tag, pe_working_set_t *data_set) { CRM_CHECK(rsc->variant == pe_native, return); if(node != NULL) { resource_node_score(rsc, node, score, tag); } else if(data_set != NULL) { slist_iter( node, node_t, data_set->nodes, lpc, resource_node_score(rsc, node, score, tag); ); } else { slist_iter( node, node_t, rsc->allowed_nodes, lpc, resource_node_score(rsc, node, score, tag); ); } if(node == NULL && score == -INFINITY) { if(rsc->allocated_to) { crm_info("Deallocating %s from %s", rsc->id, rsc->allocated_to->details->uname); crm_free(rsc->allocated_to); rsc->allocated_to = NULL; } } } void order_actions( action_t *lh_action, action_t *rh_action, enum pe_ordering order) { action_wrapper_t *wrapper = NULL; GListPtr list = NULL; crm_debug_2("Ordering Action %s before %s", lh_action->uuid, rh_action->uuid); log_action(LOG_DEBUG_4, "LH (order_actions)", lh_action, FALSE); log_action(LOG_DEBUG_4, "RH (order_actions)", rh_action, FALSE); crm_malloc0(wrapper, sizeof(action_wrapper_t)); if(wrapper != NULL) { wrapper->action = rh_action; wrapper->type = order; list = lh_action->actions_after; list = g_list_append(list, wrapper); lh_action->actions_after = list; wrapper = NULL; } if(order != pe_ordering_recover) { crm_malloc0(wrapper, sizeof(action_wrapper_t)); if(wrapper != NULL) { wrapper->action = lh_action; wrapper->type = order; list = rh_action->actions_before; list = g_list_append(list, wrapper); rh_action->actions_before = list; } } } -const char * -get_interval(crm_data_t *xml_op) -{ - const char *interval_s = NULL; - interval_s = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL); -#if CRM_DEPRECATED_SINCE_2_0_4 - if(interval_s == NULL) { - crm_data_t *params = NULL; - params = find_xml_node(xml_op, XML_TAG_PARAMS, FALSE); - if(params != NULL) { - interval_s = crm_element_value( - params, XML_LRM_ATTR_INTERVAL); - } - } -#endif - - CRM_CHECK(interval_s != NULL, - crm_err("Invalid rsc op: %s", ID(xml_op)); return "0"); - - return interval_s; -} - #define sort_return(an_int) crm_free(a_uuid); crm_free(b_uuid); return an_int gint sort_op_by_callid(gconstpointer a, gconstpointer b) { char *a_uuid = NULL; char *b_uuid = NULL; const char *a_task_id = cl_get_string(a, XML_LRM_ATTR_CALLID); const char *b_task_id = cl_get_string(b, XML_LRM_ATTR_CALLID); const char *a_key = cl_get_string(a, XML_ATTR_TRANSITION_MAGIC); const char *b_key = cl_get_string(b, XML_ATTR_TRANSITION_MAGIC); const char *a_xml_id = ID(a); const char *b_xml_id = ID(b); + + int dummy = -1; int a_id = -1; int b_id = -1; int a_rc = -1; int b_rc = -1; int a_status = -1; int b_status = -1; int a_call_id = -1; int b_call_id = -1; if(safe_str_eq(a_xml_id, b_xml_id)) { /* We have duplicate lrm_rsc_op entries in the status * section which is unliklely to be a good thing * - we can handle it easily enough, but we need to get * to the bottom of why its happening. */ pe_err("Duplicate lrm_rsc_op entries named %s", a_xml_id); sort_return(0); } CRM_CHECK(a_task_id != NULL && b_task_id != NULL, sort_return(0)); a_call_id = crm_parse_int(a_task_id, NULL); b_call_id = crm_parse_int(b_task_id, NULL); if(a_call_id == -1 && b_call_id == -1) { /* both are pending ops so it doesnt matter since * stops are never pending */ sort_return(0); } else if(a_call_id >= 0 && a_call_id < b_call_id) { crm_debug_4("%s (%d) < %s (%d) : call id", ID(a), a_call_id, ID(b), b_call_id); sort_return(-1); } else if(b_call_id >= 0 && a_call_id > b_call_id) { crm_debug_4("%s (%d) > %s (%d) : call id", ID(a), a_call_id, ID(b), b_call_id); sort_return(1); } crm_debug_5("%s (%d) == %s (%d) : continuing", ID(a), a_call_id, ID(b), b_call_id); /* now process pending ops */ CRM_CHECK(a_key != NULL && b_key != NULL, sort_return(0)); CRM_CHECK(decode_transition_magic( - a_key,&a_uuid,&a_id,&a_status, &a_rc), sort_return(0)); + a_key, &a_uuid, &a_id, &dummy, &a_status, &a_rc), + sort_return(0)); CRM_CHECK(decode_transition_magic( - b_key,&b_uuid,&b_id,&b_status, &b_rc), sort_return(0)); + b_key, &b_uuid, &b_id, &dummy, &b_status, &b_rc), + sort_return(0)); /* try and determin the relative age of the operation... * some pending operations (ie. a start) may have been supuerceeded * by a subsequent stop * * [a|b]_id == -1 means its a shutdown operation and _always_ comes last */ if(safe_str_neq(a_uuid, b_uuid) || a_id == b_id) { /* * some of the logic in here may be redundant... * * if the UUID from the TE doesnt match then one better * be a pending operation. * pending operations dont survive between elections and joins * because we query the LRM directly */ CRM_CHECK(a_call_id == -1 || b_call_id == -1, sort_return(0)); CRM_CHECK(a_call_id >= 0 || b_call_id >= 0, sort_return(0)); if(b_call_id == -1) { crm_debug_2("%s (%d) < %s (%d) : transition + call id", ID(a), a_call_id, ID(b), b_call_id); sort_return(-1); } if(a_call_id == -1) { crm_debug_2("%s (%d) > %s (%d) : transition + call id", ID(a), a_call_id, ID(b), b_call_id); sort_return(1); } } else if((a_id >= 0 && a_id < b_id) || b_id == -1) { crm_debug_3("%s (%d) < %s (%d) : transition", ID(a), a_id, ID(b), b_id); sort_return(-1); } else if((b_id >= 0 && a_id > b_id) || a_id == -1) { crm_debug_3("%s (%d) > %s (%d) : transition", ID(a), a_id, ID(b), b_id); sort_return(1); } /* we should never end up here */ crm_err("%s (%d:%d:%s) ?? %s (%d:%d:%s) : default", ID(a), a_call_id, a_id, a_uuid, ID(b), b_call_id, b_id, b_uuid); CRM_CHECK(FALSE, sort_return(0)); } diff --git a/lib/crm/pengine/utils.h b/lib/crm/pengine/utils.h index ee7775e822..acaea98c38 100644 --- a/lib/crm/pengine/utils.h +++ b/lib/crm/pengine/utils.h @@ -1,129 +1,128 @@ /* $Id: utils.h,v 1.4 2006/06/21 11:06:13 andrew Exp $ */ /* * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef PE_UTILS__H #define PE_UTILS__H #include #include /* Node helper functions */ extern node_t *pe_find_node_id(GListPtr node_list, const char *id); extern node_t *node_copy(node_t *this_node) ; /* Binary like operators for lists of nodes */ extern GListPtr node_list_dup(GListPtr list1, gboolean reset, gboolean filter); extern GListPtr node_list_and(GListPtr list1, GListPtr list2, gboolean filter); extern GListPtr node_list_xor(GListPtr list1, GListPtr list2, gboolean filter); extern GListPtr node_list_minus(GListPtr list1,GListPtr list2,gboolean filter); extern gboolean node_list_eq(GListPtr list1, GListPtr list2, gboolean filter); extern GListPtr node_list_or(GListPtr list1, GListPtr list2, gboolean filter); extern void pe_free_shallow(GListPtr alist); extern void pe_free_shallow_adv(GListPtr alist, gboolean with_data); /* For creating the transition graph */ extern crm_data_t *action2xml(action_t *action, gboolean as_input); /* Printing functions for debug */ extern void print_node( const char *pre_text, node_t *node, gboolean details); extern void print_resource( int log_level, const char *pre_text, resource_t *rsc, gboolean details); extern void log_action( unsigned int log_level, const char *pre_text, action_t *action, gboolean details); /* Sorting functions */ extern gint sort_rsc_priority(gconstpointer a, gconstpointer b); extern crm_data_t *find_rsc_op_entry(resource_t *rsc, const char *key); extern action_t *custom_action( resource_t *rsc, char *key, const char *task, node_t *on_node, gboolean optional, gboolean foo, pe_working_set_t *data_set); #define delete_key(rsc) generate_op_key(rsc->id, CRMD_ACTION_DELETE, 0) #define delete_action(rsc, node) custom_action( \ rsc, delete_key(rsc), CRMD_ACTION_DELETE, node, \ FALSE, TRUE, data_set); #define stopped_key(rsc) generate_op_key(rsc->id, CRMD_ACTION_STOPPED, 0) #define stopped_action(rsc, node, optional) custom_action( \ rsc, stopped_key(rsc), CRMD_ACTION_STOPPED, node, \ optional, TRUE, data_set); #define stop_key(rsc) generate_op_key(rsc->id, CRMD_ACTION_STOP, 0) #define stop_action(rsc, node, optional) custom_action( \ rsc, stop_key(rsc), CRMD_ACTION_STOP, node, \ optional, TRUE, data_set); #define start_key(rsc) generate_op_key(rsc->id, CRMD_ACTION_START, 0) #define start_action(rsc, node, optional) custom_action( \ rsc, start_key(rsc), CRMD_ACTION_START, node, \ optional, TRUE, data_set) #define started_key(rsc) generate_op_key(rsc->id, CRMD_ACTION_STARTED, 0) #define started_action(rsc, node, optional) custom_action( \ rsc, started_key(rsc), CRMD_ACTION_STARTED, node, \ optional, TRUE, data_set) #define promote_key(rsc) generate_op_key(rsc->id, CRMD_ACTION_PROMOTE, 0) #define promote_action(rsc, node, optional) custom_action( \ rsc, promote_key(rsc), CRMD_ACTION_PROMOTE, node, \ optional, TRUE, data_set) #define promoted_key(rsc) generate_op_key(rsc->id, CRMD_ACTION_PROMOTED, 0) #define promoted_action(rsc, node, optional) custom_action( \ rsc, promoted_key(rsc), CRMD_ACTION_PROMOTED, node, \ optional, TRUE, data_set) #define demote_key(rsc) generate_op_key(rsc->id, CRMD_ACTION_DEMOTE, 0) #define demote_action(rsc, node, optional) custom_action( \ rsc, demote_key(rsc), CRMD_ACTION_DEMOTE, node, \ optional, TRUE, data_set) #define demoted_key(rsc) generate_op_key(rsc->id, CRMD_ACTION_DEMOTED, 0) #define demoted_action(rsc, node, optional) custom_action( \ rsc, demoted_key(rsc), CRMD_ACTION_DEMOTED, node, \ optional, TRUE, data_set) extern GListPtr find_actions(GListPtr input, const char *key, node_t *on_node); extern GListPtr find_actions_exact( GListPtr input, const char *key, node_t *on_node); extern GListPtr find_recurring_actions(GListPtr input, node_t *not_on_node); extern void set_id(crm_data_t *xml_obj, const char *prefix, int child); extern void pe_free_action(action_t *action); extern void resource_location(resource_t *rsc, node_t *node, int score, const char *tag, pe_working_set_t *data_set); extern void order_actions(action_t *lh_action, action_t *rh_action, enum pe_ordering order); -extern const char *get_interval(crm_data_t *xml_op); extern gint sort_op_by_callid(gconstpointer a, gconstpointer b); #endif diff --git a/lib/crm/transition/actions.c b/lib/crm/transition/actions.c deleted file mode 100644 index bd17c6d25a..0000000000 --- a/lib/crm/transition/actions.c +++ /dev/null @@ -1,452 +0,0 @@ -/* $Id: actions.c,v 1.4 2006/07/06 09:30:27 andrew Exp $ */ -/* - * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -gboolean initiate_action(action_t *action); -void cib_action_updated( - const HA_Message *msg, int call_id, int rc, - crm_data_t *output, void *user_data); - - - -static void -cib_fencing_updated(const HA_Message *msg, int call_id, int rc, - crm_data_t *output, void *user_data) -{ - if(rc < cib_ok) { - crm_err("CIB update failed: %s", cib_error2string(rc)); - crm_log_xml_warn(msg, "[Failed Update]"); - } - check_for_completion(); -} - -void -send_stonith_update(stonith_ops_t * op) -{ - enum cib_errors rc = cib_ok; - const char *target = op->node_name; - const char *uuid = op->node_uuid; - - /* zero out the node-status & remove all LRM status info */ - crm_data_t *update = NULL; - crm_data_t *node_state = create_xml_node(NULL, XML_CIB_TAG_STATE); - - CRM_CHECK(op->node_name != NULL); - CRM_CHECK(op->node_uuid != NULL); - - crm_xml_add(node_state, XML_ATTR_UUID, uuid); - crm_xml_add(node_state, XML_ATTR_UNAME, target); - crm_xml_add(node_state, XML_CIB_ATTR_HASTATE, DEADSTATUS); - crm_xml_add(node_state, XML_CIB_ATTR_INCCM, XML_BOOLEAN_NO); - crm_xml_add(node_state, XML_CIB_ATTR_CRMDSTATE, OFFLINESTATUS); - crm_xml_add(node_state, XML_CIB_ATTR_JOINSTATE, CRMD_JOINSTATE_DOWN); - crm_xml_add(node_state, XML_CIB_ATTR_EXPSTATE, CRMD_JOINSTATE_DOWN); - crm_xml_add(node_state, XML_CIB_ATTR_REPLACE, XML_CIB_TAG_LRM); - create_xml_node(node_state, XML_CIB_TAG_LRM); - - update = create_cib_fragment(node_state, XML_CIB_TAG_STATUS); - - rc = te_cib_conn->cmds->update( - te_cib_conn, XML_CIB_TAG_STATUS, update, NULL, - cib_quorum_override|cib_scope_local); - - if(rc < cib_ok) { - const char *fail_text = "Couldnt update CIB after stonith"; - crm_err("CIB update failed: %s", cib_error2string(rc)); - send_complete(fail_text, update, te_failed, i_cancel); - - } else { - /* delay processing the trigger until the update completes */ - add_cib_op_callback(rc, FALSE, NULL, cib_fencing_updated); - } - - free_xml(node_state); - free_xml(update); - return; -} - -static gboolean -te_fence_node(action_t *action) -{ - char *key = NULL; - const char *id = NULL; - const char *uuid = NULL; - const char *target = NULL; - stonith_ops_t * st_op = NULL; - - id = ID(action->xml); - target = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); - uuid = crm_element_value(action->xml, XML_LRM_ATTR_TARGET_UUID); - - CRM_CHECK(id != NULL); - CRM_CHECK(uuid != NULL); - CRM_CHECK(target != NULL); - if(crm_assert_failed) { - /* error */ - te_log_action(LOG_ERR, "Corrupted command (id=%s): no node", - crm_str(id)); - return FALSE; - } - - te_log_action(LOG_INFO, - "Executing fencing operation (%s) on %s (timeout=%d)", - id, target, transition_idle_timeout / 2); -#ifdef TESTING - action->complete = TRUE; - process_trigger(action->id); - return TRUE; -#endif - - crm_malloc0(st_op, sizeof(stonith_ops_t)); - st_op->optype = RESET; - st_op->timeout = transition_idle_timeout / 2; - st_op->node_name = crm_strdup(target); - st_op->node_uuid = crm_strdup(uuid); - - key = generate_transition_key(transition_counter, te_uuid); - st_op->private_data = crm_concat(id, key, ';'); - crm_free(key); - - if(stonithd_input_IPC_channel() == NULL) { - crm_err("Cannot fence %s: stonith not available", target); - return FALSE; - - } else if (ST_OK != stonithd_node_fence( st_op )) { - crm_err("Cannot fence %s: stonithd_node_fence() call failed ", - target); - return FALSE; - } - stop_te_timer(transition_timer); - start_te_timer(transition_timer); - return TRUE; -} - -static gboolean -te_crm_command(action_t *action) -{ - char *value = NULL; - char *counter = NULL; - HA_Message *cmd = NULL; - - const char *id = NULL; - const char *task = NULL; - const char *on_node = NULL; - - gboolean ret = TRUE; - - id = ID(action->xml); - task = crm_element_value(action->xml, XML_LRM_ATTR_TASK); - on_node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); - - CRM_CHECK(on_node != NULL && strlen(on_node) != 0); - if(crm_assert_failed) { - /* error */ - te_log_action(LOG_ERR, "Corrupted command (id=%s) %s: no node", - crm_str(id), crm_str(task)); - return FALSE; - } - - te_log_action(LOG_INFO, "Executing crm-event (%s): %s on %s", - crm_str(id), crm_str(task), on_node); - -#ifdef TESTING - action->complete = TRUE; - process_trigger(action->id); - return TRUE; -#endif - - cmd = create_request(task, NULL, on_node, CRM_SYSTEM_CRMD, - CRM_SYSTEM_TENGINE, NULL); - - counter = generate_transition_key(transition_counter, te_uuid); - crm_xml_add(cmd, XML_ATTR_TRANSITION_KEY, counter); - ret = send_ipc_message(crm_ch, cmd); - crm_free(counter); - crm_msg_del(cmd); - - value = g_hash_table_lookup(action->params, XML_ATTR_TE_NOWAIT); - if(ret == FALSE) { - crm_err("Action %d failed: send", action->id); - return FALSE; - - } else if(crm_is_true(value)) { - crm_info("Skipping wait for %d", action->id); - action->complete = TRUE; - process_trigger(action->id); - - } else if(ret && action->timeout > 0) { - crm_debug_3("Setting timer for action %d",action->id); - action->timer->reason = timeout_action_warn; - start_te_timer(action->timer); - } - return TRUE; -} - -static gboolean -te_rsc_command(action_t *action) -{ - /* never overwrite stop actions in the CIB with - * anything other than completed results - * - * Writing pending stops makes it look like the - * resource is running again - */ - const char *task = NULL; - const char *on_node = NULL; - action->invoked = FALSE; - - on_node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); - CRM_CHECK(on_node != NULL && strlen(on_node) != 0); - if(crm_assert_failed) { - /* error */ - te_log_action(LOG_ERR, "Corrupted command(id=%s) %s: no node", - ID(action->xml), crm_str(task)); - return FALSE; - } - -#ifdef TESTING - cib_action_update(action, LRM_OP_DONE); - return TRUE; -#endif - - cib_action_updated(NULL, 0, cib_ok, NULL, action); - return TRUE; -} - -gboolean -cib_action_update(action_t *action, int status) -{ - char *code = NULL; - crm_data_t *fragment = NULL; - crm_data_t *state = NULL; - crm_data_t *rsc = NULL; - crm_data_t *xml_op = NULL; - char *op_id = NULL; - - enum cib_errors rc = cib_ok; - const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK); - const char *rsc_id = crm_element_value(action->xml, XML_LRM_ATTR_RSCID); - const char *target = crm_element_value(action->xml, XML_LRM_ATTR_TARGET); - const char *task_uuid = crm_element_value( - action->xml, XML_LRM_ATTR_TASK_KEY); - - const char *target_uuid = - crm_element_value(action->xml, XML_LRM_ATTR_TARGET_UUID); - - int call_options = cib_quorum_override|cib_scope_local; - - if(status == LRM_OP_TIMEOUT) { - if(crm_element_value(action->xml, XML_LRM_ATTR_RSCID) != NULL) { - crm_warn("%s: %s %s on %s timed out", - crm_element_name(action->xml), task_uuid, rsc_id, target); - } else { - crm_warn("%s: %s on %s timed out", - crm_element_name(action->xml), task_uuid, target); - } - } - code = crm_itoa(status); - -/* - update the CIB - - - - - -*/ - - fragment = NULL; - state = create_xml_node(NULL, XML_CIB_TAG_STATE); - - crm_xml_add(state, XML_ATTR_UUID, target_uuid); - crm_xml_add(state, XML_ATTR_UNAME, target); - - rsc = create_xml_node(state, XML_CIB_TAG_LRM); - rsc = create_xml_node(rsc, XML_LRM_TAG_RESOURCES); - rsc = create_xml_node(rsc, XML_LRM_TAG_RESOURCE); - - xml_op = create_xml_node(rsc,XML_LRM_TAG_RSC_OP); - - crm_xml_add(rsc, XML_ATTR_ID, rsc_id); - crm_xml_add(xml_op, XML_ATTR_ID, task); - - op_id = generate_op_key(rsc_id, task, action->interval); - crm_xml_add(xml_op, XML_ATTR_ID, op_id); - crm_free(op_id); - - crm_xml_add(xml_op, XML_LRM_ATTR_TASK, task); - crm_xml_add(rsc, XML_LRM_ATTR_RSCSTATE, - get_rsc_state(task, status)); - - crm_xml_add(xml_op, XML_LRM_ATTR_OPSTATUS, code); - crm_xml_add(xml_op, XML_LRM_ATTR_CALLID, "-1"); - crm_xml_add(xml_op, XML_LRM_ATTR_RC, code); - crm_xml_add(xml_op, XML_ATTR_ORIGIN, __FUNCTION__); - - crm_free(code); - - code = generate_transition_key(transition_counter, te_uuid); - crm_xml_add(xml_op, XML_ATTR_TRANSITION_KEY, code); - crm_free(code); - - code = generate_transition_magic( - crm_element_value(xml_op, XML_ATTR_TRANSITION_KEY), status, status); - crm_xml_add(xml_op, XML_ATTR_TRANSITION_MAGIC, code); - crm_free(code); - - set_node_tstamp(xml_op); - - fragment = create_cib_fragment(state, XML_CIB_TAG_STATUS); - - crm_debug_3("Updating CIB with \"%s\" (%s): %s %s on %s", - status<0?"new action":XML_ATTR_TIMEOUT, - crm_element_name(action->xml), crm_str(task), rsc_id, target); - -#ifndef TESTING - rc = te_cib_conn->cmds->update( - te_cib_conn, XML_CIB_TAG_STATUS, fragment, NULL, call_options); - - crm_debug("Updating CIB with %s action %d: %s %s on %s (call_id=%d)", - op_status2text(status), action->id, task_uuid, rsc_id, target, rc); - - if(status == LRM_OP_PENDING) { - crm_debug_2("Waiting for callback id: %d", rc); - add_cib_op_callback(rc, FALSE, action, cib_action_updated); - } -#else - te_log_action(LOG_INFO, "Initiating action %d: %s %s on %s", - action->id, task_uuid, rsc_id, target); - call_options = 0; - { - HA_Message *cmd = ha_msg_new(11); - ha_msg_add(cmd, F_TYPE, T_CRM); - ha_msg_add(cmd, F_CRM_VERSION, CRM_FEATURE_SET); - ha_msg_add(cmd, F_CRM_MSG_TYPE, XML_ATTR_REQUEST); - ha_msg_add(cmd, F_CRM_TASK, CRM_OP_EVENTCC); - ha_msg_add(cmd, F_CRM_SYS_TO, CRM_SYSTEM_TENGINE); - ha_msg_add(cmd, F_CRM_SYS_FROM, CRM_SYSTEM_TENGINE); - ha_msg_addstruct(cmd, crm_element_name(state), state); - send_ipc_message(crm_ch, cmd); - crm_msg_del(cmd); - } -#endif - free_xml(fragment); - free_xml(state); - - action->sent_update = TRUE; - - if(rc < cib_ok) { - return FALSE; - } - - return TRUE; -} - - -void -cib_action_updated( - const HA_Message *msg, int call_id, int rc, crm_data_t *output, void *user_data) -{ - HA_Message *cmd = NULL; - crm_data_t *rsc_op = NULL; - action_t *action = user_data; - char *counter = crm_itoa(transition_counter); - - const char *task = NULL; - const char *value = NULL; - const char *rsc_id = NULL; - const char *on_node = NULL; - const char *task_uuid = NULL; - - CRM_CHECK(action != NULL); if(crm_assert_failed) { return; } - CRM_CHECK(action->xml != NULL); if(crm_assert_failed) { return; } - - rsc_op = action->xml; - task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK); - task_uuid = crm_element_value(action->xml, XML_LRM_ATTR_TASK_KEY); - rsc_id = crm_element_value(rsc_op, XML_LRM_ATTR_RSCID); - on_node = crm_element_value(rsc_op, XML_LRM_ATTR_TARGET); - counter = generate_transition_key(transition_counter, te_uuid); - crm_xml_add(rsc_op, XML_ATTR_TRANSITION_KEY, counter); - crm_free(counter); - - if(rc < cib_ok) { - crm_err("Update for action %d: %s %s on %s FAILED", - action->id, task_uuid, rsc_id, on_node); - send_complete(cib_error2string(rc), output, te_failed, i_cancel); - return; - } - - if(te_fsa_state != s_in_transition) { - int pending_updates = num_cib_op_callbacks(); - if(pending_updates == 0) { - send_complete("CIB update queue empty", output, - te_done, i_cib_complete); - } else { - crm_debug("Still waiting on %d callbacks", - pending_updates); - } - crm_debug("Not executing action: Not in a transition: %d", - te_fsa_state); - return; - } - - crm_info("Initiating action %d: %s on %s", - action->id, task_uuid, on_node); - - if(rsc_op != NULL) { - crm_log_xml_debug_2(rsc_op, "Performing"); - } - cmd = create_request(CRM_OP_INVOKE_LRM, rsc_op, on_node, - CRM_SYSTEM_LRMD, CRM_SYSTEM_TENGINE, NULL); - - -#ifndef TESTING - send_ipc_message(crm_ch, cmd); -#else - crm_log_message(LOG_INFO, cmd); -#endif - crm_msg_del(cmd); - - action->invoked = TRUE; - value = g_hash_table_lookup(action->params, XML_ATTR_TE_NOWAIT); - if(crm_is_true(value)) { - crm_debug("Skipping wait for %d", action->id); - action->complete = TRUE; - process_trigger(action->id); - - } else if(action->timeout > 0) { - crm_debug_3("Setting timer for action %s", task_uuid); - action->timer->reason = timeout_action_warn; - start_te_timer(action->timer); - } -} -