diff --git a/lib/pacemaker/pcmk_sched_transition.c b/lib/pacemaker/pcmk_sched_transition.c index 9e990710a6..6d8f499402 100644 --- a/lib/pacemaker/pcmk_sched_transition.c +++ b/lib/pacemaker/pcmk_sched_transition.c @@ -1,931 +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 #include #include #include #include #include #include #include #include #include // lrmd_event_data_t, lrmd_free_event() #include #include #include #include #include #include #include #include "libpacemaker_private.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; 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(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); } /*! * \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(out, cib_node, name, "value++"); free(name); name = pcmk__lastfailure_name(resource, task, interval_ms); 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; } /*! * \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); } /*! * \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 != 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=%d)", node, rc); } free(xpath); CRM_ASSERT(rc == pcmk_ok); return cib_object; } /*! * \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 = pcmk__inject_node(cib_conn, node, NULL); if (up) { 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 { 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; } /*! * \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; 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) != 0) { cib_resource = find_resource_xml(cib_node, lrm_name); if (cib_resource != NULL) { return cib_resource; } } 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)) { 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)) { // @TODO query configuration for provider out->err(out, "Please specify the provider for resource %s", resource); return NULL; } 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(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(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(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; 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 = 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 = 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 = 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(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(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(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(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 = pcmk__inject_node(cib, node, NULL); CRM_ASSERT(cib_node != NULL); pcmk__inject_failcount(out, cib_node, resource, task, interval_ms, outcome); 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 = 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 = pcmk__inject_node(fake_cib, node, - ((router_node == NULL)? uuid: node)); - CRM_ASSERT(cib_node != NULL); - - cib_resource = pcmk__inject_resource_history(out, 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); - 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(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 = pcmk__inject_node_state_change(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..babeb36289 100644 --- a/lib/pacemaker/pcmk_simulate.c +++ b/lib/pacemaker/pcmk_simulate.c @@ -1,560 +1,829 @@ /* * 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 #include #include #include #include #include #include #include #include #include #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 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) { action_host = action->node->details->uname; } else if (!pcmk_is_set(action->flags, pe_action_pseudo)) { action_host = ""; } if (pcmk__str_eq(action->task, RSC_CANCEL, pcmk__str_casei)) { prefix = "Cancel "; task = action->cancel_task; } if (action->rsc && action->rsc->clone_name) { clone_name = action->rsc->clone_name; } if (clone_name) { 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"); 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); } 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); } else if (action->rsc && 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); } 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; } 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) { 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); } 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); } 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); 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) { 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) { 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); 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); } 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) { 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); } } } +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 = pcmk__inject_node(fake_cib, node, + ((router_node == NULL)? uuid: node)); + CRM_ASSERT(cib_node != NULL); + + cib_resource = pcmk__inject_resource_history(out, 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); + 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(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 = pcmk__inject_node_state_change(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; +} + 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) { 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, 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); 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 (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)) { PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok); 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)); 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 (!out->is_quiet(out)) { pcmk__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__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); }