azkaban-uncached
Changes
src/java/azkaban/webapp/servlet/JMXHttpServlet.java 143(+143 -0)
src/web/js/azkaban.jmx.view.js 189(+189 -0)
Details
diff --git a/src/java/azkaban/webapp/AzkabanWebServer.java b/src/java/azkaban/webapp/AzkabanWebServer.java
index 1af216f..f9f62a5 100644
--- a/src/java/azkaban/webapp/AzkabanWebServer.java
+++ b/src/java/azkaban/webapp/AzkabanWebServer.java
@@ -31,8 +31,14 @@ import java.util.Comparator;
import java.util.List;
import java.util.TimeZone;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.IntrospectionException;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.ObjectName;
+import javax.management.ReflectionException;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
@@ -73,6 +79,7 @@ import azkaban.webapp.servlet.AzkabanServletContextListener;
import azkaban.webapp.servlet.AbstractAzkabanServlet;
import azkaban.webapp.servlet.ExecutorServlet;
+import azkaban.webapp.servlet.JMXHttpServlet;
import azkaban.webapp.servlet.ScheduleServlet;
import azkaban.webapp.servlet.HistoryServlet;
import azkaban.webapp.servlet.IndexServlet;
@@ -179,9 +186,6 @@ public class AzkabanWebServer implements AzkabanServer {
}
configureMBeanServer();
-
-
-
}
private void setViewerPlugins(List<ViewerPlugin> viewerPlugins) {
@@ -441,6 +445,7 @@ public class AzkabanWebServer implements AzkabanServer {
root.addServlet(new ServletHolder(new ExecutorServlet()),"/executor");
root.addServlet(new ServletHolder(new HistoryServlet()), "/history");
root.addServlet(new ServletHolder(new ScheduleServlet()),"/schedule");
+ root.addServlet(new ServletHolder(new JMXHttpServlet()),"/jmx");
String viewerPluginDir = azkabanSettings.getString("viewer.plugin.dir", "plugins/viewer");
app.setViewerPlugins(loadViewerPlugins(root, viewerPluginDir, app.getVelocityEngine()));
@@ -723,7 +728,27 @@ public class AzkabanWebServer implements AzkabanServer {
} catch (Exception e) {
logger.error("Error registering mbean " + mbeanClass.getCanonicalName(), e);
}
-
}
+ public List<ObjectName> getMbeanNames() {
+ return registeredMBeans;
+ }
+
+ public MBeanInfo getMBeanInfo(ObjectName name) {
+ try {
+ return mbeanServer.getMBeanInfo(name);
+ } catch (Exception e) {
+ logger.error(e);
+ return null;
+ }
+ }
+
+ public Object getMBeanAttribute(ObjectName name, String attribute) {
+ try {
+ return mbeanServer.getAttribute(name, attribute);
+ } catch (Exception e) {
+ logger.error(e);
+ return null;
+ }
+ }
}
diff --git a/src/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java b/src/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
index e257bc5..8810352 100644
--- a/src/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
+++ b/src/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
@@ -341,10 +341,14 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
* @throws IOException
*/
protected void writeJSON(HttpServletResponse resp, Object obj) throws IOException {
- resp.setContentType(JSON_MIME_TYPE);
- JSONUtils.toJSON(obj, resp.getOutputStream());
+ writeJSON(resp, obj, false);
}
+ protected void writeJSON(HttpServletResponse resp, Object obj, boolean pretty) throws IOException {
+ resp.setContentType(JSON_MIME_TYPE);
+ JSONUtils.toJSON(obj, resp.getOutputStream(), true);
+ }
+
/**
* Retrieve the Azkaban application
*
src/java/azkaban/webapp/servlet/JMXHttpServlet.java 143(+143 -0)
diff --git a/src/java/azkaban/webapp/servlet/JMXHttpServlet.java b/src/java/azkaban/webapp/servlet/JMXHttpServlet.java
new file mode 100644
index 0000000..6bd1e94
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/JMXHttpServlet.java
@@ -0,0 +1,143 @@
+package azkaban.webapp.servlet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+
+import javax.management.MBeanInfo;
+import javax.management.ObjectName;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+
+import azkaban.user.Permission;
+import azkaban.user.Role;
+import azkaban.user.User;
+import azkaban.user.UserManager;
+import azkaban.webapp.AzkabanWebServer;
+import azkaban.webapp.session.Session;
+
+/**
+ * Limited set of jmx calls for when you cannot attach to the jvm
+ */
+public class JMXHttpServlet extends LoginAbstractAzkabanServlet {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ private static final Logger logger = Logger.getLogger(JMXHttpServlet.class.getName());
+
+ private UserManager userManager;
+ private AzkabanWebServer server;
+
+ private static final String GET_MBEANS = "getMBeans";
+ private static final String GET_MBEAN_INFO = "getMBeanInfo";
+ private static final String GET_MBEAN_ATTRIBUTE = "getAttribute";
+ private static final String ATTRIBUTE = "attribute";
+ private static final String MBEAN = "mBean";
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+
+ server = (AzkabanWebServer)getApplication();
+ userManager = server.getUserManager();
+ }
+
+ @Override
+ protected void handleGet(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
+ if (hasParam(req, "ajax")){
+ HashMap<String,Object> ret = new HashMap<String,Object>();
+
+ if(!hasAdminRole(session.getUser())) {
+ ret.put("error", "User " + session.getUser().getUserId() + " has no permission.");
+ }
+ String ajax = getParam(req, "ajax");
+ if (GET_MBEANS.equals(ajax)) {
+ ret.put("mbeans", server.getMbeanNames());
+ }
+ else if (GET_MBEAN_INFO.equals(ajax)) {
+ if (hasParam(req, MBEAN)) {
+ String mbeanName = getParam(req, MBEAN);
+ try {
+ ObjectName name = new ObjectName(mbeanName);
+ MBeanInfo info = server.getMBeanInfo(name);
+ ret.put("attributes", info.getAttributes());
+ ret.put("description", info.getDescription());
+ } catch (Exception e) {
+ logger.error(e);
+ ret.put("error", "'" + mbeanName + "' is not a valid mBean name");
+ }
+ }
+ else {
+ ret.put("error", "No 'mbean' name parameter specified" );
+ }
+ }
+ else if (GET_MBEAN_ATTRIBUTE.equals(ajax)) {
+ if (!hasParam(req, MBEAN) || !hasParam(req, ATTRIBUTE)) {
+ ret.put("error", "Parameters 'mbean' and 'attribute' must be set");
+ }
+ else {
+ String mbeanName = getParam(req, MBEAN);
+ String attribute = getParam(req, ATTRIBUTE);
+
+ try {
+ ObjectName name = new ObjectName(mbeanName);
+ Object obj = server.getMBeanAttribute(name, attribute);
+ ret.put("value", obj);
+ } catch (Exception e) {
+ logger.error(e);
+ ret.put("error", "'" + mbeanName + "' is not a valid mBean name");
+ }
+ }
+ }
+ else {
+ ret.put("commands", new String[] {
+ GET_MBEANS,
+ GET_MBEAN_INFO+"&"+MBEAN+"=<name>",
+ GET_MBEAN_ATTRIBUTE+"&"+MBEAN+"=<name>&"+ATTRIBUTE+"=<attributename>"}
+ );
+ }
+ this.writeJSON(resp, ret, true);
+ }
+ else {
+ handleJMXPage(req, resp, session);
+ }
+ }
+
+ private void handleJMXPage(HttpServletRequest req, HttpServletResponse resp, Session session) {
+ Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/jmxpage.vm");
+
+ if(!hasAdminRole(session.getUser())) {
+ page.add("errorMsg", "User " + session.getUser().getUserId() + " has no permission.");
+ page.render();
+ return;
+ }
+
+ page.add("mbeans", server.getMbeanNames());
+
+ page.render();
+ }
+
+ @Override
+ protected void handlePost(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
+
+ }
+
+ private boolean hasAdminRole(User user) {
+ for(String roleName: user.getRoles()) {
+ Role role = userManager.getRole(roleName);
+ Permission perm = role.getPermission();
+ if (perm.isPermissionSet(Permission.Type.ADMIN)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm b/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm
new file mode 100644
index 0000000..64fcc9c
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/jmxpage.vm
@@ -0,0 +1,86 @@
+#*
+ * Copyright 2012 LinkedIn, Inc
+ *
+ * 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 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.
+*#
+
+<!DOCTYPE html>
+<html>
+ <head>
+#parse( "azkaban/webapp/servlet/velocity/style.vm" )
+ <script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>
+ <script type="text/javascript" src="${context}/js/namespace.js"></script>
+ <script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
+ <script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
+ <script type="text/javascript">
+ var contextURL = "${context}";
+ var currentTime = ${currentTime};
+ var timezone = "${timezone}";
+ var errorMessage = null;
+ var successMessage = null;
+ </script>
+ </head>
+ <body>
+#set($current_page="all")
+#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
+#if($errorMsg)
+<div class="box-error-message"><pre>$errorMsg</pre></div>
+#end
+
+ <div class="content">
+ <div id="all-jobs-content">
+ <div class="section-hd flow-header">
+ <h2><a href="${context}/jmx">Admin JMX Http Page</span></a></h2>
+ </div>
+ </div>
+
+ <div id="flow-tabs">
+ <table id="all-jobs" class="all-jobs job-table">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Domain</th>
+ <th>Canonical Name</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+#foreach($bean in $mbeans)
+ <tr>
+ <td>${bean.keyPropertyList.get("name")}</td>
+ <td>${bean.domain}</td>
+ <td>${bean.canonicalName}</td>
+ <td><div class="btn4 querybtn" id="${bean.canonicalName}">Query</div></td>
+ </tr>
+ <tr class="childrow" id="${bean.canonicalName}-child" style="display: none;">
+ <td class="expandedFlow">
+ <table class="innerTable">
+ <thead>
+ <tr>
+ <th class="tb-name">Attribute Name</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody id="${bean.canonicalName}-tbody">
+ </tbody>
+ </table>
+ </td>
+ </tr>
+#end
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </body>
+</html>
src/web/js/azkaban.jmx.view.js 189(+189 -0)
diff --git a/src/web/js/azkaban.jmx.view.js b/src/web/js/azkaban.jmx.view.js
new file mode 100644
index 0000000..5c9193a
--- /dev/null
+++ b/src/web/js/azkaban.jmx.view.js
@@ -0,0 +1,189 @@
+$.namespace('azkaban');
+
+var projectTableView;
+azkaban.ProjectTableView= Backbone.View.extend({
+ events : {
+ "click .project-expand": "expandProject"
+ },
+ initialize : function(settings) {
+
+ },
+ expandProject : function(evt) {
+ if (evt.target.tagName!="SPAN") {
+ return;
+ }
+
+ var target = evt.currentTarget;
+ var targetId = target.id;
+ var requestURL = contextURL + "/manager";
+
+ var targetExpanded = $('#' + targetId + '-child');
+ var targetTBody = $('#' + targetId + '-tbody');
+
+ var createFlowListFunction = this.createFlowListTable;
+
+ if (target.loading) {
+ console.log("Still loading.");
+ }
+ else if (target.loaded) {
+ if($(targetExpanded).is(':visible')) {
+ $(target).addClass('expand').removeClass('collapse');
+ $(targetExpanded).fadeOut("fast");
+ }
+ else {
+ $(target).addClass('collapse').removeClass('expand');
+ $(targetExpanded).fadeIn();
+ }
+ }
+ else {
+ // projectId is available
+ $(target).addClass('wait').removeClass('collapse').removeClass('expand');
+ target.loading = true;
+
+ $.get(
+ requestURL,
+ {"project": targetId, "ajax":"fetchprojectflows"},
+ function(data) {
+ console.log("Success");
+ target.loaded = true;
+ target.loading = false;
+
+ createFlowListFunction(data, targetTBody);
+
+ $(target).addClass('collapse').removeClass('wait');
+ $(targetExpanded).fadeIn("fast");
+ },
+ "json"
+ );
+ }
+ },
+ render: function() {
+ },
+ createFlowListTable : function(data, innerTable) {
+ var flows = data.flows;
+ flows.sort(function(a,b){return a.flowId.localeCompare(b.flowId);});
+
+ var requestURL = contextURL + "/manager?project=" + data.project + "&flow=";
+ for (var i = 0; i < flows.length; ++i) {
+ var id = flows[i].flowId;
+
+ var tr = document.createElement("tr");
+ var idtd = document.createElement("td");
+ $(idtd).addClass("tb-name");
+
+ var ida = document.createElement("a");
+ ida.project = data.project;
+ $(ida).text(id);
+ $(ida).attr("href", requestURL + id);
+
+ $(idtd).append(ida);
+ $(tr).append(idtd);
+ $(innerTable).append(tr);
+ }
+ }
+});
+
+var projectHeaderView;
+azkaban.ProjectHeaderView= Backbone.View.extend({
+ events : {
+ "click #create-project-btn":"handleCreateProjectJob"
+ },
+ initialize : function(settings) {
+ if (settings.errorMsg && settings.errorMsg != "null") {
+ // Chrome bug in displaying placeholder text. Need to hide the box.
+ $('#searchtextbox').hide();
+ $('.messaging').addClass("error");
+ $('.messaging').removeClass("success");
+ $('.messaging').html(settings.errorMsg);
+ }
+ else if (settings.successMsg && settings.successMsg != "null") {
+ $('#searchtextbox').hide();
+ $('.messaging').addClass("success");
+ $('.messaging').removeClass("error");
+ $('#message').html(settings.successMsg);
+ }
+ else {
+ $('#searchtextbox').show();
+ $('.messaging').removeClass("success");
+ $('.messaging').removeClass("error");
+ }
+
+ $('#messageClose').click(function() {
+ $('#searchtextbox').show();
+
+ $('.messaging').slideUp('fast', function() {
+ $('.messaging').removeClass("success");
+ $('.messaging').removeClass("error");
+ });
+ });
+ },
+ handleCreateProjectJob : function(evt) {
+ console.log("click create project");
+ $('#create-project').modal({
+ closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
+ position: ["20%",],
+ containerId: 'confirm-container',
+ containerCss: {
+ 'height': '220px',
+ 'width': '565px'
+ },
+ onShow: function (dialog) {
+ var modal = this;
+ $("#errorMsg").hide();
+ }
+ });
+ },
+ render: function() {
+ }
+});
+
+var createProjectView;
+azkaban.CreateProjectView= Backbone.View.extend({
+ events : {
+ "click #create-btn": "handleCreateProject"
+ },
+ initialize : function(settings) {
+ $("#errorMsg").hide();
+ },
+ handleCreateProject : function(evt) {
+ // First make sure we can upload
+ var projectName = $('#path').val();
+ var description = $('#description').val();
+
+ console.log("Creating");
+ $.ajax({
+ async: "false",
+ url: "manager",
+ dataType: "json",
+ type: "POST",
+ data: {action:"create", name:projectName, description:description},
+ success: function(data) {
+ if (data.status == "success") {
+ if (data.action == "redirect") {
+ window.location = data.path;
+ }
+ }
+ else {
+ if (data.action == "login") {
+ window.location = "";
+ }
+ else {
+ $("#errorMsg").text("ERROR: " + data.message);
+ $("#errorMsg").slideDown("fast");
+ }
+ }
+ }
+ });
+
+ },
+ render: function() {
+ }
+});
+
+var tableSorterView;
+$(function() {
+ projectHeaderView = new azkaban.ProjectHeaderView({el:$( '#all-jobs-content'), successMsg: successMessage, errorMsg: errorMessage });
+ projectTableView = new azkaban.ProjectTableView({el:$('#all-jobs')});
+ tableSorterView = new azkaban.TableSorter({el:$('#all-jobs'), initialSort: $('.tb-name')});
+ uploadView = new azkaban.CreateProjectView({el:$('#create-project')});
+});