package io.lumify.yarn;
import com.beust.jcommander.DynamicParameter;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.internal.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.yarn.api.ApplicationConstants;
import org.apache.hadoop.yarn.api.records.*;
import org.apache.hadoop.yarn.client.api.YarnClient;
import org.apache.hadoop.yarn.client.api.YarnClientApplication;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.exceptions.YarnException;
import org.apache.hadoop.yarn.util.ConverterUtils;
import org.apache.hadoop.yarn.util.Records;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
public abstract class ClientBase {
@SuppressWarnings("OctalInteger")
public static final short FILE_PERMISSIONS = (short) 0710;
@Parameter(names = {"-memory", "-mem"}, description = "Memory for each process in MB.")
private int memory = 512;
@Parameter(names = {"-cores"}, description = "Number of virtual cores each process uses.")
private int virtualCores = 1;
@Parameter(names = {"-instances", "-i"}, description = "Number of instances to start.")
private int instances = 1;
@Parameter(names = {"-jar"}, description = "Path to jar.", required = true)
private String jar = null;
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
@DynamicParameter(names = {"-env"}, description = "Environment variable override. (e.g.: -envPATH=/foo:/bar -envLD_LIBRARY_PATH=/baz)")
private Map<String, String> environmentVariableOverrides = new HashMap<>();
protected int run(String[] args) throws Exception {
new JCommander(this, args);
printEnv();
final String myClasspath = System.getProperty("java.class.path");
final String localResourceJarFileName = getAppName() + ".jar";
final File jarPath = new File(jar);
if (!jarPath.isFile()) {
throw new Exception("YARN app must be packaged as a jar file (found path: " + jarPath + ").");
}
System.out.println("Using jar path: " + jarPath);
final String classPathEnv = myClasspath + ":./" + localResourceJarFileName;
System.out.println("Classpath: " + classPathEnv);
final YarnConfiguration conf = new YarnConfiguration();
final FileSystem fs = FileSystem.get(conf);
final Path remotePath = new Path(fs.getHomeDirectory(), getAppName());
final YarnClient yarnClient = createYarnClient(conf);
final YarnClientApplication app = yarnClient.createApplication();
final ContainerLaunchContext amContainer = createContainerLaunchContextRecord(classPathEnv, remotePath);
final Resource capability = createResourceRecord();
final ApplicationSubmissionContext appContext = createApplicationSubmissionContext(app, amContainer, capability);
final ApplicationId appId = appContext.getApplicationId();
amContainer.setLocalResources(createLocalResources(fs, remotePath, localResourceJarFileName, jarPath));
amContainer.setEnvironment(createEnvironment(classPathEnv));
System.out.println("Submitting application " + appId);
yarnClient.submitApplication(appContext);
waitForApplication(yarnClient, appId, 30, TimeUnit.SECONDS);
return 0;
}
protected abstract String getAppName();
private void waitForApplication(YarnClient yarnClient, ApplicationId appId, int time, TimeUnit timeUnit) throws YarnException, IOException, InterruptedException {
Date startTime = new Date();
Date endTime = new Date(startTime.getTime() + timeUnit.toMillis(time));
ApplicationReport appReport = yarnClient.getApplicationReport(appId);
YarnApplicationState appState = appReport.getYarnApplicationState();
while (appState != YarnApplicationState.FINISHED &&
appState != YarnApplicationState.KILLED &&
appState != YarnApplicationState.FAILED &&
appState != YarnApplicationState.RUNNING) {
if (System.currentTimeMillis() > endTime.getTime()) {
break;
}
Thread.sleep(100);
appReport = yarnClient.getApplicationReport(appId);
appState = appReport.getYarnApplicationState();
}
System.out.println("Application " + appId + " state " + appState);
}
private Map<String, String> createEnvironment(String classPathEnv) {
Map<String, String> appMasterEnv = new HashMap<>();
appMasterEnv.putAll(System.getenv());
appMasterEnv.put(ApplicationConstants.Environment.CLASSPATH.name(), classPathEnv);
appMasterEnv.putAll(environmentVariableOverrides);
return appMasterEnv;
}
private Map<String, LocalResource> createLocalResources(FileSystem fs, Path remotePath, String localResourceJarFileName, File jarPath) throws IOException {
Map<String, LocalResource> localResources = new HashMap<>();
addToLocalResources(fs, remotePath, jarPath.getPath(), localResourceJarFileName, localResources, null);
return localResources;
}
private YarnClient createYarnClient(YarnConfiguration conf) {
YarnClient yarnClient = YarnClient.createYarnClient();
yarnClient.init(conf);
yarnClient.start();
return yarnClient;
}
private Resource createResourceRecord() {
Resource capability = Records.newRecord(Resource.class);
capability.setMemory(memory);
capability.setVirtualCores(virtualCores);
return capability;
}
private ContainerLaunchContext createContainerLaunchContextRecord(String classPathEnv, Path remotePath) {
ContainerLaunchContext amContainer = Records.newRecord(ContainerLaunchContext.class);
String command = "${JAVA_HOME}/bin/java"
+ " -Xmx" + memory + "M"
+ " -Djava.net.preferIPv4Stack=true"
+ " -cp " + classPathEnv
+ " " + getApplicationMasterClass().getName()
+ " -memory " + memory
+ " -cores " + virtualCores
+ " -instances " + instances
+ " -appname " + getAppName()
+ " -remotepath " + remotePath
+ " 1>" + ApplicationConstants.LOG_DIR_EXPANSION_VAR + "/stdout"
+ " 2>" + ApplicationConstants.LOG_DIR_EXPANSION_VAR + "/stderr";
System.out.println("Running: " + command);
amContainer.setCommands(Collections.singletonList(command));
return amContainer;
}
protected abstract Class getApplicationMasterClass();
private ApplicationSubmissionContext createApplicationSubmissionContext(YarnClientApplication app, ContainerLaunchContext amContainer, Resource capability) {
final ApplicationSubmissionContext appContext = app.getApplicationSubmissionContext();
appContext.setApplicationName(getAppName());
appContext.setAMContainerSpec(amContainer);
appContext.setResource(capability);
appContext.setQueue("default");
return appContext;
}
private void addToLocalResources(FileSystem fs, Path remotePath, String fileSrcPath, String fileDstPath, Map<String, LocalResource> localResources, String resources) throws IOException {
Path dst = new Path(remotePath, fileDstPath);
if (fileSrcPath == null) {
FSDataOutputStream out = null;
try {
out = FileSystem.create(fs, dst, new FsPermission(FILE_PERMISSIONS));
out.writeUTF(resources);
} finally {
IOUtils.closeQuietly(out);
}
} else {
fs.copyFromLocalFile(new Path(fileSrcPath), dst);
}
FileStatus scFileStatus = fs.getFileStatus(dst);
LocalResource localResource = LocalResource.newInstance(ConverterUtils.getYarnUrlFromURI(dst.toUri()), LocalResourceType.FILE, LocalResourceVisibility.APPLICATION, scFileStatus.getLen(), scFileStatus.getModificationTime());
localResources.put(fileDstPath, localResource);
}
public static void printEnv() {
System.out.println("Environment:");
LinkedList<Map.Entry<String, String>> environmentVariables = Lists.newLinkedList(System.getenv().entrySet());
Collections.sort(environmentVariables, new Comparator<Map.Entry<String, String>>() {
@Override
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
for (Map.Entry<String, String> e : environmentVariables) {
System.out.println(" " + e.getKey() + "=" + e.getValue());
}
}
}