All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.persistent.PersistentTasksCustomMetadata Maven / Gradle / Ivy

/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */
package org.elasticsearch.persistent;

import org.elasticsearch.ResourceAlreadyExistsException;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.AbstractNamedDiffable;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.NamedDiff;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.xcontent.ConstructingObjectParser;
import org.elasticsearch.xcontent.ObjectParser;
import org.elasticsearch.xcontent.ObjectParser.NamedObjectParser;
import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.ToXContentObject;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static org.elasticsearch.cluster.metadata.Metadata.ALL_CONTEXTS;
import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg;

/**
 * A cluster state record that contains a list of all running persistent tasks
 */
public final class PersistentTasksCustomMetadata extends AbstractNamedDiffable implements Metadata.Custom {

    public static final String TYPE = "persistent_tasks";
    private static final String API_CONTEXT = Metadata.XContentContext.API.toString();
    static final Assignment LOST_NODE_ASSIGNMENT = new Assignment(null, "awaiting reassignment after node loss");

    // TODO: Implement custom Diff for tasks
    private final Map> tasks;
    private final long lastAllocationId;

    public PersistentTasksCustomMetadata(long lastAllocationId, Map> tasks) {
        this.lastAllocationId = lastAllocationId;
        this.tasks = tasks;
    }

    private static final ObjectParser PERSISTENT_TASKS_PARSER = new ObjectParser<>(TYPE, Builder::new);

    private static final ObjectParser, Void> PERSISTENT_TASK_PARSER = new ObjectParser<>(
        "tasks",
        TaskBuilder::new
    );

    public static final ConstructingObjectParser ASSIGNMENT_PARSER = new ConstructingObjectParser<>(
        "assignment",
        objects -> new Assignment((String) objects[0], (String) objects[1])
    );

    private static final NamedObjectParser, Void> TASK_DESCRIPTION_PARSER;

    static {
        // Tasks parser initialization
        PERSISTENT_TASKS_PARSER.declareLong(Builder::setLastAllocationId, new ParseField("last_allocation_id"));
        PERSISTENT_TASKS_PARSER.declareObjectArray(Builder::setTasks, PERSISTENT_TASK_PARSER, new ParseField("tasks"));

        // Task description parser initialization
        ObjectParser, String> parser = new ObjectParser<>("named");
        parser.declareObject(
            TaskDescriptionBuilder::setParams,
            (p, c) -> p.namedObject(PersistentTaskParams.class, c, null),
            new ParseField("params")
        );
        parser.declareObject(
            TaskDescriptionBuilder::setState,
            (p, c) -> p.namedObject(PersistentTaskState.class, c, null),
            new ParseField("state", "status")
        );
        TASK_DESCRIPTION_PARSER = (XContentParser p, Void c, String name) -> parser.parse(p, new TaskDescriptionBuilder<>(name), name);

        // Assignment parser
        ASSIGNMENT_PARSER.declareStringOrNull(constructorArg(), new ParseField("executor_node"));
        ASSIGNMENT_PARSER.declareStringOrNull(constructorArg(), new ParseField("explanation"));

        // Task parser initialization
        PERSISTENT_TASK_PARSER.declareString(TaskBuilder::setId, new ParseField("id"));
        PERSISTENT_TASK_PARSER.declareString(TaskBuilder::setTaskName, new ParseField("name"));
        PERSISTENT_TASK_PARSER.declareLong(TaskBuilder::setAllocationId, new ParseField("allocation_id"));

        PERSISTENT_TASK_PARSER.declareNamedObjects(
            (TaskBuilder taskBuilder, List> objects) -> {
                if (objects.size() != 1) {
                    throw new IllegalArgumentException("only one task description per task is allowed");
                }
                TaskDescriptionBuilder builder = objects.get(0);
                taskBuilder.setTaskName(builder.taskName);
                taskBuilder.setParams(builder.params);
                taskBuilder.setState(builder.state);
            },
            TASK_DESCRIPTION_PARSER,
            new ParseField("task")
        );
        PERSISTENT_TASK_PARSER.declareObject(TaskBuilder::setAssignment, ASSIGNMENT_PARSER, new ParseField("assignment"));
        PERSISTENT_TASK_PARSER.declareLong(
            TaskBuilder::setAllocationIdOnLastStatusUpdate,
            new ParseField("allocation_id_on_last_status_update")
        );
    }

    public static PersistentTasksCustomMetadata getPersistentTasksCustomMetadata(ClusterState clusterState) {
        return clusterState.getMetadata().custom(PersistentTasksCustomMetadata.TYPE);
    }

    /**
     * Private builder used in XContent parser to build task-specific portion (params and state)
     */
    private static class TaskDescriptionBuilder {

        private final String taskName;
        private Params params;
        private PersistentTaskState state;

        private TaskDescriptionBuilder(String taskName) {
            this.taskName = taskName;
        }

        private TaskDescriptionBuilder setParams(Params params) {
            this.params = params;
            return this;
        }

        private TaskDescriptionBuilder setState(PersistentTaskState state) {
            this.state = state;
            return this;
        }
    }

    public Collection> tasks() {
        return this.tasks.values();
    }

    public Map> taskMap() {
        return this.tasks;
    }

    public PersistentTask getTask(String id) {
        return this.tasks.get(id);
    }

    public Collection> findTasks(String taskName, Predicate> predicate) {
        return this.tasks().stream().filter(p -> taskName.equals(p.getTaskName())).filter(predicate).collect(Collectors.toList());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PersistentTasksCustomMetadata that = (PersistentTasksCustomMetadata) o;
        return lastAllocationId == that.lastAllocationId && Objects.equals(tasks, that.tasks);
    }

    @Override
    public int hashCode() {
        return Objects.hash(tasks, lastAllocationId);
    }

    @Override
    public String toString() {
        return Strings.toString(this);
    }

    public long getNumberOfTasksOnNode(String nodeId, String taskName) {
        return tasks.values()
            .stream()
            .filter(task -> taskName.equals(task.taskName) && nodeId.equals(task.assignment.executorNode))
            .count();
    }

    @Override
    public Version getMinimalSupportedVersion() {
        return Version.CURRENT.minimumCompatibilityVersion();
    }

    @Override
    public EnumSet context() {
        return ALL_CONTEXTS;
    }

    public static PersistentTasksCustomMetadata fromXContent(XContentParser parser) {
        return PERSISTENT_TASKS_PARSER.apply(parser, null).build();
    }

    @SuppressWarnings("unchecked")
    public static  PersistentTask getTaskWithId(ClusterState clusterState, String taskId) {
        PersistentTasksCustomMetadata tasks = clusterState.metadata().custom(PersistentTasksCustomMetadata.TYPE);
        if (tasks != null) {
            return (PersistentTask) tasks.getTask(taskId);
        }
        return null;
    }

    /**
     * Unassign any persistent tasks executing on nodes that are no longer in
     * the cluster. If the task's assigment has a non-null executor node and that
     * node is no longer in the cluster then the assignment is set to
     * {@link #LOST_NODE_ASSIGNMENT}
     *
     * @param clusterState The clusterstate
     * @return If no changes the argument {@code clusterState} is returned else
     *          a copy with the modified tasks
     */
    public static ClusterState disassociateDeadNodes(ClusterState clusterState) {
        PersistentTasksCustomMetadata tasks = getPersistentTasksCustomMetadata(clusterState);
        if (tasks == null) {
            return clusterState;
        }

        PersistentTasksCustomMetadata.Builder taskBuilder = PersistentTasksCustomMetadata.builder(tasks);
        for (PersistentTask task : tasks.tasks()) {
            if (task.getAssignment().getExecutorNode() != null
                && clusterState.nodes().nodeExists(task.getAssignment().getExecutorNode()) == false) {
                taskBuilder.reassignTask(task.getId(), LOST_NODE_ASSIGNMENT);
            }
        }

        if (taskBuilder.isChanged() == false) {
            return clusterState;
        }

        Metadata.Builder metadataBuilder = Metadata.builder(clusterState.metadata());
        metadataBuilder.putCustom(TYPE, taskBuilder.build());
        return ClusterState.builder(clusterState).metadata(metadataBuilder).build();
    }

    public static class Assignment {
        @Nullable
        private final String executorNode;
        private final String explanation;

        public Assignment(String executorNode, String explanation) {
            this.executorNode = executorNode;
            assert explanation != null;
            this.explanation = explanation;
        }

        @Nullable
        public String getExecutorNode() {
            return executorNode;
        }

        public String getExplanation() {
            return explanation;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Assignment that = (Assignment) o;
            return Objects.equals(executorNode, that.executorNode) && Objects.equals(explanation, that.explanation);
        }

        @Override
        public int hashCode() {
            return Objects.hash(executorNode, explanation);
        }

        public boolean isAssigned() {
            return executorNode != null;
        }

        @Override
        public String toString() {
            return "node: [" + executorNode + "], explanation: [" + explanation + "]";
        }
    }

    public static final Assignment INITIAL_ASSIGNMENT = new Assignment(null, "waiting for initial assignment");

    /**
     * A record that represents a single running persistent task
     */
    public static class PersistentTask

implements Writeable, ToXContentObject { private final String id; private final long allocationId; private final String taskName; private final P params; private final @Nullable PersistentTaskState state; private final Assignment assignment; private final @Nullable Long allocationIdOnLastStatusUpdate; public PersistentTask(final String id, final String name, final P params, final long allocationId, final Assignment assignment) { this(id, allocationId, name, params, null, assignment, null); } public PersistentTask(final PersistentTask

task, final long allocationId, final Assignment assignment) { this(task.id, allocationId, task.taskName, task.params, task.state, assignment, task.allocationId); } public PersistentTask(final PersistentTask

task, final PersistentTaskState state) { this(task.id, task.allocationId, task.taskName, task.params, state, task.assignment, task.allocationId); } private PersistentTask( final String id, final long allocationId, final String name, final P params, final PersistentTaskState state, final Assignment assignment, final Long allocationIdOnLastStatusUpdate ) { this.id = id; this.allocationId = allocationId; this.taskName = name; this.params = params; this.state = state; this.assignment = assignment; this.allocationIdOnLastStatusUpdate = allocationIdOnLastStatusUpdate; if (params != null) { if (params.getWriteableName().equals(taskName) == false) { throw new IllegalArgumentException( "params have to have the same writeable name as task. params: " + params.getWriteableName() + " task: " + taskName ); } } if (state != null) { if (state.getWriteableName().equals(taskName) == false) { throw new IllegalArgumentException( "status has to have the same writeable name as task. status: " + state.getWriteableName() + " task: " + taskName ); } } } @SuppressWarnings("unchecked") public PersistentTask(StreamInput in) throws IOException { id = in.readString(); allocationId = in.readLong(); taskName = in.readString(); if (in.getVersion().onOrAfter(Version.V_6_3_0)) { params = (P) in.readNamedWriteable(PersistentTaskParams.class); } else { params = (P) in.readOptionalNamedWriteable(PersistentTaskParams.class); } state = in.readOptionalNamedWriteable(PersistentTaskState.class); assignment = new Assignment(in.readOptionalString(), in.readString()); allocationIdOnLastStatusUpdate = in.readOptionalLong(); } @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(id); out.writeLong(allocationId); out.writeString(taskName); if (out.getVersion().onOrAfter(Version.V_6_3_0)) { out.writeNamedWriteable(params); } else { out.writeOptionalNamedWriteable(params); } out.writeOptionalNamedWriteable(state); out.writeOptionalString(assignment.executorNode); out.writeString(assignment.explanation); out.writeOptionalLong(allocationIdOnLastStatusUpdate); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PersistentTask that = (PersistentTask) o; return Objects.equals(id, that.id) && allocationId == that.allocationId && Objects.equals(taskName, that.taskName) && Objects.equals(params, that.params) && Objects.equals(state, that.state) && Objects.equals(assignment, that.assignment) && Objects.equals(allocationIdOnLastStatusUpdate, that.allocationIdOnLastStatusUpdate); } @Override public int hashCode() { return Objects.hash(id, allocationId, taskName, params, state, assignment, allocationIdOnLastStatusUpdate); } @Override public String toString() { return Strings.toString(this); } public String getId() { return id; } public long getAllocationId() { return allocationId; } public String getTaskName() { return taskName; } @Nullable public P getParams() { return params; } @Nullable public String getExecutorNode() { return assignment.executorNode; } public Assignment getAssignment() { return assignment; } public boolean isAssigned() { return assignment.isAssigned(); } @Nullable public PersistentTaskState getState() { return state; } @Override public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params xParams) throws IOException { builder.startObject(); { builder.field("id", id); builder.startObject("task"); { builder.startObject(taskName); { if (params != null) { builder.field("params", params, xParams); } if (state != null) { builder.field("state", state, xParams); } } builder.endObject(); } builder.endObject(); if (API_CONTEXT.equals(xParams.param(Metadata.CONTEXT_MODE_PARAM, API_CONTEXT))) { // These are transient values that shouldn't be persisted to gateway cluster state or snapshot builder.field("allocation_id", allocationId); builder.startObject("assignment"); { builder.field("executor_node", assignment.executorNode); builder.field("explanation", assignment.explanation); } builder.endObject(); if (allocationIdOnLastStatusUpdate != null) { builder.field("allocation_id_on_last_status_update", allocationIdOnLastStatusUpdate); } } } builder.endObject(); return builder; } @Override public boolean isFragment() { return false; } } private static class TaskBuilder { private String id; private long allocationId; private String taskName; private Params params; private PersistentTaskState state; private Assignment assignment = INITIAL_ASSIGNMENT; private Long allocationIdOnLastStatusUpdate; public TaskBuilder setId(String id) { this.id = id; return this; } public TaskBuilder setAllocationId(long allocationId) { this.allocationId = allocationId; return this; } public TaskBuilder setTaskName(String taskName) { this.taskName = taskName; return this; } public TaskBuilder setParams(Params params) { this.params = params; return this; } public TaskBuilder setState(PersistentTaskState state) { this.state = state; return this; } public TaskBuilder setAssignment(Assignment assignment) { this.assignment = assignment; return this; } public TaskBuilder setAllocationIdOnLastStatusUpdate(Long allocationIdOnLastStatusUpdate) { this.allocationIdOnLastStatusUpdate = allocationIdOnLastStatusUpdate; return this; } public PersistentTask build() { return new PersistentTask<>(id, allocationId, taskName, params, state, assignment, allocationIdOnLastStatusUpdate); } } @Override public String getWriteableName() { return TYPE; } public PersistentTasksCustomMetadata(StreamInput in) throws IOException { lastAllocationId = in.readLong(); tasks = in.readMap(StreamInput::readString, PersistentTask::new); } @Override public void writeTo(StreamOutput out) throws IOException { out.writeLong(lastAllocationId); Map> filteredTasks = tasks.values() .stream() .filter(t -> ClusterState.FeatureAware.shouldSerialize(out, t.getParams())) .collect(Collectors.toMap(PersistentTask::getId, Function.identity())); out.writeMap(filteredTasks, StreamOutput::writeString, (stream, value) -> value.writeTo(stream)); } public static NamedDiff readDiffFrom(StreamInput in) throws IOException { return readDiffFrom(Metadata.Custom.class, TYPE, in); } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.field("last_allocation_id", lastAllocationId); builder.startArray("tasks"); { for (PersistentTask entry : tasks.values()) { entry.toXContent(builder, params); } } builder.endArray(); return builder; } public static Builder builder() { return new Builder(); } public static Builder builder(PersistentTasksCustomMetadata tasks) { return new Builder(tasks); } public static class Builder { private final Map> tasks = new HashMap<>(); private long lastAllocationId; private boolean changed; private Builder() {} private Builder(PersistentTasksCustomMetadata tasksInProgress) { if (tasksInProgress != null) { tasks.putAll(tasksInProgress.tasks); lastAllocationId = tasksInProgress.lastAllocationId; } else { lastAllocationId = 0; } } public long getLastAllocationId() { return lastAllocationId; } private Builder setLastAllocationId(long currentId) { this.lastAllocationId = currentId; return this; } private Builder setTasks(List> tasks) { for (TaskBuilder builder : tasks) { PersistentTask task = builder.build(); this.tasks.put(task.getId(), task); } return this; } private long getNextAllocationId() { lastAllocationId++; return lastAllocationId; } /** * Adds a new task to the builder *

* After the task is added its id can be found by calling {{@link #getLastAllocationId()}} method. */ public Builder addTask(String taskId, String taskName, Params params, Assignment assignment) { changed = true; PersistentTask previousTask = tasks.put( taskId, new PersistentTask<>(taskId, taskName, params, getNextAllocationId(), assignment) ); if (previousTask != null) { throw new ResourceAlreadyExistsException("Trying to override task with id {" + taskId + "}"); } return this; } /** * Reassigns the task to another node */ public Builder reassignTask(String taskId, Assignment assignment) { PersistentTask taskInProgress = tasks.get(taskId); if (taskInProgress != null) { changed = true; tasks.put(taskId, new PersistentTask<>(taskInProgress, getNextAllocationId(), assignment)); } else { throw new ResourceNotFoundException("cannot reassign task with id {" + taskId + "}, the task no longer exists"); } return this; } /** * Updates the task state */ public Builder updateTaskState(final String taskId, final PersistentTaskState taskState) { PersistentTask taskInProgress = tasks.get(taskId); if (taskInProgress != null) { changed = true; tasks.put(taskId, new PersistentTask<>(taskInProgress, taskState)); } else { throw new ResourceNotFoundException("cannot update task with id {" + taskId + "}, the task no longer exists"); } return this; } /** * Removes the task */ public Builder removeTask(String taskId) { if (tasks.remove(taskId) != null) { changed = true; } else { throw new ResourceNotFoundException("cannot remove task with id {" + taskId + "}, the task no longer exists"); } return this; } /** * Checks if the task is currently present in the list */ public boolean hasTask(String taskId) { return tasks.containsKey(taskId); } /** * Checks if the task is currently present in the list and has the right allocation id */ public boolean hasTask(String taskId, long allocationId) { PersistentTask taskInProgress = tasks.get(taskId); if (taskInProgress != null) { return taskInProgress.getAllocationId() == allocationId; } return false; } Set getCurrentTaskIds() { return tasks.keySet(); } /** * Returns true if any the task list was changed since the builder was created */ public boolean isChanged() { return changed; } public PersistentTasksCustomMetadata build() { return new PersistentTasksCustomMetadata(lastAllocationId, Collections.unmodifiableMap(tasks)); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy