package org.myrobotlab.service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.myrobotlab.cmdline.CmdLine;
import org.myrobotlab.codec.CodecJson;
import org.myrobotlab.codec.CodecUtils;
import org.myrobotlab.framework.Platform;
import org.myrobotlab.framework.ProcessData;
import org.myrobotlab.framework.Service;
import org.myrobotlab.framework.ServiceType;
import org.myrobotlab.framework.Status;
import org.myrobotlab.framework.repo.GitHub;
import org.myrobotlab.framework.repo.Repo;
import org.myrobotlab.framework.repo.ServiceData;
import org.myrobotlab.io.FileIO;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.logging.Logging;
import org.myrobotlab.net.Http;
import org.slf4j.Logger;
import com.google.gson.internal.LinkedTreeMap;
/**
* @author GroG
*
* Agent is responsible for processes in the same way Runtime is
* responsible for Services.
*
* List of responsibilities:
*
* Start an MyRobotLab process with the needed command line parameters.
* This includes classpath /libraries/jar/* and
* java.jni/jna.home=/libraries/native
*
* Memory xms xmx should be adjusted if necessary
*
* Pass command line parameters to new instance or MyRobotLab
*
* Manage testing
*
* Several modes exist - normal = set env and keep process in map, with
* re-directed stdin stdout & stderr streams envOnly = set the correct
* environment then terminate
*
*
* default is start a new process with relayed cmdline and redirect
* stdin stout & stderr streams, terminate if no subprocesses exist
*
* =================================================================== *
* References :
*
* http://www.excelsior-usa.com/articles/java-to-exe.html
*
* possible small wrappers mac / linux / windows
* http://mypomodoro.googlecode
* .com/svn-history/r89/trunk/src/main/java/org
* /mypomodoro/util/Restart.java
*
* http://java.dzone.com/articles/programmatically-restart-java
* http://stackoverflow
* .com/questions/3468987/executing-another-application-from-java
*
*
* TODO - ARMV 6 7 8 ??? -
* http://www.binarytides.com/linux-cpu-information/ - lscpu
*
* Architecture: armv7l Byte Order: Little Endian CPU(s): 4 On-line
* CPU(s) list: 0-3 Thread(s) per core: 1 Core(s) per socket: 1
* Socket(s): 4
*
*
* TODO - soft floating point vs hard floating point readelf -A
* /proc/self/exe | grep Tag_ABI_VFP_args soft = nothing hard =
* Tag_ABI_VFP_args: VFP registers
*
* PACKAGING jsmooth - windows only javafx - 1.76u - more dependencies ?
* http://stackoverflow.com/questions/1967549/java-packaging-tools-
* alternatives-for-jsmooth-launch4j-onejar
*
* TODO classpath order - for quick bleeding edge updates? rsync
* exploded classpath
*
* TODO - check for Java 1.7 or > addShutdownHook check for network
* connectivity TODO - proxy -Dhttp.proxyHost=webproxy
* -Dhttp.proxyPort=80 -Dhttps.proxyHost=webproxy -Dhttps.proxyPort=80
* -Dhttp.proxyUserName="myusername" -Dhttp.proxyPassword="mypassword"
*
* TODO? how to get vm args http:*
* stackoverflow.com/questions/1490869/how-to-get
* -vm-arguments-from-inside-of-java-application http:*
* java.dzone.com/articles/programmatically-restart-java http:*
* stackoverflow.com
* /questions/9911686/getresource-some-jar-returns-null-although
* -some-jar-exists-in-geturls RuntimeMXBean runtimeMxBean =
* ManagementFactory.getRuntimeMXBean(); List<String> arguments =
* runtimeMxBean.getInputArguments();
*
* TODO - on java -jar myrobotlab.jar | make a copy if agent.jar does
* not exist.. if it does then spawn the Agent there ... it would make
* upgrading myrobotlab.jar "trivial" !!!
*
* TO TEST - -agent "-test -logLevel WARN"
*/
public class Agent extends Service {
private static final long serialVersionUID = 1L;
public final static Logger log = LoggerFactory.getLogger(Agent.class);
static HashSet<String> dependencies = new HashSet<String>();
static HashMap<Integer, ProcessData> processes = new HashMap<Integer, ProcessData>();
static List<String> agentJVMArgs = new ArrayList<String>();
static transient SimpleDateFormat formatter = new SimpleDateFormat("yyyy.MM.dd:HH:mm:ss");
static Platform platform = Platform.getLocalInstance();
static CmdLine runtimeArgs;
static String currentBranch = platform.getBranch();
static String currentVersion = platform.getVersion();
// FIXME - all update functionality will need to be moved to Runtime
// it should take parameters such that it will be possible at some point to
// do an update
// from a child process & update the agent :)
static HashSet<String> possibleVersions = new HashSet<String>();
// String lastBranch = null;
// WebGui webAdmin = null; can't have a peer untile nettosphere is part of
// base build
// boolean updateRestartProcesses = false;
static String updateUrl = "http://mrl-bucket-01.s3.amazonaws.com/current/%s";
static String jarUrlTemplate = "http://mrl-bucket-01.s3.amazonaws.com/current/%s/myrobotlab.jar";
static String versionUrlTemplate = "http://mrl-bucket-01.s3.amazonaws.com/current/%s/version.txt";
static String versionsListUrlTemplate = "http://mrl-bucket-01.s3.amazonaws.com/";
static boolean checkRemoteVersions = false;
static String latestRemote;
static Agent agent;
public Agent(String n) {
super(n);
log.info("Agent {} Pid {} is alive", n, Runtime.getPid());
agentJVMArgs = Runtime.getJVMArgs();
if (currentBranch == null) {
currentBranch = platform.getBranch();
}
if (currentVersion == null) {
currentVersion = platform.getVersion();
}
// add my branch
// possibleBranches.add(agentBranch);
if (checkRemoteVersions) {
// TODO - turn into asynchronous call
// so no connection will not lead to an irritating 'wait' timeout
getRemoteBranches();
}
setBranch(currentBranch);
agent = this;
}
// revert ! only 1 global autoUpdate - all processes - not Agent (yet)
static public void autoUpdate(boolean b) {
if (agent != null) {
String name = String.format("%s.timer.processUpdates", agent.getName());
if (b) {
agent.addTask(name, 1000 * 60, "processUpdates");
} else {
agent.purgeTask(name);
}
}
}
static public void startWebGui() {
try {
// no reference at all to WebGui
// look ma no reference !
WebGui webgui = (WebGui) Runtime.create("webAdmin", "WebGui");
// send("webAdmin", "setPort", 8887);
webgui.setPort(8887);
Runtime.start("webAdmin", "WebGui");
} catch (Exception e) {
Logging.logError(e);
}
}
/**
* checks the current branch looks if the verstion.txt has been changed
*
* @throws IOException
* @throws InterruptedException
* @throws URISyntaxException
*/
static synchronized public void processUpdates() throws IOException, URISyntaxException, InterruptedException {
String remoteVersion = getLatestRemoteVersion(currentBranch);
if (remoteVersion == null) {
log.error("checkForUpdates %s is null", currentBranch);
} else {
log.info("found remote version {}", remoteVersion);
File checkIfWeHaveJar = new File(String.format("%s/myrobotlab.%s.jar", currentBranch, remoteVersion));
if (!checkIfWeHaveJar.exists()) {
log.info("downloading remote version {}", remoteVersion);
downloadLatest(currentBranch);
}
for (Integer key : processes.keySet()) {
ProcessData process = processes.get(key);
if (!currentBranch.equals(process.branch)) {
log.info("skipping update of {} because its on branch {}", process.id, process.branch);
continue;
}
if (remoteVersion.equals(process.version)) {
log.info("skipping update of {} {} because its already version {}", process.id, process.name, process.version);
continue;
}
// FIXME - it would be nice to send a SIG_TERM to
// the process before we kill the jvm
// process.process.getOutputStream().write("/Runtime/releaseAll".getBytes());
process.version = remoteVersion;
if (process.isRunning()) {
restart(process.id);
}
}
}
}
/**
*
* @param id
* @throws IOException
* @throws InterruptedException
* @throws URISyntaxException
*/
static public synchronized void restart(Integer id) throws IOException, URISyntaxException, InterruptedException {
// if null or processes.size == 0 then self
if (id == null || processes.size() == 0){
log.info("restarting self");
spawn(Runtime.getGlobalArgs());
terminateSelfOnly();
} else {
log.info("restarting process {}", id);
kill(id);
spawn2(processes.get(id));
}
}
/**
* return a non-running process structure from an existing one with a new id
*
* @param id
* @return
*/
static public ProcessData copy(Integer id) {
if (!processes.containsKey(id)) {
log.error("cannot copy %d does not exist", id);
return null;
}
ProcessData pd = processes.get(id);
ProcessData pd2 = new ProcessData(pd);
pd2.startTs = null;
pd2.stopTs = null;
pd2.id = getNextProcessId();
processes.put(pd2.id, pd2);
if (agent != null) {
agent.broadcastState();
}
return pd2;
}
static public void copyAndStart(Integer id) throws IOException {
// returns a non running copy with new process id
// on the processes list
ProcessData pd2 = copy(id);
spawn2(pd2);
if (agent != null) {
agent.broadcastState();
}
}
static public void downloadLatest(String branch) throws IOException {
String version = getLatestRemoteVersion(branch);
log.info("downloading version {} /{}", version, branch);
byte[] myrobotlabjar = getLatestRemoteJar(branch);
if (myrobotlabjar == null) {
throw new IOException("could not download");
}
log.info("{} bytes", myrobotlabjar.length);
/*
* File archive = new File(String.format("%s/archive", branch));
* archive.mkdirs();
*/
FileOutputStream fos = new FileOutputStream(String.format("%s/myrobotlab.%s.jar", branch, version));
fos.write(myrobotlabjar);
fos.close();
}
static public String formatList(ArrayList<String> args) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < args.size(); ++i) {
log.info(args.get(i));
sb.append(String.format("%s ", args.get(i)));
}
return sb.toString();
}
/**
* gets id from name
*
* @param name
* @return
*/
static public Integer getId(String name) {
for (Integer pid : processes.keySet()) {
if (pid.equals(name)) {
return processes.get(pid).id;
}
}
return null;
}
// FIXME - should just be be saveRemoteJar() - but shouldn't be from
// multiple threads
static public byte[] getLatestRemoteJar(String branch) {
return Http.get(String.format(jarUrlTemplate, branch));
}
static public String getLatestRemoteVersion(String branch) {
byte[] data = Http.get(String.format(versionUrlTemplate, branch));
if (data != null) {
return new String(data);
}
return null;
}
/**
* gets name from id
*
* @param id
* @return
*/
static public String getName(Integer id) {
for (Integer pid : processes.keySet()) {
if (pid.equals(id)) {
return processes.get(pid).name;
}
}
return null;
}
static synchronized public Integer getNextProcessId() {
Integer ret = 0;
for (int i = 0; i < processes.size(); ++i) {
if (!processes.containsKey(ret)) {
return ret;
}
ret += 1;
}
return ret;
}
static public Set<String> getRemoteBranches() {
Set<String> possibleBranches = new HashSet<String>();
try {
// TODO - all http gets use HttpClient static methods and promise
// for asynchronous
// get gitHub's branches
byte[] r = Http.get(GitHub.BRANCHES);
if (r != null) {
String branches = new String(r);
CodecJson decoder = new CodecJson();
// decoder.decodeArray(Branch)
Object[] array = decoder.decodeArray(branches);
for (int i = 0; i < array.length; ++i) {
@SuppressWarnings("unchecked")
LinkedTreeMap<String,String> m = (LinkedTreeMap<String,String>) array[i];
if (m.containsKey("name")) {
possibleBranches.add(m.get("name").toString());
}
}
}
} catch (Exception e) {
Logging.logError(e);
}
return possibleBranches;
}
static synchronized public HashSet<String> getPossibleVersions() {
// clear versions
possibleVersions.clear();
// get local versions
File branchFolder = new File(currentBranch);
if (!branchFolder.isDirectory()) {
log.error("{} not a directory", currentBranch);
} else {
File[] listOfFiles = branchFolder.listFiles();
for (int i = 0; i < listOfFiles.length; ++i) {
File file = listOfFiles[i];
if (!file.isDirectory()) {
if (file.getName().startsWith("myrobotlab.")) {
String version = getFileVersion(file.getName());
if (version != null) {
possibleVersions.add(version);
}
}
}
}
}
// TODO !!! make asynchronous promise !!!!
String remote = getLatestRemoteVersion(currentBranch);
if (remote != null) {
if (!possibleVersions.contains(remote)) {
possibleVersions.add(remote);
if (agent != null) {
agent.invoke("newVersionAvailable", remote);
}
latestRemote = remote;
}
}
return possibleVersions;
}
static public String newVersionAvailable() {
return latestRemote;
}
static public String getFileVersion(String name) {
if (!name.startsWith("myrobotlab.")) {
return null;
}
String[] parts = name.split("\\.");
if (parts.length != 5) {
return null;
}
String version = String.format("%s.%s.%s", parts[1], parts[2], parts[3]);
return version;
}
/**
* get a list of all the processes currently governed by this Agent
*
* @return
*/
static public HashMap<Integer, ProcessData> getProcesses() {
return processes;
}
/*
* - REMOVE only Runtime should install public List<Status> install(String
* fullType) { List<Status> ret = new ArrayList<Status>();
* ret.add(Status.info("install %s", fullType)); try { Repo repo =
* Repo.getLocalInstance();
*
* if (!repo.isServiceTypeInstalled(fullType)) { repo.install(fullType); if
* (repo.hasErrors()) { ret.addAll(repo.getErrors()); }
*
* } else { log.info("installed {}", fullType); } } catch (Exception e) {
* ret.add(Status.error(e)); } return ret; }
*/
static public Integer kill(Integer id) {
if (processes.containsKey(id)) {
if (agent != null) {
agent.info("terminating %s", id);
}
ProcessData process = processes.get(id);
process.process.destroy();
process.state = ProcessData.STATE_STOPPED;
if (process.monitor != null) {
process.monitor.interrupt();
process.monitor = null;
}
// remove(processes.get(name));
if (agent != null) {
agent.info("%s haz beeen terminated", id);
agent.broadcastState();
}
return id;
}
log.warn("%s? no sir, I don't know that punk...", id);
return null;
}
/*
* BAD IDEA - data type ambiguity is a drag public Integer kill(String name)
* { return kill(getId(name)); }
*/
static public void killAll() {
for (Integer id : processes.keySet()) {
kill(id);
}
log.info("no survivors sir...");
if (agent != null) {
agent.broadcastState();
}
}
static public void killAndRemove(Integer id) {
if (processes.containsKey(id)) {
kill(id);
processes.remove(id);
if (agent != null) {
agent.broadcastState();
}
}
}
/**
* list processes
*/
static public String[] lp() {
Object[] objs = processes.keySet().toArray();
String[] pd = new String[objs.length];
for (int i = 0; i < objs.length; ++i) {
Integer id = (Integer) objs[i];
ProcessData p = processes.get(id);
pd[i] = String.format("%d - %s [%s - %s]", id, p.name, p.branch, p.version);
}
return pd;
}
static public Integer publishTerminated(Integer id) {
log.info("terminated %d %s", id, getName(id));
if (!processes.containsKey(id)){
log.error("processes {} not found");
return id;
}
// if you don't fork with Agent allowed to
// exist without instances - then
if (!runtimeArgs.containsKey("-fork")){
// spin through instances - if I'm the only
// thing left - terminate
boolean processesStillRunning = false;
for (ProcessData pd : processes.values()){
if (pd.isRunning()){
processesStillRunning = true;
break;
}
}
if (!processesStillRunning){
shutdown();
}
}
if (agent != null) {
agent.broadcastState();
}
return id;
}
/**
* This is a great idea & test - because we want complete control over
* environment and dependencies - the ability to purge completely - and
* start from the beginning - but it should be in another service and not
* part of the Agent. The 'Test' service could use Agent as a peer
*
* @return
*/
static public List<Status> serviceTest() {
List<Status> ret = new ArrayList<Status>();
// CLEAN FOR TEST METHOD
// FIXME DEPRECATE !!!
// RUNTIME is responsible for running services
// REPO is responsible for possible services
// String[] serviceTypeNames =
// Runtime.getInstance().getServiceTypeNames();
HashSet<String> skipTest = new HashSet<String>();
skipTest.add("org.myrobotlab.service.Runtime");
skipTest.add("org.myrobotlab.service.OpenNi");
/*
* skipTest.add("org.myrobotlab.service.Agent");
* skipTest.add("org.myrobotlab.service.Incubator");
* skipTest.add("org.myrobotlab.service.InMoov"); // just too big and
* complicated at the moment
* skipTest.add("org.myrobotlab.service.Test");
* skipTest.add("org.myrobotlab.service.Cli"); // ?? No ?
*/
long installTime = 0;
Repo repo = Runtime.getInstance().getRepo();
ServiceData serviceData = ServiceData.getLocalInstance();
ArrayList<ServiceType> serviceTypes = serviceData.getServiceTypes();
ret.add(Status.info("serviceTest will test %d services", serviceTypes.size()));
long startTime = System.currentTimeMillis();
ret.add(Status.info("startTime", "%d", startTime));
for (int i = 0; i < serviceTypes.size(); ++i) {
ServiceType serviceType = serviceTypes.get(i);
// TODO - option to disable
if (!serviceType.isAvailable()) {
continue;
}
// serviceType = "org.myrobotlab.service.OpenCV";
if (skipTest.contains(serviceType.getName())) {
log.info("skipping %s", serviceType.getName());
continue;
}
try {
// agent.serviceTest(); // WTF?
// status.addInfo("perparing clean environment for %s",
// serviceType);
// clean environment
// FIXME - optimize clean
// SUPER CLEAN - force .repo to clear !!
// repo.clearRepo();
// less clean but faster
// repo.clearLibraries();
// repo.clearServiceData();
// comment all out for dirty
// install Test dependencies
long installStartTime = System.currentTimeMillis();
repo.install("org.myrobotlab.service.Test");
repo.install(serviceType.getName());
installTime += System.currentTimeMillis() - installStartTime;
// clean test.json part file
// spawn a test - attach to cli - test 1 service end to end
// ,"-invoke", "test","test","org.myrobotlab.service.Clock"
Process process = spawn(
new String[] { "-runtimeName", "testEnv", "-service", "test", "Test", "-logLevel", "WARN", "-noEnv", "-invoke", "test", "test", serviceType.getName() });
process.waitFor();
// destroy - start again next service
// wait for partFile report .. test.json
// NOT NEEDED - foreign process has ended
byte[] data = FileIO.loadPartFile("test.json", 60000);
if (data != null) {
String test = new String(data);
Status testResult = CodecUtils.fromJson(test, Status.class);
if (testResult.isError()) {
ret.add(testResult);
}
} else {
Status.info("could not get results");
}
// destroy env
kill(getId("testEnv"));
} catch (Exception e) {
ret.add(Status.error(e));
continue;
}
}
ret.add(Status.info("installTime", "%d", installTime));
ret.add(Status.info("installTime %d", installTime));
ret.add(Status.info("testTimeMs %d", System.currentTimeMillis() - startTime));
ret.add(Status.info("testTimeMinutes %d", TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - startTime)));
ret.add(Status.info("endTime %d", System.currentTimeMillis()));
try {
FileIO.savePartFile(new File("fullTest.json"), CodecUtils.toJson(ret).getBytes());
} catch (Exception e) {
Logging.logError(e);
}
return ret;
}
static public String setBranch(String branch) {
currentBranch = branch;
if (checkRemoteVersions){
getPossibleVersions();
}
return currentBranch;
}
static public Map<String, String> setEnv(Map<String, String> env) {
Platform platform = Platform.getLocalInstance();
String platformId = platform.getPlatformId();
if (platform.isLinux()) {
String ldPath = String.format("'pwd'/libraries/native:'pwd'/libraries/native/%s:${LD_LIBRARY_PATH}", platformId);
env.put("LD_LIBRARY_PATH", ldPath);
} else if (platform.isMac()) {
String dyPath = String.format("'pwd'/libraries/native:'pwd'/libraries/native/%s:${DYLD_LIBRARY_PATH}", platformId);
env.put("DYLD_LIBRARY_PATH", dyPath);
} else if (platform.isWindows()) {
// this just borks the path in Windows - additionally (unlike Linux) - i don't think you need native code on the PATH
// and Windows does not have a LD_LIBRARY_PATH
// String path = String.format("PATH=%%CD%%\\libraries\\native;PATH=%%CD%%\\libraries\\native\\%s;%%PATH%%", platformId);
// env.put("PATH", path);
// we need to sanitize against a non-ascii username
// work around for Jython bug in 2.7.0...
env.put("APPDATA", "%%CD%%");
} else {
log.error("unkown operating system");
}
return env;
}
static public void shutdown() {
log.info("terminating others");
killAll();
log.info("terminating self ... goodbye...");
Runtime.exit();
}
static public synchronized Process spawn(String[] in) throws IOException, URISyntaxException, InterruptedException {
// runtime vs develop time
String jarPath = null;
if (FileIO.isJar()){
log.info("I am a jar - must be runtime");
// runtime
jarPath = FileIO.getRoot();
} else {
// develop time (post build)
log.info("I am not a jar - must be develop time");
String test = "build/lib/myrobotlab.jar";
File recentlyBuilt = new File(test);
if (!recentlyBuilt.exists()) {
log.error("umm .. I need to start a jar - would you mind building one with build.xml");
log.error("perhaps in the future I can change all the classpaths etc to start an instances with the bin classes - but no time to do that now");
log.error("adios... hope we meet again...");
System.exit(-1);
}
jarPath = new File(test).getAbsolutePath();
}
return spawn(jarPath, in);
}
/**
* Responsibility - This method will always call Runtime. To start Runtime
* correctly environment must correctly be setup
*
* @param jarPath - Absolute path to myrobotlab.jar
* @param in - command line parameters
* @return
* @throws IOException
* @throws URISyntaxException
* @throws InterruptedException
*/
static public synchronized Process spawn(String jarPath, String[] in) throws IOException, URISyntaxException, InterruptedException {
// handle url to path utf-8 crazyness here
// getCodeSource().getLocation().toURI().getPath()
File jarPathDir = new File(jarPath);
// jarPathDir.getAbsolutePath() is absolutely necessary - relative paths will not work
ProcessData pd = new ProcessData(agent, jarPathDir.getAbsolutePath(), in, currentBranch, currentVersion);
CmdLine cmdline = new CmdLine(in);
if (cmdline.hasSwitch("-autoUpdate")) {
autoUpdate(true);
}
log.info(String.format("Agent starting spawn %s", formatter.format(new Date())));
log.info("in args {}", Arrays.toString(in));
// String jvmMemory = "-Xmx2048m -Xms256m";
long totalMemory = Runtime.getTotalPhysicalMemory();
if (totalMemory == 0) {
log.info("could not get total physical memory");
} else {
log.info("total physical memory returned is {} Mb", totalMemory / 1048576);
}
// need to fill it out as best you can before submitting to spawn2
return spawn2(pd);
}
static public synchronized Process spawn2(ProcessData pd) throws IOException {
log.info("============== spawn begin ==============");
String runtimeName = pd.name;
// this needs cmdLine
String[] cmdLine = pd.buildCmdLine();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < cmdLine.length; ++i) {
sb.append(cmdLine[i]);
sb.append(" ");
}
// log.info(String.format("in %s spawning -> [%s]", b.getAbsolutePath(), sb.toString()));
ProcessBuilder builder = new ProcessBuilder(cmdLine);
// setting working directory to wherever the jar is...
String spawnDir = new File(pd.jarPath).getParent();
builder.directory(new File(spawnDir));
log.info(String.format("in %s spawning -> [%s]", spawnDir, sb.toString()));
// environment variables setup
setEnv(builder.environment());
Process process = builder.start();
pd.process = process;
pd.startTs = System.currentTimeMillis();
pd.monitor = new ProcessData.Monitor(pd);
pd.monitor.start();
pd.state = ProcessData.STATE_RUNNING;
if (pd.id == null) {
pd.id = getNextProcessId();
}
if (processes.containsKey(pd.id)) {
if (agent != null) {
agent.info("restarting %d %s", pd.id, pd.name);
}
} else {
if (agent != null) {
agent.info("starting new %d %s", pd.id, pd.name);
}
processes.put(pd.id, pd);
}
// attach our cli to the latest instance
// *** interesting - not processing input/output will block the thread
// in the spawned process ***
// which I assume is the beginning main thread doing a write to std::out
// and it blocking before anything else can happen
log.info("Agent finished spawn {}", formatter.format(new Date()));
if (agent != null) {
Cli cli = Runtime.getCli();
cli.add(runtimeName, process.getInputStream(), process.getOutputStream());
cli.attach(runtimeName);
agent.broadcastState();
}
return process;
}
/**
* DEPRECATE ? spawn2 should do this checking ?
*
* @param id
* @throws IOException
* @throws URISyntaxException
* @throws InterruptedException
*/
static public void start(Integer id) throws IOException, URISyntaxException, InterruptedException {
if (!processes.containsKey(id)) {
log.error("start process %s can not start - process does not exist", id);
return;
}
ProcessData p = processes.get(id);
if (p.isRunning()) {
log.warn("process %s already started", id);
return;
}
spawn2(p);
}
static public void terminateSelfOnly() {
log.info("goodbye .. cruel world");
System.exit(0);
}
static public void update() throws IOException {
Platform platform = Platform.getLocalInstance();
update(platform.getBranch());
}
static public void update(String branch) throws IOException {
log.info("update({})", branch);
// so we need to get the version of the jar contained in the {branch}
// directory ..
FileIO.extract(String.format("%s/myrobotlab.jar", branch), "resource/version.txt", String.format("%s/version.txt", branch));
String currentVersion = FileIO.toString(String.format("%s/version.txt", branch));
if (currentVersion == null) {
log.error("{}/version.txt current version is null", branch);
return;
}
// compare that with the latest http://s3/current/{branch}/version.txt
// and figure
String latestVersion = getLatestRemoteVersion(branch);
if (latestVersion == null) {
log.error("s3 version.txt current version is null", branch);
return;
}
if (!latestVersion.equals(currentVersion)) {
log.info("latest %s > current %s - updating", latestVersion, currentVersion);
downloadLatest(branch);
}
// FIXME - restart processes
// if (updateRestartProcesses) {
// }
}
/**
* This static method returns all the details of the class without it having
* to be constructed. It has description, categories, dependencies, and peer
* definitions.
*
* @return ServiceType - returns all the data
*
*/
static public ServiceType getMetaData() {
ServiceType meta = new ServiceType(Agent.class.getCanonicalName());
meta.addDescription("Agent - responsible for creating the environment and maintaining, tracking and terminating all processes");
meta.addCategory("framework");
meta.setSponsor("GroG");
// meta.addPeer("webadmin", "WebGui", "webgui for the Agent");
return meta;
}
/**
* First method JVM executes when myrobotlab.jar is in jar form.
*
* -agent "-logLevel DEBUG -service webgui WebGui"
*
* @param args
*/
// FIXME - add -help
// TODO - add jvm memory other runtime info
// FIXME - a way to route parameters from command line to Agent vs Runtime -
// the current concept is ok - but it does not work ..
// make it work if necessary prefix everything by -agent-<...>
public static void main(String[] args) {
try {
// System.out.println("Agent.main starting"); - with static args it doesnt really 'start'
// FIXME - I think the basic idea is to have
// parameters route to Agent or to the target instance
// initially I was thinking of having all agent parameters
// in a -agent \"-param1 value1 -param2 value2\" -services gui GUI
// .. instance params
// but that didn't work due to the parsing of CmdLine ...
// need a good solution
// split agent commands from runtime co\mmands
// String[] agentArgs = new String[0];
ArrayList<String> inArgs = new ArrayList<String>();
// -agent \"-params -service ... \" string encoded
runtimeArgs = new CmdLine(args);
if (runtimeArgs.containsKey("-?") || runtimeArgs.containsKey("-h") || runtimeArgs.containsKey("-help") || runtimeArgs.containsKey("--help")) {
Runtime.mainHelp();
return;
}
if (runtimeArgs.containsKey("-version")){
System.out.println(String.format("%s branch %s version %s", platform.getBranch(), platform.getPlatformId(), platform.getVersion()));
return;
}
// -service for Runtime -process a b c d :)
if (runtimeArgs.containsKey("-agent")) {
// List<String> list = runtimeArgs.getArgumentList("-agent");
String tmp = runtimeArgs.getArgument("-agent", 0);
String[] agentPassedArgs = tmp.split(" ");
if (agentPassedArgs.length > 1) {
for (int i = 0; i < agentPassedArgs.length; ++i) {
inArgs.add(agentPassedArgs[i]);
}
} else {
if (tmp != null) {
inArgs.add(tmp);
}
}
}
// default args passed to runtime from Agent
inArgs.add("-isAgent");
// String[] agentArgs = inArgs.toArray(new String[inArgs.size()]);
// CmdLine agentCmd = new CmdLine(agentArgs);
// FIXME -isAgent identifier sent -- default to setting log name to
// agent.log !!!
// Runtime.setRuntimeName("bootstrap");
// Runtime.main(agentArgs);
// Agent agentx = (Agent) Runtime.start("agent", "Agent");
Process p = null;
if (runtimeArgs.containsKey("-test")) {
serviceTest();
} else {
if (!runtimeArgs.containsKey("-fork")){
Runtime.start("agent", "Agent");
}
if (!runtimeArgs.containsKey("-client")){
p = spawn(args); // <-- agent's is now in charge of first
} else {
Runtime.start("cli", "Cli");
}
}
// change of design - agent will try to shutdown
// as soon as the mrl processes starts
if (runtimeArgs.containsKey("-install")) {
p.waitFor();
shutdown();
}
} catch (Exception e) {
log.error("unsuccessful spawn", e);
} finally {
// big hammer
// System.out.println("Agent.main leaving");- with static args it doesnt really 'start'
// System.exit(0);
}
}
}