diff options
Diffstat (limited to '')
-rw-r--r-- | lib/pengine/bundle.c | 2004 |
1 files changed, 2004 insertions, 0 deletions
diff --git a/lib/pengine/bundle.c b/lib/pengine/bundle.c new file mode 100644 index 0000000..ff1b365 --- /dev/null +++ b/lib/pengine/bundle.c @@ -0,0 +1,2004 @@ +/* + * Copyright 2004-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <ctype.h> +#include <stdint.h> + +#include <crm/pengine/rules.h> +#include <crm/pengine/status.h> +#include <crm/pengine/internal.h> +#include <crm/msg_xml.h> +#include <crm/common/output.h> +#include <crm/common/xml_internal.h> +#include <pe_status_private.h> + +#define PE__VARIANT_BUNDLE 1 +#include "./variant.h" + +/*! + * \internal + * \brief Get maximum number of bundle replicas allowed to run + * + * \param[in] rsc Bundle or bundled resource to check + * + * \return Maximum replicas for bundle corresponding to \p rsc + */ +int +pe__bundle_max(const pe_resource_t *rsc) +{ + const pe__bundle_variant_data_t *bundle_data = NULL; + + get_bundle_variant_data(bundle_data, pe__const_top_resource(rsc, true)); + return bundle_data->nreplicas; +} + +/*! + * \internal + * \brief Get maximum number of bundle replicas allowed to run on one node + * + * \param[in] rsc Bundle or bundled resource to check + * + * \return Maximum replicas per node for bundle corresponding to \p rsc + */ +int +pe__bundle_max_per_node(const pe_resource_t *rsc) +{ + const pe__bundle_variant_data_t *bundle_data = NULL; + + get_bundle_variant_data(bundle_data, pe__const_top_resource(rsc, true)); + return bundle_data->nreplicas_per_host; +} + +static char * +next_ip(const char *last_ip) +{ + unsigned int oct1 = 0; + unsigned int oct2 = 0; + unsigned int oct3 = 0; + unsigned int oct4 = 0; + int rc = sscanf(last_ip, "%u.%u.%u.%u", &oct1, &oct2, &oct3, &oct4); + + if (rc != 4) { + /*@ TODO check for IPv6 */ + return NULL; + + } else if (oct3 > 253) { + return NULL; + + } else if (oct4 > 253) { + ++oct3; + oct4 = 1; + + } else { + ++oct4; + } + + return crm_strdup_printf("%u.%u.%u.%u", oct1, oct2, oct3, oct4); +} + +static void +allocate_ip(pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, + GString *buffer) +{ + if(data->ip_range_start == NULL) { + return; + + } else if(data->ip_last) { + replica->ipaddr = next_ip(data->ip_last); + + } else { + replica->ipaddr = strdup(data->ip_range_start); + } + + data->ip_last = replica->ipaddr; + switch (data->agent_type) { + case PE__CONTAINER_AGENT_DOCKER: + case PE__CONTAINER_AGENT_PODMAN: + if (data->add_host) { + g_string_append_printf(buffer, " --add-host=%s-%d:%s", + data->prefix, replica->offset, + replica->ipaddr); + } else { + g_string_append_printf(buffer, " --hosts-entry=%s=%s-%d", + replica->ipaddr, data->prefix, + replica->offset); + } + break; + + case PE__CONTAINER_AGENT_RKT: + g_string_append_printf(buffer, " --hosts-entry=%s=%s-%d", + replica->ipaddr, data->prefix, + replica->offset); + break; + + default: // PE__CONTAINER_AGENT_UNKNOWN + break; + } +} + +static xmlNode * +create_resource(const char *name, const char *provider, const char *kind) +{ + xmlNode *rsc = create_xml_node(NULL, XML_CIB_TAG_RESOURCE); + + crm_xml_add(rsc, XML_ATTR_ID, name); + crm_xml_add(rsc, XML_AGENT_ATTR_CLASS, PCMK_RESOURCE_CLASS_OCF); + crm_xml_add(rsc, XML_AGENT_ATTR_PROVIDER, provider); + crm_xml_add(rsc, XML_ATTR_TYPE, kind); + + return rsc; +} + +/*! + * \internal + * \brief Check whether cluster can manage resource inside container + * + * \param[in,out] data Container variant data + * + * \return TRUE if networking configuration is acceptable, FALSE otherwise + * + * \note The resource is manageable if an IP range or control port has been + * specified. If a control port is used without an IP range, replicas per + * host must be 1. + */ +static bool +valid_network(pe__bundle_variant_data_t *data) +{ + if(data->ip_range_start) { + return TRUE; + } + if(data->control_port) { + if(data->nreplicas_per_host > 1) { + pe_err("Specifying the 'control-port' for %s requires 'replicas-per-host=1'", data->prefix); + data->nreplicas_per_host = 1; + // @TODO to be sure: pe__clear_resource_flags(rsc, pe_rsc_unique); + } + return TRUE; + } + return FALSE; +} + +static int +create_ip_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, + pe__bundle_replica_t *replica) +{ + if(data->ip_range_start) { + char *id = NULL; + xmlNode *xml_ip = NULL; + xmlNode *xml_obj = NULL; + + id = crm_strdup_printf("%s-ip-%s", data->prefix, replica->ipaddr); + crm_xml_sanitize_id(id); + xml_ip = create_resource(id, "heartbeat", "IPaddr2"); + free(id); + + xml_obj = create_xml_node(xml_ip, XML_TAG_ATTR_SETS); + crm_xml_set_id(xml_obj, "%s-attributes-%d", + data->prefix, replica->offset); + + crm_create_nvpair_xml(xml_obj, NULL, "ip", replica->ipaddr); + if(data->host_network) { + crm_create_nvpair_xml(xml_obj, NULL, "nic", data->host_network); + } + + if(data->host_netmask) { + crm_create_nvpair_xml(xml_obj, NULL, + "cidr_netmask", data->host_netmask); + + } else { + crm_create_nvpair_xml(xml_obj, NULL, "cidr_netmask", "32"); + } + + xml_obj = create_xml_node(xml_ip, "operations"); + crm_create_op_xml(xml_obj, ID(xml_ip), "monitor", "60s", NULL); + + // TODO: Other ops? Timeouts and intervals from underlying resource? + + if (pe__unpack_resource(xml_ip, &replica->ip, parent, + parent->cluster) != pcmk_rc_ok) { + return pcmk_rc_unpack_error; + } + + parent->children = g_list_append(parent->children, replica->ip); + } + return pcmk_rc_ok; +} + +static const char* +container_agent_str(enum pe__container_agent t) +{ + switch (t) { + case PE__CONTAINER_AGENT_DOCKER: return PE__CONTAINER_AGENT_DOCKER_S; + case PE__CONTAINER_AGENT_RKT: return PE__CONTAINER_AGENT_RKT_S; + case PE__CONTAINER_AGENT_PODMAN: return PE__CONTAINER_AGENT_PODMAN_S; + default: // PE__CONTAINER_AGENT_UNKNOWN + break; + } + return PE__CONTAINER_AGENT_UNKNOWN_S; +} + +static int +create_container_resource(pe_resource_t *parent, + const pe__bundle_variant_data_t *data, + pe__bundle_replica_t *replica) +{ + char *id = NULL; + xmlNode *xml_container = NULL; + xmlNode *xml_obj = NULL; + + // Agent-specific + const char *hostname_opt = NULL; + const char *env_opt = NULL; + const char *agent_str = NULL; + int volid = 0; // rkt-only + + GString *buffer = NULL; + GString *dbuffer = NULL; + + // Where syntax differences are drop-in replacements, set them now + switch (data->agent_type) { + case PE__CONTAINER_AGENT_DOCKER: + case PE__CONTAINER_AGENT_PODMAN: + hostname_opt = "-h "; + env_opt = "-e "; + break; + case PE__CONTAINER_AGENT_RKT: + hostname_opt = "--hostname="; + env_opt = "--environment="; + break; + default: // PE__CONTAINER_AGENT_UNKNOWN + return pcmk_rc_unpack_error; + } + agent_str = container_agent_str(data->agent_type); + + buffer = g_string_sized_new(4096); + + id = crm_strdup_printf("%s-%s-%d", data->prefix, agent_str, + replica->offset); + crm_xml_sanitize_id(id); + xml_container = create_resource(id, "heartbeat", agent_str); + free(id); + + xml_obj = create_xml_node(xml_container, XML_TAG_ATTR_SETS); + crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, replica->offset); + + crm_create_nvpair_xml(xml_obj, NULL, "image", data->image); + crm_create_nvpair_xml(xml_obj, NULL, "allow_pull", XML_BOOLEAN_TRUE); + crm_create_nvpair_xml(xml_obj, NULL, "force_kill", XML_BOOLEAN_FALSE); + crm_create_nvpair_xml(xml_obj, NULL, "reuse", XML_BOOLEAN_FALSE); + + if (data->agent_type == PE__CONTAINER_AGENT_DOCKER) { + g_string_append(buffer, " --restart=no"); + } + + /* Set a container hostname only if we have an IP to map it to. The user can + * set -h or --uts=host themselves if they want a nicer name for logs, but + * this makes applications happy who need their hostname to match the IP + * they bind to. + */ + if (data->ip_range_start != NULL) { + g_string_append_printf(buffer, " %s%s-%d", hostname_opt, data->prefix, + replica->offset); + } + pcmk__g_strcat(buffer, " ", env_opt, "PCMK_stderr=1", NULL); + + if (data->container_network != NULL) { + pcmk__g_strcat(buffer, " --net=", data->container_network, NULL); + } + + if (data->control_port != NULL) { + pcmk__g_strcat(buffer, " ", env_opt, "PCMK_remote_port=", + data->control_port, NULL); + } else { + g_string_append_printf(buffer, " %sPCMK_remote_port=%d", env_opt, + DEFAULT_REMOTE_PORT); + } + + for (GList *iter = data->mounts; iter != NULL; iter = iter->next) { + pe__bundle_mount_t *mount = (pe__bundle_mount_t *) iter->data; + char *source = NULL; + + if (pcmk_is_set(mount->flags, pe__bundle_mount_subdir)) { + source = crm_strdup_printf("%s/%s-%d", mount->source, data->prefix, + replica->offset); + pcmk__add_separated_word(&dbuffer, 1024, source, ","); + } + + switch (data->agent_type) { + case PE__CONTAINER_AGENT_DOCKER: + case PE__CONTAINER_AGENT_PODMAN: + pcmk__g_strcat(buffer, + " -v ", pcmk__s(source, mount->source), + ":", mount->target, NULL); + + if (mount->options != NULL) { + pcmk__g_strcat(buffer, ":", mount->options, NULL); + } + break; + case PE__CONTAINER_AGENT_RKT: + g_string_append_printf(buffer, + " --volume vol%d,kind=host," + "source=%s%s%s " + "--mount volume=vol%d,target=%s", + volid, pcmk__s(source, mount->source), + (mount->options != NULL)? "," : "", + pcmk__s(mount->options, ""), + volid, mount->target); + volid++; + break; + default: + break; + } + free(source); + } + + for (GList *iter = data->ports; iter != NULL; iter = iter->next) { + pe__bundle_port_t *port = (pe__bundle_port_t *) iter->data; + + switch (data->agent_type) { + case PE__CONTAINER_AGENT_DOCKER: + case PE__CONTAINER_AGENT_PODMAN: + if (replica->ipaddr != NULL) { + pcmk__g_strcat(buffer, + " -p ", replica->ipaddr, ":", port->source, + ":", port->target, NULL); + + } else if (!pcmk__str_eq(data->container_network, "host", + pcmk__str_none)) { + // No need to do port mapping if net == host + pcmk__g_strcat(buffer, + " -p ", port->source, ":", port->target, + NULL); + } + break; + case PE__CONTAINER_AGENT_RKT: + if (replica->ipaddr != NULL) { + pcmk__g_strcat(buffer, + " --port=", port->target, + ":", replica->ipaddr, ":", port->source, + NULL); + } else { + pcmk__g_strcat(buffer, + " --port=", port->target, ":", port->source, + NULL); + } + break; + default: + break; + } + } + + /* @COMPAT: We should use pcmk__add_word() here, but we can't yet, because + * it would cause restarts during rolling upgrades. + * + * In a previous version of the container resource creation logic, if + * data->launcher_options is not NULL, we append + * (" %s", data->launcher_options) even if data->launcher_options is an + * empty string. Likewise for data->container_host_options. Using + * + * pcmk__add_word(buffer, 0, data->launcher_options) + * + * removes that extra trailing space, causing a resource definition change. + */ + if (data->launcher_options != NULL) { + pcmk__g_strcat(buffer, " ", data->launcher_options, NULL); + } + + if (data->container_host_options != NULL) { + pcmk__g_strcat(buffer, " ", data->container_host_options, NULL); + } + + crm_create_nvpair_xml(xml_obj, NULL, "run_opts", + (const char *) buffer->str); + g_string_free(buffer, TRUE); + + crm_create_nvpair_xml(xml_obj, NULL, "mount_points", + (dbuffer != NULL)? (const char *) dbuffer->str : ""); + if (dbuffer != NULL) { + g_string_free(dbuffer, TRUE); + } + + if (replica->child != NULL) { + if (data->container_command != NULL) { + crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", + data->container_command); + } else { + crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", + SBIN_DIR "/pacemaker-remoted"); + } + + /* TODO: Allow users to specify their own? + * + * We just want to know if the container is alive; we'll monitor the + * child independently. + */ + crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true"); +#if 0 + /* @TODO Consider supporting the use case where we can start and stop + * resources, but not proxy local commands (such as setting node + * attributes), by running the local executor in stand-alone mode. + * However, this would probably be better done via ACLs as with other + * Pacemaker Remote nodes. + */ + } else if ((child != NULL) && data->untrusted) { + crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", + CRM_DAEMON_DIR "/pacemaker-execd"); + crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", + CRM_DAEMON_DIR "/pacemaker/cts-exec-helper -c poke"); +#endif + } else { + if (data->container_command != NULL) { + crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", + data->container_command); + } + + /* TODO: Allow users to specify their own? + * + * We don't know what's in the container, so we just want to know if it + * is alive. + */ + crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true"); + } + + xml_obj = create_xml_node(xml_container, "operations"); + crm_create_op_xml(xml_obj, ID(xml_container), "monitor", "60s", NULL); + + // TODO: Other ops? Timeouts and intervals from underlying resource? + if (pe__unpack_resource(xml_container, &replica->container, parent, + parent->cluster) != pcmk_rc_ok) { + return pcmk_rc_unpack_error; + } + pe__set_resource_flags(replica->container, pe_rsc_replica_container); + parent->children = g_list_append(parent->children, replica->container); + + return pcmk_rc_ok; +} + +/*! + * \brief Ban a node from a resource's (and its children's) allowed nodes list + * + * \param[in,out] rsc Resource to modify + * \param[in] uname Name of node to ban + */ +static void +disallow_node(pe_resource_t *rsc, const char *uname) +{ + gpointer match = g_hash_table_lookup(rsc->allowed_nodes, uname); + + if (match) { + ((pe_node_t *) match)->weight = -INFINITY; + ((pe_node_t *) match)->rsc_discover_mode = pe_discover_never; + } + if (rsc->children) { + g_list_foreach(rsc->children, (GFunc) disallow_node, (gpointer) uname); + } +} + +static int +create_remote_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, + pe__bundle_replica_t *replica) +{ + if (replica->child && valid_network(data)) { + GHashTableIter gIter; + pe_node_t *node = NULL; + xmlNode *xml_remote = NULL; + char *id = crm_strdup_printf("%s-%d", data->prefix, replica->offset); + char *port_s = NULL; + const char *uname = NULL; + const char *connect_name = NULL; + + if (pe_find_resource(parent->cluster->resources, id) != NULL) { + free(id); + // The biggest hammer we have + id = crm_strdup_printf("pcmk-internal-%s-remote-%d", + replica->child->id, replica->offset); + //@TODO return error instead of asserting? + CRM_ASSERT(pe_find_resource(parent->cluster->resources, + id) == NULL); + } + + /* REMOTE_CONTAINER_HACK: Using "#uname" as the server name when the + * connection does not have its own IP is a magic string that we use to + * support nested remotes (i.e. a bundle running on a remote node). + */ + connect_name = (replica->ipaddr? replica->ipaddr : "#uname"); + + if (data->control_port == NULL) { + port_s = pcmk__itoa(DEFAULT_REMOTE_PORT); + } + + /* This sets replica->container as replica->remote's container, which is + * similar to what happens with guest nodes. This is how the scheduler + * knows that the bundle node is fenced by recovering the container, and + * that remote should be ordered relative to the container. + */ + xml_remote = pe_create_remote_xml(NULL, id, replica->container->id, + NULL, NULL, NULL, + connect_name, (data->control_port? + data->control_port : port_s)); + free(port_s); + + /* Abandon our created ID, and pull the copy from the XML, because we + * need something that will get freed during data set cleanup to use as + * the node ID and uname. + */ + free(id); + id = NULL; + uname = ID(xml_remote); + + /* Ensure a node has been created for the guest (it may have already + * been, if it has a permanent node attribute), and ensure its weight is + * -INFINITY so no other resources can run on it. + */ + node = pe_find_node(parent->cluster->nodes, uname); + if (node == NULL) { + node = pe_create_node(uname, uname, "remote", "-INFINITY", + parent->cluster); + } else { + node->weight = -INFINITY; + } + node->rsc_discover_mode = pe_discover_never; + + /* unpack_remote_nodes() ensures that each remote node and guest node + * has a pe_node_t entry. Ideally, it would do the same for bundle nodes. + * Unfortunately, a bundle has to be mostly unpacked before it's obvious + * what nodes will be needed, so we do it just above. + * + * Worse, that means that the node may have been utilized while + * unpacking other resources, without our weight correction. The most + * likely place for this to happen is when pe__unpack_resource() calls + * resource_location() to set a default score in symmetric clusters. + * This adds a node *copy* to each resource's allowed nodes, and these + * copies will have the wrong weight. + * + * As a hacky workaround, fix those copies here. + * + * @TODO Possible alternative: ensure bundles are unpacked before other + * resources, so the weight is correct before any copies are made. + */ + g_list_foreach(parent->cluster->resources, (GFunc) disallow_node, + (gpointer) uname); + + replica->node = pe__copy_node(node); + replica->node->weight = 500; + replica->node->rsc_discover_mode = pe_discover_exclusive; + + /* Ensure the node shows up as allowed and with the correct discovery set */ + if (replica->child->allowed_nodes != NULL) { + g_hash_table_destroy(replica->child->allowed_nodes); + } + replica->child->allowed_nodes = pcmk__strkey_table(NULL, free); + g_hash_table_insert(replica->child->allowed_nodes, + (gpointer) replica->node->details->id, + pe__copy_node(replica->node)); + + { + pe_node_t *copy = pe__copy_node(replica->node); + copy->weight = -INFINITY; + g_hash_table_insert(replica->child->parent->allowed_nodes, + (gpointer) replica->node->details->id, copy); + } + if (pe__unpack_resource(xml_remote, &replica->remote, parent, + parent->cluster) != pcmk_rc_ok) { + return pcmk_rc_unpack_error; + } + + g_hash_table_iter_init(&gIter, replica->remote->allowed_nodes); + while (g_hash_table_iter_next(&gIter, NULL, (void **)&node)) { + if (pe__is_guest_or_remote_node(node)) { + /* Remote resources can only run on 'normal' cluster node */ + node->weight = -INFINITY; + } + } + + replica->node->details->remote_rsc = replica->remote; + + // Ensure pe__is_guest_node() functions correctly immediately + replica->remote->container = replica->container; + + /* A bundle's #kind is closer to "container" (guest node) than the + * "remote" set by pe_create_node(). + */ + g_hash_table_insert(replica->node->details->attrs, + strdup(CRM_ATTR_KIND), strdup("container")); + + /* One effect of this is that setup_container() will add + * replica->remote to replica->container's fillers, which will make + * pe__resource_contains_guest_node() true for replica->container. + * + * replica->child does NOT get added to replica->container's fillers. + * The only noticeable effect if it did would be for its fail count to + * be taken into account when checking replica->container's migration + * threshold. + */ + parent->children = g_list_append(parent->children, replica->remote); + } + return pcmk_rc_ok; +} + +static int +create_replica_resources(pe_resource_t *parent, pe__bundle_variant_data_t *data, + pe__bundle_replica_t *replica) +{ + int rc = pcmk_rc_ok; + + rc = create_container_resource(parent, data, replica); + if (rc != pcmk_rc_ok) { + return rc; + } + + rc = create_ip_resource(parent, data, replica); + if (rc != pcmk_rc_ok) { + return rc; + } + + rc = create_remote_resource(parent, data, replica); + if (rc != pcmk_rc_ok) { + return rc; + } + + if ((replica->child != NULL) && (replica->ipaddr != NULL)) { + add_hash_param(replica->child->meta, "external-ip", replica->ipaddr); + } + + if (replica->remote != NULL) { + /* + * Allow the remote connection resource to be allocated to a + * different node than the one on which the container is active. + * + * This makes it possible to have Pacemaker Remote nodes running + * containers with pacemaker-remoted inside in order to start + * services inside those containers. + */ + pe__set_resource_flags(replica->remote, pe_rsc_allow_remote_remotes); + } + return rc; +} + +static void +mount_add(pe__bundle_variant_data_t *bundle_data, const char *source, + const char *target, const char *options, uint32_t flags) +{ + pe__bundle_mount_t *mount = calloc(1, sizeof(pe__bundle_mount_t)); + + CRM_ASSERT(mount != NULL); + mount->source = strdup(source); + mount->target = strdup(target); + pcmk__str_update(&mount->options, options); + mount->flags = flags; + bundle_data->mounts = g_list_append(bundle_data->mounts, mount); +} + +static void +mount_free(pe__bundle_mount_t *mount) +{ + free(mount->source); + free(mount->target); + free(mount->options); + free(mount); +} + +static void +port_free(pe__bundle_port_t *port) +{ + free(port->source); + free(port->target); + free(port); +} + +static pe__bundle_replica_t * +replica_for_remote(pe_resource_t *remote) +{ + pe_resource_t *top = remote; + pe__bundle_variant_data_t *bundle_data = NULL; + + if (top == NULL) { + return NULL; + } + + while (top->parent != NULL) { + top = top->parent; + } + + get_bundle_variant_data(bundle_data, top); + for (GList *gIter = bundle_data->replicas; gIter != NULL; + gIter = gIter->next) { + pe__bundle_replica_t *replica = gIter->data; + + if (replica->remote == remote) { + return replica; + } + } + CRM_LOG_ASSERT(FALSE); + return NULL; +} + +bool +pe__bundle_needs_remote_name(pe_resource_t *rsc) +{ + const char *value; + GHashTable *params = NULL; + + if (rsc == NULL) { + return false; + } + + // Use NULL node since pcmk__bundle_expand() uses that to set value + params = pe_rsc_params(rsc, NULL, rsc->cluster); + value = g_hash_table_lookup(params, XML_RSC_ATTR_REMOTE_RA_ADDR); + + return pcmk__str_eq(value, "#uname", pcmk__str_casei) + && xml_contains_remote_node(rsc->xml); +} + +const char * +pe__add_bundle_remote_name(pe_resource_t *rsc, pe_working_set_t *data_set, + xmlNode *xml, const char *field) +{ + // REMOTE_CONTAINER_HACK: Allow remote nodes that start containers with pacemaker remote inside + + pe_node_t *node = NULL; + pe__bundle_replica_t *replica = NULL; + + if (!pe__bundle_needs_remote_name(rsc)) { + return NULL; + } + + replica = replica_for_remote(rsc); + if (replica == NULL) { + return NULL; + } + + node = replica->container->allocated_to; + if (node == NULL) { + /* If it won't be running anywhere after the + * transition, go with where it's running now. + */ + node = pe__current_node(replica->container); + } + + if(node == NULL) { + crm_trace("Cannot determine address for bundle connection %s", rsc->id); + return NULL; + } + + crm_trace("Setting address for bundle connection %s to bundle host %s", + rsc->id, pe__node_name(node)); + if(xml != NULL && field != NULL) { + crm_xml_add(xml, field, node->details->uname); + } + + return node->details->uname; +} + +#define pe__set_bundle_mount_flags(mount_xml, flags, flags_to_set) do { \ + flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \ + "Bundle mount", ID(mount_xml), flags, \ + (flags_to_set), #flags_to_set); \ + } while (0) + +gboolean +pe__unpack_bundle(pe_resource_t *rsc, pe_working_set_t *data_set) +{ + const char *value = NULL; + xmlNode *xml_obj = NULL; + xmlNode *xml_resource = NULL; + pe__bundle_variant_data_t *bundle_data = NULL; + bool need_log_mount = TRUE; + + CRM_ASSERT(rsc != NULL); + pe_rsc_trace(rsc, "Processing resource %s...", rsc->id); + + bundle_data = calloc(1, sizeof(pe__bundle_variant_data_t)); + rsc->variant_opaque = bundle_data; + bundle_data->prefix = strdup(rsc->id); + + xml_obj = first_named_child(rsc->xml, PE__CONTAINER_AGENT_DOCKER_S); + if (xml_obj != NULL) { + bundle_data->agent_type = PE__CONTAINER_AGENT_DOCKER; + } else { + xml_obj = first_named_child(rsc->xml, PE__CONTAINER_AGENT_RKT_S); + if (xml_obj != NULL) { + bundle_data->agent_type = PE__CONTAINER_AGENT_RKT; + } else { + xml_obj = first_named_child(rsc->xml, PE__CONTAINER_AGENT_PODMAN_S); + if (xml_obj != NULL) { + bundle_data->agent_type = PE__CONTAINER_AGENT_PODMAN; + } else { + return FALSE; + } + } + } + + // Use 0 for default, minimum, and invalid promoted-max + value = crm_element_value(xml_obj, XML_RSC_ATTR_PROMOTED_MAX); + if (value == NULL) { + // @COMPAT deprecated since 2.0.0 + value = crm_element_value(xml_obj, "masters"); + } + pcmk__scan_min_int(value, &bundle_data->promoted_max, 0); + + // Default replicas to promoted-max if it was specified and 1 otherwise + value = crm_element_value(xml_obj, "replicas"); + if ((value == NULL) && (bundle_data->promoted_max > 0)) { + bundle_data->nreplicas = bundle_data->promoted_max; + } else { + pcmk__scan_min_int(value, &bundle_data->nreplicas, 1); + } + + /* + * Communication between containers on the same host via the + * floating IPs only works if the container is started with: + * --userland-proxy=false --ip-masq=false + */ + value = crm_element_value(xml_obj, "replicas-per-host"); + pcmk__scan_min_int(value, &bundle_data->nreplicas_per_host, 1); + if (bundle_data->nreplicas_per_host == 1) { + pe__clear_resource_flags(rsc, pe_rsc_unique); + } + + bundle_data->container_command = crm_element_value_copy(xml_obj, "run-command"); + bundle_data->launcher_options = crm_element_value_copy(xml_obj, "options"); + bundle_data->image = crm_element_value_copy(xml_obj, "image"); + bundle_data->container_network = crm_element_value_copy(xml_obj, "network"); + + xml_obj = first_named_child(rsc->xml, "network"); + if(xml_obj) { + + bundle_data->ip_range_start = crm_element_value_copy(xml_obj, "ip-range-start"); + bundle_data->host_netmask = crm_element_value_copy(xml_obj, "host-netmask"); + bundle_data->host_network = crm_element_value_copy(xml_obj, "host-interface"); + bundle_data->control_port = crm_element_value_copy(xml_obj, "control-port"); + value = crm_element_value(xml_obj, "add-host"); + if (crm_str_to_boolean(value, &bundle_data->add_host) != 1) { + bundle_data->add_host = TRUE; + } + + for (xmlNode *xml_child = pcmk__xe_first_child(xml_obj); xml_child != NULL; + xml_child = pcmk__xe_next(xml_child)) { + + pe__bundle_port_t *port = calloc(1, sizeof(pe__bundle_port_t)); + port->source = crm_element_value_copy(xml_child, "port"); + + if(port->source == NULL) { + port->source = crm_element_value_copy(xml_child, "range"); + } else { + port->target = crm_element_value_copy(xml_child, "internal-port"); + } + + if(port->source != NULL && strlen(port->source) > 0) { + if(port->target == NULL) { + port->target = strdup(port->source); + } + bundle_data->ports = g_list_append(bundle_data->ports, port); + + } else { + pe_err("Invalid port directive %s", ID(xml_child)); + port_free(port); + } + } + } + + xml_obj = first_named_child(rsc->xml, "storage"); + for (xmlNode *xml_child = pcmk__xe_first_child(xml_obj); xml_child != NULL; + xml_child = pcmk__xe_next(xml_child)) { + + const char *source = crm_element_value(xml_child, "source-dir"); + const char *target = crm_element_value(xml_child, "target-dir"); + const char *options = crm_element_value(xml_child, "options"); + int flags = pe__bundle_mount_none; + + if (source == NULL) { + source = crm_element_value(xml_child, "source-dir-root"); + pe__set_bundle_mount_flags(xml_child, flags, + pe__bundle_mount_subdir); + } + + if (source && target) { + mount_add(bundle_data, source, target, options, flags); + if (strcmp(target, "/var/log") == 0) { + need_log_mount = FALSE; + } + } else { + pe_err("Invalid mount directive %s", ID(xml_child)); + } + } + + xml_obj = first_named_child(rsc->xml, "primitive"); + if (xml_obj && valid_network(bundle_data)) { + char *value = NULL; + xmlNode *xml_set = NULL; + + xml_resource = create_xml_node(NULL, XML_CIB_TAG_INCARNATION); + + /* @COMPAT We no longer use the <master> tag, but we need to keep it as + * part of the resource name, so that bundles don't restart in a rolling + * upgrade. (It also avoids needing to change regression tests.) + */ + crm_xml_set_id(xml_resource, "%s-%s", bundle_data->prefix, + (bundle_data->promoted_max? "master" + : (const char *)xml_resource->name)); + + xml_set = create_xml_node(xml_resource, XML_TAG_META_SETS); + crm_xml_set_id(xml_set, "%s-%s-meta", bundle_data->prefix, xml_resource->name); + + crm_create_nvpair_xml(xml_set, NULL, + XML_RSC_ATTR_ORDERED, XML_BOOLEAN_TRUE); + + value = pcmk__itoa(bundle_data->nreplicas); + crm_create_nvpair_xml(xml_set, NULL, + XML_RSC_ATTR_INCARNATION_MAX, value); + free(value); + + value = pcmk__itoa(bundle_data->nreplicas_per_host); + crm_create_nvpair_xml(xml_set, NULL, + XML_RSC_ATTR_INCARNATION_NODEMAX, value); + free(value); + + crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_UNIQUE, + pcmk__btoa(bundle_data->nreplicas_per_host > 1)); + + if (bundle_data->promoted_max) { + crm_create_nvpair_xml(xml_set, NULL, + XML_RSC_ATTR_PROMOTABLE, XML_BOOLEAN_TRUE); + + value = pcmk__itoa(bundle_data->promoted_max); + crm_create_nvpair_xml(xml_set, NULL, + XML_RSC_ATTR_PROMOTED_MAX, value); + free(value); + } + + //crm_xml_add(xml_obj, XML_ATTR_ID, bundle_data->prefix); + add_node_copy(xml_resource, xml_obj); + + } else if(xml_obj) { + pe_err("Cannot control %s inside %s without either ip-range-start or control-port", + rsc->id, ID(xml_obj)); + return FALSE; + } + + if(xml_resource) { + int lpc = 0; + GList *childIter = NULL; + pe__bundle_port_t *port = NULL; + GString *buffer = NULL; + + if (pe__unpack_resource(xml_resource, &(bundle_data->child), rsc, + data_set) != pcmk_rc_ok) { + return FALSE; + } + + /* Currently, we always map the default authentication key location + * into the same location inside the container. + * + * Ideally, we would respect the host's PCMK_authkey_location, but: + * - it may be different on different nodes; + * - the actual connection will do extra checking to make sure the key + * file exists and is readable, that we can't do here on the DC + * - tools such as crm_resource and crm_simulate may not have the same + * environment variables as the cluster, causing operation digests to + * differ + * + * Always using the default location inside the container is fine, + * because we control the pacemaker_remote environment, and it avoids + * having to pass another environment variable to the container. + * + * @TODO A better solution may be to have only pacemaker_remote use the + * environment variable, and have the cluster nodes use a new + * cluster option for key location. This would introduce the limitation + * of the location being the same on all cluster nodes, but that's + * reasonable. + */ + mount_add(bundle_data, DEFAULT_REMOTE_KEY_LOCATION, + DEFAULT_REMOTE_KEY_LOCATION, NULL, pe__bundle_mount_none); + + if (need_log_mount) { + mount_add(bundle_data, CRM_BUNDLE_DIR, "/var/log", NULL, + pe__bundle_mount_subdir); + } + + port = calloc(1, sizeof(pe__bundle_port_t)); + if(bundle_data->control_port) { + port->source = strdup(bundle_data->control_port); + } else { + /* If we wanted to respect PCMK_remote_port, we could use + * crm_default_remote_port() here and elsewhere in this file instead + * of DEFAULT_REMOTE_PORT. + * + * However, it gains nothing, since we control both the container + * environment and the connection resource parameters, and the user + * can use a different port if desired by setting control-port. + */ + port->source = pcmk__itoa(DEFAULT_REMOTE_PORT); + } + port->target = strdup(port->source); + bundle_data->ports = g_list_append(bundle_data->ports, port); + + buffer = g_string_sized_new(1024); + for (childIter = bundle_data->child->children; childIter != NULL; + childIter = childIter->next) { + + pe__bundle_replica_t *replica = calloc(1, sizeof(pe__bundle_replica_t)); + + replica->child = childIter->data; + replica->child->exclusive_discover = TRUE; + replica->offset = lpc++; + + // Ensure the child's notify gets set based on the underlying primitive's value + if (pcmk_is_set(replica->child->flags, pe_rsc_notify)) { + pe__set_resource_flags(bundle_data->child, pe_rsc_notify); + } + + allocate_ip(bundle_data, replica, buffer); + bundle_data->replicas = g_list_append(bundle_data->replicas, + replica); + bundle_data->attribute_target = g_hash_table_lookup(replica->child->meta, + XML_RSC_ATTR_TARGET); + } + bundle_data->container_host_options = g_string_free(buffer, FALSE); + + if (bundle_data->attribute_target) { + g_hash_table_replace(rsc->meta, strdup(XML_RSC_ATTR_TARGET), + strdup(bundle_data->attribute_target)); + g_hash_table_replace(bundle_data->child->meta, + strdup(XML_RSC_ATTR_TARGET), + strdup(bundle_data->attribute_target)); + } + + } else { + // Just a naked container, no pacemaker-remote + GString *buffer = g_string_sized_new(1024); + + for (int lpc = 0; lpc < bundle_data->nreplicas; lpc++) { + pe__bundle_replica_t *replica = calloc(1, sizeof(pe__bundle_replica_t)); + + replica->offset = lpc; + allocate_ip(bundle_data, replica, buffer); + bundle_data->replicas = g_list_append(bundle_data->replicas, + replica); + } + bundle_data->container_host_options = g_string_free(buffer, FALSE); + } + + for (GList *gIter = bundle_data->replicas; gIter != NULL; + gIter = gIter->next) { + pe__bundle_replica_t *replica = gIter->data; + + if (create_replica_resources(rsc, bundle_data, replica) != pcmk_rc_ok) { + pe_err("Failed unpacking resource %s", rsc->id); + rsc->fns->free(rsc); + return FALSE; + } + + /* Utilization needs special handling for bundles. It makes no sense for + * the inner primitive to have utilization, because it is tied + * one-to-one to the guest node created by the container resource -- and + * there's no way to set capacities for that guest node anyway. + * + * What the user really wants is to configure utilization for the + * container. However, the schema only allows utilization for + * primitives, and the container resource is implicit anyway, so the + * user can *only* configure utilization for the inner primitive. If + * they do, move the primitive's utilization values to the container. + * + * @TODO This means that bundles without an inner primitive can't have + * utilization. An alternative might be to allow utilization values in + * the top-level bundle XML in the schema, and copy those to each + * container. + */ + if (replica->child != NULL) { + GHashTable *empty = replica->container->utilization; + + replica->container->utilization = replica->child->utilization; + replica->child->utilization = empty; + } + } + + if (bundle_data->child) { + rsc->children = g_list_append(rsc->children, bundle_data->child); + } + return TRUE; +} + +static int +replica_resource_active(pe_resource_t *rsc, gboolean all) +{ + if (rsc) { + gboolean child_active = rsc->fns->active(rsc, all); + + if (child_active && !all) { + return TRUE; + } else if (!child_active && all) { + return FALSE; + } + } + return -1; +} + +gboolean +pe__bundle_active(pe_resource_t *rsc, gboolean all) +{ + pe__bundle_variant_data_t *bundle_data = NULL; + GList *iter = NULL; + + get_bundle_variant_data(bundle_data, rsc); + for (iter = bundle_data->replicas; iter != NULL; iter = iter->next) { + pe__bundle_replica_t *replica = iter->data; + int rsc_active; + + rsc_active = replica_resource_active(replica->ip, all); + if (rsc_active >= 0) { + return (gboolean) rsc_active; + } + + rsc_active = replica_resource_active(replica->child, all); + if (rsc_active >= 0) { + return (gboolean) rsc_active; + } + + rsc_active = replica_resource_active(replica->container, all); + if (rsc_active >= 0) { + return (gboolean) rsc_active; + } + + rsc_active = replica_resource_active(replica->remote, all); + if (rsc_active >= 0) { + return (gboolean) rsc_active; + } + } + + /* If "all" is TRUE, we've already checked that no resources were inactive, + * so return TRUE; if "all" is FALSE, we didn't find any active resources, + * so return FALSE. + */ + return all; +} + +/*! + * \internal + * \brief Find the bundle replica corresponding to a given node + * + * \param[in] bundle Top-level bundle resource + * \param[in] node Node to search for + * + * \return Bundle replica if found, NULL otherwise + */ +pe_resource_t * +pe__find_bundle_replica(const pe_resource_t *bundle, const pe_node_t *node) +{ + pe__bundle_variant_data_t *bundle_data = NULL; + CRM_ASSERT(bundle && node); + + get_bundle_variant_data(bundle_data, bundle); + for (GList *gIter = bundle_data->replicas; gIter != NULL; + gIter = gIter->next) { + pe__bundle_replica_t *replica = gIter->data; + + CRM_ASSERT(replica && replica->node); + if (replica->node->details == node->details) { + return replica->child; + } + } + return NULL; +} + +/*! + * \internal + * \deprecated This function will be removed in a future release + */ +static void +print_rsc_in_list(pe_resource_t *rsc, const char *pre_text, long options, + void *print_data) +{ + if (rsc != NULL) { + if (options & pe_print_html) { + status_print("<li>"); + } + rsc->fns->print(rsc, pre_text, options, print_data); + if (options & pe_print_html) { + status_print("</li>\n"); + } + } +} + +/*! + * \internal + * \deprecated This function will be removed in a future release + */ +static void +bundle_print_xml(pe_resource_t *rsc, const char *pre_text, long options, + void *print_data) +{ + pe__bundle_variant_data_t *bundle_data = NULL; + char *child_text = NULL; + CRM_CHECK(rsc != NULL, return); + + if (pre_text == NULL) { + pre_text = ""; + } + child_text = crm_strdup_printf("%s ", pre_text); + + get_bundle_variant_data(bundle_data, rsc); + + status_print("%s<bundle ", pre_text); + status_print(XML_ATTR_ID "=\"%s\" ", rsc->id); + status_print("type=\"%s\" ", container_agent_str(bundle_data->agent_type)); + status_print("image=\"%s\" ", bundle_data->image); + status_print("unique=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_unique)); + status_print("managed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_managed)); + status_print("failed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_failed)); + status_print(">\n"); + + for (GList *gIter = bundle_data->replicas; gIter != NULL; + gIter = gIter->next) { + pe__bundle_replica_t *replica = gIter->data; + + CRM_ASSERT(replica); + status_print("%s <replica " XML_ATTR_ID "=\"%d\">\n", + pre_text, replica->offset); + print_rsc_in_list(replica->ip, child_text, options, print_data); + print_rsc_in_list(replica->child, child_text, options, print_data); + print_rsc_in_list(replica->container, child_text, options, print_data); + print_rsc_in_list(replica->remote, child_text, options, print_data); + status_print("%s </replica>\n", pre_text); + } + status_print("%s</bundle>\n", pre_text); + free(child_text); +} + +PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pe_resource_t *", "GList *", "GList *") +int +pe__bundle_xml(pcmk__output_t *out, va_list args) +{ + uint32_t show_opts = va_arg(args, uint32_t); + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + + pe__bundle_variant_data_t *bundle_data = NULL; + int rc = pcmk_rc_no_output; + gboolean printed_header = FALSE; + gboolean print_everything = TRUE; + + const char *desc = NULL; + + CRM_ASSERT(rsc != NULL); + + get_bundle_variant_data(bundle_data, rsc); + + if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { + return rc; + } + + print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches); + + for (GList *gIter = bundle_data->replicas; gIter != NULL; + gIter = gIter->next) { + pe__bundle_replica_t *replica = gIter->data; + char *id = NULL; + gboolean print_ip, print_child, print_ctnr, print_remote; + + CRM_ASSERT(replica); + + if (pcmk__rsc_filtered_by_node(replica->container, only_node)) { + continue; + } + + print_ip = replica->ip != NULL && + !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything); + print_child = replica->child != NULL && + !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything); + print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything); + print_remote = replica->remote != NULL && + !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything); + + if (!print_everything && !print_ip && !print_child && !print_ctnr && !print_remote) { + continue; + } + + if (!printed_header) { + printed_header = TRUE; + + desc = pe__resource_description(rsc, show_opts); + + rc = pe__name_and_nvpairs_xml(out, true, "bundle", 8, + "id", rsc->id, + "type", container_agent_str(bundle_data->agent_type), + "image", bundle_data->image, + "unique", pe__rsc_bool_str(rsc, pe_rsc_unique), + "maintenance", pe__rsc_bool_str(rsc, pe_rsc_maintenance), + "managed", pe__rsc_bool_str(rsc, pe_rsc_managed), + "failed", pe__rsc_bool_str(rsc, pe_rsc_failed), + "description", desc); + CRM_ASSERT(rc == pcmk_rc_ok); + } + + id = pcmk__itoa(replica->offset); + rc = pe__name_and_nvpairs_xml(out, true, "replica", 1, "id", id); + free(id); + CRM_ASSERT(rc == pcmk_rc_ok); + + if (print_ip) { + out->message(out, crm_map_element_name(replica->ip->xml), show_opts, + replica->ip, only_node, only_rsc); + } + + if (print_child) { + out->message(out, crm_map_element_name(replica->child->xml), show_opts, + replica->child, only_node, only_rsc); + } + + if (print_ctnr) { + out->message(out, crm_map_element_name(replica->container->xml), show_opts, + replica->container, only_node, only_rsc); + } + + if (print_remote) { + out->message(out, crm_map_element_name(replica->remote->xml), show_opts, + replica->remote, only_node, only_rsc); + } + + pcmk__output_xml_pop_parent(out); // replica + } + + if (printed_header) { + pcmk__output_xml_pop_parent(out); // bundle + } + + return rc; +} + +static void +pe__bundle_replica_output_html(pcmk__output_t *out, pe__bundle_replica_t *replica, + pe_node_t *node, uint32_t show_opts) +{ + pe_resource_t *rsc = replica->child; + + int offset = 0; + char buffer[LINE_MAX]; + + if(rsc == NULL) { + rsc = replica->container; + } + + if (replica->remote) { + offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", + rsc_printable_id(replica->remote)); + } else { + offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", + rsc_printable_id(replica->container)); + } + if (replica->ipaddr) { + offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", + replica->ipaddr); + } + + pe__common_output_html(out, rsc, buffer, node, show_opts); +} + +/*! + * \internal + * \brief Get a string describing a resource's unmanaged state or lack thereof + * + * \param[in] rsc Resource to describe + * + * \return A string indicating that a resource is in maintenance mode or + * otherwise unmanaged, or an empty string otherwise + */ +static const char * +get_unmanaged_str(const pe_resource_t *rsc) +{ + if (pcmk_is_set(rsc->flags, pe_rsc_maintenance)) { + return " (maintenance)"; + } + if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { + return " (unmanaged)"; + } + return ""; +} + +PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pe_resource_t *", "GList *", "GList *") +int +pe__bundle_html(pcmk__output_t *out, va_list args) +{ + uint32_t show_opts = va_arg(args, uint32_t); + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + + const char *desc = NULL; + pe__bundle_variant_data_t *bundle_data = NULL; + int rc = pcmk_rc_no_output; + gboolean print_everything = TRUE; + + CRM_ASSERT(rsc != NULL); + + get_bundle_variant_data(bundle_data, rsc); + + desc = pe__resource_description(rsc, show_opts); + + if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { + return rc; + } + + print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches); + + for (GList *gIter = bundle_data->replicas; gIter != NULL; + gIter = gIter->next) { + pe__bundle_replica_t *replica = gIter->data; + gboolean print_ip, print_child, print_ctnr, print_remote; + + CRM_ASSERT(replica); + + if (pcmk__rsc_filtered_by_node(replica->container, only_node)) { + continue; + } + + print_ip = replica->ip != NULL && + !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything); + print_child = replica->child != NULL && + !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything); + print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything); + print_remote = replica->remote != NULL && + !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything); + + if (pcmk_is_set(show_opts, pcmk_show_implicit_rscs) || + (print_everything == FALSE && (print_ip || print_child || print_ctnr || print_remote))) { + /* The text output messages used below require pe_print_implicit to + * be set to do anything. + */ + uint32_t new_show_opts = show_opts | pcmk_show_implicit_rscs; + + PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s", + (bundle_data->nreplicas > 1)? " set" : "", + rsc->id, bundle_data->image, + pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", + desc ? " (" : "", desc ? desc : "", desc ? ")" : "", + get_unmanaged_str(rsc)); + + if (pcmk__list_of_multiple(bundle_data->replicas)) { + out->begin_list(out, NULL, NULL, "Replica[%d]", replica->offset); + } + + if (print_ip) { + out->message(out, crm_map_element_name(replica->ip->xml), + new_show_opts, replica->ip, only_node, only_rsc); + } + + if (print_child) { + out->message(out, crm_map_element_name(replica->child->xml), + new_show_opts, replica->child, only_node, only_rsc); + } + + if (print_ctnr) { + out->message(out, crm_map_element_name(replica->container->xml), + new_show_opts, replica->container, only_node, only_rsc); + } + + if (print_remote) { + out->message(out, crm_map_element_name(replica->remote->xml), + new_show_opts, replica->remote, only_node, only_rsc); + } + + if (pcmk__list_of_multiple(bundle_data->replicas)) { + out->end_list(out); + } + } else if (print_everything == FALSE && !(print_ip || print_child || print_ctnr || print_remote)) { + continue; + } else { + PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s", + (bundle_data->nreplicas > 1)? " set" : "", + rsc->id, bundle_data->image, + pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", + desc ? " (" : "", desc ? desc : "", desc ? ")" : "", + get_unmanaged_str(rsc)); + + pe__bundle_replica_output_html(out, replica, pe__current_node(replica->container), + show_opts); + } + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +static void +pe__bundle_replica_output_text(pcmk__output_t *out, pe__bundle_replica_t *replica, + pe_node_t *node, uint32_t show_opts) +{ + const pe_resource_t *rsc = replica->child; + + int offset = 0; + char buffer[LINE_MAX]; + + if(rsc == NULL) { + rsc = replica->container; + } + + if (replica->remote) { + offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", + rsc_printable_id(replica->remote)); + } else { + offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", + rsc_printable_id(replica->container)); + } + if (replica->ipaddr) { + offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", + replica->ipaddr); + } + + pe__common_output_text(out, rsc, buffer, node, show_opts); +} + +PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pe_resource_t *", "GList *", "GList *") +int +pe__bundle_text(pcmk__output_t *out, va_list args) +{ + uint32_t show_opts = va_arg(args, uint32_t); + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + + const char *desc = NULL; + pe__bundle_variant_data_t *bundle_data = NULL; + int rc = pcmk_rc_no_output; + gboolean print_everything = TRUE; + + desc = pe__resource_description(rsc, show_opts); + + get_bundle_variant_data(bundle_data, rsc); + + CRM_ASSERT(rsc != NULL); + + if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { + return rc; + } + + print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches); + + for (GList *gIter = bundle_data->replicas; gIter != NULL; + gIter = gIter->next) { + pe__bundle_replica_t *replica = gIter->data; + gboolean print_ip, print_child, print_ctnr, print_remote; + + CRM_ASSERT(replica); + + if (pcmk__rsc_filtered_by_node(replica->container, only_node)) { + continue; + } + + print_ip = replica->ip != NULL && + !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything); + print_child = replica->child != NULL && + !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything); + print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything); + print_remote = replica->remote != NULL && + !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything); + + if (pcmk_is_set(show_opts, pcmk_show_implicit_rscs) || + (print_everything == FALSE && (print_ip || print_child || print_ctnr || print_remote))) { + /* The text output messages used below require pe_print_implicit to + * be set to do anything. + */ + uint32_t new_show_opts = show_opts | pcmk_show_implicit_rscs; + + PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s", + (bundle_data->nreplicas > 1)? " set" : "", + rsc->id, bundle_data->image, + pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", + desc ? " (" : "", desc ? desc : "", desc ? ")" : "", + get_unmanaged_str(rsc)); + + if (pcmk__list_of_multiple(bundle_data->replicas)) { + out->list_item(out, NULL, "Replica[%d]", replica->offset); + } + + out->begin_list(out, NULL, NULL, NULL); + + if (print_ip) { + out->message(out, crm_map_element_name(replica->ip->xml), + new_show_opts, replica->ip, only_node, only_rsc); + } + + if (print_child) { + out->message(out, crm_map_element_name(replica->child->xml), + new_show_opts, replica->child, only_node, only_rsc); + } + + if (print_ctnr) { + out->message(out, crm_map_element_name(replica->container->xml), + new_show_opts, replica->container, only_node, only_rsc); + } + + if (print_remote) { + out->message(out, crm_map_element_name(replica->remote->xml), + new_show_opts, replica->remote, only_node, only_rsc); + } + + out->end_list(out); + } else if (print_everything == FALSE && !(print_ip || print_child || print_ctnr || print_remote)) { + continue; + } else { + PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s", + (bundle_data->nreplicas > 1)? " set" : "", + rsc->id, bundle_data->image, + pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", + desc ? " (" : "", desc ? desc : "", desc ? ")" : "", + get_unmanaged_str(rsc)); + + pe__bundle_replica_output_text(out, replica, pe__current_node(replica->container), + show_opts); + } + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +/*! + * \internal + * \deprecated This function will be removed in a future release + */ +static void +print_bundle_replica(pe__bundle_replica_t *replica, const char *pre_text, + long options, void *print_data) +{ + pe_node_t *node = NULL; + pe_resource_t *rsc = replica->child; + + int offset = 0; + char buffer[LINE_MAX]; + + if(rsc == NULL) { + rsc = replica->container; + } + + if (replica->remote) { + offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", + rsc_printable_id(replica->remote)); + } else { + offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", + rsc_printable_id(replica->container)); + } + if (replica->ipaddr) { + offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", + replica->ipaddr); + } + + node = pe__current_node(replica->container); + common_print(rsc, pre_text, buffer, node, options, print_data); +} + +/*! + * \internal + * \deprecated This function will be removed in a future release + */ +void +pe__print_bundle(pe_resource_t *rsc, const char *pre_text, long options, + void *print_data) +{ + pe__bundle_variant_data_t *bundle_data = NULL; + char *child_text = NULL; + CRM_CHECK(rsc != NULL, return); + + if (options & pe_print_xml) { + bundle_print_xml(rsc, pre_text, options, print_data); + return; + } + + get_bundle_variant_data(bundle_data, rsc); + + if (pre_text == NULL) { + pre_text = " "; + } + + status_print("%sContainer bundle%s: %s [%s]%s%s\n", + pre_text, ((bundle_data->nreplicas > 1)? " set" : ""), + rsc->id, bundle_data->image, + pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", + pcmk_is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)"); + if (options & pe_print_html) { + status_print("<br />\n<ul>\n"); + } + + + for (GList *gIter = bundle_data->replicas; gIter != NULL; + gIter = gIter->next) { + pe__bundle_replica_t *replica = gIter->data; + + CRM_ASSERT(replica); + if (options & pe_print_html) { + status_print("<li>"); + } + + if (pcmk_is_set(options, pe_print_implicit)) { + child_text = crm_strdup_printf(" %s", pre_text); + if (pcmk__list_of_multiple(bundle_data->replicas)) { + status_print(" %sReplica[%d]\n", pre_text, replica->offset); + } + if (options & pe_print_html) { + status_print("<br />\n<ul>\n"); + } + print_rsc_in_list(replica->ip, child_text, options, print_data); + print_rsc_in_list(replica->container, child_text, options, print_data); + print_rsc_in_list(replica->remote, child_text, options, print_data); + print_rsc_in_list(replica->child, child_text, options, print_data); + if (options & pe_print_html) { + status_print("</ul>\n"); + } + } else { + child_text = crm_strdup_printf("%s ", pre_text); + print_bundle_replica(replica, child_text, options, print_data); + } + free(child_text); + + if (options & pe_print_html) { + status_print("</li>\n"); + } + } + if (options & pe_print_html) { + status_print("</ul>\n"); + } +} + +static void +free_bundle_replica(pe__bundle_replica_t *replica) +{ + if (replica == NULL) { + return; + } + + if (replica->node) { + free(replica->node); + replica->node = NULL; + } + + if (replica->ip) { + free_xml(replica->ip->xml); + replica->ip->xml = NULL; + replica->ip->fns->free(replica->ip); + replica->ip = NULL; + } + if (replica->container) { + free_xml(replica->container->xml); + replica->container->xml = NULL; + replica->container->fns->free(replica->container); + replica->container = NULL; + } + if (replica->remote) { + free_xml(replica->remote->xml); + replica->remote->xml = NULL; + replica->remote->fns->free(replica->remote); + replica->remote = NULL; + } + free(replica->ipaddr); + free(replica); +} + +void +pe__free_bundle(pe_resource_t *rsc) +{ + pe__bundle_variant_data_t *bundle_data = NULL; + CRM_CHECK(rsc != NULL, return); + + get_bundle_variant_data(bundle_data, rsc); + pe_rsc_trace(rsc, "Freeing %s", rsc->id); + + free(bundle_data->prefix); + free(bundle_data->image); + free(bundle_data->control_port); + free(bundle_data->host_network); + free(bundle_data->host_netmask); + free(bundle_data->ip_range_start); + free(bundle_data->container_network); + free(bundle_data->launcher_options); + free(bundle_data->container_command); + g_free(bundle_data->container_host_options); + + g_list_free_full(bundle_data->replicas, + (GDestroyNotify) free_bundle_replica); + g_list_free_full(bundle_data->mounts, (GDestroyNotify)mount_free); + g_list_free_full(bundle_data->ports, (GDestroyNotify)port_free); + g_list_free(rsc->children); + + if(bundle_data->child) { + free_xml(bundle_data->child->xml); + bundle_data->child->xml = NULL; + bundle_data->child->fns->free(bundle_data->child); + } + common_free(rsc); +} + +enum rsc_role_e +pe__bundle_resource_state(const pe_resource_t *rsc, gboolean current) +{ + enum rsc_role_e container_role = RSC_ROLE_UNKNOWN; + return container_role; +} + +/*! + * \brief Get the number of configured replicas in a bundle + * + * \param[in] rsc Bundle resource + * + * \return Number of configured replicas, or 0 on error + */ +int +pe_bundle_replicas(const pe_resource_t *rsc) +{ + if ((rsc == NULL) || (rsc->variant != pe_container)) { + return 0; + } else { + pe__bundle_variant_data_t *bundle_data = NULL; + + get_bundle_variant_data(bundle_data, rsc); + return bundle_data->nreplicas; + } +} + +void +pe__count_bundle(pe_resource_t *rsc) +{ + pe__bundle_variant_data_t *bundle_data = NULL; + + get_bundle_variant_data(bundle_data, rsc); + for (GList *item = bundle_data->replicas; item != NULL; item = item->next) { + pe__bundle_replica_t *replica = item->data; + + if (replica->ip) { + replica->ip->fns->count(replica->ip); + } + if (replica->child) { + replica->child->fns->count(replica->child); + } + if (replica->container) { + replica->container->fns->count(replica->container); + } + if (replica->remote) { + replica->remote->fns->count(replica->remote); + } + } +} + +gboolean +pe__bundle_is_filtered(const pe_resource_t *rsc, GList *only_rsc, + gboolean check_parent) +{ + gboolean passes = FALSE; + pe__bundle_variant_data_t *bundle_data = NULL; + + if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) { + passes = TRUE; + } else { + get_bundle_variant_data(bundle_data, rsc); + + for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { + pe__bundle_replica_t *replica = gIter->data; + + if (replica->ip != NULL && !replica->ip->fns->is_filtered(replica->ip, only_rsc, FALSE)) { + passes = TRUE; + break; + } else if (replica->child != NULL && !replica->child->fns->is_filtered(replica->child, only_rsc, FALSE)) { + passes = TRUE; + break; + } else if (!replica->container->fns->is_filtered(replica->container, only_rsc, FALSE)) { + passes = TRUE; + break; + } else if (replica->remote != NULL && !replica->remote->fns->is_filtered(replica->remote, only_rsc, FALSE)) { + passes = TRUE; + break; + } + } + } + + return !passes; +} + +/*! + * \internal + * \brief Get a list of a bundle's containers + * + * \param[in] bundle Bundle resource + * + * \return Newly created list of \p bundle's containers + * \note It is the caller's responsibility to free the result with + * g_list_free(). + */ +GList * +pe__bundle_containers(const pe_resource_t *bundle) +{ + GList *containers = NULL; + const pe__bundle_variant_data_t *data = NULL; + + get_bundle_variant_data(data, bundle); + for (GList *iter = data->replicas; iter != NULL; iter = iter->next) { + pe__bundle_replica_t *replica = iter->data; + + containers = g_list_append(containers, replica->container); + } + return containers; +} + +// Bundle implementation of resource_object_functions_t:active_node() +pe_node_t * +pe__bundle_active_node(const pe_resource_t *rsc, unsigned int *count_all, + unsigned int *count_clean) +{ + pe_node_t *active = NULL; + pe_node_t *node = NULL; + pe_resource_t *container = NULL; + GList *containers = NULL; + GList *iter = NULL; + GHashTable *nodes = NULL; + const pe__bundle_variant_data_t *data = NULL; + + if (count_all != NULL) { + *count_all = 0; + } + if (count_clean != NULL) { + *count_clean = 0; + } + if (rsc == NULL) { + return NULL; + } + + /* For the purposes of this method, we only care about where the bundle's + * containers are active, so build a list of active containers. + */ + get_bundle_variant_data(data, rsc); + for (iter = data->replicas; iter != NULL; iter = iter->next) { + pe__bundle_replica_t *replica = iter->data; + + if (replica->container->running_on != NULL) { + containers = g_list_append(containers, replica->container); + } + } + if (containers == NULL) { + return NULL; + } + + /* If the bundle has only a single active container, just use that + * container's method. If live migration is ever supported for bundle + * containers, this will allow us to prefer the migration source when there + * is only one container and it is migrating. For now, this just lets us + * avoid creating the nodes table. + */ + if (pcmk__list_of_1(containers)) { + container = containers->data; + node = container->fns->active_node(container, count_all, count_clean); + g_list_free(containers); + return node; + } + + // Add all containers' active nodes to a hash table (for uniqueness) + nodes = g_hash_table_new(NULL, NULL); + for (iter = containers; iter != NULL; iter = iter->next) { + container = iter->data; + + for (GList *node_iter = container->running_on; node_iter != NULL; + node_iter = node_iter->next) { + node = node_iter->data; + + // If insert returns true, we haven't counted this node yet + if (g_hash_table_insert(nodes, (gpointer) node->details, + (gpointer) node) + && !pe__count_active_node(rsc, node, &active, count_all, + count_clean)) { + goto done; + } + } + } + +done: + g_list_free(containers); + g_hash_table_destroy(nodes); + return active; +} |