package com.neverwinterdp.vm.environment.yarn;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.yarn.api.ApplicationConstants;
import org.apache.hadoop.yarn.api.ApplicationConstants.Environment;
import org.apache.hadoop.yarn.api.protocolrecords.AllocateResponse;
import org.apache.hadoop.yarn.api.protocolrecords.RegisterApplicationMasterResponse;
import org.apache.hadoop.yarn.api.records.Container;
import org.apache.hadoop.yarn.api.records.ContainerExitStatus;
import org.apache.hadoop.yarn.api.records.ContainerLaunchContext;
import org.apache.hadoop.yarn.api.records.ContainerState;
import org.apache.hadoop.yarn.api.records.ContainerStatus;
import org.apache.hadoop.yarn.api.records.FinalApplicationStatus;
import org.apache.hadoop.yarn.api.records.NodeReport;
import org.apache.hadoop.yarn.api.records.Priority;
import org.apache.hadoop.yarn.api.records.Resource;
import org.apache.hadoop.yarn.client.api.AMRMClient;
import org.apache.hadoop.yarn.client.api.NMClient;
import org.apache.hadoop.yarn.client.api.async.AMRMClientAsync;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.exceptions.YarnException;
import org.apache.hadoop.yarn.util.Apps;
import org.apache.hadoop.yarn.util.Records;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.mycila.jmx.annotation.JmxBean;
import com.neverwinterdp.vm.VMConfig;
@Singleton
@JmxBean("role=vm-manager, type=YarnManager, name=YarnManager")
public class YarnManager {
private Logger logger = LoggerFactory.getLogger(YarnManager.class.getName());
private VMConfig vmConfig ;
private Map<String, String> yarnConfig;
private Configuration conf ;
private AMRMClient<ContainerRequest> amrmClient ;
private AMRMClientAsync<ContainerRequest> amrmClientAsync ;
private NMClient nmClient;
private ContainerRequestQueue containerRequestQueue = new ContainerRequestQueue ();
public Map<String, String> getYarnConfig() { return this.yarnConfig ; }
public AMRMClient<ContainerRequest> getAMRMClient() { return this.amrmClient ; }
public NMClient getNMClient() { return this.nmClient ; }
@Inject
public void onInit(VMConfig vmConfig) throws Exception {
logger.info("Start init(VMConfig vmConfig)");
this.vmConfig = vmConfig;
try {
this.yarnConfig = vmConfig.getHadoopProperties();
conf = new YarnConfiguration() ;
vmConfig.overrideHadoopConfiguration(conf);
amrmClient = AMRMClient.createAMRMClient();
amrmClientAsync = AMRMClientAsync.createAMRMClientAsync(amrmClient, 1000, new AMRMCallbackHandler());
amrmClientAsync.init(conf);
amrmClientAsync.start();
nmClient = NMClient.createNMClient();
nmClient.init(conf);
nmClient.start();
// Register with RM
String appHostName = InetAddress.getLocalHost().getHostAddress() ;
RegisterApplicationMasterResponse registerResponse = amrmClientAsync.registerApplicationMaster(appHostName, 0, "");
System.out.println("amrmClientAsync.registerApplicationMaster");
} catch(Throwable t) {
logger.error("Error: " , t);
t.printStackTrace();
}
logger.info("Finish init(VMConfig vmConfig)");
}
public void onDestroy() {
logger.info("Start onDestroy()");
try {
if(amrmClientAsync != null) {
amrmClientAsync.unregisterApplicationMaster(FinalApplicationStatus.SUCCEEDED, "", "");
amrmClientAsync.stop();
amrmClientAsync.close();
}
if(nmClient != null) {
nmClient.stop();
nmClient.close();
}
} catch(Exception ex) {
logger.error("Cannot destroy YarnManager properly", ex);
}
logger.info("Finish onDestroy()");
}
public ContainerRequest createContainerRequest(int priority, int numOfCores, int memory) {
//Priority for worker containers - priorities are intra-application
Priority containerPriority = Priority.newInstance(priority);
//Resource requirements for worker containers
Resource resource = Resource.newInstance(memory, numOfCores);
ContainerRequest containerReq = new ContainerRequest(resource, null /* hosts*/, null /*racks*/, containerPriority);
return containerReq;
}
public void asyncAdd(ContainerRequest containerReq, ContainerRequestCallback callback) {
logger.info("Start asyncAdd(ContainerRequest containerReq, ContainerRequestCallback callback)");
System.err.println("Start asyncAdd(ContainerRequest containerReq, ContainerRequestCallback callback)");
System.err.println(" container request hash code = " + containerReq.hashCode());
containerReq.setCallback(callback);
containerRequestQueue.offer(containerReq);
amrmClientAsync.addContainerRequest(containerReq);
System.err.println("Finish asyncAdd(ContainerRequest containerReq, ContainerRequestCallback callback)");
logger.info("Finish asyncAdd(ContainerRequest containerReq, ContainerRequestCallback callback)");
}
public List<Container> getAllocatedContainers() throws YarnException, IOException {
AllocateResponse response = amrmClient.allocate(0);
return response.getAllocatedContainers() ;
}
public void startContainer(Container container, VMConfig appVMConfig) throws YarnException, IOException {
String command = appVMConfig.buildCommand();
ContainerLaunchContext ctx = Records.newRecord(ContainerLaunchContext.class);
if(vmConfig.getVmResources().size() > 0) {
appVMConfig.getVmResources().putAll(vmConfig.getVmResources());
VMResources vmResources = new VMResources(conf, appVMConfig);
ctx.setLocalResources(vmResources);
}
Map<String, String> appEnv = new HashMap<String, String>();
boolean miniClusterEnv = vmConfig.getEnvironment() == VMConfig.Environment.YARN_MINICLUSTER;
setupAppClasspath(miniClusterEnv , conf, appEnv);
ctx.setEnvironment(appEnv);
StringBuilder sb = new StringBuilder();
List<String> commands = Collections.singletonList(
sb.append(command).
append(" 1> ").append(ApplicationConstants.LOG_DIR_EXPANSION_VAR).append("/stdout").
append(" 2> ").append(ApplicationConstants.LOG_DIR_EXPANSION_VAR).append("/stderr").toString()
);
ctx.setCommands(commands);
nmClient.startContainer(container, ctx);
//TODO: update vm descriptor status
}
void setupAppClasspath(boolean miniClusterEnv, Configuration conf, Map<String, String> appMasterEnv) {
if(miniClusterEnv) {
String cps = System.getProperty("java.class.path") ;
String[] cp = cps.split(":") ;
for(String selCp : cp) {
Apps.addToEnvironment(appMasterEnv, Environment.CLASSPATH.name(), selCp, ":");
}
} else {
StringBuilder classPathEnv = new StringBuilder();
classPathEnv.append(Environment.CLASSPATH.$()).append(File.pathSeparatorChar);
classPathEnv.append("./*");
String[] classpath = conf.getStrings(
YarnConfiguration.YARN_APPLICATION_CLASSPATH,
YarnConfiguration.DEFAULT_YARN_APPLICATION_CLASSPATH
) ;
for (String selClasspath : classpath) {
classPathEnv.append(File.pathSeparatorChar);
classPathEnv.append(selClasspath.trim());
}
appMasterEnv.put(Environment.CLASSPATH.name(), classPathEnv.toString());
System.err.println("CLASSPATH: " + classPathEnv);
}
Apps.addToEnvironment(appMasterEnv, Environment.CLASSPATH.name(), Environment.PWD.$() + File.separator + "*", ":");
}
class AMRMCallbackHandler implements AMRMClientAsync.CallbackHandler {
public void onContainersCompleted(List<ContainerStatus> statuses) {
logger.info("Start onContainersCompleted(List<ContainerStatus> statuses)");
for (ContainerStatus status: statuses) {
assert (status.getState() == ContainerState.COMPLETE);
int exitStatus = status.getExitStatus();
//TODO: update vm descriptor status
if (exitStatus != ContainerExitStatus.SUCCESS) {
} else {
}
}
logger.info("Finish onContainersCompleted(List<ContainerStatus> statuses)");
}
public void onContainersAllocated(List<Container> containers) {
logger.info("Start onContainersAllocated(List<Container> containers)");
System.err.println("Start onContainersAllocated(List<Container> containers)");
//TODO: review on allocated container code
Container container = containers.get(0) ;
ContainerRequest containerReq = containerRequestQueue.take(container);
if(containerReq ==null) {
//TODO: research on this issue
//http://hadoop.apache.org/docs/r2.6.0/api/org/apache/hadoop/yarn/client/api/AMRMClient.html#removeContainerRequest(T)
return;
}
System.err.println(" container request hash code = " + containerReq.hashCode());
containerReq.getCallback().onAllocate(YarnManager.this, containerReq, container);
amrmClientAsync.removeContainerRequest(containerReq);
// for (int i = 0; i < containers.size(); i++) {
// System.err.println(" container " + i);
// Container container = containers.get(i) ;
// ContainerRequestCallback callback = containerRequestQueue.take(container);
// callback.onAllocate(YarnManager.this, container);
// amrmClientAsync.removeContainerRequest(callback.getContainerRequest());
// }
System.err.println("Finish onContainersAllocated(List<Container> containers)");
logger.info("Finish onContainersAllocated(List<Container> containers)");
}
public void onNodesUpdated(List<NodeReport> updated) {
}
public void onError(Throwable e) {
amrmClientAsync.stop();
}
public void onShutdownRequest() {
//TODO: handle shutdown request
}
public float getProgress() { return 0; }
}
class ContainerRequestQueue {
private List<ContainerRequest> queues = new ArrayList<>();
synchronized public void offer(ContainerRequest request) {
queues.add(request);
}
synchronized public ContainerRequest take(Container container) {
ContainerRequest containerReq = null ;
System.err.println(" take for container " + container) ;
System.err.println(" callback in queues " + queues.size()) ;
int cpuCores = container.getResource().getVirtualCores();
int memory = container.getResource().getMemory();
System.err.println(" container allocate cpu " + cpuCores) ;
System.err.println(" container allocate memory " + memory) ;
for(int i = 0; i < queues.size(); i++) {
ContainerRequest sel = queues.get(i);
System.err.println(" check container request cpu = " + sel.getCapability().getVirtualCores()) ;
if(cpuCores < sel.getCapability().getVirtualCores()) continue;
System.err.println(" check container request memory = " + sel.getCapability().getMemory()) ;
if(memory < sel.getCapability().getMemory()) continue;
if(containerReq == null) {
containerReq = sel;
} else {
int callbackMemory = containerReq.getCapability().getMemory();
int callbackCpuCores = containerReq.getCapability().getVirtualCores();
//Select closest match memory and cpu cores requirement
if(sel.getCapability().getMemory() < callbackMemory &&
sel.getCapability().getVirtualCores() < callbackCpuCores) {
containerReq = sel;
continue;
}
if(sel.getCapability().getVirtualCores() < callbackCpuCores) {
containerReq = sel;
continue;
}
if(sel.getCapability().getMemory() < callbackMemory) {
containerReq = sel;
continue;
}
}
}
if(containerReq != null) queues.remove(containerReq);
return containerReq;
}
}
static public class ContainerRequest extends org.apache.hadoop.yarn.client.api.AMRMClient.ContainerRequest {
static public AtomicLong idTracker = new AtomicLong() ;
private long id ;
private ContainerRequestCallback callback ;
public ContainerRequest(Resource capability, String[] nodes, String[] racks, Priority priority) {
super(capability, nodes, racks, priority);
id = idTracker.getAndIncrement();
}
public long getId() { return id; }
public ContainerRequestCallback getCallback() { return callback; }
public void setCallback(ContainerRequestCallback callback) {
this.callback = callback;
}
}
static public interface ContainerRequestCallback {
public void onAllocate(YarnManager manager, ContainerRequest request, Container container) ;
}
}