1 /*
2 * Copyright 2004-2026 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 Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <ctype.h>
13 #include <stdbool.h> // bool, true, false
14 #include <stdint.h>
15
16 #include <crm/pengine/status.h>
17 #include <crm/pengine/internal.h>
18 #include <crm/common/xml.h>
19 #include <crm/common/output.h>
20 #include <pe_status_private.h>
21
22 enum pe__bundle_mount_flags {
23 pe__bundle_mount_none = 0x00,
24
25 // mount instance-specific subdirectory rather than source directly
26 pe__bundle_mount_subdir = 0x01
27 };
28
29 typedef struct {
30 char *source;
31 char *target;
32 char *options;
33 uint32_t flags; // bitmask of pe__bundle_mount_flags
34 } pe__bundle_mount_t;
35
36 typedef struct {
37 char *source;
38 char *target;
39 } pe__bundle_port_t;
40
41 enum pe__container_agent {
42 PE__CONTAINER_AGENT_UNKNOWN,
43 PE__CONTAINER_AGENT_DOCKER,
44 PE__CONTAINER_AGENT_PODMAN,
45 };
46
47 #define PE__CONTAINER_AGENT_UNKNOWN_S "unknown"
48 #define PE__CONTAINER_AGENT_DOCKER_S "docker"
49 #define PE__CONTAINER_AGENT_PODMAN_S "podman"
50
51 typedef struct {
52 int promoted_max;
53 int nreplicas;
54 int nreplicas_per_host;
55 char *prefix;
56 char *image;
57 const char *ip_last;
58 char *host_network;
59 char *host_netmask;
60 char *control_port;
61 char *container_network;
62 char *ip_range_start;
63 bool add_host;
64 gchar *container_host_options;
65 char *container_command;
66 char *launcher_options;
67 const char *attribute_target;
68
69 pcmk_resource_t *child;
70
71 GList *replicas; // pcmk__bundle_replica_t *
72 GList *ports; // pe__bundle_port_t *
73 GList *mounts; // pe__bundle_mount_t *
74
75 /* @TODO Maybe use a more object-oriented design instead, with a set of
76 * methods that are different per type rather than switching on this
77 */
78 enum pe__container_agent agent_type;
79 } pe__bundle_variant_data_t;
80
81 #define get_bundle_variant_data(data, rsc) do { \
82 pcmk__assert(pcmk__is_bundle(rsc)); \
83 data = rsc->priv->variant_opaque; \
84 } while (0)
85
86 /*!
87 * \internal
88 * \brief Get maximum number of bundle replicas allowed to run
89 *
90 * \param[in] rsc Bundle or bundled resource to check
91 *
92 * \return Maximum replicas for bundle corresponding to \p rsc
93 */
94 int
95 pe__bundle_max(const pcmk_resource_t *rsc)
96 {
97 const pe__bundle_variant_data_t *bundle_data = NULL;
98
99 get_bundle_variant_data(bundle_data, pe__const_top_resource(rsc, true));
100 return bundle_data->nreplicas;
101 }
102
103 /*!
104 * \internal
105 * \brief Get the resource inside a bundle
106 *
107 * \param[in] bundle Bundle to check
108 *
109 * \return Resource inside \p bundle if any, otherwise NULL
110 */
111 pcmk_resource_t *
112 pe__bundled_resource(const pcmk_resource_t *rsc)
113 {
114 const pe__bundle_variant_data_t *bundle_data = NULL;
115
116 get_bundle_variant_data(bundle_data, pe__const_top_resource(rsc, true));
117 return bundle_data->child;
118 }
119
120 /*!
121 * \internal
122 * \brief Get containerized resource corresponding to a given bundle container
123 *
124 * \param[in] instance Collective instance that might be a bundle container
125 *
126 * \return Bundled resource instance inside \p instance if it is a bundle
127 * container instance, otherwise NULL
128 */
129 const pcmk_resource_t *
130 pe__get_rsc_in_container(const pcmk_resource_t *instance)
131 {
132 const pe__bundle_variant_data_t *data = NULL;
133 const pcmk_resource_t *top = pe__const_top_resource(instance, true);
134
135 if (!pcmk__is_bundle(top)) {
136 return NULL;
137 }
138 get_bundle_variant_data(data, top);
139
140 for (const GList *iter = data->replicas; iter != NULL; iter = iter->next) {
141 const pcmk__bundle_replica_t *replica = iter->data;
142
143 if (instance == replica->container) {
144 return replica->child;
145 }
146 }
147 return NULL;
148 }
149
150 /*!
151 * \internal
152 * \brief Check whether a given node is created by a bundle
153 *
154 * \param[in] bundle Bundle resource to check
155 * \param[in] node Node to check
156 *
157 * \return true if \p node is an instance of \p bundle, otherwise false
158 */
159 bool
160 pe__node_is_bundle_instance(const pcmk_resource_t *bundle,
161 const pcmk_node_t *node)
162 {
163 pe__bundle_variant_data_t *bundle_data = NULL;
164
165 get_bundle_variant_data(bundle_data, bundle);
166 for (GList *iter = bundle_data->replicas; iter != NULL; iter = iter->next) {
167 pcmk__bundle_replica_t *replica = iter->data;
168
169 if (pcmk__same_node(node, replica->node)) {
170 return true;
171 }
172 }
173 return false;
174 }
175
176 /*!
177 * \internal
178 * \brief Get the container of a bundle's first replica
179 *
180 * \param[in] bundle Bundle resource to get container for
181 *
182 * \return Container resource from first replica of \p bundle if any,
183 * otherwise NULL
184 */
185 pcmk_resource_t *
186 pe__first_container(const pcmk_resource_t *bundle)
187 {
188 const pe__bundle_variant_data_t *bundle_data = NULL;
189 const pcmk__bundle_replica_t *replica = NULL;
190
191 get_bundle_variant_data(bundle_data, bundle);
192 if (bundle_data->replicas == NULL) {
193 return NULL;
194 }
195 replica = bundle_data->replicas->data;
196 return replica->container;
197 }
198
199 /*!
200 * \internal
201 * \brief Iterate over bundle replicas
202 *
203 * \param[in,out] bundle Bundle to iterate over
204 * \param[in] fn Function to call for each replica (its return value
205 * indicates whether to continue iterating)
206 * \param[in,out] user_data Pointer to pass to \p fn
207 */
208 void
209 pe__foreach_bundle_replica(pcmk_resource_t *bundle,
210 bool (*fn)(pcmk__bundle_replica_t *, void *),
211 void *user_data)
212 {
213 const pe__bundle_variant_data_t *bundle_data = NULL;
214
215 get_bundle_variant_data(bundle_data, bundle);
216 for (GList *iter = bundle_data->replicas; iter != NULL; iter = iter->next) {
217 if (!fn((pcmk__bundle_replica_t *) iter->data, user_data)) {
218 break;
219 }
220 }
221 }
222
223 /*!
224 * \internal
225 * \brief Iterate over const bundle replicas
226 *
227 * \param[in] bundle Bundle to iterate over
228 * \param[in] fn Function to call for each replica (its return value
229 * indicates whether to continue iterating)
230 * \param[in,out] user_data Pointer to pass to \p fn
231 */
232 void
233 pe__foreach_const_bundle_replica(const pcmk_resource_t *bundle,
234 bool (*fn)(const pcmk__bundle_replica_t *,
235 void *),
236 void *user_data)
237 {
238 const pe__bundle_variant_data_t *bundle_data = NULL;
239
240 get_bundle_variant_data(bundle_data, bundle);
241 for (const GList *iter = bundle_data->replicas; iter != NULL;
242 iter = iter->next) {
243
244 if (!fn((const pcmk__bundle_replica_t *) iter->data, user_data)) {
245 break;
246 }
247 }
248 }
249
250 static char *
251 next_ip(const char *last_ip)
252 {
253 unsigned int oct1 = 0;
254 unsigned int oct2 = 0;
255 unsigned int oct3 = 0;
256 unsigned int oct4 = 0;
257 int rc = sscanf(last_ip, "%u.%u.%u.%u", &oct1, &oct2, &oct3, &oct4);
258
259 if (rc != 4) {
260 /*@ TODO check for IPv6 */
261 return NULL;
262
263 } else if (oct3 > 253) {
264 return NULL;
265
266 } else if (oct4 > 253) {
267 ++oct3;
268 oct4 = 1;
269
270 } else {
271 ++oct4;
272 }
273
274 return pcmk__assert_asprintf("%u.%u.%u.%u", oct1, oct2, oct3, oct4);
275 }
276
277 static void
278 allocate_ip(pe__bundle_variant_data_t *data, pcmk__bundle_replica_t *replica,
279 GString *buffer)
280 {
281 if(data->ip_range_start == NULL) {
282 return;
283
284 } else if(data->ip_last) {
285 replica->ipaddr = next_ip(data->ip_last);
286
287 } else {
288 replica->ipaddr = strdup(data->ip_range_start);
289 }
290
291 data->ip_last = replica->ipaddr;
292
293 if ((data->agent_type != PE__CONTAINER_AGENT_DOCKER)
294 && (data->agent_type != PE__CONTAINER_AGENT_PODMAN)) {
295
296 return;
297 }
298
299 if (data->add_host) {
300 g_string_append_printf(buffer, " --add-host=%s-%d:%s", data->prefix,
301 replica->offset, replica->ipaddr);
302 return;
303 }
304
305 g_string_append_printf(buffer, " --hosts-entry=%s=%s-%d", replica->ipaddr,
306 data->prefix, replica->offset);
307 }
308
309 static xmlNode *
310 create_resource(const char *name, const char *provider, const char *kind)
311 {
312 xmlNode *rsc = pcmk__xe_create(NULL, PCMK_XE_PRIMITIVE);
313
314 pcmk__xe_set(rsc, PCMK_XA_ID, name);
315 pcmk__xe_set(rsc, PCMK_XA_CLASS, PCMK_RESOURCE_CLASS_OCF);
316 pcmk__xe_set(rsc, PCMK_XA_PROVIDER, provider);
317 pcmk__xe_set(rsc, PCMK_XA_TYPE, kind);
318
319 return rsc;
320 }
321
322 /*!
323 * \internal
324 * \brief Check whether cluster can manage resource inside container
325 *
326 * \param[in,out] data Container variant data
327 *
328 * \return TRUE if networking configuration is acceptable, FALSE otherwise
329 *
330 * \note The resource is manageable if an IP range or control port has been
331 * specified. If a control port is used without an IP range, replicas per
332 * host must be 1.
333 */
334 static bool
335 valid_network(pe__bundle_variant_data_t *data)
336 {
337 if(data->ip_range_start) {
338 return TRUE;
339 }
340 if(data->control_port) {
341 if(data->nreplicas_per_host > 1) {
342 pcmk__config_err("Specifying the '" PCMK_XA_CONTROL_PORT "' for %s "
343 "requires '" PCMK_XA_REPLICAS_PER_HOST "=1'",
344 data->prefix);
345 data->nreplicas_per_host = 1;
346 // @TODO to be sure:
347 // pcmk__clear_rsc_flags(rsc, pcmk__rsc_unique);
348 }
349 return TRUE;
350 }
351 return FALSE;
352 }
353
354 static int
355 create_ip_resource(pcmk_resource_t *parent, pe__bundle_variant_data_t *data,
356 pcmk__bundle_replica_t *replica)
357 {
358 char *id = NULL;
359 xmlNode *xml_ip = NULL;
360 xmlNode *xml_obj = NULL;
361 int rc = pcmk_rc_ok;
362
363 if (data->ip_range_start == NULL) {
364 goto done;
365 }
366
367 id = pcmk__assert_asprintf("%s-ip-%s", data->prefix, replica->ipaddr);
368 pcmk__xml_sanitize_id(id);
369 xml_ip = create_resource(id, "heartbeat", "IPaddr2");
370
371 xml_obj = pcmk__xe_create(xml_ip, PCMK_XE_INSTANCE_ATTRIBUTES);
372 pcmk__xe_set_id(xml_obj, "%s-attributes-%d", data->prefix, replica->offset);
373
374 crm_create_nvpair_xml(xml_obj, NULL, "ip", replica->ipaddr);
375
376 if (data->host_network != NULL) {
377 crm_create_nvpair_xml(xml_obj, NULL, "nic", data->host_network);
378 }
379
380 crm_create_nvpair_xml(xml_obj, NULL, "cidr_netmask",
381 pcmk__s(data->host_netmask, "32"));
382
383 xml_obj = pcmk__xe_create(xml_ip, PCMK_XE_OPERATIONS);
384 crm_create_op_xml(xml_obj, id, PCMK_ACTION_MONITOR, "60s", NULL);
385
386 // TODO: Other ops? Timeouts and intervals from underlying resource?
387
388 if (pe__unpack_resource(xml_ip, &replica->ip, parent,
389 parent->priv->scheduler) != pcmk_rc_ok) {
390 rc = pcmk_rc_unpack_error;
391 goto done;
392 }
393
394 parent->priv->children = g_list_append(parent->priv->children, replica->ip);
395
396 done:
397 free(id);
398 pcmk__xml_free(xml_ip);
399 return rc;
400 }
401
402 static const char*
403 container_agent_str(enum pe__container_agent t)
404 {
405 switch (t) {
406 case PE__CONTAINER_AGENT_DOCKER: return PE__CONTAINER_AGENT_DOCKER_S;
407 case PE__CONTAINER_AGENT_PODMAN: return PE__CONTAINER_AGENT_PODMAN_S;
408 default: // PE__CONTAINER_AGENT_UNKNOWN
409 break;
410 }
411 return PE__CONTAINER_AGENT_UNKNOWN_S;
412 }
413
414 static int
415 create_container_resource(pcmk_resource_t *parent,
416 const pe__bundle_variant_data_t *data,
417 pcmk__bundle_replica_t *replica)
418 {
419 char *id = NULL;
420 xmlNode *xml_container = NULL;
421 xmlNode *xml_obj = NULL;
422 const char *agent_str = NULL;
423 GString *buffer = NULL;
424 GString *dbuffer = NULL;
425 int rc = pcmk_rc_ok;
426
427 if ((data->agent_type != PE__CONTAINER_AGENT_DOCKER)
428 && (data->agent_type != PE__CONTAINER_AGENT_PODMAN)) {
429
430 rc = pcmk_rc_unpack_error;
431 goto done;
432 }
433
434 agent_str = container_agent_str(data->agent_type);
435 buffer = g_string_sized_new(4096);
436
437 id = pcmk__assert_asprintf("%s-%s-%d", data->prefix, agent_str,
438 replica->offset);
439 pcmk__xml_sanitize_id(id);
440 xml_container = create_resource(id, "heartbeat", agent_str);
441
442 xml_obj = pcmk__xe_create(xml_container, PCMK_XE_INSTANCE_ATTRIBUTES);
443 pcmk__xe_set_id(xml_obj, "%s-attributes-%d", data->prefix, replica->offset);
444
445 crm_create_nvpair_xml(xml_obj, NULL, "image", data->image);
446 crm_create_nvpair_xml(xml_obj, NULL, "allow_pull", PCMK_VALUE_TRUE);
447 crm_create_nvpair_xml(xml_obj, NULL, "force_kill", PCMK_VALUE_FALSE);
448 crm_create_nvpair_xml(xml_obj, NULL, "reuse", PCMK_VALUE_FALSE);
449
450 if (data->agent_type == PE__CONTAINER_AGENT_DOCKER) {
451 g_string_append(buffer, " --restart=no");
452 }
453
454 /* Set a container hostname only if we have an IP to map it to. The user can
455 * set -h or --uts=host themselves if they want a nicer name for logs, but
456 * this makes applications happy who need their hostname to match the IP
457 * they bind to.
458 */
459 if (data->ip_range_start != NULL) {
460 g_string_append_printf(buffer, " -h %s-%d", data->prefix,
461 replica->offset);
462 }
463
464 g_string_append(buffer, " -e PCMK_stderr=1");
465
466 if (data->container_network != NULL) {
467 pcmk__g_strcat(buffer, " --net=", data->container_network, NULL);
468 }
469
470 if (data->control_port != NULL) {
471 pcmk__g_strcat(buffer, " -e PCMK_" PCMK__ENV_REMOTE_PORT "=",
472 data->control_port, NULL);
473 } else {
474 g_string_append_printf(buffer, " -e PCMK_" PCMK__ENV_REMOTE_PORT "=%d",
475 DEFAULT_REMOTE_PORT);
476 }
477
478 for (GList *iter = data->mounts; iter != NULL; iter = iter->next) {
479 pe__bundle_mount_t *mount = (pe__bundle_mount_t *) iter->data;
480 char *source = NULL;
481
482 if (pcmk__is_set(mount->flags, pe__bundle_mount_subdir)) {
483 source = pcmk__assert_asprintf("%s/%s-%d", mount->source,
484 data->prefix, replica->offset);
485 pcmk__add_separated_word(&dbuffer, 1024, source, ",");
486 }
487
488 pcmk__g_strcat(buffer, " -v ", pcmk__s(source, mount->source), ":",
489 mount->target, NULL);
490
491 if (mount->options != NULL) {
492 pcmk__g_strcat(buffer, ":", mount->options, NULL);
493 }
494
495 free(source);
496 }
497
498 for (GList *iter = data->ports; iter != NULL; iter = iter->next) {
499 pe__bundle_port_t *port = (pe__bundle_port_t *) iter->data;
500
501 if (replica->ipaddr != NULL) {
502 pcmk__g_strcat(buffer, " -p ", replica->ipaddr, ":", port->source,
503 ":", port->target, NULL);
504
505 } else if (!pcmk__str_eq(data->container_network, PCMK_VALUE_HOST,
506 pcmk__str_none)) {
507
508 // No need to do port mapping if net == host
509 pcmk__g_strcat(buffer, " -p ", port->source, ":", port->target,
510 NULL);
511 }
512 }
513
514 /* @COMPAT: We should use pcmk__add_word() here, but we can't yet, because
515 * it would cause restarts during rolling upgrades.
516 *
517 * In a previous version of the container resource creation logic, if
518 * data->launcher_options is not NULL, we append
519 * (" %s", data->launcher_options) even if data->launcher_options is an
520 * empty string. Likewise for data->container_host_options. Using
521 *
522 * pcmk__add_word(buffer, 0, data->launcher_options)
523 *
524 * removes that extra trailing space, causing a resource definition change.
525 */
526 if (data->launcher_options != NULL) {
527 pcmk__g_strcat(buffer, " ", data->launcher_options, NULL);
528 }
529
530 if (data->container_host_options != NULL) {
531 pcmk__g_strcat(buffer, " ", data->container_host_options, NULL);
532 }
533
534 crm_create_nvpair_xml(xml_obj, NULL, "run_opts",
535 (const char *) buffer->str);
536 g_string_free(buffer, TRUE);
537
538 crm_create_nvpair_xml(xml_obj, NULL, "mount_points",
539 (dbuffer != NULL)? (const char *) dbuffer->str : "");
540 if (dbuffer != NULL) {
541 g_string_free(dbuffer, TRUE);
542 }
543
544 if (replica->child != NULL) {
545 if (data->container_command != NULL) {
546 crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
547 data->container_command);
548 } else {
549 crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
550 SBIN_DIR "/" PCMK__SERVER_REMOTED);
551 }
552
553 /* TODO: Allow users to specify their own?
554 *
555 * We just want to know if the container is alive; we'll monitor the
556 * child independently.
557 */
558 crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true");
559 #if 0
560 /* @TODO Consider supporting the use case where we can start and stop
561 * resources, but not proxy local commands (such as setting node
562 * attributes), by running the local executor in stand-alone mode.
563 * However, this would probably be better done via ACLs as with other
564 * Pacemaker Remote nodes.
565 */
566 } else if ((child != NULL) && data->untrusted) {
567 crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
568 CRM_DAEMON_DIR "/" PCMK__SERVER_EXECD);
569 crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd",
570 CRM_DAEMON_DIR "/pacemaker/cts-exec-helper -c poke");
571 #endif
572 } else {
573 if (data->container_command != NULL) {
574 crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
575 data->container_command);
576 }
577
578 /* TODO: Allow users to specify their own?
579 *
580 * We don't know what's in the container, so we just want to know if it
581 * is alive.
582 */
583 crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true");
584 }
585
586 xml_obj = pcmk__xe_create(xml_container, PCMK_XE_OPERATIONS);
587 crm_create_op_xml(xml_obj, pcmk__xe_id(xml_container), PCMK_ACTION_MONITOR,
588 "60s", NULL);
589
590 // TODO: Other ops? Timeouts and intervals from underlying resource?
591 if (pe__unpack_resource(xml_container, &replica->container, parent,
592 parent->priv->scheduler) != pcmk_rc_ok) {
593
594 rc = pcmk_rc_unpack_error;
595 goto done;
596 }
597
598 pcmk__set_rsc_flags(replica->container, pcmk__rsc_replica_container);
599 parent->priv->children = g_list_append(parent->priv->children,
600 replica->container);
601
602 done:
603 free(id);
604 pcmk__xml_free(xml_container);
605 return rc;
606 }
607
608 /*!
609 * \brief Ban a node from a resource's (and its children's) allowed nodes list
610 *
611 * \param[in,out] rsc Resource to modify
612 * \param[in] uname Name of node to ban
613 */
614 static void
615 disallow_node(pcmk_resource_t *rsc, const char *uname)
616 {
617 gpointer match = g_hash_table_lookup(rsc->priv->allowed_nodes, uname);
618
619 if (match) {
620 ((pcmk_node_t *) match)->assign->score = -PCMK_SCORE_INFINITY;
621 ((pcmk_node_t *) match)->assign->probe_mode = pcmk__probe_never;
622 }
623 g_list_foreach(rsc->priv->children, (GFunc) disallow_node,
624 (gpointer) uname);
625 }
626
627 static int
628 create_remote_resource(pcmk_resource_t *parent, pe__bundle_variant_data_t *data,
629 pcmk__bundle_replica_t *replica)
630 {
631 GHashTableIter gIter;
632 pcmk_node_t *node = NULL;
633 pcmk_node_t *copy = NULL;
634 xmlNode *xml_remote = NULL;
635 char *id = NULL;
636 const char *uname = NULL;
637 const char *connect_name = NULL;
638 pcmk_scheduler_t *scheduler = parent->priv->scheduler;
639 int rc = pcmk_rc_ok;
640
641 if ((replica->child == NULL) || !valid_network(data)) {
642 goto done;
643 }
644
645 id = pcmk__assert_asprintf("%s-%d", data->prefix, replica->offset);
646
647 if (pe_find_resource(scheduler->priv->resources, id) != NULL) {
648 free(id);
649
650 // The biggest hammer we have
651 id = pcmk__assert_asprintf("pcmk-internal-%s-remote-%d",
652 replica->child->id, replica->offset);
653
654 // @TODO return error instead of asserting?
655 pcmk__assert(pe_find_resource(scheduler->priv->resources, id) == NULL);
656 }
657
658 /* REMOTE_CONTAINER_HACK: Using "#uname" as the server name when the
659 * connection does not have its own IP is a magic string that we use to
660 * support nested remotes (i.e. a bundle running on a remote node).
661 */
662 connect_name = pcmk__s(replica->ipaddr, "#uname");
663
664 /* This sets replica->container as replica->remote's container, which is
665 * similar to what happens with guest nodes. This is how the scheduler knows
666 * that the bundle node is fenced by recovering the container, and that
667 * remote should be ordered relative to the container.
668 */
669 if (data->control_port != NULL) {
670 xml_remote = pe_create_remote_xml(NULL, id, replica->container->id,
671 NULL, NULL, NULL, connect_name,
672 data->control_port);
673
674 } else {
675 char *port_s = pcmk__itoa(DEFAULT_REMOTE_PORT);
676
677 xml_remote = pe_create_remote_xml(NULL, id, replica->container->id,
678 NULL, NULL, NULL, connect_name,
679 port_s);
680 free(port_s);
681 }
682
683 /* Abandon our created ID, and pull the copy from the XML, because we need
684 * something that will get freed during scheduler data cleanup to use as the
685 * node ID and uname.
686 */
687 uname = pcmk__xe_id(xml_remote);
688
689 /* Ensure a node has been created for the guest (it may have already been,
690 * if it has a permanent node attribute), and ensure its weight is -INFINITY
691 * so no other resources can run on it.
692 */
693 node = pcmk_find_node(scheduler, uname);
694
695 if (node == NULL) {
696 node = pe__create_node(uname, uname, PCMK_VALUE_REMOTE,
697 -PCMK_SCORE_INFINITY, scheduler);
698
699 } else {
700 node->assign->score = -PCMK_SCORE_INFINITY;
701 }
702
703 node->assign->probe_mode = pcmk__probe_never;
704
705 /* unpack_remote_nodes() ensures that each remote node and guest node has a
706 * pcmk_node_t entry. Ideally, it would do the same for bundle nodes.
707 * Unfortunately, a bundle has to be mostly unpacked before it's obvious
708 * what nodes will be needed, so we do it just above.
709 *
710 * Worse, that means that the node may have been utilized while unpacking
711 * other resources, without our weight correction. The most likely place for
712 * this to happen is when pe__unpack_resource() calls resource_location() to
713 * set a default score in symmetric clusters. This adds a node *copy* to
714 * each resource's allowed nodes, and these copies will have the wrong
715 * weight.
716 *
717 * As a hacky workaround, fix those copies here.
718 *
719 * @TODO Possible alternative: ensure bundles are unpacked before other
720 * resources, so the weight is correct before any copies are made.
721 */
722 g_list_foreach(scheduler->priv->resources, (GFunc) disallow_node,
723 (void *) uname);
724
725 replica->node = pe__copy_node(node);
726 replica->node->assign->score = 500;
727 replica->node->assign->probe_mode = pcmk__probe_exclusive;
728
729 // Ensure the node shows up as allowed and with the correct discovery set
730 g_clear_pointer(&replica->child->priv->allowed_nodes, g_hash_table_destroy);
731
732 replica->child->priv->allowed_nodes =
733 pcmk__strkey_table(NULL, pcmk__free_node_copy);
734
735 g_hash_table_insert(replica->child->priv->allowed_nodes,
736 (void *) replica->node->priv->id,
737 pe__copy_node(replica->node));
738
739 copy = pe__copy_node(replica->node);
740 copy->assign->score = -PCMK_SCORE_INFINITY;
741
742 g_hash_table_insert(replica->child->priv->parent->priv->allowed_nodes,
743 (void *) replica->node->priv->id, copy);
744
745 if (pe__unpack_resource(xml_remote, &replica->remote, parent,
746 scheduler) != pcmk_rc_ok) {
747
748 rc = pcmk_rc_unpack_error;
749 goto done;
750 }
751
752 // Make Coverity happy
753 pcmk__assert(replica->remote != NULL);
754
755 g_hash_table_iter_init(&gIter, replica->remote->priv->allowed_nodes);
756 while (g_hash_table_iter_next(&gIter, NULL, (void **)&node)) {
757 if (pcmk__is_pacemaker_remote_node(node)) {
758 // Remote resources can only run on 'normal' cluster node
759 node->assign->score = -PCMK_SCORE_INFINITY;
760 }
761 }
762
763 replica->node->priv->remote = replica->remote;
764
765 // Ensure pcmk__is_guest_or_bundle_node() functions correctly
766 replica->remote->priv->launcher = replica->container;
767
768 /* A bundle's #kind is closer to "container" (guest node) than the "remote"
769 * set by pe__create_node()
770 */
771 pcmk__insert_dup(replica->node->priv->attrs, CRM_ATTR_KIND, "container");
772
773 /* One effect of this is that unpack_launcher() will add replica->remote to
774 * replica->container's launched resources, which will make
775 * pe__resource_contains_guest_node() true for replica->container.
776 *
777 * replica->child does NOT get added to replica->container's launched
778 * resources. The only noticeable effect if it did would be for its fail
779 * count to be taken into account when checking replica->container's
780 * migration threshold.
781 */
782 parent->priv->children = g_list_append(parent->priv->children,
783 replica->remote);
784
785 done:
786 free(id);
787 pcmk__xml_free(xml_remote);
788 return rc;
789 }
790
791 static int
792 create_replica_resources(pcmk_resource_t *parent,
793 pe__bundle_variant_data_t *data,
794 pcmk__bundle_replica_t *replica)
795 {
796 int rc = pcmk_rc_ok;
797
798 rc = create_container_resource(parent, data, replica);
799 if (rc != pcmk_rc_ok) {
800 return rc;
801 }
802
803 rc = create_ip_resource(parent, data, replica);
804 if (rc != pcmk_rc_ok) {
805 return rc;
806 }
807
808 rc = create_remote_resource(parent, data, replica);
809 if (rc != pcmk_rc_ok) {
810 return rc;
811 }
812
813 if ((replica->child != NULL) && (replica->ipaddr != NULL)) {
814 pcmk__insert_meta(replica->child->priv, "external-ip", replica->ipaddr);
815 }
816
817 if (replica->remote != NULL) {
818 /*
819 * Allow the remote connection resource to be allocated to a
820 * different node than the one on which the container is active.
821 *
822 * This makes it possible to have Pacemaker Remote nodes running
823 * containers with the remote executor inside in order to start
824 * services inside those containers.
825 */
826 pcmk__set_rsc_flags(replica->remote, pcmk__rsc_remote_nesting_allowed);
827 }
828 return rc;
829 }
830
831 static void
832 mount_add(pe__bundle_variant_data_t *bundle_data, const char *source,
833 const char *target, const char *options, uint32_t flags)
834 {
835 pe__bundle_mount_t *mount = pcmk__assert_alloc(1,
836 sizeof(pe__bundle_mount_t));
837
838 mount->source = pcmk__str_copy(source);
839 mount->target = pcmk__str_copy(target);
840 mount->options = pcmk__str_copy(options);
841 mount->flags = flags;
842 bundle_data->mounts = g_list_append(bundle_data->mounts, mount);
843 }
844
845 static void
846 mount_free(pe__bundle_mount_t *mount)
847 {
848 free(mount->source);
849 free(mount->target);
850 free(mount->options);
851 free(mount);
852 }
853
854 static void
855 port_free(pe__bundle_port_t *port)
856 {
857 free(port->source);
858 free(port->target);
859 free(port);
860 }
861
862 static pcmk__bundle_replica_t *
863 replica_for_remote(pcmk_resource_t *remote)
864 {
865 pcmk_resource_t *top = remote;
866 pe__bundle_variant_data_t *bundle_data = NULL;
867
868 if (top == NULL) {
869 return NULL;
870 }
871 while (top->priv->parent != NULL) {
872 top = top->priv->parent;
873 }
874
875 get_bundle_variant_data(bundle_data, top);
876 for (GList *gIter = bundle_data->replicas; gIter != NULL;
877 gIter = gIter->next) {
878 pcmk__bundle_replica_t *replica = gIter->data;
879
880 if (replica->remote == remote) {
881 return replica;
882 }
883 }
884 CRM_LOG_ASSERT(FALSE);
885 return NULL;
886 }
887
888 bool
889 pe__bundle_needs_remote_name(pcmk_resource_t *rsc)
890 {
891 const char *value;
892 GHashTable *params = NULL;
893
894 if (rsc == NULL) {
895 return false;
896 }
897
898 // Use NULL node since pcmk__bundle_expand() uses that to set value
899 params = pe_rsc_params(rsc, NULL, rsc->priv->scheduler);
900 value = g_hash_table_lookup(params, PCMK_REMOTE_RA_ADDR);
901
902 return pcmk__str_eq(value, "#uname", pcmk__str_casei)
903 && xml_contains_remote_node(rsc->priv->xml);
904 }
905
906 const char *
907 pe__add_bundle_remote_name(pcmk_resource_t *rsc, xmlNode *xml,
908 const char *field)
909 {
910 // REMOTE_CONTAINER_HACK: Allow remote nodes that start containers with pacemaker remote inside
911
912 pcmk_node_t *node = NULL;
913 pcmk__bundle_replica_t *replica = NULL;
914
915 if (!pe__bundle_needs_remote_name(rsc)) {
916 return NULL;
917 }
918
919 replica = replica_for_remote(rsc);
920 if (replica == NULL) {
921 return NULL;
922 }
923
924 node = replica->container->priv->assigned_node;
925 if (node == NULL) {
926 /* If it won't be running anywhere after the
927 * transition, go with where it's running now.
928 */
929 node = pcmk__current_node(replica->container);
930 }
931
932 if(node == NULL) {
933 pcmk__trace("Cannot determine address for bundle connection %s",
934 rsc->id);
935 return NULL;
936 }
937
938 pcmk__trace("Setting address for bundle connection %s to bundle host %s",
939 rsc->id, pcmk__node_name(node));
940 if(xml != NULL && field != NULL) {
941 pcmk__xe_set(xml, field, node->priv->name);
942 }
943
944 return node->priv->name;
945 }
946
947 #define pe__set_bundle_mount_flags(mount_xml, flags, flags_to_set) do { \
948 flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \
949 "Bundle mount", pcmk__xe_id(mount_xml), \
950 flags, (flags_to_set), #flags_to_set); \
951 } while (0)
952
953 bool
954 pe__unpack_bundle(pcmk_resource_t *rsc)
955 {
956 const char *value = NULL;
957 xmlNode *xml_obj = NULL;
958 const xmlNode *xml_child = NULL;
959 xmlNode *xml_resource = NULL;
960 pe__bundle_variant_data_t *bundle_data = NULL;
961 bool need_log_mount = TRUE;
962
963 pcmk__assert(rsc != NULL);
964 pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id);
965
966 bundle_data = pcmk__assert_alloc(1, sizeof(pe__bundle_variant_data_t));
967 rsc->priv->variant_opaque = bundle_data;
968 bundle_data->prefix = strdup(rsc->id);
969
970 xml_obj = pcmk__xe_first_child(rsc->priv->xml, PCMK_XE_DOCKER, NULL,
971 NULL);
972 if (xml_obj != NULL) {
973 bundle_data->agent_type = PE__CONTAINER_AGENT_DOCKER;
974 }
975
976 if (xml_obj == NULL) {
977 xml_obj = pcmk__xe_first_child(rsc->priv->xml, PCMK_XE_PODMAN, NULL,
978 NULL);
979 if (xml_obj != NULL) {
980 bundle_data->agent_type = PE__CONTAINER_AGENT_PODMAN;
981 }
982 }
983
984 if (xml_obj == NULL) {
985 return FALSE;
986 }
987
988 // Use 0 for default, minimum, and invalid PCMK_XA_PROMOTED_MAX
989 value = pcmk__xe_get(xml_obj, PCMK_XA_PROMOTED_MAX);
990 pcmk__scan_min_int(value, &bundle_data->promoted_max, 0);
991
992 /* Default replicas to PCMK_XA_PROMOTED_MAX if it was specified and 1
993 * otherwise
994 */
995 value = pcmk__xe_get(xml_obj, PCMK_XA_REPLICAS);
996 if ((value == NULL) && (bundle_data->promoted_max > 0)) {
997 bundle_data->nreplicas = bundle_data->promoted_max;
998 } else {
999 pcmk__scan_min_int(value, &bundle_data->nreplicas, 1);
1000 }
1001
1002 /*
1003 * Communication between containers on the same host via the
1004 * floating IPs only works if the container is started with:
1005 * --userland-proxy=false --ip-masq=false
1006 */
1007 value = pcmk__xe_get(xml_obj, PCMK_XA_REPLICAS_PER_HOST);
1008 pcmk__scan_min_int(value, &bundle_data->nreplicas_per_host, 1);
1009 if (bundle_data->nreplicas_per_host == 1) {
1010 pcmk__clear_rsc_flags(rsc, pcmk__rsc_unique);
1011 }
1012
1013 bundle_data->container_command = pcmk__xe_get_copy(xml_obj,
1014 PCMK_XA_RUN_COMMAND);
1015 bundle_data->launcher_options = pcmk__xe_get_copy(xml_obj, PCMK_XA_OPTIONS);
1016 bundle_data->image = pcmk__xe_get_copy(xml_obj, PCMK_XA_IMAGE);
1017 bundle_data->container_network = pcmk__xe_get_copy(xml_obj,
1018 PCMK_XA_NETWORK);
1019
1020 xml_obj = pcmk__xe_first_child(rsc->priv->xml, PCMK_XE_NETWORK, NULL,
1021 NULL);
1022 if(xml_obj) {
1023 bundle_data->ip_range_start = pcmk__xe_get_copy(xml_obj,
1024 PCMK_XA_IP_RANGE_START);
1025 bundle_data->host_netmask = pcmk__xe_get_copy(xml_obj,
1026 PCMK_XA_HOST_NETMASK);
1027 bundle_data->host_network = pcmk__xe_get_copy(xml_obj,
1028 PCMK_XA_HOST_INTERFACE);
1029 bundle_data->control_port = pcmk__xe_get_copy(xml_obj,
1030 PCMK_XA_CONTROL_PORT);
1031
1032 value = pcmk__xe_get(xml_obj, PCMK_XA_ADD_HOST);
1033 if ((value == NULL)
1034 || (pcmk__parse_bool(value,
1035 &bundle_data->add_host) != pcmk_rc_ok)) {
1036
1037 // Default to true if unset or invaid
1038 bundle_data->add_host = true;
1039 }
1040
1041 for (xml_child = pcmk__xe_first_child(xml_obj, PCMK_XE_PORT_MAPPING,
1042 NULL, NULL);
1043 xml_child != NULL;
1044 xml_child = pcmk__xe_next(xml_child, PCMK_XE_PORT_MAPPING)) {
1045
1046 pe__bundle_port_t *port =
1047 pcmk__assert_alloc(1, sizeof(pe__bundle_port_t));
1048
1049 port->source = pcmk__xe_get_copy(xml_child, PCMK_XA_PORT);
1050
1051 if(port->source == NULL) {
1052 port->source = pcmk__xe_get_copy(xml_child, PCMK_XA_RANGE);
1053 } else {
1054 port->target = pcmk__xe_get_copy(xml_child,
1055 PCMK_XA_INTERNAL_PORT);
1056 }
1057
1058 if(port->source != NULL && strlen(port->source) > 0) {
1059 if(port->target == NULL) {
1060 port->target = strdup(port->source);
1061 }
1062 bundle_data->ports = g_list_append(bundle_data->ports, port);
1063
1064 } else {
1065 pcmk__config_err("Invalid " PCMK_XA_PORT " directive %s",
1066 pcmk__xe_id(xml_child));
1067 port_free(port);
1068 }
1069 }
1070 }
1071
1072 xml_obj = pcmk__xe_first_child(rsc->priv->xml, PCMK_XE_STORAGE, NULL,
1073 NULL);
1074 for (xml_child = pcmk__xe_first_child(xml_obj, PCMK_XE_STORAGE_MAPPING,
1075 NULL, NULL);
1076 xml_child != NULL;
1077 xml_child = pcmk__xe_next(xml_child, PCMK_XE_STORAGE_MAPPING)) {
1078
1079 const char *source = pcmk__xe_get(xml_child, PCMK_XA_SOURCE_DIR);
1080 const char *target = pcmk__xe_get(xml_child, PCMK_XA_TARGET_DIR);
1081 const char *options = pcmk__xe_get(xml_child, PCMK_XA_OPTIONS);
1082 int flags = pe__bundle_mount_none;
1083
1084 if (source == NULL) {
1085 source = pcmk__xe_get(xml_child, PCMK_XA_SOURCE_DIR_ROOT);
1086 pe__set_bundle_mount_flags(xml_child, flags,
1087 pe__bundle_mount_subdir);
1088 }
1089
1090 if (source && target) {
1091 mount_add(bundle_data, source, target, options, flags);
1092 if (strcmp(target, "/var/log") == 0) {
1093 need_log_mount = FALSE;
1094 }
1095 } else {
1096 pcmk__config_err("Invalid mount directive %s",
1097 pcmk__xe_id(xml_child));
1098 }
1099 }
1100
1101 xml_obj = pcmk__xe_first_child(rsc->priv->xml, PCMK_XE_PRIMITIVE, NULL,
1102 NULL);
1103 if (xml_obj && valid_network(bundle_data)) {
1104 const char *suffix = NULL;
1105 char *value = NULL;
1106 xmlNode *xml_set = NULL;
1107
1108 xml_resource = pcmk__xe_create(NULL, PCMK_XE_CLONE);
1109
1110 /* @COMPAT We no longer use the <master> tag, but we need to keep it as
1111 * part of the resource name, so that bundles don't restart in a rolling
1112 * upgrade. (It also avoids needing to change regression tests.)
1113 */
1114 suffix = (const char *) xml_resource->name;
1115 if (bundle_data->promoted_max > 0) {
1116 suffix = "master";
1117 }
1118
1119 pcmk__xe_set_id(xml_resource, "%s-%s", bundle_data->prefix, suffix);
1120
1121 xml_set = pcmk__xe_create(xml_resource, PCMK_XE_META_ATTRIBUTES);
1122 pcmk__xe_set_id(xml_set, "%s-%s-meta",
1123 bundle_data->prefix, xml_resource->name);
1124
1125 crm_create_nvpair_xml(xml_set, NULL,
1126 PCMK_META_ORDERED, PCMK_VALUE_TRUE);
1127
1128 value = pcmk__itoa(bundle_data->nreplicas);
1129 crm_create_nvpair_xml(xml_set, NULL, PCMK_META_CLONE_MAX, value);
1130 free(value);
1131
1132 value = pcmk__itoa(bundle_data->nreplicas_per_host);
1133 crm_create_nvpair_xml(xml_set, NULL, PCMK_META_CLONE_NODE_MAX, value);
1134 free(value);
1135
1136 crm_create_nvpair_xml(xml_set, NULL, PCMK_META_GLOBALLY_UNIQUE,
1137 pcmk__btoa(bundle_data->nreplicas_per_host > 1));
1138
1139 if (bundle_data->promoted_max) {
1140 crm_create_nvpair_xml(xml_set, NULL,
1141 PCMK_META_PROMOTABLE, PCMK_VALUE_TRUE);
1142
1143 value = pcmk__itoa(bundle_data->promoted_max);
1144 crm_create_nvpair_xml(xml_set, NULL, PCMK_META_PROMOTED_MAX, value);
1145 free(value);
1146 }
1147
1148 //pcmk__xe_set(xml_obj, PCMK_XA_ID, bundle_data->prefix);
1149 pcmk__xml_copy(xml_resource, xml_obj);
1150
1151 } else if(xml_obj) {
1152 pcmk__config_err("Cannot control %s inside %s without either "
1153 PCMK_XA_IP_RANGE_START " or " PCMK_XA_CONTROL_PORT,
1154 rsc->id, pcmk__xe_id(xml_obj));
1155 return FALSE;
1156 }
1157
1158 if(xml_resource) {
1159 int lpc = 0;
1160 GList *childIter = NULL;
1161 pe__bundle_port_t *port = NULL;
1162 GString *buffer = NULL;
1163 int rc = pe__unpack_resource(xml_resource, &bundle_data->child, rsc,
1164 rsc->priv->scheduler);
1165
1166 pcmk__xml_free(xml_resource);
1167
1168 if (rc != pcmk_rc_ok) {
1169 return FALSE;
1170 }
1171
1172 /* Currently, we always map the default authentication key location
1173 * into the same location inside the container.
1174 *
1175 * Ideally, we would respect the host's PCMK_authkey_location, but:
1176 * - it may be different on different nodes;
1177 * - the actual connection will do extra checking to make sure the key
1178 * file exists and is readable, that we can't do here on the DC
1179 * - tools such as crm_resource and crm_simulate may not have the same
1180 * environment variables as the cluster, causing operation digests to
1181 * differ
1182 *
1183 * Always using the default location inside the container is fine,
1184 * because we control the pacemaker_remote environment, and it avoids
1185 * having to pass another environment variable to the container.
1186 *
1187 * @TODO A better solution may be to have only pacemaker_remote use the
1188 * environment variable, and have the cluster nodes use a new
1189 * cluster option for key location. This would introduce the limitation
1190 * of the location being the same on all cluster nodes, but that's
1191 * reasonable.
1192 */
1193 mount_add(bundle_data, DEFAULT_REMOTE_KEY_LOCATION,
1194 DEFAULT_REMOTE_KEY_LOCATION, NULL, pe__bundle_mount_none);
1195
1196 if (need_log_mount) {
1197 mount_add(bundle_data, CRM_BUNDLE_DIR, "/var/log", NULL,
1198 pe__bundle_mount_subdir);
1199 }
1200
1201 port = pcmk__assert_alloc(1, sizeof(pe__bundle_port_t));
1202 if(bundle_data->control_port) {
1203 port->source = strdup(bundle_data->control_port);
1204 } else {
1205 /* If we wanted to respect PCMK_remote_port, we could use
1206 * crm_default_remote_port() here and elsewhere in this file instead
1207 * of DEFAULT_REMOTE_PORT.
1208 *
1209 * However, it gains nothing, since we control both the container
1210 * environment and the connection resource parameters, and the user
1211 * can use a different port if desired by setting
1212 * PCMK_XA_CONTROL_PORT.
1213 */
1214 port->source = pcmk__itoa(DEFAULT_REMOTE_PORT);
1215 }
1216 port->target = strdup(port->source);
1217 bundle_data->ports = g_list_append(bundle_data->ports, port);
1218
1219 buffer = g_string_sized_new(1024);
1220 for (childIter = bundle_data->child->priv->children;
1221 childIter != NULL; childIter = childIter->next) {
1222
1223 pcmk__bundle_replica_t *replica = NULL;
1224
1225 replica = pcmk__assert_alloc(1, sizeof(pcmk__bundle_replica_t));
1226 replica->child = childIter->data;
1227 pcmk__set_rsc_flags(replica->child, pcmk__rsc_exclusive_probes);
1228 replica->offset = lpc++;
1229
1230 // Ensure the child's notify gets set based on the underlying primitive's value
1231 if (pcmk__is_set(replica->child->flags, pcmk__rsc_notify)) {
1232 pcmk__set_rsc_flags(bundle_data->child, pcmk__rsc_notify);
1233 }
1234
1235 allocate_ip(bundle_data, replica, buffer);
1236 bundle_data->replicas = g_list_append(bundle_data->replicas,
1237 replica);
1238 // coverity[null_field] replica->child can't be NULL here
1239 bundle_data->attribute_target =
1240 g_hash_table_lookup(replica->child->priv->meta,
1241 PCMK_META_CONTAINER_ATTRIBUTE_TARGET);
1242 }
1243 bundle_data->container_host_options = g_string_free(buffer, FALSE);
1244
1245 if (bundle_data->attribute_target) {
1246 pcmk__insert_dup(rsc->priv->meta,
1247 PCMK_META_CONTAINER_ATTRIBUTE_TARGET,
1248 bundle_data->attribute_target);
1249 pcmk__insert_dup(bundle_data->child->priv->meta,
1250 PCMK_META_CONTAINER_ATTRIBUTE_TARGET,
1251 bundle_data->attribute_target);
1252 }
1253
1254 } else {
1255 // Just a naked container, no pacemaker-remote
1256 GString *buffer = g_string_sized_new(1024);
1257
1258 for (int lpc = 0; lpc < bundle_data->nreplicas; lpc++) {
1259 pcmk__bundle_replica_t *replica = NULL;
1260
1261 replica = pcmk__assert_alloc(1, sizeof(pcmk__bundle_replica_t));
1262 replica->offset = lpc;
1263 allocate_ip(bundle_data, replica, buffer);
1264 bundle_data->replicas = g_list_append(bundle_data->replicas,
1265 replica);
1266 }
1267 bundle_data->container_host_options = g_string_free(buffer, FALSE);
1268 }
1269
1270 for (GList *gIter = bundle_data->replicas; gIter != NULL;
1271 gIter = gIter->next) {
1272 pcmk__bundle_replica_t *replica = gIter->data;
1273
1274 if (create_replica_resources(rsc, bundle_data, replica) != pcmk_rc_ok) {
1275 pcmk__config_err("Failed unpacking resource %s", rsc->id);
1276 pcmk__free_resource(rsc);
1277 return FALSE;
1278 }
1279
1280 /* Utilization needs special handling for bundles. It makes no sense for
1281 * the inner primitive to have utilization, because it is tied
1282 * one-to-one to the guest node created by the container resource -- and
1283 * there's no way to set capacities for that guest node anyway.
1284 *
1285 * What the user really wants is to configure utilization for the
1286 * container. However, the schema only allows utilization for
1287 * primitives, and the container resource is implicit anyway, so the
1288 * user can *only* configure utilization for the inner primitive. If
1289 * they do, move the primitive's utilization values to the container.
1290 *
1291 * @TODO This means that bundles without an inner primitive can't have
1292 * utilization. An alternative might be to allow utilization values in
1293 * the top-level bundle XML in the schema, and copy those to each
1294 * container.
1295 */
1296 if (replica->child != NULL) {
1297 GHashTable *empty = replica->container->priv->utilization;
1298
1299 replica->container->priv->utilization =
1300 replica->child->priv->utilization;
1301
1302 replica->child->priv->utilization = empty;
1303 }
1304 }
1305
1306 if (bundle_data->child) {
1307 rsc->priv->children = g_list_append(rsc->priv->children,
1308 bundle_data->child);
1309 }
1310 return TRUE;
1311 }
1312
1313 static int
1314 replica_resource_active(pcmk_resource_t *rsc, gboolean all)
1315 {
1316 if (rsc) {
1317 gboolean child_active = rsc->priv->fns->active(rsc, all);
1318
1319 if (child_active && !all) {
1320 return TRUE;
1321 } else if (!child_active && all) {
1322 return FALSE;
1323 }
1324 }
1325 return -1;
1326 }
1327
1328 bool
1329 pe__bundle_active(const pcmk_resource_t *rsc, bool all)
1330 {
1331 pe__bundle_variant_data_t *bundle_data = NULL;
1332 GList *iter = NULL;
1333
1334 get_bundle_variant_data(bundle_data, rsc);
1335 for (iter = bundle_data->replicas; iter != NULL; iter = iter->next) {
1336 pcmk__bundle_replica_t *replica = iter->data;
1337 int rsc_active;
1338
1339 rsc_active = replica_resource_active(replica->ip, all);
1340 if (rsc_active >= 0) {
1341 return (bool) rsc_active;
1342 }
1343
1344 rsc_active = replica_resource_active(replica->child, all);
1345 if (rsc_active >= 0) {
1346 return (bool) rsc_active;
1347 }
1348
1349 rsc_active = replica_resource_active(replica->container, all);
1350 if (rsc_active >= 0) {
1351 return (bool) rsc_active;
1352 }
1353
1354 rsc_active = replica_resource_active(replica->remote, all);
1355 if (rsc_active >= 0) {
1356 return (bool) rsc_active;
1357 }
1358 }
1359
1360 /* If "all" is TRUE, we've already checked that no resources were inactive,
1361 * so return TRUE; if "all" is FALSE, we didn't find any active resources,
1362 * so return FALSE.
1363 */
1364 return all;
1365 }
1366
1367 /*!
1368 * \internal
1369 * \brief Find the bundle replica corresponding to a given node
1370 *
1371 * \param[in] bundle Top-level bundle resource
1372 * \param[in] node Node to search for
1373 *
1374 * \return Bundle replica if found, NULL otherwise
1375 */
1376 pcmk_resource_t *
1377 pe__find_bundle_replica(const pcmk_resource_t *bundle, const pcmk_node_t *node)
1378 {
1379 pe__bundle_variant_data_t *bundle_data = NULL;
1380
1381 pcmk__assert((bundle != NULL) && (node != NULL));
1382
1383 get_bundle_variant_data(bundle_data, bundle);
1384 for (GList *gIter = bundle_data->replicas; gIter != NULL;
1385 gIter = gIter->next) {
1386 pcmk__bundle_replica_t *replica = gIter->data;
1387
1388 pcmk__assert((replica != NULL) && (replica->node != NULL));
1389 if (pcmk__same_node(replica->node, node)) {
1390 return replica->child;
1391 }
1392 }
1393 return NULL;
1394 }
1395
1396 PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pcmk_resource_t *", "GList *",
1397 "GList *")
1398 int
1399 pe__bundle_xml(pcmk__output_t *out, va_list args)
1400 {
1401 uint32_t show_opts = va_arg(args, uint32_t);
1402 pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
1403 GList *only_node = va_arg(args, GList *);
1404 GList *only_rsc = va_arg(args, GList *);
1405
1406 pe__bundle_variant_data_t *bundle_data = NULL;
1407 int rc = pcmk_rc_no_output;
1408 gboolean printed_header = FALSE;
1409 bool print_everything = true;
1410
1411 const char *desc = NULL;
1412
1413 pcmk__assert(rsc != NULL);
1414 get_bundle_variant_data(bundle_data, rsc);
1415
1416 if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
1417 return rc;
1418 }
1419
1420 print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches);
1421
1422 for (GList *gIter = bundle_data->replicas; gIter != NULL;
1423 gIter = gIter->next) {
1424 pcmk__bundle_replica_t *replica = gIter->data;
1425 pcmk_resource_t *ip = replica->ip;
1426 pcmk_resource_t *child = replica->child;
1427 pcmk_resource_t *container = replica->container;
1428 pcmk_resource_t *remote = replica->remote;
1429 char *id = NULL;
1430 gboolean print_ip, print_child, print_ctnr, print_remote;
1431
1432 pcmk__assert(replica != NULL);
1433
1434 if (pcmk__rsc_filtered_by_node(container, only_node)) {
1435 continue;
1436 }
1437
1438 print_ip = (ip != NULL)
1439 && !ip->priv->fns->is_filtered(ip, only_rsc,
1440 print_everything);
1441 print_child = (child != NULL)
1442 && !child->priv->fns->is_filtered(child, only_rsc,
1443 print_everything);
1444 print_ctnr = !container->priv->fns->is_filtered(container, only_rsc,
1445 print_everything);
1446 print_remote = (remote != NULL)
1447 && !remote->priv->fns->is_filtered(remote, only_rsc,
1448 print_everything);
1449
1450 if (!print_everything && !print_ip && !print_child && !print_ctnr && !print_remote) {
1451 continue;
1452 }
1453
1454 if (!printed_header) {
1455 const char *type = container_agent_str(bundle_data->agent_type);
1456 const char *unique = pcmk__flag_text(rsc->flags, pcmk__rsc_unique);
1457 const char *maintenance = pcmk__flag_text(rsc->flags,
1458 pcmk__rsc_maintenance);
1459 const char *managed = pcmk__flag_text(rsc->flags,
1460 pcmk__rsc_managed);
1461 const char *failed = pcmk__flag_text(rsc->flags, pcmk__rsc_failed);
1462
1463 printed_header = TRUE;
1464
1465 desc = pe__resource_description(rsc, show_opts);
1466
1467 rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_BUNDLE,
1468 PCMK_XA_ID, rsc->id,
1469 PCMK_XA_TYPE, type,
1470 PCMK_XA_IMAGE, bundle_data->image,
1471 PCMK_XA_UNIQUE, unique,
1472 PCMK_XA_MAINTENANCE, maintenance,
1473 PCMK_XA_MANAGED, managed,
1474 PCMK_XA_FAILED, failed,
1475 PCMK_XA_DESCRIPTION, desc,
1476 NULL);
1477 pcmk__assert(rc == pcmk_rc_ok);
1478 }
1479
1480 id = pcmk__itoa(replica->offset);
1481 rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_REPLICA,
1482 PCMK_XA_ID, id,
1483 NULL);
1484 free(id);
1485 pcmk__assert(rc == pcmk_rc_ok);
1486
1487 if (print_ip) {
1488 out->message(out, (const char *) ip->priv->xml->name, show_opts,
1489 ip, only_node, only_rsc);
1490 }
1491
1492 if (print_child) {
1493 out->message(out, (const char *) child->priv->xml->name,
1494 show_opts, child, only_node, only_rsc);
1495 }
1496
1497 if (print_ctnr) {
1498 out->message(out, (const char *) container->priv->xml->name,
1499 show_opts, container, only_node, only_rsc);
1500 }
1501
1502 if (print_remote) {
1503 out->message(out, (const char *) remote->priv->xml->name,
1504 show_opts, remote, only_node, only_rsc);
1505 }
1506
1507 pcmk__output_xml_pop_parent(out); // replica
1508 }
1509
1510 if (printed_header) {
1511 pcmk__output_xml_pop_parent(out); // bundle
1512 }
1513
1514 return rc;
1515 }
1516
1517 static void
1518 pe__bundle_replica_output_html(pcmk__output_t *out,
1519 pcmk__bundle_replica_t *replica,
1520 pcmk_node_t *node, uint32_t show_opts)
1521 {
1522 const pcmk_resource_t *child_rsc = replica->child;
1523 const pcmk_resource_t *remote_rsc = replica->remote;
1524 GString *buffer = g_string_sized_new(128);
1525
1526 if (child_rsc == NULL) {
1527 child_rsc = replica->container;
1528 }
1529 if (remote_rsc == NULL) {
1530 remote_rsc = replica->container;
1531 }
1532
1533 g_string_append(buffer, rsc_printable_id(remote_rsc));
1534 if (replica->ipaddr != NULL) {
1535 pcmk__g_strcat(buffer, " (", replica->ipaddr, ")", NULL);
1536 }
1537
1538 pe__common_output_html(out, child_rsc, buffer->str, node, show_opts);
1539 g_string_free(buffer, TRUE);
1540 }
1541
1542 /*!
1543 * \internal
1544 * \brief Get a string describing a resource's unmanaged state or lack thereof
1545 *
1546 * \param[in] rsc Resource to describe
1547 *
1548 * \return A string indicating that a resource is in maintenance mode or
1549 * otherwise unmanaged, or an empty string otherwise
1550 */
1551 static const char *
1552 get_unmanaged_str(const pcmk_resource_t *rsc)
1553 {
1554 if (pcmk__is_set(rsc->flags, pcmk__rsc_maintenance)) {
1555 return " (maintenance)";
1556 }
1557 if (!pcmk__is_set(rsc->flags, pcmk__rsc_managed)) {
1558 return " (unmanaged)";
1559 }
1560 return "";
1561 }
1562
1563 PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pcmk_resource_t *", "GList *",
1564 "GList *")
1565 int
1566 pe__bundle_html(pcmk__output_t *out, va_list args)
1567 {
1568 uint32_t show_opts = va_arg(args, uint32_t);
1569 pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
1570 GList *only_node = va_arg(args, GList *);
1571 GList *only_rsc = va_arg(args, GList *);
1572
1573 const char *desc = NULL;
1574 pe__bundle_variant_data_t *bundle_data = NULL;
1575 int rc = pcmk_rc_no_output;
1576 bool print_everything = true;
1577
1578 pcmk__assert(rsc != NULL);
1579 get_bundle_variant_data(bundle_data, rsc);
1580
1581 desc = pe__resource_description(rsc, show_opts);
1582
1583 if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
1584 return rc;
1585 }
1586
1587 print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches);
1588
1589 for (GList *gIter = bundle_data->replicas; gIter != NULL;
1590 gIter = gIter->next) {
1591 pcmk__bundle_replica_t *replica = gIter->data;
1592 pcmk_resource_t *ip = replica->ip;
1593 pcmk_resource_t *child = replica->child;
1594 pcmk_resource_t *container = replica->container;
1595 pcmk_resource_t *remote = replica->remote;
1596 gboolean print_ip, print_child, print_ctnr, print_remote;
1597
1598 pcmk__assert(replica != NULL);
1599
1600 if (pcmk__rsc_filtered_by_node(container, only_node)) {
1601 continue;
1602 }
1603
1604 print_ip = (ip != NULL)
1605 && !ip->priv->fns->is_filtered(ip, only_rsc,
1606 print_everything);
1607 print_child = (child != NULL)
1608 && !child->priv->fns->is_filtered(child, only_rsc,
1609 print_everything);
1610 print_ctnr = !container->priv->fns->is_filtered(container, only_rsc,
1611 print_everything);
1612 print_remote = (remote != NULL)
1613 && !remote->priv->fns->is_filtered(remote, only_rsc,
1614 print_everything);
1615
1616 if (pcmk__is_set(show_opts, pcmk_show_implicit_rscs)
1617 || (!print_everything
1618 && (print_ip || print_child || print_ctnr || print_remote))) {
1619 /* The text output messages used below require pe_print_implicit to
1620 * be set to do anything.
1621 */
1622 const bool multiple = (bundle_data->nreplicas > 1);
1623 const bool unique = pcmk__is_set(rsc->flags, pcmk__rsc_unique);
1624 const uint32_t new_show_opts = show_opts | pcmk_show_implicit_rscs;
1625
1626 PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc,
1627 "Container bundle%s: %s [%s]%s%s%s%s%s",
1628 (multiple? " set" : ""), rsc->id,
1629 bundle_data->image,
1630 (unique? " (unique)" : ""),
1631 ((desc != NULL)? " (" : ""),
1632 pcmk__s(desc, ""),
1633 ((desc != NULL)? ")" : ""),
1634 get_unmanaged_str(rsc));
1635
1636 if (pcmk__list_of_multiple(bundle_data->replicas)) {
1637 out->begin_list(out, NULL, NULL, "Replica[%d]", replica->offset);
1638 }
1639
1640 if (print_ip) {
1641 out->message(out, (const char *) ip->priv->xml->name,
1642 new_show_opts, ip, only_node, only_rsc);
1643 }
1644
1645 if (print_child) {
1646 out->message(out, (const char *) child->priv->xml->name,
1647 new_show_opts, child, only_node, only_rsc);
1648 }
1649
1650 if (print_ctnr) {
1651 out->message(out, (const char *) container->priv->xml->name,
1652 new_show_opts, container, only_node, only_rsc);
1653 }
1654
1655 if (print_remote) {
1656 out->message(out, (const char *) remote->priv->xml->name,
1657 new_show_opts, remote, only_node, only_rsc);
1658 }
1659
1660 if (pcmk__list_of_multiple(bundle_data->replicas)) {
1661 out->end_list(out);
1662 }
1663 } else if (print_everything == FALSE && !(print_ip || print_child || print_ctnr || print_remote)) {
1664 continue;
1665 } else {
1666 const bool multiple = (bundle_data->nreplicas > 1);
1667 const bool unique = pcmk__is_set(rsc->flags, pcmk__rsc_unique);
1668
1669 PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc,
1670 "Container bundle%s: %s [%s]%s%s%s%s%s",
1671 (multiple? " set" : ""), rsc->id,
1672 bundle_data->image,
1673 (unique? " (unique)" : ""),
1674 ((desc != NULL)? " (" : ""),
1675 pcmk__s(desc, ""),
1676 ((desc != NULL)? ")" : ""),
1677 get_unmanaged_str(rsc));
1678
1679 pe__bundle_replica_output_html(out, replica,
1680 pcmk__current_node(container),
1681 show_opts);
1682 }
1683 }
1684
1685 PCMK__OUTPUT_LIST_FOOTER(out, rc);
1686 return rc;
1687 }
1688
1689 static void
1690 pe__bundle_replica_output_text(pcmk__output_t *out,
1691 pcmk__bundle_replica_t *replica,
1692 pcmk_node_t *node, uint32_t show_opts)
1693 {
1694 const pcmk_resource_t *child_rsc = replica->child;
1695 const pcmk_resource_t *remote_rsc = replica->remote;
1696 GString *buffer = g_string_sized_new(128);
1697
1698 if (child_rsc == NULL) {
1699 child_rsc = replica->container;
1700 }
1701 if (remote_rsc == NULL) {
1702 remote_rsc = replica->container;
1703 }
1704
1705 g_string_append(buffer, rsc_printable_id(remote_rsc));
1706 if (replica->ipaddr != NULL) {
1707 pcmk__g_strcat(buffer, " (", replica->ipaddr, ")", NULL);
1708 }
1709
1710 pe__common_output_text(out, child_rsc, buffer->str, node, show_opts);
1711 g_string_free(buffer, TRUE);
1712 }
1713
1714 PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pcmk_resource_t *", "GList *",
1715 "GList *")
1716 int
1717 pe__bundle_text(pcmk__output_t *out, va_list args)
1718 {
1719 uint32_t show_opts = va_arg(args, uint32_t);
1720 pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
1721 GList *only_node = va_arg(args, GList *);
1722 GList *only_rsc = va_arg(args, GList *);
1723
1724 const char *desc = NULL;
1725 pe__bundle_variant_data_t *bundle_data = NULL;
1726 int rc = pcmk_rc_no_output;
1727 bool print_everything = true;
1728
1729 desc = pe__resource_description(rsc, show_opts);
1730
1731 pcmk__assert(rsc != NULL);
1732 get_bundle_variant_data(bundle_data, rsc);
1733
1734 if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
1735 return rc;
1736 }
1737
1738 print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches);
1739
1740 for (GList *gIter = bundle_data->replicas; gIter != NULL;
1741 gIter = gIter->next) {
1742 pcmk__bundle_replica_t *replica = gIter->data;
1743 pcmk_resource_t *ip = replica->ip;
1744 pcmk_resource_t *child = replica->child;
1745 pcmk_resource_t *container = replica->container;
1746 pcmk_resource_t *remote = replica->remote;
1747 gboolean print_ip, print_child, print_ctnr, print_remote;
1748
1749 pcmk__assert(replica != NULL);
1750
1751 if (pcmk__rsc_filtered_by_node(container, only_node)) {
1752 continue;
1753 }
1754
1755 print_ip = (ip != NULL)
1756 && !ip->priv->fns->is_filtered(ip, only_rsc,
1757 print_everything);
1758 print_child = (child != NULL)
1759 && !child->priv->fns->is_filtered(child, only_rsc,
1760 print_everything);
1761 print_ctnr = !container->priv->fns->is_filtered(container, only_rsc,
1762 print_everything);
1763 print_remote = (remote != NULL)
1764 && !remote->priv->fns->is_filtered(remote, only_rsc,
1765 print_everything);
1766
1767 if (pcmk__is_set(show_opts, pcmk_show_implicit_rscs)
1768 || (!print_everything
1769 && (print_ip || print_child || print_ctnr || print_remote))) {
1770 /* The text output messages used below require pe_print_implicit to
1771 * be set to do anything.
1772 */
1773 const bool multiple = (bundle_data->nreplicas > 1);
1774 const bool unique = pcmk__is_set(rsc->flags, pcmk__rsc_unique);
1775 const uint32_t new_show_opts = show_opts | pcmk_show_implicit_rscs;
1776
1777 PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc,
1778 "Container bundle%s: %s [%s]%s%s%s%s%s",
1779 (multiple? " set" : ""), rsc->id,
1780 bundle_data->image,
1781 (unique? " (unique)" : ""),
1782 ((desc != NULL)? " (" : ""),
1783 pcmk__s(desc, ""),
1784 ((desc != NULL)? ")" : ""),
1785 get_unmanaged_str(rsc));
1786
1787 if (pcmk__list_of_multiple(bundle_data->replicas)) {
1788 out->list_item(out, NULL, "Replica[%d]", replica->offset);
1789 }
1790
1791 out->begin_list(out, NULL, NULL, NULL);
1792
1793 if (print_ip) {
1794 out->message(out, (const char *) ip->priv->xml->name,
1795 new_show_opts, ip, only_node, only_rsc);
1796 }
1797
1798 if (print_child) {
1799 out->message(out, (const char *) child->priv->xml->name,
1800 new_show_opts, child, only_node, only_rsc);
1801 }
1802
1803 if (print_ctnr) {
1804 out->message(out, (const char *) container->priv->xml->name,
1805 new_show_opts, container, only_node, only_rsc);
1806 }
1807
1808 if (print_remote) {
1809 out->message(out, (const char *) remote->priv->xml->name,
1810 new_show_opts, remote, only_node, only_rsc);
1811 }
1812
1813 out->end_list(out);
1814 } else if (print_everything == FALSE && !(print_ip || print_child || print_ctnr || print_remote)) {
1815 continue;
1816 } else {
1817 const bool multiple = (bundle_data->nreplicas > 1);
1818 const bool unique = pcmk__is_set(rsc->flags, pcmk__rsc_unique);
1819
1820 PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc,
1821 "Container bundle%s: %s [%s]%s%s%s%s%s",
1822 (multiple? " set" : ""), rsc->id,
1823 bundle_data->image,
1824 (unique? " (unique)" : ""),
1825 ((desc != NULL)? " (" : ""),
1826 pcmk__s(desc, ""),
1827 ((desc != NULL)? ")" : ""),
1828 get_unmanaged_str(rsc));
1829
1830 pe__bundle_replica_output_text(out, replica,
1831 pcmk__current_node(container),
1832 show_opts);
1833 }
1834 }
1835
1836 PCMK__OUTPUT_LIST_FOOTER(out, rc);
1837 return rc;
1838 }
1839
1840 static void
1841 free_bundle_replica(pcmk__bundle_replica_t *replica)
1842 {
|
(1) Event path: |
Condition "replica == NULL", taking false branch. |
1843 if (replica == NULL) {
1844 return;
1845 }
1846
|
CID (unavailable; MK=aee4d4f996447b196b118fbfa7ddf1e9) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(2) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(3) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
1847 g_clear_pointer(&replica->node, pcmk__free_node_copy);
1848
1849 pcmk__free_resource(replica->ip);
1850 pcmk__free_resource(replica->container);
1851 pcmk__free_resource(replica->remote);
1852
1853 free(replica->ipaddr);
1854 free(replica);
1855 }
1856
1857 void
1858 pe__free_bundle(pcmk_resource_t *rsc)
1859 {
1860 pe__bundle_variant_data_t *bundle_data = NULL;
1861 CRM_CHECK(rsc != NULL, return);
1862
1863 get_bundle_variant_data(bundle_data, rsc);
1864 pcmk__rsc_trace(rsc, "Freeing %s", rsc->id);
1865
1866 free(bundle_data->prefix);
1867 free(bundle_data->image);
1868 free(bundle_data->control_port);
1869 free(bundle_data->host_network);
1870 free(bundle_data->host_netmask);
1871 free(bundle_data->ip_range_start);
1872 free(bundle_data->container_network);
1873 free(bundle_data->launcher_options);
1874 free(bundle_data->container_command);
1875 g_free(bundle_data->container_host_options);
1876
1877 g_list_free_full(bundle_data->replicas,
1878 (GDestroyNotify) free_bundle_replica);
1879 g_list_free_full(bundle_data->mounts, (GDestroyNotify)mount_free);
1880 g_list_free_full(bundle_data->ports, (GDestroyNotify)port_free);
1881 g_list_free(rsc->priv->children);
1882
1883 pcmk__free_resource(bundle_data->child);
1884
1885 common_free(rsc);
1886 }
1887
1888 enum rsc_role_e
1889 pe__bundle_resource_state(const pcmk_resource_t *rsc, bool current)
1890 {
1891 enum rsc_role_e container_role = pcmk_role_unknown;
1892 return container_role;
1893 }
1894
1895 /*!
1896 * \brief Get the number of configured replicas in a bundle
1897 *
1898 * \param[in] rsc Bundle resource
1899 *
1900 * \return Number of configured replicas, or 0 on error
1901 */
1902 int
1903 pe_bundle_replicas(const pcmk_resource_t *rsc)
1904 {
1905 if (pcmk__is_bundle(rsc)) {
1906 pe__bundle_variant_data_t *bundle_data = NULL;
1907
1908 get_bundle_variant_data(bundle_data, rsc);
1909 return bundle_data->nreplicas;
1910 }
1911 return 0;
1912 }
1913
1914 void
1915 pe__count_bundle(pcmk_resource_t *rsc)
1916 {
1917 pe__bundle_variant_data_t *bundle_data = NULL;
1918
1919 get_bundle_variant_data(bundle_data, rsc);
1920 for (GList *item = bundle_data->replicas; item != NULL; item = item->next) {
1921 pcmk__bundle_replica_t *replica = item->data;
1922
1923 if (replica->ip) {
1924 replica->ip->priv->fns->count(replica->ip);
1925 }
1926 if (replica->child) {
1927 replica->child->priv->fns->count(replica->child);
1928 }
1929 if (replica->container) {
1930 replica->container->priv->fns->count(replica->container);
1931 }
1932 if (replica->remote) {
1933 replica->remote->priv->fns->count(replica->remote);
1934 }
1935 }
1936 }
1937
1938 bool
1939 pe__bundle_is_filtered(const pcmk_resource_t *rsc, const GList *only_rsc,
1940 bool check_parent)
1941 {
1942 pe__bundle_variant_data_t *bundle_data = NULL;
1943
1944 if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
1945 pcmk__str_star_matches)) {
1946 return false;
1947 }
1948
1949 get_bundle_variant_data(bundle_data, rsc);
1950
1951 for (const GList *iter = bundle_data->replicas; iter != NULL;
1952 iter = iter->next) {
1953 const pcmk__bundle_replica_t *replica = iter->data;
1954
1955 const pcmk_resource_t *ip = replica->ip;
1956 const pcmk_resource_t *child = replica->child;
1957 const pcmk_resource_t *container = replica->container;
1958 const pcmk_resource_t *remote = replica->remote;
1959
1960 if ((ip != NULL) && !ip->priv->fns->is_filtered(ip, only_rsc, false)) {
1961 return false;
1962 }
1963
1964 if ((child != NULL)
1965 && !child->priv->fns->is_filtered(child, only_rsc, false)) {
1966
1967 return false;
1968 }
1969
1970 if (!container->priv->fns->is_filtered(container, only_rsc, false)) {
1971 return false;
1972 }
1973
1974 if ((remote != NULL)
1975 && !remote->priv->fns->is_filtered(remote, only_rsc, false)) {
1976
1977 return false;
1978 }
1979 }
1980
1981 return true;
1982 }
1983
1984 /*!
1985 * \internal
1986 * \brief Get a list of a bundle's containers
1987 *
1988 * \param[in] bundle Bundle resource
1989 *
1990 * \return Newly created list of \p bundle's containers
1991 * \note It is the caller's responsibility to free the result with
1992 * g_list_free().
1993 */
1994 GList *
1995 pe__bundle_containers(const pcmk_resource_t *bundle)
1996 {
1997 /* @TODO It would be more efficient to do this once when unpacking the
1998 * bundle, creating a new GList* in the variant data
1999 */
2000 GList *containers = NULL;
2001 const pe__bundle_variant_data_t *data = NULL;
2002
2003 get_bundle_variant_data(data, bundle);
2004 for (GList *iter = data->replicas; iter != NULL; iter = iter->next) {
2005 pcmk__bundle_replica_t *replica = iter->data;
2006
2007 containers = g_list_append(containers, replica->container);
2008 }
2009 return containers;
2010 }
2011
2012 // Bundle implementation of pcmk__rsc_methods_t:active_node()
2013 pcmk_node_t *
2014 pe__bundle_active_node(const pcmk_resource_t *rsc, unsigned int *count_all,
2015 unsigned int *count_clean)
2016 {
2017 pcmk_node_t *active = NULL;
2018 pcmk_node_t *node = NULL;
2019 pcmk_resource_t *container = NULL;
2020 GList *containers = NULL;
2021 GList *iter = NULL;
2022 GHashTable *nodes = NULL;
2023 const pe__bundle_variant_data_t *data = NULL;
2024
2025 if (count_all != NULL) {
2026 *count_all = 0;
2027 }
2028 if (count_clean != NULL) {
2029 *count_clean = 0;
2030 }
2031 if (rsc == NULL) {
2032 return NULL;
2033 }
2034
2035 /* For the purposes of this method, we only care about where the bundle's
2036 * containers are active, so build a list of active containers.
2037 */
2038 get_bundle_variant_data(data, rsc);
2039 for (iter = data->replicas; iter != NULL; iter = iter->next) {
2040 pcmk__bundle_replica_t *replica = iter->data;
2041
2042 if (replica->container->priv->active_nodes != NULL) {
2043 containers = g_list_append(containers, replica->container);
2044 }
2045 }
2046 if (containers == NULL) {
2047 return NULL;
2048 }
2049
2050 /* If the bundle has only a single active container, just use that
2051 * container's method. If live migration is ever supported for bundle
2052 * containers, this will allow us to prefer the migration source when there
2053 * is only one container and it is migrating. For now, this just lets us
2054 * avoid creating the nodes table.
2055 */
2056 if (pcmk__list_of_1(containers)) {
2057 container = containers->data;
2058 node = container->priv->fns->active_node(container, count_all,
2059 count_clean);
2060 g_list_free(containers);
2061 return node;
2062 }
2063
2064 // Add all containers' active nodes to a hash table (for uniqueness)
2065 nodes = g_hash_table_new(NULL, NULL);
2066 for (iter = containers; iter != NULL; iter = iter->next) {
2067 container = iter->data;
2068 for (GList *node_iter = container->priv->active_nodes;
2069 node_iter != NULL; node_iter = node_iter->next) {
2070
2071 node = node_iter->data;
2072
2073 // If insert returns true, we haven't counted this node yet
2074 if (g_hash_table_insert(nodes, (gpointer) node->details,
2075 (gpointer) node)
2076 && !pe__count_active_node(rsc, node, &active, count_all,
2077 count_clean)) {
2078 goto done;
2079 }
2080 }
2081 }
2082
2083 done:
2084 g_list_free(containers);
2085 g_hash_table_destroy(nodes);
2086 return active;
2087 }
2088
2089 /*!
2090 * \internal
2091 * \brief Get maximum bundle resource instances per node
2092 *
2093 * \param[in] rsc Bundle resource to check
2094 *
2095 * \return Maximum number of \p rsc instances that can be active on one node
2096 */
2097 unsigned int
2098 pe__bundle_max_per_node(const pcmk_resource_t *rsc)
2099 {
2100 pe__bundle_variant_data_t *bundle_data = NULL;
2101
2102 get_bundle_variant_data(bundle_data, rsc);
2103 pcmk__assert(bundle_data->nreplicas_per_host >= 0);
2104 return (unsigned int) bundle_data->nreplicas_per_host;
2105 }
2106