root/lib/pacemaker/pcmk_graph_producer.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. add_node_to_xml_by_id
  2. add_node_to_xml
  3. add_maintenance_nodes
  4. add_maintenance_update
  5. add_downed_nodes
  6. clone_op_key
  7. add_node_details
  8. add_resource_details
  9. add_action_attributes
  10. create_graph_action
  11. should_add_action_to_graph
  12. ordering_can_change_actions
  13. should_add_input_to_graph
  14. pcmk__graph_has_loop
  15. create_graph_synapse
  16. add_action_to_graph
  17. pcmk__log_transition_summary
  18. pcmk__add_rsc_actions_to_graph
  19. pcmk__create_graph

   1 /*
   2  * Copyright 2004-2023 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU General Public License version 2
   7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <sys/param.h>
  13 #include <crm/crm.h>
  14 #include <crm/cib.h>
  15 #include <crm/msg_xml.h>
  16 #include <crm/common/xml.h>
  17 
  18 #include <glib.h>
  19 
  20 #include <pacemaker-internal.h>
  21 
  22 #include "libpacemaker_private.h"
  23 
  24 // Convenience macros for logging action properties
  25 
  26 #define action_type_str(flags) \
  27     (pcmk_is_set((flags), pe_action_pseudo)? "pseudo-action" : "action")
  28 
  29 #define action_optional_str(flags) \
  30     (pcmk_is_set((flags), pe_action_optional)? "optional" : "required")
  31 
  32 #define action_runnable_str(flags) \
  33     (pcmk_is_set((flags), pe_action_runnable)? "runnable" : "unrunnable")
  34 
  35 #define action_node_str(a) \
  36     (((a)->node == NULL)? "no node" : (a)->node->details->uname)
  37 
  38 /*!
  39  * \internal
  40  * \brief Add an XML node tag for a specified ID
  41  *
  42  * \param[in]     id      Node UUID to add
  43  * \param[in,out] xml     Parent XML tag to add to
  44  */
  45 static xmlNode*
  46 add_node_to_xml_by_id(const char *id, xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
  47 {
  48     xmlNode *node_xml;
  49 
  50     node_xml = create_xml_node(xml, XML_CIB_TAG_NODE);
  51     crm_xml_add(node_xml, XML_ATTR_ID, id);
  52 
  53     return node_xml;
  54 }
  55 
  56 /*!
  57  * \internal
  58  * \brief Add an XML node tag for a specified node
  59  *
  60  * \param[in]     node  Node to add
  61  * \param[in,out] xml   XML to add node to
  62  */
  63 static void
  64 add_node_to_xml(const pe_node_t *node, void *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
  65 {
  66     add_node_to_xml_by_id(node->details->id, (xmlNode *) xml);
  67 }
  68 
  69 /*!
  70  * \internal
  71  * \brief Add XML with nodes that need an update of their maintenance state
  72  *
  73  * \param[in,out] xml       Parent XML tag to add to
  74  * \param[in]     data_set  Working set for cluster
  75  */
  76 static int
  77 add_maintenance_nodes(xmlNode *xml, const pe_working_set_t *data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
  78 {
  79     GList *gIter = NULL;
  80     xmlNode *maintenance =
  81         xml?create_xml_node(xml, XML_GRAPH_TAG_MAINTENANCE):NULL;
  82     int count = 0;
  83 
  84     for (gIter = data_set->nodes; gIter != NULL;
  85          gIter = gIter->next) {
  86         pe_node_t *node = (pe_node_t *) gIter->data;
  87         struct pe_node_shared_s *details = node->details;
  88 
  89         if (!pe__is_guest_or_remote_node(node)) {
  90             continue; /* just remote nodes need to know atm */
  91         }
  92 
  93         if (details->maintenance != details->remote_maintenance) {
  94             if (maintenance) {
  95                 crm_xml_add(
  96                     add_node_to_xml_by_id(node->details->id, maintenance),
  97                     XML_NODE_IS_MAINTENANCE, details->maintenance?"1":"0");
  98             }
  99             count++;
 100         }
 101     }
 102     crm_trace("%s %d nodes to adjust maintenance-mode "
 103               "to transition", maintenance?"Added":"Counted", count);
 104     return count;
 105 }
 106 
 107 /*!
 108  * \internal
 109  * \brief Add pseudo action with nodes needing maintenance state update
 110  *
 111  * \param[in,out] data_set  Working set for cluster
 112  */
 113 static void
 114 add_maintenance_update(pe_working_set_t *data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 115 {
 116     pe_action_t *action = NULL;
 117 
 118     if (add_maintenance_nodes(NULL, data_set)) {
 119         crm_trace("adding maintenance state update pseudo action");
 120         action = get_pseudo_op(CRM_OP_MAINTENANCE_NODES, data_set);
 121         pe__set_action_flags(action, pe_action_print_always);
 122     }
 123 }
 124 
 125 /*!
 126  * \internal
 127  * \brief Add XML with nodes that an action is expected to bring down
 128  *
 129  * If a specified action is expected to bring any nodes down, add an XML block
 130  * with their UUIDs. When a node is lost, this allows the controller to
 131  * determine whether it was expected.
 132  *
 133  * \param[in,out] xml       Parent XML tag to add to
 134  * \param[in]     action    Action to check for downed nodes
 135  * \param[in]     data_set  Working set for cluster
 136  */
 137 static void
 138 add_downed_nodes(xmlNode *xml, const pe_action_t *action,
     /* [previous][next][first][last][top][bottom][index][help] */
 139                  const pe_working_set_t *data_set)
 140 {
 141     CRM_CHECK(xml && action && action->node && data_set, return);
 142 
 143     if (pcmk__str_eq(action->task, CRM_OP_SHUTDOWN, pcmk__str_casei)) {
 144 
 145         /* Shutdown makes the action's node down */
 146         xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED);
 147         add_node_to_xml_by_id(action->node->details->id, downed);
 148 
 149     } else if (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei)) {
 150 
 151         /* Fencing makes the action's node and any hosted guest nodes down */
 152         const char *fence = g_hash_table_lookup(action->meta, "stonith_action");
 153 
 154         if (pcmk__is_fencing_action(fence)) {
 155             xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED);
 156             add_node_to_xml_by_id(action->node->details->id, downed);
 157             pe_foreach_guest_node(data_set, action->node, add_node_to_xml, downed);
 158         }
 159 
 160     } else if (action->rsc && action->rsc->is_remote_node
 161                && pcmk__str_eq(action->task, CRMD_ACTION_STOP, pcmk__str_casei)) {
 162 
 163         /* Stopping a remote connection resource makes connected node down,
 164          * unless it's part of a migration
 165          */
 166         GList *iter;
 167         pe_action_t *input;
 168         gboolean migrating = FALSE;
 169 
 170         for (iter = action->actions_before; iter != NULL; iter = iter->next) {
 171             input = ((pe_action_wrapper_t *) iter->data)->action;
 172             if (input->rsc && pcmk__str_eq(action->rsc->id, input->rsc->id, pcmk__str_casei)
 173                 && pcmk__str_eq(input->task, CRMD_ACTION_MIGRATED, pcmk__str_casei)) {
 174                 migrating = TRUE;
 175                 break;
 176             }
 177         }
 178         if (!migrating) {
 179             xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED);
 180             add_node_to_xml_by_id(action->rsc->id, downed);
 181         }
 182     }
 183 }
 184 
 185 /*!
 186  * \internal
 187  * \brief Create a transition graph operation key for a clone action
 188  *
 189  * \param[in] action       Clone action
 190  * \param[in] interval_ms  Action interval in milliseconds
 191  *
 192  * \return Newly allocated string with transition graph operation key
 193  */
 194 static char *
 195 clone_op_key(const pe_action_t *action, guint interval_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
 196 {
 197     if (pcmk__str_eq(action->task, RSC_NOTIFY, pcmk__str_none)) {
 198         const char *n_type = g_hash_table_lookup(action->meta, "notify_type");
 199         const char *n_task = g_hash_table_lookup(action->meta,
 200                                                  "notify_operation");
 201 
 202         CRM_LOG_ASSERT((n_type != NULL) && (n_task != NULL));
 203         return pcmk__notify_key(action->rsc->clone_name, n_type, n_task);
 204 
 205     } else if (action->cancel_task != NULL) {
 206         return pcmk__op_key(action->rsc->clone_name, action->cancel_task,
 207                             interval_ms);
 208     } else {
 209         return pcmk__op_key(action->rsc->clone_name, action->task, interval_ms);
 210     }
 211 }
 212 
 213 /*!
 214  * \internal
 215  * \brief Add node details to transition graph action XML
 216  *
 217  * \param[in]     action  Scheduled action
 218  * \param[in,out] xml     Transition graph action XML for \p action
 219  */
 220 static void
 221 add_node_details(const pe_action_t *action, xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 222 {
 223     pe_node_t *router_node = pcmk__connection_host_for_action(action);
 224 
 225     crm_xml_add(xml, XML_LRM_ATTR_TARGET, action->node->details->uname);
 226     crm_xml_add(xml, XML_LRM_ATTR_TARGET_UUID, action->node->details->id);
 227     if (router_node != NULL) {
 228         crm_xml_add(xml, XML_LRM_ATTR_ROUTER_NODE, router_node->details->uname);
 229     }
 230 }
 231 
 232 /*!
 233  * \internal
 234  * \brief Add resource details to transition graph action XML
 235  *
 236  * \param[in]     action      Scheduled action
 237  * \param[in,out] action_xml  Transition graph action XML for \p action
 238  */
 239 static void
 240 add_resource_details(const pe_action_t *action, xmlNode *action_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 241 {
 242     xmlNode *rsc_xml = NULL;
 243     const char *attr_list[] = {
 244         XML_AGENT_ATTR_CLASS,
 245         XML_AGENT_ATTR_PROVIDER,
 246         XML_ATTR_TYPE
 247     };
 248 
 249     /* If a resource is locked to a node via shutdown-lock, mark its actions
 250      * so the controller can preserve the lock when the action completes.
 251      */
 252     if (pcmk__action_locks_rsc_to_node(action)) {
 253         crm_xml_add_ll(action_xml, XML_CONFIG_ATTR_SHUTDOWN_LOCK,
 254                        (long long) action->rsc->lock_time);
 255     }
 256 
 257     // List affected resource
 258 
 259     rsc_xml = create_xml_node(action_xml, crm_element_name(action->rsc->xml));
 260     if (pcmk_is_set(action->rsc->flags, pe_rsc_orphan)
 261         && (action->rsc->clone_name != NULL)) {
 262         /* Use the numbered instance name here, because if there is more
 263          * than one instance on a node, we need to make sure the command
 264          * goes to the right one.
 265          *
 266          * This is important even for anonymous clones, because the clone's
 267          * unique meta-attribute might have just been toggled from on to
 268          * off.
 269          */
 270         crm_debug("Using orphan clone name %s instead of %s",
 271                   action->rsc->id, action->rsc->clone_name);
 272         crm_xml_add(rsc_xml, XML_ATTR_ID, action->rsc->clone_name);
 273         crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->id);
 274 
 275     } else if (!pcmk_is_set(action->rsc->flags, pe_rsc_unique)) {
 276         const char *xml_id = ID(action->rsc->xml);
 277 
 278         crm_debug("Using anonymous clone name %s for %s (aka %s)",
 279                   xml_id, action->rsc->id, action->rsc->clone_name);
 280 
 281         /* ID is what we'd like client to use
 282          * ID_LONG is what they might know it as instead
 283          *
 284          * ID_LONG is only strictly needed /here/ during the
 285          * transition period until all nodes in the cluster
 286          * are running the new software /and/ have rebooted
 287          * once (meaning that they've only ever spoken to a DC
 288          * supporting this feature).
 289          *
 290          * If anyone toggles the unique flag to 'on', the
 291          * 'instance free' name will correspond to an orphan
 292          * and fall into the clause above instead
 293          */
 294         crm_xml_add(rsc_xml, XML_ATTR_ID, xml_id);
 295         if ((action->rsc->clone_name != NULL)
 296             && !pcmk__str_eq(xml_id, action->rsc->clone_name,
 297                              pcmk__str_none)) {
 298             crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->clone_name);
 299         } else {
 300             crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->id);
 301         }
 302 
 303     } else {
 304         CRM_ASSERT(action->rsc->clone_name == NULL);
 305         crm_xml_add(rsc_xml, XML_ATTR_ID, action->rsc->id);
 306     }
 307 
 308     for (int lpc = 0; lpc < PCMK__NELEM(attr_list); lpc++) {
 309         crm_xml_add(rsc_xml, attr_list[lpc],
 310                     g_hash_table_lookup(action->rsc->meta, attr_list[lpc]));
 311     }
 312 }
 313 
 314 /*!
 315  * \internal
 316  * \brief Add action attributes to transition graph action XML
 317  *
 318  * \param[in,out] action      Scheduled action
 319  * \param[in,out] action_xml  Transition graph action XML for \p action
 320  */
 321 static void
 322 add_action_attributes(pe_action_t *action, xmlNode *action_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 323 {
 324     xmlNode *args_xml = NULL;
 325 
 326     /* We create free-standing XML to start, so we can sort the attributes
 327      * before adding it to action_xml, which keeps the scheduler regression
 328      * test graphs comparable.
 329      */
 330     args_xml = create_xml_node(NULL, XML_TAG_ATTRS);
 331 
 332     crm_xml_add(args_xml, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
 333     g_hash_table_foreach(action->extra, hash2field, args_xml);
 334 
 335     if ((action->rsc != NULL) && (action->node != NULL)) {
 336         // Get the resource instance attributes, evaluated properly for node
 337         GHashTable *params = pe_rsc_params(action->rsc, action->node,
 338                                            action->rsc->cluster);
 339 
 340         pcmk__substitute_remote_addr(action->rsc, params);
 341 
 342         g_hash_table_foreach(params, hash2smartfield, args_xml);
 343 
 344     } else if ((action->rsc != NULL) && (action->rsc->variant <= pe_native)) {
 345         GHashTable *params = pe_rsc_params(action->rsc, NULL,
 346                                            action->rsc->cluster);
 347 
 348         g_hash_table_foreach(params, hash2smartfield, args_xml);
 349     }
 350 
 351     g_hash_table_foreach(action->meta, hash2metafield, args_xml);
 352     if (action->rsc != NULL) {
 353         pe_resource_t *parent = action->rsc;
 354 
 355         while (parent != NULL) {
 356             parent->cmds->add_graph_meta(parent, args_xml);
 357             parent = parent->parent;
 358         }
 359 
 360         pcmk__add_bundle_meta_to_xml(args_xml, action);
 361 
 362     } else if (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_none)
 363                && (action->node != NULL)) {
 364         /* Pass the node's attributes as meta-attributes.
 365          *
 366          * @TODO: Determine whether it is still necessary to do this. It was
 367          * added in 33d99707, probably for the libfence-based implementation in
 368          * c9a90bd, which is no longer used.
 369          */
 370         g_hash_table_foreach(action->node->details->attrs, hash2metafield, args_xml);
 371     }
 372 
 373     sorted_xml(args_xml, action_xml, FALSE);
 374     free_xml(args_xml);
 375 }
 376 
 377 /*!
 378  * \internal
 379  * \brief Create the transition graph XML for a scheduled action
 380  *
 381  * \param[in,out] parent        Parent XML element to add action to
 382  * \param[in,out] action        Scheduled action
 383  * \param[in]     skip_details  If false, add action details as sub-elements
 384  * \param[in]     data_set      Cluster working set
 385  */
 386 static void
 387 create_graph_action(xmlNode *parent, pe_action_t *action, bool skip_details,
     /* [previous][next][first][last][top][bottom][index][help] */
 388                     const pe_working_set_t *data_set)
 389 {
 390     bool needs_node_info = true;
 391     bool needs_maintenance_info = false;
 392     xmlNode *action_xml = NULL;
 393 
 394     if ((action == NULL) || (data_set == NULL)) {
 395         return;
 396     }
 397 
 398     // Create the top-level element based on task
 399 
 400     if (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei)) {
 401         /* All fences need node info; guest node fences are pseudo-events */
 402         action_xml = create_xml_node(parent,
 403                                      pcmk_is_set(action->flags, pe_action_pseudo)?
 404                                      XML_GRAPH_TAG_PSEUDO_EVENT :
 405                                      XML_GRAPH_TAG_CRM_EVENT);
 406 
 407     } else if (pcmk__str_any_of(action->task,
 408                                 CRM_OP_SHUTDOWN,
 409                                 CRM_OP_CLEAR_FAILCOUNT, NULL)) {
 410         action_xml = create_xml_node(parent, XML_GRAPH_TAG_CRM_EVENT);
 411 
 412     } else if (pcmk__str_eq(action->task, CRM_OP_LRM_DELETE, pcmk__str_none)) {
 413         // CIB-only clean-up for shutdown locks
 414         action_xml = create_xml_node(parent, XML_GRAPH_TAG_CRM_EVENT);
 415         crm_xml_add(action_xml, PCMK__XA_MODE, XML_TAG_CIB);
 416 
 417     } else if (pcmk_is_set(action->flags, pe_action_pseudo)) {
 418         if (pcmk__str_eq(action->task, CRM_OP_MAINTENANCE_NODES,
 419                          pcmk__str_none)) {
 420             needs_maintenance_info = true;
 421         }
 422         action_xml = create_xml_node(parent, XML_GRAPH_TAG_PSEUDO_EVENT);
 423         needs_node_info = false;
 424 
 425     } else {
 426         action_xml = create_xml_node(parent, XML_GRAPH_TAG_RSC_OP);
 427     }
 428 
 429     crm_xml_add_int(action_xml, XML_ATTR_ID, action->id);
 430     crm_xml_add(action_xml, XML_LRM_ATTR_TASK, action->task);
 431 
 432     if ((action->rsc != NULL) && (action->rsc->clone_name != NULL)) {
 433         char *clone_key = NULL;
 434         guint interval_ms;
 435 
 436         if (pcmk__guint_from_hash(action->meta, XML_LRM_ATTR_INTERVAL_MS, 0,
 437                                   &interval_ms) != pcmk_rc_ok) {
 438             interval_ms = 0;
 439         }
 440         clone_key = clone_op_key(action, interval_ms);
 441         crm_xml_add(action_xml, XML_LRM_ATTR_TASK_KEY, clone_key);
 442         crm_xml_add(action_xml, "internal_" XML_LRM_ATTR_TASK_KEY, action->uuid);
 443         free(clone_key);
 444     } else {
 445         crm_xml_add(action_xml, XML_LRM_ATTR_TASK_KEY, action->uuid);
 446     }
 447 
 448     if (needs_node_info && (action->node != NULL)) {
 449         add_node_details(action, action_xml);
 450         g_hash_table_insert(action->meta, strdup(XML_LRM_ATTR_TARGET),
 451                             strdup(action->node->details->uname));
 452         g_hash_table_insert(action->meta, strdup(XML_LRM_ATTR_TARGET_UUID),
 453                             strdup(action->node->details->id));
 454     }
 455 
 456     if (skip_details) {
 457         return;
 458     }
 459 
 460     if ((action->rsc != NULL)
 461         && !pcmk_is_set(action->flags, pe_action_pseudo)) {
 462 
 463         // This is a real resource action, so add resource details
 464         add_resource_details(action, action_xml);
 465     }
 466 
 467     /* List any attributes in effect */
 468     add_action_attributes(action, action_xml);
 469 
 470     /* List any nodes this action is expected to make down */
 471     if (needs_node_info && (action->node != NULL)) {
 472         add_downed_nodes(action_xml, action, data_set);
 473     }
 474 
 475     if (needs_maintenance_info) {
 476         add_maintenance_nodes(action_xml, data_set);
 477     }
 478 }
 479 
 480 /*!
 481  * \internal
 482  * \brief Check whether an action should be added to the transition graph
 483  *
 484  * \param[in] action  Action to check
 485  *
 486  * \return true if action should be added to graph, otherwise false
 487  */
 488 static bool
 489 should_add_action_to_graph(const pe_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 490 {
 491     if (!pcmk_is_set(action->flags, pe_action_runnable)) {
 492         crm_trace("Ignoring action %s (%d): unrunnable",
 493                   action->uuid, action->id);
 494         return false;
 495     }
 496 
 497     if (pcmk_is_set(action->flags, pe_action_optional)
 498         && !pcmk_is_set(action->flags, pe_action_print_always)) {
 499         crm_trace("Ignoring action %s (%d): optional",
 500                   action->uuid, action->id);
 501         return false;
 502     }
 503 
 504     /* Actions for unmanaged resources should be excluded from the graph,
 505      * with the exception of monitors and cancellation of recurring monitors.
 506      */
 507     if ((action->rsc != NULL)
 508         && !pcmk_is_set(action->rsc->flags, pe_rsc_managed)
 509         && !pcmk__str_eq(action->task, RSC_STATUS, pcmk__str_none)) {
 510         const char *interval_ms_s;
 511 
 512         /* A cancellation of a recurring monitor will get here because the task
 513          * is cancel rather than monitor, but the interval can still be used to
 514          * recognize it. The interval has been normalized to milliseconds by
 515          * this point, so a string comparison is sufficient.
 516          */
 517         interval_ms_s = g_hash_table_lookup(action->meta,
 518                                             XML_LRM_ATTR_INTERVAL_MS);
 519         if (pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches)) {
 520             crm_trace("Ignoring action %s (%d): for unmanaged resource (%s)",
 521                       action->uuid, action->id, action->rsc->id);
 522             return false;
 523         }
 524     }
 525 
 526     /* Always add pseudo-actions, fence actions, and shutdown actions (already
 527      * determined to be required and runnable by this point)
 528      */
 529     if (pcmk_is_set(action->flags, pe_action_pseudo)
 530         || pcmk__strcase_any_of(action->task, CRM_OP_FENCE, CRM_OP_SHUTDOWN,
 531                                 NULL)) {
 532         return true;
 533     }
 534 
 535     if (action->node == NULL) {
 536         pe_err("Skipping action %s (%d) "
 537                "because it was not allocated to a node (bug?)",
 538                action->uuid, action->id);
 539         pcmk__log_action("Unallocated", action, false);
 540         return false;
 541     }
 542 
 543     if (pcmk_is_set(action->flags, pe_action_dc)) {
 544         crm_trace("Action %s (%d) should be dumped: "
 545                   "can run on DC instead of %s",
 546                   action->uuid, action->id, pe__node_name(action->node));
 547 
 548     } else if (pe__is_guest_node(action->node)
 549                && !action->node->details->remote_requires_reset) {
 550         crm_trace("Action %s (%d) should be dumped: "
 551                   "assuming will be runnable on guest %s",
 552                   action->uuid, action->id, pe__node_name(action->node));
 553 
 554     } else if (!action->node->details->online) {
 555         pe_err("Skipping action %s (%d) "
 556                "because it was scheduled for offline node (bug?)",
 557                action->uuid, action->id);
 558         pcmk__log_action("Offline node", action, false);
 559         return false;
 560 
 561     } else if (action->node->details->unclean) {
 562         pe_err("Skipping action %s (%d) "
 563                "because it was scheduled for unclean node (bug?)",
 564                action->uuid, action->id);
 565         pcmk__log_action("Unclean node", action, false);
 566         return false;
 567     }
 568     return true;
 569 }
 570 
 571 /*!
 572  * \internal
 573  * \brief Check whether an ordering's flags can change an action
 574  *
 575  * \param[in] ordering  Ordering to check
 576  *
 577  * \return true if ordering has flags that can change an action, false otherwise
 578  */
 579 static bool
 580 ordering_can_change_actions(const pe_action_wrapper_t *ordering)
     /* [previous][next][first][last][top][bottom][index][help] */
 581 {
 582     return pcmk_any_flags_set(ordering->type, ~(pe_order_implies_first_printed
 583                                                 |pe_order_implies_then_printed
 584                                                 |pe_order_optional));
 585 }
 586 
 587 /*!
 588  * \internal
 589  * \brief Check whether an action input should be in the transition graph
 590  *
 591  * \param[in]     action  Action to check
 592  * \param[in,out] input   Action input to check
 593  *
 594  * \return true if input should be in graph, false otherwise
 595  * \note This function may not only check an input, but disable it under certian
 596  *       circumstances (load or anti-colocation orderings that are not needed).
 597  */
 598 static bool
 599 should_add_input_to_graph(const pe_action_t *action, pe_action_wrapper_t *input)
     /* [previous][next][first][last][top][bottom][index][help] */
 600 {
 601     if (input->state == pe_link_dumped) {
 602         return true;
 603     }
 604 
 605     if (input->type == pe_order_none) {
 606         crm_trace("Ignoring %s (%d) input %s (%d): "
 607                   "ordering disabled",
 608                   action->uuid, action->id,
 609                   input->action->uuid, input->action->id);
 610         return false;
 611 
 612     } else if (!pcmk_is_set(input->action->flags, pe_action_runnable)
 613                && !ordering_can_change_actions(input)) {
 614         crm_trace("Ignoring %s (%d) input %s (%d): "
 615                   "optional and input unrunnable",
 616                   action->uuid, action->id,
 617                   input->action->uuid, input->action->id);
 618         return false;
 619 
 620     } else if (!pcmk_is_set(input->action->flags, pe_action_runnable)
 621                && pcmk_is_set(input->type, pe_order_one_or_more)) {
 622         crm_trace("Ignoring %s (%d) input %s (%d): "
 623                   "one-or-more and input unrunnable",
 624                   action->uuid, action->id,
 625                   input->action->uuid, input->action->id);
 626         return false;
 627 
 628     } else if (pcmk_is_set(input->type, pe_order_implies_first_migratable)
 629                && !pcmk_is_set(input->action->flags, pe_action_runnable)) {
 630         crm_trace("Ignoring %s (%d) input %s (%d): "
 631                   "implies input migratable but input unrunnable",
 632                   action->uuid, action->id,
 633                   input->action->uuid, input->action->id);
 634         return false;
 635 
 636     } else if (pcmk_is_set(input->type, pe_order_apply_first_non_migratable)
 637                && pcmk_is_set(input->action->flags, pe_action_migrate_runnable)) {
 638         crm_trace("Ignoring %s (%d) input %s (%d): "
 639                   "only if input unmigratable but input unrunnable",
 640                   action->uuid, action->id,
 641                   input->action->uuid, input->action->id);
 642         return false;
 643 
 644     } else if ((input->type == pe_order_optional)
 645                && pcmk_is_set(input->action->flags, pe_action_migrate_runnable)
 646                && pcmk__ends_with(input->action->uuid, "_stop_0")) {
 647         crm_trace("Ignoring %s (%d) input %s (%d): "
 648                   "optional but stop in migration",
 649                   action->uuid, action->id,
 650                   input->action->uuid, input->action->id);
 651         return false;
 652 
 653     } else if (input->type == pe_order_load) {
 654         pe_node_t *input_node = input->action->node;
 655 
 656         // load orderings are relevant only if actions are for same node
 657 
 658         if (action->rsc && pcmk__str_eq(action->task, RSC_MIGRATE, pcmk__str_casei)) {
 659             pe_node_t *allocated = action->rsc->allocated_to;
 660 
 661             /* For load_stopped -> migrate_to orderings, we care about where it
 662              * has been allocated to, not where it will be executed.
 663              */
 664             if ((input_node == NULL) || (allocated == NULL)
 665                 || (input_node->details != allocated->details)) {
 666                 crm_trace("Ignoring %s (%d) input %s (%d): "
 667                           "load ordering node mismatch %s vs %s",
 668                           action->uuid, action->id,
 669                           input->action->uuid, input->action->id,
 670                           (allocated? allocated->details->uname : "<none>"),
 671                           (input_node? input_node->details->uname : "<none>"));
 672                 input->type = pe_order_none;
 673                 return false;
 674             }
 675 
 676         } else if ((input_node == NULL) || (action->node == NULL)
 677                    || (input_node->details != action->node->details)) {
 678             crm_trace("Ignoring %s (%d) input %s (%d): "
 679                       "load ordering node mismatch %s vs %s",
 680                       action->uuid, action->id,
 681                       input->action->uuid, input->action->id,
 682                       (action->node? action->node->details->uname : "<none>"),
 683                       (input_node? input_node->details->uname : "<none>"));
 684             input->type = pe_order_none;
 685             return false;
 686 
 687         } else if (pcmk_is_set(input->action->flags, pe_action_optional)) {
 688             crm_trace("Ignoring %s (%d) input %s (%d): "
 689                       "load ordering input optional",
 690                       action->uuid, action->id,
 691                       input->action->uuid, input->action->id);
 692             input->type = pe_order_none;
 693             return false;
 694         }
 695 
 696     } else if (input->type == pe_order_anti_colocation) {
 697         if (input->action->node && action->node
 698             && (input->action->node->details != action->node->details)) {
 699             crm_trace("Ignoring %s (%d) input %s (%d): "
 700                       "anti-colocation node mismatch %s vs %s",
 701                       action->uuid, action->id,
 702                       input->action->uuid, input->action->id,
 703                       pe__node_name(action->node),
 704                       pe__node_name(input->action->node));
 705             input->type = pe_order_none;
 706             return false;
 707 
 708         } else if (pcmk_is_set(input->action->flags, pe_action_optional)) {
 709             crm_trace("Ignoring %s (%d) input %s (%d): "
 710                       "anti-colocation input optional",
 711                       action->uuid, action->id,
 712                       input->action->uuid, input->action->id);
 713             input->type = pe_order_none;
 714             return false;
 715         }
 716 
 717     } else if (input->action->rsc
 718                && input->action->rsc != action->rsc
 719                && pcmk_is_set(input->action->rsc->flags, pe_rsc_failed)
 720                && !pcmk_is_set(input->action->rsc->flags, pe_rsc_managed)
 721                && pcmk__ends_with(input->action->uuid, "_stop_0")
 722                && action->rsc && pe_rsc_is_clone(action->rsc)) {
 723         crm_warn("Ignoring requirement that %s complete before %s:"
 724                  " unmanaged failed resources cannot prevent clone shutdown",
 725                  input->action->uuid, action->uuid);
 726         return false;
 727 
 728     } else if (pcmk_is_set(input->action->flags, pe_action_optional)
 729                && !pcmk_any_flags_set(input->action->flags,
 730                                       pe_action_print_always|pe_action_dumped)
 731                && !should_add_action_to_graph(input->action)) {
 732         crm_trace("Ignoring %s (%d) input %s (%d): "
 733                   "input optional",
 734                   action->uuid, action->id,
 735                   input->action->uuid, input->action->id);
 736         return false;
 737     }
 738 
 739     crm_trace("%s (%d) input %s %s (%d) on %s should be dumped: %s %s %#.6x",
 740               action->uuid, action->id, action_type_str(input->action->flags),
 741               input->action->uuid, input->action->id,
 742               action_node_str(input->action),
 743               action_runnable_str(input->action->flags),
 744               action_optional_str(input->action->flags), input->type);
 745     return true;
 746 }
 747 
 748 /*!
 749  * \internal
 750  * \brief Check whether an ordering creates an ordering loop
 751  *
 752  * \param[in]     init_action  "First" action in ordering
 753  * \param[in]     action       Callers should always set this the same as
 754  *                             \p init_action (this function may use a different
 755  *                             value for recursive calls)
 756  * \param[in,out] input        Action wrapper for "then" action in ordering
 757  *
 758  * \return true if the ordering creates a loop, otherwise false
 759  */
 760 bool
 761 pcmk__graph_has_loop(const pe_action_t *init_action, const pe_action_t *action,
     /* [previous][next][first][last][top][bottom][index][help] */
 762                      pe_action_wrapper_t *input)
 763 {
 764     bool has_loop = false;
 765 
 766     if (pcmk_is_set(input->action->flags, pe_action_tracking)) {
 767         crm_trace("Breaking tracking loop: %s@%s -> %s@%s (%#.6x)",
 768                   input->action->uuid,
 769                   input->action->node? input->action->node->details->uname : "",
 770                   action->uuid,
 771                   action->node? action->node->details->uname : "",
 772                   input->type);
 773         return false;
 774     }
 775 
 776     // Don't need to check inputs that won't be used
 777     if (!should_add_input_to_graph(action, input)) {
 778         return false;
 779     }
 780 
 781     if (input->action == init_action) {
 782         crm_debug("Input loop found in %s@%s ->...-> %s@%s",
 783                   action->uuid,
 784                   action->node? action->node->details->uname : "",
 785                   init_action->uuid,
 786                   init_action->node? init_action->node->details->uname : "");
 787         return true;
 788     }
 789 
 790     pe__set_action_flags(input->action, pe_action_tracking);
 791 
 792     crm_trace("Checking inputs of action %s@%s input %s@%s (%#.6x)"
 793               "for graph loop with %s@%s ",
 794               action->uuid,
 795               action->node? action->node->details->uname : "",
 796               input->action->uuid,
 797               input->action->node? input->action->node->details->uname : "",
 798               input->type,
 799               init_action->uuid,
 800               init_action->node? init_action->node->details->uname : "");
 801 
 802     // Recursively check input itself for loops
 803     for (GList *iter = input->action->actions_before;
 804          iter != NULL; iter = iter->next) {
 805 
 806         if (pcmk__graph_has_loop(init_action, input->action,
 807                                  (pe_action_wrapper_t *) iter->data)) {
 808             // Recursive call already logged a debug message
 809             has_loop = true;
 810             break;
 811         }
 812     }
 813 
 814     pe__clear_action_flags(input->action, pe_action_tracking);
 815 
 816     if (!has_loop) {
 817         crm_trace("No input loop found in %s@%s -> %s@%s (%#.6x)",
 818                   input->action->uuid,
 819                   input->action->node? input->action->node->details->uname : "",
 820                   action->uuid,
 821                   action->node? action->node->details->uname : "",
 822                   input->type);
 823     }
 824     return has_loop;
 825 }
 826 
 827 /*!
 828  * \internal
 829  * \brief Create a synapse XML element for a transition graph
 830  *
 831  * \param[in]     action    Action that synapse is for
 832  * \param[in,out] data_set  Cluster working set containing graph
 833  *
 834  * \return Newly added XML element for new graph synapse
 835  */
 836 static xmlNode *
 837 create_graph_synapse(const pe_action_t *action, pe_working_set_t *data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 838 {
 839     int synapse_priority = 0;
 840     xmlNode *syn = create_xml_node(data_set->graph, "synapse");
 841 
 842     crm_xml_add_int(syn, XML_ATTR_ID, data_set->num_synapse);
 843     data_set->num_synapse++;
 844 
 845     if (action->rsc != NULL) {
 846         synapse_priority = action->rsc->priority;
 847     }
 848     if (action->priority > synapse_priority) {
 849         synapse_priority = action->priority;
 850     }
 851     if (synapse_priority > 0) {
 852         crm_xml_add_int(syn, XML_CIB_ATTR_PRIORITY, synapse_priority);
 853     }
 854     return syn;
 855 }
 856 
 857 /*!
 858  * \internal
 859  * \brief Add an action to the transition graph XML if appropriate
 860  *
 861  * \param[in,out] data       Action to possibly add
 862  * \param[in,out] user_data  Cluster working set
 863  *
 864  * \note This will de-duplicate the action inputs, meaning that the
 865  *       pe_action_wrapper_t:type flags can no longer be relied on to retain
 866  *       their original settings. That means this MUST be called after
 867  *       pcmk__apply_orderings() is complete, and nothing after this should rely
 868  *       on those type flags. (For example, some code looks for type equal to
 869  *       some flag rather than whether the flag is set, and some code looks for
 870  *       particular combinations of flags -- such code must be done before
 871  *       pcmk__create_graph().)
 872  */
 873 static void
 874 add_action_to_graph(gpointer data, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 875 {
 876     pe_action_t *action = (pe_action_t *) data;
 877     pe_working_set_t *data_set = (pe_working_set_t *) user_data;
 878 
 879     xmlNode *syn = NULL;
 880     xmlNode *set = NULL;
 881     xmlNode *in = NULL;
 882 
 883     /* If we haven't already, de-duplicate inputs (even if we won't be adding
 884      * the action to the graph, so that crm_simulate's dot graphs don't have
 885      * duplicates).
 886      */
 887     if (!pcmk_is_set(action->flags, pe_action_dedup)) {
 888         pcmk__deduplicate_action_inputs(action);
 889         pe__set_action_flags(action, pe_action_dedup);
 890     }
 891 
 892     if (pcmk_is_set(action->flags, pe_action_dumped)    // Already added, or
 893         || !should_add_action_to_graph(action)) {       // shouldn't be added
 894         return;
 895     }
 896     pe__set_action_flags(action, pe_action_dumped);
 897 
 898     crm_trace("Adding action %d (%s%s%s) to graph",
 899               action->id, action->uuid,
 900               ((action->node == NULL)? "" : " on "),
 901               ((action->node == NULL)? "" : action->node->details->uname));
 902 
 903     syn = create_graph_synapse(action, data_set);
 904     set = create_xml_node(syn, "action_set");
 905     in = create_xml_node(syn, "inputs");
 906 
 907     create_graph_action(set, action, false, data_set);
 908 
 909     for (GList *lpc = action->actions_before; lpc != NULL; lpc = lpc->next) {
 910         pe_action_wrapper_t *input = (pe_action_wrapper_t *) lpc->data;
 911 
 912         if (should_add_input_to_graph(action, input)) {
 913             xmlNode *input_xml = create_xml_node(in, "trigger");
 914 
 915             input->state = pe_link_dumped;
 916             create_graph_action(input_xml, input->action, true, data_set);
 917         }
 918     }
 919 }
 920 
 921 static int transition_id = -1;
 922 
 923 /*!
 924  * \internal
 925  * \brief Log a message after calculating a transition
 926  *
 927  * \param[in] filename  Where transition input is stored
 928  */
 929 void
 930 pcmk__log_transition_summary(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
 931 {
 932     if (was_processing_error) {
 933         crm_err("Calculated transition %d (with errors)%s%s",
 934                 transition_id,
 935                 (filename == NULL)? "" : ", saving inputs in ",
 936                 (filename == NULL)? "" : filename);
 937 
 938     } else if (was_processing_warning) {
 939         crm_warn("Calculated transition %d (with warnings)%s%s",
 940                  transition_id,
 941                  (filename == NULL)? "" : ", saving inputs in ",
 942                  (filename == NULL)? "" : filename);
 943 
 944     } else {
 945         crm_notice("Calculated transition %d%s%s",
 946                    transition_id,
 947                    (filename == NULL)? "" : ", saving inputs in ",
 948                    (filename == NULL)? "" : filename);
 949     }
 950     if (crm_config_error) {
 951         crm_notice("Configuration errors found during scheduler processing,"
 952                    "  please run \"crm_verify -L\" to identify issues");
 953     }
 954 }
 955 
 956 /*!
 957  * \internal
 958  * \brief Add a resource's actions to the transition graph
 959  *
 960  * \param[in,out] rsc  Resource whose actions should be added
 961  */
 962 void
 963 pcmk__add_rsc_actions_to_graph(pe_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 964 {
 965     GList *iter = NULL;
 966 
 967     CRM_ASSERT(rsc != NULL);
 968     pe_rsc_trace(rsc, "Adding actions for %s to graph", rsc->id);
 969 
 970     // First add the resource's own actions
 971     g_list_foreach(rsc->actions, add_action_to_graph, rsc->cluster);
 972 
 973     // Then recursively add its children's actions (appropriate to variant)
 974     for (iter = rsc->children; iter != NULL; iter = iter->next) {
 975         pe_resource_t *child_rsc = (pe_resource_t *) iter->data;
 976 
 977         child_rsc->cmds->add_actions_to_graph(child_rsc);
 978     }
 979 }
 980 
 981 /*!
 982  * \internal
 983  * \brief Create a transition graph with all cluster actions needed
 984  *
 985  * \param[in,out] data_set  Cluster working set
 986  */
 987 void
 988 pcmk__create_graph(pe_working_set_t *data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 989 {
 990     GList *iter = NULL;
 991     const char *value = NULL;
 992     long long limit = 0LL;
 993 
 994     transition_id++;
 995     crm_trace("Creating transition graph %d", transition_id);
 996 
 997     data_set->graph = create_xml_node(NULL, XML_TAG_GRAPH);
 998 
 999     value = pe_pref(data_set->config_hash, "cluster-delay");
1000     crm_xml_add(data_set->graph, "cluster-delay", value);
1001 
1002     value = pe_pref(data_set->config_hash, "stonith-timeout");
1003     crm_xml_add(data_set->graph, "stonith-timeout", value);
1004 
1005     crm_xml_add(data_set->graph, "failed-stop-offset", "INFINITY");
1006 
1007     if (pcmk_is_set(data_set->flags, pe_flag_start_failure_fatal)) {
1008         crm_xml_add(data_set->graph, "failed-start-offset", "INFINITY");
1009     } else {
1010         crm_xml_add(data_set->graph, "failed-start-offset", "1");
1011     }
1012 
1013     value = pe_pref(data_set->config_hash, "batch-limit");
1014     crm_xml_add(data_set->graph, "batch-limit", value);
1015 
1016     crm_xml_add_int(data_set->graph, "transition_id", transition_id);
1017 
1018     value = pe_pref(data_set->config_hash, "migration-limit");
1019     if ((pcmk__scan_ll(value, &limit, 0LL) == pcmk_rc_ok) && (limit > 0)) {
1020         crm_xml_add(data_set->graph, "migration-limit", value);
1021     }
1022 
1023     if (data_set->recheck_by > 0) {
1024         char *recheck_epoch = NULL;
1025 
1026         recheck_epoch = crm_strdup_printf("%llu",
1027                                           (long long) data_set->recheck_by);
1028         crm_xml_add(data_set->graph, "recheck-by", recheck_epoch);
1029         free(recheck_epoch);
1030     }
1031 
1032     /* The following code will de-duplicate action inputs, so nothing past this
1033      * should rely on the action input type flags retaining their original
1034      * values.
1035      */
1036 
1037     // Add resource actions to graph
1038     for (iter = data_set->resources; iter != NULL; iter = iter->next) {
1039         pe_resource_t *rsc = (pe_resource_t *) iter->data;
1040 
1041         pe_rsc_trace(rsc, "Processing actions for %s", rsc->id);
1042         rsc->cmds->add_actions_to_graph(rsc);
1043     }
1044 
1045     // Add pseudo-action for list of nodes with maintenance state update
1046     add_maintenance_update(data_set);
1047 
1048     // Add non-resource (node) actions
1049     for (iter = data_set->actions; iter != NULL; iter = iter->next) {
1050         pe_action_t *action = (pe_action_t *) iter->data;
1051 
1052         if ((action->rsc != NULL)
1053             && (action->node != NULL)
1054             && action->node->details->shutdown
1055             && !pcmk_is_set(action->rsc->flags, pe_rsc_maintenance)
1056             && !pcmk_any_flags_set(action->flags,
1057                                    pe_action_optional|pe_action_runnable)
1058             && pcmk__str_eq(action->task, RSC_STOP, pcmk__str_none)) {
1059             /* Eventually we should just ignore the 'fence' case, but for now
1060              * it's the best way to detect (in CTS) when CIB resource updates
1061              * are being lost.
1062              */
1063             if (pcmk_is_set(data_set->flags, pe_flag_have_quorum)
1064                 || (data_set->no_quorum_policy == no_quorum_ignore)) {
1065                 crm_crit("Cannot %s %s because of %s:%s%s (%s)",
1066                          action->node->details->unclean? "fence" : "shut down",
1067                          pe__node_name(action->node), action->rsc->id,
1068                          pcmk_is_set(action->rsc->flags, pe_rsc_managed)? " blocked" : " unmanaged",
1069                          pcmk_is_set(action->rsc->flags, pe_rsc_failed)? " failed" : "",
1070                          action->uuid);
1071             }
1072         }
1073 
1074         add_action_to_graph((gpointer) action, (gpointer) data_set);
1075     }
1076 
1077     crm_log_xml_trace(data_set->graph, "graph");
1078 }

/* [previous][next][first][last][top][bottom][index][help] */