/*
* Copyright © 2014-2016 Cask Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package co.cask.cdap.internal.app.runtime.schedule;
import co.cask.cdap.api.app.ApplicationSpecification;
import co.cask.cdap.api.schedule.ScheduleSpecification;
import co.cask.cdap.app.runtime.ProgramController;
import co.cask.cdap.app.runtime.ProgramRuntimeService;
import co.cask.cdap.app.store.Store;
import co.cask.cdap.common.ApplicationNotFoundException;
import co.cask.cdap.common.ProgramNotFoundException;
import co.cask.cdap.internal.UserErrors;
import co.cask.cdap.internal.UserMessages;
import co.cask.cdap.internal.app.runtime.AbstractListener;
import co.cask.cdap.internal.app.runtime.ProgramOptionConstants;
import co.cask.cdap.internal.app.services.ProgramLifecycleService;
import co.cask.cdap.internal.app.services.PropertiesResolver;
import co.cask.cdap.proto.Id;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import org.apache.twill.common.Threads;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import javax.annotation.Nullable;
/**
* Task runner that runs a schedule.
*/
public final class ScheduleTaskRunner {
private final ProgramLifecycleService lifecycleService;
private final Store store;
private final ListeningExecutorService executorService;
private final PropertiesResolver propertiesResolver;
private final RunConstraintsChecker requirementsChecker;
public ScheduleTaskRunner(Store store, ProgramLifecycleService lifecycleService,
PropertiesResolver propertiesResolver, ListeningExecutorService taskExecutor) {
this.store = store;
this.lifecycleService = lifecycleService;
this.propertiesResolver = propertiesResolver;
this.executorService = taskExecutor;
this.requirementsChecker = new RunConstraintsChecker(store);
}
/**
* Checks if all schedule requirements are satisfied,
* then executes the given program without blocking until its completion.
*
* @param programId Program Id
* @param systemOverrides Arguments that would be supplied as system runtime arguments for the program.
* @param userOverrides Arguments to add to the user runtime arguments for the program.
* @return a {@link ListenableFuture} object that completes when the program completes
* @throws TaskExecutionException if program is already running or program is not found.
* @throws IOException if program failed to start.
*/
public ListenableFuture<?> run(Id.Program programId, Map<String, String> systemOverrides,
Map<String, String> userOverrides) throws Exception {
Map<String, String> userArgs = Maps.newHashMap();
Map<String, String> systemArgs = Maps.newHashMap();
String scheduleName = systemOverrides.get(ProgramOptionConstants.SCHEDULE_NAME);
ApplicationSpecification appSpec = store.getApplication(programId.getApplication());
if (appSpec == null || appSpec.getSchedules().get(scheduleName) == null) {
throw new TaskExecutionException(String.format(UserMessages.getMessage(UserErrors.PROGRAM_NOT_FOUND), programId),
false);
}
ScheduleSpecification spec = appSpec.getSchedules().get(scheduleName);
if (!requirementsChecker.checkSatisfied(programId, spec.getSchedule())) {
return Futures.<Void>immediateFuture(null);
}
// Schedule properties are overriden by resolved preferences
userArgs.putAll(spec.getProperties());
userArgs.putAll(propertiesResolver.getUserProperties(programId));
userArgs.putAll(userOverrides);
systemArgs.putAll(propertiesResolver.getSystemProperties(programId));
systemArgs.putAll(systemOverrides);
return execute(programId, systemArgs, userArgs);
}
/**
* Executes a program without blocking until its completion.
*
* @return a {@link ListenableFuture} object that completes when the program completes
*/
private ListenableFuture<?> execute(final Id.Program id, Map<String, String> sysArgs,
Map<String, String> userArgs) throws Exception {
ProgramRuntimeService.RuntimeInfo runtimeInfo;
try {
runtimeInfo = lifecycleService.start(id.toEntityId(), sysArgs, userArgs, false);
} catch (ProgramNotFoundException | ApplicationNotFoundException e) {
throw new TaskExecutionException(String.format(UserMessages.getMessage(UserErrors.PROGRAM_NOT_FOUND), id),
e, false);
}
final ProgramController controller = runtimeInfo.getController();
final CountDownLatch latch = new CountDownLatch(1);
controller.addListener(new AbstractListener() {
@Override
public void init(ProgramController.State state, @Nullable Throwable cause) {
if (state == ProgramController.State.COMPLETED) {
completed();
}
if (state == ProgramController.State.ERROR) {
error(controller.getFailureCause());
}
}
@Override
public void killed() {
latch.countDown();
}
@Override
public void completed() {
latch.countDown();
}
@Override
public void error(Throwable cause) {
latch.countDown();
}
}, Threads.SAME_THREAD_EXECUTOR);
return executorService.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
latch.await();
return null;
}
});
}
}