/*
* Copyright (c) 2010-2012 Grid Dynamics Consulting Services, Inc, All Rights Reserved
* http://www.griddynamics.com
*
* This library is free software; you can redistribute it and/or modify it under the terms of
* the Apache License; either
* version 2.0 of the License, or any later version.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.griddynamics.jagger.master;
import com.griddynamics.jagger.coordinator.Coordinator;
import com.griddynamics.jagger.coordinator.NodeContext;
import com.griddynamics.jagger.coordinator.NodeId;
import com.griddynamics.jagger.coordinator.NodeType;
import com.griddynamics.jagger.dbapi.entity.TaskData;
import com.griddynamics.jagger.engine.e1.ProviderUtil;
import com.griddynamics.jagger.engine.e1.collector.testgroup.TestGroupInfo;
import com.griddynamics.jagger.engine.e1.collector.testgroup.TestGroupListener;
import com.griddynamics.jagger.engine.e1.services.JaggerPlace;
import com.griddynamics.jagger.util.Futures;
import com.griddynamics.jagger.util.TimeUtils;
import com.griddynamics.jagger.util.TimeoutsConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.Service;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Responsible for composite task distribution.
*
* @author Mairbek Khadikov
*/
public class CompositeTaskDistributor implements TaskDistributor<CompositeTask> {
private static Logger log = LoggerFactory.getLogger(CompositeTaskDistributor.class);
private TimeoutsConfiguration timeoutsConfiguration;
private DistributorRegistry distributorRegistry;
private TaskIdProvider taskIdProvider;
private TaskExecutionStatusProvider taskExecutionStatusProvider;
public void setDistributorRegistry(DistributorRegistry distributorRegistry) {
this.distributorRegistry = distributorRegistry;
}
public void setTaskIdProvider(TaskIdProvider taskIdProvider) {
this.taskIdProvider = taskIdProvider;
}
@Required
public void setTimeoutsConfiguration(TimeoutsConfiguration timeoutsConfiguration) {
this.timeoutsConfiguration = timeoutsConfiguration;
}
public void setTaskExecutionStatusProvider(TaskExecutionStatusProvider taskExecutionStatusProvider) {
this.taskExecutionStatusProvider = taskExecutionStatusProvider;
}
@Override
public Service distribute(final ExecutorService executor, final String sessionId, final String taskId, final Multimap<NodeType, NodeId> availableNodes, final Coordinator coordinator, final CompositeTask task, final DistributionListener listener, final NodeContext nodeContext) {
log.debug("Composite task {} with id {} distribute configuration started", task, taskId);
Function<CompositableTask, Service> convertToRunnable = new Function<CompositableTask, Service>() {
@Override
@SuppressWarnings("unchecked")
public Service apply(CompositableTask task) {
TaskDistributor taskDistributor = distributorRegistry.getTaskDistributor(task.getClass());
task.setParentTaskId(taskId);
task.setNumber(taskIdProvider.getTaskId());
String childTaskId = taskIdProvider.stringify(task.getNumber());
taskExecutionStatusProvider.setStatus(childTaskId, TaskData.ExecutionStatus.QUEUED);
return taskDistributor.distribute(executor, sessionId, childTaskId, availableNodes, coordinator, task, listener, nodeContext);
}
};
final List<Service> leading = Lists.newLinkedList(Lists.transform(task.getLeading(), convertToRunnable));
final List<Service> attendant = Lists.newLinkedList(Lists.transform(task.getAttendant(), convertToRunnable));
Service serviceToExecute = new AbstractDistributionService(executor) {
final List<Future<State>> leadingTerminateFutures = Lists.newLinkedList();
@Override
protected void run() throws Exception {
try {
taskExecutionStatusProvider.setStatus(taskId, TaskData.ExecutionStatus.IN_PROGRESS);
TestGroupListener compositeTestGroupListener = TestGroupListener.Composer.compose(ProviderUtil
.provideElements(
task.getListeners(),
sessionId,
taskId,
nodeContext,
JaggerPlace.TEST_GROUP_LISTENER
));
TestGroupInfo testGroupInfo = new TestGroupInfo(task, sessionId);
compositeTestGroupListener.onStart(testGroupInfo);
long startTime = System.currentTimeMillis();
List<Future<State>> futures = Lists.newLinkedList();
for (Service service : Iterables.concat(leading, attendant)) {
ListenableFuture<State> future = service.start();
futures.add(future);
}
for (Future<State> future : futures) {
State state = Futures.get(future, timeoutsConfiguration.getWorkloadStartTimeout());
log.debug("Service started with state {}", state);
}
while (isRunning()) {
if (activeLeadingTasks() == 0) {
break;
}
TimeUtils.sleepMillis(500);
}
testGroupInfo.setDuration(System.currentTimeMillis() - startTime);
compositeTestGroupListener.onStop(testGroupInfo);
taskExecutionStatusProvider.setStatus(taskId, TaskData.ExecutionStatus.SUCCEEDED);
} catch (Exception e) {
log.error("Composite task failure: ", e);
taskExecutionStatusProvider.setStatus(taskId, TaskData.ExecutionStatus.FAILED);
throw e;
}
}
private int activeLeadingTasks() {
int result = 0;
Iterator<Service> it = leading.iterator();
while (it.hasNext()) {
Service service = it.next();
if (service.state() == State.TERMINATED || service.state() == State.FAILED) {
log.debug("State {}", service.state());
leadingTerminateFutures.addAll(requestTermination(Collections.singleton(service), true));
it.remove();
} else {
result++;
}
}
return result;
}
@Override
protected void shutDown() throws Exception {
stopAll();
super.shutDown();
}
private void awaitLeading(List<Future<State>> leadingFutures) {
for (Future<State> future : leadingFutures) {
Futures.get(future, timeoutsConfiguration.getWorkloadStopTimeout());
}
}
private List<Future<State>> requestTermination(Iterable<Service> services, boolean leading) {
List<Future<State>> leadingFutures = Lists.newLinkedList();
for (Service service : services) {
if (service.state() == State.FAILED) {
if (leading) {
throw new IllegalStateException("Failed to run child distributor: " + service);
} else {
log.warn("Attendant service {} failed", service);
continue;
}
}
leadingFutures.add(service.stop());
}
return leadingFutures;
}
private void stopAll() {
List<Future<State>> leadingFutures = requestTermination(leading, true);
List<Future<State>> attendantFutures = requestTermination(attendant, false);
leadingFutures.addAll(leadingTerminateFutures);
awaitLeading(leadingFutures);
awaitAttendant(attendantFutures);
}
private void awaitAttendant(List<Future<State>> attendantFutures) {
for (Future<State> future : attendantFutures) {
try {
future.get(timeoutsConfiguration.getWorkloadStopTimeout().getValue(), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
log.warn("Attendant task timeout " + timeoutsConfiguration.getWorkloadStopTimeout().toString(), e);
} catch (InterruptedException e) {
log.warn("Interrupted", e);
} catch (ExecutionException e) {
log.warn("Attendant task failed", e);
}
}
}
@Override
public String toString() {
return CompositeTaskDistributor.class.getName() + " distributor";
}
};
return new ListenableService<CompositeTask>(serviceToExecute, executor, sessionId, taskId, task, listener, Collections.EMPTY_MAP);
}
}