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