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