Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/pcmki/pcmki_sched_utils.h b/include/pcmki/pcmki_sched_utils.h
index fa1e0d6ff9..c53e9f95bb 100644
--- a/include/pcmki/pcmki_sched_utils.h
+++ b/include/pcmki/pcmki_sched_utils.h
@@ -1,65 +1,63 @@
/*
* Copyright 2004-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PENGINE_AUTILS__H
# define PENGINE_AUTILS__H
#include <stdbool.h> // bool
#include <glib.h> // GList, GHashTable, gboolean, guint
#include <crm/lrmd.h> // lrmd_event_data_t
#include <crm/cib.h> // cib_t
#include <crm/pengine/pe_types.h>
#include <crm/pengine/internal.h>
#include <pcmki/pcmki_scheduler.h>
#include <pcmki/pcmki_transition.h>
#include <pacemaker.h>
/* Constraint helper functions */
pcmk__colocation_t *invert_constraint(pcmk__colocation_t *constraint);
pe__location_t *copy_constraint(pe__location_t *constraint);
GHashTable *pcmk__copy_node_table(GHashTable *nodes);
GList *pcmk__copy_node_list(const GList *list, bool reset);
GList *pcmk__sort_nodes(GList *nodes, pe_node_t *active_node,
pe_working_set_t *data_set);
bool pcmk__node_available(const pe_node_t *node);
bool pcmk__any_node_available(GHashTable *nodes);
pe_resource_t *find_compatible_child(pe_resource_t *local_child,
pe_resource_t *rsc, enum rsc_role_e filter,
gboolean current,
pe_working_set_t *data_set);
pe_resource_t *find_compatible_child_by_node(pe_resource_t * local_child, pe_node_t * local_node, pe_resource_t * rsc,
enum rsc_role_e filter, gboolean current);
gboolean is_child_compatible(pe_resource_t *child_rsc, pe_node_t * local_node, enum rsc_role_e filter, gboolean current);
enum pe_action_flags summary_action_flags(pe_action_t * action, GList *children, pe_node_t * node);
enum action_tasks clone_child_action(pe_action_t * action);
int copies_per_node(pe_resource_t * rsc);
extern int compare_capacity(const pe_node_t * node1, const pe_node_t * node2);
extern void calculate_utilization(GHashTable * current_utilization,
GHashTable * utilization, gboolean plus);
extern void process_utilization(pe_resource_t * rsc, pe_node_t ** prefer, pe_working_set_t * data_set);
xmlNode *pcmk__create_history_xml(xmlNode *parent, lrmd_event_data_t *event,
const char *caller_version, int target_rc,
const char *node, const char *origin,
int level);
# define LOAD_STOPPED "load_stopped"
void modify_configuration(pe_working_set_t *data_set, cib_t *cib,
pcmk_injections_t *injections);
-enum transition_status run_simulation(pe_working_set_t * data_set, cib_t *cib, GList *op_fail_list);
-
#endif
diff --git a/include/pcmki/pcmki_simulate.h b/include/pcmki/pcmki_simulate.h
index 79859377ad..dabf5ca483 100644
--- a/include/pcmki/pcmki_simulate.h
+++ b/include/pcmki/pcmki_simulate.h
@@ -1,117 +1,86 @@
/*
* Copyright 2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMKI_SIMULATE__H
# define PCMKI_SIMULATE__H
#include <crm/common/output_internal.h>
#include <crm/pengine/pe_types.h>
#include <pacemaker.h>
#include <stdbool.h>
/**
- * \brief Write out a file in dot(1) format describing the actions that will
- * be taken by the scheduler in response to an input CIB file.
- *
- * \param[in] data_set Working set for the cluster.
- * \param[in] dot_file The filename to write.
- * \param[in] all_actions Write all actions, even those that are optional or
- * are on unmanaged resources.
- * \param[in] verbose Add extra information, such as action IDs, to the
- * output.
- *
- * \return Standard Pacemaker return code
- */
-int pcmk__write_sim_dotfile(pe_working_set_t *data_set, const char *dot_file,
- bool all_actions, bool verbose);
-
-/**
- * \brief Profile the configuration updates and scheduler actions in a single
- * CIB file, printing the profiling timings.
- *
- * \note \p data_set->priv must have been set to a valid \p pcmk__output_t
- * object before this function is called.
- *
- * \param[in] xml_file The CIB file to profile.
- * \param[in] repeat Number of times to run.
- * \param[in] data_set Working set for the cluster.
- * \param[in] use_date The date to set the cluster's time to (may
- * be NULL).
- */
-void pcmk__profile_file(const char *xml_file, long long repeat, pe_working_set_t *data_set,
- char *use_date);
-
-/**
+ * \internal
* \brief Profile the configuration updates and scheduler actions in every
* CIB file in a given directory, printing the profiling timings for
* each.
*
* \note \p data_set->priv must have been set to a valid \p pcmk__output_t
* object before this function is called.
*
* \param[in] dir A directory full of CIB files to be profiled.
* \param[in] repeat Number of times to run on each input file.
* \param[in] data_set Working set for the cluster.
* \param[in] use_date The date to set the cluster's time to (may
* be NULL).
*/
void pcmk__profile_dir(const char *dir, long long repeat, pe_working_set_t *data_set,
char *use_date);
/**
- * \brief Set the date of the cluster, either to the value given by
- * \p use_date, or to the "execution-date" value in the CIB.
+ * \internal
+ * \brief Simulate executing a transition
*
- * \note \p data_set->priv must have been set to a valid \p pcmk__output_t
- * object before this function is called.
+ * \param[in] data_set Cluster working set
+ * \param[in] cib CIB object for scheduler input
+ * \param[in] op_fail_list List of actions to simulate as failing
*
- * \param[in,out] data_set Working set for the cluster.
- * \param[in] print_original If \p true, the "execution-date" should
- * also be printed.
- * \param[in] use_date The date to set the cluster's time to
- * (may be NULL).
+ * \return Transition status after simulated execution
*/
-void pcmk__set_effective_date(pe_working_set_t *data_set, bool print_original, char *use_date);
+enum transition_status pcmk__simulate_transition(pe_working_set_t *data_set,
+ cib_t *cib,
+ GList *op_fail_list);
/**
+ * \internal
* \brief Simulate a cluster's response to events.
*
* This high-level function essentially implements crm_simulate(8). It operates
* on an input CIB file and various lists of events that can be simulated. It
* optionally writes out a variety of artifacts to show the results of the
* simulation. Output can be modified with various flags.
*
* \param[in,out] data_set Working set for the cluster.
* \param[in,out] out The output functions structure.
* \param[in] events A structure containing cluster events
* (node up/down, tickets, injected operations)
* and related data.
* \param[in] flags A bitfield of :pcmk_sim_flags to modify
* operation of the simulation.
* \param[in] section_opts Which portions of the cluster status output
* should be displayed?
* \param[in] use_date The date to set the cluster's time to
* (may be NULL).
* \param[in] input_file The source CIB file, which may be overwritten by
* this function (may be NULL).
* \param[in] graph_file Where to write the XML-formatted transition graph
* (may be NULL, in which case no file will be
* written).
* \param[in] dot_file Where to write the dot(1) formatted transition
* graph (may be NULL, in which case no file will
* be written). See \p pcmk__write_sim_dotfile().
*
* \return Standard Pacemaker return code
*/
int pcmk__simulate(pe_working_set_t *data_set, pcmk__output_t *out,
pcmk_injections_t *injections, unsigned int flags,
unsigned int section_opts, char *use_date, char *input_file,
char *graph_file, char *dot_file);
#endif
diff --git a/lib/pacemaker/libpacemaker_private.h b/lib/pacemaker/libpacemaker_private.h
index e07561bd64..849640fe17 100644
--- a/lib/pacemaker/libpacemaker_private.h
+++ b/lib/pacemaker/libpacemaker_private.h
@@ -1,273 +1,300 @@
/*
* Copyright 2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__LIBPACEMAKER_PRIVATE__H
# define PCMK__LIBPACEMAKER_PRIVATE__H
/* This header is for the sole use of libpacemaker, so that functions can be
* declared with G_GNUC_INTERNAL for efficiency.
*/
#include <crm/pengine/pe_types.h> // pe_action_t, pe_node_t, pe_working_set_t
// Actions
G_GNUC_INTERNAL
void pcmk__update_action_for_orderings(pe_action_t *action,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__log_action(const char *pre_text, pe_action_t *action, bool details);
G_GNUC_INTERNAL
pe_action_t *pcmk__new_rsc_pseudo_action(pe_resource_t *rsc, const char *task,
bool optional, bool runnable);
G_GNUC_INTERNAL
pe_action_t *pcmk__new_cancel_action(pe_resource_t *rsc, const char *name,
guint interval_ms, pe_node_t *node);
G_GNUC_INTERNAL
pe_action_t *pcmk__new_shutdown_action(pe_node_t *node,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__action_locks_rsc_to_node(const pe_action_t *action);
G_GNUC_INTERNAL
void pcmk__deduplicate_action_inputs(pe_action_t *action);
G_GNUC_INTERNAL
void pcmk__output_actions(pe_working_set_t *data_set);
// Producing transition graphs (pcmk_graph_producer.c)
G_GNUC_INTERNAL
bool pcmk__graph_has_loop(pe_action_t *init_action, pe_action_t *action,
pe_action_wrapper_t *input);
G_GNUC_INTERNAL
void pcmk__add_action_to_graph(pe_action_t *action, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__create_graph(pe_working_set_t *data_set);
// Fencing (pcmk_sched_fencing.c)
G_GNUC_INTERNAL
void pcmk__order_vs_fence(pe_action_t *stonith_op, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__order_vs_unfence(pe_resource_t *rsc, pe_node_t *node,
pe_action_t *action, enum pe_ordering order,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__fence_guest(pe_node_t *node, pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__node_unfenced(pe_node_t *node);
G_GNUC_INTERNAL
bool pcmk__is_unfence_device(const pe_resource_t *rsc,
const pe_working_set_t *data_set);
G_GNUC_INTERNAL
pe_resource_t *pcmk__find_constraint_resource(GList *rsc_list, const char *id);
G_GNUC_INTERNAL
xmlNode *pcmk__expand_tags_in_sets(xmlNode *xml_obj,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__valid_resource_or_tag(pe_working_set_t *data_set, const char *id,
pe_resource_t **rsc, pe_tag_t **tag);
G_GNUC_INTERNAL
bool pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr,
bool convert_rsc, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__create_internal_constraints(pe_working_set_t *data_set);
// Location constraints
G_GNUC_INTERNAL
void pcmk__unpack_location(xmlNode *xml_obj, pe_working_set_t *data_set);
G_GNUC_INTERNAL
pe__location_t *pcmk__new_location(const char *id, pe_resource_t *rsc,
int node_weight, const char *discover_mode,
pe_node_t *foo_node,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__apply_locations(pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__apply_location(pe__location_t *constraint, pe_resource_t *rsc);
// Colocation constraints
enum pcmk__coloc_affects {
pcmk__coloc_affects_nothing = 0,
pcmk__coloc_affects_location,
pcmk__coloc_affects_role,
};
G_GNUC_INTERNAL
enum pcmk__coloc_affects pcmk__colocation_affects(pe_resource_t *dependent,
pe_resource_t *primary,
pcmk__colocation_t *constraint,
bool preview);
G_GNUC_INTERNAL
void pcmk__apply_coloc_to_weights(pe_resource_t *dependent,
pe_resource_t *primary,
pcmk__colocation_t *constraint);
G_GNUC_INTERNAL
void pcmk__apply_coloc_to_priority(pe_resource_t *dependent,
pe_resource_t *primary,
pcmk__colocation_t *constraint);
G_GNUC_INTERNAL
void pcmk__unpack_colocation(xmlNode *xml_obj, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__new_colocation(const char *id, const char *node_attr, int score,
pe_resource_t *dependent, pe_resource_t *primary,
const char *dependent_role, const char *primary_role,
bool influence, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__block_colocated_starts(pe_action_t *action,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__new_ordering(pe_resource_t *lh_rsc, char *lh_task,
pe_action_t *lh_action, pe_resource_t *rh_rsc,
char *rh_task, pe_action_t *rh_action,
enum pe_ordering type, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__unpack_ordering(xmlNode *xml_obj, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__disable_invalid_orderings(pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__order_stops_before_shutdown(pe_node_t *node,
pe_action_t *shutdown_op,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__apply_orderings(pe_working_set_t *data_set);
/*!
* \internal
* \brief Create a new ordering between two resource actions
*
* \param[in] lh_rsc Resource for 'first' action
* \param[in] rh_rsc Resource for 'then' action
* \param[in] lh_task Action key for 'first' action
* \param[in] rh_task Action key for 'then' action
* \param[in] flags Bitmask of enum pe_ordering flags
* \param[in] data_set Cluster working set to add ordering to
*/
#define pcmk__order_resource_actions(lh_rsc, lh_task, rh_rsc, rh_task, \
flags, data_set) \
pcmk__new_ordering((lh_rsc), pcmk__op_key((lh_rsc)->id, (lh_task), 0), \
NULL, \
(rh_rsc), pcmk__op_key((rh_rsc)->id, (rh_task), 0), \
NULL, (flags), (data_set))
#define pcmk__order_starts(rsc1, rsc2, type, data_set) \
pcmk__order_resource_actions((rsc1), CRMD_ACTION_START, \
(rsc2), CRMD_ACTION_START, (type), (data_set))
#define pcmk__order_stops(rsc1, rsc2, type, data_set) \
pcmk__order_resource_actions((rsc1), CRMD_ACTION_STOP, \
(rsc2), CRMD_ACTION_STOP, (type), (data_set))
G_GNUC_INTERNAL
void pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__is_failed_remote_node(pe_node_t *node);
G_GNUC_INTERNAL
void pcmk__order_remote_connection_actions(pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__rsc_corresponds_to_guest(pe_resource_t *rsc, pe_node_t *node);
G_GNUC_INTERNAL
pe_node_t *pcmk__connection_host_for_action(pe_action_t *action);
G_GNUC_INTERNAL
void pcmk__substitute_remote_addr(pe_resource_t *rsc, GHashTable *params,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__add_bundle_meta_to_xml(xmlNode *args_xml, pe_action_t *action);
// Groups (pcmk_sched_group.c)
G_GNUC_INTERNAL
GList *pcmk__group_colocated_resources(pe_resource_t *rsc,
pe_resource_t *orig_rsc,
GList *colocated_rscs);
// Bundles (pcmk_sched_bundle.c)
G_GNUC_INTERNAL
void pcmk__output_bundle_actions(pe_resource_t *rsc);
+// Injections (pcmk_sched_transition.c)
+
+G_GNUC_INTERNAL
+xmlNode *pcmk__inject_node(cib_t *cib_conn, const char *node, const char *uuid);
+
+G_GNUC_INTERNAL
+xmlNode *pcmk__inject_node_state_change(cib_t *cib_conn, const char *node,
+ bool up);
+
+G_GNUC_INTERNAL
+xmlNode *pcmk__inject_resource_history(pcmk__output_t *out, xmlNode *cib_node,
+ const char *resource,
+ const char *lrm_name,
+ const char *rclass,
+ const char *rtype,
+ const char *rprovider);
+
+G_GNUC_INTERNAL
+void pcmk__inject_failcount(pcmk__output_t *out, xmlNode *cib_node,
+ const char *resource, const char *task,
+ guint interval_ms, int rc);
+
+G_GNUC_INTERNAL
+xmlNode *pcmk__inject_action_result(xmlNode *cib_resource,
+ lrmd_event_data_t *op, int target_rc);
+
+
// Functions applying to more than one variant (pcmk_sched_resource.c)
G_GNUC_INTERNAL
GList *pcmk__colocated_resources(pe_resource_t *rsc, pe_resource_t *orig_rsc,
GList *colocated_rscs);
G_GNUC_INTERNAL
void pcmk__output_resource_actions(pe_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__assign_primitive(pe_resource_t *rsc, pe_node_t *chosen, bool force);
G_GNUC_INTERNAL
bool pcmk__assign_resource(pe_resource_t *rsc, pe_node_t *node, bool force);
G_GNUC_INTERNAL
void pcmk__unassign_resource(pe_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__threshold_reached(pe_resource_t *rsc, pe_node_t *node,
pe_working_set_t *data_set,
pe_resource_t **failed);
// Functions related to probes (pcmk_sched_probes.c)
G_GNUC_INTERNAL
void pcmk__order_probes(pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__schedule_probes(pe_working_set_t *data_set);
#endif // PCMK__LIBPACEMAKER_PRIVATE__H
diff --git a/lib/pacemaker/pcmk_sched_transition.c b/lib/pacemaker/pcmk_sched_transition.c
index 84c9a2783d..6d8f499402 100644
--- a/lib/pacemaker/pcmk_sched_transition.c
+++ b/lib/pacemaker/pcmk_sched_transition.c
@@ -1,850 +1,665 @@
/*
* Copyright 2009-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/types.h>
#include <dirent.h>
#include <crm/crm.h>
#include <crm/lrmd.h> // lrmd_event_data_t, lrmd_free_event()
#include <crm/cib.h>
#include <crm/common/util.h>
#include <crm/common/iso8601.h>
#include <crm/common/xml_internal.h>
#include <crm/lrmd_internal.h>
#include <crm/pengine/status.h>
#include <pacemaker-internal.h>
-static pcmk__output_t *out = NULL;
-static cib_t *fake_cib = NULL;
-static GList *fake_resource_list = NULL;
-static GList *fake_op_fail_list = NULL;
+#include "libpacemaker_private.h"
+
gboolean bringing_nodes_online = FALSE;
#define STATUS_PATH_MAX 512
#define NEW_NODE_TEMPLATE "//"XML_CIB_TAG_NODE"[@uname='%s']"
#define NODE_TEMPLATE "//"XML_CIB_TAG_STATE"[@uname='%s']"
#define RSC_TEMPLATE "//"XML_CIB_TAG_STATE"[@uname='%s']//"XML_LRM_TAG_RESOURCE"[@id='%s']"
static void
-inject_transient_attr(xmlNode * cib_node, const char *name, const char *value)
+inject_transient_attr(pcmk__output_t *out, xmlNode *cib_node,
+ const char *name, const char *value)
{
xmlNode *attrs = NULL;
xmlNode *instance_attrs = NULL;
const char *node_uuid = ID(cib_node);
out->message(out, "inject-attr", name, value, cib_node);
attrs = first_named_child(cib_node, XML_TAG_TRANSIENT_NODEATTRS);
if (attrs == NULL) {
attrs = create_xml_node(cib_node, XML_TAG_TRANSIENT_NODEATTRS);
crm_xml_add(attrs, XML_ATTR_ID, node_uuid);
}
instance_attrs = first_named_child(attrs, XML_TAG_ATTR_SETS);
if (instance_attrs == NULL) {
instance_attrs = create_xml_node(attrs, XML_TAG_ATTR_SETS);
crm_xml_add(instance_attrs, XML_ATTR_ID, node_uuid);
}
crm_create_nvpair_xml(instance_attrs, NULL, name, value);
}
-static void
-update_failcounts(xmlNode * cib_node, const char *resource, const char *task,
- guint interval_ms, int rc)
+/*!
+ * \internal
+ * \brief Inject a fictitious fail count into a scheduler input
+ *
+ * \param[in] out Output object for displaying error messages
+ * \param[in] cib_node Node state XML to inject into
+ * \param[in] resource ID of resource for fail count to inject
+ * \param[in] task Action name for fail count to inject
+ * \param[in] interval_ms Action interval (in milliseconds) for fail count
+ * \param[in] rc Action result for fail count to inject (if 0, or 7
+ * when interval_ms is 0, nothing will be injected)
+ */
+void
+pcmk__inject_failcount(pcmk__output_t *out, xmlNode *cib_node,
+ const char *resource, const char *task,
+ guint interval_ms, int rc)
{
if (rc == 0) {
return;
} else if ((rc == 7) && (interval_ms == 0)) {
return;
} else {
char *name = NULL;
char *now = pcmk__ttoa(time(NULL));
name = pcmk__failcount_name(resource, task, interval_ms);
- inject_transient_attr(cib_node, name, "value++");
+ inject_transient_attr(out, cib_node, name, "value++");
free(name);
name = pcmk__lastfailure_name(resource, task, interval_ms);
- inject_transient_attr(cib_node, name, now);
+ inject_transient_attr(out, cib_node, name, now);
free(name);
+
free(now);
}
}
static void
create_node_entry(cib_t * cib_conn, const char *node)
{
int rc = pcmk_ok;
char *xpath = crm_strdup_printf(NEW_NODE_TEMPLATE, node);
rc = cib_conn->cmds->query(cib_conn, xpath, NULL, cib_xpath | cib_sync_call | cib_scope_local);
if (rc == -ENXIO) {
xmlNode *cib_object = create_xml_node(NULL, XML_CIB_TAG_NODE);
crm_xml_add(cib_object, XML_ATTR_ID, node); // Use node name as ID
crm_xml_add(cib_object, XML_ATTR_UNAME, node);
cib_conn->cmds->create(cib_conn, XML_CIB_TAG_NODES, cib_object,
cib_sync_call | cib_scope_local);
/* Not bothering with subsequent query to see if it exists,
we'll bomb out later in the call to query_node_uuid()... */
free_xml(cib_object);
}
free(xpath);
}
static lrmd_event_data_t *
create_op(xmlNode *cib_resource, const char *task, guint interval_ms,
int outcome)
{
lrmd_event_data_t *op = NULL;
xmlNode *xop = NULL;
op = lrmd_new_event(ID(cib_resource), task, interval_ms);
lrmd__set_result(op, outcome, PCMK_EXEC_DONE, "Simulated action result");
op->params = NULL; /* TODO: Fill me in */
op->t_run = (unsigned int) time(NULL);
op->t_rcchange = op->t_run;
op->call_id = 0;
for (xop = pcmk__xe_first_child(cib_resource); xop != NULL;
xop = pcmk__xe_next(xop)) {
int tmp = 0;
crm_element_value_int(xop, XML_LRM_ATTR_CALLID, &tmp);
if (tmp > op->call_id) {
op->call_id = tmp;
}
}
op->call_id++;
return op;
}
-static xmlNode *
-inject_op(xmlNode * cib_resource, lrmd_event_data_t * op, int target_rc)
+/*!
+ * \internal
+ * \brief Inject a fictitious resource history entry into a scheduler input
+ *
+ * \param[in] cib_resource Resource history XML to inject entry into
+ * \param[in] op Action result to inject
+ * \param[in] target_rc Expected result for action to inject
+ *
+ * \return XML of injected resource history entry
+ */
+xmlNode *
+pcmk__inject_action_result(xmlNode *cib_resource, lrmd_event_data_t *op,
+ int target_rc)
{
return pcmk__create_history_xml(cib_resource, op, CRM_FEATURE_SET,
target_rc, NULL, crm_system_name,
LOG_TRACE);
}
-static xmlNode *
-inject_node_state(cib_t * cib_conn, const char *node, const char *uuid)
+/*!
+ * \internal
+ * \brief Inject a fictitious node into a scheduler input
+ *
+ * \param[in] cib_conn Scheduler input CIB to inject node into
+ * \param[in] node Name of node to inject
+ * \param[in] uuid UUID of node to inject
+ *
+ * \return XML of node_state entry for new node
+ * \note If the global bringing_nodes_online has been set to true, a node entry
+ * in the configuration section will be added, as well as a node state
+ * entry in the status section.
+ */
+xmlNode *
+pcmk__inject_node(cib_t *cib_conn, const char *node, const char *uuid)
{
int rc = pcmk_ok;
xmlNode *cib_object = NULL;
char *xpath = crm_strdup_printf(NODE_TEMPLATE, node);
if (bringing_nodes_online) {
create_node_entry(cib_conn, node);
}
rc = cib_conn->cmds->query(cib_conn, xpath, &cib_object,
cib_xpath | cib_sync_call | cib_scope_local);
- if (cib_object && ID(cib_object) == NULL) {
- crm_err("Detected multiple node_state entries for xpath=%s, bailing", xpath);
+ if ((cib_object != NULL) && (ID(cib_object) == NULL)) {
+ crm_err("Detected multiple node_state entries for xpath=%s, bailing",
+ xpath);
crm_log_xml_warn(cib_object, "Duplicates");
free(xpath);
crm_exit(CRM_EX_SOFTWARE);
return NULL; // not reached, but makes static analysis happy
}
if (rc == -ENXIO) {
char *found_uuid = NULL;
if (uuid == NULL) {
query_node_uuid(cib_conn, node, &found_uuid, NULL);
} else {
found_uuid = strdup(uuid);
}
cib_object = create_xml_node(NULL, XML_CIB_TAG_STATE);
crm_xml_add(cib_object, XML_ATTR_UUID, found_uuid);
crm_xml_add(cib_object, XML_ATTR_UNAME, node);
cib_conn->cmds->create(cib_conn, XML_CIB_TAG_STATUS, cib_object,
cib_sync_call | cib_scope_local);
free_xml(cib_object);
free(found_uuid);
rc = cib_conn->cmds->query(cib_conn, xpath, &cib_object,
cib_xpath | cib_sync_call | cib_scope_local);
- crm_trace("injecting node state for %s. rc is %d", node, rc);
+ crm_trace("Injecting node state for %s (rc=%d)", node, rc);
}
free(xpath);
CRM_ASSERT(rc == pcmk_ok);
return cib_object;
}
-static xmlNode *
-modify_node(cib_t * cib_conn, char *node, gboolean up)
+/*!
+ * \internal
+ * \brief Inject a fictitious node state change into a scheduler input
+ *
+ * \param[in] cib_conn Scheduler input CIB to inject into
+ * \param[in] node Name of node to inject change for
+ * \param[in] up If true, change state to online, otherwise offline
+ *
+ * \return XML of changed (or added) node state entry
+ */
+xmlNode *
+pcmk__inject_node_state_change(cib_t *cib_conn, const char *node, bool up)
{
- xmlNode *cib_node = inject_node_state(cib_conn, node, NULL);
+ xmlNode *cib_node = pcmk__inject_node(cib_conn, node, NULL);
if (up) {
- crm_xml_add(cib_node, XML_NODE_IN_CLUSTER, XML_BOOLEAN_YES);
- crm_xml_add(cib_node, XML_NODE_IS_PEER, ONLINESTATUS);
- crm_xml_add(cib_node, XML_NODE_JOIN_STATE, CRMD_JOINSTATE_MEMBER);
- crm_xml_add(cib_node, XML_NODE_EXPECTED, CRMD_JOINSTATE_MEMBER);
-
+ pcmk__xe_set_props(cib_node,
+ XML_NODE_IN_CLUSTER, XML_BOOLEAN_YES,
+ XML_NODE_IS_PEER, ONLINESTATUS,
+ XML_NODE_JOIN_STATE, CRMD_JOINSTATE_MEMBER,
+ XML_NODE_EXPECTED, CRMD_JOINSTATE_MEMBER,
+ NULL);
} else {
- crm_xml_add(cib_node, XML_NODE_IN_CLUSTER, XML_BOOLEAN_NO);
- crm_xml_add(cib_node, XML_NODE_IS_PEER, OFFLINESTATUS);
- crm_xml_add(cib_node, XML_NODE_JOIN_STATE, CRMD_JOINSTATE_DOWN);
- crm_xml_add(cib_node, XML_NODE_EXPECTED, CRMD_JOINSTATE_DOWN);
+ pcmk__xe_set_props(cib_node,
+ XML_NODE_IN_CLUSTER, XML_BOOLEAN_NO,
+ XML_NODE_IS_PEER, OFFLINESTATUS,
+ XML_NODE_JOIN_STATE, CRMD_JOINSTATE_DOWN,
+ XML_NODE_EXPECTED, CRMD_JOINSTATE_DOWN,
+ NULL);
}
-
crm_xml_add(cib_node, XML_ATTR_ORIGIN, crm_system_name);
return cib_node;
}
static xmlNode *
find_resource_xml(xmlNode * cib_node, const char *resource)
{
xmlNode *match = NULL;
const char *node = crm_element_value(cib_node, XML_ATTR_UNAME);
char *xpath = crm_strdup_printf(RSC_TEMPLATE, node, resource);
match = get_xpath_object(xpath, cib_node, LOG_TRACE);
free(xpath);
return match;
}
-
-static xmlNode *
-inject_resource(xmlNode * cib_node, const char *resource, const char *lrm_name,
- const char *rclass, const char *rtype, const char *rprovider)
+/*!
+ * \internal
+ * \brief Inject a resource history element into a scheduler input
+ *
+ * \param[in] out Output object for displaying error messages
+ * \param[in] cib_node Node state XML to inject resource history entry into
+ * \param[in] resource ID (in configuration) of resource to inject
+ * \param[in] lrm_name ID of resource as used in history (e.g. clone instance)
+ * \param[in] rclass Resource agent class of resource to inject
+ * \param[in] rtype Resource agent type of resource to inject
+ * \param[in] rprovider Resource agent provider of resource to inject
+ *
+ * \return XML of injected resource history element
+ * \note If a history element already exists under either \p resource or
+ * \p lrm_name, this will return it rather than injecting a new one.
+ */
+xmlNode *
+pcmk__inject_resource_history(pcmk__output_t *out, xmlNode *cib_node,
+ const char *resource, const char *lrm_name,
+ const char *rclass, const char *rtype,
+ const char *rprovider)
{
xmlNode *lrm = NULL;
xmlNode *container = NULL;
xmlNode *cib_resource = NULL;
- char *xpath = NULL;
cib_resource = find_resource_xml(cib_node, resource);
if (cib_resource != NULL) {
/* If an existing LRM history entry uses the resource name,
* continue using it, even if lrm_name is different.
*/
return cib_resource;
}
// Check for history entry under preferred name
- if (strcmp(resource, lrm_name)) {
+ if (strcmp(resource, lrm_name) != 0) {
cib_resource = find_resource_xml(cib_node, lrm_name);
if (cib_resource != NULL) {
return cib_resource;
}
}
- /* One day, add query for class, provider, type */
-
- if (rclass == NULL || rtype == NULL) {
+ if ((rclass == NULL) || (rtype == NULL)) {
+ // @TODO query configuration for class, provider, type
out->err(out, "Resource %s not found in the status section of %s."
" Please supply the class and type to continue", resource, ID(cib_node));
return NULL;
- } else if (!pcmk__strcase_any_of(rclass, PCMK_RESOURCE_CLASS_OCF, PCMK_RESOURCE_CLASS_STONITH,
- PCMK_RESOURCE_CLASS_SERVICE, PCMK_RESOURCE_CLASS_UPSTART,
- PCMK_RESOURCE_CLASS_SYSTEMD, PCMK_RESOURCE_CLASS_LSB, NULL)) {
+ } else if (!pcmk__strcase_any_of(rclass,
+ PCMK_RESOURCE_CLASS_OCF,
+ PCMK_RESOURCE_CLASS_STONITH,
+ PCMK_RESOURCE_CLASS_SERVICE,
+ PCMK_RESOURCE_CLASS_UPSTART,
+ PCMK_RESOURCE_CLASS_SYSTEMD,
+ PCMK_RESOURCE_CLASS_LSB, NULL)) {
out->err(out, "Invalid class for %s: %s", resource, rclass);
return NULL;
} else if (pcmk_is_set(pcmk_get_ra_caps(rclass), pcmk_ra_cap_provider)
- && (rprovider == NULL)) {
+ && (rprovider == NULL)) {
+ // @TODO query configuration for provider
out->err(out, "Please specify the provider for resource %s", resource);
return NULL;
}
- xpath = (char *)xmlGetNodePath(cib_node);
- crm_info("Injecting new resource %s into %s '%s'", lrm_name, xpath, ID(cib_node));
- free(xpath);
+ crm_info("Injecting new resource %s into node state '%s'",
+ lrm_name, ID(cib_node));
lrm = first_named_child(cib_node, XML_CIB_TAG_LRM);
if (lrm == NULL) {
const char *node_uuid = ID(cib_node);
lrm = create_xml_node(cib_node, XML_CIB_TAG_LRM);
crm_xml_add(lrm, XML_ATTR_ID, node_uuid);
}
container = first_named_child(lrm, XML_LRM_TAG_RESOURCES);
if (container == NULL) {
container = create_xml_node(lrm, XML_LRM_TAG_RESOURCES);
}
cib_resource = create_xml_node(container, XML_LRM_TAG_RESOURCE);
// If we're creating a new entry, use the preferred name
crm_xml_add(cib_resource, XML_ATTR_ID, lrm_name);
crm_xml_add(cib_resource, XML_AGENT_ATTR_CLASS, rclass);
crm_xml_add(cib_resource, XML_AGENT_ATTR_PROVIDER, rprovider);
crm_xml_add(cib_resource, XML_ATTR_TYPE, rtype);
return cib_resource;
}
#define XPATH_MAX 1024
static int
-find_ticket_state(cib_t * the_cib, const char *ticket_id, xmlNode ** ticket_state_xml)
+find_ticket_state(pcmk__output_t *out, cib_t *the_cib, const char *ticket_id,
+ xmlNode **ticket_state_xml)
{
int offset = 0;
int rc = pcmk_ok;
xmlNode *xml_search = NULL;
char *xpath_string = NULL;
CRM_ASSERT(ticket_state_xml != NULL);
*ticket_state_xml = NULL;
xpath_string = calloc(1, XPATH_MAX);
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "%s", "/cib/status/tickets");
if (ticket_id) {
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "/%s[@id=\"%s\"]",
XML_CIB_TAG_TICKET_STATE, ticket_id);
}
CRM_LOG_ASSERT(offset > 0);
rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search,
cib_sync_call | cib_scope_local | cib_xpath);
if (rc != pcmk_ok) {
goto bail;
}
crm_log_xml_debug(xml_search, "Match");
if (xml_has_children(xml_search)) {
if (ticket_id) {
out->err(out, "Multiple ticket_states match ticket_id=%s", ticket_id);
}
*ticket_state_xml = xml_search;
} else {
*ticket_state_xml = xml_search;
}
bail:
free(xpath_string);
return rc;
}
static int
-set_ticket_state_attr(const char *ticket_id, const char *attr_name,
- const char *attr_value, cib_t * cib, int cib_options)
+set_ticket_state_attr(pcmk__output_t *out, const char *ticket_id,
+ const char *attr_name, const char *attr_value,
+ cib_t *cib, int cib_options)
{
int rc = pcmk_ok;
xmlNode *xml_top = NULL;
xmlNode *ticket_state_xml = NULL;
- rc = find_ticket_state(cib, ticket_id, &ticket_state_xml);
+ rc = find_ticket_state(out, cib, ticket_id, &ticket_state_xml);
if (rc == pcmk_ok) {
crm_debug("Found a match state for ticket: id=%s", ticket_id);
xml_top = ticket_state_xml;
} else if (rc != -ENXIO) {
return rc;
} else {
xmlNode *xml_obj = NULL;
xml_top = create_xml_node(NULL, XML_CIB_TAG_STATUS);
xml_obj = create_xml_node(xml_top, XML_CIB_TAG_TICKETS);
ticket_state_xml = create_xml_node(xml_obj, XML_CIB_TAG_TICKET_STATE);
crm_xml_add(ticket_state_xml, XML_ATTR_ID, ticket_id);
}
crm_xml_add(ticket_state_xml, attr_name, attr_value);
crm_log_xml_debug(xml_top, "Update");
rc = cib->cmds->modify(cib, XML_CIB_TAG_STATUS, xml_top, cib_options);
free_xml(xml_top);
return rc;
}
void
modify_configuration(pe_working_set_t * data_set, cib_t *cib, pcmk_injections_t *injections)
{
int rc = pcmk_ok;
GList *gIter = NULL;
xmlNode *cib_op = NULL;
xmlNode *cib_node = NULL;
xmlNode *cib_resource = NULL;
lrmd_event_data_t *op = NULL;
- out = data_set->priv;
+ pcmk__output_t *out = data_set->priv;
out->message(out, "inject-modify-config", injections->quorum, injections->watchdog);
if (injections->quorum) {
xmlNode *top = create_xml_node(NULL, XML_TAG_CIB);
/* crm_xml_add(top, XML_ATTR_DC_UUID, dc_uuid); */
crm_xml_add(top, XML_ATTR_HAVE_QUORUM, injections->quorum);
rc = cib->cmds->modify(cib, NULL, top, cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == pcmk_ok);
}
if (injections->watchdog) {
rc = update_attr_delegate(cib, cib_sync_call | cib_scope_local,
XML_CIB_TAG_CRMCONFIG, NULL, NULL, NULL, NULL,
XML_ATTR_HAVE_WATCHDOG, injections->watchdog, FALSE, NULL, NULL);
CRM_ASSERT(rc == pcmk_ok);
}
for (gIter = injections->node_up; gIter != NULL; gIter = gIter->next) {
char *node = (char *)gIter->data;
out->message(out, "inject-modify-node", "Online", node);
- cib_node = modify_node(cib, node, TRUE);
+ cib_node = pcmk__inject_node_state_change(cib, node, true);
CRM_ASSERT(cib_node != NULL);
rc = cib->cmds->modify(cib, XML_CIB_TAG_STATUS, cib_node,
cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == pcmk_ok);
free_xml(cib_node);
}
for (gIter = injections->node_down; gIter != NULL; gIter = gIter->next) {
char xpath[STATUS_PATH_MAX];
char *node = (char *)gIter->data;
out->message(out, "inject-modify-node", "Offline", node);
- cib_node = modify_node(cib, node, FALSE);
+ cib_node = pcmk__inject_node_state_change(cib, node, false);
CRM_ASSERT(cib_node != NULL);
rc = cib->cmds->modify(cib, XML_CIB_TAG_STATUS, cib_node,
cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == pcmk_ok);
free_xml(cib_node);
snprintf(xpath, STATUS_PATH_MAX, "//node_state[@uname='%s']/%s", node, XML_CIB_TAG_LRM);
cib->cmds->remove(cib, xpath, NULL,
cib_xpath | cib_sync_call | cib_scope_local);
snprintf(xpath, STATUS_PATH_MAX, "//node_state[@uname='%s']/%s", node,
XML_TAG_TRANSIENT_NODEATTRS);
cib->cmds->remove(cib, xpath, NULL,
cib_xpath | cib_sync_call | cib_scope_local);
}
for (gIter = injections->node_fail; gIter != NULL; gIter = gIter->next) {
char *node = (char *)gIter->data;
out->message(out, "inject-modify-node", "Failing", node);
- cib_node = modify_node(cib, node, TRUE);
+ cib_node = pcmk__inject_node_state_change(cib, node, true);
crm_xml_add(cib_node, XML_NODE_IN_CLUSTER, XML_BOOLEAN_NO);
CRM_ASSERT(cib_node != NULL);
rc = cib->cmds->modify(cib, XML_CIB_TAG_STATUS, cib_node,
cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == pcmk_ok);
free_xml(cib_node);
}
for (gIter = injections->ticket_grant; gIter != NULL; gIter = gIter->next) {
char *ticket_id = (char *)gIter->data;
out->message(out, "inject-modify-ticket", "Granting", ticket_id);
- rc = set_ticket_state_attr(ticket_id, "granted", "true",
+ rc = set_ticket_state_attr(out, ticket_id, "granted", "true",
cib, cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == pcmk_ok);
}
for (gIter = injections->ticket_revoke; gIter != NULL; gIter = gIter->next) {
char *ticket_id = (char *)gIter->data;
out->message(out, "inject-modify-ticket", "Revoking", ticket_id);
- rc = set_ticket_state_attr(ticket_id, "granted", "false",
+ rc = set_ticket_state_attr(out, ticket_id, "granted", "false",
cib, cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == pcmk_ok);
}
for (gIter = injections->ticket_standby; gIter != NULL; gIter = gIter->next) {
char *ticket_id = (char *)gIter->data;
out->message(out, "inject-modify-ticket", "Standby", ticket_id);
- rc = set_ticket_state_attr(ticket_id, "standby", "true",
+ rc = set_ticket_state_attr(out, ticket_id, "standby", "true",
cib, cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == pcmk_ok);
}
for (gIter = injections->ticket_activate; gIter != NULL; gIter = gIter->next) {
char *ticket_id = (char *)gIter->data;
out->message(out, "inject-modify-ticket", "Activating", ticket_id);
- rc = set_ticket_state_attr(ticket_id, "standby", "false",
+ rc = set_ticket_state_attr(out, ticket_id, "standby", "false",
cib, cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == pcmk_ok);
}
for (gIter = injections->op_inject; gIter != NULL; gIter = gIter->next) {
char *spec = (char *)gIter->data;
int rc = 0;
int outcome = PCMK_OCF_OK;
guint interval_ms = 0;
char *key = NULL;
char *node = NULL;
char *task = NULL;
char *resource = NULL;
const char *rtype = NULL;
const char *rclass = NULL;
const char *rprovider = NULL;
pe_resource_t *rsc = NULL;
out->message(out, "inject-spec", spec);
key = calloc(1, strlen(spec) + 1);
node = calloc(1, strlen(spec) + 1);
rc = sscanf(spec, "%[^@]@%[^=]=%d", key, node, &outcome);
if (rc != 3) {
out->err(out, "Invalid operation spec: %s. Only found %d fields", spec, rc);
free(key);
free(node);
continue;
}
parse_op_key(key, &resource, &task, &interval_ms);
rsc = pe_find_resource(data_set->resources, resource);
if (rsc == NULL) {
out->err(out, "Invalid resource name: %s", resource);
} else {
rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
rtype = crm_element_value(rsc->xml, XML_ATTR_TYPE);
rprovider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER);
- cib_node = inject_node_state(cib, node, NULL);
+ cib_node = pcmk__inject_node(cib, node, NULL);
CRM_ASSERT(cib_node != NULL);
- update_failcounts(cib_node, resource, task, interval_ms, outcome);
+ pcmk__inject_failcount(out, cib_node, resource, task, interval_ms,
+ outcome);
- cib_resource = inject_resource(cib_node, resource, resource,
- rclass, rtype, rprovider);
+ cib_resource = pcmk__inject_resource_history(out, cib_node,
+ resource, resource,
+ rclass, rtype,
+ rprovider);
CRM_ASSERT(cib_resource != NULL);
op = create_op(cib_resource, task, interval_ms, outcome);
CRM_ASSERT(op != NULL);
- cib_op = inject_op(cib_resource, op, 0);
+ cib_op = pcmk__inject_action_result(cib_resource, op, 0);
CRM_ASSERT(cib_op != NULL);
lrmd_free_event(op);
rc = cib->cmds->modify(cib, XML_CIB_TAG_STATUS, cib_node,
cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == pcmk_ok);
}
free(task);
free(node);
free(key);
}
if (!out->is_quiet(out)) {
out->end_list(out);
}
}
-
-static gboolean
-exec_pseudo_action(crm_graph_t * graph, crm_action_t * action)
-{
- const char *node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
- const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK_KEY);
-
- crm__set_graph_action_flags(action, pcmk__graph_action_confirmed);
- out->message(out, "inject-pseudo-action", node, task);
-
- pcmk__update_graph(graph, action);
- return TRUE;
-}
-
-static gboolean
-exec_rsc_action(crm_graph_t * graph, crm_action_t * action)
-{
- int rc = 0;
- GList *gIter = NULL;
- lrmd_event_data_t *op = NULL;
- int target_outcome = PCMK_OCF_OK;
-
- const char *rtype = NULL;
- const char *rclass = NULL;
- const char *resource = NULL;
- const char *rprovider = NULL;
- const char *lrm_name = NULL;
- const char *operation = crm_element_value(action->xml, "operation");
- const char *target_rc_s = crm_meta_value(action->params, XML_ATTR_TE_TARGET_RC);
-
- xmlNode *cib_node = NULL;
- xmlNode *cib_resource = NULL;
- xmlNode *action_rsc = first_named_child(action->xml, XML_CIB_TAG_RESOURCE);
-
- char *node = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET);
- char *uuid = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET_UUID);
- const char *router_node = crm_element_value(action->xml, XML_LRM_ATTR_ROUTER_NODE);
-
- if (pcmk__str_eq(operation, CRM_OP_REPROBE, pcmk__str_none)) {
- crm_info("Skipping %s op for %s", operation, node);
- goto done;
- }
-
- if (action_rsc == NULL) {
- crm_log_xml_err(action->xml, "Bad");
- free(node); free(uuid);
- return FALSE;
- }
-
- /* Look for the preferred name
- * If not found, try the expected 'local' name
- * If not found use the preferred name anyway
- */
- resource = crm_element_value(action_rsc, XML_ATTR_ID);
- CRM_ASSERT(resource != NULL); // makes static analysis happy
- lrm_name = resource; // Preferred name when writing history
- if (pe_find_resource(fake_resource_list, resource) == NULL) {
- const char *longname = crm_element_value(action_rsc, XML_ATTR_ID_LONG);
-
- if (longname && pe_find_resource(fake_resource_list, longname)) {
- resource = longname;
- }
- }
-
- if (pcmk__strcase_any_of(operation, "delete", RSC_METADATA, NULL)) {
- out->message(out, "inject-rsc-action", resource, operation, node, (guint) 0);
- goto done;
- }
-
- rclass = crm_element_value(action_rsc, XML_AGENT_ATTR_CLASS);
- rtype = crm_element_value(action_rsc, XML_ATTR_TYPE);
- rprovider = crm_element_value(action_rsc, XML_AGENT_ATTR_PROVIDER);
-
- pcmk__scan_min_int(target_rc_s, &target_outcome, 0);
-
- CRM_ASSERT(fake_cib->cmds->query(fake_cib, NULL, NULL, cib_sync_call | cib_scope_local) ==
- pcmk_ok);
-
- cib_node = inject_node_state(fake_cib, node, (router_node? node : uuid));
- CRM_ASSERT(cib_node != NULL);
-
- cib_resource = inject_resource(cib_node, resource, lrm_name,
- rclass, rtype, rprovider);
- if (cib_resource == NULL) {
- crm_err("invalid resource in transition");
- free(node); free(uuid);
- free_xml(cib_node);
- return FALSE;
- }
-
- op = pcmk__event_from_graph_action(cib_resource, action, PCMK_EXEC_DONE,
- target_outcome, "User-injected result");
-
- out->message(out, "inject-rsc-action", resource, op->op_type, node, op->interval_ms);
-
- for (gIter = fake_op_fail_list; gIter != NULL; gIter = gIter->next) {
- char *spec = (char *)gIter->data;
- char *key = NULL;
- const char *match_name = NULL;
-
- // Allow user to specify anonymous clone with or without instance number
- key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource, op->op_type,
- op->interval_ms, node);
- if (strncasecmp(key, spec, strlen(key)) == 0) {
- match_name = resource;
- }
- free(key);
-
- if ((match_name == NULL) && strcmp(resource, lrm_name)) {
- key = crm_strdup_printf(PCMK__OP_FMT "@%s=", lrm_name, op->op_type,
- op->interval_ms, node);
- if (strncasecmp(key, spec, strlen(key)) == 0) {
- match_name = lrm_name;
- }
- free(key);
- }
-
- if (match_name != NULL) {
-
- rc = sscanf(spec, "%*[^=]=%d", (int *) &op->rc);
- // ${match_name}_${task}_${interval_in_ms}@${node}=${rc}
-
- if (rc != 1) {
- out->err(out,
- "Invalid failed operation spec: %s. Result code must be integer",
- spec);
- continue;
- }
- crm__set_graph_action_flags(action, pcmk__graph_action_failed);
- graph->abort_priority = INFINITY;
- out->info(out, "Pretending action %d failed with rc=%d", action->id, op->rc);
- update_failcounts(cib_node, match_name, op->op_type,
- op->interval_ms, op->rc);
- break;
- }
- }
-
- inject_op(cib_resource, op, target_outcome);
- lrmd_free_event(op);
-
- rc = fake_cib->cmds->modify(fake_cib, XML_CIB_TAG_STATUS, cib_node,
- cib_sync_call | cib_scope_local);
- CRM_ASSERT(rc == pcmk_ok);
-
- done:
- free(node); free(uuid);
- free_xml(cib_node);
- crm__set_graph_action_flags(action, pcmk__graph_action_confirmed);
- pcmk__update_graph(graph, action);
- return TRUE;
-}
-
-static gboolean
-exec_crmd_action(crm_graph_t * graph, crm_action_t * action)
-{
- const char *node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
- const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK);
- xmlNode *rsc = first_named_child(action->xml, XML_CIB_TAG_RESOURCE);
-
- crm__set_graph_action_flags(action, pcmk__graph_action_confirmed);
- out->message(out, "inject-cluster-action", node, task, rsc);
- pcmk__update_graph(graph, action);
- return TRUE;
-}
-
-static gboolean
-exec_stonith_action(crm_graph_t * graph, crm_action_t * action)
-{
- const char *op = crm_meta_value(action->params, "stonith_action");
- char *target = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET);
-
- out->message(out, "inject-fencing-action", target, op);
-
- if(!pcmk__str_eq(op, "on", pcmk__str_casei)) {
- int rc = 0;
- char xpath[STATUS_PATH_MAX];
- xmlNode *cib_node = modify_node(fake_cib, target, FALSE);
-
- crm_xml_add(cib_node, XML_ATTR_ORIGIN, __func__);
- CRM_ASSERT(cib_node != NULL);
-
- rc = fake_cib->cmds->replace(fake_cib, XML_CIB_TAG_STATUS, cib_node,
- cib_sync_call | cib_scope_local);
- CRM_ASSERT(rc == pcmk_ok);
-
- snprintf(xpath, STATUS_PATH_MAX, "//node_state[@uname='%s']/%s", target, XML_CIB_TAG_LRM);
- fake_cib->cmds->remove(fake_cib, xpath, NULL,
- cib_xpath | cib_sync_call | cib_scope_local);
-
- snprintf(xpath, STATUS_PATH_MAX, "//node_state[@uname='%s']/%s", target,
- XML_TAG_TRANSIENT_NODEATTRS);
- fake_cib->cmds->remove(fake_cib, xpath, NULL,
- cib_xpath | cib_sync_call | cib_scope_local);
-
- free_xml(cib_node);
- }
-
- crm__set_graph_action_flags(action, pcmk__graph_action_confirmed);
- pcmk__update_graph(graph, action);
- free(target);
- return TRUE;
-}
-
-enum transition_status
-run_simulation(pe_working_set_t * data_set, cib_t *cib, GList *op_fail_list)
-{
- crm_graph_t *transition = NULL;
- enum transition_status graph_rc;
-
- crm_graph_functions_t exec_fns = {
- exec_pseudo_action,
- exec_rsc_action,
- exec_crmd_action,
- exec_stonith_action,
- };
-
- out = data_set->priv;
-
- fake_cib = cib;
- fake_op_fail_list = op_fail_list;
-
- if (!out->is_quiet(out)) {
- out->begin_list(out, NULL, NULL, "Executing Cluster Transition");
- }
-
- pcmk__set_graph_functions(&exec_fns);
- transition = pcmk__unpack_graph(data_set->graph, crm_system_name);
- pcmk__log_graph(LOG_DEBUG, transition);
-
- fake_resource_list = data_set->resources;
- do {
- graph_rc = pcmk__execute_graph(transition);
-
- } while (graph_rc == transition_active);
- fake_resource_list = NULL;
-
- if (graph_rc != transition_complete) {
- out->err(out, "Transition failed: %s",
- pcmk__graph_status2text(graph_rc));
- pcmk__log_graph(LOG_ERR, transition);
- }
- pcmk__free_graph(transition);
- if (graph_rc != transition_complete) {
- out->err(out, "An invalid transition was produced");
- }
-
- if (!out->is_quiet(out)) {
- xmlNode *cib_object = NULL;
- int rc = fake_cib->cmds->query(fake_cib, NULL, &cib_object, cib_sync_call | cib_scope_local);
-
- CRM_ASSERT(rc == pcmk_ok);
- pe_reset_working_set(data_set);
- data_set->input = cib_object;
-
- out->end_list(out);
- }
-
- return graph_rc;
-}
diff --git a/lib/pacemaker/pcmk_simulate.c b/lib/pacemaker/pcmk_simulate.c
index d97134d15a..f144b8822a 100644
--- a/lib/pacemaker/pcmk_simulate.c
+++ b/lib/pacemaker/pcmk_simulate.c
@@ -1,560 +1,991 @@
/*
* Copyright 2021-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/cib/internal.h>
#include <crm/common/output.h>
#include <crm/common/results.h>
#include <crm/pengine/pe_types.h>
#include <pacemaker-internal.h>
#include <pacemaker.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "libpacemaker_private.h"
+#define STATUS_PATH_MAX 512
+
+static pcmk__output_t *out = NULL;
+static cib_t *fake_cib = NULL;
+static GList *fake_resource_list = NULL;
+static GList *fake_op_fail_list = NULL;
+
+static void set_effective_date(pe_working_set_t *data_set, bool print_original,
+ char *use_date);
+
+/*!
+ * \internal
+ * \brief Create an action name for use in a dot graph
+ *
+ * \param[in] action Action to create name for
+ * \param[in] verbose If true, add action ID to name
+ *
+ * \return Newly allocated string with action name
+ * \note It is the caller's responsibility to free the result.
+ */
static char *
create_action_name(pe_action_t *action, bool verbose)
{
char *action_name = NULL;
const char *prefix = "";
const char *action_host = NULL;
const char *clone_name = NULL;
const char *task = action->task;
- if (action->node) {
+ if (action->node != NULL) {
action_host = action->node->details->uname;
} else if (!pcmk_is_set(action->flags, pe_action_pseudo)) {
action_host = "<none>";
}
- if (pcmk__str_eq(action->task, RSC_CANCEL, pcmk__str_casei)) {
+ if (pcmk__str_eq(action->task, RSC_CANCEL, pcmk__str_none)) {
prefix = "Cancel ";
task = action->cancel_task;
}
- if (action->rsc && action->rsc->clone_name) {
+ if (action->rsc != NULL) {
clone_name = action->rsc->clone_name;
}
- if (clone_name) {
+ if (clone_name != NULL) {
char *key = NULL;
guint interval_ms = 0;
if (pcmk__guint_from_hash(action->meta,
XML_LRM_ATTR_INTERVAL_MS, 0,
&interval_ms) != pcmk_rc_ok) {
interval_ms = 0;
}
- if (pcmk__strcase_any_of(action->task, RSC_NOTIFY, RSC_NOTIFIED, NULL)) {
- const char *n_type = g_hash_table_lookup(action->meta, "notify_key_type");
- const char *n_task = g_hash_table_lookup(action->meta, "notify_key_operation");
+ if (pcmk__strcase_any_of(action->task, RSC_NOTIFY, RSC_NOTIFIED,
+ NULL)) {
+ const char *n_type = g_hash_table_lookup(action->meta,
+ "notify_key_type");
+ const char *n_task = g_hash_table_lookup(action->meta,
+ "notify_key_operation");
CRM_ASSERT(n_type != NULL);
CRM_ASSERT(n_task != NULL);
key = pcmk__notify_key(clone_name, n_type, n_task);
-
} else {
key = pcmk__op_key(clone_name, task, interval_ms);
}
- if (action_host) {
- action_name = crm_strdup_printf("%s%s %s", prefix, key, action_host);
+ if (action_host != NULL) {
+ action_name = crm_strdup_printf("%s%s %s",
+ prefix, key, action_host);
} else {
action_name = crm_strdup_printf("%s%s", prefix, key);
}
free(key);
} else if (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei)) {
const char *op = g_hash_table_lookup(action->meta, "stonith_action");
- action_name = crm_strdup_printf("%s%s '%s' %s", prefix, action->task, op, action_host);
+ action_name = crm_strdup_printf("%s%s '%s' %s",
+ prefix, action->task, op, action_host);
} else if (action->rsc && action_host) {
- action_name = crm_strdup_printf("%s%s %s", prefix, action->uuid, action_host);
+ action_name = crm_strdup_printf("%s%s %s",
+ prefix, action->uuid, action_host);
} else if (action_host) {
- action_name = crm_strdup_printf("%s%s %s", prefix, action->task, action_host);
+ action_name = crm_strdup_printf("%s%s %s",
+ prefix, action->task, action_host);
} else {
action_name = crm_strdup_printf("%s", action->uuid);
}
if (verbose) {
char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id);
free(action_name);
action_name = with_id;
}
return action_name;
}
+/*!
+ * \internal
+ * \brief Display the status of a cluster
+ *
+ * \param[in] data_set Cluster working set
+ * \param[in] show_opts How to modify display (as pcmk_show_opt_e flags)
+ * \param[in] section_opts Sections to display (as pcmk_section_e flags)
+ * \param[in] title What to use as list title
+ * \param[in] print_spacer Whether to display a spacer first
+ */
static void
-print_cluster_status(pe_working_set_t * data_set, unsigned int show_opts,
- unsigned int section_opts, const char *title, bool print_spacer)
+print_cluster_status(pe_working_set_t *data_set, unsigned int show_opts,
+ unsigned int section_opts, const char *title,
+ bool print_spacer)
{
pcmk__output_t *out = data_set->priv;
GList *all = NULL;
section_opts |= pcmk_section_nodes | pcmk_section_resources;
all = g_list_prepend(all, (gpointer) "*");
PCMK__OUTPUT_SPACER_IF(out, print_spacer);
out->begin_list(out, NULL, NULL, "%s", title);
out->message(out, "cluster-status", data_set, 0, NULL, FALSE,
section_opts,
show_opts | pcmk_show_inactive_rscs | pcmk_show_failed_detail,
NULL, all, all);
out->end_list(out);
g_list_free(all);
}
+/*!
+ * \internal
+ * \brief Display a summary of all actions scheduled in a transition
+ *
+ * \param[in] data_set Cluster working set (fully scheduled)
+ * \param[in] print_spacer Whether to display a spacer first
+ */
static void
print_transition_summary(pe_working_set_t *data_set, bool print_spacer)
{
pcmk__output_t *out = data_set->priv;
PCMK__OUTPUT_SPACER_IF(out, print_spacer);
out->begin_list(out, NULL, NULL, "Transition Summary");
pcmk__output_actions(data_set);
out->end_list(out);
}
+/*!
+ * \internal
+ * \brief Reset a cluster working set's input, output, date, and flags
+ *
+ * \param[in] data_set Cluster working set
+ * \param[in] input What to set as cluster input
+ * \param[in] out What to set as cluster output object
+ * \param[in] use_date What to set as cluster's current timestamp
+ * \param[in] flags Cluster flags to add (pe_flag_*)
+ */
static void
reset(pe_working_set_t *data_set, xmlNodePtr input, pcmk__output_t *out,
char *use_date, unsigned int flags)
{
data_set->input = input;
data_set->priv = out;
- pcmk__set_effective_date(data_set, true, use_date);
+ set_effective_date(data_set, true, use_date);
if (pcmk_is_set(flags, pcmk_sim_sanitized)) {
pe__set_working_set_flags(data_set, pe_flag_sanitized);
}
if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
pe__set_working_set_flags(data_set, pe_flag_show_scores);
}
if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
pe__set_working_set_flags(data_set, pe_flag_show_utilization);
}
}
-int
-pcmk__write_sim_dotfile(pe_working_set_t *data_set, const char *dot_file,
- bool all_actions, bool verbose)
+/*!
+ * \brief Write out a file in dot(1) format describing the actions that will
+ * be taken by the scheduler in response to an input CIB file.
+ *
+ * \param[in] data_set Working set for the cluster
+ * \param[in] dot_file The filename to write
+ * \param[in] all_actions Write all actions, even those that are optional or
+ * are on unmanaged resources
+ * \param[in] verbose Add extra information, such as action IDs, to the
+ * output
+ *
+ * \return Standard Pacemaker return code
+ */
+static int
+write_sim_dotfile(pe_working_set_t *data_set, const char *dot_file,
+ bool all_actions, bool verbose)
{
GList *gIter = NULL;
FILE *dot_strm = fopen(dot_file, "w");
if (dot_strm == NULL) {
return errno;
}
fprintf(dot_strm, " digraph \"g\" {\n");
for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
pe_action_t *action = (pe_action_t *) gIter->data;
const char *style = "dashed";
const char *font = "black";
const char *color = "black";
char *action_name = create_action_name(action, verbose);
if (pcmk_is_set(action->flags, pe_action_pseudo)) {
font = "orange";
}
if (pcmk_is_set(action->flags, pe_action_dumped)) {
style = "bold";
color = "green";
} else if ((action->rsc != NULL)
&& !pcmk_is_set(action->rsc->flags, pe_rsc_managed)) {
color = "red";
font = "purple";
if (!all_actions) {
goto do_not_write;
}
} else if (pcmk_is_set(action->flags, pe_action_optional)) {
color = "blue";
if (!all_actions) {
goto do_not_write;
}
} else {
color = "red";
CRM_LOG_ASSERT(!pcmk_is_set(action->flags, pe_action_runnable));
}
pe__set_action_flags(action, pe_action_dumped);
fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n",
action_name, style, color, font);
do_not_write:
free(action_name);
}
for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
pe_action_t *action = (pe_action_t *) gIter->data;
GList *gIter2 = NULL;
for (gIter2 = action->actions_before; gIter2 != NULL; gIter2 = gIter2->next) {
pe_action_wrapper_t *before = (pe_action_wrapper_t *) gIter2->data;
char *before_name = NULL;
char *after_name = NULL;
const char *style = "dashed";
bool optional = true;
if (before->state == pe_link_dumped) {
optional = false;
style = "bold";
} else if (before->type == pe_order_none) {
continue;
} else if (pcmk_is_set(before->action->flags, pe_action_dumped)
&& pcmk_is_set(action->flags, pe_action_dumped)
&& before->type != pe_order_load) {
optional = false;
}
if (all_actions || !optional) {
before_name = create_action_name(before->action, verbose);
after_name = create_action_name(action, verbose);
fprintf(dot_strm, "\"%s\" -> \"%s\" [ style = %s]\n",
before_name, after_name, style);
free(before_name);
free(after_name);
}
}
}
fprintf(dot_strm, "}\n");
fflush(dot_strm);
fclose(dot_strm);
return pcmk_rc_ok;
}
-void
-pcmk__profile_file(const char *xml_file, long long repeat, pe_working_set_t *data_set, char *use_date)
+/*!
+ * \brief Profile the configuration updates and scheduler actions in a single
+ * CIB file, printing the profiling timings.
+ *
+ * \note \p data_set->priv must have been set to a valid \p pcmk__output_t
+ * object before this function is called.
+ *
+ * \param[in] xml_file The CIB file to profile
+ * \param[in] repeat Number of times to run
+ * \param[in] data_set Working set for the cluster
+ * \param[in] use_date The date to set the cluster's time to (may be NULL)
+ */
+static void
+profile_file(const char *xml_file, long long repeat, pe_working_set_t *data_set,
+ char *use_date)
{
pcmk__output_t *out = data_set->priv;
xmlNode *cib_object = NULL;
clock_t start = 0;
clock_t end;
CRM_ASSERT(out != NULL);
cib_object = filename2xml(xml_file);
start = clock();
if (pcmk_find_cib_element(cib_object, XML_CIB_TAG_STATUS) == NULL) {
create_xml_node(cib_object, XML_CIB_TAG_STATUS);
}
if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
free_xml(cib_object);
return;
}
if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
free_xml(cib_object);
return;
}
for (int i = 0; i < repeat; ++i) {
xmlNode *input = (repeat == 1)? cib_object : copy_xml(cib_object);
data_set->input = input;
- pcmk__set_effective_date(data_set, false, use_date);
+ set_effective_date(data_set, false, use_date);
pcmk__schedule_actions(data_set, input, NULL);
pe_reset_working_set(data_set);
}
end = clock();
out->message(out, "profile", xml_file, start, end);
}
void
pcmk__profile_dir(const char *dir, long long repeat, pe_working_set_t *data_set, char *use_date)
{
pcmk__output_t *out = data_set->priv;
struct dirent **namelist;
int file_num = scandir(dir, &namelist, 0, alphasort);
CRM_ASSERT(out != NULL);
if (file_num > 0) {
struct stat prop;
char buffer[FILENAME_MAX];
out->begin_list(out, NULL, NULL, "Timings");
while (file_num--) {
if ('.' == namelist[file_num]->d_name[0]) {
free(namelist[file_num]);
continue;
} else if (!pcmk__ends_with_ext(namelist[file_num]->d_name,
".xml")) {
free(namelist[file_num]);
continue;
}
snprintf(buffer, sizeof(buffer), "%s/%s", dir, namelist[file_num]->d_name);
if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
- pcmk__profile_file(buffer, repeat, data_set, use_date);
+ profile_file(buffer, repeat, data_set, use_date);
}
free(namelist[file_num]);
}
free(namelist);
out->end_list(out);
}
}
-void
-pcmk__set_effective_date(pe_working_set_t *data_set, bool print_original, char *use_date)
+/*!
+ * \brief Set the date of the cluster, either to the value given by
+ * \p use_date, or to the "execution-date" value in the CIB.
+ *
+ * \note \p data_set->priv must have been set to a valid \p pcmk__output_t
+ * object before this function is called.
+ *
+ * \param[in,out] data_set Working set for the cluster
+ * \param[in] print_original If \p true, the "execution-date" should
+ * also be printed
+ * \param[in] use_date The date to set the cluster's time to
+ * (may be NULL)
+ */
+static void
+set_effective_date(pe_working_set_t *data_set, bool print_original,
+ char *use_date)
{
pcmk__output_t *out = data_set->priv;
time_t original_date = 0;
CRM_ASSERT(out != NULL);
crm_element_value_epoch(data_set->input, "execution-date", &original_date);
if (use_date) {
data_set->now = crm_time_new(use_date);
out->info(out, "Setting effective cluster time: %s", use_date);
crm_time_log(LOG_NOTICE, "Pretending 'now' is", data_set->now,
crm_time_log_date | crm_time_log_timeofday);
} else if (original_date) {
data_set->now = crm_time_new(NULL);
crm_time_set_timet(data_set->now, &original_date);
if (print_original) {
char *when = crm_time_as_string(data_set->now,
crm_time_log_date|crm_time_log_timeofday);
out->info(out, "Using the original execution date of: %s", when);
free(when);
}
}
}
+/*!
+ * \internal
+ * \brief Simulate successfully executing a pseudo-action in a graph
+ *
+ * \param[in] graph Graph to update with pseudo-action result
+ * \param[in] action Pseudo-action to simulate executing
+ *
+ * \return TRUE
+ */
+static gboolean
+simulate_pseudo_action(crm_graph_t *graph, crm_action_t *action)
+{
+ const char *node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
+ const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK_KEY);
+
+ crm__set_graph_action_flags(action, pcmk__graph_action_confirmed);
+ out->message(out, "inject-pseudo-action", node, task);
+
+ pcmk__update_graph(graph, action);
+ return TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Simulate executing a resource action in a graph
+ *
+ * \param[in] graph Graph to update with resource action result
+ * \param[in] action Resource action to simulate executing
+ *
+ * \return TRUE if action is validly specified, otherwise FALSE
+ */
+static gboolean
+simulate_resource_action(crm_graph_t *graph, crm_action_t *action)
+{
+ int rc;
+ lrmd_event_data_t *op = NULL;
+ int target_outcome = PCMK_OCF_OK;
+
+ const char *rtype = NULL;
+ const char *rclass = NULL;
+ const char *resource = NULL;
+ const char *rprovider = NULL;
+ const char *resource_config_name = NULL;
+ const char *operation = crm_element_value(action->xml, "operation");
+ const char *target_rc_s = crm_meta_value(action->params,
+ XML_ATTR_TE_TARGET_RC);
+
+ xmlNode *cib_node = NULL;
+ xmlNode *cib_resource = NULL;
+ xmlNode *action_rsc = first_named_child(action->xml, XML_CIB_TAG_RESOURCE);
+
+ char *node = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET);
+ char *uuid = NULL;
+ const char *router_node = crm_element_value(action->xml,
+ XML_LRM_ATTR_ROUTER_NODE);
+
+ // Certain actions don't need to be displayed or history entries
+ if (pcmk__str_eq(operation, CRM_OP_REPROBE, pcmk__str_none)) {
+ crm_debug("No history injection for %s op on %s", operation, node);
+ goto done; // Confirm action and update graph
+ }
+
+ if (action_rsc == NULL) { // Shouldn't be possible
+ crm_log_xml_err(action->xml, "Bad");
+ free(node);
+ return FALSE;
+ }
+
+ /* A resource might be known by different names in the configuration and in
+ * the action (for example, a clone instance). Grab the configuration name
+ * (which is preferred when writing history), and if necessary, the instance
+ * name.
+ */
+ resource_config_name = crm_element_value(action_rsc, XML_ATTR_ID);
+ if (resource_config_name == NULL) { // Shouldn't be possible
+ crm_log_xml_err(action->xml, "No ID");
+ free(node);
+ return FALSE;
+ }
+ resource = resource_config_name;
+ if (pe_find_resource(fake_resource_list, resource) == NULL) {
+ const char *longname = crm_element_value(action_rsc, XML_ATTR_ID_LONG);
+
+ if ((longname != NULL)
+ && (pe_find_resource(fake_resource_list, longname) != NULL)) {
+ resource = longname;
+ }
+ }
+
+ // Certain actions need to be displayed but don't need history entries
+ if (pcmk__strcase_any_of(operation, "delete", RSC_METADATA, NULL)) {
+ out->message(out, "inject-rsc-action", resource, operation, node,
+ (guint) 0);
+ goto done; // Confirm action and update graph
+ }
+
+ rclass = crm_element_value(action_rsc, XML_AGENT_ATTR_CLASS);
+ rtype = crm_element_value(action_rsc, XML_ATTR_TYPE);
+ rprovider = crm_element_value(action_rsc, XML_AGENT_ATTR_PROVIDER);
+
+ pcmk__scan_min_int(target_rc_s, &target_outcome, 0);
+
+ CRM_ASSERT(fake_cib->cmds->query(fake_cib, NULL, NULL,
+ cib_sync_call|cib_scope_local) == pcmk_ok);
+
+ // Ensure the action node is in the CIB
+ uuid = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET_UUID);
+ cib_node = pcmk__inject_node(fake_cib, node,
+ ((router_node == NULL)? uuid: node));
+ free(uuid);
+ CRM_ASSERT(cib_node != NULL);
+
+ // Add a history entry for the action
+ cib_resource = pcmk__inject_resource_history(out, cib_node, resource,
+ resource_config_name,
+ rclass, rtype, rprovider);
+ if (cib_resource == NULL) {
+ crm_err("Could not simulate action %d history for resource %s",
+ action->id, resource);
+ free(node);
+ free_xml(cib_node);
+ return FALSE;
+ }
+
+ // Simulate and display an executor event for the action result
+ op = pcmk__event_from_graph_action(cib_resource, action, PCMK_EXEC_DONE,
+ target_outcome, "User-injected result");
+ out->message(out, "inject-rsc-action", resource, op->op_type, node,
+ op->interval_ms);
+
+ // Check whether action is in a list of desired simulated failures
+ for (GList *iter = fake_op_fail_list; iter != NULL; iter = iter->next) {
+ char *spec = (char *) iter->data;
+ char *key = NULL;
+ const char *match_name = NULL;
+
+ // Allow user to specify anonymous clone with or without instance number
+ key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource, op->op_type,
+ op->interval_ms, node);
+ if (strncasecmp(key, spec, strlen(key)) == 0) {
+ match_name = resource;
+ }
+ free(key);
+
+ // If not found, try the resource's name in the configuration
+ if ((match_name == NULL)
+ && (strcmp(resource, resource_config_name) != 0)) {
+
+ key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource_config_name,
+ op->op_type, op->interval_ms, node);
+ if (strncasecmp(key, spec, strlen(key)) == 0) {
+ match_name = resource_config_name;
+ }
+ free(key);
+ }
+
+ if (match_name == NULL) {
+ continue; // This failed action entry doesn't match
+ }
+
+ // ${match_name}_${task}_${interval_in_ms}@${node}=${rc}
+ rc = sscanf(spec, "%*[^=]=%d", (int *) &op->rc);
+ if (rc != 1) {
+ out->err(out, "Invalid failed operation '%s' "
+ "(result code must be integer)", spec);
+ continue; // Keep checking other list entries
+ }
+
+ out->info(out, "Pretending action %d failed with rc=%d",
+ action->id, op->rc);
+ crm__set_graph_action_flags(action, pcmk__graph_action_failed);
+ graph->abort_priority = INFINITY;
+ pcmk__inject_failcount(out, cib_node, match_name, op->op_type,
+ op->interval_ms, op->rc);
+ break;
+ }
+
+ pcmk__inject_action_result(cib_resource, op, target_outcome);
+ lrmd_free_event(op);
+ rc = fake_cib->cmds->modify(fake_cib, XML_CIB_TAG_STATUS, cib_node,
+ cib_sync_call|cib_scope_local);
+ CRM_ASSERT(rc == pcmk_ok);
+
+ done:
+ free(node);
+ free_xml(cib_node);
+ crm__set_graph_action_flags(action, pcmk__graph_action_confirmed);
+ pcmk__update_graph(graph, action);
+ return TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Simulate successfully executing a cluster action
+ *
+ * \param[in] graph Graph to update with action result
+ * \param[in] action Cluster action to simulate
+ *
+ * \return TRUE
+ */
+static gboolean
+simulate_cluster_action(crm_graph_t *graph, crm_action_t *action)
+{
+ const char *node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
+ const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK);
+ xmlNode *rsc = first_named_child(action->xml, XML_CIB_TAG_RESOURCE);
+
+ crm__set_graph_action_flags(action, pcmk__graph_action_confirmed);
+ out->message(out, "inject-cluster-action", node, task, rsc);
+ pcmk__update_graph(graph, action);
+ return TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Simulate successfully executing a fencing action
+ *
+ * \param[in] graph Graph to update with action result
+ * \param[in] action Fencing action to simulate
+ *
+ * \return TRUE
+ */
+static gboolean
+simulate_fencing_action(crm_graph_t *graph, crm_action_t *action)
+{
+ const char *op = crm_meta_value(action->params, "stonith_action");
+ char *target = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET);
+
+ out->message(out, "inject-fencing-action", target, op);
+
+ if (!pcmk__str_eq(op, "on", pcmk__str_casei)) {
+ int rc = pcmk_ok;
+ char xpath[STATUS_PATH_MAX];
+
+ // Set node state to offline
+ xmlNode *cib_node = pcmk__inject_node_state_change(fake_cib, target,
+ false);
+
+ CRM_ASSERT(cib_node != NULL);
+ crm_xml_add(cib_node, XML_ATTR_ORIGIN, __func__);
+ rc = fake_cib->cmds->replace(fake_cib, XML_CIB_TAG_STATUS, cib_node,
+ cib_sync_call|cib_scope_local);
+ CRM_ASSERT(rc == pcmk_ok);
+
+ // Simulate controller clearing node's resource history and attributes
+ snprintf(xpath, STATUS_PATH_MAX, "//node_state[@uname='%s']/%s",
+ target, XML_CIB_TAG_LRM);
+ fake_cib->cmds->remove(fake_cib, xpath, NULL,
+ cib_xpath|cib_sync_call|cib_scope_local);
+
+ snprintf(xpath, STATUS_PATH_MAX, "//node_state[@uname='%s']/%s",
+ target, XML_TAG_TRANSIENT_NODEATTRS);
+ fake_cib->cmds->remove(fake_cib, xpath, NULL,
+ cib_xpath|cib_sync_call|cib_scope_local);
+
+ free_xml(cib_node);
+ }
+
+ crm__set_graph_action_flags(action, pcmk__graph_action_confirmed);
+ pcmk__update_graph(graph, action);
+ free(target);
+ return TRUE;
+}
+
+enum transition_status
+pcmk__simulate_transition(pe_working_set_t *data_set, cib_t *cib,
+ GList *op_fail_list)
+{
+ crm_graph_t *transition = NULL;
+ enum transition_status graph_rc;
+
+ crm_graph_functions_t simulation_fns = {
+ simulate_pseudo_action,
+ simulate_resource_action,
+ simulate_cluster_action,
+ simulate_fencing_action,
+ };
+
+ out = data_set->priv;
+
+ fake_cib = cib;
+ fake_op_fail_list = op_fail_list;
+
+ if (!out->is_quiet(out)) {
+ out->begin_list(out, NULL, NULL, "Executing Cluster Transition");
+ }
+
+ pcmk__set_graph_functions(&simulation_fns);
+ transition = pcmk__unpack_graph(data_set->graph, crm_system_name);
+ pcmk__log_graph(LOG_DEBUG, transition);
+
+ fake_resource_list = data_set->resources;
+ do {
+ graph_rc = pcmk__execute_graph(transition);
+ } while (graph_rc == transition_active);
+ fake_resource_list = NULL;
+
+ if (graph_rc != transition_complete) {
+ out->err(out, "Transition failed: %s",
+ pcmk__graph_status2text(graph_rc));
+ pcmk__log_graph(LOG_ERR, transition);
+ out->err(out, "An invalid transition was produced");
+ }
+ pcmk__free_graph(transition);
+
+ if (!out->is_quiet(out)) {
+ // If not quiet, we'll need the resulting CIB for later display
+ xmlNode *cib_object = NULL;
+ int rc = fake_cib->cmds->query(fake_cib, NULL, &cib_object,
+ cib_sync_call|cib_scope_local);
+
+ CRM_ASSERT(rc == pcmk_ok);
+ pe_reset_working_set(data_set);
+ data_set->input = cib_object;
+ out->end_list(out);
+ }
+ return graph_rc;
+}
+
int
-pcmk__simulate(pe_working_set_t *data_set, pcmk__output_t *out, pcmk_injections_t *injections,
- unsigned int flags, unsigned int section_opts, char *use_date,
- char *input_file, char *graph_file, char *dot_file)
+pcmk__simulate(pe_working_set_t *data_set, pcmk__output_t *out,
+ pcmk_injections_t *injections, unsigned int flags,
+ unsigned int section_opts, char *use_date, char *input_file,
+ char *graph_file, char *dot_file)
{
int printed = pcmk_rc_no_output;
int rc = pcmk_rc_ok;
xmlNodePtr input = NULL;
cib_t *cib = NULL;
- bool modified = false;
rc = cib__signon_query(&cib, &input);
if (rc != pcmk_rc_ok) {
goto simulate_done;
}
reset(data_set, input, out, use_date, flags);
cluster_status(data_set);
if (!out->is_quiet(out)) {
if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) {
printed = out->message(out, "maint-mode", data_set->flags);
}
if (data_set->disabled_resources || data_set->blocked_resources) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
- printed = out->info(out, "%d of %d resource instances DISABLED and %d BLOCKED "
- "from further action due to failure",
- data_set->disabled_resources, data_set->ninstances,
+ printed = out->info(out,
+ "%d of %d resource instances DISABLED and "
+ "%d BLOCKED from further action due to failure",
+ data_set->disabled_resources,
+ data_set->ninstances,
data_set->blocked_resources);
}
/* Most formatted output headers use caps for each word, but this one
* only has the first word capitalized for compatibility with pcs.
*/
- print_cluster_status(data_set, pcmk_is_set(flags, pcmk_sim_show_pending) ? pcmk_show_pending : 0,
- section_opts, "Current cluster status", printed == pcmk_rc_ok);
+ print_cluster_status(data_set,
+ pcmk_is_set(flags, pcmk_sim_show_pending)? pcmk_show_pending : 0,
+ section_opts, "Current cluster status",
+ (printed == pcmk_rc_ok));
printed = pcmk_rc_ok;
}
- modified = injections->node_down != NULL || injections->node_fail != NULL ||
- injections->node_up != NULL || injections->op_inject != NULL ||
- injections->ticket_activate != NULL || injections->ticket_grant != NULL ||
- injections->ticket_revoke != NULL || injections->ticket_standby != NULL ||
- injections->watchdog != NULL || injections->watchdog != NULL;
+ // If the user requested any injections, handle them
+ if ((injections->node_down != NULL)
+ || (injections->node_fail != NULL)
+ || (injections->node_up != NULL)
+ || (injections->op_inject != NULL)
+ || (injections->ticket_activate != NULL)
+ || (injections->ticket_grant != NULL)
+ || (injections->ticket_revoke != NULL)
+ || (injections->ticket_standby != NULL)
+ || (injections->watchdog != NULL)) {
- if (modified) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
modify_configuration(data_set, cib, injections);
printed = pcmk_rc_ok;
rc = cib->cmds->query(cib, NULL, &input, cib_sync_call);
if (rc != pcmk_rc_ok) {
rc = pcmk_legacy2rc(rc);
goto simulate_done;
}
cleanup_calculations(data_set);
reset(data_set, input, out, use_date, flags);
cluster_status(data_set);
}
if (input_file != NULL) {
rc = write_xml_file(input, input_file, FALSE);
if (rc < 0) {
rc = pcmk_legacy2rc(rc);
goto simulate_done;
}
}
if (pcmk_any_flags_set(flags, pcmk_sim_process | pcmk_sim_simulate)) {
crm_time_t *local_date = NULL;
pcmk__output_t *logger_out = NULL;
- if (pcmk_all_flags_set(data_set->flags, pe_flag_show_scores|pe_flag_show_utilization)) {
+ if (pcmk_all_flags_set(data_set->flags,
+ pe_flag_show_scores|pe_flag_show_utilization)) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
- out->begin_list(out, NULL, NULL, "Allocation Scores and Utilization Information");
+ out->begin_list(out, NULL, NULL,
+ "Allocation Scores and Utilization Information");
printed = pcmk_rc_ok;
+
} else if (pcmk_is_set(data_set->flags, pe_flag_show_scores)) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
out->begin_list(out, NULL, NULL, "Allocation Scores");
printed = pcmk_rc_ok;
+
} else if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
out->begin_list(out, NULL, NULL, "Utilization Information");
printed = pcmk_rc_ok;
+
} else {
logger_out = pcmk__new_logger();
if (logger_out == NULL) {
rc = pcmk_rc_error;
goto simulate_done;
}
-
data_set->priv = logger_out;
}
pcmk__schedule_actions(data_set, input, local_date);
if (logger_out == NULL) {
out->end_list(out);
} else {
logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
pcmk__output_free(logger_out);
data_set->priv = out;
}
input = NULL; /* Don't try and free it twice */
if (graph_file != NULL) {
rc = write_xml_file(data_set->graph, graph_file, FALSE);
if (rc < 0) {
rc = pcmk_rc_graph_error;
goto simulate_done;
}
}
if (dot_file != NULL) {
- rc = pcmk__write_sim_dotfile(data_set, dot_file,
- pcmk_is_set(flags, pcmk_sim_all_actions),
- pcmk_is_set(flags, pcmk_sim_verbose));
+ rc = write_sim_dotfile(data_set, dot_file,
+ pcmk_is_set(flags, pcmk_sim_all_actions),
+ pcmk_is_set(flags, pcmk_sim_verbose));
if (rc != pcmk_rc_ok) {
rc = pcmk_rc_dot_error;
goto simulate_done;
}
}
if (!out->is_quiet(out)) {
print_transition_summary(data_set, printed == pcmk_rc_ok);
}
}
rc = pcmk_rc_ok;
- if (pcmk_is_set(flags, pcmk_sim_simulate)) {
- PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
- if (run_simulation(data_set, cib, injections->op_fail) != transition_complete) {
- rc = pcmk_rc_invalid_transition;
- }
+ if (!pcmk_is_set(flags, pcmk_sim_simulate)) {
+ goto simulate_done;
+ }
- if (!out->is_quiet(out)) {
- pcmk__set_effective_date(data_set, true, use_date);
+ PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
+ if (pcmk__simulate_transition(data_set, cib,
+ injections->op_fail) != transition_complete) {
+ rc = pcmk_rc_invalid_transition;
+ }
- if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
- pe__set_working_set_flags(data_set, pe_flag_show_scores);
- }
- if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
- pe__set_working_set_flags(data_set, pe_flag_show_utilization);
- }
+ if (out->is_quiet(out)) {
+ goto simulate_done;
+ }
- cluster_status(data_set);
- print_cluster_status(data_set, 0, section_opts, "Revised Cluster Status", true);
- }
+ set_effective_date(data_set, true, use_date);
+
+ if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
+ pe__set_working_set_flags(data_set, pe_flag_show_scores);
+ }
+ if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
+ pe__set_working_set_flags(data_set, pe_flag_show_utilization);
}
+ cluster_status(data_set);
+ print_cluster_status(data_set, 0, section_opts, "Revised Cluster Status",
+ true);
+
simulate_done:
if (cib) {
cib->cmds->signoff(cib);
cib_delete(cib);
}
-
return rc;
}
int
-pcmk_simulate(xmlNodePtr *xml, pe_working_set_t *data_set, pcmk_injections_t *injections,
- unsigned int flags, unsigned int section_opts, char *use_date,
- char *input_file, char *graph_file, char *dot_file)
+pcmk_simulate(xmlNodePtr *xml, pe_working_set_t *data_set,
+ pcmk_injections_t *injections, unsigned int flags,
+ unsigned int section_opts, char *use_date, char *input_file,
+ char *graph_file, char *dot_file)
{
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__out_prologue(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
pe__register_messages(out);
pcmk__register_lib_messages(out);
rc = pcmk__simulate(data_set, out, injections, flags, section_opts,
use_date, input_file, graph_file, dot_file);
pcmk__out_epilogue(out, xml, rc);
return rc;
}
void
pcmk_free_injections(pcmk_injections_t *injections)
{
if (injections == NULL) {
return;
}
g_list_free_full(injections->node_up, g_free);
g_list_free_full(injections->node_down, g_free);
g_list_free_full(injections->node_fail, g_free);
g_list_free_full(injections->op_fail, g_free);
g_list_free_full(injections->op_inject, g_free);
g_list_free_full(injections->ticket_grant, g_free);
g_list_free_full(injections->ticket_revoke, g_free);
g_list_free_full(injections->ticket_standby, g_free);
g_list_free_full(injections->ticket_activate, g_free);
free(injections->quorum);
free(injections->watchdog);
free(injections);
}
diff --git a/tools/crm_resource_runtime.c b/tools/crm_resource_runtime.c
index 501f61f3ba..b877beea69 100644
--- a/tools/crm_resource_runtime.c
+++ b/tools/crm_resource_runtime.c
@@ -1,2011 +1,2011 @@
/*
* Copyright 2004-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm_resource.h>
#include <crm/common/ipc_controld.h>
#include <crm/common/lists_internal.h>
#include <crm/services_internal.h>
resource_checks_t *
cli_check_resource(pe_resource_t *rsc, char *role_s, char *managed)
{
pe_resource_t *parent = uber_parent(rsc);
resource_checks_t *rc = calloc(1, sizeof(resource_checks_t));
if (role_s) {
enum rsc_role_e role = text2role(role_s);
if (role == RSC_ROLE_STOPPED) {
rc->flags |= rsc_remain_stopped;
} else if (pcmk_is_set(parent->flags, pe_rsc_promotable) &&
(role == RSC_ROLE_UNPROMOTED)) {
rc->flags |= rsc_unpromotable;
}
}
if (managed && !crm_is_true(managed)) {
rc->flags |= rsc_unmanaged;
}
if (rsc->lock_node) {
rc->lock_node = rsc->lock_node->details->uname;
}
rc->rsc = rsc;
return rc;
}
static GList *
build_node_info_list(pe_resource_t *rsc)
{
GList *retval = NULL;
for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
pe_resource_t *child = (pe_resource_t *) iter->data;
for (GList *iter2 = child->running_on; iter2 != NULL; iter2 = iter2->next) {
pe_node_t *node = (pe_node_t *) iter2->data;
node_info_t *ni = calloc(1, sizeof(node_info_t));
ni->node_name = node->details->uname;
ni->promoted = pcmk_is_set(rsc->flags, pe_rsc_promotable) &&
child->fns->state(child, TRUE) == RSC_ROLE_PROMOTED;
retval = g_list_prepend(retval, ni);
}
}
return retval;
}
GList *
cli_resource_search(pe_resource_t *rsc, const char *requested_name,
pe_working_set_t *data_set)
{
GList *retval = NULL;
pe_resource_t *parent = uber_parent(rsc);
if (pe_rsc_is_clone(rsc)) {
retval = build_node_info_list(rsc);
/* The anonymous clone children's common ID is supplied */
} else if (pe_rsc_is_clone(parent)
&& !pcmk_is_set(rsc->flags, pe_rsc_unique)
&& rsc->clone_name
&& pcmk__str_eq(requested_name, rsc->clone_name, pcmk__str_casei)
&& !pcmk__str_eq(requested_name, rsc->id, pcmk__str_casei)) {
retval = build_node_info_list(parent);
} else if (rsc->running_on != NULL) {
for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) {
pe_node_t *node = (pe_node_t *) iter->data;
node_info_t *ni = calloc(1, sizeof(node_info_t));
ni->node_name = node->details->uname;
ni->promoted = (rsc->fns->state(rsc, TRUE) == RSC_ROLE_PROMOTED);
retval = g_list_prepend(retval, ni);
}
}
return retval;
}
#define XPATH_MAX 1024
// \return Standard Pacemaker return code
static int
find_resource_attr(pcmk__output_t *out, cib_t * the_cib, const char *attr,
const char *rsc, const char *attr_set_type, const char *set_name,
const char *attr_id, const char *attr_name, char **value)
{
int offset = 0;
int rc = pcmk_rc_ok;
xmlNode *xml_search = NULL;
char *xpath_string = NULL;
const char *xpath_base = NULL;
if(value) {
*value = NULL;
}
if(the_cib == NULL) {
return ENOTCONN;
}
xpath_base = pcmk_cib_xpath_for(XML_CIB_TAG_RESOURCES);
if (xpath_base == NULL) {
crm_err(XML_CIB_TAG_RESOURCES " CIB element not known (bug?)");
return ENOMSG;
}
xpath_string = calloc(1, XPATH_MAX);
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "%s",
xpath_base);
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//*[@id=\"%s\"]", rsc);
if (attr_set_type) {
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "/%s", attr_set_type);
if (set_name) {
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "[@id=\"%s\"]", set_name);
}
}
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//nvpair[");
if (attr_id) {
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "@id=\"%s\"", attr_id);
}
if (attr_name) {
if (attr_id) {
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, " and ");
}
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "@name=\"%s\"", attr_name);
}
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "]");
CRM_LOG_ASSERT(offset > 0);
rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search,
cib_sync_call | cib_scope_local | cib_xpath);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
goto done;
}
crm_log_xml_debug(xml_search, "Match");
if (xml_has_children(xml_search)) {
xmlNode *child = NULL;
rc = ENOTUNIQ;
out->info(out, "Multiple attributes match name=%s", attr_name);
for (child = pcmk__xml_first_child(xml_search); child != NULL;
child = pcmk__xml_next(child)) {
out->info(out, " Value: %s \t(id=%s)",
crm_element_value(child, XML_NVPAIR_ATTR_VALUE), ID(child));
}
out->spacer(out);
} else if(value) {
const char *tmp = crm_element_value(xml_search, attr);
if (tmp) {
*value = strdup(tmp);
}
}
done:
free(xpath_string);
free_xml(xml_search);
return rc;
}
/* PRIVATE. Use the find_matching_attr_resources instead. */
static void
find_matching_attr_resources_recursive(pcmk__output_t *out, GList/* <pe_resource_t*> */ ** result,
pe_resource_t * rsc, const char * rsc_id,
const char * attr_set, const char * attr_set_type,
const char * attr_id, const char * attr_name,
cib_t * cib, const char * cmd, int depth)
{
int rc = pcmk_rc_ok;
char *lookup_id = clone_strip(rsc->id);
char *local_attr_id = NULL;
/* visit the children */
for(GList *gIter = rsc->children; gIter; gIter = gIter->next) {
find_matching_attr_resources_recursive(out, result, (pe_resource_t*)gIter->data,
rsc_id, attr_set, attr_set_type,
attr_id, attr_name, cib, cmd, depth+1);
/* do it only once for clones */
if(pe_clone == rsc->variant) {
break;
}
}
rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type,
attr_set, attr_id, attr_name, &local_attr_id);
/* Post-order traversal.
* The root is always on the list and it is the last item. */
if((0 == depth) || (pcmk_rc_ok == rc)) {
/* push the head */
*result = g_list_append(*result, rsc);
}
free(local_attr_id);
free(lookup_id);
}
/* The result is a linearized pre-ordered tree of resources. */
static GList/*<pe_resource_t*>*/ *
find_matching_attr_resources(pcmk__output_t *out, pe_resource_t * rsc,
const char * rsc_id, const char * attr_set,
const char * attr_set_type, const char * attr_id,
const char * attr_name, cib_t * cib, const char * cmd,
gboolean force)
{
int rc = pcmk_rc_ok;
char *lookup_id = NULL;
char *local_attr_id = NULL;
GList * result = NULL;
/* If --force is used, update only the requested resource (clone or primitive).
* Otherwise, if the primitive has the attribute, use that.
* Otherwise use the clone. */
if(force == TRUE) {
return g_list_append(result, rsc);
}
if(rsc->parent && pe_clone == rsc->parent->variant) {
int rc = pcmk_rc_ok;
char *local_attr_id = NULL;
rc = find_resource_attr(out, cib, XML_ATTR_ID, rsc_id, attr_set_type,
attr_set, attr_id, attr_name, &local_attr_id);
free(local_attr_id);
if(rc != pcmk_rc_ok) {
rsc = rsc->parent;
out->info(out, "Performing %s of '%s' on '%s', the parent of '%s'",
cmd, attr_name, rsc->id, rsc_id);
}
return g_list_append(result, rsc);
} else if(rsc->parent == NULL && rsc->children && pe_clone == rsc->variant) {
pe_resource_t *child = rsc->children->data;
if(child->variant == pe_native) {
lookup_id = clone_strip(child->id); /* Could be a cloned group! */
rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type,
attr_set, attr_id, attr_name, &local_attr_id);
if(rc == pcmk_rc_ok) {
rsc = child;
out->info(out, "A value for '%s' already exists in child '%s', performing %s on that instead of '%s'",
attr_name, lookup_id, cmd, rsc_id);
}
free(local_attr_id);
free(lookup_id);
}
return g_list_append(result, rsc);
}
/* If the resource is a group ==> children inherit the attribute if defined. */
find_matching_attr_resources_recursive(out, &result, rsc, rsc_id, attr_set,
attr_set_type, attr_id, attr_name,
cib, cmd, 0);
return result;
}
// \return Standard Pacemaker return code
int
cli_resource_update_attribute(pe_resource_t *rsc, const char *requested_name,
const char *attr_set, const char *attr_set_type,
const char *attr_id, const char *attr_name,
const char *attr_value, gboolean recursive,
cib_t *cib, int cib_options,
pe_working_set_t *data_set, gboolean force)
{
pcmk__output_t *out = data_set->priv;
int rc = pcmk_rc_ok;
static bool need_init = TRUE;
char *local_attr_id = NULL;
char *local_attr_set = NULL;
GList/*<pe_resource_t*>*/ *resources = NULL;
const char *common_attr_id = attr_id;
if (attr_id == NULL && force == FALSE) {
find_resource_attr (out, cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL,
NULL, NULL, attr_name, NULL);
}
if (pcmk__str_eq(attr_set_type, XML_TAG_ATTR_SETS, pcmk__str_casei)) {
if (force == FALSE) {
rc = find_resource_attr(out, cib, XML_ATTR_ID, uber_parent(rsc)->id,
XML_TAG_META_SETS, attr_set, attr_id,
attr_name, &local_attr_id);
if (rc == pcmk_rc_ok && !out->is_quiet(out)) {
out->err(out, "WARNING: There is already a meta attribute for '%s' called '%s' (id=%s)",
uber_parent(rsc)->id, attr_name, local_attr_id);
out->err(out, " Delete '%s' first or use the force option to override",
local_attr_id);
}
free(local_attr_id);
if (rc == pcmk_rc_ok) {
return ENOTUNIQ;
}
}
resources = g_list_append(resources, rsc);
} else {
resources = find_matching_attr_resources(out, rsc, requested_name, attr_set, attr_set_type,
attr_id, attr_name, cib, "update", force);
}
/* If either attr_set or attr_id is specified,
* one clearly intends to modify a single resource.
* It is the last item on the resource list.*/
for(GList *gIter = (attr_set||attr_id) ? g_list_last(resources) : resources
; gIter; gIter = gIter->next) {
char *lookup_id = NULL;
xmlNode *xml_top = NULL;
xmlNode *xml_obj = NULL;
local_attr_id = NULL;
local_attr_set = NULL;
rsc = (pe_resource_t*)gIter->data;
attr_id = common_attr_id;
lookup_id = clone_strip(rsc->id); /* Could be a cloned group! */
rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type,
attr_set, attr_id, attr_name, &local_attr_id);
if (rc == pcmk_rc_ok) {
crm_debug("Found a match for name=%s: id=%s", attr_name, local_attr_id);
attr_id = local_attr_id;
} else if (rc != ENXIO) {
free(lookup_id);
free(local_attr_id);
g_list_free(resources);
return rc;
} else {
const char *tag = crm_element_name(rsc->xml);
if (attr_set == NULL) {
local_attr_set = crm_strdup_printf("%s-%s", lookup_id,
attr_set_type);
attr_set = local_attr_set;
}
if (attr_id == NULL) {
local_attr_id = crm_strdup_printf("%s-%s", attr_set, attr_name);
attr_id = local_attr_id;
}
xml_top = create_xml_node(NULL, tag);
crm_xml_add(xml_top, XML_ATTR_ID, lookup_id);
xml_obj = create_xml_node(xml_top, attr_set_type);
crm_xml_add(xml_obj, XML_ATTR_ID, attr_set);
}
xml_obj = crm_create_nvpair_xml(xml_obj, attr_id, attr_name, attr_value);
if (xml_top == NULL) {
xml_top = xml_obj;
}
crm_log_xml_debug(xml_top, "Update");
rc = cib->cmds->modify(cib, XML_CIB_TAG_RESOURCES, xml_top, cib_options);
rc = pcmk_legacy2rc(rc);
if (rc == pcmk_rc_ok) {
out->info(out, "Set '%s' option: id=%s%s%s%s%s value=%s", lookup_id, local_attr_id,
attr_set ? " set=" : "", attr_set ? attr_set : "",
attr_name ? " name=" : "", attr_name ? attr_name : "", attr_value);
}
free_xml(xml_top);
free(lookup_id);
free(local_attr_id);
free(local_attr_set);
if(recursive && pcmk__str_eq(attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) {
GList *lpc = NULL;
if(need_init) {
need_init = FALSE;
pcmk__unpack_constraints(data_set);
pe__clear_resource_flags_on_all(data_set, pe_rsc_allocating);
}
crm_debug("Looking for dependencies %p", rsc->rsc_cons_lhs);
pe__set_resource_flags(rsc, pe_rsc_allocating);
for (lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) {
pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
crm_debug("Checking %s %d", cons->id, cons->score);
if ((cons->score > 0)
&& !pcmk_is_set(cons->dependent->flags, pe_rsc_allocating)) {
/* Don't get into colocation loops */
crm_debug("Setting %s=%s for dependent resource %s",
attr_name, attr_value, cons->dependent->id);
cli_resource_update_attribute(cons->dependent,
cons->dependent->id, NULL,
attr_set_type, NULL,
attr_name, attr_value,
recursive, cib, cib_options,
data_set, force);
}
}
}
}
g_list_free(resources);
return rc;
}
// \return Standard Pacemaker return code
int
cli_resource_delete_attribute(pe_resource_t *rsc, const char *requested_name,
const char *attr_set, const char *attr_set_type,
const char *attr_id, const char *attr_name,
cib_t *cib, int cib_options,
pe_working_set_t *data_set, gboolean force)
{
pcmk__output_t *out = data_set->priv;
int rc = pcmk_rc_ok;
GList/*<pe_resource_t*>*/ *resources = NULL;
if (attr_id == NULL && force == FALSE) {
find_resource_attr(out, cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL,
NULL, NULL, attr_name, NULL);
}
if(pcmk__str_eq(attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) {
resources = find_matching_attr_resources(out, rsc, requested_name, attr_set, attr_set_type,
attr_id, attr_name, cib, "delete", force);
} else {
resources = g_list_append(resources, rsc);
}
for(GList *gIter = resources; gIter; gIter = gIter->next) {
char *lookup_id = NULL;
xmlNode *xml_obj = NULL;
char *local_attr_id = NULL;
rsc = (pe_resource_t*)gIter->data;
lookup_id = clone_strip(rsc->id);
rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type,
attr_set, attr_id, attr_name, &local_attr_id);
if (rc == ENXIO) {
free(lookup_id);
rc = pcmk_rc_ok;
continue;
} else if (rc != pcmk_rc_ok) {
free(lookup_id);
g_list_free(resources);
return rc;
}
if (attr_id == NULL) {
attr_id = local_attr_id;
}
xml_obj = crm_create_nvpair_xml(NULL, attr_id, attr_name, NULL);
crm_log_xml_debug(xml_obj, "Delete");
CRM_ASSERT(cib);
rc = cib->cmds->remove(cib, XML_CIB_TAG_RESOURCES, xml_obj, cib_options);
rc = pcmk_legacy2rc(rc);
if (rc == pcmk_rc_ok) {
out->info(out, "Deleted '%s' option: id=%s%s%s%s%s", lookup_id, local_attr_id,
attr_set ? " set=" : "", attr_set ? attr_set : "",
attr_name ? " name=" : "", attr_name ? attr_name : "");
}
free(lookup_id);
free_xml(xml_obj);
free(local_attr_id);
}
g_list_free(resources);
return rc;
}
// \return Standard Pacemaker return code
static int
send_lrm_rsc_op(pcmk_ipc_api_t *controld_api, bool do_fail_resource,
const char *host_uname, const char *rsc_id, pe_working_set_t *data_set)
{
pcmk__output_t *out = data_set->priv;
const char *router_node = host_uname;
const char *rsc_api_id = NULL;
const char *rsc_long_id = NULL;
const char *rsc_class = NULL;
const char *rsc_provider = NULL;
const char *rsc_type = NULL;
bool cib_only = false;
pe_resource_t *rsc = pe_find_resource(data_set->resources, rsc_id);
if (rsc == NULL) {
out->err(out, "Resource %s not found", rsc_id);
return ENXIO;
} else if (rsc->variant != pe_native) {
out->err(out, "We can only process primitive resources, not %s", rsc_id);
return EINVAL;
}
rsc_class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
rsc_provider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER),
rsc_type = crm_element_value(rsc->xml, XML_ATTR_TYPE);
if ((rsc_class == NULL) || (rsc_type == NULL)) {
out->err(out, "Resource %s does not have a class and type", rsc_id);
return EINVAL;
}
{
pe_node_t *node = pe_find_node(data_set->nodes, host_uname);
if (node == NULL) {
out->err(out, "Node %s not found", host_uname);
return pcmk_rc_node_unknown;
}
if (!(node->details->online)) {
if (do_fail_resource) {
out->err(out, "Node %s is not online", host_uname);
return ENOTCONN;
} else {
cib_only = true;
}
}
if (!cib_only && pe__is_guest_or_remote_node(node)) {
node = pe__current_node(node->details->remote_rsc);
if (node == NULL) {
out->err(out, "No cluster connection to Pacemaker Remote node %s detected",
host_uname);
return ENOTCONN;
}
router_node = node->details->uname;
}
}
if (rsc->clone_name) {
rsc_api_id = rsc->clone_name;
rsc_long_id = rsc->id;
} else {
rsc_api_id = rsc->id;
}
if (do_fail_resource) {
return pcmk_controld_api_fail(controld_api, host_uname, router_node,
rsc_api_id, rsc_long_id,
rsc_class, rsc_provider, rsc_type);
} else {
return pcmk_controld_api_refresh(controld_api, host_uname, router_node,
rsc_api_id, rsc_long_id, rsc_class,
rsc_provider, rsc_type, cib_only);
}
}
/*!
* \internal
* \brief Get resource name as used in failure-related node attributes
*
* \param[in] rsc Resource to check
*
* \return Newly allocated string containing resource's fail name
* \note The caller is responsible for freeing the result.
*/
static inline char *
rsc_fail_name(pe_resource_t *rsc)
{
const char *name = (rsc->clone_name? rsc->clone_name : rsc->id);
return pcmk_is_set(rsc->flags, pe_rsc_unique)? strdup(name) : clone_strip(name);
}
// \return Standard Pacemaker return code
static int
clear_rsc_history(pcmk_ipc_api_t *controld_api, const char *host_uname,
const char *rsc_id, pe_working_set_t *data_set)
{
int rc = pcmk_rc_ok;
/* Erase the resource's entire LRM history in the CIB, even if we're only
* clearing a single operation's fail count. If we erased only entries for a
* single operation, we might wind up with a wrong idea of the current
* resource state, and we might not re-probe the resource.
*/
rc = send_lrm_rsc_op(controld_api, false, host_uname, rsc_id, data_set);
if (rc != pcmk_rc_ok) {
return rc;
}
crm_trace("Processing %d mainloop inputs",
pcmk_controld_api_replies_expected(controld_api));
while (g_main_context_iteration(NULL, FALSE)) {
crm_trace("Processed mainloop input, %d still remaining",
pcmk_controld_api_replies_expected(controld_api));
}
return rc;
}
// \return Standard Pacemaker return code
static int
clear_rsc_failures(pcmk__output_t *out, pcmk_ipc_api_t *controld_api,
const char *node_name, const char *rsc_id, const char *operation,
const char *interval_spec, pe_working_set_t *data_set)
{
int rc = pcmk_rc_ok;
const char *failed_value = NULL;
const char *failed_id = NULL;
const char *interval_ms_s = NULL;
GHashTable *rscs = NULL;
GHashTableIter iter;
/* Create a hash table to use as a set of resources to clean. This lets us
* clean each resource only once (per node) regardless of how many failed
* operations it has.
*/
rscs = pcmk__strkey_table(NULL, NULL);
// Normalize interval to milliseconds for comparison to history entry
if (operation) {
interval_ms_s = crm_strdup_printf("%u",
crm_parse_interval_spec(interval_spec));
}
for (xmlNode *xml_op = pcmk__xml_first_child(data_set->failed);
xml_op != NULL;
xml_op = pcmk__xml_next(xml_op)) {
failed_id = crm_element_value(xml_op, XML_LRM_ATTR_RSCID);
if (failed_id == NULL) {
// Malformed history entry, should never happen
continue;
}
// No resource specified means all resources match
if (rsc_id) {
pe_resource_t *fail_rsc = pe_find_resource_with_flags(data_set->resources,
failed_id,
pe_find_renamed|pe_find_anon);
if (!fail_rsc || !pcmk__str_eq(rsc_id, fail_rsc->id, pcmk__str_casei)) {
continue;
}
}
// Host name should always have been provided by this point
failed_value = crm_element_value(xml_op, XML_ATTR_UNAME);
if (!pcmk__str_eq(node_name, failed_value, pcmk__str_casei)) {
continue;
}
// No operation specified means all operations match
if (operation) {
failed_value = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
if (!pcmk__str_eq(operation, failed_value, pcmk__str_casei)) {
continue;
}
// Interval (if operation was specified) defaults to 0 (not all)
failed_value = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL_MS);
if (!pcmk__str_eq(interval_ms_s, failed_value, pcmk__str_casei)) {
continue;
}
}
g_hash_table_add(rscs, (gpointer) failed_id);
}
g_hash_table_iter_init(&iter, rscs);
while (g_hash_table_iter_next(&iter, (gpointer *) &failed_id, NULL)) {
crm_debug("Erasing failures of %s on %s", failed_id, node_name);
rc = clear_rsc_history(controld_api, node_name, failed_id, data_set);
if (rc != pcmk_rc_ok) {
return rc;
}
}
g_hash_table_destroy(rscs);
return rc;
}
// \return Standard Pacemaker return code
static int
clear_rsc_fail_attrs(pe_resource_t *rsc, const char *operation,
const char *interval_spec, pe_node_t *node)
{
int rc = pcmk_rc_ok;
int attr_options = pcmk__node_attr_none;
char *rsc_name = rsc_fail_name(rsc);
if (pe__is_guest_or_remote_node(node)) {
attr_options |= pcmk__node_attr_remote;
}
rc = pcmk__node_attr_request_clear(NULL, node->details->uname, rsc_name,
operation, interval_spec, NULL,
attr_options);
free(rsc_name);
return rc;
}
// \return Standard Pacemaker return code
int
cli_resource_delete(pcmk_ipc_api_t *controld_api, const char *host_uname,
pe_resource_t *rsc, const char *operation,
const char *interval_spec, bool just_failures,
pe_working_set_t *data_set, gboolean force)
{
pcmk__output_t *out = data_set->priv;
int rc = pcmk_rc_ok;
pe_node_t *node = NULL;
if (rsc == NULL) {
return ENXIO;
} else if (rsc->children) {
GList *lpc = NULL;
for (lpc = rsc->children; lpc != NULL; lpc = lpc->next) {
pe_resource_t *child = (pe_resource_t *) lpc->data;
rc = cli_resource_delete(controld_api, host_uname, child, operation,
interval_spec, just_failures, data_set,
force);
if (rc != pcmk_rc_ok) {
return rc;
}
}
return pcmk_rc_ok;
} else if (host_uname == NULL) {
GList *lpc = NULL;
GList *nodes = g_hash_table_get_values(rsc->known_on);
if(nodes == NULL && force) {
nodes = pcmk__copy_node_list(data_set->nodes, false);
} else if(nodes == NULL && rsc->exclusive_discover) {
GHashTableIter iter;
pe_node_t *node = NULL;
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void**)&node)) {
if(node->weight >= 0) {
nodes = g_list_prepend(nodes, node);
}
}
} else if(nodes == NULL) {
nodes = g_hash_table_get_values(rsc->allowed_nodes);
}
for (lpc = nodes; lpc != NULL; lpc = lpc->next) {
node = (pe_node_t *) lpc->data;
if (node->details->online) {
rc = cli_resource_delete(controld_api, node->details->uname,
rsc, operation, interval_spec,
just_failures, data_set, force);
}
if (rc != pcmk_rc_ok) {
g_list_free(nodes);
return rc;
}
}
g_list_free(nodes);
return pcmk_rc_ok;
}
node = pe_find_node(data_set->nodes, host_uname);
if (node == NULL) {
out->err(out, "Unable to clean up %s because node %s not found",
rsc->id, host_uname);
return ENODEV;
}
if (!node->details->rsc_discovery_enabled) {
out->err(out, "Unable to clean up %s because resource discovery disabled on %s",
rsc->id, host_uname);
return EOPNOTSUPP;
}
if (controld_api == NULL) {
out->err(out, "Dry run: skipping clean-up of %s on %s due to CIB_file",
rsc->id, host_uname);
return pcmk_rc_ok;
}
rc = clear_rsc_fail_attrs(rsc, operation, interval_spec, node);
if (rc != pcmk_rc_ok) {
out->err(out, "Unable to clean up %s failures on %s: %s",
rsc->id, host_uname, pcmk_rc_str(rc));
return rc;
}
if (just_failures) {
rc = clear_rsc_failures(out, controld_api, host_uname, rsc->id, operation,
interval_spec, data_set);
} else {
rc = clear_rsc_history(controld_api, host_uname, rsc->id, data_set);
}
if (rc != pcmk_rc_ok) {
out->err(out, "Cleaned %s failures on %s, but unable to clean history: %s",
rsc->id, host_uname, pcmk_strerror(rc));
} else {
out->info(out, "Cleaned up %s on %s", rsc->id, host_uname);
}
return rc;
}
// \return Standard Pacemaker return code
int
cli_cleanup_all(pcmk_ipc_api_t *controld_api, const char *node_name,
const char *operation, const char *interval_spec,
pe_working_set_t *data_set)
{
pcmk__output_t *out = data_set->priv;
int rc = pcmk_rc_ok;
int attr_options = pcmk__node_attr_none;
const char *display_name = node_name? node_name : "all nodes";
if (controld_api == NULL) {
out->info(out, "Dry run: skipping clean-up of %s due to CIB_file",
display_name);
return rc;
}
if (node_name) {
pe_node_t *node = pe_find_node(data_set->nodes, node_name);
if (node == NULL) {
out->err(out, "Unknown node: %s", node_name);
return ENXIO;
}
if (pe__is_guest_or_remote_node(node)) {
attr_options |= pcmk__node_attr_remote;
}
}
rc = pcmk__node_attr_request_clear(NULL, node_name, NULL, operation,
interval_spec, NULL, attr_options);
if (rc != pcmk_rc_ok) {
out->err(out, "Unable to clean up all failures on %s: %s",
display_name, pcmk_rc_str(rc));
return rc;
}
if (node_name) {
rc = clear_rsc_failures(out, controld_api, node_name, NULL,
operation, interval_spec, data_set);
if (rc != pcmk_rc_ok) {
out->err(out, "Cleaned all resource failures on %s, but unable to clean history: %s",
node_name, pcmk_strerror(rc));
return rc;
}
} else {
for (GList *iter = data_set->nodes; iter; iter = iter->next) {
pe_node_t *node = (pe_node_t *) iter->data;
rc = clear_rsc_failures(out, controld_api, node->details->uname, NULL,
operation, interval_spec, data_set);
if (rc != pcmk_rc_ok) {
out->err(out, "Cleaned all resource failures on all nodes, but unable to clean history: %s",
pcmk_strerror(rc));
return rc;
}
}
}
out->info(out, "Cleaned up all resources on %s", display_name);
return rc;
}
int
cli_resource_check(pcmk__output_t *out, cib_t * cib_conn, pe_resource_t *rsc)
{
char *role_s = NULL;
char *managed = NULL;
pe_resource_t *parent = uber_parent(rsc);
int rc = pcmk_rc_no_output;
resource_checks_t *checks = NULL;
find_resource_attr(out, cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id,
NULL, NULL, NULL, XML_RSC_ATTR_MANAGED, &managed);
find_resource_attr(out, cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id,
NULL, NULL, NULL, XML_RSC_ATTR_TARGET_ROLE, &role_s);
checks = cli_check_resource(rsc, role_s, managed);
if (checks->flags != 0 || checks->lock_node != NULL) {
rc = out->message(out, "resource-check-list", checks);
}
free(role_s);
free(managed);
free(checks);
return rc;
}
// \return Standard Pacemaker return code
int
cli_resource_fail(pcmk_ipc_api_t *controld_api, const char *host_uname,
const char *rsc_id, pe_working_set_t *data_set)
{
crm_notice("Failing %s on %s", rsc_id, host_uname);
return send_lrm_rsc_op(controld_api, true, host_uname, rsc_id, data_set);
}
static GHashTable *
generate_resource_params(pe_resource_t *rsc, pe_node_t *node,
pe_working_set_t *data_set)
{
GHashTable *params = NULL;
GHashTable *meta = NULL;
GHashTable *combined = NULL;
GHashTableIter iter;
char *key = NULL;
char *value = NULL;
combined = pcmk__strkey_table(free, free);
params = pe_rsc_params(rsc, node, data_set);
if (params != NULL) {
g_hash_table_iter_init(&iter, params);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
g_hash_table_insert(combined, strdup(key), strdup(value));
}
}
meta = pcmk__strkey_table(free, free);
get_meta_attributes(meta, rsc, node, data_set);
if (meta != NULL) {
g_hash_table_iter_init(&iter, meta);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
char *crm_name = crm_meta_name(key);
g_hash_table_insert(combined, crm_name, strdup(value));
}
g_hash_table_destroy(meta);
}
return combined;
}
bool resource_is_running_on(pe_resource_t *rsc, const char *host)
{
bool found = TRUE;
GList *hIter = NULL;
GList *hosts = NULL;
if(rsc == NULL) {
return FALSE;
}
rsc->fns->location(rsc, &hosts, TRUE);
for (hIter = hosts; host != NULL && hIter != NULL; hIter = hIter->next) {
pe_node_t *node = (pe_node_t *) hIter->data;
if(strcmp(host, node->details->uname) == 0) {
crm_trace("Resource %s is running on %s\n", rsc->id, host);
goto done;
} else if(strcmp(host, node->details->id) == 0) {
crm_trace("Resource %s is running on %s\n", rsc->id, host);
goto done;
}
}
if(host != NULL) {
crm_trace("Resource %s is not running on: %s\n", rsc->id, host);
found = FALSE;
} else if(host == NULL && hosts == NULL) {
crm_trace("Resource %s is not running\n", rsc->id);
found = FALSE;
}
done:
g_list_free(hosts);
return found;
}
/*!
* \internal
* \brief Create a list of all resources active on host from a given list
*
* \param[in] host Name of host to check whether resources are active
* \param[in] rsc_list List of resources to check
*
* \return New list of resources from list that are active on host
*/
static GList *
get_active_resources(const char *host, GList *rsc_list)
{
GList *rIter = NULL;
GList *active = NULL;
for (rIter = rsc_list; rIter != NULL; rIter = rIter->next) {
pe_resource_t *rsc = (pe_resource_t *) rIter->data;
/* Expand groups to their members, because if we're restarting a member
* other than the first, we can't otherwise tell which resources are
* stopping and starting.
*/
if (rsc->variant == pe_group) {
active = g_list_concat(active,
get_active_resources(host, rsc->children));
} else if (resource_is_running_on(rsc, host)) {
active = g_list_append(active, strdup(rsc->id));
}
}
return active;
}
static void dump_list(GList *items, const char *tag)
{
int lpc = 0;
GList *item = NULL;
for (item = items; item != NULL; item = item->next) {
crm_trace("%s[%d]: %s", tag, lpc, (char*)item->data);
lpc++;
}
}
static void display_list(pcmk__output_t *out, GList *items, const char *tag)
{
GList *item = NULL;
for (item = items; item != NULL; item = item->next) {
out->info(out, "%s%s", tag, (const char *)item->data);
}
}
/*!
* \internal
* \brief Upgrade XML to latest schema version and use it as working set input
*
* This also updates the working set timestamp to the current time.
*
* \param[in] data_set Working set instance to update
* \param[in] xml XML to use as input
*
* \return Standard Pacemaker return code
* \note On success, caller is responsible for freeing memory allocated for
* data_set->now.
* \todo This follows the example of other callers of cli_config_update()
* and returns ENOKEY ("Required key not available") if that fails,
* but perhaps pcmk_rc_schema_validation would be better in that case.
*/
int
update_working_set_xml(pe_working_set_t *data_set, xmlNode **xml)
{
if (cli_config_update(xml, NULL, FALSE) == FALSE) {
return ENOKEY;
}
data_set->input = *xml;
data_set->now = crm_time_new(NULL);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Update a working set's XML input based on a CIB query
*
* \param[in] data_set Data set instance to initialize
* \param[in] cib Connection to the CIB manager
*
* \return Standard Pacemaker return code
* \note On success, caller is responsible for freeing memory allocated for
* data_set->input and data_set->now.
*/
static int
update_working_set_from_cib(pcmk__output_t *out, pe_working_set_t * data_set,
cib_t *cib)
{
xmlNode *cib_xml_copy = NULL;
int rc = pcmk_rc_ok;
rc = cib->cmds->query(cib, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
out->err(out, "Could not obtain the current CIB: %s (%d)", pcmk_strerror(rc), rc);
return rc;
}
rc = update_working_set_xml(data_set, &cib_xml_copy);
if (rc != pcmk_rc_ok) {
out->err(out, "Could not upgrade the current CIB XML");
free_xml(cib_xml_copy);
return rc;
}
return rc;
}
// \return Standard Pacemaker return code
static int
update_dataset(cib_t *cib, pe_working_set_t * data_set, bool simulate)
{
char *pid = NULL;
char *shadow_file = NULL;
cib_t *shadow_cib = NULL;
int rc = pcmk_rc_ok;
pcmk__output_t *out = data_set->priv;
pe_reset_working_set(data_set);
rc = update_working_set_from_cib(out, data_set, cib);
if (rc != pcmk_rc_ok) {
return rc;
}
if(simulate) {
bool prev_quiet = false;
pid = pcmk__getpid_s();
shadow_cib = cib_shadow_new(pid);
shadow_file = get_shadow_file(pid);
if (shadow_cib == NULL) {
out->err(out, "Could not create shadow cib: '%s'", pid);
rc = ENXIO;
goto done;
}
rc = write_xml_file(data_set->input, shadow_file, FALSE);
if (rc < 0) {
out->err(out, "Could not populate shadow cib: %s (%d)", pcmk_strerror(rc), rc);
goto done;
}
rc = shadow_cib->cmds->signon(shadow_cib, crm_system_name, cib_command);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
out->err(out, "Could not connect to shadow cib: %s (%d)", pcmk_strerror(rc), rc);
goto done;
}
pcmk__schedule_actions(data_set, data_set->input, NULL);
prev_quiet = out->is_quiet(out);
out->quiet = true;
- run_simulation(data_set, shadow_cib, NULL);
+ pcmk__simulate_transition(data_set, shadow_cib, NULL);
out->quiet = prev_quiet;
rc = update_dataset(shadow_cib, data_set, FALSE);
} else {
cluster_status(data_set);
}
done:
/* Do not free data_set->input here, we need rsc->xml to be valid later on */
cib_delete(shadow_cib);
free(pid);
if(shadow_file) {
unlink(shadow_file);
free(shadow_file);
}
return rc;
}
static int
max_delay_for_resource(pe_working_set_t * data_set, pe_resource_t *rsc)
{
int delay = 0;
int max_delay = 0;
if(rsc && rsc->children) {
GList *iter = NULL;
for(iter = rsc->children; iter; iter = iter->next) {
pe_resource_t *child = (pe_resource_t *)iter->data;
delay = max_delay_for_resource(data_set, child);
if(delay > max_delay) {
double seconds = delay / 1000.0;
crm_trace("Calculated new delay of %.1fs due to %s", seconds, child->id);
max_delay = delay;
}
}
} else if(rsc) {
char *key = crm_strdup_printf("%s_%s_0", rsc->id, RSC_STOP);
pe_action_t *stop = custom_action(rsc, key, RSC_STOP, NULL, TRUE, FALSE, data_set);
const char *value = g_hash_table_lookup(stop->meta, XML_ATTR_TIMEOUT);
long long result_ll;
if ((pcmk__scan_ll(value, &result_ll, -1LL) == pcmk_rc_ok)
&& (result_ll >= 0) && (result_ll <= INT_MAX)) {
max_delay = (int) result_ll;
} else {
max_delay = -1;
}
pe_free_action(stop);
}
return max_delay;
}
static int
max_delay_in(pe_working_set_t * data_set, GList *resources)
{
int max_delay = 0;
GList *item = NULL;
for (item = resources; item != NULL; item = item->next) {
int delay = 0;
pe_resource_t *rsc = pe_find_resource(data_set->resources, (const char *)item->data);
delay = max_delay_for_resource(data_set, rsc);
if(delay > max_delay) {
double seconds = delay / 1000.0;
crm_trace("Calculated new delay of %.1fs due to %s", seconds, rsc->id);
max_delay = delay;
}
}
return 5 + (max_delay / 1000);
}
#define waiting_for_starts(d, r, h) ((d != NULL) || \
(!resource_is_running_on((r), (h))))
/*!
* \internal
* \brief Restart a resource (on a particular host if requested).
*
* \param[in] rsc The resource to restart
* \param[in] host The host to restart the resource on (or NULL for all)
* \param[in] timeout_ms Consider failed if actions do not complete in this time
* (specified in milliseconds, but a two-second
* granularity is actually used; if 0, a timeout will be
* calculated based on the resource timeout)
* \param[in] cib Connection to the CIB manager
*
* \return Standard Pacemaker return code (exits on certain failures)
*/
int
cli_resource_restart(pcmk__output_t *out, pe_resource_t *rsc, const char *host,
const char *move_lifetime, int timeout_ms, cib_t *cib,
int cib_options, gboolean promoted_role_only, gboolean force)
{
int rc = pcmk_rc_ok;
int lpc = 0;
int before = 0;
int step_timeout_s = 0;
int sleep_interval = 2;
int timeout = timeout_ms / 1000;
bool stop_via_ban = FALSE;
char *rsc_id = NULL;
char *orig_target_role = NULL;
GList *list_delta = NULL;
GList *target_active = NULL;
GList *current_active = NULL;
GList *restart_target_active = NULL;
pe_working_set_t *data_set = NULL;
if (!resource_is_running_on(rsc, host)) {
const char *id = rsc->clone_name?rsc->clone_name:rsc->id;
if(host) {
out->err(out, "%s is not running on %s and so cannot be restarted", id, host);
} else {
out->err(out, "%s is not running anywhere and so cannot be restarted", id);
}
return ENXIO;
}
rsc_id = strdup(rsc->id);
if ((pe_rsc_is_clone(rsc) || pe_bundle_replicas(rsc)) && host) {
stop_via_ban = TRUE;
}
/*
grab full cib
determine originally active resources
disable or ban
poll cib and watch for affected resources to get stopped
without --timeout, calculate the stop timeout for each step and wait for that
if we hit --timeout or the service timeout, re-enable or un-ban, report failure and indicate which resources we couldn't take down
if everything stopped, re-enable or un-ban
poll cib and watch for affected resources to get started
without --timeout, calculate the start timeout for each step and wait for that
if we hit --timeout or the service timeout, report (different) failure and indicate which resources we couldn't bring back up
report success
Optimizations:
- use constraints to determine ordered list of affected resources
- Allow a --no-deps option (aka. --force-restart)
*/
data_set = pe_new_working_set();
if (data_set == NULL) {
crm_perror(LOG_ERR, "Could not allocate working set");
rc = ENOMEM;
goto done;
}
data_set->priv = out;
pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat);
rc = update_dataset(cib, data_set, FALSE);
if(rc != pcmk_rc_ok) {
out->err(out, "Could not get new resource list: %s (%d)", pcmk_strerror(rc), rc);
goto done;
}
restart_target_active = get_active_resources(host, data_set->resources);
current_active = get_active_resources(host, data_set->resources);
dump_list(current_active, "Origin");
if (stop_via_ban) {
/* Stop the clone or bundle instance by banning it from the host */
out->quiet = true;
rc = cli_resource_ban(out, rsc_id, host, move_lifetime, NULL, cib,
cib_options, promoted_role_only);
} else {
/* Stop the resource by setting target-role to Stopped.
* Remember any existing target-role so we can restore it later
* (though it only makes any difference if it's Unpromoted).
*/
char *lookup_id = clone_strip(rsc->id);
find_resource_attr(out, cib, XML_NVPAIR_ATTR_VALUE, lookup_id, NULL, NULL,
NULL, XML_RSC_ATTR_TARGET_ROLE, &orig_target_role);
free(lookup_id);
rc = cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS,
NULL, XML_RSC_ATTR_TARGET_ROLE,
RSC_STOPPED, FALSE, cib, cib_options,
data_set, force);
}
if(rc != pcmk_rc_ok) {
out->err(out, "Could not set target-role for %s: %s (%d)", rsc_id, pcmk_strerror(rc), rc);
if (current_active) {
g_list_free_full(current_active, free);
}
if (restart_target_active) {
g_list_free_full(restart_target_active, free);
}
goto done;
}
rc = update_dataset(cib, data_set, TRUE);
if(rc != pcmk_rc_ok) {
out->err(out, "Could not determine which resources would be stopped");
goto failure;
}
target_active = get_active_resources(host, data_set->resources);
dump_list(target_active, "Target");
list_delta = pcmk__subtract_lists(current_active, target_active, (GCompareFunc) strcmp);
out->info(out, "Waiting for %d resources to stop:", g_list_length(list_delta));
display_list(out, list_delta, " * ");
step_timeout_s = timeout / sleep_interval;
while (list_delta != NULL) {
before = g_list_length(list_delta);
if(timeout_ms == 0) {
step_timeout_s = max_delay_in(data_set, list_delta) / sleep_interval;
}
/* We probably don't need the entire step timeout */
for(lpc = 0; (lpc < step_timeout_s) && (list_delta != NULL); lpc++) {
sleep(sleep_interval);
if(timeout) {
timeout -= sleep_interval;
crm_trace("%ds remaining", timeout);
}
rc = update_dataset(cib, data_set, FALSE);
if(rc != pcmk_rc_ok) {
out->err(out, "Could not determine which resources were stopped");
goto failure;
}
if (current_active) {
g_list_free_full(current_active, free);
}
current_active = get_active_resources(host, data_set->resources);
g_list_free(list_delta);
list_delta = pcmk__subtract_lists(current_active, target_active, (GCompareFunc) strcmp);
dump_list(current_active, "Current");
dump_list(list_delta, "Delta");
}
crm_trace("%d (was %d) resources remaining", g_list_length(list_delta), before);
if(before == g_list_length(list_delta)) {
/* aborted during stop phase, print the contents of list_delta */
out->err(out, "Could not complete shutdown of %s, %d resources remaining", rsc_id, g_list_length(list_delta));
display_list(out, list_delta, " * ");
rc = ETIME;
goto failure;
}
}
if (stop_via_ban) {
rc = cli_resource_clear(rsc_id, host, NULL, cib, cib_options, TRUE, force);
} else if (orig_target_role) {
rc = cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS,
NULL, XML_RSC_ATTR_TARGET_ROLE,
orig_target_role, FALSE, cib,
cib_options, data_set, force);
free(orig_target_role);
orig_target_role = NULL;
} else {
rc = cli_resource_delete_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS,
NULL, XML_RSC_ATTR_TARGET_ROLE, cib,
cib_options, data_set, force);
}
if(rc != pcmk_rc_ok) {
out->err(out, "Could not unset target-role for %s: %s (%d)", rsc_id, pcmk_strerror(rc), rc);
goto done;
}
if (target_active) {
g_list_free_full(target_active, free);
}
target_active = restart_target_active;
list_delta = pcmk__subtract_lists(target_active, current_active, (GCompareFunc) strcmp);
out->info(out, "Waiting for %d resources to start again:", g_list_length(list_delta));
display_list(out, list_delta, " * ");
step_timeout_s = timeout / sleep_interval;
while (waiting_for_starts(list_delta, rsc, host)) {
before = g_list_length(list_delta);
if(timeout_ms == 0) {
step_timeout_s = max_delay_in(data_set, list_delta) / sleep_interval;
}
/* We probably don't need the entire step timeout */
for (lpc = 0; (lpc < step_timeout_s) && waiting_for_starts(list_delta, rsc, host); lpc++) {
sleep(sleep_interval);
if(timeout) {
timeout -= sleep_interval;
crm_trace("%ds remaining", timeout);
}
rc = update_dataset(cib, data_set, FALSE);
if(rc != pcmk_rc_ok) {
out->err(out, "Could not determine which resources were started");
goto failure;
}
if (current_active) {
g_list_free_full(current_active, free);
}
/* It's OK if dependent resources moved to a different node,
* so we check active resources on all nodes.
*/
current_active = get_active_resources(NULL, data_set->resources);
g_list_free(list_delta);
list_delta = pcmk__subtract_lists(target_active, current_active, (GCompareFunc) strcmp);
dump_list(current_active, "Current");
dump_list(list_delta, "Delta");
}
if(before == g_list_length(list_delta)) {
/* aborted during start phase, print the contents of list_delta */
out->err(out, "Could not complete restart of %s, %d resources remaining", rsc_id, g_list_length(list_delta));
display_list(out, list_delta, " * ");
rc = ETIME;
goto failure;
}
}
rc = pcmk_rc_ok;
goto done;
failure:
if (stop_via_ban) {
cli_resource_clear(rsc_id, host, NULL, cib, cib_options, TRUE, force);
} else if (orig_target_role) {
cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL,
XML_RSC_ATTR_TARGET_ROLE, orig_target_role,
FALSE, cib, cib_options, data_set, force);
free(orig_target_role);
} else {
cli_resource_delete_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL,
XML_RSC_ATTR_TARGET_ROLE, cib, cib_options,
data_set, force);
}
done:
if (list_delta) {
g_list_free(list_delta);
}
if (current_active) {
g_list_free_full(current_active, free);
}
if (target_active && (target_active != restart_target_active)) {
g_list_free_full(target_active, free);
}
if (restart_target_active) {
g_list_free_full(restart_target_active, free);
}
free(rsc_id);
pe_free_working_set(data_set);
return rc;
}
static inline bool action_is_pending(pe_action_t *action)
{
if (pcmk_any_flags_set(action->flags, pe_action_optional|pe_action_pseudo)
|| !pcmk_is_set(action->flags, pe_action_runnable)
|| pcmk__str_eq("notify", action->task, pcmk__str_casei)) {
return false;
}
return true;
}
/*!
* \internal
* \brief Return TRUE if any actions in a list are pending
*
* \param[in] actions List of actions to check
*
* \return TRUE if any actions in the list are pending, FALSE otherwise
*/
static bool
actions_are_pending(GList *actions)
{
GList *action;
for (action = actions; action != NULL; action = action->next) {
pe_action_t *a = (pe_action_t *)action->data;
if (action_is_pending(a)) {
crm_notice("Waiting for %s (flags=%#.8x)", a->uuid, a->flags);
return TRUE;
}
}
return FALSE;
}
static void
print_pending_actions(pcmk__output_t *out, GList *actions)
{
GList *action;
out->info(out, "Pending actions:");
for (action = actions; action != NULL; action = action->next) {
pe_action_t *a = (pe_action_t *) action->data;
if (!action_is_pending(a)) {
continue;
}
if (a->node) {
out->info(out, "\tAction %d: %s\ton %s", a->id, a->uuid, a->node->details->uname);
} else {
out->info(out, "\tAction %d: %s", a->id, a->uuid);
}
}
}
/* For --wait, timeout (in seconds) to use if caller doesn't specify one */
#define WAIT_DEFAULT_TIMEOUT_S (60 * 60)
/* For --wait, how long to sleep between cluster state checks */
#define WAIT_SLEEP_S (2)
/*!
* \internal
* \brief Wait until all pending cluster actions are complete
*
* This waits until either the CIB's transition graph is idle or a timeout is
* reached.
*
* \param[in] timeout_ms Consider failed if actions do not complete in this time
* (specified in milliseconds, but one-second granularity
* is actually used; if 0, a default will be used)
* \param[in] cib Connection to the CIB manager
*
* \return Standard Pacemaker return code
*/
int
wait_till_stable(pcmk__output_t *out, int timeout_ms, cib_t * cib)
{
pe_working_set_t *data_set = NULL;
int rc = pcmk_rc_ok;
int timeout_s = timeout_ms? ((timeout_ms + 999) / 1000) : WAIT_DEFAULT_TIMEOUT_S;
time_t expire_time = time(NULL) + timeout_s;
time_t time_diff;
bool printed_version_warning = out->is_quiet(out); // i.e. don't print if quiet
data_set = pe_new_working_set();
if (data_set == NULL) {
return ENOMEM;
}
pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat);
do {
/* Abort if timeout is reached */
time_diff = expire_time - time(NULL);
if (time_diff > 0) {
crm_info("Waiting up to %ld seconds for cluster actions to complete", time_diff);
} else {
print_pending_actions(out, data_set->actions);
pe_free_working_set(data_set);
return ETIME;
}
if (rc == pcmk_rc_ok) { /* this avoids sleep on first loop iteration */
sleep(WAIT_SLEEP_S);
}
/* Get latest transition graph */
pe_reset_working_set(data_set);
rc = update_working_set_from_cib(out, data_set, cib);
if (rc != pcmk_rc_ok) {
pe_free_working_set(data_set);
return rc;
}
pcmk__schedule_actions(data_set, data_set->input, NULL);
if (!printed_version_warning) {
/* If the DC has a different version than the local node, the two
* could come to different conclusions about what actions need to be
* done. Warn the user in this case.
*
* @TODO A possible long-term solution would be to reimplement the
* wait as a new controller operation that would be forwarded to the
* DC. However, that would have potential problems of its own.
*/
const char *dc_version = g_hash_table_lookup(data_set->config_hash,
"dc-version");
if (!pcmk__str_eq(dc_version, PACEMAKER_VERSION "-" BUILD_VERSION, pcmk__str_casei)) {
out->info(out, "warning: wait option may not work properly in "
"mixed-version cluster");
printed_version_warning = TRUE;
}
}
} while (actions_are_pending(data_set->actions));
pe_free_working_set(data_set);
return rc;
}
static const char *
get_action(const char *rsc_action) {
const char *action = NULL;
if (pcmk__str_eq(rsc_action, "validate", pcmk__str_casei)) {
action = "validate-all";
} else if (pcmk__str_eq(rsc_action, "force-check", pcmk__str_casei)) {
action = "monitor";
} else if (pcmk__strcase_any_of(rsc_action, "force-start", "force-stop",
"force-demote", "force-promote", NULL)) {
action = rsc_action+6;
} else {
action = rsc_action;
}
return action;
}
/*!
* \brief Set up environment variables as expected by resource agents
*
* When the cluster executes resource agents, it adds certain environment
* variables (directly or via resource meta-attributes) expected by some
* resource agents. Add the essential ones that many resource agents expect, so
* the behavior is the same for command-line execution.
*
* \param[in] params Resource parameters that will be passed to agent
* \param[in] timeout_ms Action timeout (in milliseconds)
* \param[in] check_level OCF check level
* \param[in] verbosity Verbosity level
*/
static void
set_agent_environment(GHashTable *params, int timeout_ms, int check_level,
int verbosity)
{
g_hash_table_insert(params, strdup("CRM_meta_timeout"),
crm_strdup_printf("%d", timeout_ms));
g_hash_table_insert(params, strdup(XML_ATTR_CRM_VERSION),
strdup(CRM_FEATURE_SET));
if (check_level >= 0) {
char *level = crm_strdup_printf("%d", check_level);
setenv("OCF_CHECK_LEVEL", level, 1);
free(level);
}
setenv("HA_debug", (verbosity > 0)? "1" : "0", 1);
if (verbosity > 1) {
setenv("OCF_TRACE_RA", "1", 1);
}
/* A resource agent using the standard ocf-shellfuncs library will not print
* messages to stderr if it doesn't have a controlling terminal (e.g. if
* crm_resource is called via script or ssh). This forces it to do so.
*/
setenv("OCF_TRACE_FILE", "/dev/stderr", 0);
}
/*!
* \internal
* \brief Apply command-line overrides to resource parameters
*
* \param[in] params Parameters to be passed to agent
* \param[in] overrides Parameters to override (or NULL if none)
*/
static void
apply_overrides(GHashTable *params, GHashTable *overrides)
{
if (overrides != NULL) {
GHashTableIter iter;
char *name = NULL;
char *value = NULL;
g_hash_table_iter_init(&iter, overrides);
while (g_hash_table_iter_next(&iter, (gpointer *) &name,
(gpointer *) &value)) {
g_hash_table_replace(params, strdup(name), strdup(value));
}
}
}
crm_exit_t
cli_resource_execute_from_params(pcmk__output_t *out, const char *rsc_name,
const char *rsc_class, const char *rsc_prov,
const char *rsc_type, const char *rsc_action,
GHashTable *params, GHashTable *override_hash,
int timeout_ms, int resource_verbose, gboolean force,
int check_level)
{
const char *class = rsc_class;
const char *action = get_action(rsc_action);
crm_exit_t exit_code = CRM_EX_OK;
svc_action_t *op = NULL;
// If no timeout was provided, use the same default as the cluster
if (timeout_ms == 0) {
timeout_ms = crm_get_msec(CRM_DEFAULT_OP_TIMEOUT_S);
}
set_agent_environment(params, timeout_ms, check_level, resource_verbose);
apply_overrides(params, override_hash);
op = services__create_resource_action(rsc_name? rsc_name : "test",
rsc_class, rsc_prov, rsc_type, action,
0, timeout_ms, params, 0);
if (op == NULL) {
out->err(out, "Could not execute %s using %s%s%s:%s: %s",
action, rsc_class, (rsc_prov? ":" : ""),
(rsc_prov? rsc_prov : ""), rsc_type, strerror(ENOMEM));
g_hash_table_destroy(params);
return CRM_EX_OSERR;
}
if (pcmk__str_eq(rsc_class, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei)) {
class = resources_find_service_class(rsc_type);
}
if (!pcmk__strcase_any_of(class, PCMK_RESOURCE_CLASS_OCF,
PCMK_RESOURCE_CLASS_LSB, NULL)) {
services__set_result(op, CRM_EX_UNIMPLEMENT_FEATURE, PCMK_EXEC_ERROR,
"Manual execution of this standard is unsupported");
}
if (op->rc != PCMK_OCF_UNKNOWN) {
exit_code = op->rc;
goto done;
}
services_action_sync(op);
// Map results to OCF codes for consistent reporting to user
{
enum ocf_exitcode ocf_code = services_result2ocf(class, action, op->rc);
// Cast variable instead of function return to keep compilers happy
exit_code = (crm_exit_t) ocf_code;
}
done:
out->message(out, "resource-agent-action", resource_verbose, rsc_class,
rsc_prov, rsc_type, rsc_name, rsc_action, override_hash,
exit_code, op->status, services__exit_reason(op),
op->stdout_data, op->stderr_data);
services_action_free(op);
return exit_code;
}
crm_exit_t
cli_resource_execute(pe_resource_t *rsc, const char *requested_name,
const char *rsc_action, GHashTable *override_hash,
int timeout_ms, cib_t * cib, pe_working_set_t *data_set,
int resource_verbose, gboolean force, int check_level)
{
pcmk__output_t *out = data_set->priv;
crm_exit_t exit_code = CRM_EX_OK;
const char *rid = NULL;
const char *rtype = NULL;
const char *rprov = NULL;
const char *rclass = NULL;
GHashTable *params = NULL;
if (pcmk__strcase_any_of(rsc_action, "force-start", "force-demote",
"force-promote", NULL)) {
if(pe_rsc_is_clone(rsc)) {
GList *nodes = cli_resource_search(rsc, requested_name, data_set);
if(nodes != NULL && force == FALSE) {
out->err(out, "It is not safe to %s %s here: the cluster claims it is already active",
rsc_action, rsc->id);
out->err(out, "Try setting target-role=Stopped first or specifying "
"the force option");
return CRM_EX_UNSAFE;
}
g_list_free_full(nodes, free);
}
}
if(pe_rsc_is_clone(rsc)) {
/* Grab the first child resource in the hope it's not a group */
rsc = rsc->children->data;
}
if(rsc->variant == pe_group) {
out->err(out, "Sorry, the %s option doesn't support group resources", rsc_action);
return CRM_EX_UNIMPLEMENT_FEATURE;
} else if (rsc->variant == pe_container || pe_rsc_is_bundled(rsc)) {
out->err(out, "Sorry, the %s option doesn't support bundled resources", rsc_action);
return CRM_EX_UNIMPLEMENT_FEATURE;
}
rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
rprov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER);
rtype = crm_element_value(rsc->xml, XML_ATTR_TYPE);
params = generate_resource_params(rsc, NULL /* @TODO use local node */,
data_set);
if (timeout_ms == 0) {
timeout_ms = pe_get_configured_timeout(rsc, get_action(rsc_action), data_set);
}
rid = pe_rsc_is_anon_clone(rsc->parent)? requested_name : rsc->id;
exit_code = cli_resource_execute_from_params(out, rid, rclass, rprov, rtype, rsc_action,
params, override_hash, timeout_ms,
resource_verbose, force, check_level);
return exit_code;
}
// \return Standard Pacemaker return code
int
cli_resource_move(pe_resource_t *rsc, const char *rsc_id, const char *host_name,
const char *move_lifetime, cib_t *cib, int cib_options,
pe_working_set_t *data_set, gboolean promoted_role_only,
gboolean force)
{
pcmk__output_t *out = data_set->priv;
int rc = pcmk_rc_ok;
unsigned int count = 0;
pe_node_t *current = NULL;
pe_node_t *dest = pe_find_node(data_set->nodes, host_name);
bool cur_is_dest = FALSE;
if (dest == NULL) {
return pcmk_rc_node_unknown;
}
if (promoted_role_only && !pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
pe_resource_t *p = uber_parent(rsc);
if (pcmk_is_set(p->flags, pe_rsc_promotable)) {
out->info(out, "Using parent '%s' for move instead of '%s'.", rsc->id, rsc_id);
rsc_id = p->id;
rsc = p;
} else {
out->info(out, "Ignoring master option: %s is not promotable", rsc_id);
promoted_role_only = FALSE;
}
}
current = pe__find_active_requires(rsc, &count);
if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
GList *iter = NULL;
unsigned int promoted_count = 0;
pe_node_t *promoted_node = NULL;
for(iter = rsc->children; iter; iter = iter->next) {
pe_resource_t *child = (pe_resource_t *)iter->data;
enum rsc_role_e child_role = child->fns->state(child, TRUE);
if (child_role == RSC_ROLE_PROMOTED) {
rsc = child;
promoted_node = pe__current_node(child);
promoted_count++;
}
}
if (promoted_role_only || (promoted_count != 0)) {
count = promoted_count;
current = promoted_node;
}
}
if (count > 1) {
if (pe_rsc_is_clone(rsc)) {
current = NULL;
} else {
return pcmk_rc_multiple;
}
}
if (current && (current->details == dest->details)) {
cur_is_dest = TRUE;
if (force) {
crm_info("%s is already %s on %s, reinforcing placement with location constraint.",
rsc_id, promoted_role_only?"promoted":"active", dest->details->uname);
} else {
return pcmk_rc_already;
}
}
/* Clear any previous prefer constraints across all nodes. */
cli_resource_clear(rsc_id, NULL, data_set->nodes, cib, cib_options, FALSE, force);
/* Clear any previous ban constraints on 'dest'. */
cli_resource_clear(rsc_id, dest->details->uname, data_set->nodes, cib,
cib_options, TRUE, force);
/* Record an explicit preference for 'dest' */
rc = cli_resource_prefer(out, rsc_id, dest->details->uname, move_lifetime,
cib, cib_options, promoted_role_only);
crm_trace("%s%s now prefers node %s%s",
rsc->id, (promoted_role_only? " (promoted)" : ""),
dest->details->uname, force?"(forced)":"");
/* only ban the previous location if current location != destination location.
* it is possible to use -M to enforce a location without regard of where the
* resource is currently located */
if(force && (cur_is_dest == FALSE)) {
/* Ban the original location if possible */
if(current) {
(void)cli_resource_ban(out, rsc_id, current->details->uname, move_lifetime,
NULL, cib, cib_options, promoted_role_only);
} else if(count > 1) {
out->info(out, "Resource '%s' is currently %s in %d locations. "
"One may now move to %s",
rsc_id, (promoted_role_only? "promoted" : "active"),
count, dest->details->uname);
out->info(out, "To prevent '%s' from being %s at a specific location, "
"specify a node.",
rsc_id, (promoted_role_only? "promoted" : "active"));
} else {
crm_trace("Not banning %s from its current location: not active", rsc_id);
}
}
return rc;
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 3:50 AM (4 h, 42 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1018141
Default Alt Text
(163 KB)

Event Timeline