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).
 300          *
 301          * If anyone toggles the unique flag to 'on', the
 302          * 'instance free' name will correspond to an orphan
 303          * and fall into the clause above instead
 304          */
 305         crm_xml_add(rsc_xml, PCMK_XA_ID, xml_id);
 306         if ((action->rsc->priv->history_id != NULL)
 307             && !pcmk__str_eq(xml_id, action->rsc->priv->history_id,
 308                              pcmk__str_none)) {
 309             crm_xml_add(rsc_xml, PCMK__XA_LONG_ID,
 310                         action->rsc->priv->history_id);
 311         } else {
 312             crm_xml_add(rsc_xml, PCMK__XA_LONG_ID, action->rsc->id);
 313         }
 314 
 315     } else {
 316         pcmk__assert(action->rsc->priv->history_id == NULL);
 317         crm_xml_add(rsc_xml, PCMK_XA_ID, action->rsc->id);
 318     }
 319 
 320     for (int lpc = 0; lpc < PCMK__NELEM(attr_list); lpc++) {
 321         crm_xml_add(rsc_xml, attr_list[lpc],
 322                     g_hash_table_lookup(action->rsc->priv->meta,
 323                                         attr_list[lpc]));
 324     }
 325 }
 326 
 327 /*!
 328  * \internal
 329  * \brief Add action attributes to transition graph action XML
 330  *
 331  * \param[in,out] action      Scheduled action
 332  * \param[in,out] action_xml  Transition graph action XML for \p action
 333  */
 334 static void
 335 add_action_attributes(pcmk_action_t *action, xmlNode *action_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 336 {
 337     xmlNode *args_xml = NULL;
 338     pcmk_resource_t *rsc = action->rsc;
 339 
 340     /* We create free-standing XML to start, so we can sort the attributes
 341      * before adding it to action_xml, which keeps the scheduler regression
 342      * test graphs comparable.
 343      */
 344     args_xml = pcmk__xe_create(action_xml, PCMK__XE_ATTRIBUTES);
 345 
 346     crm_xml_add(args_xml, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
 347     g_hash_table_foreach(action->extra, hash2field, args_xml);
 348 
 349     if ((rsc != NULL) && (action->node != NULL)) {
 350         // Get the resource instance attributes, evaluated properly for node
 351         GHashTable *params = pe_rsc_params(rsc, action->node,
 352                                            rsc->priv->scheduler);
 353 
 354         pcmk__substitute_remote_addr(rsc, params);
 355 
 356         g_hash_table_foreach(params, hash2smartfield, args_xml);
 357 
 358     } else if ((rsc != NULL)
 359                && (rsc->priv->variant <= pcmk__rsc_variant_primitive)) {
 360         GHashTable *params = pe_rsc_params(rsc, NULL, rsc->priv->scheduler);
 361 
 362         g_hash_table_foreach(params, hash2smartfield, args_xml);
 363     }
 364 
 365     g_hash_table_foreach(action->meta, hash2metafield, args_xml);
 366     if (rsc != NULL) {
 367         pcmk_resource_t *parent = rsc;
 368 
 369         while (parent != NULL) {
 370             parent->priv->cmds->add_graph_meta(parent, args_xml);
 371             parent = parent->priv->parent;
 372         }
 373 
 374         pcmk__add_guest_meta_to_xml(args_xml, action);
 375     }
 376 
 377     pcmk__xe_sort_attrs(args_xml);
 378 }
 379 
 380 /*!
 381  * \internal
 382  * \brief Create the transition graph XML for a scheduled action
 383  *
 384  * \param[in,out] parent        Parent XML element to add action to
 385  * \param[in,out] action        Scheduled action
 386  * \param[in]     skip_details  If false, add action details as sub-elements
 387  * \param[in]     scheduler     Scheduler data
 388  */
 389 static void
 390 create_graph_action(xmlNode *parent, pcmk_action_t *action, bool skip_details,
     /* [previous][next][first][last][top][bottom][index][help] */
 391                     const pcmk_scheduler_t *scheduler)
 392 {
 393     bool needs_node_info = true;
 394     bool needs_maintenance_info = false;
 395     xmlNode *action_xml = NULL;
 396 
 397     if ((action == NULL) || (scheduler == NULL)) {
 398         return;
 399     }
 400 
 401     // Create the top-level element based on task
 402 
 403     if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH, pcmk__str_none)) {
 404         /* All fences need node info; guest node fences are pseudo-events */
 405         if (pcmk_is_set(action->flags, pcmk__action_pseudo)) {
 406             action_xml = pcmk__xe_create(parent, PCMK__XE_PSEUDO_EVENT);
 407         } else {
 408             action_xml = pcmk__xe_create(parent, PCMK__XE_CRM_EVENT);
 409         }
 410 
 411     } else if (pcmk__str_any_of(action->task,
 412                                 PCMK_ACTION_DO_SHUTDOWN,
 413                                 PCMK_ACTION_CLEAR_FAILCOUNT, NULL)) {
 414         action_xml = pcmk__xe_create(parent, PCMK__XE_CRM_EVENT);
 415 
 416     } else if (pcmk__str_eq(action->task, PCMK_ACTION_LRM_DELETE,
 417                             pcmk__str_none)) {
 418         // CIB-only clean-up for shutdown locks
 419         action_xml = pcmk__xe_create(parent, PCMK__XE_CRM_EVENT);
 420         crm_xml_add(action_xml, PCMK__XA_MODE, PCMK__VALUE_CIB);
 421 
 422     } else if (pcmk_is_set(action->flags, pcmk__action_pseudo)) {
 423         if (pcmk__str_eq(action->task, PCMK_ACTION_MAINTENANCE_NODES,
 424                          pcmk__str_none)) {
 425             needs_maintenance_info = true;
 426         }
 427         action_xml = pcmk__xe_create(parent, PCMK__XE_PSEUDO_EVENT);
 428         needs_node_info = false;
 429 
 430     } else {
 431         action_xml = pcmk__xe_create(parent, PCMK__XE_RSC_OP);
 432     }
 433 
 434     crm_xml_add_int(action_xml, PCMK_XA_ID, action->id);
 435     crm_xml_add(action_xml, PCMK_XA_OPERATION, action->task);
 436 
 437     if ((action->rsc != NULL) && (action->rsc->priv->history_id != NULL)) {
 438         char *clone_key = NULL;
 439         guint interval_ms;
 440 
 441         if (pcmk__guint_from_hash(action->meta, PCMK_META_INTERVAL, 0,
 442                                   &interval_ms) != pcmk_rc_ok) {
 443             interval_ms = 0;
 444         }
 445         clone_key = clone_op_key(action, interval_ms);
 446         crm_xml_add(action_xml, PCMK__XA_OPERATION_KEY, clone_key);
 447         crm_xml_add(action_xml, "internal_" PCMK__XA_OPERATION_KEY,
 448                     action->uuid);
 449         free(clone_key);
 450     } else {
 451         crm_xml_add(action_xml, PCMK__XA_OPERATION_KEY, action->uuid);
 452     }
 453 
 454     if (needs_node_info && (action->node != NULL)) {
 455         add_node_details(action, action_xml);
 456         pcmk__insert_dup(action->meta, PCMK__META_ON_NODE,
 457                          action->node->priv->name);
 458         pcmk__insert_dup(action->meta, PCMK__META_ON_NODE_UUID,
 459                          action->node->priv->id);
 460     }
 461 
 462     if (skip_details) {
 463         return;
 464     }
 465 
 466     if ((action->rsc != NULL)
 467         && !pcmk_is_set(action->flags, pcmk__action_pseudo)) {
 468 
 469         // This is a real resource action, so add resource details
 470         add_resource_details(action, action_xml);
 471     }
 472 
 473     /* List any attributes in effect */
 474     add_action_attributes(action, action_xml);
 475 
 476     /* List any nodes this action is expected to make down */
 477     if (needs_node_info && (action->node != NULL)) {
 478         add_downed_nodes(action_xml, action);
 479     }
 480 
 481     if (needs_maintenance_info) {
 482         add_maintenance_nodes(action_xml, scheduler);
 483     }
 484 }
 485 
 486 /*!
 487  * \internal
 488  * \brief Check whether an action should be added to the transition graph
 489  *
 490  * \param[in,out] action  Action to check
 491  *
 492  * \return true if action should be added to graph, otherwise false
 493  */
 494 static bool
 495 should_add_action_to_graph(pcmk_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 496 {
 497     if (!pcmk_is_set(action->flags, pcmk__action_runnable)) {
 498         crm_trace("Ignoring action %s (%d): unrunnable",
 499                   action->uuid, action->id);
 500         return false;
 501     }
 502 
 503     if (pcmk_is_set(action->flags, pcmk__action_optional)
 504         && !pcmk_is_set(action->flags, pcmk__action_always_in_graph)) {
 505         crm_trace("Ignoring action %s (%d): optional",
 506                   action->uuid, action->id);
 507         return false;
 508     }
 509 
 510     /* Actions for unmanaged resources should be excluded from the graph,
 511      * with the exception of monitors and cancellation of recurring monitors.
 512      */
 513     if ((action->rsc != NULL)
 514         && !pcmk_is_set(action->rsc->flags, pcmk__rsc_managed)
 515         && !pcmk__str_eq(action->task, PCMK_ACTION_MONITOR, pcmk__str_none)) {
 516 
 517         const char *interval_ms_s;
 518 
 519         /* A cancellation of a recurring monitor will get here because the task
 520          * is cancel rather than monitor, but the interval can still be used to
 521          * recognize it. The interval has been normalized to milliseconds by
 522          * this point, so a string comparison is sufficient.
 523          */
 524         interval_ms_s = g_hash_table_lookup(action->meta, PCMK_META_INTERVAL);
 525         if (pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches)) {
 526             crm_trace("Ignoring action %s (%d): for unmanaged resource (%s)",
 527                       action->uuid, action->id, action->rsc->id);
 528             return false;
 529         }
 530     }
 531 
 532     /* Always add pseudo-actions, fence actions, and shutdown actions (already
 533      * determined to be required and runnable by this point)
 534      */
 535     if (pcmk_is_set(action->flags, pcmk__action_pseudo)
 536         || pcmk__strcase_any_of(action->task, PCMK_ACTION_STONITH,
 537                                 PCMK_ACTION_DO_SHUTDOWN, NULL)) {
 538         return true;
 539     }
 540 
 541     if (action->node == NULL) {
 542         pcmk__sched_err(action->scheduler,
 543                         "Skipping action %s (%d) "
 544                         "because it was not assigned to a node (bug?)",
 545                         action->uuid, action->id);
 546         pcmk__log_action("Unassigned", action, false);
 547         return false;
 548     }
 549 
 550     if (pcmk_is_set(action->flags, pcmk__action_on_dc)) {
 551         crm_trace("Action %s (%d) should be dumped: "
 552                   "can run on DC instead of %s",
 553                   action->uuid, action->id, pcmk__node_name(action->node));
 554 
 555     } else if (pcmk__is_guest_or_bundle_node(action->node)
 556                && !pcmk_is_set(action->node->priv->flags,
 557                                pcmk__node_remote_reset)) {
 558         crm_trace("Action %s (%d) should be dumped: "
 559                   "assuming will be runnable on guest %s",
 560                   action->uuid, action->id, pcmk__node_name(action->node));
 561 
 562     } else if (!action->node->details->online) {
 563         pcmk__sched_err(action->scheduler,
 564                         "Skipping action %s (%d) "
 565                         "because it was scheduled for offline node (bug?)",
 566                         action->uuid, action->id);
 567         pcmk__log_action("Offline node", action, false);
 568         return false;
 569 
 570     } else if (action->node->details->unclean) {
 571         pcmk__sched_err(action->scheduler,
 572                         "Skipping action %s (%d) "
 573                         "because it was scheduled for unclean node (bug?)",
 574                         action->uuid, action->id);
 575         pcmk__log_action("Unclean node", action, false);
 576         return false;
 577     }
 578     return true;
 579 }
 580 
 581 /*!
 582  * \internal
 583  * \brief Check whether an ordering's flags can change an action
 584  *
 585  * \param[in] ordering  Ordering to check
 586  *
 587  * \return true if ordering has flags that can change an action, false otherwise
 588  */
 589 static bool
 590 ordering_can_change_actions(const pcmk__related_action_t *ordering)
     /* [previous][next][first][last][top][bottom][index][help] */
 591 {
 592     return pcmk_any_flags_set(ordering->flags,
 593                               ~(pcmk__ar_then_implies_first_graphed
 594                                 |pcmk__ar_first_implies_then_graphed
 595                                 |pcmk__ar_ordered));
 596 }
 597 
 598 /*!
 599  * \internal
 600  * \brief Check whether an action input should be in the transition graph
 601  *
 602  * \param[in]     action  Action to check
 603  * \param[in,out] input   Action input to check
 604  *
 605  * \return true if input should be in graph, false otherwise
 606  * \note This function may not only check an input, but disable it under certian
 607  *       circumstances (load or anti-colocation orderings that are not needed).
 608  */
 609 static bool
 610 should_add_input_to_graph(const pcmk_action_t *action,
     /* [previous][next][first][last][top][bottom][index][help] */
 611                           pcmk__related_action_t *input)
 612 {
 613     if (input->graphed) {
 614         return true;
 615     }
 616 
 617     if (input->flags == pcmk__ar_none) {
 618         crm_trace("Ignoring %s (%d) input %s (%d): "
 619                   "ordering disabled",
 620                   action->uuid, action->id,
 621                   input->action->uuid, input->action->id);
 622         return false;
 623 
 624     } else if (!pcmk_is_set(input->action->flags, pcmk__action_runnable)
 625                && !ordering_can_change_actions(input)) {
 626         crm_trace("Ignoring %s (%d) input %s (%d): "
 627                   "optional and input unrunnable",
 628                   action->uuid, action->id,
 629                   input->action->uuid, input->action->id);
 630         return false;
 631 
 632     } else if (!pcmk_is_set(input->action->flags, pcmk__action_runnable)
 633                && pcmk_is_set(input->flags, pcmk__ar_min_runnable)) {
 634         crm_trace("Ignoring %s (%d) input %s (%d): "
 635                   "minimum number of instances required but input unrunnable",
 636                   action->uuid, action->id,
 637                   input->action->uuid, input->action->id);
 638         return false;
 639 
 640     } else if (pcmk_is_set(input->flags, pcmk__ar_unmigratable_then_blocks)
 641                && !pcmk_is_set(input->action->flags, pcmk__action_runnable)) {
 642         crm_trace("Ignoring %s (%d) input %s (%d): "
 643                   "input blocked if 'then' unmigratable",
 644                   action->uuid, action->id,
 645                   input->action->uuid, input->action->id);
 646         return false;
 647 
 648     } else if (pcmk_is_set(input->flags, pcmk__ar_if_first_unmigratable)
 649                && pcmk_is_set(input->action->flags, pcmk__action_migratable)) {
 650         crm_trace("Ignoring %s (%d) input %s (%d): ordering applies "
 651                   "only if input is unmigratable, but it is migratable",
 652                   action->uuid, action->id,
 653                   input->action->uuid, input->action->id);
 654         return false;
 655 
 656     } else if ((input->flags == pcmk__ar_ordered)
 657                && pcmk_is_set(input->action->flags, pcmk__action_migratable)
 658                && pcmk__ends_with(input->action->uuid, "_stop_0")) {
 659         crm_trace("Ignoring %s (%d) input %s (%d): "
 660                   "optional but stop in migration",
 661                   action->uuid, action->id,
 662                   input->action->uuid, input->action->id);
 663         return false;
 664 
 665     } else if (input->flags == pcmk__ar_if_on_same_node_or_target) {
 666         pcmk_node_t *input_node = input->action->node;
 667 
 668         if ((action->rsc != NULL)
 669             && pcmk__str_eq(action->task, PCMK_ACTION_MIGRATE_TO,
 670                             pcmk__str_none)) {
 671 
 672             pcmk_node_t *assigned = action->rsc->priv->assigned_node;
 673 
 674             /* For load_stopped -> migrate_to orderings, we care about where
 675              * the resource has been assigned, not where migrate_to will be
 676              * executed.
 677              */
 678             if (!pcmk__same_node(input_node, assigned)) {
 679                 crm_trace("Ignoring %s (%d) input %s (%d): "
 680                           "migration target %s is not same as input node %s",
 681                           action->uuid, action->id,
 682                           input->action->uuid, input->action->id,
 683                           (assigned? assigned->priv->name : "<none>"),
 684                           (input_node? input_node->priv->name : "<none>"));
 685                 input->flags = pcmk__ar_none;
 686                 return false;
 687             }
 688 
 689         } else if (!pcmk__same_node(input_node, action->node)) {
 690             crm_trace("Ignoring %s (%d) input %s (%d): "
 691                       "not on same node (%s vs %s)",
 692                       action->uuid, action->id,
 693                       input->action->uuid, input->action->id,
 694                       (action->node? action->node->priv->name : "<none>"),
 695                       (input_node? input_node->priv->name : "<none>"));
 696             input->flags = pcmk__ar_none;
 697             return false;
 698 
 699         } else if (pcmk_is_set(input->action->flags, pcmk__action_optional)) {
 700             crm_trace("Ignoring %s (%d) input %s (%d): "
 701                       "ordering optional",
 702                       action->uuid, action->id,
 703                       input->action->uuid, input->action->id);
 704             input->flags = pcmk__ar_none;
 705             return false;
 706         }
 707 
 708     } else if (input->flags == pcmk__ar_if_required_on_same_node) {
 709         if (input->action->node && action->node
 710             && !pcmk__same_node(input->action->node, action->node)) {
 711             crm_trace("Ignoring %s (%d) input %s (%d): "
 712                       "not on same node (%s vs %s)",
 713                       action->uuid, action->id,
 714                       input->action->uuid, input->action->id,
 715                       pcmk__node_name(action->node),
 716                       pcmk__node_name(input->action->node));
 717             input->flags = pcmk__ar_none;
 718             return false;
 719 
 720         } else if (pcmk_is_set(input->action->flags, pcmk__action_optional)) {
 721             crm_trace("Ignoring %s (%d) input %s (%d): optional",
 722                       action->uuid, action->id,
 723                       input->action->uuid, input->action->id);
 724             input->flags = pcmk__ar_none;
 725             return false;
 726         }
 727 
 728     } else if (input->action->rsc
 729                && input->action->rsc != action->rsc
 730                && pcmk_is_set(input->action->rsc->flags, pcmk__rsc_failed)
 731                && !pcmk_is_set(input->action->rsc->flags, pcmk__rsc_managed)
 732                && pcmk__ends_with(input->action->uuid, "_stop_0")
 733                && pcmk__is_clone(action->rsc)) {
 734         crm_warn("Ignoring requirement that %s complete before %s:"
 735                  " unmanaged failed resources cannot prevent clone shutdown",
 736                  input->action->uuid, action->uuid);
 737         return false;
 738 
 739     } else if (pcmk_is_set(input->action->flags, pcmk__action_optional)
 740                && !pcmk_any_flags_set(input->action->flags,
 741                                       pcmk__action_always_in_graph
 742                                       |pcmk__action_added_to_graph)
 743                && !should_add_action_to_graph(input->action)) {
 744         crm_trace("Ignoring %s (%d) input %s (%d): "
 745                   "input optional",
 746                   action->uuid, action->id,
 747                   input->action->uuid, input->action->id);
 748         return false;
 749     }
 750 
 751     crm_trace("%s (%d) input %s %s (%d) on %s should be dumped: %s %s %#.6x",
 752               action->uuid, action->id, action_type_str(input->action->flags),
 753               input->action->uuid, input->action->id,
 754               action_node_str(input->action),
 755               action_runnable_str(input->action->flags),
 756               action_optional_str(input->action->flags), input->flags);
 757     return true;
 758 }
 759 
 760 /*!
 761  * \internal
 762  * \brief Check whether an ordering creates an ordering loop
 763  *
 764  * \param[in]     init_action  "First" action in ordering
 765  * \param[in]     action       Callers should always set this the same as
 766  *                             \p init_action (this function may use a different
 767  *                             value for recursive calls)
 768  * \param[in,out] input        Action wrapper for "then" action in ordering
 769  *
 770  * \return true if the ordering creates a loop, otherwise false
 771  */
 772 bool
 773 pcmk__graph_has_loop(const pcmk_action_t *init_action,
     /* [previous][next][first][last][top][bottom][index][help] */
 774                      const pcmk_action_t *action, pcmk__related_action_t *input)
 775 {
 776     bool has_loop = false;
 777 
 778     if (pcmk_is_set(input->action->flags, pcmk__action_detect_loop)) {
 779         crm_trace("Breaking tracking loop: %s@%s -> %s@%s (%#.6x)",
 780                   input->action->uuid,
 781                   input->action->node? input->action->node->priv->name : "",
 782                   action->uuid,
 783                   action->node? action->node->priv->name : "",
 784                   input->flags);
 785         return false;
 786     }
 787 
 788     // Don't need to check inputs that won't be used
 789     if (!should_add_input_to_graph(action, input)) {
 790         return false;
 791     }
 792 
 793     if (input->action == init_action) {
 794         crm_debug("Input loop found in %s@%s ->...-> %s@%s",
 795                   action->uuid,
 796                   action->node? action->node->priv->name : "",
 797                   init_action->uuid,
 798                   init_action->node? init_action->node->priv->name : "");
 799         return true;
 800     }
 801 
 802     pcmk__set_action_flags(input->action, pcmk__action_detect_loop);
 803 
 804     crm_trace("Checking inputs of action %s@%s input %s@%s (%#.6x)"
 805               "for graph loop with %s@%s ",
 806               action->uuid,
 807               action->node? action->node->priv->name : "",
 808               input->action->uuid,
 809               input->action->node? input->action->node->priv->name : "",
 810               input->flags,
 811               init_action->uuid,
 812               init_action->node? init_action->node->priv->name : "");
 813 
 814     // Recursively check input itself for loops
 815     for (GList *iter = input->action->actions_before;
 816          iter != NULL; iter = iter->next) {
 817 
 818         if (pcmk__graph_has_loop(init_action, input->action,
 819                                  (pcmk__related_action_t *) iter->data)) {
 820             // Recursive call already logged a debug message
 821             has_loop = true;
 822             break;
 823         }
 824     }
 825 
 826     pcmk__clear_action_flags(input->action, pcmk__action_detect_loop);
 827 
 828     if (!has_loop) {
 829         crm_trace("No input loop found in %s@%s -> %s@%s (%#.6x)",
 830                   input->action->uuid,
 831                   input->action->node? input->action->node->priv->name : "",
 832                   action->uuid,
 833                   action->node? action->node->priv->name : "",
 834                   input->flags);
 835     }
 836     return has_loop;
 837 }
 838 
 839 /*!
 840  * \internal
 841  * \brief Create a synapse XML element for a transition graph
 842  *
 843  * \param[in]     action     Action that synapse is for
 844  * \param[in,out] scheduler  Scheduler data containing graph
 845  *
 846  * \return Newly added XML element for new graph synapse
 847  */
 848 static xmlNode *
 849 create_graph_synapse(const pcmk_action_t *action, pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 850 {
 851     int synapse_priority = 0;
 852     xmlNode *syn = pcmk__xe_create(scheduler->priv->graph, PCMK__XE_SYNAPSE);
 853 
 854     crm_xml_add_int(syn, PCMK_XA_ID, scheduler->priv->synapse_count++);
 855 
 856     if (action->rsc != NULL) {
 857         synapse_priority = action->rsc->priv->priority;
 858     }
 859     if (action->priority > synapse_priority) {
 860         synapse_priority = action->priority;
 861     }
 862     if (synapse_priority > 0) {
 863         crm_xml_add_int(syn, PCMK__XA_PRIORITY, synapse_priority);
 864     }
 865     return syn;
 866 }
 867 
 868 /*!
 869  * \internal
 870  * \brief Add an action to the transition graph XML if appropriate
 871  *
 872  * \param[in,out] data       Action to possibly add
 873  * \param[in,out] user_data  Scheduler data
 874  *
 875  * \note This will de-duplicate the action inputs, meaning that the
 876  *       pcmk__related_action_t:type flags can no longer be relied on to retain
 877  *       their original settings. That means this MUST be called after
 878  *       pcmk__apply_orderings() is complete, and nothing after this should rely
 879  *       on those type flags. (For example, some code looks for type equal to
 880  *       some flag rather than whether the flag is set, and some code looks for
 881  *       particular combinations of flags -- such code must be done before
 882  *       pcmk__create_graph().)
 883  */
 884 static void
 885 add_action_to_graph(gpointer data, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 886 {
 887     pcmk_action_t *action = (pcmk_action_t *) data;
 888     pcmk_scheduler_t *scheduler = (pcmk_scheduler_t *) user_data;
 889 
 890     xmlNode *syn = NULL;
 891     xmlNode *set = NULL;
 892     xmlNode *in = NULL;
 893 
 894     /* If we haven't already, de-duplicate inputs (even if we won't be adding
 895      * the action to the graph, so that crm_simulate's dot graphs don't have
 896      * duplicates).
 897      */
 898     if (!pcmk_is_set(action->flags, pcmk__action_inputs_deduplicated)) {
 899         pcmk__deduplicate_action_inputs(action);
 900         pcmk__set_action_flags(action, pcmk__action_inputs_deduplicated);
 901     }
 902 
 903     if (pcmk_is_set(action->flags, pcmk__action_added_to_graph)
 904         || !should_add_action_to_graph(action)) {
 905         return; // Already added, or shouldn't be
 906     }
 907     pcmk__set_action_flags(action, pcmk__action_added_to_graph);
 908 
 909     crm_trace("Adding action %d (%s%s%s) to graph",
 910               action->id, action->uuid,
 911               ((action->node == NULL)? "" : " on "),
 912               ((action->node == NULL)? "" : action->node->priv->name));
 913 
 914     syn = create_graph_synapse(action, scheduler);
 915     set = pcmk__xe_create(syn, PCMK__XE_ACTION_SET);
 916     in = pcmk__xe_create(syn, PCMK__XE_INPUTS);
 917 
 918     create_graph_action(set, action, false, scheduler);
 919 
 920     for (GList *lpc = action->actions_before; lpc != NULL; lpc = lpc->next) {
 921         pcmk__related_action_t *input = lpc->data;
 922 
 923         if (should_add_input_to_graph(action, input)) {
 924             xmlNode *input_xml = pcmk__xe_create(in, PCMK__XE_TRIGGER);
 925 
 926             input->graphed = true;
 927             create_graph_action(input_xml, input->action, true, scheduler);
 928         }
 929     }
 930 }
 931 
 932 static int transition_id = 0;
 933 
 934 /*!
 935  * \internal
 936  * \brief Log a message after calculating a transition
 937  *
 938  * \param[in] scheduler  Scheduler data
 939  * \param[in] filename   Where transition input is stored
 940  */
 941 void
 942 pcmk__log_transition_summary(const pcmk_scheduler_t *scheduler,
     /* [previous][next][first][last][top][bottom][index][help] */
 943                              const char *filename)
 944 {
 945     if (pcmk_is_set(scheduler->flags, pcmk__sched_processing_error)
 946         || pcmk__config_has_error) {
 947         crm_err("Calculated transition %d (with errors)%s%s",
 948                 transition_id,
 949                 (filename == NULL)? "" : ", saving inputs in ",
 950                 (filename == NULL)? "" : filename);
 951 
 952     } else if (pcmk_is_set(scheduler->flags, pcmk__sched_processing_warning)
 953                || pcmk__config_has_warning) {
 954         crm_warn("Calculated transition %d (with warnings)%s%s",
 955                  transition_id,
 956                  (filename == NULL)? "" : ", saving inputs in ",
 957                  (filename == NULL)? "" : filename);
 958 
 959     } else {
 960         crm_notice("Calculated transition %d%s%s",
 961                    transition_id,
 962                    (filename == NULL)? "" : ", saving inputs in ",
 963                    (filename == NULL)? "" : filename);
 964     }
 965     if (pcmk__config_has_error) {
 966         crm_notice("Configuration errors found during scheduler processing,"
 967                    "  please run \"crm_verify -L\" to identify issues");
 968     }
 969 }
 970 
 971 /*!
 972  * \internal
 973  * \brief Add a resource's actions to the transition graph
 974  *
 975  * \param[in,out] rsc  Resource whose actions should be added
 976  */
 977 void
 978 pcmk__add_rsc_actions_to_graph(pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 979 {
 980     GList *iter = NULL;
 981 
 982     pcmk__assert(rsc != NULL);
 983 
 984     pcmk__rsc_trace(rsc, "Adding actions for %s to graph", rsc->id);
 985 
 986     // First add the resource's own actions
 987     g_list_foreach(rsc->priv->actions, add_action_to_graph,
 988                    rsc->priv->scheduler);
 989 
 990     // Then recursively add its children's actions (appropriate to variant)
 991     for (iter = rsc->priv->children; iter != NULL; iter = iter->next) {
 992         pcmk_resource_t *child_rsc = (pcmk_resource_t *) iter->data;
 993 
 994         child_rsc->priv->cmds->add_actions_to_graph(child_rsc);
 995     }
 996 }
 997 
 998 /*!
 999  * \internal
1000  * \brief Create a transition graph with all cluster actions needed
1001  *
1002  * \param[in,out] scheduler  Scheduler data
1003  */
1004 void
1005 pcmk__create_graph(pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
1006 {
1007     GList *iter = NULL;
1008     const char *value = NULL;
1009     long long limit = 0LL;
1010     GHashTable *config_hash = scheduler->priv->options;
1011     int rc = pcmk_rc_ok;
1012 
1013     transition_id++;
1014     crm_trace("Creating transition graph %d", transition_id);
1015 
1016     scheduler->priv->graph = pcmk__xe_create(NULL, PCMK__XE_TRANSITION_GRAPH);
1017 
1018     value = pcmk__cluster_option(config_hash, PCMK_OPT_CLUSTER_DELAY);
1019     crm_xml_add(scheduler->priv->graph, PCMK_OPT_CLUSTER_DELAY, value);
1020 
1021     value = pcmk__cluster_option(config_hash, PCMK_OPT_STONITH_TIMEOUT);
1022     crm_xml_add(scheduler->priv->graph, PCMK_OPT_STONITH_TIMEOUT, value);
1023 
1024     crm_xml_add(scheduler->priv->graph, PCMK__XA_FAILED_STOP_OFFSET,
1025                 PCMK_VALUE_INFINITY);
1026 
1027     if (pcmk_is_set(scheduler->flags, pcmk__sched_start_failure_fatal)) {
1028         crm_xml_add(scheduler->priv->graph, PCMK__XA_FAILED_START_OFFSET,
1029                     PCMK_VALUE_INFINITY);
1030     } else {
1031         crm_xml_add(scheduler->priv->graph, PCMK__XA_FAILED_START_OFFSET, "1");
1032     }
1033 
1034     value = pcmk__cluster_option(config_hash, PCMK_OPT_BATCH_LIMIT);
1035     crm_xml_add(scheduler->priv->graph, PCMK_OPT_BATCH_LIMIT, value);
1036 
1037     crm_xml_add_int(scheduler->priv->graph, "transition_id", transition_id);
1038 
1039     value = pcmk__cluster_option(config_hash, PCMK_OPT_MIGRATION_LIMIT);
1040     rc = pcmk__scan_ll(value, &limit, 0LL);
1041     if (rc != pcmk_rc_ok) {
1042         crm_warn("Ignoring invalid value '%s' for " PCMK_OPT_MIGRATION_LIMIT
1043                  ": %s", value, pcmk_rc_str(rc));
1044     } else if (limit > 0) {
1045         crm_xml_add(scheduler->priv->graph, PCMK_OPT_MIGRATION_LIMIT, value);
1046     }
1047 
1048     if (scheduler->priv->recheck_by > 0) {
1049         char *recheck_epoch = NULL;
1050 
1051         recheck_epoch = crm_strdup_printf("%llu", (unsigned long long)
1052                                           scheduler->priv->recheck_by);
1053         crm_xml_add(scheduler->priv->graph, "recheck-by", recheck_epoch);
1054         free(recheck_epoch);
1055     }
1056 
1057     /* The following code will de-duplicate action inputs, so nothing past this
1058      * should rely on the action input type flags retaining their original
1059      * values.
1060      */
1061 
1062     // Add resource actions to graph
1063     for (iter = scheduler->priv->resources; iter != NULL; iter = iter->next) {
1064         pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
1065 
1066         pcmk__rsc_trace(rsc, "Processing actions for %s", rsc->id);
1067         rsc->priv->cmds->add_actions_to_graph(rsc);
1068     }
1069 
1070     // Add pseudo-action for list of nodes with maintenance state update
1071     add_maintenance_update(scheduler);
1072 
1073     // Add non-resource (node) actions
1074     for (iter = scheduler->priv->actions; iter != NULL; iter = iter->next) {
1075         pcmk_action_t *action = (pcmk_action_t *) iter->data;
1076 
1077         if ((action->rsc != NULL)
1078             && (action->node != NULL)
1079             && action->node->details->shutdown
1080             && !pcmk_is_set(action->rsc->flags, pcmk__rsc_maintenance)
1081             && !pcmk_any_flags_set(action->flags,
1082                                    pcmk__action_optional|pcmk__action_runnable)
1083             && pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_none)) {
1084             /* Eventually we should just ignore the 'fence' case, but for now
1085              * it's the best way to detect (in CTS) when CIB resource updates
1086              * are being lost.
1087              */
1088             if (pcmk_is_set(scheduler->flags, pcmk__sched_quorate)
1089                 || (scheduler->no_quorum_policy == pcmk_no_quorum_ignore)) {
1090                 const bool managed = pcmk_is_set(action->rsc->flags,
1091                                                  pcmk__rsc_managed);
1092                 const bool failed = pcmk_is_set(action->rsc->flags,
1093                                                 pcmk__rsc_failed);
1094 
1095                 crm_crit("Cannot %s %s because of %s:%s%s (%s)",
1096                          action->node->details->unclean? "fence" : "shut down",
1097                          pcmk__node_name(action->node), action->rsc->id,
1098                          (managed? " blocked" : " unmanaged"),
1099                          (failed? " failed" : ""), action->uuid);
1100             }
1101         }
1102 
1103         add_action_to_graph((gpointer) action, (gpointer) scheduler);
1104     }
1105 
1106     crm_log_xml_trace(scheduler->priv->graph, "graph");
1107 }

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