Initial Commit of the Hyracks Admin Console

git-svn-id: https://hyracks.googlecode.com/svn/branches/hyracks_dev_next@562 123451ca-8445-de46-9d55-352943316053
diff --git a/hyracks-control-cc/pom.xml b/hyracks-control-cc/pom.xml
index f0a13a1..3fed82f 100644
--- a/hyracks-control-cc/pom.xml
+++ b/hyracks-control-cc/pom.xml
@@ -34,7 +34,14 @@
   	<dependency>
   		<groupId>org.eclipse.jetty</groupId>
   		<artifactId>jetty-server</artifactId>
-  		<version>8.0.0.M1</version>
+  		<version>8.0.0.RC0</version>
+  		<type>jar</type>
+  		<scope>compile</scope>
+  	</dependency>
+  	<dependency>
+  		<groupId>org.eclipse.jetty</groupId>
+  		<artifactId>jetty-webapp</artifactId>
+  		<version>8.0.0.RC0</version>
   		<type>jar</type>
   		<scope>compile</scope>
   	</dependency>
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/ClusterControllerService.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/ClusterControllerService.java
index d864c05..cbbef2d 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/ClusterControllerService.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/ClusterControllerService.java
@@ -33,7 +33,6 @@
 
 import edu.uci.ics.hyracks.api.client.ClusterControllerInfo;
 import edu.uci.ics.hyracks.api.client.IHyracksClientInterface;
-import edu.uci.ics.hyracks.api.comm.NetworkAddress;
 import edu.uci.ics.hyracks.api.context.ICCContext;
 import edu.uci.ics.hyracks.api.dataflow.TaskAttemptId;
 import edu.uci.ics.hyracks.api.exceptions.HyracksException;
@@ -66,9 +65,9 @@
 import edu.uci.ics.hyracks.control.common.base.INodeController;
 import edu.uci.ics.hyracks.control.common.context.ServerContext;
 import edu.uci.ics.hyracks.control.common.controllers.CCConfig;
-import edu.uci.ics.hyracks.control.common.controllers.NCConfig;
 import edu.uci.ics.hyracks.control.common.controllers.NodeParameters;
 import edu.uci.ics.hyracks.control.common.controllers.NodeRegistration;
+import edu.uci.ics.hyracks.control.common.heartbeat.HeartbeatData;
 import edu.uci.ics.hyracks.control.common.job.PartitionDescriptor;
 import edu.uci.ics.hyracks.control.common.job.PartitionRequest;
 import edu.uci.ics.hyracks.control.common.job.profiling.om.JobProfile;
@@ -196,9 +195,7 @@
     public NodeParameters registerNode(NodeRegistration reg) throws Exception {
         INodeController nodeController = reg.getNodeController();
         String id = reg.getNodeId();
-        NCConfig ncConfig = reg.getNCConfig();
-        NetworkAddress dataPort = reg.getDataPort();
-        NodeControllerState state = new NodeControllerState(nodeController, ncConfig, dataPort);
+        NodeControllerState state = new NodeControllerState(nodeController, reg);
         jobQueue.scheduleAndSync(new RegisterNodeEvent(this, id, state));
         nodeController.notifyRegistration(this);
         LOGGER.log(Level.INFO, "Registered INodeController: id = " + id);
@@ -259,8 +256,8 @@
     }
 
     @Override
-    public synchronized void nodeHeartbeat(String id) throws Exception {
-        jobQueue.schedule(new NodeHeartbeatEvent(this, id));
+    public synchronized void nodeHeartbeat(String id, HeartbeatData hbData) throws Exception {
+        jobQueue.schedule(new NodeHeartbeatEvent(this, id, hbData));
     }
 
     @Override
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/NodeControllerState.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/NodeControllerState.java
index 2b059b2..21fac2f 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/NodeControllerState.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/NodeControllerState.java
@@ -17,12 +17,19 @@
 import java.util.HashSet;
 import java.util.Set;
 
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import edu.uci.ics.hyracks.api.comm.NetworkAddress;
 import edu.uci.ics.hyracks.api.job.JobId;
 import edu.uci.ics.hyracks.control.common.base.INodeController;
 import edu.uci.ics.hyracks.control.common.controllers.NCConfig;
+import edu.uci.ics.hyracks.control.common.controllers.NodeRegistration;
+import edu.uci.ics.hyracks.control.common.heartbeat.HeartbeatData;
 
 public class NodeControllerState {
+    private static final int RRD_SIZE = 720;
+
     private final INodeController nodeController;
 
     private final NCConfig ncConfig;
@@ -31,17 +38,84 @@
 
     private final Set<JobId> activeJobIds;
 
+    private final String osName;
+
+    private final String arch;
+
+    private final String osVersion;
+
+    private final int nProcessors;
+
+    private final long[] hbTime;
+
+    private final long[] heapInitSize;
+
+    private final long[] heapUsedSize;
+
+    private final long[] heapCommittedSize;
+
+    private final long[] heapMaxSize;
+
+    private final long[] nonheapInitSize;
+
+    private final long[] nonheapUsedSize;
+
+    private final long[] nonheapCommittedSize;
+
+    private final long[] nonheapMaxSize;
+
+    private final int[] threadCount;
+
+    private final int[] peakThreadCount;
+
+    private final double[] systemLoadAverage;
+
+    private int rrdPtr;
+
     private int lastHeartbeatDuration;
 
-    public NodeControllerState(INodeController nodeController, NCConfig ncConfig, NetworkAddress dataPort) {
+    public NodeControllerState(INodeController nodeController, NodeRegistration reg) {
         this.nodeController = nodeController;
-        this.ncConfig = ncConfig;
-        this.dataPort = dataPort;
+        ncConfig = reg.getNCConfig();
+        dataPort = reg.getDataPort();
         activeJobIds = new HashSet<JobId>();
+
+        osName = reg.getOSName();
+        arch = reg.getArch();
+        osVersion = reg.getOSVersion();
+        nProcessors = reg.getNProcessors();
+
+        hbTime = new long[RRD_SIZE];
+        heapInitSize = new long[RRD_SIZE];
+        heapUsedSize = new long[RRD_SIZE];
+        heapCommittedSize = new long[RRD_SIZE];
+        heapMaxSize = new long[RRD_SIZE];
+        nonheapInitSize = new long[RRD_SIZE];
+        nonheapUsedSize = new long[RRD_SIZE];
+        nonheapCommittedSize = new long[RRD_SIZE];
+        nonheapMaxSize = new long[RRD_SIZE];
+        threadCount = new int[RRD_SIZE];
+        peakThreadCount = new int[RRD_SIZE];
+        systemLoadAverage = new double[RRD_SIZE];
+        rrdPtr = 0;
     }
 
-    public void notifyHeartbeat() {
+    public void notifyHeartbeat(HeartbeatData hbData) {
         lastHeartbeatDuration = 0;
+
+        hbTime[rrdPtr] = System.currentTimeMillis();
+        heapInitSize[rrdPtr] = hbData.heapInitSize;
+        heapUsedSize[rrdPtr] = hbData.heapUsedSize;
+        heapCommittedSize[rrdPtr] = hbData.heapCommittedSize;
+        heapMaxSize[rrdPtr] = hbData.heapMaxSize;
+        nonheapInitSize[rrdPtr] = hbData.nonheapInitSize;
+        nonheapUsedSize[rrdPtr] = hbData.nonheapUsedSize;
+        nonheapCommittedSize[rrdPtr] = hbData.nonheapCommittedSize;
+        nonheapMaxSize[rrdPtr] = hbData.nonheapMaxSize;
+        threadCount[rrdPtr] = hbData.threadCount;
+        peakThreadCount[rrdPtr] = hbData.peakThreadCount;
+        systemLoadAverage[rrdPtr] = hbData.systemLoadAverage;
+        rrdPtr = (rrdPtr + 1) % RRD_SIZE;
     }
 
     public int incrementLastHeartbeatDuration() {
@@ -67,4 +141,38 @@
     public NetworkAddress getDataPort() {
         return dataPort;
     }
+
+    public JSONObject toSummaryJSON() throws JSONException {
+        JSONObject o = new JSONObject();
+        o.put("node-id", ncConfig.nodeId);
+        o.put("heap-used", heapUsedSize[(rrdPtr + RRD_SIZE - 1) % RRD_SIZE]);
+        o.put("system-load-average", systemLoadAverage[(rrdPtr + RRD_SIZE - 1) % RRD_SIZE]);
+
+        return o;
+    }
+
+    public JSONObject toDetailedJSON() throws JSONException {
+        JSONObject o = new JSONObject();
+
+        o.put("node-id", ncConfig.nodeId);
+        o.put("os-name", osName);
+        o.put("arch", arch);
+        o.put("os-version", osVersion);
+        o.put("num-processors", nProcessors);
+        o.put("rrd-ptr", rrdPtr);
+        o.put("heartbeat-times", hbTime);
+        o.put("heap-init-sizes", heapInitSize);
+        o.put("heap-used-sizes", heapUsedSize);
+        o.put("heap-committed-sizes", heapCommittedSize);
+        o.put("heap-max-sizes", heapMaxSize);
+        o.put("nonheap-init-sizes", nonheapInitSize);
+        o.put("nonheap-used-sizes", nonheapUsedSize);
+        o.put("nonheap-committed-sizes", nonheapCommittedSize);
+        o.put("nonheap-max-sizes", nonheapMaxSize);
+        o.put("thread-counts", threadCount);
+        o.put("peak-thread-counts", peakThreadCount);
+        o.put("system-load-averages", systemLoadAverage);
+
+        return o;
+    }
 }
\ No newline at end of file
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/JobRun.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/JobRun.java
index 798c8a8..8fdde7c 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/JobRun.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/JobRun.java
@@ -59,6 +59,12 @@
 
     private JobScheduler js;
 
+    private long createTime;
+
+    private long startTime;
+
+    private long endTime;
+
     private JobStatus status;
 
     private Exception exception;
@@ -95,6 +101,30 @@
         return status;
     }
 
+    public long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(long createTime) {
+        this.createTime = createTime;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(long startTime) {
+        this.startTime = startTime;
+    }
+
+    public long getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(long endTime) {
+        this.endTime = endTime;
+    }
+
     public synchronized Exception getException() {
         return exception;
     }
@@ -145,6 +175,9 @@
         JSONObject result = new JSONObject();
 
         result.put("job-id", jobId.toString());
+        result.put("create-time", getCreateTime());
+        result.put("start-time", getCreateTime());
+        result.put("end-time", getCreateTime());
 
         JSONArray aClusters = new JSONArray();
         for (ActivityCluster ac : activityClusters) {
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetJobSummariesJSONEvent.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetJobSummariesJSONEvent.java
index bf5e5c3..eda4338 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetJobSummariesJSONEvent.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetJobSummariesJSONEvent.java
@@ -35,7 +35,11 @@
         for (JobRun run : ccs.getRunMap().values()) {
             JSONObject jo = new JSONObject();
             jo.put("type", "job-summary");
-            jo.put("id", run.getJobId().toString());
+            jo.put("job-id", run.getJobId().toString());
+            jo.put("application-name", run.getJobActivityGraph().getApplicationName());
+            jo.put("create-time", run.getCreateTime());
+            jo.put("start-time", run.getCreateTime());
+            jo.put("end-time", run.getCreateTime());
             jo.put("status", run.getStatus().toString());
             summaries.put(jo);
         }
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetNodeEvent.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetNodeDetailsJSONEvent.java
similarity index 70%
rename from hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetNodeEvent.java
rename to hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetNodeDetailsJSONEvent.java
index 0db7f2a..284ec35 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetNodeEvent.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetNodeDetailsJSONEvent.java
@@ -14,26 +14,33 @@
  */
 package edu.uci.ics.hyracks.control.cc.job.manager.events;
 
+import org.json.JSONObject;
+
 import edu.uci.ics.hyracks.control.cc.ClusterControllerService;
 import edu.uci.ics.hyracks.control.cc.NodeControllerState;
 import edu.uci.ics.hyracks.control.cc.jobqueue.SynchronizableEvent;
 
-public class GetNodeEvent extends SynchronizableEvent {
+public class GetNodeDetailsJSONEvent extends SynchronizableEvent {
     private final ClusterControllerService ccs;
     private final String nodeId;
-    private NodeControllerState state;
+    private JSONObject detail;
 
-    public GetNodeEvent(ClusterControllerService ccs, String nodeId) {
+    public GetNodeDetailsJSONEvent(ClusterControllerService ccs, String nodeId) {
         this.ccs = ccs;
         this.nodeId = nodeId;
     }
 
     @Override
     protected void doRun() throws Exception {
-        state = ccs.getNodeMap().get(nodeId);
+        NodeControllerState ncs = ccs.getNodeMap().get(nodeId);
+        if (ncs == null) {
+            detail = new JSONObject();
+            return;
+        }
+        detail = ncs.toDetailedJSON();
     }
 
-    public NodeControllerState getNodeState() {
-        return state;
+    public JSONObject getDetail() {
+        return detail;
     }
 }
\ No newline at end of file
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetNodeEvent.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetNodeSummariesJSONEvent.java
similarity index 70%
copy from hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetNodeEvent.java
copy to hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetNodeSummariesJSONEvent.java
index 0db7f2a..512bce2 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetNodeEvent.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/GetNodeSummariesJSONEvent.java
@@ -14,26 +14,29 @@
  */
 package edu.uci.ics.hyracks.control.cc.job.manager.events;
 
+import org.json.JSONArray;
+
 import edu.uci.ics.hyracks.control.cc.ClusterControllerService;
 import edu.uci.ics.hyracks.control.cc.NodeControllerState;
 import edu.uci.ics.hyracks.control.cc.jobqueue.SynchronizableEvent;
 
-public class GetNodeEvent extends SynchronizableEvent {
+public class GetNodeSummariesJSONEvent extends SynchronizableEvent {
     private final ClusterControllerService ccs;
-    private final String nodeId;
-    private NodeControllerState state;
+    private JSONArray summaries;
 
-    public GetNodeEvent(ClusterControllerService ccs, String nodeId) {
+    public GetNodeSummariesJSONEvent(ClusterControllerService ccs) {
         this.ccs = ccs;
-        this.nodeId = nodeId;
     }
 
     @Override
     protected void doRun() throws Exception {
-        state = ccs.getNodeMap().get(nodeId);
+        summaries = new JSONArray();
+        for (NodeControllerState ncs : ccs.getNodeMap().values()) {
+            summaries.put(ncs.toSummaryJSON());
+        }
     }
 
-    public NodeControllerState getNodeState() {
-        return state;
+    public JSONArray getSummaries() {
+        return summaries;
     }
 }
\ No newline at end of file
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/NodeHeartbeatEvent.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/NodeHeartbeatEvent.java
index 8643e6a..98d17ad 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/NodeHeartbeatEvent.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/manager/events/NodeHeartbeatEvent.java
@@ -20,14 +20,17 @@
 import edu.uci.ics.hyracks.control.cc.ClusterControllerService;
 import edu.uci.ics.hyracks.control.cc.NodeControllerState;
 import edu.uci.ics.hyracks.control.cc.jobqueue.SynchronizableEvent;
+import edu.uci.ics.hyracks.control.common.heartbeat.HeartbeatData;
 
 public class NodeHeartbeatEvent extends SynchronizableEvent {
     private final ClusterControllerService ccs;
     private final String nodeId;
+    private final HeartbeatData hbData;
 
-    public NodeHeartbeatEvent(ClusterControllerService ccs, String nodeId) {
+    public NodeHeartbeatEvent(ClusterControllerService ccs, String nodeId, HeartbeatData hbData) {
         this.ccs = ccs;
         this.nodeId = nodeId;
+        this.hbData = hbData;
     }
 
     @Override
@@ -35,7 +38,7 @@
         Map<String, NodeControllerState> nodeMap = ccs.getNodeMap();
         NodeControllerState state = nodeMap.get(nodeId);
         if (state != null) {
-            state.notifyHeartbeat();
+            state.notifyHeartbeat(hbData);
         }
     }
 
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/AdminConsoleHandler.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/AdminConsoleHandler.java
index a0510f9..8021d1a 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/AdminConsoleHandler.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/AdminConsoleHandler.java
@@ -15,8 +15,7 @@
 package edu.uci.ics.hyracks.control.cc.web;
 
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Map;
+import java.net.URL;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
@@ -26,51 +25,21 @@
 import org.eclipse.jetty.server.handler.AbstractHandler;
 
 import edu.uci.ics.hyracks.control.cc.ClusterControllerService;
-import edu.uci.ics.hyracks.control.cc.NodeControllerState;
-import edu.uci.ics.hyracks.control.cc.jobqueue.SynchronizableEvent;
 
 public class AdminConsoleHandler extends AbstractHandler {
-    private ClusterControllerService ccs;
-
     public AdminConsoleHandler(ClusterControllerService ccs) {
-        this.ccs = ccs;
     }
 
     @Override
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
             throws IOException, ServletException {
-        if (!"/".equals(target)) {
-            return;
+        String basedir = System.getProperty("basedir");
+        if (basedir == null) {
+            response.getWriter().println("No Administration Console found.");
         }
-        response.setContentType("text/html;charset=utf-8");
-        response.setStatus(HttpServletResponse.SC_OK);
-        baseRequest.setHandled(true);
-        final PrintWriter writer = response.getWriter();
-        writer.println("<html><head><title>Hyracks Admin Console</title></head><body>");
-        writer.println("<h1>Hyracks Admin Console</h1>");
-        writer.println("<h2>Node Controllers</h2>");
-        writer.println("<table><tr><td>Node Id</td><td>Host</td></tr>");
-        try {
-            ccs.getJobQueue().scheduleAndSync(new SynchronizableEvent() {
-                @Override
-                protected void doRun() throws Exception {
-                    for (Map.Entry<String, NodeControllerState> e : ccs.getNodeMap().entrySet()) {
-                        try {
-                            writer.print("<tr><td>");
-                            writer.print(e.getKey());
-                            writer.print("</td><td>");
-                            writer.print(e.getValue().getLastHeartbeatDuration());
-                            writer.print("</td></tr>");
-                        } catch (Exception ex) {
-                        }
-                    }
-                }
-            });
-        } catch (Exception e) {
-            throw new IOException(e);
-        }
-        writer.println("</table>");
-        writer.println("</body></html>");
-        writer.flush();
+
+        URL url = new URL(request.getRequestURL().toString());
+        String ccLoc = url.getProtocol() + "://" + url.getHost() + ":" + url.getPort();
+        response.sendRedirect("/console?cclocation=" + ccLoc);
     }
 }
\ No newline at end of file
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/RESTAPIFunction.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/JobsRESTAPIFunction.java
similarity index 95%
rename from hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/RESTAPIFunction.java
rename to hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/JobsRESTAPIFunction.java
index a488d2f..bd46276 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/RESTAPIFunction.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/JobsRESTAPIFunction.java
@@ -24,10 +24,10 @@
 import edu.uci.ics.hyracks.control.cc.job.manager.events.GetJobSummariesJSONEvent;
 import edu.uci.ics.hyracks.control.cc.web.util.IJSONOutputFunction;
 
-public class RESTAPIFunction implements IJSONOutputFunction {
+public class JobsRESTAPIFunction implements IJSONOutputFunction {
     private ClusterControllerService ccs;
 
-    public RESTAPIFunction(ClusterControllerService ccs) {
+    public JobsRESTAPIFunction(ClusterControllerService ccs) {
         this.ccs = ccs;
     }
 
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/NodesRESTAPIFunction.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/NodesRESTAPIFunction.java
new file mode 100644
index 0000000..9059de7
--- /dev/null
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/NodesRESTAPIFunction.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2009-2010 by The Regents of the University of California
+ * 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 from
+ * 
+ *     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 edu.uci.ics.hyracks.control.cc.web;
+
+import org.json.JSONObject;
+
+import edu.uci.ics.hyracks.control.cc.ClusterControllerService;
+import edu.uci.ics.hyracks.control.cc.job.manager.events.GetNodeDetailsJSONEvent;
+import edu.uci.ics.hyracks.control.cc.job.manager.events.GetNodeSummariesJSONEvent;
+import edu.uci.ics.hyracks.control.cc.web.util.IJSONOutputFunction;
+
+public class NodesRESTAPIFunction implements IJSONOutputFunction {
+    private ClusterControllerService ccs;
+
+    public NodesRESTAPIFunction(ClusterControllerService ccs) {
+        this.ccs = ccs;
+    }
+
+    @Override
+    public JSONObject invoke(String[] arguments) throws Exception {
+        JSONObject result = new JSONObject();
+        switch (arguments.length) {
+            case 1: {
+                if ("".equals(arguments[0])) {
+                    GetNodeSummariesJSONEvent gnse = new GetNodeSummariesJSONEvent(ccs);
+                    ccs.getJobQueue().scheduleAndSync(gnse);
+                    result.put("result", gnse.getSummaries());
+                } else {
+                    String nodeId = arguments[0];
+                    GetNodeDetailsJSONEvent gnde = new GetNodeDetailsJSONEvent(ccs, nodeId);
+                    ccs.getJobQueue().scheduleAndSync(gnde);
+                    result.put("result", gnde.getDetail());
+                }
+            }
+        }
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/WebServer.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/WebServer.java
index efbc8f7..fb5ad21 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/WebServer.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/web/WebServer.java
@@ -14,6 +14,9 @@
  */
 package edu.uci.ics.hyracks.control.cc.web;
 
+import java.io.File;
+import java.util.logging.Logger;
+
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Server;
@@ -21,12 +24,15 @@
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.server.handler.HandlerCollection;
 import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.webapp.WebAppContext;
 
 import edu.uci.ics.hyracks.control.cc.ClusterControllerService;
 import edu.uci.ics.hyracks.control.cc.web.util.JSONOutputRequestHandler;
 import edu.uci.ics.hyracks.control.cc.web.util.RoutingHandler;
 
 public class WebServer {
+    private final static Logger LOGGER = Logger.getLogger(WebServer.class.getName());
+
     private final ClusterControllerService ccs;
     private final Server server;
     private final SelectChannelConnector connector;
@@ -48,17 +54,30 @@
     private void addHandlers() {
         ContextHandler handler = new ContextHandler("/rest");
         RoutingHandler rh = new RoutingHandler();
-        rh.addHandler("jobs", new JSONOutputRequestHandler(new RESTAPIFunction(ccs)));
+        rh.addHandler("jobs", new JSONOutputRequestHandler(new JobsRESTAPIFunction(ccs)));
+        rh.addHandler("nodes", new JSONOutputRequestHandler(new NodesRESTAPIFunction(ccs)));
         handler.setHandler(rh);
         addHandler(handler);
 
-        handler = new ContextHandler("/admin");
+        handler = new ContextHandler("/adminconsole");
         handler.setHandler(new AdminConsoleHandler(ccs));
         addHandler(handler);
 
         handler = new ContextHandler("/applications");
         handler.setHandler(new ApplicationInstallationHandler(ccs));
         addHandler(handler);
+
+        String basedir = System.getProperty("basedir");
+        if (basedir != null) {
+            File warFile = new File(basedir, "console/hyracks-admin-console.war");
+            LOGGER.info("Looking for admin console binary in: " + warFile.getAbsolutePath());
+            if (warFile.exists()) {
+                WebAppContext waCtx = new WebAppContext();
+                waCtx.setContextPath("/console");
+                waCtx.setWar(warFile.getAbsolutePath());
+                addHandler(waCtx);
+            }
+        }
     }
 
     public void setPort(int port) {