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..a4fa578 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 additionalProp) 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(additionalProp);
+ 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..add2d05
--- /dev/null
+++ b/azkaban-common/src/main/java/azkaban/project/validator/ValidatorConfigs.java
@@ -0,0 +1,34 @@
+package azkaban.project.validator;
+
+public class ValidatorConfigs {
+
+ /** 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..cc2056a 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 prop = new Props();
+ if (autoFix == null) {
+ prop.put(ValidatorConfigs.CUSTOM_AUTO_FIX_FLAG_PARAM, "false");
+ } else if (autoFix.equals("on")) {
+ prop.put(ValidatorConfigs.CUSTOM_AUTO_FIX_FLAG_PARAM, "true");
+ }
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, prop);
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..08331f3 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
@@ -34,6 +34,18 @@
</div>
</div>
</fieldset>
+ #if ($validatorFixPrompt.booleanValue())
+ <fieldset class="form-horizontal">
+ <div class="form-group">
+ <label for="fix" class="col-sm-3 control-label">
+ $validatorFixLabel.toString()<a href=$validatorFixLink.toString() target="_blank">?</a>
+ </label>
+ <div class="col-sm-9">
+ <input type="checkbox" class="form-control" id="fix" name="fix" checked>
+ </div>
+ </div>
+ </fieldset>
+ #end
</div>
<div class="modal-footer">
<input type="hidden" name="project" value="$project.name">
diff --git a/azkaban-webserver/src/restli/java/azkaban/restli/ProjectManagerResource.java b/azkaban-webserver/src/restli/java/azkaban/restli/ProjectManagerResource.java
index a3b61b8..cce43d0 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 prop = new Props();
logger.info("Downloaded to " + archiveFile.toString());
- projectManager.uploadProject(project, archiveFile, "zip", user);
+ projectManager.uploadProject(project, archiveFile, "zip", user, prop);
} catch (IOException e) {
String errorMsg =
"Download of URL " + packageUrl + " to " + archiveFile.toString()