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