Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/crm/crmd/callbacks.c b/crm/crmd/callbacks.c
index f344b219c5..116b0ed989 100644
--- a/crm/crmd/callbacks.c
+++ b/crm/crmd/callbacks.c
@@ -1,656 +1,664 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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 <portability.h>
#include <sys/param.h>
#include <crm/crm.h>
#include <string.h>
#include <crmd_fsa.h>
#include <heartbeat.h>
#include <hb_api.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/msg.h>
#include <crm/cib.h>
#include <crmd.h>
#include <crmd_messages.h>
#include <crmd_callbacks.h>
#include <crm/dmalloc_wrapper.h>
GHashTable *crmd_peer_state = NULL;
crm_data_t *find_xml_in_hamessage(const HA_Message * msg);
void crmd_ha_connection_destroy(gpointer user_data);
/* From join_dc... */
extern gboolean check_join_state(
enum crmd_fsa_state cur_state, const char *source);
/* #define MAX_EMPTY_CALLBACKS 20 */
/* int empty_callbacks = 0; */
#define trigger_fsa(source) crm_debug_3("Triggering FSA: %s", __FUNCTION__); \
G_main_set_trigger(source);
gboolean
crmd_ha_msg_dispatch(ll_cluster_t *cluster_conn, gpointer user_data)
{
IPC_Channel *channel = NULL;
gboolean stay_connected = TRUE;
crm_debug_3("Invoked");
if(cluster_conn != NULL) {
channel = cluster_conn->llc_ops->ipcchan(cluster_conn);
}
CRM_CHECK(cluster_conn != NULL, ;);
CRM_CHECK(channel != NULL, ;);
if(channel != NULL && IPC_ISRCONN(channel)) {
if(cluster_conn->llc_ops->msgready(cluster_conn) == 0) {
crm_debug_2("no message ready yet");
}
/* invoke the callbacks but dont block */
cluster_conn->llc_ops->rcvmsg(cluster_conn, 0);
}
if (channel == NULL || channel->ch_status != IPC_CONNECT) {
if(is_set(fsa_input_register, R_HA_DISCONNECTED) == FALSE) {
crm_crit("Lost connection to heartbeat service.");
} else {
crm_info("Lost connection to heartbeat service.");
}
trigger_fsa(fsa_source);
stay_connected = FALSE;
}
return stay_connected;
}
void
crmd_ha_connection_destroy(gpointer user_data)
{
crm_debug_3("Invoked");
if(is_set(fsa_input_register, R_HA_DISCONNECTED)) {
/* we signed out, so this is expected */
crm_info("Heartbeat disconnection complete");
return;
}
crm_crit("Lost connection to heartbeat service!");
register_fsa_input(C_HA_DISCONNECT, I_ERROR, NULL);
trigger_fsa(fsa_source);
}
void
crmd_ha_msg_callback(HA_Message * msg, void* private_data)
{
int level = LOG_DEBUG;
ha_msg_input_t *new_input = NULL;
oc_node_t *from_node = NULL;
const char *from = ha_msg_value(msg, F_ORIG);
const char *seq = ha_msg_value(msg, F_SEQ);
const char *op = ha_msg_value(msg, F_CRM_TASK);
const char *sys_to = ha_msg_value(msg, F_CRM_SYS_TO);
const char *sys_from = ha_msg_value(msg, F_CRM_SYS_FROM);
CRM_DEV_ASSERT(from != NULL);
crm_debug_2("HA[inbound]: %s from %s", op, from);
if(fsa_membership_copy == NULL) {
crm_debug("Ignoring HA messages until we are"
" connected to the CCM (%s op from %s)", op, from);
crm_log_message_adv(
LOG_MSG, "HA[inbound]: Ignore (No CCM)", msg);
return;
}
from_node = g_hash_table_lookup(fsa_membership_copy->members, from);
if(from_node == NULL) {
if(safe_str_eq(op, CRM_OP_VOTE)) {
level = LOG_WARNING;
} else if(AM_I_DC && safe_str_eq(op, CRM_OP_JOIN_ANNOUNCE)) {
level = LOG_WARNING;
} else if(safe_str_eq(sys_from, CRM_SYSTEM_DC)) {
level = LOG_WARNING;
}
crm_log_maybe(level,
"Ignoring HA message (op=%s) from %s: not in our"
" membership list (size=%d)", op, from,
g_hash_table_size(fsa_membership_copy->members));
crm_log_message_adv(LOG_MSG, "HA[inbound]: CCM Discard", msg);
} else if(safe_str_eq(sys_to, CRM_SYSTEM_DC) && AM_I_DC == FALSE) {
crm_debug_2("Ignoring message for the DC [F_SEQ=%s]", seq);
return;
} else if(safe_str_eq(sys_from, CRM_SYSTEM_DC)) {
if(AM_I_DC && safe_str_neq(from, fsa_our_uname)) {
crm_err("Another DC detected: %s (op=%s)", from, op);
/* make sure the election happens NOW */
level = LOG_WARNING;
if(fsa_state != S_ELECTION) {
new_input = new_ha_msg_input(msg);
register_fsa_error_adv(
C_FSA_INTERNAL, I_ELECTION, NULL,
new_input, __FUNCTION__);
}
#if 0
/* still thinking about this one...
* could create a timing issue if we dont notice the
* election before a new DC is elected.
*/
} else if(fsa_our_dc != NULL && safe_str_neq(from,fsa_our_dc)){
crm_warn("Ignoring message from wrong DC: %s vs. %s ",
from, fsa_our_dc);
return;
#endif
} else {
crm_debug_2("Processing DC message from %s [F_SEQ=%s]",
from, seq);
}
}
if(new_input == NULL) {
crm_log_message_adv(LOG_MSG, "HA[inbound]", msg);
new_input = new_ha_msg_input(msg);
route_message(C_HA_MESSAGE, new_input);
}
delete_ha_msg_input(new_input);
trigger_fsa(fsa_source);
return;
}
/*
* Apparently returning TRUE means "stay connected, keep doing stuff".
* Returning FALSE means "we're all done, close the connection"
*/
gboolean
crmd_ipc_msg_callback(IPC_Channel *client, gpointer user_data)
{
int lpc = 0;
HA_Message *msg = NULL;
ha_msg_input_t *new_input = NULL;
crmd_client_t *curr_client = (crmd_client_t*)user_data;
gboolean stay_connected = TRUE;
crm_debug_2("Invoked: %s",
curr_client->table_key);
while(IPC_ISRCONN(client)) {
if(client->ops->is_message_pending(client) == 0) {
break;
}
msg = msgfromIPC_noauth(client);
if (msg == NULL) {
crm_info("%s: no message this time",
curr_client->table_key);
continue;
}
lpc++;
new_input = new_ha_msg_input(msg);
crm_msg_del(msg);
crm_debug_2("Processing msg from %s", curr_client->table_key);
crm_log_message_adv(LOG_DEBUG_2, "CRMd[inbound]", new_input->msg);
if(crmd_authorize_message(new_input, curr_client)) {
route_message(C_IPC_MESSAGE, new_input);
}
delete_ha_msg_input(new_input);
msg = NULL;
new_input = NULL;
if(client->ch_status != IPC_CONNECT) {
break;
}
}
crm_debug_2("Processed %d messages", lpc);
if (client->ch_status != IPC_CONNECT) {
stay_connected = FALSE;
process_client_disconnect(curr_client);
}
trigger_fsa(fsa_source);
return stay_connected;
}
extern GCHSource *lrm_source;
gboolean
lrm_dispatch(IPC_Channel *src_not_used, gpointer user_data)
{
/* ?? src == lrm_channel ?? */
ll_lrm_t *lrm = (ll_lrm_t*)user_data;
IPC_Channel *lrm_channel = lrm->lrm_ops->ipcchan(lrm);
crm_debug_3("Invoked");
lrm->lrm_ops->rcvmsg(lrm, FALSE);
if(lrm_channel->ch_status != IPC_CONNECT) {
if(is_set(fsa_input_register, R_LRM_CONNECTED)) {
crm_crit("LRM Connection failed");
register_fsa_input(C_FSA_INTERNAL, I_ERROR, NULL);
clear_bit_inplace(fsa_input_register, R_LRM_CONNECTED);
} else {
crm_info("LRM Connection disconnected");
}
lrm_source = NULL;
return FALSE;
}
return TRUE;
}
extern gboolean process_lrm_event(lrm_op_t *op);
void
lrm_op_callback(lrm_op_t* op)
{
CRM_CHECK(op != NULL, return);
process_lrm_event(op);
}
void
crmd_ha_status_callback(
const char *node, const char * status, void* private_data)
{
crm_data_t *update = NULL;
crm_notice("Status update: Node %s now has status [%s]",node,status);
if(safe_str_eq(status, DEADSTATUS)) {
/* this node is taost */
update = create_node_state(
node, status, XML_BOOLEAN_NO, OFFLINESTATUS,
CRMD_STATE_INACTIVE, NULL, TRUE, __FUNCTION__);
- crm_xml_add(update, XML_CIB_ATTR_REPLACE, XML_TAG_TRANSIENT_NODEATTRS);
-
+ if(update) {
+ crm_xml_add(update, XML_CIB_ATTR_REPLACE,
+ XML_TAG_TRANSIENT_NODEATTRS);
+ }
+
} else if(safe_str_eq(status, ACTIVESTATUS)) {
update = create_node_state(
- node, status, NULL, NULL, NULL, NULL, FALSE, __FUNCTION__);
+ node, status, NULL, NULL, NULL, NULL,
+ FALSE, __FUNCTION__);
}
if(update != NULL) {
/* this change should not be broadcast */
fsa_cib_anon_update(
XML_CIB_TAG_STATUS, update,
cib_inhibit_bcast|cib_scope_local|cib_quorum_override);
trigger_fsa(fsa_source);
free_xml(update);
+
+ } else {
+ crm_info("Ping node %s is %s", node, status);
}
+
}
void
crmd_client_status_callback(const char * node, const char * client,
- const char * status, void * private)
+ const char * status, void * private)
{
const char *join = NULL;
crm_data_t *update = NULL;
gboolean clear_shutdown = FALSE;
crm_debug_3("Invoked");
if(safe_str_neq(client, CRM_SYSTEM_CRMD)) {
return;
}
if(safe_str_eq(status, JOINSTATUS)){
status = ONLINESTATUS;
clear_shutdown = TRUE;
} else if(safe_str_eq(status, LEAVESTATUS)){
status = OFFLINESTATUS;
join = CRMD_STATE_INACTIVE;
/* clear_shutdown = TRUE; */
}
set_bit_inplace(fsa_input_register, R_PEER_DATA);
g_hash_table_replace(
crmd_peer_state, crm_strdup(node), crm_strdup(status));
if(is_set(fsa_input_register, R_CIB_CONNECTED) == FALSE) {
return;
} else if(fsa_state == S_STOPPING) {
return;
}
crm_notice("Status update: Client %s/%s now has status [%s]",
node, client, status);
if(safe_str_eq(status, ONLINESTATUS)) {
/* remove the cached value in case it changed */
crm_info("Uncaching UUID for %s", node);
unget_uuid(node);
}
if(safe_str_eq(node, fsa_our_dc) && safe_str_eq(status, OFFLINESTATUS)){
/* did our DC leave us */
crm_info("Got client status callback - our DC is dead");
register_fsa_input(C_CRMD_STATUS_CALLBACK, I_ELECTION, NULL);
} else {
crm_debug_3("Got client status callback");
update = create_node_state(
node, NULL, NULL, status, join,
NULL, clear_shutdown, __FUNCTION__);
if(clear_shutdown) {
crm_xml_add(update, XML_CIB_ATTR_REPLACE,
XML_TAG_TRANSIENT_NODEATTRS);
}
/* it is safe to keep these updates on the local node
* each node updates their own CIB
*/
fsa_cib_anon_update(
XML_CIB_TAG_STATUS, update,
cib_inhibit_bcast|cib_scope_local|cib_quorum_override);
free_xml(update);
if(AM_I_DC && safe_str_eq(status, OFFLINESTATUS)) {
g_hash_table_remove(confirmed_nodes, node);
g_hash_table_remove(finalized_nodes, node);
g_hash_table_remove(integrated_nodes, node);
g_hash_table_remove(welcomed_nodes, node);
check_join_state(fsa_state, __FUNCTION__);
}
}
trigger_fsa(fsa_source);
}
static void
crmd_ipc_connection_destroy(gpointer user_data)
{
crmd_client_t *client = user_data;
if(client == NULL) {
crm_debug_4("No client to delete");
return;
}
if(client->client_source != NULL) {
crm_debug_4("Deleting %s (%p) from mainloop",
client->uuid, client->client_source);
G_main_del_IPC_Channel(client->client_source);
client->client_source = NULL;
}
crm_debug_3("Freeing %s client", client->uuid);
crm_free(client->table_key);
crm_free(client->sub_sys);
crm_free(client->uuid);
crm_free(client);
return;
}
gboolean
crmd_client_connect(IPC_Channel *client_channel, gpointer user_data)
{
crm_debug_3("Invoked");
if (client_channel == NULL) {
crm_err("Channel was NULL");
} else if (client_channel->ch_status == IPC_DISCONNECT) {
crm_err("Channel was disconnected");
} else {
crmd_client_t *blank_client = NULL;
crm_debug_3("Channel connected");
crm_malloc0(blank_client, sizeof(crmd_client_t));
if (blank_client == NULL) {
return FALSE;
}
client_channel->ops->set_recv_qlen(client_channel, 100);
client_channel->ops->set_send_qlen(client_channel, 100);
blank_client->client_channel = client_channel;
blank_client->sub_sys = NULL;
blank_client->uuid = NULL;
blank_client->table_key = NULL;
blank_client->client_source =
G_main_add_IPC_Channel(
G_PRIORITY_LOW, client_channel,
FALSE, crmd_ipc_msg_callback,
blank_client, crmd_ipc_connection_destroy);
}
return TRUE;
}
gboolean ccm_dispatch(int fd, gpointer user_data)
{
int rc = 0;
oc_ev_t *ccm_token = (oc_ev_t*)user_data;
gboolean was_error = FALSE;
crm_debug_3("Invoked");
rc = oc_ev_handle_event(ccm_token);
if(rc != 0) {
if(is_set(fsa_input_register, R_CCM_DISCONNECTED) == FALSE) {
/* we signed out, so this is expected */
register_fsa_input(C_CCM_CALLBACK, I_ERROR, NULL);
crm_err("CCM connection appears to have failed: rc=%d.",
rc);
}
was_error = TRUE;
}
trigger_fsa(fsa_source);
return !was_error;
}
static gboolean fsa_have_quorum = FALSE;
void
crmd_ccm_msg_callback(
oc_ed_t event, void *cookie, size_t size, const void *data)
{
int instance = -1;
gboolean update_cache = FALSE;
struct crmd_ccm_data_s *event_data = NULL;
const oc_ev_membership_t *membership = data;
gboolean update_quorum = FALSE;
gboolean trigger_transition = FALSE;
crm_debug_3("Invoked");
if(data != NULL) {
instance = membership->m_instance;
}
crm_info("Quorum %s after event=%s (id=%d)",
ccm_have_quorum(event)?"(re)attained":"lost",
ccm_event_name(event), instance);
switch(event) {
case OC_EV_MS_NEW_MEMBERSHIP:
case OC_EV_MS_INVALID:/* fall through */
update_cache = TRUE;
update_quorum = TRUE;
break;
case OC_EV_MS_NOT_PRIMARY:
#if UNTESTED
if(AM_I_DC == FALSE) {
break;
}
/* tell the TE to pretend it had completed and stop */
/* side effect: we'll end up in S_IDLE */
register_fsa_action(A_TE_HALT, TRUE);
#endif
break;
case OC_EV_MS_PRIMARY_RESTORED:
fsa_membership_copy->id = instance;
if(AM_I_DC && need_transition(fsa_state)) {
trigger_transition = TRUE;
}
break;
case OC_EV_MS_EVICTED:
update_quorum = TRUE;
register_fsa_input(C_FSA_INTERNAL, I_STOP, NULL);
crm_err("Shutting down after CCM event: %s",
ccm_event_name(event));
break;
default:
crm_err("Unknown CCM event: %d", event);
}
if(update_quorum && ccm_have_quorum(event) == FALSE) {
/* did we just loose quorum? */
if(fsa_have_quorum && need_transition(fsa_state)) {
crm_info("Quorum lost: triggering transition (%s)",
ccm_event_name(event));
trigger_transition = TRUE;
}
fsa_have_quorum = FALSE;
} else if(update_quorum) {
crm_debug_2("Updating quorum after event %s",
ccm_event_name(event));
fsa_have_quorum = TRUE;
}
if(trigger_transition) {
crm_debug_2("Scheduling transition after event %s",
ccm_event_name(event));
/* make sure that when we query the CIB that it has
* the changes that triggered the transition
*/
switch(event) {
case OC_EV_MS_NEW_MEMBERSHIP:
case OC_EV_MS_INVALID:
case OC_EV_MS_PRIMARY_RESTORED:
fsa_membership_copy->id = instance;
break;
default:
break;
}
if(update_cache == FALSE) {
/* a stand-alone transition */
register_fsa_action(A_TE_CANCEL);
}
}
if(update_cache) {
crm_debug_2("Updating cache after event %s",
ccm_event_name(event));
crm_malloc0(event_data, sizeof(struct crmd_ccm_data_s));
if(event_data == NULL) { return; }
event_data->event = event;
if(data != NULL) {
event_data->oc = copy_ccm_oc_data(data);
}
register_fsa_input_adv(
C_CCM_CALLBACK, I_CCM_EVENT, event_data,
trigger_transition?A_TE_CANCEL:A_NOTHING,
FALSE, __FUNCTION__);
if (event_data->oc) {
crm_free(event_data->oc);
event_data->oc = NULL;
}
crm_free(event_data);
}
oc_ev_callback_done(cookie);
return;
}
void
crmd_cib_connection_destroy(gpointer user_data)
{
crm_debug_3("Invoked");
trigger_fsa(fsa_source);
if(is_set(fsa_input_register, R_CIB_CONNECTED) == FALSE) {
crm_info("Connection to the CIB terminated...");
return;
}
/* eventually this will trigger a reconnect, not a shutdown */
crm_err("Connection to the CIB terminated...");
register_fsa_input(C_FSA_INTERNAL, I_ERROR, NULL);
clear_bit_inplace(fsa_input_register, R_CIB_CONNECTED);
return;
}
longclock_t fsa_start = 0;
longclock_t fsa_stop = 0;
longclock_t fsa_diff = 0;
gboolean
crm_fsa_trigger(gpointer user_data)
{
unsigned int fsa_diff_ms = 0;
if(fsa_diff_max_ms > 0) {
fsa_start = time_longclock();
}
crm_debug_2("Invoked (queue len: %d)", g_list_length(fsa_message_queue));
s_crmd_fsa(C_FSA_INTERNAL);
crm_debug_2("Exited (queue len: %d)", g_list_length(fsa_message_queue));
if(fsa_diff_max_ms > 0) {
fsa_stop = time_longclock();
fsa_diff = sub_longclock(fsa_stop, fsa_start);
fsa_diff_ms = longclockto_ms(fsa_diff);
if(fsa_diff_ms > fsa_diff_max_ms) {
crm_err("FSA took %dms to complete", fsa_diff_ms);
} else if(fsa_diff_ms > fsa_diff_warn_ms) {
crm_warn("FSA took %dms to complete", fsa_diff_ms);
}
}
return TRUE;
}
diff --git a/crm/crmd/utils.c b/crm/crmd/utils.c
index 13d6290261..46c3c3496b 100644
--- a/crm/crmd/utils.c
+++ b/crm/crmd/utils.c
@@ -1,1389 +1,1395 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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 <portability.h>
#include <sys/param.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crmd_fsa.h>
#include <clplumbing/Gmain_timeout.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <heartbeat.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/msg.h>
#include <crmd_messages.h>
#include <crmd_utils.h>
#include <crm/dmalloc_wrapper.h>
void copy_ccm_node(oc_node_t a_node, oc_node_t *a_node_copy);
/* A_DC_TIMER_STOP, A_DC_TIMER_START,
* A_FINALIZE_TIMER_STOP, A_FINALIZE_TIMER_START
* A_INTEGRATE_TIMER_STOP, A_INTEGRATE_TIMER_START
*/
enum crmd_fsa_input
do_timer_control(long long action,
enum crmd_fsa_cause cause,
enum crmd_fsa_state cur_state,
enum crmd_fsa_input current_input,
fsa_data_t *msg_data)
{
gboolean timer_op_ok = TRUE;
enum crmd_fsa_input result = I_NULL;
if(action & A_DC_TIMER_STOP) {
timer_op_ok = crm_timer_stop(election_trigger);
} else if(action & A_FINALIZE_TIMER_STOP) {
timer_op_ok = crm_timer_stop(finalization_timer);
} else if(action & A_INTEGRATE_TIMER_STOP) {
timer_op_ok = crm_timer_stop(integration_timer);
/* } else if(action & A_ELECTION_TIMEOUT_STOP) { */
/* timer_op_ok = crm_timer_stop(election_timeout); */
}
/* dont start a timer that wasnt already running */
if(action & A_DC_TIMER_START && timer_op_ok) {
crm_timer_start(election_trigger);
if(AM_I_DC) {
/* there can be only one */
result = I_ELECTION;
}
} else if(action & A_FINALIZE_TIMER_START) {
crm_timer_start(finalization_timer);
} else if(action & A_INTEGRATE_TIMER_START) {
crm_timer_start(integration_timer);
/* } else if(action & A_ELECTION_TIMEOUT_START) { */
/* crm_timer_start(election_timeout); */
}
return I_NULL;
}
static const char *
get_timer_desc(fsa_timer_t *timer)
{
if(timer == election_trigger) {
return "Election Trigger";
} else if(timer == election_timeout) {
return "Election Timeout";
} else if(timer == shutdown_escalation_timer) {
return "Shutdown Escalation";
} else if(timer == integration_timer) {
return "Integration Timer";
} else if(timer == finalization_timer) {
return "Finalization Timer";
} else if(timer == wait_timer) {
return "Wait Timer";
} else if(timer == recheck_timer) {
return "PEngine Recheck Timer";
}
return "Unknown Timer";
}
gboolean
crm_timer_popped(gpointer data)
{
fsa_timer_t *timer = (fsa_timer_t *)data;
if(timer == wait_timer
|| timer == recheck_timer
|| timer == finalization_timer
|| timer == election_trigger) {
crm_info("%s (%s) just popped!",
get_timer_desc(timer),
fsa_input2string(timer->fsa_input));
} else {
crm_err("%s (%s) just popped!",
get_timer_desc(timer),
fsa_input2string(timer->fsa_input));
}
if(timer->repeat == FALSE) {
crm_timer_stop(timer); /* make it _not_ go off again */
}
if(timer->fsa_input == I_INTEGRATED) {
register_fsa_input_before(
C_TIMER_POPPED, timer->fsa_input, NULL);
} else if(timer->fsa_input == I_PE_CALC
&& fsa_state != S_IDLE) {
crm_debug("Discarding %s event in state: %s",
fsa_input2string(timer->fsa_input),
fsa_state2string(fsa_state));
} else if(timer->fsa_input == I_FINALIZED
&& fsa_state != S_FINALIZE_JOIN) {
crm_debug("Discarding %s event in state: %s",
fsa_input2string(timer->fsa_input),
fsa_state2string(fsa_state));
} else if(timer->fsa_input != I_NULL) {
register_fsa_input(C_TIMER_POPPED, timer->fsa_input, NULL);
}
crm_debug_3("Triggering FSA: %s", __FUNCTION__);
G_main_set_trigger(fsa_source);
return TRUE;
}
gboolean
crm_timer_start(fsa_timer_t *timer)
{
const char *timer_desc = get_timer_desc(timer);
if(timer->source_id == 0 && timer->period_ms > 0) {
timer->source_id = Gmain_timeout_add(
timer->period_ms, timer->callback, (void*)timer);
CRM_ASSERT(timer->source_id != 0);
crm_debug("Started %s (%s:%dms), src=%d",
timer_desc, fsa_input2string(timer->fsa_input),
timer->period_ms, timer->source_id);
} else if(timer->period_ms < 0) {
crm_err("Tried to start %s (%s:%dms) with a -ve period",
timer_desc, fsa_input2string(timer->fsa_input),
timer->period_ms);
} else {
crm_debug("%s (%s:%dms) already running: src=%d",
timer_desc, fsa_input2string(timer->fsa_input),
timer->period_ms, timer->source_id);
return FALSE;
}
return TRUE;
}
gboolean
crm_timer_stop(fsa_timer_t *timer)
{
const char *timer_desc = get_timer_desc(timer);
if(timer == NULL) {
crm_err("Attempted to stop NULL timer");
return FALSE;
} else if(timer->source_id != 0) {
crm_debug("Stopping %s (%s:%dms), src=%d",
timer_desc, fsa_input2string(timer->fsa_input),
timer->period_ms, timer->source_id);
Gmain_timeout_remove(timer->source_id);
timer->source_id = 0;
} else {
crm_debug_2("%s (%s:%dms) already stopped",
timer_desc, fsa_input2string(timer->fsa_input),
timer->period_ms);
return FALSE;
}
return TRUE;
}
long long
toggle_bit(long long action_list, long long action)
{
crm_debug_5("Toggling bit %.16llx", action);
action_list ^= action;
crm_debug_5("Result %.16llx", action_list & action);
return action_list;
}
long long
clear_bit(long long action_list, long long action)
{
unsigned int level = LOG_DEBUG_5;
crm_log_maybe(level, "Clearing bit\t%.16llx", action);
/* ensure its set */
action_list |= action;
/* then toggle */
action_list = action_list ^ action;
return action_list;
}
long long
set_bit(long long action_list, long long action)
{
unsigned int level = LOG_DEBUG_5;
crm_log_maybe(level, "Setting bit\t%.16llx", action);
action_list |= action;
return action_list;
}
gboolean
is_set(long long action_list, long long action)
{
crm_debug_5("Checking bit\t%.16llx in %.16llx", action, action_list);
return ((action_list & action) == action);
}
gboolean
is_set_any(long long action_list, long long action)
{
crm_debug_5("Checking bit\t%.16llx in %.16llx", action, action_list);
return ((action_list & action) != 0);
}
const char *
fsa_input2string(enum crmd_fsa_input input)
{
const char *inputAsText = NULL;
switch(input){
case I_NULL:
inputAsText = "I_NULL";
break;
case I_CCM_EVENT:
inputAsText = "I_CCM_EVENT";
break;
case I_CIB_OP:
inputAsText = "I_CIB_OP";
break;
case I_CIB_UPDATE:
inputAsText = "I_CIB_UPDATE";
break;
case I_DC_TIMEOUT:
inputAsText = "I_DC_TIMEOUT";
break;
case I_ELECTION:
inputAsText = "I_ELECTION";
break;
case I_PE_CALC:
inputAsText = "I_PE_CALC";
break;
case I_RELEASE_DC:
inputAsText = "I_RELEASE_DC";
break;
case I_ELECTION_DC:
inputAsText = "I_ELECTION_DC";
break;
case I_ERROR:
inputAsText = "I_ERROR";
break;
case I_FAIL:
inputAsText = "I_FAIL";
break;
case I_INTEGRATED:
inputAsText = "I_INTEGRATED";
break;
case I_FINALIZED:
inputAsText = "I_FINALIZED";
break;
case I_NODE_JOIN:
inputAsText = "I_NODE_JOIN";
break;
case I_JOIN_OFFER:
inputAsText = "I_JOIN_OFFER";
break;
case I_JOIN_REQUEST:
inputAsText = "I_JOIN_REQUEST";
break;
case I_JOIN_RESULT:
inputAsText = "I_JOIN_RESULT";
break;
case I_NOT_DC:
inputAsText = "I_NOT_DC";
break;
case I_RECOVERED:
inputAsText = "I_RECOVERED";
break;
case I_RELEASE_FAIL:
inputAsText = "I_RELEASE_FAIL";
break;
case I_RELEASE_SUCCESS:
inputAsText = "I_RELEASE_SUCCESS";
break;
case I_RESTART:
inputAsText = "I_RESTART";
break;
case I_PE_SUCCESS:
inputAsText = "I_PE_SUCCESS";
break;
case I_ROUTER:
inputAsText = "I_ROUTER";
break;
case I_SHUTDOWN:
inputAsText = "I_SHUTDOWN";
break;
case I_STARTUP:
inputAsText = "I_STARTUP";
break;
case I_TE_SUCCESS:
inputAsText = "I_TE_SUCCESS";
break;
case I_STOP:
inputAsText = "I_STOP";
break;
case I_DC_HEARTBEAT:
inputAsText = "I_DC_HEARTBEAT";
break;
case I_WAIT_FOR_EVENT:
inputAsText = "I_WAIT_FOR_EVENT";
break;
case I_LRM_EVENT:
inputAsText = "I_LRM_EVENT";
break;
case I_PENDING:
inputAsText = "I_PENDING";
break;
case I_HALT:
inputAsText = "I_HALT";
break;
case I_TERMINATE:
inputAsText = "I_TERMINATE";
break;
case I_ILLEGAL:
inputAsText = "I_ILLEGAL";
break;
}
if(inputAsText == NULL) {
crm_err("Input %d is unknown", input);
inputAsText = "<UNKNOWN_INPUT>";
}
return inputAsText;
}
const char *
fsa_state2string(enum crmd_fsa_state state)
{
const char *stateAsText = NULL;
switch(state){
case S_IDLE:
stateAsText = "S_IDLE";
break;
case S_ELECTION:
stateAsText = "S_ELECTION";
break;
case S_INTEGRATION:
stateAsText = "S_INTEGRATION";
break;
case S_FINALIZE_JOIN:
stateAsText = "S_FINALIZE_JOIN";
break;
case S_NOT_DC:
stateAsText = "S_NOT_DC";
break;
case S_POLICY_ENGINE:
stateAsText = "S_POLICY_ENGINE";
break;
case S_RECOVERY:
stateAsText = "S_RECOVERY";
break;
case S_RELEASE_DC:
stateAsText = "S_RELEASE_DC";
break;
case S_PENDING:
stateAsText = "S_PENDING";
break;
case S_STOPPING:
stateAsText = "S_STOPPING";
break;
case S_TERMINATE:
stateAsText = "S_TERMINATE";
break;
case S_TRANSITION_ENGINE:
stateAsText = "S_TRANSITION_ENGINE";
break;
case S_STARTING:
stateAsText = "S_STARTING";
break;
case S_HALT:
stateAsText = "S_HALT";
break;
case S_ILLEGAL:
stateAsText = "S_ILLEGAL";
break;
}
if(stateAsText == NULL) {
crm_err("State %d is unknown", state);
stateAsText = "<UNKNOWN_STATE>";
}
return stateAsText;
}
const char *
fsa_cause2string(enum crmd_fsa_cause cause)
{
const char *causeAsText = NULL;
switch(cause){
case C_UNKNOWN:
causeAsText = "C_UNKNOWN";
break;
case C_STARTUP:
causeAsText = "C_STARTUP";
break;
case C_IPC_MESSAGE:
causeAsText = "C_IPC_MESSAGE";
break;
case C_HA_MESSAGE:
causeAsText = "C_HA_MESSAGE";
break;
case C_CCM_CALLBACK:
causeAsText = "C_CCM_CALLBACK";
break;
case C_TIMER_POPPED:
causeAsText = "C_TIMER_POPPED";
break;
case C_SHUTDOWN:
causeAsText = "C_SHUTDOWN";
break;
case C_HEARTBEAT_FAILED:
causeAsText = "C_HEARTBEAT_FAILED";
break;
case C_SUBSYSTEM_CONNECT:
causeAsText = "C_SUBSYSTEM_CONNECT";
break;
case C_LRM_OP_CALLBACK:
causeAsText = "C_LRM_OP_CALLBACK";
break;
case C_LRM_MONITOR_CALLBACK:
causeAsText = "C_LRM_MONITOR_CALLBACK";
break;
case C_CRMD_STATUS_CALLBACK:
causeAsText = "C_CRMD_STATUS_CALLBACK";
break;
case C_HA_DISCONNECT:
causeAsText = "C_HA_DISCONNECT";
break;
case C_FSA_INTERNAL:
causeAsText = "C_FSA_INTERNAL";
break;
case C_ILLEGAL:
causeAsText = "C_ILLEGAL";
break;
}
if(causeAsText == NULL) {
crm_err("Cause %d is unknown", cause);
causeAsText = "<UNKNOWN_CAUSE>";
}
return causeAsText;
}
const char *
fsa_action2string(long long action)
{
const char *actionAsText = NULL;
switch(action){
case A_NOTHING:
actionAsText = "A_NOTHING";
break;
case A_ELECTION_START:
actionAsText = "A_ELECTION_START";
break;
case A_READCONFIG:
actionAsText = "A_READCONFIG";
break;
case O_RELEASE:
actionAsText = "O_RELEASE";
break;
case A_STARTUP:
actionAsText = "A_STARTUP";
break;
case A_STARTED:
actionAsText = "A_STARTED";
break;
case A_HA_CONNECT:
actionAsText = "A_HA_CONNECT";
break;
case A_HA_DISCONNECT:
actionAsText = "A_HA_DISCONNECT";
break;
case A_LRM_CONNECT:
actionAsText = "A_LRM_CONNECT";
break;
case A_LRM_EVENT:
actionAsText = "A_LRM_EVENT";
break;
case A_LRM_INVOKE:
actionAsText = "A_LRM_INVOKE";
break;
case A_LRM_DISCONNECT:
actionAsText = "A_LRM_DISCONNECT";
break;
case A_CL_JOIN_QUERY:
actionAsText = "A_CL_JOIN_QUERY";
break;
case A_DC_TIMER_STOP:
actionAsText = "A_DC_TIMER_STOP";
break;
case A_DC_TIMER_START:
actionAsText = "A_DC_TIMER_START";
break;
case A_INTEGRATE_TIMER_START:
actionAsText = "A_INTEGRATE_TIMER_START";
break;
case A_INTEGRATE_TIMER_STOP:
actionAsText = "A_INTEGRATE_TIMER_STOP";
break;
case A_FINALIZE_TIMER_START:
actionAsText = "A_FINALIZE_TIMER_START";
break;
case A_FINALIZE_TIMER_STOP:
actionAsText = "A_FINALIZE_TIMER_STOP";
break;
case A_ELECTION_COUNT:
actionAsText = "A_ELECTION_COUNT";
break;
case A_ELECTION_VOTE:
actionAsText = "A_ELECTION_VOTE";
break;
case A_ELECTION_CHECK:
actionAsText = "A_ELECTION_CHECK";
break;
case A_CL_JOIN_ANNOUNCE:
actionAsText = "A_CL_JOIN_ANNOUNCE";
break;
case A_CL_JOIN_REQUEST:
actionAsText = "A_CL_JOIN_REQUEST";
break;
case A_CL_JOIN_RESULT:
actionAsText = "A_CL_JOIN_RESULT";
break;
case A_DC_JOIN_OFFER_ALL:
actionAsText = "A_DC_JOIN_OFFER_ALL";
break;
case A_DC_JOIN_OFFER_ONE:
actionAsText = "A_DC_JOIN_OFFER_ONE";
break;
case A_DC_JOIN_PROCESS_REQ:
actionAsText = "A_DC_JOIN_PROCESS_REQ";
break;
case A_DC_JOIN_PROCESS_ACK:
actionAsText = "A_DC_JOIN_PROCESS_ACK";
break;
case A_DC_JOIN_FINALIZE:
actionAsText = "A_DC_JOIN_FINALIZE";
break;
case A_MSG_PROCESS:
actionAsText = "A_MSG_PROCESS";
break;
case A_MSG_ROUTE:
actionAsText = "A_MSG_ROUTE";
break;
case A_RECOVER:
actionAsText = "A_RECOVER";
break;
case A_DC_RELEASE:
actionAsText = "A_DC_RELEASE";
break;
case A_DC_RELEASED:
actionAsText = "A_DC_RELEASED";
break;
case A_DC_TAKEOVER:
actionAsText = "A_DC_TAKEOVER";
break;
case A_SHUTDOWN:
actionAsText = "A_SHUTDOWN";
break;
case A_SHUTDOWN_REQ:
actionAsText = "A_SHUTDOWN_REQ";
break;
case A_STOP:
actionAsText = "A_STOP ";
break;
case A_EXIT_0:
actionAsText = "A_EXIT_0";
break;
case A_EXIT_1:
actionAsText = "A_EXIT_1";
break;
case A_CCM_CONNECT:
actionAsText = "A_CCM_CONNECT";
break;
case A_CCM_DISCONNECT:
actionAsText = "A_CCM_DISCONNECT";
break;
case A_CCM_EVENT:
actionAsText = "A_CCM_EVENT";
break;
case A_CCM_UPDATE_CACHE:
actionAsText = "A_CCM_UPDATE_CACHE";
break;
case A_CIB_BUMPGEN:
actionAsText = "A_CIB_BUMPGEN";
break;
case A_CIB_INVOKE:
actionAsText = "A_CIB_INVOKE";
break;
case O_CIB_RESTART:
actionAsText = "O_CIB_RESTART";
break;
case A_CIB_START:
actionAsText = "A_CIB_START";
break;
case A_CIB_STOP:
actionAsText = "A_CIB_STOP";
break;
case A_TE_INVOKE:
actionAsText = "A_TE_INVOKE";
break;
case O_TE_RESTART:
actionAsText = "O_TE_RESTART";
break;
case A_TE_START:
actionAsText = "A_TE_START";
break;
case A_TE_STOP:
actionAsText = "A_TE_STOP";
break;
case A_TE_HALT:
actionAsText = "A_TE_HALT";
break;
case A_TE_CANCEL:
actionAsText = "A_TE_CANCEL";
break;
case A_PE_INVOKE:
actionAsText = "A_PE_INVOKE";
break;
case O_PE_RESTART:
actionAsText = "O_PE_RESTART";
break;
case A_PE_START:
actionAsText = "A_PE_START";
break;
case A_PE_STOP:
actionAsText = "A_PE_STOP";
break;
case A_NODE_BLOCK:
actionAsText = "A_NODE_BLOCK";
break;
case A_UPDATE_NODESTATUS:
actionAsText = "A_UPDATE_NODESTATUS";
break;
case A_LOG:
actionAsText = "A_LOG ";
break;
case A_ERROR:
actionAsText = "A_ERROR ";
break;
case A_WARN:
actionAsText = "A_WARN ";
break;
}
if(actionAsText == NULL) {
crm_err("Action %.16llx is unknown", action);
actionAsText = "<UNKNOWN_ACTION>";
}
return actionAsText;
}
void
fsa_dump_inputs(int log_level, const char *text, long long input_register)
{
if(input_register == A_NOTHING) {
return;
}
if(text == NULL) {
text = "Input register contents:";
}
if(is_set(input_register, R_THE_DC)) {
crm_log_maybe(log_level,
"%s %.16llx (R_THE_DC)", text, R_THE_DC);
}
if(is_set(input_register, R_STARTING)) {
crm_log_maybe(log_level, "%s %.16llx (R_STARTING)",
text, R_STARTING);
}
if(is_set(input_register, R_SHUTDOWN)) {
crm_log_maybe(log_level, "%s %.16llx (R_SHUTDOWN)",
text, R_SHUTDOWN);
}
if(is_set(input_register, R_STAYDOWN)) {
crm_log_maybe(log_level, "%s %.16llx (R_STAYDOWN)",
text, R_STAYDOWN);
}
if(is_set(input_register, R_JOIN_OK)) {
crm_log_maybe(log_level, "%s %.16llx (R_JOIN_OK)",
text, R_JOIN_OK);
}
if(is_set(input_register, R_READ_CONFIG)) {
crm_log_maybe(log_level, "%s %.16llx (R_READ_CONFIG)",
text, R_READ_CONFIG);
}
if(is_set(input_register, R_INVOKE_PE)) {
crm_log_maybe(log_level, "%s %.16llx (R_INVOKE_PE)",
text, R_INVOKE_PE);
}
if(is_set(input_register, R_CIB_CONNECTED)) {
crm_log_maybe(log_level, "%s %.16llx (R_CIB_CONNECTED)",
text, R_CIB_CONNECTED);
}
if(is_set(input_register, R_PE_CONNECTED)) {
crm_log_maybe(log_level, "%s %.16llx (R_PE_CONNECTED)",
text, R_PE_CONNECTED);
}
if(is_set(input_register, R_TE_CONNECTED)) {
crm_log_maybe(log_level, "%s %.16llx (R_TE_CONNECTED)",
text, R_TE_CONNECTED);
}
if(is_set(input_register, R_LRM_CONNECTED)) {
crm_log_maybe(log_level, "%s %.16llx (R_LRM_CONNECTED)",
text, R_LRM_CONNECTED);
}
if(is_set(input_register, R_CIB_REQUIRED)) {
crm_log_maybe(log_level, "%s %.16llx (R_CIB_REQUIRED)",
text, R_CIB_REQUIRED);
}
if(is_set(input_register, R_PE_REQUIRED)) {
crm_log_maybe(log_level, "%s %.16llx (R_PE_REQUIRED)",
text, R_PE_REQUIRED);
}
if(is_set(input_register, R_TE_REQUIRED)) {
crm_log_maybe(log_level, "%s %.16llx (R_TE_REQUIRED)",
text, R_TE_REQUIRED);
}
if(is_set(input_register, R_REQ_PEND)) {
crm_log_maybe(log_level, "%s %.16llx (R_REQ_PEND)",
text, R_REQ_PEND);
}
if(is_set(input_register, R_PE_PEND)) {
crm_log_maybe(log_level, "%s %.16llx (R_PE_PEND)",
text, R_PE_PEND);
}
if(is_set(input_register, R_TE_PEND)) {
crm_log_maybe(log_level, "%s %.16llx (R_TE_PEND)",
text, R_TE_PEND);
}
if(is_set(input_register, R_RESP_PEND)) {
crm_log_maybe(log_level, "%s %.16llx (R_RESP_PEND)",
text, R_RESP_PEND);
}
if(is_set(input_register, R_CIB_DONE)) {
crm_log_maybe(log_level, "%s %.16llx (R_CIB_DONE)",
text, R_CIB_DONE);
}
if(is_set(input_register, R_HAVE_CIB)) {
crm_log_maybe(log_level, "%s %.16llx (R_HAVE_CIB)",
text, R_HAVE_CIB);
}
if(is_set(input_register, R_CIB_ASKED)) {
crm_log_maybe(log_level, "%s %.16llx (R_CIB_ASKED)",
text, R_CIB_ASKED);
}
if(is_set(input_register, R_CCM_DATA)) {
crm_log_maybe(log_level, "%s %.16llx (R_CCM_DATA)",
text, R_CCM_DATA);
}
if(is_set(input_register, R_PEER_DATA)) {
crm_log_maybe(log_level, "%s %.16llx (R_PEER_DATA)",
text, R_PEER_DATA);
}
if(is_set(input_register, R_IN_RECOVERY)) {
crm_log_maybe(log_level, "%s %.16llx (R_IN_RECOVERY)",
text, R_IN_RECOVERY);
}
}
void
fsa_dump_actions(long long action, const char *text)
{
int log_level = LOG_DEBUG_3;
if(is_set(action, A_READCONFIG)) {
crm_log_maybe(log_level,
"Action %.16llx (A_READCONFIG) %s", A_READCONFIG, text);
}
if(is_set(action, A_STARTUP)) {
crm_log_maybe(log_level,
"Action %.16llx (A_STARTUP) %s", A_STARTUP, text);
}
if(is_set(action, A_STARTED)) {
crm_log_maybe(log_level,
"Action %.16llx (A_STARTED) %s", A_STARTED, text);
}
if(is_set(action, A_HA_CONNECT)) {
crm_log_maybe(log_level,
"Action %.16llx (A_CONNECT) %s", A_HA_CONNECT, text);
}
if(is_set(action, A_HA_DISCONNECT)) {
crm_log_maybe(log_level,
"Action %.16llx (A_DISCONNECT) %s",
A_HA_DISCONNECT, text);
}
if(is_set(action, A_LRM_CONNECT)) {
crm_log_maybe(log_level,
"Action %.16llx (A_LRM_CONNECT) %s",
A_LRM_CONNECT, text);
}
if(is_set(action, A_LRM_EVENT)) {
crm_log_maybe(log_level,
"Action %.16llx (A_LRM_EVENT) %s",
A_LRM_EVENT, text);
}
if(is_set(action, A_LRM_INVOKE)) {
crm_log_maybe(log_level,
"Action %.16llx (A_LRM_INVOKE) %s",
A_LRM_INVOKE, text);
}
if(is_set(action, A_LRM_DISCONNECT)) {
crm_log_maybe(log_level,
"Action %.16llx (A_LRM_DISCONNECT) %s",
A_LRM_DISCONNECT, text);
}
if(is_set(action, A_DC_TIMER_STOP)) {
crm_log_maybe(log_level,
"Action %.16llx (A_DC_TIMER_STOP) %s",
A_DC_TIMER_STOP, text);
}
if(is_set(action, A_DC_TIMER_START)) {
crm_log_maybe(log_level,
"Action %.16llx (A_DC_TIMER_START) %s",
A_DC_TIMER_START, text);
}
if(is_set(action, A_INTEGRATE_TIMER_START)) {
crm_log_maybe(log_level,
"Action %.16llx (A_INTEGRATE_TIMER_START) %s",
A_INTEGRATE_TIMER_START, text);
}
if(is_set(action, A_INTEGRATE_TIMER_STOP)) {
crm_log_maybe(log_level,
"Action %.16llx (A_INTEGRATE_TIMER_STOP) %s",
A_INTEGRATE_TIMER_STOP, text);
}
if(is_set(action, A_FINALIZE_TIMER_START)) {
crm_log_maybe(log_level,
"Action %.16llx (A_FINALIZE_TIMER_START) %s",
A_FINALIZE_TIMER_START, text);
}
if(is_set(action, A_FINALIZE_TIMER_STOP)) {
crm_log_maybe(log_level,
"Action %.16llx (A_FINALIZE_TIMER_STOP) %s",
A_FINALIZE_TIMER_STOP, text);
}
if(is_set(action, A_ELECTION_COUNT)) {
crm_log_maybe(log_level,
"Action %.16llx (A_ELECTION_COUNT) %s",
A_ELECTION_COUNT, text);
}
if(is_set(action, A_ELECTION_VOTE)) {
crm_log_maybe(log_level,
"Action %.16llx (A_ELECTION_VOTE) %s",
A_ELECTION_VOTE, text);
}
if(is_set(action, A_ELECTION_CHECK)) {
crm_log_maybe(log_level,
"Action %.16llx (A_ELECTION_CHECK) %s",
A_ELECTION_CHECK, text);
}
if(is_set(action, A_CL_JOIN_ANNOUNCE)) {
crm_log_maybe(log_level,
"Action %.16llx (A_CL_JOIN_ANNOUNCE) %s",
A_CL_JOIN_ANNOUNCE, text);
}
if(is_set(action, A_CL_JOIN_REQUEST)) {
crm_log_maybe(log_level,
"Action %.16llx (A_CL_JOIN_REQUEST) %s",
A_CL_JOIN_REQUEST, text);
}
if(is_set(action, A_CL_JOIN_RESULT)) {
crm_log_maybe(log_level,
"Action %.16llx (A_CL_JOIN_RESULT) %s",
A_CL_JOIN_RESULT, text);
}
if(is_set(action, A_DC_JOIN_OFFER_ALL)) {
crm_log_maybe(log_level,
"Action %.16llx (A_DC_JOIN_OFFER_ALL) %s",
A_DC_JOIN_OFFER_ALL, text);
}
if(is_set(action, A_DC_JOIN_OFFER_ONE)) {
crm_log_maybe(log_level,
"Action %.16llx (A_DC_JOIN_OFFER_ONE) %s",
A_DC_JOIN_OFFER_ONE, text);
}
if(is_set(action, A_DC_JOIN_PROCESS_REQ)) {
crm_log_maybe(log_level,
"Action %.16llx (A_DC_JOIN_PROCESS_REQ) %s",
A_DC_JOIN_PROCESS_REQ, text);
}
if(is_set(action, A_DC_JOIN_PROCESS_ACK)) {
crm_log_maybe(log_level,
"Action %.16llx (A_DC_JOIN_PROCESS_ACK) %s",
A_DC_JOIN_PROCESS_ACK, text);
}
if(is_set(action, A_DC_JOIN_FINALIZE)) {
crm_log_maybe(log_level,
"Action %.16llx (A_DC_JOIN_FINALIZE) %s",
A_DC_JOIN_FINALIZE, text);
}
if(is_set(action, A_MSG_PROCESS)) {
crm_log_maybe(log_level,
"Action %.16llx (A_MSG_PROCESS) %s",
A_MSG_PROCESS, text);
}
if(is_set(action, A_MSG_ROUTE)) {
crm_log_maybe(log_level,
"Action %.16llx (A_MSG_ROUTE) %s",
A_MSG_ROUTE, text);
}
if(is_set(action, A_RECOVER)) {
crm_log_maybe(log_level,
"Action %.16llx (A_RECOVER) %s",
A_RECOVER, text);
}
if(is_set(action, A_DC_RELEASE)) {
crm_log_maybe(log_level,
"Action %.16llx (A_DC_RELEASE) %s",
A_DC_RELEASE, text);
}
if(is_set(action, A_DC_RELEASED)) {
crm_log_maybe(log_level,
"Action %.16llx (A_DC_RELEASED) %s",
A_DC_RELEASED, text);
}
if(is_set(action, A_DC_TAKEOVER)) {
crm_log_maybe(log_level,
"Action %.16llx (A_DC_TAKEOVER) %s",
A_DC_TAKEOVER, text);
}
if(is_set(action, A_SHUTDOWN)) {
crm_log_maybe(log_level,
"Action %.16llx (A_SHUTDOWN) %s", A_SHUTDOWN, text);
}
if(is_set(action, A_SHUTDOWN_REQ)) {
crm_log_maybe(log_level,
"Action %.16llx (A_SHUTDOWN_REQ) %s",
A_SHUTDOWN_REQ, text);
}
if(is_set(action, A_STOP)) {
crm_log_maybe(log_level,
"Action %.16llx (A_STOP ) %s", A_STOP , text);
}
if(is_set(action, A_EXIT_0)) {
crm_log_maybe(log_level,
"Action %.16llx (A_EXIT_0) %s", A_EXIT_0, text);
}
if(is_set(action, A_EXIT_1)) {
crm_log_maybe(log_level,
"Action %.16llx (A_EXIT_1) %s", A_EXIT_1, text);
}
if(is_set(action, A_CCM_CONNECT)) {
crm_log_maybe(log_level,
"Action %.16llx (A_CCM_CONNECT) %s",
A_CCM_CONNECT, text);
}
if(is_set(action, A_CCM_DISCONNECT)) {
crm_log_maybe(log_level,
"Action %.16llx (A_CCM_DISCONNECT) %s",
A_CCM_DISCONNECT, text);
}
if(is_set(action, A_CCM_EVENT)) {
crm_log_maybe(log_level,
"Action %.16llx (A_CCM_EVENT) %s",
A_CCM_EVENT, text);
}
if(is_set(action, A_CCM_UPDATE_CACHE)) {
crm_log_maybe(log_level,
"Action %.16llx (A_CCM_UPDATE_CACHE) %s",
A_CCM_UPDATE_CACHE, text);
}
if(is_set(action, A_CIB_BUMPGEN)) {
crm_log_maybe(log_level,
"Action %.16llx (A_CIB_BUMPGEN) %s",
A_CIB_BUMPGEN, text);
}
if(is_set(action, A_CIB_INVOKE)) {
crm_log_maybe(log_level,
"Action %.16llx (A_CIB_INVOKE) %s",
A_CIB_INVOKE, text);
}
if(is_set(action, A_CIB_START)) {
crm_log_maybe(log_level,
"Action %.16llx (A_CIB_START) %s",
A_CIB_START, text);
}
if(is_set(action, A_CIB_STOP)) {
crm_log_maybe(log_level,
"Action %.16llx (A_CIB_STOP) %s", A_CIB_STOP, text);
}
if(is_set(action, A_TE_INVOKE)) {
crm_log_maybe(log_level,
"Action %.16llx (A_TE_INVOKE) %s", A_TE_INVOKE, text);
}
if(is_set(action, A_TE_START)) {
crm_log_maybe(log_level,
"Action %.16llx (A_TE_START) %s",
A_TE_START, text);
}
if(is_set(action, A_TE_STOP)) {
crm_log_maybe(log_level,
"Action %.16llx (A_TE_STOP) %s", A_TE_STOP, text);
}
if(is_set(action, A_TE_CANCEL)) {
crm_log_maybe(log_level,
"Action %.16llx (A_TE_CANCEL) %s",
A_TE_CANCEL, text);
}
if(is_set(action, A_PE_INVOKE)) {
crm_log_maybe(log_level,
"Action %.16llx (A_PE_INVOKE) %s",
A_PE_INVOKE, text);
}
if(is_set(action, A_PE_START)) {
crm_log_maybe(log_level,
"Action %.16llx (A_PE_START) %s", A_PE_START, text);
}
if(is_set(action, A_PE_STOP)) {
crm_log_maybe(log_level,
"Action %.16llx (A_PE_STOP) %s", A_PE_STOP, text);
}
if(is_set(action, A_NODE_BLOCK)) {
crm_log_maybe(log_level,
"Action %.16llx (A_NODE_BLOCK) %s",
A_NODE_BLOCK, text);
}
if(is_set(action, A_UPDATE_NODESTATUS)) {
crm_log_maybe(log_level,
"Action %.16llx (A_UPDATE_NODESTATUS) %s",
A_UPDATE_NODESTATUS, text);
}
if(is_set(action, A_LOG)) {
crm_log_maybe(log_level,
"Action %.16llx (A_LOG ) %s", A_LOG, text);
}
if(is_set(action, A_ERROR)) {
crm_log_maybe(log_level,
"Action %.16llx (A_ERROR ) %s", A_ERROR, text);
}
if(is_set(action, A_WARN)) {
crm_log_maybe(log_level,
"Action %.16llx (A_WARN ) %s", A_WARN, text);
}
}
void
create_node_entry(const char *uuid, const char *uname, const char *type)
{
/* make sure a node entry exists for the new node
*
* this will add anyone except the first ever node in the cluster
* since it will also be the DC which doesnt go through the
* join process (with itself). We can include a special case
* later if desired.
*/
crm_data_t *tmp1 = create_xml_node(NULL, XML_CIB_TAG_NODE);
crm_debug_3("Creating node entry for %s", uname);
set_uuid(fsa_cluster_conn, tmp1, XML_ATTR_UUID, uname);
crm_xml_add(tmp1, XML_ATTR_UNAME, uname);
crm_xml_add(tmp1, XML_ATTR_TYPE, type);
fsa_cib_anon_update(XML_CIB_TAG_NODES, tmp1,
cib_scope_local|cib_quorum_override);
free_xml(tmp1);
}
struct crmd_ccm_data_s *
copy_ccm_data(const struct crmd_ccm_data_s *ccm_input)
{
const oc_ev_membership_t *oc_in =
(const oc_ev_membership_t *)ccm_input->oc;
struct crmd_ccm_data_s *ccm_input_copy = NULL;
crm_malloc0(ccm_input_copy, sizeof(struct crmd_ccm_data_s));
ccm_input_copy->oc = copy_ccm_oc_data(oc_in);
ccm_input_copy->event = ccm_input->event;
return ccm_input_copy;
}
oc_ev_membership_t *
copy_ccm_oc_data(const oc_ev_membership_t *oc_in)
{
unsigned lpc = 0;
int size = 0;
int offset = 0;
unsigned num_nodes = 0;
oc_ev_membership_t *oc_copy = NULL;
if(oc_in->m_n_member > 0
&& num_nodes < oc_in->m_n_member + oc_in->m_memb_idx) {
num_nodes = oc_in->m_n_member + oc_in->m_memb_idx;
crm_debug_3("Updated ccm nodes to %d - 1", num_nodes);
}
if(oc_in->m_n_in > 0
&& num_nodes < oc_in->m_n_in + oc_in->m_in_idx) {
num_nodes = oc_in->m_n_in + oc_in->m_in_idx;
crm_debug_3("Updated ccm nodes to %d - 2", num_nodes);
}
if(oc_in->m_n_out > 0
&& num_nodes < oc_in->m_n_out + oc_in->m_out_idx) {
num_nodes = oc_in->m_n_out + oc_in->m_out_idx;
crm_debug_3("Updated ccm nodes to %d - 3", num_nodes);
}
/* why 2*??
* ccm code does it like this so i guess its right...
*/
size = sizeof(oc_ev_membership_t)
+ sizeof(int)
+ 2*num_nodes*sizeof(oc_node_t);
crm_debug_3("Copying %d ccm nodes", num_nodes);
crm_malloc0(oc_copy, size);
oc_copy->m_instance = oc_in->m_instance;
oc_copy->m_n_member = oc_in->m_n_member;
oc_copy->m_memb_idx = oc_in->m_memb_idx;
oc_copy->m_n_out = oc_in->m_n_out;
oc_copy->m_out_idx = oc_in->m_out_idx;
oc_copy->m_n_in = oc_in->m_n_in;
oc_copy->m_in_idx = oc_in->m_in_idx;
crm_debug_3("instance=%d, nodes=%d (idx=%d), new=%d (idx=%d), lost=%d (idx=%d)",
oc_in->m_instance,
oc_in->m_n_member,
oc_in->m_memb_idx,
oc_in->m_n_in,
oc_in->m_in_idx,
oc_in->m_n_out,
oc_in->m_out_idx);
offset = oc_in->m_memb_idx;
for(lpc = 0; lpc < oc_in->m_n_member; lpc++) {
oc_node_t a_node = oc_in->m_array[lpc+offset];
oc_node_t *a_node_copy = &(oc_copy->m_array[lpc+offset]);
crm_debug_3("Copying ccm member node %d", lpc);
copy_ccm_node(a_node, a_node_copy);
}
offset = oc_in->m_in_idx;
for(lpc = 0; lpc < oc_in->m_n_in; lpc++) {
oc_node_t a_node = oc_in->m_array[lpc+offset];
oc_node_t *a_node_copy = &(oc_copy->m_array[lpc+offset]);
crm_debug_3("Copying ccm new node %d", lpc);
copy_ccm_node(a_node, a_node_copy);
}
offset = oc_in->m_out_idx;
for(lpc = 0; lpc < oc_in->m_n_out; lpc++) {
oc_node_t a_node = oc_in->m_array[lpc+offset];
oc_node_t *a_node_copy = &(oc_copy->m_array[lpc+offset]);
crm_debug_3("Copying ccm lost node %d", lpc);
copy_ccm_node(a_node, a_node_copy);
}
return oc_copy;
}
void
copy_ccm_node(oc_node_t a_node, oc_node_t *a_node_copy)
{
crm_debug_3("Copying ccm node: id=%d, born=%d, uname=%s",
a_node.node_id, a_node.node_born_on,
a_node.node_uname);
a_node_copy->node_id = a_node.node_id;
a_node_copy->node_born_on = a_node.node_born_on;
a_node_copy->node_uname = NULL;
if(a_node.node_uname != NULL) {
a_node_copy->node_uname =
crm_strdup(a_node.node_uname);
} else {
crm_err("Node Id %d had a NULL uname!",
a_node.node_id);
}
crm_debug_3("Copied ccm node: id=%d, born=%d, uname=%s",
a_node_copy->node_id, a_node_copy->node_born_on,
a_node_copy->node_uname);
}
crm_data_t*
create_node_state(
const char *uname, const char *ha_state, const char *ccm_state,
const char *crmd_state, const char *join_state, const char *exp_state,
gboolean clear_shutdown, const char *src)
{
crm_data_t *node_state = create_xml_node(NULL, XML_CIB_TAG_STATE);
crm_debug_2("%s Creating node state entry for %s", src, uname);
set_uuid(fsa_cluster_conn, node_state, XML_ATTR_UUID, uname);
+ if(ID(node_state) == NULL) {
+ crm_debug("Node %s is not a cluster member", uname);
+ free_xml(node_state);
+ return NULL;
+ }
+
crm_xml_add(node_state, XML_ATTR_UNAME, uname);
crm_xml_add(node_state, XML_CIB_ATTR_HASTATE, ha_state);
crm_xml_add(node_state, XML_CIB_ATTR_INCCM, ccm_state);
crm_xml_add(node_state, XML_CIB_ATTR_CRMDSTATE, crmd_state);
crm_xml_add(node_state, XML_CIB_ATTR_JOINSTATE, join_state);
crm_xml_add(node_state, XML_CIB_ATTR_EXPSTATE, exp_state);
crm_xml_add(node_state, XML_ATTR_ORIGIN, src);
if(clear_shutdown) {
crm_xml_add(node_state, XML_CIB_ATTR_SHUTDOWN, "0");
#if CRM_DEPRECATED_SINCE_2_0_3
crm_xml_add(node_state, "clear_shutdown", "true");
#endif
/* crm_xml_add(node_state, */
/* XML_CIB_ATTR_REPLACE, XML_TAG_TRANSIENT_NODEATTRS); */
}
crm_log_xml_debug_3(node_state, "created");
return node_state;
}
gboolean
need_transition(enum crmd_fsa_state state)
{
if(state == S_POLICY_ENGINE
|| state == S_TRANSITION_ENGINE
|| state == S_IDLE) {
return TRUE;
}
return FALSE;
}
extern GHashTable *ipc_clients;
void
process_client_disconnect(crmd_client_t *curr_client)
{
struct crm_subsystem_s *the_subsystem = NULL;
CRM_CHECK(curr_client != NULL, return);
crm_debug_2("received HUP from %s", curr_client->table_key);
if (curr_client->sub_sys == NULL) {
crm_debug_2("Client hadn't registered with us yet");
} else if (strcasecmp(CRM_SYSTEM_PENGINE, curr_client->sub_sys) == 0) {
the_subsystem = pe_subsystem;
} else if (strcasecmp(CRM_SYSTEM_TENGINE, curr_client->sub_sys) == 0) {
the_subsystem = te_subsystem;
} else if (strcasecmp(CRM_SYSTEM_CIB, curr_client->sub_sys) == 0){
the_subsystem = cib_subsystem;
}
if(the_subsystem != NULL) {
the_subsystem->ipc = NULL;
crm_info("Received HUP from %s:[%d]",
the_subsystem->name, the_subsystem->pid);
} else {
/* else that was a transient client */
crm_debug("Received HUP from transient client");
}
if (curr_client->table_key != NULL) {
/*
* Key is destroyed below:
* curr_client->table_key
* Value is cleaned up by:
* G_main_del_IPC_Channel
*/
g_hash_table_remove(ipc_clients, curr_client->table_key);
}
}
void update_dc(HA_Message *msg, gboolean assert_same)
{
const char *dc_version = NULL;
const char *welcome_from = NULL;
if(msg != NULL) {
dc_version = cl_get_string(msg, F_CRM_VERSION);
welcome_from = cl_get_string(msg, F_CRM_HOST_FROM);
CRM_CHECK(dc_version != NULL, return);
CRM_CHECK(welcome_from != NULL, return);
if(AM_I_DC) {
CRM_CHECK(safe_str_eq(welcome_from, fsa_our_uname),
return);
}
if(assert_same) {
CRM_CHECK(fsa_our_dc != NULL, ;);
CRM_CHECK(safe_str_eq(fsa_our_dc, welcome_from), ;);
}
}
crm_free(fsa_our_dc);
crm_free(fsa_our_dc_version);
fsa_our_dc = NULL;
fsa_our_dc_version = NULL;
if(welcome_from != NULL) {
fsa_our_dc = crm_strdup(welcome_from);
}
if(dc_version != NULL) {
fsa_our_dc_version = crm_strdup(dc_version);
}
crm_info("Set DC to %s (%s)",
crm_str(fsa_our_dc), crm_str(fsa_our_dc_version));
}
diff --git a/crm/tengine/actions.c b/crm/tengine/actions.c
index 1fea291455..2a9b53cfcf 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 <andrew@beekhof.net>
*
* 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 <portability.h>
#include <sys/param.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/common/msg.h>
#include <crm/common/xml.h>
#include <tengine.h>
#include <heartbeat.h>
#include <clplumbing/Gmain_timeout.h>
#include <lrm/lrm_api.h>
#include <clplumbing/lsb_exitcodes.h>
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);
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);
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
<node_state id="hadev">
<lrm>
<lrm_resources>
<lrm_resource id="rsc2" last_op="start" op_code="0" target="hadev"/>
*/
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);
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 = crm_itoa(transition_graph->id);
+ 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);
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/events.c b/crm/tengine/events.c
index 1341ac75c3..da847280e9 100644
--- a/crm/tengine/events.c
+++ b/crm/tengine/events.c
@@ -1,546 +1,551 @@
/* $Id: events.c,v 1.23 2006/08/14 09:14:45 andrew Exp $ */
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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 <portability.h>
#include <sys/param.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/common/msg.h>
#include <crm/common/xml.h>
#include <tengine.h>
#include <heartbeat.h>
#include <clplumbing/Gmain_timeout.h>
#include <lrm/lrm_api.h>
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);
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]
...
<status>
<node_state id="node1" state=CRMD_STATE_ACTIVE exp_state="active">
<lrm>
<lrm_resources>
<rsc_state id="" rsc_id="rsc4" node_id="node1" rsc_state="stopped"/>
*/
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_action_t *action, int rc)
{
crm_data_t *rsc = NULL;
char *attr_name = NULL;
const char *task = NULL;
const char *rsc_id = NULL;
const char *on_node = NULL;
const char *on_uuid = NULL;
const char *interval = NULL;
if(rc == 99) {
/* this is an internal code for "we're busy, try again" */
return;
}
interval = g_hash_table_lookup(
action->params, crm_meta_name("interval"));
if(interval == NULL) {
return;
}
CRM_CHECK(action->xml != NULL, return);
rsc = find_xml_node(action->xml, XML_CIB_TAG_RESOURCE, TRUE);
CRM_CHECK(rsc != NULL, return);
rsc_id = ID(rsc);
CRM_CHECK(rsc_id != NULL, return);
task = crm_element_value(action->xml, XML_LRM_ATTR_TASK);
on_node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
on_uuid = crm_element_value(action->xml, XML_LRM_ATTR_TARGET_UUID);
CRM_CHECK(task != NULL, return);
CRM_CHECK(on_uuid != NULL, return);
CRM_CHECK(on_node != NULL, return);
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);
}
/*
* 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)
{
int log_level_fail = LOG_ERR;
int target_rc = 0;
const char *target_rc_s = NULL;
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;
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);
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);
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;
}
+
+ crm_free(update_te_uuid);
/* stop this event's timer if it had one */
stop_te_timer(action->timer);
action->confirmed = TRUE;
target_rc_s = g_hash_table_lookup(
action->params,crm_meta_name(XML_ATTR_TE_TARGET_RC));
if(target_rc_s != NULL) {
crm_debug_2("Target rc: %s vs. %d", target_rc_s, op_rc_i);
target_rc = crm_parse_int(target_rc_s, NULL);
if(target_rc == op_rc_i) {
crm_debug_2("Target rc: == %d", op_rc_i);
if(op_status_i != LRM_OP_DONE) {
crm_debug_2("Re-mapping op status to"
" LRM_OP_DONE for %s",update_event);
op_status_i = LRM_OP_DONE;
}
} else {
crm_debug_2("Target rc: != %d", op_rc_i);
if(op_status_i != LRM_OP_ERROR) {
crm_info("Re-mapping op status to"
" LRM_OP_ERROR for %s", update_event);
op_status_i = LRM_OP_ERROR;
}
}
}
/* 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;
case LRM_OP_PENDING:
crm_debug("Ignoring pending operation");
return -5;
break;
case LRM_OP_DONE:
break;
case LRM_OP_ERROR:
/* This is the code we use for direct nack's */
if(op_rc_i == 99) {
log_level_fail = LOG_WARNING;
}
/* fall through */
case LRM_OP_TIMEOUT:
case LRM_OP_NOTSUPPORTED:
action->failed = TRUE;
crm_log_maybe(log_level_fail,
"Action %s on %s failed (target: %d vs. rc: %d): %s",
update_event, this_uname, target_rc,
op_rc_i, op_status2text(op_status_i));
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);
}
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) {
/* ignore probes */
if(target_rc != EXECRA_NOT_RUNNING) {
update_failcount(action, op_rc_i);
}
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);
return action->id;
}
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;
CRM_ASSERT(event != NULL);
rsc_id = crm_element_value(event, XML_ATTR_ID);
magic = crm_element_value(event, XML_ATTR_TRANSITION_MAGIC);
if(magic == NULL) {
crm_log_xml_debug_2(event, "Skipping \"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");
} else if(rc == -5) {
crm_log_xml_debug_2(event, "match:pending");
} else if(rc != -1) {
crm_warn("Search for %s terminated: %d",
ID(event), rc);
abort_transition(INFINITY, tg_restart,
"Unexpected event", event);
}
if(rc != -1) {
return;
}
);
);
/* unexpected event, trigger a pe-recompute */
/* possibly do this only for certain types of actions */
crm_warn("Event not found.");
crm_log_xml_info(event, "match:not-found");
abort_transition(INFINITY, tg_restart, "Unexpected event", event);
return;
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Oct 16, 3:26 PM (7 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2536541
Default Alt Text
(82 KB)

Event Timeline