package cz.cuni.mff.d3s.been.cluster.context; import static cz.cuni.mff.d3s.been.cluster.Names.BENCHMARKS_CONTEXT_ID; import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.hazelcast.core.IMap; import com.hazelcast.core.Instance; import cz.cuni.mff.d3s.been.cluster.Names; import cz.cuni.mff.d3s.been.core.persistence.TaskEntity; import cz.cuni.mff.d3s.been.core.task.*; import cz.cuni.mff.d3s.been.persistence.DAOException; import cz.cuni.mff.d3s.been.persistence.task.PersistentDescriptors; /** * Utility class for operations related to task contexts. * * @author Kuba Brecka */ public class TaskContexts { /** slf4j logger */ private static final Logger log = LoggerFactory.getLogger(TaskContexts.class); /** BEEN cluster connection */ private ClusterContext clusterContext; /** * Package private constructor, creates a new instance that uses the specified * BEEN cluster context. * * @param clusterContext * the cluster context to use */ TaskContexts(ClusterContext clusterContext) { // package private visibility prevents out-of-package instantiation this.clusterContext = clusterContext; } /** * Returns the map which holds task context entries. * * @return the task contexts map */ public IMap<String, TaskContextEntry> getTaskContextsMap() { return clusterContext.getMap(Names.TASK_CONTEXTS_MAP_NAME); } /** * Returns a task context with the specified ID. * * @param id * ID of the task context to retrieve * @return the task context entry with the specified ID or null if no such * task context exists */ public TaskContextEntry getTaskContext(String id) { return getTaskContextsMap().get(id); } /** * Returns a collection of all available task contexts in the Hazelcast map. * * @return all task contexts */ public Collection<TaskContextEntry> getTaskContexts() { return getTaskContextsMap().values(); } /** * Puts/updates the entry about the specified task context in the Hazelcast * map. * * @param entry * the task context entry to put */ public void putContextEntry(TaskContextEntry entry) { getTaskContextsMap().put(entry.getId(), entry); } /** * Submits a context to the cluster. * <p/> * Submit does not create or schedule any tasks. Tasks will be created and * scheduled by the framework some time later. * * @param descriptor * descriptor of a context * @param benchmarkId * ID of the benchmark under which the task context should be created * @return id of the submitted context */ public String submit(TaskContextDescriptor descriptor, String benchmarkId) { TaskContextEntry contextEntry = new TaskContextEntry(); contextEntry.setTaskContextDescriptor(descriptor); contextEntry.setId(UUID.randomUUID().toString()); contextEntry.setBenchmarkId(benchmarkId); contextEntry.setContextState(TaskContextState.WAITING); contextEntry.setCreated(new Date().getTime()); checkContextBeforeSubmit(contextEntry); putContextEntry(contextEntry); log.debug("Task context was submitted with ID {}", contextEntry.getId()); persistContextDescriptor(descriptor, benchmarkId, contextEntry); return contextEntry.getId(); } /** * Stores the specified context descriptor into the persistence layer. * * @param descriptor * the descriptor to persist * @param benchmarkId * the benchmark ID under which the descriptor was submitted * @param contextEntry * the task context entry of the submitted context */ private void persistContextDescriptor(TaskContextDescriptor descriptor, String benchmarkId, TaskContextEntry contextEntry) { final TaskEntity entity = PersistentDescriptors.wrapContextDescriptor( descriptor, null, contextEntry.getId(), benchmarkId); try { clusterContext.getPersistence().asyncPersist(PersistentDescriptors.CONTEXT_DESCRIPTOR, entity); } catch (DAOException e) { log.error("Persisting context descriptor failed.", e); // continues without rethrowing, because the only reason for a DAOException is when // the object cannot be serialized. } } /** * Performs various sanity checks of the task context entry and throws an * exception when an invalid state is found. * * @param contextEntry * the task context entry to check */ private void checkContextBeforeSubmit(TaskContextEntry contextEntry) { TaskContextDescriptor descriptor = contextEntry.getTaskContextDescriptor(); Collection<TaskEntry> entriesToSubmit = getTaskEntries(contextEntry); Map<String, TaskEntry> entries = new HashMap<>(); for (TaskEntry e : entriesToSubmit) { if (e.getTaskDescriptor().getType() != TaskType.TASK) { throw new IllegalArgumentException("Task context contains a TaskDescriptor with a type that is not task."); } TaskDescriptor taskDescriptor = e.getTaskDescriptor(); entries.put(taskDescriptor.getName(), e); } for (Task task : descriptor.getTask()) { if (task.isSetRunAfterTask()) { String runAfter = task.getRunAfterTask(); String taskName = task.getName(); if (taskName.equals(runAfter)) { String msg = String.format("Cannot wait for itself to finish: %s", taskName); throw new IllegalArgumentException(); } if (entries.get(runAfter) == null) { String msg = String.format("No such task to wait for %s", runAfter); throw new IllegalArgumentException(msg); } } } // TODO check for cycles } /** * Submits a single task descriptor in a new task context. Since all tasks * must belong to some task context, this is the preferred way to submit * single tasks. * * @param taskDescriptor * the task descriptor to submit * @return the ID of the newly created task context */ public String submitTaskInNewContext(TaskDescriptor taskDescriptor) { if (taskDescriptor.getType() != TaskType.TASK) { throw new IllegalArgumentException("TaskDescriptor's type is not task."); } TaskContextDescriptor contextDescriptor = new TaskContextDescriptor(); Task taskInTaskContext = new Task(); taskInTaskContext.setName(taskDescriptor.getName()); Descriptor descriptorInTaskContext = new Descriptor(); descriptorInTaskContext.setTaskDescriptor(taskDescriptor); taskInTaskContext.setDescriptor(descriptorInTaskContext); contextDescriptor.getTask().add(taskInTaskContext); return submit(contextDescriptor, null); } /** * Submits a benchmark with the specified task descriptor describing the * benchmark generator task. This method can also be used for resubmitting a * generator task. When resubmitting, specify a 'benchmarkId' parameter and * set it to the ID of the benchmark for which the generator is being * resubmitted. When submitting a new benchmark, set the 'benchmarkId' to the * newly created benchmark entry. * * @param benchmarkTaskDescriptor * generator task descriptor * @param benchmarkId * the ID of the benchmark * @return the ID of the newly submitted task */ public String submitBenchmarkTask(TaskDescriptor benchmarkTaskDescriptor, String benchmarkId) { if (benchmarkTaskDescriptor.getType() != TaskType.BENCHMARK) { throw new IllegalArgumentException("TaskDescriptor's type is not benchmark."); } TaskEntry taskEntry = TaskEntries.create(benchmarkTaskDescriptor, BENCHMARKS_CONTEXT_ID); taskEntry.setBenchmarkId(benchmarkId); String taskId = clusterContext.getTasks().submit(taskEntry); clusterContext.getBenchmarks().addBenchmarkToBenchmarksContext(taskEntry); return taskEntry.getId(); } /** * Creates a collection of task entries from the descriptor in the specified * task context entry. This method will parse the context descriptor and * create tasks from the templates and put these tasks into the Hazelcast map. * * @param taskContextEntry * the task context entry from which the task entries should be * created * @return a collection of the newly creates tasks */ private Collection<TaskEntry> getTaskEntries(TaskContextEntry taskContextEntry) { TaskContextDescriptor descriptor = taskContextEntry.getTaskContextDescriptor(); String contextId = taskContextEntry.getId(); Collection<TaskEntry> entries = new ArrayList<>(); Map<String, String> nameToId = new HashMap<>(); for (Task t : descriptor.getTask()) { TaskDescriptor td; if (t.getDescriptor().isSetTaskDescriptor()) { td = t.getDescriptor().getTaskDescriptor(); } else { String templateName = t.getDescriptor().getFromTemplate(); td = cloneTemplateWithName(descriptor, templateName); } td.setName(t.getName()); setTaskProperties(descriptor, t, td); TaskEntry taskEntry = TaskEntries.create(td, contextId); if (t.isSetRunAfterTask()) { taskEntry.setTaskDependency(t.getRunAfterTask()); } nameToId.put(t.getName(), taskEntry.getId()); entries.add(taskEntry); } // map names to ids for (TaskEntry entry : entries) { if (entry.isSetTaskDependency()) { final String dependsOn = entry.getTaskDependency(); entry.setTaskDependency(nameToId.get(dependsOn)); } } return entries; } /** * Runs the specified context. This will submit all the contained tasks and * set the state of the task context to running. * * @param contextId * the ID of the context to run */ public void runContext(String contextId) { TaskContextEntry contextEntry = getTaskContext(contextId); if (contextEntry == null) { // TODO return; } Collection<TaskEntry> entriesToSubmit = getTaskEntries(contextEntry); // TODO consider transactions for (TaskEntry taskEntry : entriesToSubmit) { taskEntry.setBenchmarkId(contextEntry.getBenchmarkId()); String taskId = clusterContext.getTasks().submit(taskEntry); contextEntry.getContainedTask().add(taskId); log.debug("Task was submitted with ID {}", taskId); } contextEntry.setContextState(TaskContextState.RUNNING); putContextEntry(contextEntry); } /** * Propagates properties set in the context descriptor to the individual task. * * @param descriptor * the context descriptor from which the properties should be * extracted * @param task * the task entry in the context descriptor * @param td * the task descriptor into which the properties should be set */ private void setTaskProperties(TaskContextDescriptor descriptor, Task task, TaskDescriptor td) { HashMap<String, String> properties = new HashMap<>(); if (descriptor.isSetProperties()) { for (Property property : descriptor.getProperties().getProperty()) { properties.put(property.getName(), property.getValue()); } } if (td.isSetProperties()) { for (TaskProperty property : td.getProperties().getProperty()) { properties.put(property.getName(), property.getValue()); } } if (task.isSetProperties()) { for (Property property : task.getProperties().getProperty()) { properties.put(property.getName(), property.getValue()); } } if (!td.isSetProperties()) { td.setProperties(new TaskProperties()); } td.getProperties().getProperty().clear(); for (Map.Entry<String, String> property : properties.entrySet()) { TaskProperty taskProperty = new TaskProperty(); taskProperty.setName(property.getKey()); taskProperty.setValue(property.getValue()); td.getProperties().getProperty().add(taskProperty); } } /** * Clones a task template from a context descriptor. * * @param descriptor * the context descriptor * @param templateName * the name of the template that should be cloned * @return the cloned task descriptor */ private TaskDescriptor cloneTemplateWithName(TaskContextDescriptor descriptor, String templateName) { for (Template t : descriptor.getTemplates().getTemplate()) { if (t.getName().equals(templateName)) { return (TaskDescriptor) t.getTaskDescriptor().clone(); } } throw new IllegalArgumentException(String.format("Cannot find template with name '%s'", templateName)); } /** * Destroys allocated cluster-wide instances (checkpoint map and latches) for * a given TaskContextEntry. * * @param taskContextEntry * entry to clean up */ public void cleanupTaskContext(TaskContextEntry taskContextEntry) { log.debug("Destroying cluster instances for task context {}", taskContextEntry.getId()); // destroy the checkpoint map clusterContext.getMap("checkpointmap_" + taskContextEntry.getId()).destroy(); // destroy latches Collection<Instance> latches = clusterContext.getInstances(Instance.InstanceType.COUNT_DOWN_LATCH); for (Instance instance : latches) { String latchName = instance.getId().toString(); if (latchName.startsWith("d:latch_" + taskContextEntry.getId() + "_")) { instance.destroy(); } } putContextEntry(taskContextEntry); } /** * Removes the task context with the specified ID from Hazelcast map of tasks * contexts. The task context must be in a final state (finished). Also * removes all tasks contained in the context. * * @param taskContextId * ID of the task context to remove */ public void remove(String taskContextId) { TaskContextEntry taskContextEntry = getTaskContext(taskContextId); TaskContextState state = taskContextEntry.getContextState(); if (state == TaskContextState.FINISHED || state == TaskContextState.FAILED) { log.info("Removing task context {} from map.", taskContextId); for (String taskId : taskContextEntry.getContainedTask()) { clusterContext.getTasks().remove(taskId); } getTaskContextsMap().remove(taskContextId); } else { throw new IllegalStateException(String.format( "Trying to remove task context %s, but it's in state %s.", taskContextId, state)); } } /** * Returns a list of all available tasks in the specified task context. * * @param taskContextId * the ID of the task context * @return collection of task entries within the context */ public Collection<TaskEntry> getTasksInTaskContext(String taskContextId) { TaskContextEntry taskContextEntry = getTaskContext(taskContextId); Collection<TaskEntry> result = new ArrayList<>(); for (String taskId : taskContextEntry.getContainedTask()) { TaskEntry entry = clusterContext.getTasks().getTask(taskId); if (entry != null) { result.add(entry); } } return result; } /** * Kills the specified context (i.e. kills all the contained tasks). This * method only submits the "killing" operation and returns immediately. There * is no guarantee about when the tasks or the context will actually be * killed. * * @param taskContextId * the ID of the task context to kill */ public void kill(String taskContextId) { TaskContextEntry taskContextEntry = getTaskContext(taskContextId); TaskContextState state = taskContextEntry.getContextState(); if (state == TaskContextState.FAILED || state == TaskContextState.FINISHED) { throw new IllegalStateException(String.format( "Trying to kill task context %s, but it's in state %s.", taskContextId, state)); } List<String> containedTasks = taskContextEntry.getContainedTask(); for (String taskId : containedTasks) { clusterContext.getTasks().kill(taskId); } } }