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

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