Details
diff --git a/azkaban-common/src/main/java/azkaban/project/ProjectLogEvent.java b/azkaban-common/src/main/java/azkaban/project/ProjectLogEvent.java
index 5512291..1e78168 100644
--- a/azkaban-common/src/main/java/azkaban/project/ProjectLogEvent.java
+++ b/azkaban-common/src/main/java/azkaban/project/ProjectLogEvent.java
@@ -33,7 +33,8 @@ public class ProjectLogEvent {
SCHEDULE(7),
SLA(8),
PROXY_USER(9),
- PURGE(10);
+ PURGE(10),
+ PROPERTY_OVERRIDE(11);
private int numVal;
@@ -47,30 +48,32 @@ public class ProjectLogEvent {
public static EventType fromInteger(int x) {
switch (x) {
- case 1:
- return CREATED;
- case 2:
- return DELETED;
- case 3:
- return USER_PERMISSION;
- case 4:
- return GROUP_PERMISSION;
- case 5:
- return DESCRIPTION;
- case 6:
- return UPLOADED;
- case 7:
- return SCHEDULE;
- case 8:
- return SLA;
- case 9:
- return PROXY_USER;
- case 10:
- return PURGE;
- case 128:
- return ERROR;
- default:
- return ERROR;
+ case 1:
+ return CREATED;
+ case 2:
+ return DELETED;
+ case 3:
+ return USER_PERMISSION;
+ case 4:
+ return GROUP_PERMISSION;
+ case 5:
+ return DESCRIPTION;
+ case 6:
+ return UPLOADED;
+ case 7:
+ return SCHEDULE;
+ case 8:
+ return SLA;
+ case 9:
+ return PROXY_USER;
+ case 10:
+ return PURGE;
+ case 11:
+ return PROPERTY_OVERRIDE;
+ case 128:
+ return ERROR;
+ default:
+ return ERROR;
}
}
}
diff --git a/azkaban-common/src/main/java/azkaban/project/ProjectManager.java b/azkaban-common/src/main/java/azkaban/project/ProjectManager.java
index a069c29..b079bbd 100644
--- a/azkaban-common/src/main/java/azkaban/project/ProjectManager.java
+++ b/azkaban-common/src/main/java/azkaban/project/ProjectManager.java
@@ -32,9 +32,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import azkaban.flow.Flow;
-import azkaban.project.DirectoryFlowLoader;
import azkaban.project.ProjectLogEvent.EventType;
-import azkaban.project.ProjectWhitelist.WhitelistType;
import azkaban.project.validator.ValidationReport;
import azkaban.project.validator.ValidationStatus;
import azkaban.project.validator.ValidatorConfigs;
@@ -44,6 +42,7 @@ import azkaban.user.Permission;
import azkaban.user.Permission.Type;
import azkaban.user.User;
import azkaban.utils.Props;
+import azkaban.utils.PropsUtils;
import azkaban.utils.Utils;
public class ProjectManager {
@@ -354,16 +353,22 @@ public class ProjectManager {
return projectLoader.fetchProjectProperty(project, jobName + ".jor");
}
- public void setJobOverrideProperty(Project project, Props prop, String jobName)
+ public void setJobOverrideProperty(Project project, Props prop, String jobName, User modifier)
throws ProjectManagerException {
prop.setSource(jobName + ".jor");
Props oldProps =
projectLoader.fetchProjectProperty(project, prop.getSource());
+
if (oldProps == null) {
projectLoader.uploadProjectProperty(project, prop);
} else {
projectLoader.updateProjectProperty(project, prop);
}
+
+ String diffMessage = PropsUtils.getPropertyDiff(oldProps, prop);
+
+ projectLoader.postEvent(project, EventType.PROPERTY_OVERRIDE,
+ modifier.getUserId(), diffMessage);
return;
}
diff --git a/azkaban-common/src/main/java/azkaban/utils/PropsUtils.java b/azkaban-common/src/main/java/azkaban/utils/PropsUtils.java
index c013c4b..9d5171b 100644
--- a/azkaban-common/src/main/java/azkaban/utils/PropsUtils.java
+++ b/azkaban-common/src/main/java/azkaban/utils/PropsUtils.java
@@ -37,6 +37,9 @@ import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
+import com.google.common.collect.Maps;
+import com.google.common.collect.MapDifference;
+
import azkaban.executor.ExecutableFlowBase;
import azkaban.flow.CommonJobProperties;
@@ -365,4 +368,44 @@ public class PropsUtils {
return propsMap;
}
+
+ /**
+ * @param oldProps
+ * @param newProps
+ * @return the difference between oldProps and newProps.
+ */
+ public static String getPropertyDiff(Props oldProps, Props newProps) {
+
+ StringBuilder builder = new StringBuilder("");
+
+ MapDifference<String, String> md =
+ Maps.difference(toStringMap(oldProps, false), toStringMap(newProps, false));
+
+ Map<String, String> newlyCreatedProperty = md.entriesOnlyOnRight();
+ if (newlyCreatedProperty != null && newlyCreatedProperty.size() > 0) {
+ builder.append("Newly created Properties: ");
+ newlyCreatedProperty.forEach((k, v) -> {
+ builder.append("[ " + k + ", " + v + "], ");
+ });
+ builder.append("\n");
+ }
+
+ Map<String, String> deletedProperty = md.entriesOnlyOnLeft();
+ if (deletedProperty != null && deletedProperty.size() > 0) {
+ builder.append("Deleted Properties: ");
+ deletedProperty.forEach((k, v) -> {
+ builder.append("[ " + k + ", " + v + "], ");
+ });
+ builder.append("\n");
+ }
+
+ Map<String, MapDifference.ValueDifference<String>> diffProperties = md.entriesDiffering();
+ if (diffProperties != null && diffProperties.size() > 0) {
+ builder.append("Modified Properties: ");
+ diffProperties.forEach((k, v) -> {
+ builder.append("[ " + k + ", " + v.leftValue() + "-->" + v.rightValue() + "], ");
+ });
+ }
+ return builder.toString();
+ }
}
diff --git a/azkaban-common/src/test/java/azkaban/utils/PropsUtilsTest.java b/azkaban-common/src/test/java/azkaban/utils/PropsUtilsTest.java
index 54464cd..3201d65 100644
--- a/azkaban-common/src/test/java/azkaban/utils/PropsUtilsTest.java
+++ b/azkaban-common/src/test/java/azkaban/utils/PropsUtilsTest.java
@@ -208,6 +208,37 @@ public class PropsUtilsTest {
failIfNotException(props);
}
+ @Test
+ public void testGetPropertyDiff() throws IOException {
+ Props oldProps = new Props();
+ Props newProps1 = new Props();
+
+ oldProps.put("a", "a_value1");
+ oldProps.put("b", "b_value1");
+
+ newProps1.put("b", "b_value2");
+
+ String message1 = PropsUtils.getPropertyDiff(oldProps, newProps1);
+ Assert.assertEquals(message1, "Deleted Properties: [ a, a_value1], \nModified Properties: [ b, b_value1-->b_value2], ");
+
+ Props newProps2 = new Props();
+
+ newProps2.put("a", "a_value1");
+ newProps2.put("b", "b_value1");
+ newProps2.put("c", "c_value1");
+
+ String message2 = PropsUtils.getPropertyDiff(oldProps, newProps2);
+ Assert.assertEquals(message2, "Newly created Properties: [ c, c_value1], \n");
+
+ Props newProps3 = new Props();
+
+ newProps3.put("b", "b_value1");
+ newProps3.put("c", "a_value1");
+
+ String message3 = PropsUtils.getPropertyDiff(oldProps, newProps3);
+ Assert.assertEquals(message3, "Newly created Properties: [ c, a_value1], \nDeleted Properties: [ a, a_value1], \n");
+ }
+
private void failIfNotException(Props props) {
try {
PropsUtils.resolveProps(props);
diff --git a/azkaban-web-server/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/azkaban-web-server/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index 685cc80..2ba49ef 100644
--- a/azkaban-web-server/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/azkaban-web-server/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -25,7 +25,6 @@ import java.io.OutputStream;
import java.io.Writer;
import java.security.AccessControlException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -298,7 +297,7 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
}
} else if (ajaxName.equals("setJobOverrideProperty")) {
if (handleAjaxPermission(project, user, Type.WRITE, ret)) {
- ajaxSetJobOverrideProperty(project, ret, req);
+ ajaxSetJobOverrideProperty(project, ret, req, user);
}
} else {
ret.put("error", "Cannot execute command " + ajaxName);
@@ -734,7 +733,7 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
}
private void ajaxSetJobOverrideProperty(Project project,
- HashMap<String, Object> ret, HttpServletRequest req)
+ HashMap<String, Object> ret, HttpServletRequest req, User user)
throws ServletException {
String flowName = getParam(req, "flowName");
String jobName = getParam(req, "jobName");
@@ -756,7 +755,7 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
@SuppressWarnings("unchecked")
Props overrideParams = new Props(null, jobParamGroup);
try {
- projectManager.setJobOverrideProperty(project, overrideParams, jobName);
+ projectManager.setJobOverrideProperty(project, overrideParams, jobName, user);
} catch (ProjectManagerException e) {
ret.put("error", "Failed to upload job override property");
}
diff --git a/azkaban-web-server/src/web/js/azkaban/view/project-logs.js b/azkaban-web-server/src/web/js/azkaban/view/project-logs.js
index f4bd5c3..178b678 100644
--- a/azkaban-web-server/src/web/js/azkaban/view/project-logs.js
+++ b/azkaban-web-server/src/web/js/azkaban/view/project-logs.js
@@ -29,7 +29,8 @@ var typeMapping = {
"GROUP_PERMISSION" : "Group Permission",
"DESCRIPTION" : "Description Set",
"SCHEDULE": "Schedule",
- "UPLOADED": "Uploaded"
+ "UPLOADED": "Uploaded",
+ "PROPERTY_OVERRIDE": "Property Override"
};
var projectLogView;
@@ -88,13 +89,20 @@ azkaban.ProjectLogView = Backbone.View.extend({
$(containerUser).text(user);
var containerType = document.createElement("td");
+ var containerMessage = document.createElement("td");
+
+ // If the event is a property override change, highlight by red color.
+ if(type == "PROPERTY_OVERRIDE"){
+ $(containerMessage).css("color", "red");
+ $(containerMessage).css("white-space", "pre-wrap");
+ $(containerType).css("color", "red");
+ }
+
$(containerType).addClass("type");
$(containerType).addClass(type);
$(containerType).text(typeMapping[type] ? typeMapping[type] : type);
- var containerMessage = document.createElement("td");
- $(containerMessage).addClass("message");
- $(containerMessage).text(message);
+ $(containerMessage).html(message);
$(containerEvent).append(containerTime);
$(containerEvent).append(containerUser);