azkaban-developers

Merge pull request #362 from Victsm/validator-plugin Add

12/5/2014 8:08:57 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 54be62a..17865bb 100644
--- a/azkaban-common/src/main/java/azkaban/project/ProjectManager.java
+++ b/azkaban-common/src/main/java/azkaban/project/ProjectManager.java
@@ -35,6 +35,7 @@ import azkaban.flow.Flow;
 import azkaban.project.ProjectLogEvent.EventType;
 import azkaban.project.validator.ValidationStatus;
 import azkaban.project.validator.ValidationReport;
+import azkaban.project.validator.ValidatorConfigs;
 import azkaban.project.validator.ValidatorManager;
 import azkaban.project.validator.XmlValidatorManager;
 import azkaban.user.Permission;
@@ -47,7 +48,6 @@ import azkaban.utils.Utils;
 public class ProjectManager {
   private static final Logger logger = Logger.getLogger(ProjectManager.class);
 
-  public static final String PROJECT_ARCHIVE_FILE_PATH = "project.archive.file.path";
 
   private ConcurrentHashMap<Integer, Project> projectsById =
       new ConcurrentHashMap<Integer, Project>();
@@ -79,7 +79,7 @@ public class ProjectManager {
     // Each validator will take certain key/value pairs from the prop to initialize
     // itself.
     Props prop = new Props(props);
-    prop.put(PROJECT_ARCHIVE_FILE_PATH, "initialize");
+    prop.put(ValidatorConfigs.PROJECT_ARCHIVE_FILE_PATH, "initialize");
     loadAllProjects();
   }
 
@@ -118,6 +118,10 @@ public class ProjectManager {
     return new ArrayList<String>(projectsByName.keySet());
   }
 
+  public Props getProps() {
+    return props;
+  }
+
   public List<Project> getUserProjects(User user) {
     ArrayList<Project> array = new ArrayList<Project>();
     for (Project project : projectsById.values()) {
@@ -350,7 +354,7 @@ public class ProjectManager {
   }
 
   public Map<String, ValidationReport> uploadProject(Project project, File archive, String fileType,
-      User uploader) throws ProjectManagerException {
+      User uploader, Props additionalProps) throws ProjectManagerException {
     logger.info("Uploading files to " + project.getName());
 
     // Unzip.
@@ -374,7 +378,8 @@ public class ProjectManager {
     // key, it is necessary to create a new instance of Props to make sure these different
     // values are isolated from each other.
     Props prop = new Props(props);
-    prop.put(PROJECT_ARCHIVE_FILE_PATH, archive.getAbsolutePath());
+    prop.putAll(additionalProps);
+    prop.put(ValidatorConfigs.PROJECT_ARCHIVE_FILE_PATH, archive.getAbsolutePath());
     // Basically, we want to make sure that for different invocations to the uploadProject method,
     // the validators are using different values for the PROJECT_ARCHIVE_FILE_PATH configuration key.
     // In addition, we want to reload the validator objects for each upload, so that
diff --git a/azkaban-common/src/main/java/azkaban/project/validator/ValidatorConfigs.java b/azkaban-common/src/main/java/azkaban/project/validator/ValidatorConfigs.java
new file mode 100644
index 0000000..56beb00
--- /dev/null
+++ b/azkaban-common/src/main/java/azkaban/project/validator/ValidatorConfigs.java
@@ -0,0 +1,36 @@
+package azkaban.project.validator;
+
+public class ValidatorConfigs {
+
+  private ValidatorConfigs() {} // Prevents instantiation
+
+  /** Key for the config param specifying the directory containing validator JAR files **/
+  public static final String VALIDATOR_PLUGIN_DIR = "project.validators.dir";
+
+  /** Default validator directory **/
+  public static final String DEFAULT_VALIDATOR_DIR = "validators";
+
+  /** Key for the config param specifying the location of validator xml configuration file, no default value **/
+  public static final String XML_FILE_PARAM = "project.validators.xml.file";
+
+  /** Key for the config param indicating whether the user choose to turn on the auto-fix feature **/
+  public static final String CUSTOM_AUTO_FIX_FLAG_PARAM = "project.validators.fix.flag";
+
+  /** Default custom auto fix flag. Turn auto-fix feature on by default. **/
+  public static final Boolean DEFAULT_CUSTOM_AUTO_FIX_FLAG = true;
+
+  /** Key for the config param indicating whether to show auto-fix related UI to the user **/
+  public static final String VALIDATOR_AUTO_FIX_PROMPT_FLAG_PARAM = "project.validators.fix.prompt";
+
+  /** Do not show auto-fix related UI by default **/
+  public static final Boolean DEFAULT_VALIDATOR_AUTO_FIX_PROMPT_FLAG = false;
+
+  /** Key for the config param specifying the label to be displayed with auto-fix UI **/
+  public static final String VALIDATOR_AUTO_FIX_PROMPT_LABEL_PARAM = "project.validators.fix.label";
+
+  /** Key for the config param specifying the link address with detailed information about auto-fix **/
+  public static final String VALIDATOR_AUTO_FIX_PROMPT_LINK_PARAM = "project.validators.fix.link";
+
+  /** Key for the confi param indicating path to the project archive file **/
+  public static final String PROJECT_ARCHIVE_FILE_PATH = "project.archive.file.path";
+}
diff --git a/azkaban-common/src/main/java/azkaban/project/validator/XmlValidatorManager.java b/azkaban-common/src/main/java/azkaban/project/validator/XmlValidatorManager.java
index 7a7e011..1254911 100644
--- a/azkaban-common/src/main/java/azkaban/project/validator/XmlValidatorManager.java
+++ b/azkaban-common/src/main/java/azkaban/project/validator/XmlValidatorManager.java
@@ -42,9 +42,6 @@ import azkaban.utils.Props;
 public class XmlValidatorManager implements ValidatorManager {
   private static final Logger logger = Logger.getLogger(XmlValidatorManager.class);
 
-  public static final String DEFAULT_VALIDATOR_DIR = "validators";
-  public static final String VALIDATOR_PLUGIN_DIR = "project.validators.dir";
-  public static final String XML_FILE_PARAM = "project.validators.xml.file";
   public static final String AZKABAN_VALIDATOR_TAG = "azkaban-validators";
   public static final String VALIDATOR_TAG = "validator";
   public static final String CLASSNAME_ATTR = "classname";
@@ -65,7 +62,7 @@ public class XmlValidatorManager implements ValidatorManager {
    * @param props
    */
   public XmlValidatorManager(Props props) {
-    validatorDirPath = props.getString(VALIDATOR_PLUGIN_DIR, DEFAULT_VALIDATOR_DIR);
+    validatorDirPath = props.getString(ValidatorConfigs.VALIDATOR_PLUGIN_DIR, ValidatorConfigs.DEFAULT_VALIDATOR_DIR);
     File validatorDir = new File(validatorDirPath);
     if (!validatorDir.canRead() || !validatorDir.isDirectory()) {
       logger.warn("Validator directory " + validatorDirPath
@@ -138,11 +135,11 @@ public class XmlValidatorManager implements ValidatorManager {
     DirectoryFlowLoader flowLoader = new DirectoryFlowLoader(log);
     validators.put(flowLoader.getValidatorName(), flowLoader);
 
-    if (!props.containsKey(XML_FILE_PARAM)) {
-      logger.warn("Azkaban properties file does not contain the key " + XML_FILE_PARAM);
+    if (!props.containsKey(ValidatorConfigs.XML_FILE_PARAM)) {
+      logger.warn("Azkaban properties file does not contain the key " + ValidatorConfigs.XML_FILE_PARAM);
       return;
     }
-    String xmlPath = props.get(XML_FILE_PARAM);
+    String xmlPath = props.get(ValidatorConfigs.XML_FILE_PARAM);
     File file = new File(xmlPath);
     if (!file.exists()) {
       logger.error("Azkaban validator configuration file " + xmlPath + " does not exist.");
diff --git a/azkaban-common/src/test/java/azkaban/project/validator/XmlValidatorManagerTest.java b/azkaban-common/src/test/java/azkaban/project/validator/XmlValidatorManagerTest.java
index 217e9a6..c1e1ab0 100644
--- a/azkaban-common/src/test/java/azkaban/project/validator/XmlValidatorManagerTest.java
+++ b/azkaban-common/src/test/java/azkaban/project/validator/XmlValidatorManagerTest.java
@@ -36,7 +36,7 @@ public class XmlValidatorManagerTest {
   public void testDefaultValidator() {
     Props props = new Props(baseProps);
     URL validatorUrl = Resources.getResource("project/testValidators");
-    props.put(XmlValidatorManager.VALIDATOR_PLUGIN_DIR, validatorUrl.getPath());
+    props.put(ValidatorConfigs.VALIDATOR_PLUGIN_DIR, validatorUrl.getPath());
 
     XmlValidatorManager manager = new XmlValidatorManager(props);
     assertEquals("XmlValidatorManager should contain only the default validator when no xml configuration "
@@ -54,9 +54,8 @@ public class XmlValidatorManagerTest {
     Props props = new Props(baseProps);
     URL validatorUrl = Resources.getResource("project/testValidators");
     URL configUrl = Resources.getResource("test-conf/azkaban-validators-test1.xml");
-    props.put(XmlValidatorManager.VALIDATOR_PLUGIN_DIR, validatorUrl.getPath());
-    props.put(XmlValidatorManager.XML_FILE_PARAM,
-        configUrl.getPath());
+    props.put(ValidatorConfigs.VALIDATOR_PLUGIN_DIR, validatorUrl.getPath());
+    props.put(ValidatorConfigs.XML_FILE_PARAM, configUrl.getPath());
 
     new XmlValidatorManager(props);
 
@@ -72,9 +71,8 @@ public class XmlValidatorManagerTest {
     Props props = new Props(baseProps);
     URL validatorUrl = Resources.getResource("project/testValidators");
     URL configUrl = Resources.getResource("test-conf/azkaban-validators-test2.xml");
-    props.put(XmlValidatorManager.VALIDATOR_PLUGIN_DIR, validatorUrl.getPath());
-    props.put(XmlValidatorManager.XML_FILE_PARAM,
-        configUrl.getPath());
+    props.put(ValidatorConfigs.VALIDATOR_PLUGIN_DIR, validatorUrl.getPath());
+    props.put(ValidatorConfigs.XML_FILE_PARAM, configUrl.getPath());
 
     XmlValidatorManager manager = new XmlValidatorManager(props);
     assertEquals("XmlValidatorManager should contain 2 validators.", manager.getValidatorsInfo().size(), 2);
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 45e52f1..3f7de9c 100644
--- a/azkaban-webserver/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/azkaban-webserver/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -58,6 +58,7 @@ import azkaban.project.ProjectLogEvent;
 import azkaban.project.ProjectManager;
 import azkaban.project.ProjectManagerException;
 import azkaban.project.validator.ValidationReport;
+import azkaban.project.validator.ValidatorConfigs;
 import azkaban.scheduler.Schedule;
 import azkaban.scheduler.ScheduleManager;
 import azkaban.scheduler.ScheduleManagerException;
@@ -1340,6 +1341,13 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
             project.getUsersWithPermission(Type.ADMIN), ","));
         Permission perm = this.getPermissionObject(project, user, Type.ADMIN);
         page.add("userpermission", perm);
+        page.add("validatorFixPrompt", projectManager.getProps()
+            .getBoolean(ValidatorConfigs.VALIDATOR_AUTO_FIX_PROMPT_FLAG_PARAM,
+                ValidatorConfigs.DEFAULT_VALIDATOR_AUTO_FIX_PROMPT_FLAG));
+        page.add("validatorFixLabel", projectManager.getProps()
+            .get(ValidatorConfigs.VALIDATOR_AUTO_FIX_PROMPT_LABEL_PARAM));
+        page.add("validatorFixLink", projectManager.getProps()
+            .get(ValidatorConfigs.VALIDATOR_AUTO_FIX_PROMPT_LINK_PARAM));
 
         boolean adminPerm = perm.isPermissionSet(Type.ADMIN);
         if (adminPerm) {
@@ -1414,6 +1422,13 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
     User user = session.getUser();
     String projectName = (String) multipart.get("project");
     Project project = projectManager.getProject(projectName);
+    String autoFix = (String) multipart.get("fix");
+    Props props = new Props();
+    if (autoFix != null && autoFix.equals("on")) {
+      props.put(ValidatorConfigs.CUSTOM_AUTO_FIX_FLAG_PARAM, "true");
+    } else {
+      props.put(ValidatorConfigs.CUSTOM_AUTO_FIX_FLAG_PARAM, "false");
+    }
 
     if (projectName == null || projectName.isEmpty()) {
       ret.put("error", "No project name found.");
@@ -1453,10 +1468,16 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
         out.close();
 
         Map<String, ValidationReport> reports = projectManager.uploadProject(
-            project, archiveFile, type, user);
+            project, archiveFile, type, user, props);
         StringBuffer message = 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/>");
+            }
+            message.append("<br/>");
+          }
           if (!report.getErrorMsgs().isEmpty()) {
             message.append("Validator " + reportEntry.getKey() + " reports errors:<ul>");
             for (String msg : report.getErrorMsgs()) {
diff --git a/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/projectmodals.vm b/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/projectmodals.vm
index ccbbbdd..3977134 100644
--- a/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/projectmodals.vm
+++ b/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/projectmodals.vm
@@ -33,6 +33,21 @@
                       <input type="file" class="form-control" id="file" name="file">
                     </div>
                   </div>
+                  #if ($validatorFixPrompt.booleanValue())
+                    <div class="form-group">
+                      <label for="fix" class="col-sm-3 control-label">
+                        $validatorFixLabel.toString()
+                        <a href=$validatorFixLink.toString() target="_blank">
+                          <span class="ui-icon ui-icon-info" style="display:inline-block;"></span>
+                        </a>
+                      </label>
+                      <div class="col-sm-3">
+                        <div class="checkbox">
+                          <input type="checkbox" id="fix" name="fix" checked="checked">
+                        </div>
+                      </div>
+                    </div>
+                  #end
                 </fieldset>
               </div>
               <div class="modal-footer">
diff --git a/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/projectpage.vm b/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/projectpage.vm
index 360c9db..d747f68 100644
--- a/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/projectpage.vm
+++ b/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/projectpage.vm
@@ -38,6 +38,7 @@
       var projectName = "$project.name";
     </script>
     <link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css" />
+    <link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui.css">
   </head>
   <body>
 
diff --git a/azkaban-webserver/src/restli/java/azkaban/restli/ProjectManagerResource.java b/azkaban-webserver/src/restli/java/azkaban/restli/ProjectManagerResource.java
index a3b61b8..0c48564 100644
--- a/azkaban-webserver/src/restli/java/azkaban/restli/ProjectManagerResource.java
+++ b/azkaban-webserver/src/restli/java/azkaban/restli/ProjectManagerResource.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2014 LinkedIn Corp.
- * 
+ *
  * 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
@@ -19,6 +19,7 @@ import java.io.File;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
+
 import javax.servlet.ServletException;
 
 import org.apache.commons.io.FileUtils;
@@ -30,6 +31,7 @@ import azkaban.project.ProjectManagerException;
 import azkaban.user.Permission;
 import azkaban.user.User;
 import azkaban.user.UserManagerException;
+import azkaban.utils.Props;
 import azkaban.utils.Utils;
 import azkaban.webapp.AzkabanWebServer;
 
@@ -95,9 +97,10 @@ public class ProjectManagerResource extends ResourceContextHolder {
       // complete.
       logger.info("Downloading package from " + packageUrl);
       FileUtils.copyURLToFile(url, archiveFile);
+      Props props = new Props();
 
       logger.info("Downloaded to " + archiveFile.toString());
-      projectManager.uploadProject(project, archiveFile, "zip", user);
+      projectManager.uploadProject(project, archiveFile, "zip", user, props);
     } catch (IOException e) {
       String errorMsg =
           "Download of URL " + packageUrl + " to " + archiveFile.toString()