/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.twill.internal;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.apache.twill.api.LocalFile;
import org.apache.twill.api.RunId;
import org.apache.twill.api.RuntimeSpecification;
import org.apache.twill.filesystem.Location;
import org.apache.twill.internal.state.Message;
import org.apache.twill.internal.state.StateNode;
import org.apache.twill.launcher.TwillLauncher;
import org.apache.twill.zookeeper.NodeData;
import org.apache.twill.zookeeper.ZKClient;
import org.apache.twill.zookeeper.ZKOperations;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* This class helps launching a container.
*/
public final class TwillContainerLauncher {
private static final Logger LOG = LoggerFactory.getLogger(TwillContainerLauncher.class);
private static final double HEAP_MIN_RATIO = 0.7d;
private final RuntimeSpecification runtimeSpec;
private final ProcessLauncher.PrepareLaunchContext launchContext;
private final ZKClient zkClient;
private final int instanceCount;
private final String jvmOpts;
private final int reservedMemory;
private final Location secureStoreLocation;
public TwillContainerLauncher(RuntimeSpecification runtimeSpec, ProcessLauncher.PrepareLaunchContext launchContext,
ZKClient zkClient, int instanceCount, String jvmOpts, int reservedMemory,
Location secureStoreLocation) {
this.runtimeSpec = runtimeSpec;
this.launchContext = launchContext;
this.zkClient = zkClient;
this.instanceCount = instanceCount;
this.jvmOpts = jvmOpts;
this.reservedMemory = reservedMemory;
this.secureStoreLocation = secureStoreLocation;
}
public TwillContainerController start(RunId runId, int instanceId, Class<?> mainClass, String classPath) {
ProcessLauncher.PrepareLaunchContext.AfterResources afterResources = null;
ProcessLauncher.PrepareLaunchContext.ResourcesAdder resourcesAdder = null;
// Clean up zookeeper path in case this is a retry and there are old messages and state there.
Futures.getUnchecked(ZKOperations.ignoreError(
ZKOperations.recursiveDelete(zkClient, "/" + runId), KeeperException.NoNodeException.class, null));
// Adds all file to be localized to container
if (!runtimeSpec.getLocalFiles().isEmpty()) {
resourcesAdder = launchContext.withResources();
for (LocalFile localFile : runtimeSpec.getLocalFiles()) {
afterResources = resourcesAdder.add(localFile);
}
}
// Optionally localize secure store.
try {
if (secureStoreLocation != null && secureStoreLocation.exists()) {
if (resourcesAdder == null) {
resourcesAdder = launchContext.withResources();
}
afterResources = resourcesAdder.add(new DefaultLocalFile(Constants.Files.CREDENTIALS,
secureStoreLocation.toURI(),
secureStoreLocation.lastModified(),
secureStoreLocation.length(), false, null));
}
} catch (IOException e) {
LOG.warn("Failed to launch container with secure store {}.", secureStoreLocation.toURI());
}
if (afterResources == null) {
afterResources = launchContext.noResources();
}
int memory = runtimeSpec.getResourceSpecification().getMemorySize();
if (((double) (memory - reservedMemory) / memory) >= HEAP_MIN_RATIO) {
// Reduce -Xmx by the reserved memory size.
memory = runtimeSpec.getResourceSpecification().getMemorySize() - reservedMemory;
} else {
// If it is a small VM, just discount it by the min ratio.
memory = (int) Math.ceil(memory * HEAP_MIN_RATIO);
}
// Currently no reporting is supported for runnable containers
ProcessController<Void> processController = afterResources
.withEnvironment()
.add(EnvKeys.TWILL_RUN_ID, runId.getId())
.add(EnvKeys.TWILL_RUNNABLE_NAME, runtimeSpec.getName())
.add(EnvKeys.TWILL_INSTANCE_ID, Integer.toString(instanceId))
.add(EnvKeys.TWILL_INSTANCE_COUNT, Integer.toString(instanceCount))
.withCommands()
.add("java",
"-Djava.io.tmpdir=tmp",
"-Dyarn.container=$" + EnvKeys.YARN_CONTAINER_ID,
"-Dtwill.runnable=$" + EnvKeys.TWILL_APP_NAME + ".$" + EnvKeys.TWILL_RUNNABLE_NAME,
"-cp", Constants.Files.LAUNCHER_JAR + ":" + classPath,
"-Xmx" + memory + "m",
jvmOpts,
TwillLauncher.class.getName(),
Constants.Files.CONTAINER_JAR,
mainClass.getName(),
Boolean.TRUE.toString())
.redirectOutput(Constants.STDOUT).redirectError(Constants.STDERR)
.launch();
TwillContainerControllerImpl controller = new TwillContainerControllerImpl(zkClient, runId, processController);
controller.start();
return controller;
}
private static final class TwillContainerControllerImpl extends AbstractZKServiceController
implements TwillContainerController {
private final ProcessController<Void> processController;
protected TwillContainerControllerImpl(ZKClient zkClient, RunId runId,
ProcessController<Void> processController) {
super(runId, zkClient);
this.processController = processController;
}
@Override
protected void doStartUp() {
// No-op
}
@Override
protected void doShutDown() {
// No-op
}
@Override
protected void instanceNodeUpdated(NodeData nodeData) {
// No-op
}
@Override
protected void instanceNodeFailed(Throwable cause) {
// No-op
}
@Override
protected void stateNodeUpdated(StateNode stateNode) {
// No-op
}
@Override
public ListenableFuture<Message> sendMessage(Message message) {
return sendMessage(message, message);
}
@Override
public synchronized void completed(int exitStatus) {
if (exitStatus != 0) { // If a container terminated with exit code != 0, treat it as error
// fireStateChange(new StateNode(State.FAILED, new StackTraceElement[0]));
}
forceShutDown();
}
@Override
public void kill() {
processController.cancel();
}
}
}