You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@accumulo.apache.org by bi...@apache.org on 2012/06/02 00:06:57 UTC

svn commit: r1345371 - in /accumulo/trunk/server/src/main: java/org/apache/accumulo/server/monitor/ java/org/apache/accumulo/server/monitor/servlets/ resources/web/

Author: billie
Date: Fri Jun  1 22:06:57 2012
New Revision: 1345371

URL: http://svn.apache.org/viewvc?rev=1345371&view=rev
Log:
ACCUMULO-196 created shell servlet

Added:
    accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/servlets/ShellServlet.java   (with props)
Modified:
    accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/Monitor.java
    accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/servlets/BasicServlet.java
    accumulo/trunk/server/src/main/resources/web/screen.css

Modified: accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/Monitor.java
URL: http://svn.apache.org/viewvc/accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/Monitor.java?rev=1345371&r1=1345370&r2=1345371&view=diff
==============================================================================
--- accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/Monitor.java (original)
+++ accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/Monitor.java Fri Jun  1 22:06:57 2012
@@ -60,6 +60,7 @@ import org.apache.accumulo.server.monito
 import org.apache.accumulo.server.monitor.servlets.MasterServlet;
 import org.apache.accumulo.server.monitor.servlets.OperationServlet;
 import org.apache.accumulo.server.monitor.servlets.ProblemServlet;
+import org.apache.accumulo.server.monitor.servlets.ShellServlet;
 import org.apache.accumulo.server.monitor.servlets.TServersServlet;
 import org.apache.accumulo.server.monitor.servlets.TablesServlet;
 import org.apache.accumulo.server.monitor.servlets.VisServlet;
@@ -148,6 +149,8 @@ public class Monitor {
   
   private static ServerConfiguration config;
   
+  private static EmbeddedWebServer server;
+  
   public static Map<String,Double> summarizeTableStats(MasterMonitorInfo mmi) {
     Map<String,Double> compactingByTable = new HashMap<String,Double>();
     if (mmi != null && mmi.tServerInfo != null) {
@@ -317,7 +320,7 @@ public class Monitor {
         indexCacheRequestTracker.startingUpdates();
         dataCacheHitTracker.startingUpdates();
         dataCacheRequestTracker.startingUpdates();
-
+      
         for (TabletServerStatus server : mmi.tServerInfo) {
           TableInfo summary = Monitor.summarizeTableStats(server);
           totalIngestRate += summary.ingestRate;
@@ -464,7 +467,6 @@ public class Monitor {
   public void run(String hostname) {
     Monitor.START_TIME = System.currentTimeMillis();
     int port = config.getConfiguration().getPort(Property.MONITOR_PORT);
-    EmbeddedWebServer server;
     try {
       log.debug("Creating monitor on port " + port);
       server = EmbeddedWebServer.create(port);
@@ -488,6 +490,8 @@ public class Monitor {
     server.addServlet(Summary.class, "/trace/summary");
     server.addServlet(ListType.class, "/trace/listType");
     server.addServlet(ShowTrace.class, "/trace/show");
+    if (server.isUsingSsl())
+      server.addServlet(ShellServlet.class, "/shell");
     LogService.startLogListener(Monitor.getSystemConfiguration());
     server.start();
     
@@ -547,7 +551,7 @@ public class Monitor {
   public static double getTotalScanRate() {
     return totalScanRate;
   }
-
+  
   public static double getTotalQueryByteRate() {
     return totalQueryByteRate;
   }
@@ -633,7 +637,7 @@ public class Monitor {
       return new ArrayList<Pair<Long,Integer>>(scanRateOverTime);
     }
   }
-
+  
   public static List<Pair<Long,Double>> getQueryByteRateOverTime() {
     synchronized (queryByteRateOverTime) {
       return new ArrayList<Pair<Long,Double>>(queryByteRateOverTime);
@@ -659,4 +663,8 @@ public class Monitor {
   public static Instance getInstance() {
     return instance;
   }
+  
+  public static boolean isUsingSsl() {
+    return server.isUsingSsl();
+  }
 }

Modified: accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/servlets/BasicServlet.java
URL: http://svn.apache.org/viewvc/accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/servlets/BasicServlet.java?rev=1345371&r1=1345370&r2=1345371&view=diff
==============================================================================
--- accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/servlets/BasicServlet.java (original)
+++ accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/servlets/BasicServlet.java Fri Jun  1 22:06:57 2012
@@ -73,7 +73,7 @@ abstract public class BasicServlet exten
     }
   }
   
-  protected final void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     doGet(req, resp);
   }
   
@@ -136,7 +136,8 @@ abstract public class BasicServlet exten
     // BEGIN HEADER
     sb.append("<head>\n");
     sb.append("<title>").append(getTitle(req)).append(" - Accumulo ").append(Constants.VERSION).append("</title>\n");
-    if ((refresh > 0) && (req.getRequestURI().startsWith("/docs") == false) && (req.getRequestURI().startsWith("/vis") == false))
+    if ((refresh > 0) && (req.getRequestURI().startsWith("/docs") == false) && (req.getRequestURI().startsWith("/vis") == false)
+        && (req.getRequestURI().startsWith("/shell") == false))
       sb.append("<meta http-equiv='refresh' content='" + refresh + "' />\n");
     sb.append("<meta http-equiv='Content-Type' content='").append(DEFAULT_CONTENT_TYPE).append("' />\n");
     sb.append("<meta http-equiv='Content-Script-Type' content='text/javascript' />\n");
@@ -190,6 +191,8 @@ abstract public class BasicServlet exten
     sb.append("<hr />\n");
     sb.append("<a href='/xml'>XML</a><br />\n");
     sb.append("<a href='/json'>JSON</a><hr />\n");
+    if (Monitor.isUsingSsl())
+      sb.append("<a href='/shell'>Shell</a><hr />\n");
     sb.append("<div class='smalltext'>[<a href='").append("/op?action=refresh&value=").append(refresh < 1 ? "5" : "-1");
     sb.append("&redir=").append(currentPage(req)).append("'>");
     sb.append(refresh < 1 ? "en" : "dis").append("able&nbsp;auto-refresh</a>]</div>\n");

Added: accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/servlets/ShellServlet.java
URL: http://svn.apache.org/viewvc/accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/servlets/ShellServlet.java?rev=1345371&view=auto
==============================================================================
--- accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/servlets/ShellServlet.java (added)
+++ accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/servlets/ShellServlet.java Fri Jun  1 22:06:57 2012
@@ -0,0 +1,331 @@
+/**
+ * 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.accumulo.server.monitor.servlets;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import jline.ConsoleReader;
+
+import org.apache.accumulo.core.util.shell.Shell;
+
+public class ShellServlet extends BasicServlet {
+  private static final long serialVersionUID = 1L;
+  private Map<String,ShellExecutionThread> userShells = new HashMap<String,ShellExecutionThread>();
+  private ExecutorService service = Executors.newCachedThreadPool();
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    return "Shell";
+  }
+  
+  @Override
+  protected void pageBody(HttpServletRequest req, HttpServletResponse response, StringBuilder sb) throws IOException {
+    HttpSession session = req.getSession(true);
+    String user = (String) session.getAttribute("user");
+    if (user == null) {
+      // user attribute is null, check to see if username and password are passed as parameters
+      user = req.getParameter("user");
+      String pass = req.getParameter("pass");
+      String mock = req.getParameter("mock");
+      if (user == null || pass == null) {
+        // username or password are null, re-authenticate
+        sb.append(authenticationForm(req.getRequestURI()));
+        return;
+      }
+      try {
+        // get a new shell for this user
+        ShellExecutionThread shellThread = new ShellExecutionThread(user, pass, mock);
+        service.submit(shellThread);
+        userShells.put(session.getId(), shellThread);
+      } catch (IOException e) {
+        // error validating user, reauthenticate
+        sb.append("<div id='loginError'>Invalid user/password</div>" + authenticationForm(req.getRequestURI()));
+        return;
+      }
+      session.setAttribute("user", user);
+    }
+    if (!userShells.containsKey(session.getId())) {
+      // no existing shell for this user, re-authenticate
+      sb.append(authenticationForm(req.getRequestURI()));
+      return;
+    }
+    ShellExecutionThread shellThread = userShells.get(session.getId());
+    shellThread.getOutput();
+    shellThread.printInfo();
+    sb.append("<div id='shell'>\n");
+    sb.append("<pre id='shellResponse'>").append(shellThread.getOutput()).append("</pre>\n");
+    sb.append("<form><span id='shellPrompt'>").append(shellThread.getPrompt());
+    sb.append("</span><input type='text' name='cmd' id='cmd' onkeydown='return handleKeyDown(event.keyCode);'>\n");
+    sb.append("</form>\n</div>\n");
+    sb.append("<script type='text/javascript'>\n");
+    sb.append("var url = '").append(req.getRequestURL().toString()).append("';\n");
+    sb.append("var xmlhttp = new XMLHttpRequest();\n");
+    sb.append("var hsize = 1000;\n");
+    sb.append("var hindex = 0;\n");
+    sb.append("var history = new Array();\n");
+    sb.append("\n");
+    sb.append("function handleKeyDown(keyCode) {\n");
+    sb.append("  if (keyCode==13) {\n");
+    sb.append("    submitCmd(document.getElementById('cmd').value);\n");
+    sb.append("    hindex = history.length;\n");
+    sb.append("    return false;\n");
+    sb.append("  } else if (keyCode==38) {\n");
+    sb.append("    hindex = hindex==0 ? history.length : hindex - 1;\n");
+    sb.append("    if (hindex == history.length)\n");
+    sb.append("      document.getElementById('cmd').value = '';\n");
+    sb.append("    else\n");
+    sb.append("      document.getElementById('cmd').value = history[hindex];\n");
+    sb.append("    return false;\n");
+    sb.append("  } else if (keyCode==40) {\n");
+    sb.append("    hindex = hindex==history.length ? history.length : hindex + 1;\n");
+    sb.append("    if (hindex == history.length)\n");
+    sb.append("      document.getElementById('cmd').value = '';\n");
+    sb.append("    else\n");
+    sb.append("      document.getElementById('cmd').value = history[hindex];\n");
+    sb.append("    return false;\n");
+    sb.append("  }\n");
+    sb.append("  return true;\n");
+    sb.append("}\n");
+    sb.append("\n");
+    sb.append("function submitCmd(cmd) {\n");
+    sb.append("  if (cmd=='history') {\n");
+    sb.append("    document.getElementById('shellResponse').innerHTML += document.getElementById('shellPrompt').innerHTML+cmd+'\\n';\n");
+    sb.append("    document.getElementById('shellResponse').innerHTML += history.join('\\n');\n");
+    sb.append("    return\n");
+    sb.append("  }\n");
+    sb.append("  xmlhttp.open('POST',url+'?cmd='+cmd,false);\n");
+    sb.append("  xmlhttp.send();\n");
+    sb.append("  var text = xmlhttp.responseText;\n");
+    sb.append("  var index = text.lastIndexOf('\\n');\n");
+    sb.append("  if (index >= 0) {\n");
+    sb.append("    if (index > 0 && document.getElementById('cmd').type == 'text') {\n");
+    sb.append("      if (history.length == hsize)\n");
+    sb.append("        history.shift()\n");
+    sb.append("      history.push(cmd)\n");
+    sb.append("    }\n");
+    sb.append("    if (text.charAt(text.length-1)=='*') {\n");
+    sb.append("      document.getElementById('cmd').type = 'password';\n");
+    sb.append("      text = text.substring(0,xmlhttp.responseText.length-2);\n");
+    sb.append("    } else {\n");
+    sb.append("      document.getElementById('cmd').type = 'text';\n");
+    sb.append("    }\n");
+    sb.append("    document.getElementById('shellResponse').innerHTML += text.substring(0,index+1);\n");
+    sb.append("    document.getElementById('shellPrompt').innerHTML = text.substring(index+1);\n");
+    sb.append("    document.getElementById('cmd').value = '';\n");
+    sb.append("    document.getElementById('shell').scrollTop = document.getElementById('cmd').offsetTop;\n");
+    sb.append("  } else {\n");
+    sb.append("    window.location = url;\n");
+    sb.append("  }\n");
+    sb.append("}\n");
+    sb.append("</script>\n");
+    sb.append("<script type='text/javascript'>window.onload = function() { document.getElementById('cmd').select(); }</script>\n");
+  }
+  
+  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+    HttpSession session = req.getSession(true);
+    String user = (String) session.getAttribute("user");
+    if (user == null || !userShells.containsKey(session.getId())) {
+      // no existing shell for user, re-authenticate
+      doGet(req, resp);
+      return;
+    }
+    ShellExecutionThread shellThread = userShells.get(session.getId());
+    String cmd = req.getParameter("cmd");
+    if (cmd == null) {
+      // the command is null, just print prompt
+      resp.getWriter().append(shellThread.getPrompt());
+      resp.getWriter().flush();
+      return;
+    }
+    shellThread.addInputString(cmd);
+    shellThread.waitUntilReady();
+    if (shellThread.isDone()) {
+      // the command was exit, invalidate session
+      userShells.remove(session.getId());
+      session.invalidate();
+      return;
+    }
+    // get the shell's output
+    StringBuilder sb = new StringBuilder();
+    sb.append(shellThread.getOutput().replace("<", "&lt;").replace(">", "&gt;"));
+    if (sb.length() == 0 || !(sb.charAt(sb.length() - 1) == '\n'))
+      sb.append("\n");
+    // check if shell is waiting for input
+    if (!shellThread.isWaitingForInput())
+      sb.append(shellThread.getPrompt());
+    // check if shell is waiting for password input
+    if (shellThread.isMasking())
+      sb.append("*");
+    resp.getWriter().append(sb.toString());
+    resp.getWriter().flush();
+  }
+  
+  private String authenticationForm(String requestURI) {
+    return "<div id='login'><form method=POST action='" + requestURI + "'>"
+        + "<table><tr><td>Mock:&nbsp</td><td><input type='checkbox' name='mock' value='mock'></td></tr>"
+        + "<tr><td>Username:&nbsp;</td><td><input type='text' name='user'></td></tr>"
+        + "<tr><td>Password:&nbsp;</td><td><input type='password' name='pass'></td><td><input type='submit' value='Enter'></td></tr></table></form></div>";
+  }
+  
+  private static class StringBuilderOutputStream extends OutputStream {
+    StringBuilder sb = new StringBuilder();
+    
+    @Override
+    public void write(int b) throws IOException {
+      sb.append((char) (0xff & b));
+    }
+    
+    public String get() {
+      return sb.toString();
+    }
+    
+    public void clear() {
+      sb.setLength(0);
+    }
+  }
+  
+  private static class ShellExecutionThread extends InputStream implements Runnable {
+    private Shell shell;
+    StringBuilderOutputStream output;
+    private String cmd;
+    private int cmdIndex;
+    private boolean done;
+    private boolean readWait;
+    
+    private ShellExecutionThread(String username, String password, String mock) throws IOException {
+      this.done = false;
+      this.cmd = null;
+      this.cmdIndex = 0;
+      this.readWait = false;
+      this.output = new StringBuilderOutputStream();
+      ConsoleReader reader = new ConsoleReader(this, new OutputStreamWriter(output));
+      this.shell = new Shell(reader, new PrintWriter(output));
+      shell.setLogErrorsToConsole();
+      if (mock != null) {
+        if (shell.config("--fake", "-u", username, "-p", password))
+          throw new IOException("mock shell config error");
+      } else if (shell.config("-u", username, "-p", password)) {
+        throw new IOException("shell config error");
+      }
+    }
+    
+    @Override
+    public synchronized int read() throws IOException {
+      if (cmd == null) {
+        readWait = true;
+        this.notifyAll();
+      }
+      while (cmd == null) {
+        try {
+          this.wait();
+        } catch (InterruptedException e) {}
+      }
+      readWait = false;
+      int c;
+      if (cmdIndex == cmd.length())
+        c = '\n';
+      else
+        c = cmd.charAt(cmdIndex);
+      cmdIndex++;
+      if (cmdIndex > cmd.length()) {
+        cmd = null;
+        cmdIndex = 0;
+        this.notifyAll();
+      }
+      return c;
+    }
+    
+    @Override
+    public synchronized void run() {
+      Thread.currentThread().setName("shell thread");
+      while (!shell.hasExited()) {
+        while (cmd == null) {
+          try {
+            this.wait();
+          } catch (InterruptedException e) {}
+        }
+        String tcmd = cmd;
+        cmd = null;
+        cmdIndex = 0;
+        try {
+          shell.execCommand(tcmd, false, true);
+        } catch (IOException e) {}
+        this.notifyAll();
+      }
+      done = true;
+      this.notifyAll();
+    }
+    
+    public synchronized void addInputString(String s) {
+      if (done)
+        throw new IllegalStateException("adding string to exited shell");
+      if (cmd == null) {
+        cmd = s;
+      } else {
+        throw new IllegalStateException("adding string to shell not waiting for input");
+      }
+      this.notifyAll();
+    }
+    
+    public synchronized void waitUntilReady() {
+      while (cmd != null) {
+        try {
+          this.wait();
+        } catch (InterruptedException e) {}
+      }
+    }
+    
+    public synchronized String getOutput() {
+      String s = output.get();
+      output.clear();
+      return s;
+    }
+    
+    public String getPrompt() {
+      return shell.getDefaultPrompt();
+    }
+    
+    public void printInfo() throws IOException {
+      shell.printInfo();
+    }
+    
+    public boolean isMasking() {
+      return shell.isMasking();
+    }
+    
+    public synchronized boolean isWaitingForInput() {
+      return readWait;
+    }
+    
+    public boolean isDone() {
+      return done;
+    }
+  }
+}

Propchange: accumulo/trunk/server/src/main/java/org/apache/accumulo/server/monitor/servlets/ShellServlet.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: accumulo/trunk/server/src/main/resources/web/screen.css
URL: http://svn.apache.org/viewvc/accumulo/trunk/server/src/main/resources/web/screen.css?rev=1345371&r1=1345370&r2=1345371&view=diff
==============================================================================
--- accumulo/trunk/server/src/main/resources/web/screen.css (original)
+++ accumulo/trunk/server/src/main/resources/web/screen.css Fri Jun  1 22:06:57 2012
@@ -341,3 +341,48 @@ pre.logevent {
     border: 1px solid #c4c4c4;
     background-color: #ffffff;
 }
+
+#shell {
+    text-align: left;
+    color: #55d839;
+    border: 1px;
+    padding-left: 5px;
+    background-color: #000000;
+    height: 40em;
+    overflow: auto;
+    font-family: Monaco, monospace;
+    font-size: 100%;
+}
+
+#shell span {
+    display: inline;
+}
+
+#shell input {
+    display: inline;
+    border: none;
+    width: 60%;
+    color: #55d839;
+    background-color: #000000;
+    font-family: Monaco, monospace;
+    font-size: 100%;
+}
+
+#shell pre {
+    font-family: Monaco, monospace;
+    font-size: 100%;
+}
+
+#login {
+    text-align: left;
+}
+
+#login table {
+    margin-left: 0;
+    min-width: 0%;
+}
+
+#loginError {
+    text-align: left;
+    color: red;
+}