azkaban-aplcache

Merge pull request #393 from Victsm/validator-plugin Separate

1/29/2015 7:37:50 PM

Details

diff --git a/azkaban-common/src/main/java/azkaban/project/ProjectManager.java b/azkaban-common/src/main/java/azkaban/project/ProjectManager.java
index 9bcbf05..4af1c9e 100644
--- a/azkaban-common/src/main/java/azkaban/project/ProjectManager.java
+++ b/azkaban-common/src/main/java/azkaban/project/ProjectManager.java
@@ -80,6 +80,9 @@ public class ProjectManager {
     // initialize itself.
     Props prop = new Props(props);
     prop.put(ValidatorConfigs.PROJECT_ARCHIVE_FILE_PATH, "initialize");
+    // By instantiating an object of XmlValidatorManager, this will verify the
+    // config files for the validators.
+    new XmlValidatorManager(prop);
     loadAllProjects();
   }
 
@@ -360,7 +363,7 @@ public class ProjectManager {
    * caller of this method should call method
    * {@ProjectFileHandler.deleteLocalFile}
    * to delete the temporary file.
-   * 
+   *
    * @param project
    * @param version - latest version is used if value is -1
    * @return ProjectFileHandler - null if can't find project zip file based on
diff --git a/azkaban-common/src/main/java/azkaban/project/validator/ValidationReport.java b/azkaban-common/src/main/java/azkaban/project/validator/ValidationReport.java
index 9cccffb..e586ca1 100644
--- a/azkaban-common/src/main/java/azkaban/project/validator/ValidationReport.java
+++ b/azkaban-common/src/main/java/azkaban/project/validator/ValidationReport.java
@@ -7,32 +7,43 @@ import java.util.Set;
  * The result of a project validation generated by a {@link ProjectValidator}. It contains
  * an enum of type {@link ValidationStatus} representing whether the validation passes,
  * generates warnings, or generates errors. Accordingly, three sets of String are also
- * maintained, storing the messages generated by the {@link ProjectValidator} at each of
- * the 3 {@link ValidationStatus} levels, i.e., {@link ValidationStatus#PASS},
- * {@link ValidationStatus#WARN}, and {@link ValidationStatus#ERROR}.
+ * maintained, storing the messages generated by the {@link ProjectValidator} at both
+ * {@link ValidationStatus#WARN} and {@link ValidationStatus#ERROR} level, as well as
+ * information messages associated with both levels.
  */
 public class ValidationReport {
 
   protected ValidationStatus _status;
-  protected Set<String> _passMsgs;
+  protected Set<String> _infoMsgs;
   protected Set<String> _warningMsgs;
   protected Set<String> _errorMsgs;
 
   public ValidationReport() {
     _status = ValidationStatus.PASS;
-    _passMsgs = new HashSet<String>();
+    _infoMsgs = new HashSet<String>();
     _warningMsgs = new HashSet<String>();
     _errorMsgs = new HashSet<String>();
   }
 
   /**
-   * Add a message with status level being {@link ValidationStatus#PASS}
+   * Add an information message associated with warning messages
    *
    * @param msgs
    */
-  public void addPassMsgs(Set<String> msgs) {
-    if (msgs != null) {
-      _passMsgs.addAll(msgs);
+  public void addWarnLevelInfoMsg(String msg) {
+    if (msg != null) {
+      _infoMsgs.add("WARN" + msg);
+    }
+  }
+
+  /**
+   * Add an information message associated with error messages
+   *
+   * @param msgs
+   */
+  public void addErrorLevelInfoMsg(String msg) {
+    if (msg != null) {
+      _infoMsgs.add("ERROR" + msg);
     }
   }
 
@@ -74,12 +85,12 @@ public class ValidationReport {
   }
 
   /**
-   * Retrieve the messages associated with status level {@link ValidationStatus#PASS}
+   * Retrieve the list of information messages.
    *
    * @return
    */
-  public Set<String> getPassMsgs() {
-    return _passMsgs;
+  public Set<String> getInfoMsgs() {
+    return _infoMsgs;
   }
 
   /**
@@ -100,4 +111,35 @@ public class ValidationReport {
     return _errorMsgs;
   }
 
+  /**
+   * Return the severity level this information message is associated with.
+   *
+   * @param msg
+   * @return
+   */
+  public static ValidationStatus getInfoMsgLevel(String msg) {
+    if (msg.startsWith("ERROR")) {
+      return ValidationStatus.ERROR;
+    }
+    if (msg.startsWith("WARN")) {
+      return ValidationStatus.WARN;
+    }
+    return ValidationStatus.PASS;
+  }
+
+  /**
+   * Get the raw information message.
+   *
+   * @param msg
+   * @return
+   */
+  public static String getInfoMsg(String msg) {
+    if (msg.startsWith("ERROR")) {
+      return msg.replaceFirst("ERROR", "");
+    }
+    if (msg.startsWith("WARN")) {
+      return msg.replaceFirst("WARN", "");
+    }
+    return msg;
+  }
 }
diff --git a/azkaban-common/src/test/java/azkaban/project/validator/ValidationReportTest.java b/azkaban-common/src/test/java/azkaban/project/validator/ValidationReportTest.java
new file mode 100644
index 0000000..4b4bb18
--- /dev/null
+++ b/azkaban-common/src/test/java/azkaban/project/validator/ValidationReportTest.java
@@ -0,0 +1,59 @@
+package azkaban.project.validator;
+
+import static org.junit.Assert.*;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+
+/**
+ * Test adding messages to {@link ValidationReport}
+ */
+public class ValidationReportTest {
+
+  @Test
+  public void testAddWarnLevelInfoMsg() {
+    ValidationReport report = new ValidationReport();
+    String msg = "test warn level info message.";
+    report.addWarnLevelInfoMsg(msg);
+    for (String info : report.getInfoMsgs()) {
+      assertEquals("Info message added through addWarnLevelInfoMsg should have level set to WARN",
+          ValidationReport.getInfoMsgLevel(info), ValidationStatus.WARN);
+      assertEquals("Retrieved info message does not match the original one.",
+          ValidationReport.getInfoMsg(info), msg);
+    }
+  }
+
+  @Test
+  public void testAddErrorLevelInfoMsg() {
+    ValidationReport report = new ValidationReport();
+    String msg = "test error level error message.";
+    report.addErrorLevelInfoMsg(msg);
+    for (String info : report.getInfoMsgs()) {
+      assertEquals("Info message added through addErrorLevelInfoMsg should have level set to ERROR",
+          ValidationReport.getInfoMsgLevel(info), ValidationStatus.ERROR);
+      assertEquals("Retrieved info message does not match the original one.",
+          ValidationReport.getInfoMsg(info), msg);
+    }
+  }
+
+  @Test
+  public void testAddMsgs() {
+    ValidationReport report = new ValidationReport();
+    Set<String> msgs = new HashSet<String>();
+    msgs.add("test msg 1.");
+    msgs.add("test msg 2.");
+    report.addWarningMsgs(msgs);
+    assertEquals("Level of severity is not warn.",
+        report.getStatus(), ValidationStatus.WARN);
+    report.addErrorMsgs(msgs);
+    assertEquals("Number of error messages retrieved does not match.",
+        report.getErrorMsgs().size(), 2);
+    assertEquals("Number of warn messages retrieved does not match.",
+        report.getWarningMsgs().size(), 2);
+    assertEquals("Level of severity is not error.",
+        report.getStatus(), ValidationStatus.ERROR);
+  }
+
+}
diff --git a/azkaban-soloserver/.gitignore b/azkaban-soloserver/.gitignore
index 5ab4d17..eb3d928 100644
--- a/azkaban-soloserver/.gitignore
+++ b/azkaban-soloserver/.gitignore
@@ -4,5 +4,5 @@ executions/
 plugins/
 projects/
 temp/
-validators/
+validator_jars/
 *.log
diff --git a/azkaban-webserver/src/main/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java b/azkaban-webserver/src/main/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
index 5b54af4..12c5993 100644
--- a/azkaban-webserver/src/main/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
+++ b/azkaban-webserver/src/main/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
@@ -52,6 +52,8 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
       .forPattern("z");
   private static final String AZKABAN_SUCCESS_MESSAGE =
       "azkaban.success.message";
+  private static final String AZKABAN_WARN_MESSAGE =
+      "azkaban.warn.message";
   private static final String AZKABAN_FAILURE_MESSAGE =
       "azkaban.failure.message";
 
@@ -222,6 +224,20 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
   }
 
   /**
+   * Sets a warning message in azkaban.warn.message in the cookie. This will
+   * be used by the web client javascript to somehow display the message
+   *
+   * @param response
+   * @param warnMsg
+   */
+  protected void setWarnMessageInCookie(HttpServletResponse response,
+      String errorMsg) {
+    Cookie cookie = new Cookie(AZKABAN_WARN_MESSAGE, errorMsg);
+    cookie.setPath("/");
+    response.addCookie(cookie);
+  }
+
+  /**
    * Sets a message in azkaban.success.message in the cookie. This will be used
    * by the web client javascript to somehow display the message
    *
@@ -251,6 +267,21 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
   }
 
   /**
+   * Retrieves a warn message from a cookie. azkaban.warn.message
+   *
+   * @param request
+   * @return
+   */
+  protected String getWarnMessageFromCookie(HttpServletRequest request) {
+    Cookie cookie = getCookieByName(request, AZKABAN_WARN_MESSAGE);
+
+    if (cookie == null) {
+      return null;
+    }
+    return cookie.getValue();
+  }
+
+  /**
    * Retrieves a success message from a cookie. azkaban.failure.message
    *
    * @param request
@@ -312,6 +343,11 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
         : errorMsg);
     setErrorMessageInCookie(resp, null);
 
+    String warnMsg = getWarnMessageFromCookie(req);
+    page.add("warn_message", warnMsg == null || warnMsg.isEmpty() ? "null"
+        : warnMsg);
+    setWarnMessageInCookie(resp, null);
+
     String successMsg = getSuccessMessageFromCookie(req);
     page.add("success_message",
         successMsg == null || successMsg.isEmpty() ? "null" : successMsg);
diff --git a/azkaban-webserver/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/azkaban-webserver/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index e7d5883..34a8fd8 100644
--- a/azkaban-webserver/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/azkaban-webserver/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -426,9 +426,9 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 
   /**
    * Download project zip file from DB and send it back client.
-   * 
+   *
    * This method requires a project name and an optional project version.
-   * 
+   *
    * @param req
    * @param resp
    * @param session
@@ -1582,34 +1582,50 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
         Map<String, ValidationReport> reports =
             projectManager.uploadProject(project, archiveFile, type, user,
                 props);
-        StringBuffer message = new StringBuffer();
+        StringBuffer errorMsgs = new StringBuffer();
+        StringBuffer warnMsgs = new StringBuffer();
         for (Entry<String, ValidationReport> reportEntry : reports.entrySet()) {
           ValidationReport report = reportEntry.getValue();
-          if (!report.getPassMsgs().isEmpty()) {
-            for (String msg : report.getPassMsgs()) {
-              message.append(msg + "<br/>");
+          if (!report.getInfoMsgs().isEmpty()) {
+            for (String msg : report.getInfoMsgs()) {
+              switch (ValidationReport.getInfoMsgLevel(msg)) {
+                case ERROR:
+                  errorMsgs.append(ValidationReport.getInfoMsg(msg) + "<br/>");
+                  break;
+                case WARN:
+                  warnMsgs.append(ValidationReport.getInfoMsg(msg) + "<br/>");
+                  break;
+                default:
+                  break;
+              }
             }
-            message.append("<br/>");
           }
           if (!report.getErrorMsgs().isEmpty()) {
-            message.append("Validator " + reportEntry.getKey()
+            errorMsgs.append("Validator " + reportEntry.getKey()
                 + " reports errors:<ul>");
             for (String msg : report.getErrorMsgs()) {
-              message.append("<li>" + msg + "</li>");
+              errorMsgs.append("<li>" + msg + "</li>");
             }
-            message.append("</ul>");
+            errorMsgs.append("</ul>");
           }
           if (!report.getWarningMsgs().isEmpty()) {
-            message.append("Validator " + reportEntry.getKey()
+            warnMsgs.append("Validator " + reportEntry.getKey()
                 + " reports warnings:<ul>");
             for (String msg : report.getWarningMsgs()) {
-              message.append("<li>" + msg + "</li>");
+              warnMsgs.append("<li>" + msg + "</li>");
             }
-            message.append("</ul>");
+            warnMsgs.append("</ul>");
           }
         }
-        if (message.length() > 0) {
-          ret.put("error", message.toString());
+        if (errorMsgs.length() > 0) {
+          // If putting more than 4000 characters in the cookie, the entire message
+          // will somehow get discarded.
+          ret.put("error", errorMsgs.length() > 4000 ?
+              errorMsgs.substring(0, 4000) : errorMsgs.toString());
+        }
+        if (warnMsgs.length() > 0) {
+          ret.put("warn", warnMsgs.length() > 4000 ?
+              warnMsgs.substring(0, 4000) : warnMsgs.toString());
         }
       } catch (Exception e) {
         logger.info("Installation Failed.", e);
@@ -1643,6 +1659,10 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
       setErrorMessageInCookie(resp, ret.get("error"));
     }
 
+    if (ret.containsKey("warn")) {
+      setWarnMessageInCookie(resp, ret.get("warn"));
+    }
+
     resp.sendRedirect(req.getRequestURI() + "?project=" + projectName);
   }
 
diff --git a/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/alerts.vm b/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/alerts.vm
index 10831fb..1516519 100644
--- a/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/alerts.vm
+++ b/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/alerts.vm
@@ -21,6 +21,11 @@
         <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
         $error_message
       </div>
+#elseif ($warn_message != "null")
+      <div class="alert alert-warning alert-dismissable">
+        <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
+        $warn_message
+      </div>
 #elseif ($success_message != "null")
       <div class="alert alert-success">
         <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>