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

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