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