blob: 181f51191a16091c31b3372ea5cf2def290494d3 [file] [log] [blame]
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <crm/msg_xml.h>
#include <allocate.h>
#include <notif.h>
#include <utils.h>
#define VARIANT_CONTAINER 1
#include <lib/pengine/variant.h>
static bool
is_child_container_node(container_variant_data_t *data, pe_node_t *node)
{
for (GListPtr gIter = data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
if(node->details == tuple->node->details) {
return TRUE;
}
}
return FALSE;
}
gint sort_clone_instance(gconstpointer a, gconstpointer b, gpointer data_set);
void distribute_children(resource_t *rsc, GListPtr children, GListPtr nodes,
int max, int per_host_max, pe_working_set_t * data_set);
static GListPtr get_container_list(resource_t *rsc)
{
GListPtr containers = NULL;
container_variant_data_t *data = NULL;
if(rsc->variant == pe_container) {
get_container_variant_data(data, rsc);
for (GListPtr gIter = data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
containers = g_list_append(containers, tuple->docker);
}
}
return containers;
}
static GListPtr get_containers_or_children(resource_t *rsc)
{
GListPtr containers = NULL;
container_variant_data_t *data = NULL;
if(rsc->variant == pe_container) {
get_container_variant_data(data, rsc);
for (GListPtr gIter = data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
containers = g_list_append(containers, tuple->docker);
}
return containers;
} else {
return rsc->children;
}
}
static bool
migration_threshold_reached(resource_t *rsc, node_t *node,
pe_working_set_t *data_set)
{
int fail_count, countdown;
/* Migration threshold of 0 means never force away */
if (rsc->migration_threshold == 0) {
return FALSE;
}
// If we're ignoring failures, also ignore the migration threshold
if (is_set(rsc->flags, pe_rsc_failure_ignored)) {
return FALSE;
}
/* If there are no failures, there's no need to force away */
fail_count = pe_get_failcount(node, rsc, NULL,
pe_fc_effective|pe_fc_fillers, NULL,
data_set);
if (fail_count <= 0) {
return FALSE;
}
/* How many more times recovery will be tried on this node */
countdown = QB_MAX(rsc->migration_threshold - fail_count, 0);
if (countdown == 0) {
crm_warn("Forcing %s away from %s after %d failures (max=%d)",
rsc->id, node->details->uname, fail_count,
rsc->migration_threshold);
return TRUE;
}
crm_info("%s can fail %d more times on %s before being forced off",
rsc->id, countdown, node->details->uname);
return FALSE;
}
node_t *
pcmk__bundle_allocate(pe_resource_t *rsc, pe_node_t *prefer,
pe_working_set_t *data_set)
{
GListPtr containers = NULL;
GListPtr nodes = NULL;
container_variant_data_t *container_data = NULL;
CRM_CHECK(rsc != NULL, return NULL);
get_container_variant_data(container_data, rsc);
set_bit(rsc->flags, pe_rsc_allocating);
containers = get_container_list(rsc);
pe__show_node_weights(!show_scores, rsc, __FUNCTION__, rsc->allowed_nodes);
nodes = g_hash_table_get_values(rsc->allowed_nodes);
nodes = g_list_sort_with_data(nodes, sort_node_weight, NULL);
containers = g_list_sort_with_data(containers, sort_clone_instance, data_set);
distribute_children(rsc, containers, nodes,
container_data->replicas, container_data->replicas_per_host, data_set);
g_list_free(nodes);
g_list_free(containers);
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
pe_node_t *docker_host = tuple->docker->allocated_to;
CRM_ASSERT(tuple);
if(tuple->ip) {
pe_rsc_trace(rsc, "Allocating bundle %s IP %s",
rsc->id, tuple->ip->id);
tuple->ip->cmds->allocate(tuple->ip, prefer, data_set);
}
if(tuple->remote && is_remote_node(docker_host)) {
/* We need 'nested' connection resources to be on the same
* host because pacemaker-remoted only supports a single
* active connection
*/
rsc_colocation_new("child-remote-with-docker-remote", NULL,
INFINITY, tuple->remote, docker_host->details->remote_rsc, NULL, NULL, data_set);
}
if(tuple->remote) {
pe_rsc_trace(rsc, "Allocating bundle %s connection %s",
rsc->id, tuple->remote->id);
tuple->remote->cmds->allocate(tuple->remote, prefer, data_set);
}
// Explicitly allocate tuple->child before the container->child
if(tuple->child) {
pe_node_t *node = NULL;
GHashTableIter iter;
g_hash_table_iter_init(&iter, tuple->child->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & node)) {
if(node->details != tuple->node->details) {
node->weight = -INFINITY;
} else if(migration_threshold_reached(tuple->child, node, data_set) == FALSE) {
node->weight = INFINITY;
}
}
set_bit(tuple->child->parent->flags, pe_rsc_allocating);
pe_rsc_trace(rsc, "Allocating bundle %s replica child %s",
rsc->id, tuple->child->id);
tuple->child->cmds->allocate(tuple->child, tuple->node, data_set);
clear_bit(tuple->child->parent->flags, pe_rsc_allocating);
}
}
if(container_data->child) {
pe_node_t *node = NULL;
GHashTableIter iter;
g_hash_table_iter_init(&iter, container_data->child->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & node)) {
if(is_child_container_node(container_data, node)) {
node->weight = 0;
} else {
node->weight = -INFINITY;
}
}
pe_rsc_trace(rsc, "Allocating bundle %s child %s",
rsc->id, container_data->child->id);
container_data->child->cmds->allocate(container_data->child, prefer, data_set);
}
clear_bit(rsc->flags, pe_rsc_allocating);
clear_bit(rsc->flags, pe_rsc_provisional);
return NULL;
}
void
container_create_actions(resource_t * rsc, pe_working_set_t * data_set)
{
pe_action_t *action = NULL;
GListPtr containers = NULL;
container_variant_data_t *container_data = NULL;
CRM_CHECK(rsc != NULL, return);
containers = get_container_list(rsc);
get_container_variant_data(container_data, rsc);
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
CRM_ASSERT(tuple);
if(tuple->ip) {
tuple->ip->cmds->create_actions(tuple->ip, data_set);
}
if(tuple->docker) {
tuple->docker->cmds->create_actions(tuple->docker, data_set);
}
if(tuple->remote) {
tuple->remote->cmds->create_actions(tuple->remote, data_set);
}
}
clone_create_pseudo_actions(rsc, containers, NULL, NULL, data_set);
if(container_data->child) {
container_data->child->cmds->create_actions(container_data->child, data_set);
if(container_data->child->variant == pe_master) {
/* promote */
action = create_pseudo_resource_op(rsc, RSC_PROMOTE, TRUE, TRUE, data_set);
action = create_pseudo_resource_op(rsc, RSC_PROMOTED, TRUE, TRUE, data_set);
action->priority = INFINITY;
/* demote */
action = create_pseudo_resource_op(rsc, RSC_DEMOTE, TRUE, TRUE, data_set);
action = create_pseudo_resource_op(rsc, RSC_DEMOTED, TRUE, TRUE, data_set);
action->priority = INFINITY;
}
}
g_list_free(containers);
}
void
container_internal_constraints(resource_t * rsc, pe_working_set_t * data_set)
{
container_variant_data_t *container_data = NULL;
CRM_CHECK(rsc != NULL, return);
get_container_variant_data(container_data, rsc);
if(container_data->child) {
new_rsc_order(rsc, RSC_START, container_data->child, RSC_START, pe_order_implies_first_printed, data_set);
new_rsc_order(rsc, RSC_STOP, container_data->child, RSC_STOP, pe_order_implies_first_printed, data_set);
if(container_data->child->children) {
new_rsc_order(container_data->child, RSC_STARTED, rsc, RSC_STARTED, pe_order_implies_then_printed, data_set);
new_rsc_order(container_data->child, RSC_STOPPED, rsc, RSC_STOPPED, pe_order_implies_then_printed, data_set);
} else {
new_rsc_order(container_data->child, RSC_START, rsc, RSC_STARTED, pe_order_implies_then_printed, data_set);
new_rsc_order(container_data->child, RSC_STOP, rsc, RSC_STOPPED, pe_order_implies_then_printed, data_set);
}
}
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
CRM_ASSERT(tuple);
CRM_ASSERT(tuple->docker);
tuple->docker->cmds->internal_constraints(tuple->docker, data_set);
order_start_start(rsc, tuple->docker, pe_order_runnable_left | pe_order_implies_first_printed);
if(tuple->child) {
order_stop_stop(rsc, tuple->child, pe_order_implies_first_printed);
}
order_stop_stop(rsc, tuple->docker, pe_order_implies_first_printed);
new_rsc_order(tuple->docker, RSC_START, rsc, RSC_STARTED, pe_order_implies_then_printed, data_set);
new_rsc_order(tuple->docker, RSC_STOP, rsc, RSC_STOPPED, pe_order_implies_then_printed, data_set);
if(tuple->ip) {
tuple->ip->cmds->internal_constraints(tuple->ip, data_set);
// Start ip then docker
new_rsc_order(tuple->ip, RSC_START, tuple->docker, RSC_START,
pe_order_runnable_left|pe_order_preserve, data_set);
new_rsc_order(tuple->docker, RSC_STOP, tuple->ip, RSC_STOP,
pe_order_implies_first|pe_order_preserve, data_set);
rsc_colocation_new("ip-with-docker", NULL, INFINITY, tuple->ip, tuple->docker, NULL, NULL, data_set);
}
if(tuple->remote) {
/* This handles ordering and colocating remote relative to docker
* (via "resource-with-container"). Since IP is also ordered and
* colocated relative to docker, we don't need to do anything
* explicit here with IP.
*/
tuple->remote->cmds->internal_constraints(tuple->remote, data_set);
}
if(tuple->child) {
CRM_ASSERT(tuple->remote);
// Start of the remote then child is implicit in the PE's remote logic
}
}
if(container_data->child) {
container_data->child->cmds->internal_constraints(container_data->child, data_set);
if(container_data->child->variant == pe_master) {
master_promotion_constraints(rsc, data_set);
/* child demoted before global demoted */
new_rsc_order(container_data->child, RSC_DEMOTED, rsc, RSC_DEMOTED, pe_order_implies_then_printed, data_set);
/* global demote before child demote */
new_rsc_order(rsc, RSC_DEMOTE, container_data->child, RSC_DEMOTE, pe_order_implies_first_printed, data_set);
/* child promoted before global promoted */
new_rsc_order(container_data->child, RSC_PROMOTED, rsc, RSC_PROMOTED, pe_order_implies_then_printed, data_set);
/* global promote before child promote */
new_rsc_order(rsc, RSC_PROMOTE, container_data->child, RSC_PROMOTE, pe_order_implies_first_printed, data_set);
}
} else {
// int type = pe_order_optional | pe_order_implies_then | pe_order_restart;
// custom_action_order(rsc, generate_op_key(rsc->id, RSC_STOP, 0), NULL,
// rsc, generate_op_key(rsc->id, RSC_START, 0), NULL, pe_order_optional, data_set);
}
}
static resource_t *
find_compatible_tuple_by_node(resource_t * rsc_lh, node_t * candidate, resource_t * rsc,
enum rsc_role_e filter, gboolean current)
{
container_variant_data_t *container_data = NULL;
CRM_CHECK(candidate != NULL, return NULL);
get_container_variant_data(container_data, rsc);
crm_trace("Looking for compatible child from %s for %s on %s",
rsc_lh->id, rsc->id, candidate->details->uname);
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
if(is_child_compatible(tuple->docker, candidate, filter, current)) {
crm_trace("Pairing %s with %s on %s",
rsc_lh->id, tuple->docker->id, candidate->details->uname);
return tuple->docker;
}
}
crm_trace("Can't pair %s with %s", rsc_lh->id, rsc->id);
return NULL;
}
static resource_t *
find_compatible_tuple(resource_t *rsc_lh, resource_t * rsc, enum rsc_role_e filter,
gboolean current)
{
GListPtr scratch = NULL;
resource_t *pair = NULL;
node_t *active_node_lh = NULL;
active_node_lh = rsc_lh->fns->location(rsc_lh, NULL, current);
if (active_node_lh) {
return find_compatible_tuple_by_node(rsc_lh, active_node_lh, rsc, filter, current);
}
scratch = g_hash_table_get_values(rsc_lh->allowed_nodes);
scratch = g_list_sort_with_data(scratch, sort_node_weight, NULL);
for (GListPtr gIter = scratch; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
pair = find_compatible_tuple_by_node(rsc_lh, node, rsc, filter, current);
if (pair) {
goto done;
}
}
pe_rsc_debug(rsc, "Can't pair %s with %s", rsc_lh->id, rsc->id);
done:
g_list_free(scratch);
return pair;
}
void
container_rsc_colocation_lh(resource_t * rsc, resource_t * rsc_rh, rsc_colocation_t * constraint)
{
/* -- Never called --
*
* Instead we add the colocation constraints to the child and call from there
*/
CRM_ASSERT(FALSE);
}
int copies_per_node(resource_t * rsc)
{
/* Strictly speaking, there should be a 'copies_per_node' addition
* to the resource function table and each case would be a
* function. However that would be serious overkill to return an
* int. In fact, it seems to me that both function tables
* could/should be replaced by resources.{c,h} full of
* rsc_{some_operation} functions containing a switch as below
* which calls out to functions named {variant}_{some_operation}
* as needed.
*/
switch(rsc->variant) {
case pe_unknown:
return 0;
case pe_native:
case pe_group:
return 1;
case pe_clone:
case pe_master:
{
const char *max_clones_node = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INCARNATION_NODEMAX);
return crm_parse_int(max_clones_node, "1");
}
case pe_container:
{
container_variant_data_t *data = NULL;
get_container_variant_data(data, rsc);
return data->replicas_per_host;
}
}
return 0;
}
void
container_rsc_colocation_rh(resource_t * rsc_lh, resource_t * rsc, rsc_colocation_t * constraint)
{
GListPtr allocated_rhs = NULL;
container_variant_data_t *container_data = NULL;
CRM_CHECK(constraint != NULL, return);
CRM_CHECK(rsc_lh != NULL, pe_err("rsc_lh was NULL for %s", constraint->id); return);
CRM_CHECK(rsc != NULL, pe_err("rsc was NULL for %s", constraint->id); return);
CRM_ASSERT(rsc_lh->variant == pe_native);
if (constraint->score == 0) {
return;
}
if (is_set(rsc->flags, pe_rsc_provisional)) {
pe_rsc_trace(rsc, "%s is still provisional", rsc->id);
return;
} else if(constraint->rsc_lh->variant > pe_group) {
resource_t *rh_child = find_compatible_tuple(rsc_lh, rsc, RSC_ROLE_UNKNOWN, FALSE);
if (rh_child) {
pe_rsc_debug(rsc, "Pairing %s with %s", rsc_lh->id, rh_child->id);
rsc_lh->cmds->rsc_colocation_lh(rsc_lh, rh_child, constraint);
} else if (constraint->score >= INFINITY) {
crm_notice("Cannot pair %s with instance of %s", rsc_lh->id, rsc->id);
assign_node(rsc_lh, NULL, TRUE);
} else {
pe_rsc_debug(rsc, "Cannot pair %s with instance of %s", rsc_lh->id, rsc->id);
}
return;
}
get_container_variant_data(container_data, rsc);
pe_rsc_trace(rsc, "Processing constraint %s: %s -> %s %d",
constraint->id, rsc_lh->id, rsc->id, constraint->score);
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
if (constraint->score < INFINITY) {
tuple->docker->cmds->rsc_colocation_rh(rsc_lh, tuple->docker, constraint);
} else {
node_t *chosen = tuple->docker->fns->location(tuple->docker, NULL, FALSE);
if (chosen == NULL || is_set_recursive(tuple->docker, pe_rsc_block, TRUE)) {
continue;
}
if(constraint->role_rh >= RSC_ROLE_MASTER && tuple->child == NULL) {
continue;
}
if(constraint->role_rh >= RSC_ROLE_MASTER && tuple->child->next_role < RSC_ROLE_MASTER) {
continue;
}
pe_rsc_trace(rsc, "Allowing %s: %s %d", constraint->id, chosen->details->uname, chosen->weight);
allocated_rhs = g_list_prepend(allocated_rhs, chosen);
}
}
if (constraint->score >= INFINITY) {
node_list_exclude(rsc_lh->allowed_nodes, allocated_rhs, FALSE);
}
g_list_free(allocated_rhs);
}
enum pe_action_flags
container_action_flags(action_t * action, node_t * node)
{
GListPtr containers = NULL;
enum pe_action_flags flags = 0;
container_variant_data_t *data = NULL;
get_container_variant_data(data, action->rsc);
if(data->child) {
enum action_tasks task = get_complex_task(data->child, action->task, TRUE);
switch(task) {
case no_action:
case action_notify:
case action_notified:
case action_promote:
case action_promoted:
case action_demote:
case action_demoted:
return summary_action_flags(action, data->child->children, node);
default:
break;
}
}
containers = get_container_list(action->rsc);
flags = summary_action_flags(action, containers, node);
g_list_free(containers);
return flags;
}
resource_t *
find_compatible_child_by_node(resource_t * local_child, node_t * local_node, resource_t * rsc,
enum rsc_role_e filter, gboolean current)
{
GListPtr gIter = NULL;
GListPtr children = NULL;
if (local_node == NULL) {
crm_err("Can't colocate unrunnable child %s with %s", local_child->id, rsc->id);
return NULL;
}
crm_trace("Looking for compatible child from %s for %s on %s",
local_child->id, rsc->id, local_node->details->uname);
children = get_containers_or_children(rsc);
for (gIter = children; gIter != NULL; gIter = gIter->next) {
resource_t *child_rsc = (resource_t *) gIter->data;
if(is_child_compatible(child_rsc, local_node, filter, current)) {
crm_trace("Pairing %s with %s on %s",
local_child->id, child_rsc->id, local_node->details->uname);
return child_rsc;
}
}
crm_trace("Can't pair %s with %s", local_child->id, rsc->id);
if(children != rsc->children) {
g_list_free(children);
}
return NULL;
}
static container_grouping_t *
tuple_for_docker(resource_t *rsc, resource_t *docker, node_t *node)
{
if(rsc->variant == pe_container) {
container_variant_data_t *data = NULL;
get_container_variant_data(data, rsc);
for (GListPtr gIter = data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
if(tuple->child
&& docker == tuple->docker
&& node->details == tuple->node->details) {
return tuple;
}
}
}
return NULL;
}
static enum pe_graph_flags
container_update_interleave_actions(action_t * first, action_t * then, node_t * node, enum pe_action_flags flags,
enum pe_action_flags filter, enum pe_ordering type)
{
GListPtr gIter = NULL;
GListPtr children = NULL;
gboolean current = FALSE;
enum pe_graph_flags changed = pe_graph_none;
/* Fix this - lazy */
if (crm_ends_with(first->uuid, "_stopped_0")
|| crm_ends_with(first->uuid, "_demoted_0")) {
current = TRUE;
}
children = get_containers_or_children(then->rsc);
for (gIter = children; gIter != NULL; gIter = gIter->next) {
resource_t *then_child = (resource_t *) gIter->data;
resource_t *first_child = find_compatible_child(then_child, first->rsc, RSC_ROLE_UNKNOWN, current);
if (first_child == NULL && current) {
crm_trace("Ignore");
} else if (first_child == NULL) {
crm_debug("No match found for %s (%d / %s / %s)", then_child->id, current, first->uuid, then->uuid);
/* Me no like this hack - but what else can we do?
*
* If there is no-one active or about to be active
* on the same node as then_child, then they must
* not be allowed to start
*/
if (type & (pe_order_runnable_left | pe_order_implies_then) /* Mandatory */ ) {
pe_rsc_info(then->rsc, "Inhibiting %s from being active", then_child->id);
if(assign_node(then_child, NULL, TRUE)) {
changed |= pe_graph_updated_then;
}
}
} else {
pe_action_t *first_action = NULL;
pe_action_t *then_action = NULL;
enum action_tasks task = clone_child_action(first);
const char *first_task = task2text(task);
container_grouping_t *first_tuple = tuple_for_docker(first->rsc, first_child, node);
container_grouping_t *then_tuple = tuple_for_docker(then->rsc, then_child, node);
if(strstr(first->task, "stop") && first_tuple && first_tuple->child) {
/* Except for 'stopped' we should be looking at the
* in-container resource, actions for the child will
* happen later and are therefor more likely to align
* with the user's intent.
*/
first_action = find_first_action(first_tuple->child->actions, NULL, task2text(task), node);
} else {
first_action = find_first_action(first_child->actions, NULL, task2text(task), node);
}
if(strstr(then->task, "mote") && then_tuple && then_tuple->child) {
/* Promote/demote actions will never be found for the
* docker resource, look in the child instead
*
* Alternatively treat:
* 'XXXX then promote YYYY' as 'XXXX then start container for YYYY', and
* 'demote XXXX then stop YYYY' as 'stop container for XXXX then stop YYYY'
*/
then_action = find_first_action(then_tuple->child->actions, NULL, then->task, node);
} else {
then_action = find_first_action(then_child->actions, NULL, then->task, node);
}
if (first_action == NULL) {
if (is_not_set(first_child->flags, pe_rsc_orphan)
&& crm_str_eq(first_task, RSC_STOP, TRUE) == FALSE
&& crm_str_eq(first_task, RSC_DEMOTE, TRUE) == FALSE) {
crm_err("Internal error: No action found for %s in %s (first)",
first_task, first_child->id);
} else {
crm_trace("No action found for %s in %s%s (first)",
first_task, first_child->id,
is_set(first_child->flags, pe_rsc_orphan) ? " (ORPHAN)" : "");
}
continue;
}
/* We're only interested if 'then' is neither stopping nor being demoted */
if (then_action == NULL) {
if (is_not_set(then_child->flags, pe_rsc_orphan)
&& crm_str_eq(then->task, RSC_STOP, TRUE) == FALSE
&& crm_str_eq(then->task, RSC_DEMOTE, TRUE) == FALSE) {
crm_err("Internal error: No action found for %s in %s (then)",
then->task, then_child->id);
} else {
crm_trace("No action found for %s in %s%s (then)",
then->task, then_child->id,
is_set(then_child->flags, pe_rsc_orphan) ? " (ORPHAN)" : "");
}
continue;
}
if (order_actions(first_action, then_action, type)) {
crm_debug("Created constraint for %s (%d) -> %s (%d) %.6x",
first_action->uuid, is_set(first_action->flags, pe_action_optional),
then_action->uuid, is_set(then_action->flags, pe_action_optional), type);
changed |= (pe_graph_updated_first | pe_graph_updated_then);
}
if(first_action && then_action) {
changed |= then_child->cmds->update_actions(first_action, then_action, node,
first_child->cmds->action_flags(first_action, node),
filter, type);
} else {
crm_err("Nothing found either for %s (%p) or %s (%p) %s",
first_child->id, first_action,
then_child->id, then_action, task2text(task));
}
}
}
if(children != then->rsc->children) {
g_list_free(children);
}
return changed;
}
bool can_interleave_actions(pe_action_t *first, pe_action_t *then)
{
bool interleave = FALSE;
resource_t *rsc = NULL;
const char *interleave_s = NULL;
if(first->rsc == NULL || then->rsc == NULL) {
crm_trace("Not interleaving %s with %s (both must be resources)", first->uuid, then->uuid);
return FALSE;
} else if(first->rsc == then->rsc) {
crm_trace("Not interleaving %s with %s (must belong to different resources)", first->uuid, then->uuid);
return FALSE;
} else if(first->rsc->variant < pe_clone || then->rsc->variant < pe_clone) {
crm_trace("Not interleaving %s with %s (both sides must be clones, masters, or bundles)", first->uuid, then->uuid);
return FALSE;
}
if (crm_ends_with(then->uuid, "_stop_0") || crm_ends_with(then->uuid, "_demote_0")) {
rsc = first->rsc;
} else {
rsc = then->rsc;
}
interleave_s = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INTERLEAVE);
interleave = crm_is_true(interleave_s);
crm_trace("Interleave %s -> %s: %s (based on %s)",
first->uuid, then->uuid, interleave ? "yes" : "no", rsc->id);
return interleave;
}
enum pe_graph_flags
container_update_actions(action_t * first, action_t * then, node_t * node, enum pe_action_flags flags,
enum pe_action_flags filter, enum pe_ordering type)
{
enum pe_graph_flags changed = pe_graph_none;
crm_trace("%s -> %s", first->uuid, then->uuid);
if(can_interleave_actions(first, then)) {
changed = container_update_interleave_actions(first, then, node, flags, filter, type);
} else if(then->rsc) {
GListPtr gIter = NULL;
GListPtr children = NULL;
// Handle the 'primitive' ordering case
changed |= native_update_actions(first, then, node, flags, filter, type);
// Now any children (or containers in the case of a bundle)
children = get_containers_or_children(then->rsc);
for (gIter = children; gIter != NULL; gIter = gIter->next) {
resource_t *then_child = (resource_t *) gIter->data;
enum pe_graph_flags then_child_changed = pe_graph_none;
action_t *then_child_action = find_first_action(then_child->actions, NULL, then->task, node);
if (then_child_action) {
enum pe_action_flags then_child_flags = then_child->cmds->action_flags(then_child_action, node);
if (is_set(then_child_flags, pe_action_runnable)) {
then_child_changed |=
then_child->cmds->update_actions(first, then_child_action, node, flags, filter, type);
}
changed |= then_child_changed;
if (then_child_changed & pe_graph_updated_then) {
for (GListPtr lpc = then_child_action->actions_after; lpc != NULL; lpc = lpc->next) {
action_wrapper_t *next = (action_wrapper_t *) lpc->data;
update_action(next->action);
}
}
}
}
if(children != then->rsc->children) {
g_list_free(children);
}
}
return changed;
}
void
container_rsc_location(pe_resource_t *rsc, pe__location_t *constraint)
{
container_variant_data_t *container_data = NULL;
get_container_variant_data(container_data, rsc);
pe_rsc_trace(rsc, "Processing location constraint %s for %s", constraint->id, rsc->id);
native_rsc_location(rsc, constraint);
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
if (tuple->docker) {
tuple->docker->cmds->rsc_location(tuple->docker, constraint);
}
if(tuple->ip) {
tuple->ip->cmds->rsc_location(tuple->ip, constraint);
}
}
if(container_data->child && (constraint->role_filter == RSC_ROLE_SLAVE || constraint->role_filter == RSC_ROLE_MASTER)) {
container_data->child->cmds->rsc_location(container_data->child, constraint);
container_data->child->rsc_location = g_list_prepend(container_data->child->rsc_location, constraint);
}
}
void
container_expand(resource_t * rsc, pe_working_set_t * data_set)
{
container_variant_data_t *container_data = NULL;
CRM_CHECK(rsc != NULL, return);
get_container_variant_data(container_data, rsc);
if(container_data->child) {
container_data->child->cmds->expand(container_data->child, data_set);
}
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
CRM_ASSERT(tuple);
if (tuple->remote && tuple->docker && container_fix_remote_addr(tuple->remote)) {
// REMOTE_CONTAINER_HACK: Allow remote nodes that start containers with pacemaker remote inside
xmlNode *nvpair = get_xpath_object("//nvpair[@name='addr']", tuple->remote->xml, LOG_ERR);
const char *calculated_addr = container_fix_remote_addr_in(tuple->remote, nvpair, "value");
if (calculated_addr) {
crm_trace("Set address for bundle connection %s to bundle host %s",
tuple->remote->id, calculated_addr);
g_hash_table_replace(tuple->remote->parameters, strdup("addr"), strdup(calculated_addr));
} else {
/* The only way to get here is if the remote connection is
* neither currently running nor scheduled to run. That means we
* won't be doing any operations that require addr (only start
* requires it; we additionally use it to compare digests when
* unpacking status, promote, and migrate_from history, but
* that's already happened by this point).
*/
crm_info("Unable to determine address for bundle %s remote connection",
rsc->id);
}
}
if(tuple->ip) {
tuple->ip->cmds->expand(tuple->ip, data_set);
}
if(tuple->docker) {
tuple->docker->cmds->expand(tuple->docker, data_set);
}
if(tuple->remote) {
tuple->remote->cmds->expand(tuple->remote, data_set);
}
}
}
gboolean
container_create_probe(resource_t * rsc, node_t * node, action_t * complete,
gboolean force, pe_working_set_t * data_set)
{
bool any_created = FALSE;
container_variant_data_t *container_data = NULL;
CRM_CHECK(rsc != NULL, return FALSE);
get_container_variant_data(container_data, rsc);
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
CRM_ASSERT(tuple);
if(tuple->ip) {
any_created |= tuple->ip->cmds->create_probe(tuple->ip, node, complete, force, data_set);
}
if(tuple->child && node->details == tuple->node->details) {
any_created |= tuple->child->cmds->create_probe(tuple->child, node, complete, force, data_set);
}
if(tuple->docker) {
bool created = tuple->docker->cmds->create_probe(tuple->docker, node, complete, force, data_set);
if(created) {
any_created = TRUE;
/* If we're limited to one replica per host (due to
* the lack of an IP range probably), then we don't
* want any of our peer containers starting until
* we've established that no other copies are already
* running.
*
* Partly this is to ensure that replicas_per_host is
* observed, but also to ensure that the containers
* don't fail to start because the necessary port
* mappings (which won't include an IP for uniqueness)
* are already taken
*/
for (GListPtr tIter = container_data->tuples; tIter != NULL && container_data->replicas_per_host == 1; tIter = tIter->next) {
container_grouping_t *other = (container_grouping_t *)tIter->data;
if ((other != tuple) && (other != NULL)
&& (other->docker != NULL)) {
custom_action_order(tuple->docker, generate_op_key(tuple->docker->id, RSC_STATUS, 0), NULL,
other->docker, generate_op_key(other->docker->id, RSC_START, 0), NULL,
pe_order_optional|pe_order_same_node, data_set);
}
}
}
}
if (tuple->docker && tuple->remote
&& tuple->remote->cmds->create_probe(tuple->remote, node, complete,
force, data_set)) {
/* Do not probe the remote resource until we know where docker is running
* Required for REMOTE_CONTAINER_HACK to correctly probe remote resources
*/
char *probe_uuid = generate_op_key(tuple->remote->id, RSC_STATUS, 0);
action_t *probe = find_first_action(tuple->remote->actions, probe_uuid, NULL, node);
free(probe_uuid);
if (probe) {
any_created = TRUE;
crm_trace("Ordering %s probe on %s",
tuple->remote->id, node->details->uname);
custom_action_order(tuple->docker,
generate_op_key(tuple->docker->id, RSC_START, 0),
NULL, tuple->remote, NULL, probe,
pe_order_probe, data_set);
}
}
}
return any_created;
}
void
container_append_meta(resource_t * rsc, xmlNode * xml)
{
}
void container_LogActions(
resource_t * rsc, pe_working_set_t * data_set, gboolean terminal)
{
container_variant_data_t *container_data = NULL;
CRM_CHECK(rsc != NULL, return);
get_container_variant_data(container_data, rsc);
for (GListPtr gIter = container_data->tuples; gIter != NULL; gIter = gIter->next) {
container_grouping_t *tuple = (container_grouping_t *)gIter->data;
CRM_ASSERT(tuple);
if(tuple->ip) {
LogActions(tuple->ip, data_set, terminal);
}
if(tuple->docker) {
LogActions(tuple->docker, data_set, terminal);
}
if(tuple->remote) {
LogActions(tuple->remote, data_set, terminal);
}
if(tuple->child) {
LogActions(tuple->child, data_set, terminal);
}
}
}