/* * 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.deploy; import co.cask.cdap.app.deploy.ConfigResponse; import co.cask.cdap.app.deploy.Configurator; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.common.io.Files; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import java.io.File; /** * SandboxConfigurator spawns a seperate JVM to run configuration of an Application. * <p> * This class is responsible for starting the process of generating configuration * by passing in the JAR file of Application be configured by a seperate JVM. * </p> * * @see InMemoryConfigurator */ public final class SandboxConfigurator implements Configurator { /** * Prefix of temporary file. */ private static final String PREFIX = "app-specification"; /** * Extension of temporary. */ private static final String EXT = ".json"; /** * Name of JAR file. */ private final File jarFilename; /** * Sandbox process. */ private Process process; /** * Constructor. * * @param jarFilename Name of the JAR file. */ public SandboxConfigurator(File jarFilename) { Preconditions.checkNotNull(jarFilename); this.jarFilename = jarFilename; } /** * Helper for simplifying creating {@link SandboxConfigurator}. * * @param jarFilename Name of the file. * @return An instance of {@link ListenableFuture} */ public static ListenableFuture<ConfigResponse> config(File jarFilename) { SandboxConfigurator sc = new SandboxConfigurator(jarFilename); return sc.config(); } /** * Runs the <code>Application.configure()</code> in a sandbox JVM * with high level of security. * * @return An instance of {@link ListenableFuture} */ @Override public ListenableFuture<ConfigResponse> config() { final SettableFuture<ConfigResponse> result = SettableFuture.create(); final File outputFile; try { outputFile = File.createTempFile(PREFIX, EXT); // Run the command in seperate JVM. process = Runtime.getRuntime().exec(getCommand(outputFile)); // Add future to handle the case when the future is cancelled. // OnSuccess, we don't do anything other than cleaning the output. // onFailure, we make sure that process is destroyed. Futures.addCallback( result, new FutureCallback<ConfigResponse>() { private void deleteOutput() { if (outputFile.exists()) { outputFile.delete(); } } @Override public void onSuccess(final ConfigResponse result) { // Delete the output file on delete. deleteOutput(); } @Override public void onFailure(final Throwable t) { // In case the future was cancelled, we have to // destroy the process. if (result.isCancelled()) { process.destroy(); } deleteOutput(); } } ); } catch (Exception e) { // Returns a {@code ListenableFuture} which has an exception set immediately // upon construction. return Futures.immediateFailedFuture(e); } // Start a thread that waits for command execution to complete or till it's cancelled. new Thread() { @Override public void run() { try { // Wait for process to exit and extract the return. If cancelled the process will // be shutdown. process.waitFor(); int exit = process.exitValue(); if (exit == 0) { result.set(new DefaultConfigResponse(0, Files.newReaderSupplier(outputFile, Charsets.UTF_8))); } else { result.set(new DefaultConfigResponse(exit, null)); } } catch (Exception e) { result.setException(e); } } }.start(); return result; } /** * @return Returns the command used to execute configure using <code>outputFile</code> in which * the output of run would be stored. */ private String getCommand(File outputFile) { return String.format( "--jar %s --output %s", jarFilename.getAbsolutePath(), outputFile.getAbsolutePath() ); } }