/*
* Copyright © 2014 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.service;
import co.cask.cdap.app.program.Program;
import co.cask.cdap.app.program.Programs;
import co.cask.cdap.app.runtime.AbstractProgramRuntimeService;
import co.cask.cdap.app.runtime.ProgramController;
import co.cask.cdap.app.runtime.ProgramOptions;
import co.cask.cdap.common.async.ExecutorUtils;
import co.cask.cdap.common.conf.CConfiguration;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.internal.app.runtime.AbstractListener;
import co.cask.cdap.internal.app.runtime.ProgramRunnerFactory;
import co.cask.cdap.proto.Id;
import co.cask.cdap.proto.InMemoryProgramLiveInfo;
import co.cask.cdap.proto.NotRunningProgramLiveInfo;
import co.cask.cdap.proto.ProgramLiveInfo;
import co.cask.cdap.proto.ProgramType;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Inject;
import org.apache.commons.io.FileUtils;
import org.apache.twill.api.RunId;
import org.apache.twill.common.Threads;
import org.apache.twill.discovery.Discoverable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
*
*/
public final class InMemoryProgramRuntimeService extends AbstractProgramRuntimeService {
private static final Logger LOG = LoggerFactory.getLogger(InMemoryProgramRuntimeService.class);
private final CConfiguration cConf;
@Inject
public InMemoryProgramRuntimeService(ProgramRunnerFactory programRunnerFactory, CConfiguration cConf) {
super(programRunnerFactory);
this.cConf = cConf;
}
@Override
public synchronized RuntimeInfo run(Program program, ProgramOptions options) {
try {
File tmpDir = new File(cConf.get(Constants.CFG_LOCAL_DATA_DIR), cConf.get(Constants.AppFabric.TEMP_DIR));
final File destinationUnpackedJarDir = new File(tmpDir, String.format("%s.%s",
program.getName(), UUID.randomUUID().toString()));
Preconditions.checkState(!destinationUnpackedJarDir.exists());
destinationUnpackedJarDir.mkdirs();
Program bundleJarProgram = Programs.createWithUnpack(program.getJarLocation(), destinationUnpackedJarDir);
RuntimeInfo info = super.run(bundleJarProgram, options);
info.getController().addListener(new AbstractListener() {
@Override
public void stopped() {
try {
FileUtils.deleteDirectory(destinationUnpackedJarDir);
} catch (IOException e) {
LOG.warn("Failed to cleanup temporary program directory {}.", destinationUnpackedJarDir, e);
}
}
@Override
public void error(Throwable cause) {
try {
FileUtils.deleteDirectory(destinationUnpackedJarDir);
} catch (IOException e) {
LOG.warn("Failed to cleanup temporary program directory {}.", destinationUnpackedJarDir, e);
}
}
}, ExecutorUtils.newThreadExecutor(Threads.createDaemonThreadFactory("program-clean-up-%d")));
return info;
} catch (IOException e) {
throw new RuntimeException("Error unpackaging program " + program.getName());
}
}
@Override
public ProgramLiveInfo getLiveInfo(Id.Program programId, ProgramType type) {
if (!isRunning(programId, type)) {
return new NotRunningProgramLiveInfo(programId, type);
}
if (type.equals(ProgramType.SERVICE)) {
Set<String> discoverableNames = new HashSet<String>();
for (Map.Entry<RunId, RuntimeInfo> entry : list(type).entrySet()) {
RuntimeInfo runtimeInfo = entry.getValue();
InMemoryServiceRunner.ServiceProgramController controller = (InMemoryServiceRunner.ServiceProgramController)
runtimeInfo.getController();
List<ProgramController> programControllers = controller.getProgramControllers();
for (ProgramController programController : programControllers) {
List<Discoverable> discoverables = ((InMemoryRunnableProgramController) programController).getDiscoverables();
for (Discoverable discoverable : discoverables) {
discoverableNames.add(discoverable.getName());
}
}
}
return new InMemoryProgramLiveInfo(programId, type, Lists.newArrayList(discoverableNames));
} else {
return new InMemoryProgramLiveInfo(programId, type);
}
}
@Override
protected void shutDown() throws Exception {
stopAllPrograms();
}
private void stopAllPrograms() {
LOG.info("Stopping all running programs.");
List<ListenableFuture<ProgramController>> futures = Lists.newLinkedList();
for (ProgramType type : ProgramType.values()) {
for (Map.Entry<RunId, RuntimeInfo> entry : list(type).entrySet()) {
RuntimeInfo runtimeInfo = entry.getValue();
if (isRunning(runtimeInfo.getProgramId(), type)) {
futures.add(runtimeInfo.getController().stop());
}
}
}
// unchecked because we cannot do much if it fails. We will still shutdown the standalone CDAP instance.
try {
Futures.successfulAsList(futures).get(60, TimeUnit.SECONDS);
LOG.info("All programs have been stopped.");
} catch (ExecutionException e) {
// note this should not happen because we wait on a successfulAsList
LOG.warn("Got exception while waiting for all programs to stop", e.getCause());
} catch (InterruptedException e) {
LOG.warn("Got interrupted exception while waiting for all programs to stop", e);
Thread.currentThread().interrupt();
} catch (TimeoutException e) {
// can't do much more than log it. We still want to exit.
LOG.warn("Timeout while waiting for all programs to stop.");
}
}
}