azkaban-uncached
Changes
src/java/azkaban/scheduler/JdbcScheduleLoader.java 215(+131 -84)
src/java/azkaban/scheduler/Schedule.java 226(+133 -93)
src/java/azkaban/scheduler/ScheduleManager.java 69(+56 -13)
src/java/azkaban/utils/EmailMessage.java 20(+13 -7)
src/java/azkaban/webapp/servlet/ScheduleServlet.java 211(+210 -1)
src/java/azkaban/webapp/servlet/velocity/flowpage.vm 162(+161 -1)
src/sql/create_schedule_table.sql 2(+2 -0)
src/web/css/azkaban.css 332(+324 -8)
src/web/js/azkaban.exflow.options.view.js 421(+421 -0)
src/web/js/azkaban.flow.view.js 173(+91 -82)
src/web/js/azkaban.scheduled.view.js 285(+270 -15)
Details
src/java/azkaban/scheduler/JdbcScheduleLoader.java 215(+131 -84)
diff --git a/src/java/azkaban/scheduler/JdbcScheduleLoader.java b/src/java/azkaban/scheduler/JdbcScheduleLoader.java
index febfd09..e950938 100644
--- a/src/java/azkaban/scheduler/JdbcScheduleLoader.java
+++ b/src/java/azkaban/scheduler/JdbcScheduleLoader.java
@@ -17,12 +17,14 @@
package azkaban.scheduler;
+import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import javax.sql.DataSource;
@@ -36,58 +38,84 @@ import org.joda.time.ReadablePeriod;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
+import azkaban.sla.SLA;
+import azkaban.sla.SLAManagerException;
+import azkaban.sla.JdbcSLALoader.EncodingType;
import azkaban.utils.DataSourceUtils;
+import azkaban.utils.GZIPUtils;
+import azkaban.utils.JSONUtils;
import azkaban.utils.Props;
public class JdbcScheduleLoader implements ScheduleLoader {
+
+ private static Logger logger = Logger.getLogger(JdbcScheduleLoader.class);
- private DataSource dataSource;
- private static DateTimeFormatter FILE_DATEFORMAT = DateTimeFormat.forPattern("yyyy-MM-dd.HH.mm.ss.SSS");
- private static Logger logger = Logger.getLogger(JdbcScheduleLoader.class);
+ public static enum EncodingType {
+ PLAIN(1), GZIP(2);
+
+ private int numVal;
+
+ EncodingType(int numVal) {
+ this.numVal = numVal;
+ }
+
+ public int getNumVal() {
+ return numVal;
+ }
+
+ public static EncodingType fromInteger(int x) {
+ switch (x) {
+ case 1:
+ return PLAIN;
+ case 2:
+ return GZIP;
+ default:
+ return PLAIN;
+ }
+ }
+ }
- private static final String SCHEDULE = "schedule";
- // schedule ids
-// private static final String PROJECTGUID = "projectGuid";
-// private static final String FLOWGUID = "flowGuid";
-//
-// private static final String SCHEDULEID = "scheduleId";
- private static final String PROJECTID = "projectId";
- private static final String PROJECTNAME = "projectName";
- private static final String FLOWNAME = "flowName";
- // status
- private static final String STATUS = "status";
- // schedule info
- private static final String FIRSTSCHEDTIME = "firstSchedTime";
- private static final String TIMEZONE = "timezone";
- private static final String PERIOD = "period";
- private static final String LASTMODIFYTIME = "lastModifyTime";
- private static final String NEXTEXECTIME = "nextExecTime";
- // auditing info
- private static final String SUBMITTIME = "submitTime";
- private static final String SUBMITUSER = "userSubmit";
+ private DataSource dataSource;
+ private EncodingType defaultEncodingType = EncodingType.GZIP;
private static final String scheduleTableName = "schedules";
- private static String SELECT_SCHEDULE_BY_KEY =
- "SELECT project_id, project_name, flow_name, status, first_sched_time, timezone, period, last_modify_time, next_exec_time, submit_time, submit_user FROM " + scheduleTableName + " WHERE project_id=? AND flow_name=?";
-
private static String SELECT_ALL_SCHEDULES =
- "SELECT project_id, project_name, flow_name, status, first_sched_time, timezone, period, last_modify_time, next_exec_time, submit_time, submit_user FROM " + scheduleTableName;
+ "SELECT project_id, project_name, flow_name, status, first_sched_time, timezone, period, last_modify_time, next_exec_time, submit_time, submit_user, enc_type, schedule_options FROM " + scheduleTableName;
private static String INSERT_SCHEDULE =
- "INSERT INTO " + scheduleTableName + " ( project_id, project_name, flow_name, status, first_sched_time, timezone, period, last_modify_time, next_exec_time, submit_time, submit_user) values (?,?,?,?,?,?,?,?,?,?,?)";
+ "INSERT INTO " + scheduleTableName + " ( project_id, project_name, flow_name, status, first_sched_time, timezone, period, last_modify_time, next_exec_time, submit_time, submit_user, enc_type, schedule_options) values (?,?,?,?,?,?,?,?,?,?,?,?,?)";
private static String REMOVE_SCHEDULE_BY_KEY =
"DELETE FROM " + scheduleTableName + " WHERE project_id=? AND flow_name=?";
private static String UPDATE_SCHEDULE_BY_KEY =
- "UPDATE " + scheduleTableName + " SET status=?, first_sched_time=?, timezone=?, period=?, last_modify_time=?, next_exec_time=?, submit_time=?, submit_user=? WHERE project_id=? AND flow_name=?";
+ "UPDATE " + scheduleTableName + " SET status=?, first_sched_time=?, timezone=?, period=?, last_modify_time=?, next_exec_time=?, submit_time=?, submit_user=?, enc_type=?, schedule_options=? WHERE project_id=? AND flow_name=?";
private static String UPDATE_NEXT_EXEC_TIME =
"UPDATE " + scheduleTableName + " SET next_exec_time=? WHERE project_id=? AND flow_name=?";
+ private Connection getConnection() throws ScheduleManagerException {
+ Connection connection = null;
+ try {
+ connection = dataSource.getConnection();
+ } catch (Exception e) {
+ DbUtils.closeQuietly(connection);
+ throw new ScheduleManagerException("Error getting DB connection.", e);
+ }
+
+ return connection;
+ }
+
+ public EncodingType getDefaultEncodingType() {
+ return defaultEncodingType;
+ }
+ public void setDefaultEncodingType(EncodingType defaultEncodingType) {
+ this.defaultEncodingType = defaultEncodingType;
+ }
+
public JdbcScheduleLoader(Props props) {
String databaseType = props.getString("database.type");
@@ -106,15 +134,7 @@ public class JdbcScheduleLoader implements ScheduleLoader {
@Override
public List<Schedule> loadSchedules() throws ScheduleManagerException {
logger.info("Loading all schedules from db.");
- Connection connection = null;
- try {
- connection = dataSource.getConnection();
- } catch (SQLException e1) {
- logger.error("Failed to get db connection!");
- e1.printStackTrace();
- DbUtils.closeQuietly(connection);
- throw new ScheduleManagerException("Failed to get db connection!", e1);
- }
+ Connection connection = getConnection();
QueryRunner runner = new QueryRunner();
ResultSetHandler<List<Schedule>> handler = new ScheduleResultHandler();
@@ -139,7 +159,7 @@ public class JdbcScheduleLoader implements ScheduleLoader {
}
else {
try {
- updateNextExecTime(sched, connection);
+ updateNextExecTime(sched);
} catch (Exception e) {
DbUtils.closeQuietly(connection);
throw new ScheduleManagerException("Update next execution time failed.", e);
@@ -172,10 +192,29 @@ public class JdbcScheduleLoader implements ScheduleLoader {
}
}
- @Override
+
public void insertSchedule(Schedule s) throws ScheduleManagerException {
logger.info("Inserting schedule " + s.getScheduleName() + " into db.");
+ insertSchedule(s, defaultEncodingType);
+ }
+ public void insertSchedule(Schedule s, EncodingType encType) throws ScheduleManagerException {
+
+ String json = JSONUtils.toJSON(s.optionToObject());
+ byte[] data = null;
+ try {
+ byte[] stringData = json.getBytes("UTF-8");
+ data = stringData;
+
+ if (encType == EncodingType.GZIP) {
+ data = GZIPUtils.gzipBytes(stringData);
+ }
+ logger.debug("NumChars: " + json.length() + " UTF-8:" + stringData.length + " Gzip:"+ data.length);
+ }
+ catch (IOException e) {
+ throw new ScheduleManagerException("Error encoding the schedule options" + s.getSchedOptions());
+ }
+
QueryRunner runner = new QueryRunner(dataSource);
try {
int inserts = runner.update(
@@ -190,7 +229,9 @@ public class JdbcScheduleLoader implements ScheduleLoader {
s.getLastModifyTime(),
s.getNextExecTime(),
s.getSubmitTime(),
- s.getSubmitUser());
+ s.getSubmitUser(),
+ encType.getNumVal(),
+ data);
if (inserts == 0) {
throw new ScheduleManagerException("No schedule has been inserted.");
}
@@ -200,8 +241,10 @@ public class JdbcScheduleLoader implements ScheduleLoader {
}
}
- private void updateNextExecTime(Schedule s, Connection connection) throws ScheduleManagerException
+ @Override
+ public void updateNextExecTime(Schedule s) throws ScheduleManagerException
{
+ Connection connection = getConnection();
QueryRunner runner = new QueryRunner();
try {
int updates = runner.update(connection, UPDATE_NEXT_EXEC_TIME, s.getNextExecTime(), s.getProjectId(), s.getFlowName());
@@ -214,6 +257,25 @@ public class JdbcScheduleLoader implements ScheduleLoader {
@Override
public void updateSchedule(Schedule s) throws ScheduleManagerException {
logger.info("Updating schedule " + s.getScheduleName() + " into db.");
+ updateSchedule(s, defaultEncodingType);
+ }
+
+ public void updateSchedule(Schedule s, EncodingType encType) throws ScheduleManagerException {
+
+ String json = JSONUtils.toJSON(s.optionToObject());
+ byte[] data = null;
+ try {
+ byte[] stringData = json.getBytes("UTF-8");
+ data = stringData;
+
+ if (encType == EncodingType.GZIP) {
+ data = GZIPUtils.gzipBytes(stringData);
+ }
+ logger.debug("NumChars: " + json.length() + " UTF-8:" + stringData.length + " Gzip:"+ data.length);
+ }
+ catch (IOException e) {
+ throw new ScheduleManagerException("Error encoding the schedule options" + s.getSchedOptions());
+ }
QueryRunner runner = new QueryRunner(dataSource);
@@ -227,7 +289,9 @@ public class JdbcScheduleLoader implements ScheduleLoader {
s.getLastModifyTime(),
s.getNextExecTime(),
s.getSubmitTime(),
- s.getSubmitUser(),
+ s.getSubmitUser(),
+ encType.getNumVal(),
+ data,
s.getProjectId(),
s.getFlowName());
if (updates == 0) {
@@ -238,46 +302,6 @@ public class JdbcScheduleLoader implements ScheduleLoader {
throw new ScheduleManagerException("Update schedule " + s.getScheduleName() + " into db failed. ", e);
}
}
-
-// private Schedule fromJSON(HashMap<String, Object> obj) {
-// long projectGuid = Long.valueOf((String) obj.get(PROJECTGUID));
-// String projectId = (String) obj.get(PROJECTID);
-// long flowGuid = Long.valueOf((String) obj.get(FLOWGUID));
-// String flowId = (String) obj.get(FLOWID);
-// String status = (String) obj.get(STATUS);
-// long firstSchedTime = Long.valueOf((String) obj.get(FIRSTSCHEDTIME));
-// String timezone = (String) obj.get(TIMEZONE);
-// String period = (String) obj.get(PERIOD);
-// long lastModifyTime = Long.valueOf((String) obj.get(LASTMODIFYTIME));
-// long nextExecTime = Long.valueOf((String) obj.get(NEXTEXECTIME));
-// long submitTime = Long.valueOf((String) obj.get(SUBMITTIME));
-// String submitUser = (String) obj.get(SUBMITUSER);
-//
-// return new Schedule(projectId, flowId, status, firstSchedTime, timezone, period, lastModifyTime, nextExecTime, submitTime, submitUser);
-//
-// }
-//
-// private HashMap<String, Object> toJSON(Schedule s) {
-// HashMap<String, Object> object = new HashMap<String, Object>();
-//// object.put(PROJECTGUID, s.getProjectGuid());
-// object.put(SCHEDULEID, s.getScheduleId());
-// object.put(PROJECTID, s.getProjectId());
-//// object.put(FLOWGUID, s.getFlowGuid());
-// object.put(FLOWID, s.getFlowId());
-//
-// object.put(STATUS, s.getStatus());
-//
-// object.put(FIRSTSCHEDTIME, s.getFirstSchedTime());
-// object.put(TIMEZONE, s.getTimezone());
-// object.put(PERIOD, s.getPeriod());
-//
-// object.put(LASTMODIFYTIME, s.getLastModifyTime());
-// object.put(NEXTEXECTIME, s.getNextExecTime());
-// object.put(SUBMITTIME, s.getSubmitTime());
-// object.put(SUBMITUSER, s.getSubmitUser());
-//
-// return object;
-// }
public class ScheduleResultHandler implements ResultSetHandler<List<Schedule>> {
@Override
@@ -299,8 +323,31 @@ public class JdbcScheduleLoader implements ScheduleLoader {
long nextExecTime = rs.getLong(9);
long submitTime = rs.getLong(10);
String submitUser = rs.getString(11);
+ int encodingType = rs.getInt(12);
+ byte[] data = rs.getBytes(13);
+
+ Map<String, Object> options = null;
+ if (data != null) {
+ EncodingType encType = EncodingType.fromInteger(encodingType);
+ Object optsObj;
+ try {
+ // Convoluted way to inflate strings. Should find common package or helper function.
+ if (encType == EncodingType.GZIP) {
+ // Decompress the sucker.
+ String jsonString = GZIPUtils.unGzipString(data, "UTF-8");
+ optsObj = JSONUtils.parseJSONFromString(jsonString);
+ }
+ else {
+ String jsonString = new String(data, "UTF-8");
+ optsObj = JSONUtils.parseJSONFromString(jsonString);
+ }
+ options = Schedule.createScheduleOptionFromObject(optsObj);
+ } catch (IOException e) {
+ throw new SQLException("Error reconstructing schedule options " + projectName + "." + flowName);
+ }
+ }
- Schedule s = new Schedule(projectId, projectName, flowName, status, firstSchedTime, timezone, period, lastModifyTime, nextExecTime, submitTime, submitUser);
+ Schedule s = new Schedule(projectId, projectName, flowName, status, firstSchedTime, timezone, period, lastModifyTime, nextExecTime, submitTime, submitUser, options);
schedules.add(s);
} while (rs.next());
src/java/azkaban/scheduler/Schedule.java 226(+133 -93)
diff --git a/src/java/azkaban/scheduler/Schedule.java b/src/java/azkaban/scheduler/Schedule.java
index a8c0c91..98b6973 100644
--- a/src/java/azkaban/scheduler/Schedule.java
+++ b/src/java/azkaban/scheduler/Schedule.java
@@ -18,6 +18,11 @@ package azkaban.scheduler;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Days;
@@ -50,6 +55,7 @@ public class Schedule{
private String submitUser;
private String status;
private long submitTime;
+ private Map<String, Object> schedOptions;
public Schedule(
int projectId,
@@ -62,7 +68,8 @@ public class Schedule{
long lastModifyTime,
long nextExecTime,
long submitTime,
- String submitUser
+ String submitUser,
+ Map<String, Object> schedOptions
) {
this.projectId = projectId;
this.projectName = projectName;
@@ -75,31 +82,43 @@ public class Schedule{
this.submitUser = submitUser;
this.status = status;
this.submitTime = submitTime;
+ this.schedOptions = schedOptions;
}
-
+
public Schedule(
- int projectId,
- String projectName,
- String flowName,
- String status,
- long firstSchedTime,
- String timezone,
- String period,
- long lastModifyTime,
- long nextExecTime,
- long submitTime,
- String submitUser) {
+ int projectId,
+ String projectName,
+ String flowName,
+ String status,
+ long firstSchedTime,
+ String timezoneId,
+ String period,
+ long lastModifyTime,
+ long nextExecTime,
+ long submitTime,
+ String submitUser,
+ Map<String, Object> schedOptions
+ ) {
this.projectId = projectId;
this.projectName = projectName;
this.flowName = flowName;
this.firstSchedTime = firstSchedTime;
- this.timezone = DateTimeZone.forID(timezone);
+ this.timezone = DateTimeZone.forID(timezoneId);
this.lastModifyTime = lastModifyTime;
this.period = parsePeriodString(period);
this.nextExecTime = nextExecTime;
this.submitUser = submitUser;
this.status = status;
this.submitTime = submitTime;
+ this.schedOptions = schedOptions;
+ }
+
+ public Map<String, Object> getSchedOptions() {
+ return schedOptions;
+ }
+
+ public void setSchedOptions(Map<String, Object> schedOptions) {
+ this.schedOptions = schedOptions;
}
public String getScheduleName() {
@@ -155,85 +174,77 @@ public class Schedule{
}
public boolean updateTime() {
- if (new DateTime(nextExecTime).isAfterNow()) {
- return true;
- }
-
- if (period != null) {
- DateTime nextTime = getNextRuntime(nextExecTime, timezone, period);
-
- this.nextExecTime = nextTime.getMillis();
- return true;
- }
-
- return false;
- }
-// public String getFlowId(){
-// return this.scheduleId.split("\\.")[1];
-// }
-//
-// public String getProjectId(){
-// return this.scheduleId.split("\\.")[0];
-// }
-
+ if (new DateTime(nextExecTime).isAfterNow()) {
+ return true;
+ }
+
+ if (period != null) {
+ DateTime nextTime = getNextRuntime(nextExecTime, timezone, period);
+
+ this.nextExecTime = nextTime.getMillis();
+ return true;
+ }
+
+ return false;
+ }
- private DateTime getNextRuntime(long scheduleTime, DateTimeZone timezone, ReadablePeriod period) {
- DateTime now = new DateTime();
- DateTime date = new DateTime(scheduleTime).withZone(timezone);
- int count = 0;
- while (!now.isBefore(date)) {
- if (count > 100000) {
- throw new IllegalStateException(
- "100000 increments of period did not get to present time.");
- }
-
- if (period == null) {
- break;
- } else {
- date = date.plus(period);
- }
-
- count += 1;
- }
-
- return date;
- }
-
- public static ReadablePeriod parsePeriodString(String periodStr) {
- ReadablePeriod period;
- char periodUnit = periodStr.charAt(periodStr.length() - 1);
- if (periodUnit == 'n') {
- return null;
- }
-
- int periodInt = Integer.parseInt(periodStr.substring(0,
- periodStr.length() - 1));
- switch (periodUnit) {
- case 'M':
- period = Months.months(periodInt);
- break;
- case 'w':
- period = Weeks.weeks(periodInt);
- break;
- case 'd':
- period = Days.days(periodInt);
- break;
- case 'h':
- period = Hours.hours(periodInt);
- break;
- case 'm':
- period = Minutes.minutes(periodInt);
- break;
- case 's':
- period = Seconds.seconds(periodInt);
- break;
- default:
- throw new IllegalArgumentException("Invalid schedule period unit '"
- + periodUnit);
- }
-
- return period;
- }
+ private DateTime getNextRuntime(long scheduleTime, DateTimeZone timezone, ReadablePeriod period) {
+ DateTime now = new DateTime();
+ DateTime date = new DateTime(scheduleTime).withZone(timezone);
+ int count = 0;
+ while (!now.isBefore(date)) {
+ if (count > 100000) {
+ throw new IllegalStateException(
+ "100000 increments of period did not get to present time.");
+ }
+
+ if (period == null) {
+ break;
+ } else {
+ date = date.plus(period);
+ }
+
+ count += 1;
+ }
+
+ return date;
+ }
+
+ public static ReadablePeriod parsePeriodString(String periodStr) {
+ ReadablePeriod period;
+ char periodUnit = periodStr.charAt(periodStr.length() - 1);
+ if (periodUnit == 'n') {
+ return null;
+ }
+
+ int periodInt = Integer.parseInt(periodStr.substring(0,
+ periodStr.length() - 1));
+ switch (periodUnit) {
+ case 'M':
+ period = Months.months(periodInt);
+ break;
+ case 'w':
+ period = Weeks.weeks(periodInt);
+ break;
+ case 'd':
+ period = Days.days(periodInt);
+ break;
+ case 'h':
+ period = Hours.hours(periodInt);
+ break;
+ case 'm':
+ period = Minutes.minutes(periodInt);
+ break;
+ case 's':
+ period = Seconds.seconds(periodInt);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid schedule period unit '"
+ + periodUnit);
+ }
+
+ return period;
+ }
public static String createPeriodString(ReadablePeriod period) {
String periodStr = "n";
@@ -264,7 +275,36 @@ public class Schedule{
return periodStr;
}
-
+
+ public Map<String,Object> optionToObject() {
+ //HashMap<String, Object> optionObject = new HashMap<String, Object>();
+
+
+ return schedOptions;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Map<String, Object> createScheduleOptionFromObject(Object obj) {
+ Map<String, Object> options = new HashMap<String, Object>();
+ if(obj != null) {
+ HashMap<String, Object> optionObject = (HashMap<String,Object>)obj;
+ options.putAll(optionObject);
+ return options;
+ }
+ else {
+ return new HashMap<String, Object>();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<String> getDisabledJobs() {
+ if (schedOptions.containsKey("disabled")) {
+ return (List<String>) schedOptions.get("disabled");
+ }
+ else {
+ return new ArrayList<String>();
+ }
+ }
}
\ No newline at end of file
diff --git a/src/java/azkaban/scheduler/ScheduleLoader.java b/src/java/azkaban/scheduler/ScheduleLoader.java
index 4b55b4e..9caed47 100644
--- a/src/java/azkaban/scheduler/ScheduleLoader.java
+++ b/src/java/azkaban/scheduler/ScheduleLoader.java
@@ -16,6 +16,7 @@
package azkaban.scheduler;
+import java.sql.Connection;
import java.util.List;
public interface ScheduleLoader {
@@ -28,4 +29,6 @@ public interface ScheduleLoader {
public void removeSchedule(Schedule s) throws ScheduleManagerException;
+ public void updateNextExecTime(Schedule s) throws ScheduleManagerException;
+
}
\ No newline at end of file
src/java/azkaban/scheduler/ScheduleManager.java 69(+56 -13)
diff --git a/src/java/azkaban/scheduler/ScheduleManager.java b/src/java/azkaban/scheduler/ScheduleManager.java
index ef0f076..9c3c459 100644
--- a/src/java/azkaban/scheduler/ScheduleManager.java
+++ b/src/java/azkaban/scheduler/ScheduleManager.java
@@ -17,6 +17,7 @@
package azkaban.scheduler;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
@@ -34,6 +35,8 @@ import org.joda.time.format.DateTimeFormatter;
import azkaban.executor.ExecutableFlow;
import azkaban.executor.ExecutorManager;
import azkaban.executor.ExecutorManagerException;
+import azkaban.executor.ExecutableFlow.FailureAction;
+import azkaban.executor.ExecutableFlow.Status;
import azkaban.flow.Flow;
import azkaban.jobExecutor.utils.JobExecutionException;
@@ -169,9 +172,10 @@ public class ScheduleManager {
final long lastModifyTime,
final long nextExecTime,
final long submitTime,
- final String submitUser
+ final String submitUser,
+ final Map<String, Object> options
) {
- Schedule sched = new Schedule(projectId, projectName, flowName, status, firstSchedTime, timezone, period, lastModifyTime, nextExecTime, submitTime, submitUser);
+ Schedule sched = new Schedule(projectId, projectName, flowName, status, firstSchedTime, timezone, period, lastModifyTime, nextExecTime, submitTime, submitUser, options);
logger.info("Scheduling flow '" + sched.getScheduleName() + "' for "
+ _dateFormat.print(firstSchedTime) + " with a period of "
+ period == null ? "(non-recurring)" : period);
@@ -362,18 +366,57 @@ public class ScheduleManager {
// Create ExecutableFlow
ExecutableFlow exflow = new ExecutableFlow(flow);
exflow.setSubmitUser(runningSched.getSubmitUser());
+
+ Map<String, Object> scheduleOptions = runningSched.getSchedOptions();
+
+ if(scheduleOptions != null && scheduleOptions.containsKey("flowOptions")) {
+ Map<String, Object> flowOptions = (Map<String, Object>) scheduleOptions.get("flowOptions");
+
+ if (flowOptions.containsKey("failureAction")) {
+ String option = (String) flowOptions.get("failureAction");
+ if (option.equals("finishCurrent") ) {
+ exflow.setFailureAction(FailureAction.FINISH_CURRENTLY_RUNNING);
+ }
+ else if (option.equals("cancelImmediately")) {
+ exflow.setFailureAction(FailureAction.CANCEL_ALL);
+ }
+ else if (option.equals("finishPossible")) {
+ exflow.setFailureAction(FailureAction.FINISH_ALL_POSSIBLE);
+ }
+ }
- // TODO make disabled in scheduled flow
- // Map<String, String> paramGroup =
- // this.getParamGroup(req, "disabled");
- // for (Map.Entry<String, String> entry:
- // paramGroup.entrySet()) {
- // boolean nodeDisabled =
- // Boolean.parseBoolean(entry.getValue());
- // exflow.setStatus(entry.getKey(),
- // nodeDisabled ? Status.DISABLED :
- // Status.READY);
- // }
+ if (flowOptions.containsKey("failureEmails")) {
+ String emails = (String) flowOptions.get("failureEmails");
+ String[] emailSplit = emails.split("\\s*,\\s*|\\s*;\\s*|\\s+");
+ exflow.setFailureEmails(Arrays.asList(emailSplit));
+ }
+ if (flowOptions.containsKey("successEmails")) {
+ String emails = (String) flowOptions.get("successEmails");
+ String[] emailSplit = emails.split("\\s*,\\s*|\\s*;\\s*|\\s+");
+ exflow.setSuccessEmails(Arrays.asList(emailSplit));
+ }
+ if (flowOptions.containsKey("notifyFailureFirst")) {
+ exflow.setNotifyOnFirstFailure(Boolean.parseBoolean((String)flowOptions.get("notifyFailureFirst")));
+ }
+ if (flowOptions.containsKey("notifyFailureLast")) {
+ exflow.setNotifyOnLastFailure(Boolean.parseBoolean((String)flowOptions.get("notifyFailureLast")));
+ }
+ if (flowOptions.containsKey("executingJobOption")) {
+ String option = (String)flowOptions.get("jobOption");
+ // Not set yet
+ }
+
+ Map<String, String> flowParamGroup = this.getParamGroup(req, "flowOverride");
+ exflow.addFlowParameters(flowParamGroup);
+
+ // Setup disabled
+ Map<String, String> paramGroup = this.getParamGroup(req, "disable");
+ for (Map.Entry<String, String> entry: paramGroup.entrySet()) {
+ boolean nodeDisabled = Boolean.parseBoolean(entry.getValue());
+ exflow.setStatus(entry.getKey(), nodeDisabled ? Status.DISABLED : Status.READY);
+ }
+
+ }
try {
executorManager.submitExecutableFlow(exflow);
src/java/azkaban/utils/EmailMessage.java 20(+13 -7)
diff --git a/src/java/azkaban/utils/EmailMessage.java b/src/java/azkaban/utils/EmailMessage.java
index 1ac7607..b510d6c 100644
--- a/src/java/azkaban/utils/EmailMessage.java
+++ b/src/java/azkaban/utils/EmailMessage.java
@@ -21,7 +21,10 @@ import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
+import com.sun.mail.smtp.SMTPTransport;
+
public class EmailMessage {
+ private static String protocol = "smtp";
private List<String> _toAddress = new ArrayList<String>();
private String _mailHost;
private String _mailUser;
@@ -129,12 +132,13 @@ public class EmailMessage {
public void sendEmail() throws MessagingException {
checkSettings();
Properties props = new Properties();
- props.setProperty("mail.transport.protocol", "smtp");
- props.put("mail.host", _mailHost);
+// props.setProperty("mail.transport.protocol", "smtp");
+ props.put("mail."+protocol+".host", _mailHost);
+ props.put("mail."+protocol+".auth", "true");
props.put("mail.user", _mailUser);
props.put("mail.password", _mailPassword);
- Session session = Session.getDefaultInstance(props);
+ Session session = Session.getInstance(props, null);
Message message = new MimeMessage(session);
InternetAddress from = new InternetAddress(_fromAddress, false);
message.setFrom(from);
@@ -160,11 +164,13 @@ public class EmailMessage {
message.setContent(_body.toString(), _mimeType);
}
- Transport transport = session.getTransport();
- transport.connect();
- transport.sendMessage(message,
+// Transport transport = session.getTransport();
+
+ SMTPTransport t = (SMTPTransport) session.getTransport(protocol);
+ t.connect(_mailHost, _mailUser, _mailPassword);
+ t.sendMessage(message,
message.getRecipients(Message.RecipientType.TO));
- transport.close();
+ t.close();
}
public void setBody(String body) {
diff --git a/src/java/azkaban/webapp/AzkabanWebServer.java b/src/java/azkaban/webapp/AzkabanWebServer.java
index a11f8cb..42c5a06 100644
--- a/src/java/azkaban/webapp/AzkabanWebServer.java
+++ b/src/java/azkaban/webapp/AzkabanWebServer.java
@@ -53,6 +53,8 @@ import azkaban.project.ProjectManager;
import azkaban.scheduler.JdbcScheduleLoader;
import azkaban.scheduler.ScheduleManager;
+import azkaban.sla.JdbcSLALoader;
+import azkaban.sla.SLAManager;
import azkaban.user.UserManager;
import azkaban.user.XmlUserManager;
import azkaban.utils.FileIOUtils;
@@ -121,6 +123,7 @@ public class AzkabanWebServer implements AzkabanServer {
private ProjectManager projectManager;
private ExecutorManager executorManager;
private ScheduleManager scheduleManager;
+ private SLAManager slaManager;
private final ClassLoader baseClassLoader;
@@ -148,6 +151,7 @@ public class AzkabanWebServer implements AzkabanServer {
projectManager = loadProjectManager(props);
executorManager = loadExecutorManager(props);
scheduleManager = loadScheduleManager(executorManager, props);
+ slaManager = loadSLAManager();
baseClassLoader = getBaseClassloader();
tempDir = new File(props.getString("azkaban.temp.dir", "temp"));
@@ -162,6 +166,8 @@ public class AzkabanWebServer implements AzkabanServer {
}
}
+
+
private void setViewerPlugins(List<ViewerPlugin> viewerPlugins) {
this.viewerPlugins = viewerPlugins;
}
@@ -211,6 +217,11 @@ public class AzkabanWebServer implements AzkabanServer {
return schedManager;
}
+ private SLAManager loadSLAManager() {
+ SLAManager slaManager = new SLAManager(executorManager, projectManager, new JdbcSLALoader(props));
+ return slaManager;
+ }
+
/**
* Returns the web session cache.
*
@@ -252,6 +263,10 @@ public class AzkabanWebServer implements AzkabanServer {
return executorManager;
}
+ public SLAManager getSLAManager() {
+ return slaManager;
+ }
+
public ScheduleManager getScheduleManager() {
return scheduleManager;
}
@@ -654,4 +669,6 @@ public class AzkabanWebServer implements AzkabanServer {
return props;
}
+
+
}
src/java/azkaban/webapp/servlet/ScheduleServlet.java 211(+210 -1)
diff --git a/src/java/azkaban/webapp/servlet/ScheduleServlet.java b/src/java/azkaban/webapp/servlet/ScheduleServlet.java
index 480ea9a..40214f7 100644
--- a/src/java/azkaban/webapp/servlet/ScheduleServlet.java
+++ b/src/java/azkaban/webapp/servlet/ScheduleServlet.java
@@ -17,6 +17,7 @@
package azkaban.webapp.servlet;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -25,15 +26,21 @@ import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import javax.swing.text.StyledEditorKit.BoldAction;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.joda.time.Hours;
import org.joda.time.LocalDateTime;
+import org.joda.time.Minutes;
import org.joda.time.ReadablePeriod;
import org.joda.time.format.DateTimeFormat;
+import azkaban.executor.ExecutableFlow;
+import azkaban.executor.ExecutorManagerException;
import azkaban.flow.Flow;
+import azkaban.flow.Node;
import azkaban.project.Project;
import azkaban.project.ProjectManager;
import azkaban.project.ProjectLogEvent.EventType;
@@ -47,12 +54,18 @@ import azkaban.webapp.AzkabanWebServer;
import azkaban.webapp.session.Session;
import azkaban.scheduler.Schedule;
import azkaban.scheduler.ScheduleManager;
+import azkaban.sla.FlowRule;
+import azkaban.sla.JobRule;
+import azkaban.sla.SLA;
+import azkaban.sla.SLAManager;
+import azkaban.sla.SLA.SlaAction;
public class ScheduleServlet extends LoginAbstractAzkabanServlet {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(ScheduleServlet.class);
private ProjectManager projectManager;
private ScheduleManager scheduleManager;
+ private SLAManager slaManager;
private UserManager userManager;
@Override
@@ -62,15 +75,211 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
projectManager = server.getProjectManager();
scheduleManager = server.getScheduleManager();
userManager = server.getUserManager();
+ slaManager = server.getSLAManager();
}
@Override
protected void handleGet(HttpServletRequest req, HttpServletResponse resp,
Session session) throws ServletException, IOException {
+ if (hasParam(req, "ajax")) {
+ handleAJAXAction(req, resp, session);
+ }
+ else {
+ handleGetAllSchedules(req, resp, session);
+ }
+ }
+
+ private void handleAJAXAction(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException, IOException {
+ HashMap<String, Object> ret = new HashMap<String, Object>();
+ String ajaxName = getParam(req, "ajax");
+
+ if (ajaxName.equals("schedInfo")) {
+ ajaxSchedInfo(req, resp, ret, session.getUser());
+ }
+ else if(ajaxName.equals("setSla")) {
+ ajaxSetSla(req, resp, ret, session.getUser());
+ }
+
+ if (ret != null) {
+ this.writeJSON(resp, ret);
+ }
+ }
+
+ private void ajaxSetSla(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user) {
+ try {
+
+ int projectId = getIntParam(req, "projectId");
+ String flowName = getParam(req, "flowName");
+
+ Project project = projectManager.getProject(projectId);
+ if(!hasPermission(project, user, Permission.Type.SCHEDULE)) {
+ ret.put("error", "User " + user + " does not have permission to set SLA for this flow.");
+ return;
+ }
+
+ String slaEmals = getParam(req, "slaEmails");
+ System.out.println(slaEmals);
+
+ String flowRules = getParam(req, "flowRules");
+ FlowRule flowRule = parseFlowRule(flowRules);
+
+ List<JobRule> jobRule = new ArrayList<JobRule>();
+ Map<String, String> jobRules = getParamGroup(req, "jobRules");
+ System.out.println(jobRules);
+ for(String job : jobRules.keySet()) {
+ JobRule jr = parseJobRule(job, jobRules.get(job));
+ jobRule.add(jr);
+ }
+ Map<String, Object> options= new HashMap<String, Object>();
+ options.put("slaEmails", slaEmals);
+ options.put("flowRules", flowRules);
+ options.put("jobRules", jobRule);
+ Schedule sched = scheduleManager.getSchedule(new Pair<Integer, String>(projectId, flowName));
+ //slaManager.addFlowSLA(projectId, project.getName(), flowName, "ready", sched.getFirstSchedTime(), sched.getTimezone(), sched.getPeriod(), DateTime.now(), DateTime.now(), DateTime.now(), user, options);
+
+ } catch (ServletException e) {
+ ret.put("error", e);
+ }
+
+ }
+
+
+ private FlowRule parseFlowRule(String flowRules) {
+ String[] parts = flowRules.split(",");
+ String duration = parts[0];
+ String emailAction = parts[1];
+ String killAction = parts[2];
+ if(emailAction.equals("on") || killAction.equals("on")) {
+ if(!duration.equals("")) {
+ FlowRule r = new FlowRule();
+ ReadablePeriod dur = parseDuration(duration);
+ r.setDuration(dur);
+ List<SlaAction> actions = new ArrayList<SLA.SlaAction>();
+ if(emailAction.equals("on")) {
+ actions.add(SlaAction.SENDEMAIL);
+ }
+ if(killAction.equals("on")) {
+ actions.add(SlaAction.KILL);
+ }
+ r.setActions(actions);
+ return r;
+ }
+ }
+ return null;
+ }
+
+ private JobRule parseJobRule(String job, String jobRule) {
+ String[] parts = jobRule.split(",");
+ String duration = parts[0];
+ String emailAction = parts[1];
+ String killAction = parts[2];
+ if(emailAction.equals("on") || killAction.equals("on")) {
+ if(!duration.equals("")) {
+ JobRule r = new JobRule();
+ r.setJobId(job);
+ ReadablePeriod dur = parseDuration(duration);
+ r.setDuration(dur);
+ List<SlaAction> actions = new ArrayList<SLA.SlaAction>();
+ if(emailAction.equals("on")) {
+ actions.add(SlaAction.SENDEMAIL);
+ }
+ if(killAction.equals("on")) {
+ actions.add(SlaAction.KILL);
+ }
+ r.setActions(actions);
+ return r;
+ }
+ }
+ return null;
+ }
+
+ private ReadablePeriod parseDuration(String duration) {
+ int hour = Integer.parseInt(duration.split(",")[0]);
+ int min = Integer.parseInt(duration.split(",")[1]);
+ return Hours.hours(hour).toPeriod().plus(Minutes.minutes(min).toPeriod());
+ }
+
+ @SuppressWarnings("unchecked")
+ private void ajaxSchedInfo(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user) {
+ int projId;
+ try {
+ projId = getIntParam(req, "projId");
+ String flowName = getParam(req, "flowName");
+
+ Project project = getProjectAjaxByPermission(ret, projId, user, Type.READ);
+ if (project == null) {
+ ret.put("error", "Error loading project. Project " + projId + " doesn't exist");
+ return;
+ }
+
+ Flow flow = project.getFlow(flowName);
+ if (flow == null) {
+ ret.put("error", "Error loading flow. Flow " + flowName + " doesn't exist in " + projId);
+ return;
+ }
+
+ SLA sla = slaManager.getSLA(new Pair<Integer, String>(projId, flowName));
+
+ if(sla != null) {
+ ret.put("slaEmails", (List<String>)sla.getSlaOptions().get("slaEmails"));
+ List<String> allJobs = new ArrayList<String>();
+ for(Node n : flow.getNodes()) {
+ allJobs.add(n.getId());
+ }
+ ret.put("allJobs", allJobs);
+ if(sla.getFlowRules() != null) {
+ ret.put("flowRules", sla.getFlowRules());
+ }
+ if(sla.getJobRules() != null) {
+ ret.put("jobRules", sla.getJobRules());
+ }
+ }
+ else {
+ ret.put("slaEmails", flow.getFailureEmails());
+ List<String> allJobs = new ArrayList<String>();
+ Schedule sched = scheduleManager.getSchedule(new Pair<Integer, String>(projId, flowName));
+ List<String> disabled = sched.getDisabledJobs();
+ for(Node n : flow.getNodes()) {
+ if(!disabled.contains(n.getId())) {
+ allJobs.add(n.getId());
+ }
+ }
+ ret.put("allJobs", allJobs);
+
+
+ }
+ } catch (ServletException e) {
+ ret.put("error", e);
+ }
+
+ }
+
+ protected Project getProjectAjaxByPermission(Map<String, Object> ret, int projectId, User user, Permission.Type type) {
+ Project project = projectManager.getProject(projectId);
+
+ if (project == null) {
+ ret.put("error", "Project '" + project + "' not found.");
+ }
+ else if (!hasPermission(project, user, type)) {
+ ret.put("error", "User '" + user.getUserId() + "' doesn't have " + type.name() + " permissions on " + project.getName());
+ }
+ else {
+ return project;
+ }
+
+ return null;
+ }
+
+ private void handleGetAllSchedules(HttpServletRequest req, HttpServletResponse resp,
+ Session session) throws ServletException, IOException{
+
Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/scheduledflowpage.vm");
List<Schedule> schedules = scheduleManager.getSchedules();
page.add("schedules", schedules);
+
+ List<SLA> slas = slaManager.getSLAs();
+ page.add("slas", slas);
page.render();
}
@@ -205,7 +414,7 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
//ScheduledFlow schedFlow = scheduleManager.schedule(scheduleId, projectId, flowId, userExec, userSubmit, submitTime, firstSchedTime, thePeriod);
//project.info("User '" + user.getUserId() + "' has scheduled " + flow.getId() + "[" + schedFlow.toNiceString() + "].");
- Schedule schedule = scheduleManager.scheduleFlow(projectId, projectName, flowName, "ready", firstSchedTime.getMillis(), timezone, thePeriod, submitTime.getMillis(), firstSchedTime.getMillis(), firstSchedTime.getMillis(), user.getUserId());
+ Schedule schedule = scheduleManager.scheduleFlow(projectId, projectName, flowName, "ready", firstSchedTime.getMillis(), timezone, thePeriod, submitTime.getMillis(), firstSchedTime.getMillis(), firstSchedTime.getMillis(), user.getUserId(), null);
logger.info("User '" + user.getUserId() + "' has scheduled " + "[" + projectName + flowName + " (" + projectId +")" + "].");
projectManager.postProjectEvent(project, EventType.SCHEDULE, user.getUserId(), "Schedule " + schedule.getScheduleName() + " has been added.");
src/java/azkaban/webapp/servlet/velocity/flowpage.vm 162(+161 -1)
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index ee2436b..b4e1355 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -131,7 +131,166 @@
</div>
</div>
<!-- modal content -->
- <div id="schedule-flow" class="modal">
+ <div id="advScheduleModalBackground" class="modalBackground2">
+ <div id="schedule-options" class="modal modalContainer2">
+ <a href='#' title='Close' class='modal-close'>x</a>
+ <h3>Advanced Schedule Options</h3>
+ <div>
+ <ul class="optionsPicker">
+ <li id="scheduleGeneralOptions">General Options</li>
+ <li id="scheduleFlowOptions">Flow Options</li>
+ <li id="scheduleSlaOptions">SLA Options</li>
+ </ul>
+ </div>
+ <div class="optionsPane">
+ <div id="scheduleSlaPanel" class="generalPanel panel">
+ <div id="slaActions">
+ <h4>SLA Alert Emails</h4>
+ <dl>
+ <dt >SLA Alert Emails</dt>
+ <dd>
+ <textarea id="slaEmails"></textarea>
+ </dd>
+ </dl>
+ </div>
+ <div id="slaRules">
+ <h4>Flow SLA Rules</h4>
+ <div class="tableDiv">
+ <table id="flowRulesTbl">
+ <thead>
+ <tr>
+ <th>Flow/Job</th>
+ <th>Finish In</th>
+ <th>Email Action</th>
+ <th>Kill Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ <h4>Job SLA Rules</h4>
+ <div class="tableDiv">
+ <table id="jobRulesTbl">
+ <thead>
+ <tr>
+ <th>Flow/Job</th>
+ <th>Finish In</th>
+ <th>Email Action</th>
+ <th>Kill Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ <div id="scheduleGeneralPanel" class="generalPanel panel">
+ <div id="scheduleBasicInfo">
+ <h4>Basic Scheduling Information</h4>
+ <dl>
+ <dt>Schedule Time</dt>
+ <dd>
+ <input id="advHour" type="text" size="2" value="12"/>
+ <input id="advMinutes" type="text" size="2" value="00"/>
+ <select id="advAm_pm">
+ <option>pm</option>
+ <option>am</option>
+ </select>
+ <select id="advTimezone">
+ <option>PDT</option>
+ <option>UTC</option>
+ </select>
+ </dd>
+ <dt>Schedule Date</dt>
+ <dd><input type="text" id="advDate" /></dd>
+ <dt>Recurrence</dt>
+ <dd>
+ <input id="advIs_recurring" type="checkbox" checked /><span>repeat every</span>
+ <input id="advPeriod" type="text" size="2" value="1"/>
+ <select id="advPeriod_units">
+ <option value="d">Days</option>
+ <option value="h">Hours</option>
+ <option value="m">Minutes</option>
+ <option value="M">Months</option>
+ <option value="w">Weeks</option>
+ </select>
+ </dd>
+ </dl>
+ </div>
+ <div id="scheduleCompleteActions">
+ <h4>Completion Actions</h4>
+ <dl>
+ <dt class="disabled">Failure Action</dt>
+ <dd>
+ <select id="scheduleFailureAction" name="failureAction">
+ <option value="finishCurrent">Finish Current Running</option>
+ <option value="cancelImmediately">Cancel All</option>
+ <option value="finishPossible">Finish All Possible</option>
+ </select>
+ </dd>
+ <dt>Failure Email</dt>
+ <dd>
+ <textarea id="scheduleFailureEmails"></textarea>
+ </dd>
+ <dt>Notify on Failure</dt>
+ <dd>
+ <input id="scheduleNotifyFailureFirst" class="checkbox" type="checkbox" name="notify" value="first" checked >First Failure</input>
+ <input id="scheduleNotifyFailureLast" class="checkbox" type="checkbox" name="notify" value="last">Flow Stop</input>
+ </dd>
+ <dt>Success Email</dt>
+ <dd>
+ <textarea id="scheduleSuccessEmails"></textarea>
+ </dd>
+ <dt class="disabled" >Concurrent Execution</dt>
+ <dd id="scheduleExecutingJob" class="disabled">
+ <input id="scheduleIgnore" class="radio" type="radio" name="concurrent" value="ignore" checked /><label class="radioLabel" for="ignore">Run Concurrently</label>
+ <input id="schedulePipeline" class="radio" type="radio" name="concurrent" value="pipeline" /><label class="radioLabel" for="pipeline">Pipeline</label>
+ <input id="scheduleQueue" class="radio" type="radio" name="concurrent" value="queue" /><label class="radioLabel" for="queue">Queue Job</label>
+ </dd>
+ </dl>
+ </div>
+ <div id="scheduleFlowPropertyOverride">
+ <h4>Flow Property Override</h4>
+ <div class="tableDiv">
+ <table>
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr id="scheduleAddRow"><td id="scheduleAddRow-col" colspan="2"><span class="addIcon"></span><a href="#">Add Row</a></td></tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ <div id="scheduleGraphPanel" class="graphPanel panel">
+ <div id="scheduleJobListCustom" class="jobList">
+ <div class="filterList">
+ <input class="filter" placeholder=" Job Filter" />
+ </div>
+ <div class="list">
+ </div>
+ <div class="btn5 resetPanZoomBtn" >Reset Pan Zoom</div>
+ </div>
+ <div id="scheduleSvgDivCustom" class="svgDiv" >
+ <svg class="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed" >
+ </svg>
+ </div>
+ </div>
+ </div>
+ <div class="actions">
+ <a class="yes btn1" id="adv-schedule-btn" href="#">Schedule</a>
+ <a class="no simplemodal-close btn3" id="schedule-cancel-btn" href="#">Cancel</a>
+ </div>
+ </div>
+ </div>
+
+ <div id="schedule-flow" class="modal">
<h3>Schedule Flow</h3>
<div id="errorMsg" class="box-error-message">$errorMsg</div>
@@ -177,6 +336,7 @@
<div class="actions">
<a class="yes btn2" id="schedule-btn" href="#">Schedule The Flow</a>
<a class="no simplemodal-close btn3" href="#">Cancel</a>
+ <a class="btn2" id="adv-schedule-opt-btn" href="#">Advanced Schedule Options</a>
</div>
</div>
<div id="invalid-session" class="modal">
diff --git a/src/java/azkaban/webapp/servlet/velocity/scheduledflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/scheduledflowpage.vm
index 09d387a..b2511ed 100644
--- a/src/java/azkaban/webapp/servlet/velocity/scheduledflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/scheduledflowpage.vm
@@ -18,8 +18,12 @@
<html>
<head>
#parse( "azkaban/webapp/servlet/velocity/style.vm" )
- <script type="text/javascript" src="${context}/js/jquery/jquery.js"></script>
- <script type="text/javascript" src="${context}/js/jqueryui/jquery-ui.custom.min.js"></script>
+ <link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-timepicker-addon.css" />
+ <link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui.css" />
+ <script type="text/javascript" src="${context}/js/jquery/jquery-1.8.3.min.js"></script>
+ <script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-1.9.2.custom.min.js"></script>
+ <script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-timepicker-addon.js"></script>
+ <script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-sliderAccess.js"></script>
<script type="text/javascript" src="${context}/js/namespace.js"></script>
<script type="text/javascript" src="${context}/js/underscore-1.2.1-min.js"></script>
<script type="text/javascript" src="${context}/js/backbone-0.5.3-min.js"></script>
@@ -32,7 +36,6 @@
var timezone = "${timezone}";
var errorMessage = null;
var successMessage = null;
-
</script>
</head>
<body>
@@ -43,12 +46,12 @@
<div class="content">
#if($errorMsg)
- <div class="box-error-message">$errorMsg</div>
+ <div class="box-error-message">$errorMsg</div>
#else
#if($error_message != "null")
- <div class="box-error-message">$error_message</div>
+ <div class="box-error-message">$error_message</div>
#elseif($success_message != "null")
- <div class="box-success-message">$success_message</div>
+ <div class="box-success-message">$success_message</div>
#end
#end
@@ -70,7 +73,7 @@
<th class="date">First Scheduled to Run</th>
<th class="date">Next Execution Time</th>
<th class="date">Repeats Every</th>
- <th class="action">Action</th>
+ <th colspan="2" class="action">Action</th>
</tr>
</thead>
<tbody>
@@ -90,6 +93,7 @@
<td>$utils.formatDateTime(${sched.nextExecTime})</td>
<td>$utils.formatPeriod(${sched.period})</td>
<td><button id="removeSchedBtn" onclick="removeSched(${sched.projectId}, '${sched.flowName}')" >Remove Schedule</button></td>
+ <td><button id="addSlaBtn" onclick="slaView.initFromSched(${sched.projectId}, '${sched.flowName}')" >Set SLA</button></td>
</tr>
#end
#else
@@ -97,8 +101,117 @@
#end
</tbody>
</table>
+ </div>
+
+
+ <div id="all-sla-content">
+ <div class="section-hd">
+ <h2>Flow SLAs</h2>
+ </div>
+ </div>
+
+ <div class="scheduledFlows">
+ <table id="slaTbl">
+ <thead>
+ <tr>
+ <th>Flow</th>
+ <th>Project</th>
+ <th>User</th>
+ <th>Submitted By</th>
+ <th class="date">First SLA check Time</th>
+ <th class="date">Next SLA check Time</th>
+ <th class="date">Repeats Every</th>
+ <th colspan="2" class="action">Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ #if($slas)
+#foreach($sla in $slas)
+ <tr class="row" >
+
+ <td class="tb-name">
+ <a href="${context}/manager?project=${sla.projectName}&flow=${sla.flowName}">${sla.flowName}</a>
+ </td>
+ <td>
+ <a href="${context}/manager?project=${sla.projectName}">${sla.projectName}</a>
+ </td>
+ <td>${sla.submitUser}</td>
+ <td>${sla.submitUser}</td>
+ <td>$utils.formatDateTime(${sla.firstCheckTime})</td>
+ <td>$utils.formatDateTime(${sla.nextCheckTime})</td>
+ <td>$utils.formatPeriod(${sla.period})</td>
+ <td><button id="removeSchedBtn" onclick="removeSla(${sla.projectId}, '${sla.flowName}')" >Remove SLA</button></td>
+ </tr>
+#end
+#else
+ <tr><td class="last">No Flow SLA Set</td></tr>
+#end
+ </tbody>
+ </table>
+ </div>
+ </div>
-
+ <!-- modal content -->
+
+ <div id="slaModalBackground" class="modalBackground2">
+ <div id="sla-options" class="modal modalContainer2">
+ <a href='#' title='Close' class='modal-close'>x</a>
+ <h3>SLA Options</h3>
+ <div>
+ <ul class="optionsPicker">
+ <li id="slaOptions">General SLA Options</li>
+ </ul>
+ </div>
+ <div class="optionsPane">
+ <div id="slaPanel" class="generalPanel panel">
+ <div id="slaActions">
+ <h4>SLA Alert Emails</h4>
+ <dl>
+ <dt >SLA Alert Emails</dt>
+ <dd>
+ <textarea id="slaEmails"></textarea>
+ </dd>
+ </dl>
+ </div>
+ <div id="slaRules">
+ <h4>Flow SLA Rules</h4>
+ <div class="tableDiv">
+ <table id="flowRulesTbl">
+ <thead>
+ <tr>
+ <th>Flow/Job</th>
+ <th>Finish In</th>
+ <th>Email Action</th>
+ <th>Kill Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ <h4>Job SLA Rules</h4>
+ <div class="tableDiv">
+ <table id="jobRulesTbl">
+ <thead>
+ <tr>
+ <th>Flow/Job</th>
+ <th>Finish In</th>
+ <th>Email Action</th>
+ <th>Kill Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="actions">
+ <a class="yes btn1" id="remove-sla-btn" href="#">Remove SLA</a>
+ <a class="yes btn1" id="set-sla-btn" href="#">Set SLA</a>
+ <a class="no simplemodal-close btn3" id="sla-cancel-btn" href="#">Cancel</a>
+ </div>
</div>
</div>
</body>
src/sql/create_schedule_table.sql 2(+2 -0)
diff --git a/src/sql/create_schedule_table.sql b/src/sql/create_schedule_table.sql
index defadce..381ce6d 100644
--- a/src/sql/create_schedule_table.sql
+++ b/src/sql/create_schedule_table.sql
@@ -12,6 +12,8 @@ CREATE TABLE schedules (
next_exec_time BIGINT,
submit_time BIGINT,
submit_user VARCHAR(128),
+ enc_type TINYINT,
+ options LONGBLOB,
primary key(project_id, flow_name)
) ENGINE=InnoDB;
src/web/css/azkaban.css 332(+324 -8)
diff --git a/src/web/css/azkaban.css b/src/web/css/azkaban.css
index 7c1aa97..a3c402c 100644
--- a/src/web/css/azkaban.css
+++ b/src/web/css/azkaban.css
@@ -1351,6 +1351,10 @@ tr:hover td {
background-color: #F0F0F0;
}
+.radioLabel.disabled {
+ opacity: 0.3;
+}
+
#executing-options {
left: 100px;
right: 100px;
@@ -1358,14 +1362,6 @@ tr:hover td {
bottom: 40px;
}
-#scheduled {
-
-}
-
-.radioLabel.disabled {
- opacity: 0.3;
-}
-
#executing-options .svgDiv {
position: absolute;
background-color: #CCC;
@@ -2330,6 +2326,326 @@ span .nowrap {
cursor: pointer;
}
+#schedule-options {
+ left: 100px;
+ right: 100px;
+ top: 50px;
+ bottom: 40px;
+}
+
+#schedule-options .svgDiv {
+ position: absolute;
+ background-color: #CCC;
+ padding: 1px;
+ left: 270px;
+ right: 0px;
+ top: 0px;
+ bottom: 0px;
+}
+
+#schedule-options .jobList {
+ position: absolute;
+ width: 255px;
+ top: 0px;
+ bottom: 0px;
+ padding: 5px;
+ background-color: #F0F0F0;
+}
+
+#schedule-options .list {
+ width: 255px;
+}
+
+#schedule-options ul.optionsPicker {
+ margin-left: 30px;
+}
+
+#schedule-options ul.optionsPicker li {
+ float: left;
+ font-size: 12pt;
+ font-weight: bold;
+ margin-right: 15px;
+ cursor: pointer;
+ color: #CCC;
+}
+
+#schedule-options ul.optionsPicker li.selected {
+ text-decoration: underline;
+ color: #000;
+}
+
+#schedule-options ul.optionsPicker li.selected:hover {
+ color: #000;
+}
+
+#schedule-options ul.optionsPicker li:hover {
+ color: #888;
+}
+
+#schedule-options .optionsPane {
+ position: absolute;
+ top: 85px;
+ background-color: #FFF;
+ left: 0px;
+ right: 0px;
+ bottom: 0px;
+}
+
+#schedule-options .panel {
+ position: absolute;
+ width: 100%;
+ top: 0px;
+ bottom: 65px;
+}
+
+#schedule-options .generalPanel.panel {
+ background-color: #F4F4F4;
+ padding-top: 15px;
+}
+
+#schedule-options h3 {
+ margin-left: 20px;
+ font-size: 14pt;
+ border-bottom: 1px solid #CCC;
+}
+
+#schedule-options h4 {
+ margin-left: 20px;
+ font-size: 12pt;
+ border-bottom: 1px solid #CCC;
+}
+
+#scheduleGeneralPanel {
+ overflow: auto;
+}
+
+#scheduleGeneralPanel dt {
+ width: 150px;
+ font-size: 10pt;
+ font-weight: bold;
+ margin-top: 5px;
+}
+
+#scheduleGeneralPanel textarea {
+ width: 500px;
+}
+
+#scheduleGeneralPanel table #addRow {
+ cursor: pointer;
+}
+
+#scheduleGeneralPanel table tr {
+ height: 24px;
+}
+
+#scheduleGeneralPanel table .editable {
+
+}
+
+#scheduleGeneralPanel table .editable input {
+ border: 1px solid #009FC9;
+ height: 16px;
+}
+
+#scheduleGeneralPanel table .name {
+ width: 40%;
+}
+
+#scheduleGeneralPanel span.addIcon {
+ display: block;
+ width: 16px;
+ height: 16px;
+ background-image: url("./images/addIcon.png");
+}
+
+#scheduleGeneralPanel span.removeIcon {
+ display: block;
+ visibility:hidden;
+ disabled: true;
+ width: 16px;
+ height: 16px;
+ background-image: url("./images/removeIcon.png");
+ cursor: pointer;
+}
+
+#scheduleGeneralPanel .editable:hover span.removeIcon {
+ visibility:visible;
+}
+
+#scheduleGeneralPanel {
+}
+
+#scheduleGeneralPanel span {
+ float: left;
+ margin-left: 5px;
+}
+
+#scheduleGeneralPanel dd {
+ font-size: 10pt;
+}
+
+
+
+
+#sla-options {
+ left: 100px;
+ right: 100px;
+ top: 50px;
+ bottom: 40px;
+}
+
+#sla-options .svgDiv {
+ position: absolute;
+ background-color: #CCC;
+ padding: 1px;
+ left: 270px;
+ right: 0px;
+ top: 0px;
+ bottom: 0px;
+}
+
+#sla-options .jobList {
+ position: absolute;
+ width: 255px;
+ top: 0px;
+ bottom: 0px;
+ padding: 5px;
+ background-color: #F0F0F0;
+}
+
+#sla-options .list {
+ width: 255px;
+}
+
+#sla-options ul.optionsPicker {
+ margin-left: 30px;
+}
+
+#sla-options ul.optionsPicker li {
+ float: left;
+ font-size: 12pt;
+ font-weight: bold;
+ margin-right: 15px;
+ cursor: pointer;
+ color: #CCC;
+}
+
+#sla-options ul.optionsPicker li.selected {
+ text-decoration: underline;
+ color: #000;
+}
+
+#sla-options ul.optionsPicker li.selected:hover {
+ color: #000;
+}
+
+#sla-options ul.optionsPicker li:hover {
+ color: #888;
+}
+
+#sla-options .optionsPane {
+ position: absolute;
+ top: 85px;
+ background-color: #FFF;
+ left: 0px;
+ right: 0px;
+ bottom: 0px;
+}
+
+#sla-options .panel {
+ position: absolute;
+ width: 100%;
+ top: 0px;
+ bottom: 65px;
+}
+
+#sla-options .generalPanel.panel {
+ background-color: #F4F4F4;
+ padding-top: 15px;
+}
+
+#sla-options h3 {
+ margin-left: 20px;
+ font-size: 14pt;
+ border-bottom: 1px solid #CCC;
+}
+
+#sla-options h4 {
+ margin-left: 20px;
+ font-size: 12pt;
+ border-bottom: 1px solid #CCC;
+}
+
+#slaPanel {
+ overflow: auto;
+}
+
+#slaPanel dt {
+ width: 150px;
+ font-size: 10pt;
+ font-weight: bold;
+ margin-top: 5px;
+}
+
+#slaPanel textarea {
+ width: 500px;
+}
+
+#slaPanel table #addRow {
+ cursor: pointer;
+}
+
+#slaPanel table tr {
+ height: 24px;
+}
+
+#slaPanel table .editable {
+
+}
+
+#slaPanel table .editable input {
+ border: 1px solid #009FC9;
+ height: 16px;
+}
+
+#slaPanel table .name {
+ width: 40%;
+}
+
+#slaPanel span.addIcon {
+ display: block;
+ width: 16px;
+ height: 16px;
+ background-image: url("./images/addIcon.png");
+}
+
+#slaPanel span.removeIcon {
+ display: block;
+ visibility:hidden;
+ disabled: true;
+ width: 16px;
+ height: 16px;
+ background-image: url("./images/removeIcon.png");
+ cursor: pointer;
+}
+
+#slaPanel .editable:hover span.removeIcon {
+ visibility:visible;
+}
+
+#slaPanel {
+}
+
+#slaPanel span {
+ float: left;
+ margin-left: 5px;
+}
+
+#slaPanel dd {
+ font-size: 10pt;
+}
+
+
.azkaban-charts .expandable-hitarea { background-position: -32px -16px; }
.azkaban-charts .expandable-hitarea.collapse { background-position: 0 -16px; }
/* clean up */
src/web/js/azkaban.exflow.options.view.js 421(+421 -0)
diff --git a/src/web/js/azkaban.exflow.options.view.js b/src/web/js/azkaban.exflow.options.view.js
index e6fdefb..035e18d 100644
--- a/src/web/js/azkaban.exflow.options.view.js
+++ b/src/web/js/azkaban.exflow.options.view.js
@@ -15,9 +15,11 @@
*/
var executeFlowView;
+var advancedScheduleView;
var customSvgGraphView;
var customJobListView;
var cloneModel;
+var scheduleModel;
function recurseAllAncestors(nodes, disabledMap, id, disable) {
var node = nodes[id];
@@ -86,6 +88,425 @@ azkaban.ContextMenu = Backbone.View.extend({
}
});
+azkaban.AdvancedScheduleView = Backbone.View.extend({
+ events : {
+ "click" : "closeEditingTarget",
+ "click #adv-schedule-btn": "handleScheduleFlow",
+ "click #schedule-cancel-btn": "handleCancel",
+ "click .modal-close": "handleCancel",
+ "click #scheduleGeneralOptions": "handleGeneralOptionsSelect",
+ "click #scheduleFlowOptions": "handleFlowOptionsSelect",
+ "click #scheduleSlaOptions": "handleSlaOptionsSelect",
+ "click #scheduleAddRow": "handleAddRow",
+ "click table .editable": "handleEditColumn",
+ "click table .removeIcon": "handleRemoveColumn"
+ },
+ initialize: function(setting) {
+ this.contextMenu = new azkaban.ContextMenu({el:$('#disableJobMenu')});
+ this.handleGeneralOptionsSelect();
+ },
+ show: function() {
+ this.handleGeneralOptionsSelect();
+ $('#advScheduleModalBackground').show();
+ $('#schedule-options').show();
+ this.cloneModel = this.model.clone();
+ scheduleModel = this.cloneModel;
+
+ var fetchData = {"project": projectName, "ajax":"flowInfo", "flow":flowName};
+// if (execId) {
+// fetchData.execid = execId;
+// }
+ this.executeURL = contextURL + "/executor";
+ this.scheduleURL = contextURL + "/schedule";
+ var handleAddRow = this.handleAddRow;
+
+ var data = this.cloneModel.get("data");
+ var nodes = {};
+ for (var i=0; i < data.nodes.length; ++i) {
+ var node = data.nodes[i];
+ nodes[node.id] = node;
+ }
+
+ for (var i=0; i < data.edges.length; ++i) {
+ var edge = data.edges[i];
+ var fromNode = nodes[edge.from];
+ var toNode = nodes[edge.target];
+
+ if (!fromNode.outNodes) {
+ fromNode.outNodes = {};
+ }
+ fromNode.outNodes[toNode.id] = toNode;
+
+ if (!toNode.inNodes) {
+ toNode.inNodes = {};
+ }
+ toNode.inNodes[fromNode.id] = fromNode;
+ }
+ this.cloneModel.set({nodes: nodes});
+
+ var disabled = {};
+// for (var i = 0; i < data.nodes.length; ++i) {
+// var updateNode = data.nodes[i];
+// if (updateNode.status == "DISABLED" || updateNode.status == "SKIPPED") {
+// updateNode.status = "READY";
+// disabled[updateNode.id] = true;
+// }
+// if (updateNode.status == "SUCCEEDED") {
+// disabled[updateNode.id] = true;
+// }
+// }
+ this.cloneModel.set({disabled: disabled});
+
+ $.get(
+ this.executeURL,
+ fetchData,
+ function(data) {
+ if (data.error) {
+ alert(data.error);
+ }
+ else {
+ if (data.successEmails) {
+ $('#scheduleSuccessEmails').val(data.successEmails.join());
+ }
+ if (data.failureEmails) {
+ $('#scheduleFailureEmails').val(data.failureEmails.join());
+ }
+
+ if (data.failureAction) {
+ $('#scheduleFailureAction').val(data.failureAction);
+ }
+ if (data.notifyFailureFirst) {
+ $('#scheduleNotifyFailureFirst').attr('checked', true);
+ }
+ if (data.notifyFailureLast) {
+ $('#scheduleNotifyFailureLast').attr('checked', true);
+ }
+ if (data.flowParam) {
+ var flowParam = data.flowParam;
+ for (var key in flowParam) {
+ var row = handleAddRow();
+ var td = $(row).find('td');
+ $(td[0]).text(key);
+ $(td[1]).text(flowParam[key]);
+ }
+ }
+
+ if (!data.running || data.running.length == 0) {
+ $(".radio").attr("disabled", "disabled");
+ $(".radioLabel").addClass("disabled", "disabled");
+ }
+ }
+ },
+ "json"
+ );
+ },
+ handleCancel: function(evt) {
+ var scheduleURL = contextURL + "/schedule";
+ $('#advScheduleModalBackground').hide();
+ $('#schedule-options').hide();
+ },
+ handleGeneralOptionsSelect: function(evt) {
+ $('#scheduleFlowOptions').removeClass('selected');
+ $('#scheduleSlaOptions').removeClass('selected');
+ $('#scheduleGeneralOptions').addClass('selected');
+
+ $('#scheduleGeneralPanel').show();
+ $('#scheduleGraphPanel').hide();
+ $('#scheduleSlaPanel').hide();
+ },
+ handleSlaOptionsSelect: function(evt) {
+ $('#scheduleFlowOptions').removeClass('selected');
+ $('#scheduleSlaOptions').addClass('selected');
+ $('#scheduleGeneralOptions').removeClass('selected');
+
+ $('#scheduleSlaPanel').show();
+ $('#scheduleGraphPanel').hide();
+ $('#scheduleGeneralPanel').hide();
+ },
+ handleFlowOptionsSelect: function(evt) {
+ $('#scheduleGeneralOptions').removeClass('selected');
+ $('#scheduleFlowOptions').addClass('selected');
+ $('#scheduleSlaOptions').removeClass('selected');
+
+ $('#scheduleGraphPanel').show();
+ $('#scheduleGeneralPanel').hide();
+ $('#scheduleSlaPanel').hide();
+
+ if (this.flowSetup) {
+ return;
+ }
+
+ customSvgGraphView = new azkaban.SvgGraphView({el:$('#scheduleSvgDivCustom'), model: this.cloneModel, rightClick: {id: 'disableJobMenu', callback: this.handleDisableMenuClick}});
+ customJobsListView = new azkaban.JobListView({el:$('#scheduleJobListCustom'), model: this.cloneModel, rightClick: {id: 'disableJobMenu', callback: this.handleDisableMenuClick}});
+ this.cloneModel.trigger("change:graph");
+
+ this.flowSetup = true;
+ },
+ handleScheduleFlow: function(evt) {
+ var scheduleURL = contextURL + "/scheduler";
+ var disabled = this.cloneModel.get("disabled");
+ var failureAction = $('#failureAction').val();
+ var failureEmails = $('#failureEmails').val();
+ var successEmails = $('#successEmails').val();
+ var notifyFailureFirst = $('#notifyFailureFirst').is(':checked');
+ var notifyFailureLast = $('#notifyFailureLast').is(':checked');
+ var executingJobOption = $('input:radio[name=gender]:checked').val();
+
+ var flowOverride = {};
+ var editRows = $(".editRow");
+ for (var i = 0; i < editRows.length; ++i) {
+ var row = editRows[i];
+ var td = $(row).find('td');
+ var key = $(td[0]).text();
+ var val = $(td[1]).text();
+
+ if (key && key.length > 0) {
+ flowOverride[key] = val;
+ }
+ }
+
+ var scheduleData = {
+ project: projectName,
+ ajax: "advScheduleFlow",
+ flow: flowName,
+ hour: $('#advHour').val(),
+ min: $('#advMinutes').val(),
+ am_pm: $('#advAm_pm').val(),
+ timezone: $('#advTimezone').val(),
+ date: $('#advDate').val(),
+ period: $('#advPeriod').val()+$('#advPeriod_units').val(),
+ disable: this.cloneModel.get('disabled'),
+ failureAction: failureAction,
+ failureEmails: failureEmails,
+ successEmails: successEmails,
+ notifyFailureFirst: notifyFailureFirst,
+ notifyFailureLast: notifyFailureLast,
+ executingJobOption: executingJobOption,
+ flowOverride: flowOverride
+ };
+
+ $.get(
+ scheduleURL,
+ scheduleData,
+ function(data) {
+ if (data.error) {
+ alert(data.error);
+ }
+ else {
+ var redirectURL = contextURL + "/schedule";
+ window.location.href = redirectURL;
+ }
+ },
+ "json"
+ );
+ },
+ handleAddRow: function(evt) {
+ var tr = document.createElement("tr");
+ var tdName = document.createElement("td");
+ var tdValue = document.createElement("td");
+
+ var icon = document.createElement("span");
+ $(icon).addClass("removeIcon");
+ var nameData = document.createElement("span");
+ $(nameData).addClass("spanValue");
+ var valueData = document.createElement("span");
+ $(valueData).addClass("spanValue");
+
+ $(tdName).append(icon);
+ $(tdName).append(nameData);
+ $(tdName).addClass("name");
+ $(tdName).addClass("editable");
+
+ $(tdValue).append(valueData);
+ $(tdValue).addClass("editable");
+
+ $(tr).addClass("editRow");
+ $(tr).append(tdName);
+ $(tr).append(tdValue);
+
+ $(tr).insertBefore("#scheduleAddRow");
+ return tr;
+ },
+ handleEditColumn : function(evt) {
+ var curTarget = evt.currentTarget;
+
+ if (this.editingTarget != curTarget) {
+ this.closeEditingTarget();
+
+ var text = $(curTarget).children(".spanValue").text();
+ $(curTarget).empty();
+
+ var input = document.createElement("input");
+ $(input).attr("type", "text");
+ $(input).css("width", "100%");
+ $(input).val(text);
+ $(curTarget).addClass("editing");
+ $(curTarget).append(input);
+ $(input).focus();
+ this.editingTarget = curTarget;
+ }
+ },
+ handleRemoveColumn : function(evt) {
+ var curTarget = evt.currentTarget;
+ // Should be the table
+ var row = curTarget.parentElement.parentElement;
+ $(row).remove();
+ },
+ closeEditingTarget: function(evt) {
+ if (this.editingTarget != null && this.editingTarget != evt.target && this.editingTarget != evt.target.parentElement ) {
+ var input = $(this.editingTarget).children("input")[0];
+ var text = $(input).val();
+ $(input).remove();
+
+ var valueData = document.createElement("span");
+ $(valueData).addClass("spanValue");
+ $(valueData).text(text);
+
+ if ($(this.editingTarget).hasClass("name")) {
+ var icon = document.createElement("span");
+ $(icon).addClass("removeIcon");
+ $(this.editingTarget).append(icon);
+ }
+
+ $(this.editingTarget).removeClass("editing");
+ $(this.editingTarget).append(valueData);
+ this.editingTarget = null;
+ }
+ },
+ handleDisableMenuClick : function(action, el, pos) {
+ var jobid = el[0].jobid;
+ var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowName + "&job=" + jobid;
+ if (action == "open") {
+ window.location.href = requestURL;
+ }
+ else if(action == "openwindow") {
+ window.open(requestURL);
+ }
+ else if(action == "disable") {
+ var disabled = scheduleModel.get("disabled");
+
+ disabled[jobid] = true;
+ scheduleModel.set({disabled: disabled});
+ scheduleModel.trigger("change:disabled");
+ }
+ else if(action == "disableAll") {
+ var disabled = scheduleModel.get("disabled");
+
+ var nodes = scheduleModel.get("nodes");
+ for (var key in nodes) {
+ disabled[key] = true;
+ }
+
+ scheduleModel.set({disabled: disabled});
+ scheduleModel.trigger("change:disabled");
+ }
+ else if (action == "disableParents") {
+ var disabled = scheduleModel.get("disabled");
+ var nodes = scheduleModel.get("nodes");
+ var inNodes = nodes[jobid].inNodes;
+
+ if (inNodes) {
+ for (var key in inNodes) {
+ disabled[key] = true;
+ }
+ }
+
+ scheduleModel.set({disabled: disabled});
+ scheduleModel.trigger("change:disabled");
+ }
+ else if (action == "disableChildren") {
+ var disabledMap = scheduleModel.get("disabled");
+ var nodes = scheduleModel.get("nodes");
+ var outNodes = nodes[jobid].outNodes;
+
+ if (outNodes) {
+ for (var key in outNodes) {
+ disabledMap[key] = true;
+ }
+ }
+
+ scheduleModel.set({disabled: disabledMap});
+ scheduleModel.trigger("change:disabled");
+ }
+ else if (action == "disableAncestors") {
+ var disabled = scheduleModel.get("disabled");
+ var nodes = scheduleModel.get("nodes");
+
+ recurseAllAncestors(nodes, disabled, jobid, true);
+
+ scheduleModel.set({disabled: disabled});
+ scheduleModel.trigger("change:disabled");
+ }
+ else if (action == "disableDescendents") {
+ var disabled = scheduleModel.get("disabled");
+ var nodes = scheduleModel.get("nodes");
+
+ recurseAllDescendents(nodes, disabled, jobid, true);
+
+ scheduleModel.set({disabled: disabled});
+ scheduleModel.trigger("change:disabled");
+ }
+ else if(action == "enable") {
+ var disabled = scheduleModel.get("disabled");
+
+ disabled[jobid] = false;
+ scheduleModel.set({disabled: disabled});
+ scheduleModel.trigger("change:disabled");
+ }
+ else if(action == "enableAll") {
+ disabled = {};
+ scheduleModel.set({disabled: disabled});
+ scheduleModel.trigger("change:disabled");
+ }
+ else if (action == "enableParents") {
+ var disabled = scheduleModel.get("disabled");
+ var nodes = scheduleModel.get("nodes");
+ var inNodes = nodes[jobid].inNodes;
+
+ if (inNodes) {
+ for (var key in inNodes) {
+ disabled[key] = false;
+ }
+ }
+
+ scheduleModel.set({disabled: disabled});
+ scheduleModel.trigger("change:disabled");
+ }
+ else if (action == "enableChildren") {
+ var disabled = scheduleModel.get("disabled");
+ var nodes = scheduleModel.get("nodes");
+ var outNodes = nodes[jobid].outNodes;
+
+ if (outNodes) {
+ for (var key in outNodes) {
+ disabled[key] = false;
+ }
+ }
+
+ scheduleModel.set({disabled: disabled});
+ scheduleModel.trigger("change:disabled");
+ }
+ else if (action == "enableAncestors") {
+ var disabled = scheduleModel.get("disabled");
+ var nodes = scheduleModel.get("nodes");
+
+ recurseAllAncestors(nodes, disabled, jobid, false);
+
+ this.cloneModel.set({disabled: disabled});
+ this.cloneModel.trigger("change:disabled");
+ }
+ else if (action == "enableDescendents") {
+ var disabled = this.cloneModel.get("disabled");
+ var nodes = this.cloneModel.get("nodes");
+
+ recurseAllDescendents(nodes, disabled, jobid, false);
+
+ scheduleModel.set({disabled: disabled});
+ scheduleModel.trigger("change:disabled");
+ }
+ }
+});
+
azkaban.ExecuteFlowView = Backbone.View.extend({
events : {
"click" : "closeEditingTarget",
src/web/js/azkaban.flow.view.js 173(+91 -82)
diff --git a/src/web/js/azkaban.flow.view.js b/src/web/js/azkaban.flow.view.js
index 0624033..7c05e77 100644
--- a/src/web/js/azkaban.flow.view.js
+++ b/src/web/js/azkaban.flow.view.js
@@ -282,77 +282,83 @@ azkaban.ExecutionModel = Backbone.Model.extend({});
var scheduleFlowView;
azkaban.ScheduleFlowView = Backbone.View.extend({
- events : {
- "click #schedule-btn": "handleScheduleFlow"
- },
- initialize : function(settings) {
- $( "#datepicker" ).datepicker();
- $( "#datepicker" ).datepicker('setDate', new Date());
- $("#errorMsg").hide();
- },
- handleScheduleFlow : function(evt) {
- // First make sure we can upload
-// var projectName = $('#path').val();
- var description = $('#description').val();
-
- var hourVal = $('#hour').val();
- var minutesVal = $('#minutes').val();
- var ampmVal = $('#am_pm').val();
- var timezoneVal = $('#timezone').val();
- var dateVal = $('#datepicker').val();
- var is_recurringVal = $('#is_recurring').val();
- var periodVal = $('#period').val();
- var periodUnits = $('#period_units').val();
+ events : {
+ "click #schedule-btn": "handleScheduleFlow",
+ "click #adv-schedule-opt-btn": "handleAdvancedSchedule"
+ },
+ initialize : function(settings) {
+ $( "#datepicker" ).datepicker();
+ $( "#datepicker" ).datepicker('setDate', new Date());
+ $("#errorMsg").hide();
+ },
+ handleAdvancedSchedule : function(evt) {
+ console.log("Clicked advanced schedule options button");
+ //$('#confirm-container').hide();
+ $.modal.close();
+ advancedScheduleView.show();
+ },
+ show: function() {
+// this.cloneModel = this.model.clone();
+// cloneModel = this.cloneModel;
+ },
+ handleScheduleFlow : function(evt) {
- console.log("Creating schedule for "+projectName+"."+flowName);
- $.ajax({
- async: "false",
- url: "schedule",
- dataType: "json",
- type: "POST",
- data: {
- action:"scheduleFlow",
+ var hourVal = $('#hour').val();
+ var minutesVal = $('#minutes').val();
+ var ampmVal = $('#am_pm').val();
+ var timezoneVal = $('#timezone').val();
+ var dateVal = $('#datepicker').val();
+ var is_recurringVal = $('#is_recurring').val();
+ var periodVal = $('#period').val();
+ var periodUnits = $('#period_units').val();
- projectId:projectId,
- projectName:projectName,
- flowName:flowName,
- hour:hourVal,
- minutes:minutesVal,
- am_pm:ampmVal,
- timezone:timezoneVal,
- date:dateVal,
- userExec:"dummy",
- is_recurring:is_recurringVal,
- period:periodVal,
- period_units:periodUnits
- },
- success: function(data) {
- if (data.status == "success") {
- console.log("Successfully scheduled for "+projectName+"."+flowName);
- if (data.action == "redirect") {
- window.location = contextURL + "/manager?project=" + projectName + "&flow=" + flowName ;
- }
- else{
- $("#success_message").text("Flow " + projectName + "." + flowName + " scheduled!" );
- window.location = contextURL + "/manager?project=" + projectName + "&flow=" + flowName ;
+ console.log("Creating schedule for "+projectName+"."+flowName);
+ $.ajax({
+ async: "false",
+ url: "schedule",
+ dataType: "json",
+ type: "POST",
+ data: {
+ action:"scheduleFlow",
+ projectId:projectId,
+ projectName:projectName,
+ flowName:flowName,
+ hour:hourVal,
+ minutes:minutesVal,
+ am_pm:ampmVal,
+ timezone:timezoneVal,
+ date:dateVal,
+ userExec:"dummy",
+ is_recurring:is_recurringVal,
+ period:periodVal,
+ period_units:periodUnits
+ },
+ success: function(data) {
+ if (data.status == "success") {
+ console.log("Successfully scheduled for "+projectName+"."+flowName);
+ if (data.action == "redirect") {
+ window.location = contextURL + "/manager?project=" + projectName + "&flow=" + flowName ;
+ }
+ else{
+ $("#success_message").text("Flow " + projectName + "." + flowName + " scheduled!" );
+ window.location = contextURL + "/manager?project=" + projectName + "&flow=" + flowName ;
+ }
+ }
+ else {
+ if (data.action == "login") {
+ window.location = "";
+ }
+ else {
+ $("#errorMsg").text("ERROR: " + data.message);
+ $("#errorMsg").slideDown("fast");
+ }
}
- }
- else {
- if (data.action == "login") {
- window.location = "";
- }
- else {
- $("#errorMsg").text("ERROR: " + data.message);
- $("#errorMsg").slideDown("fast");
- }
- }
- }
- });
+ }
+ });
- },
- render: function() {
-
- }
+ },
+ render: function() {
+ }
});
@@ -367,8 +373,9 @@ $(function() {
graphModel = new azkaban.GraphModel();
svgGraphView = new azkaban.SvgGraphView({el:$('#svgDiv'), model: graphModel, rightClick: {id: 'jobMenu', callback: handleJobMenuClick}});
jobsListView = new azkaban.JobListView({el:$('#jobList'), model: graphModel, rightClick: {id: 'jobMenu', callback: handleJobMenuClick}});
- scheduleFlowView = new azkaban.ScheduleFlowView({el:$('#schedule-flow')});
+ scheduleFlowView = new azkaban.ScheduleFlowView({el:$('#schedule-flow'), model: graphModel});
executeFlowView = new azkaban.ExecuteFlowView({el:$('#executing-options'), model: graphModel});
+ advancedScheduleView = new azkaban.AdvancedScheduleView({el:$('#schedule-options'), model: graphModel});
var requestURL = contextURL + "/manager";
// Set up the Flow options view. Create a new one every time :p
@@ -435,20 +442,22 @@ $(function() {
"json"
);
+
$('#scheduleflowbtn').click( function() {
- console.log("schedule button clicked");
- $('#schedule-flow').modal({
- closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
- position: ["20%",],
- containerId: 'confirm-container',
- containerCss: {
- 'height': '220px',
- 'width': '500px'
- },
- onShow: function (dialog) {
- var modal = this;
- $("#errorMsg").hide();
- }
- });
+ console.log("schedule button clicked");
+ $('#schedule-flow').modal({
+ closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
+ position: ["20%",],
+ containerId: 'confirm-container',
+ containerCss: {
+ 'height': '220px',
+ 'width': '500px'
+ },
+ onShow: function (dialog) {
+ var modal = this;
+ $("#errorMsg").hide();
+ }
+ });
});
+
});
src/web/js/azkaban.scheduled.view.js 285(+270 -15)
diff --git a/src/web/js/azkaban.scheduled.view.js b/src/web/js/azkaban.scheduled.view.js
index 5cea0ac..9b0ee9a 100644
--- a/src/web/js/azkaban.scheduled.view.js
+++ b/src/web/js/azkaban.scheduled.view.js
@@ -1,21 +1,276 @@
$.namespace('azkaban');
+
function removeSched(projectId, flowName) {
- var scheduleURL = contextURL + "/schedule"
- var redirectURL = contextURL + "/schedule"
- $.post(
- scheduleURL,
- {"action":"removeSched", "projectId":projectId, "flowName":flowName},
- function(data) {
- if (data.error) {
+ var scheduleURL = contextURL + "/schedule"
+ var redirectURL = contextURL + "/schedule"
+ $.post(
+ scheduleURL,
+ {"action":"removeSched", "projectId":projectId, "flowName":flowName},
+ function(data) {
+ if (data.error) {
+// alert(data.error)
+ $('#errorMsg').text(data.error)
+ }
+ else {
+// alert("Schedule "+schedId+" removed!")
+ window.location = redirectURL
+ }
+ },
+ "json"
+ )
+}
+
+function removeSla(projectId, flowName) {
+ var scheduleURL = contextURL + "/schedule"
+ var redirectURL = contextURL + "/schedule"
+ $.post(
+ scheduleURL,
+ {"action":"removeSla", "projectId":projectId, "flowName":flowName},
+ function(data) {
+ if (data.error) {
// alert(data.error)
- $('#errorMsg').text(data.error)
- }
- else {
+ $('#errorMsg').text(data.error)
+ }
+ else {
// alert("Schedule "+schedId+" removed!")
- window.location = redirectURL
- }
- },
- "json"
- )
+ window.location = redirectURL
+ }
+ },
+ "json"
+ )
}
+
+azkaban.ChangeSlaView = Backbone.View.extend({
+ events : {
+ "click" : "closeEditingTarget",
+ "click #set-sla-btn": "handleSetSla",
+ "click #remove-sla-btn": "handleRemoveSla",
+ "click #sla-cancel-btn": "handleSlaCancel",
+ "click .modal-close": "handleSlaCancel",
+ },
+ initialize: function(setting) {
+
+ },
+ handleSlaCancel: function(evt) {
+ console.log("Clicked cancel button");
+ var scheduleURL = contextURL + "/schedule";
+
+ $('#slaModalBackground').hide();
+ $('#sla-options').hide();
+ },
+ initFromSched: function(projId, flowName) {
+ this.projectId = projId;
+ this.flowName = flowName;
+ this.scheduleURL = contextURL + "/schedule"
+ var fetchScheduleData = {"projId": this.projectId, "ajax":"schedInfo", "flowName":this.flowName};
+
+ $.get(
+ this.scheduleURL,
+ fetchScheduleData,
+ function(data) {
+ if (data.error) {
+ alert(data.error);
+ }
+ else {
+ if (data.slaEmails) {
+ $('#slaEmails').val(data.slaEmails.join());
+ }
+ var flowRulesTbl = document.getElementById("flowRulesTbl").tBodies[0];
+ var flowRuleRow = flowRulesTbl.insertRow(-1);
+ var cflowName = flowRuleRow.insertCell(0);
+ cflowName.innerHTML = flowName;
+ var cflowduration = flowRuleRow.insertCell(1);
+ var flowDuration = document.createElement("input");
+ flowDuration.setAttribute("type", "text");
+ flowDuration.setAttribute("id", "flowDuration");
+ flowDuration.setAttribute("class", "durationpick");
+ if(data.flowRules) {
+ flowDuration.setAttribute("value", data.flowRules.duration);
+ }
+ cflowduration.appendChild(flowDuration);
+ var emailAct = flowRuleRow.insertCell(2);
+ var checkEmailAct = document.createElement("input");
+ checkEmailAct.setAttribute("type", "checkbox");
+ emailAct.appendChild(checkEmailAct);
+ var killAct = flowRuleRow.insertCell(3);
+ var checkKillAct = document.createElement("input");
+ checkKillAct.setAttribute("type", "checkbox");
+ killAct.appendChild(checkKillAct);
+
+ var jobRulesTbl = document.getElementById("jobRulesTbl").tBodies[0];
+ var allJobs = data.allJobs;
+ for (var job in allJobs) {
+
+ var jobRuleRow = jobRulesTbl.insertRow(-1);
+ var cjobName = jobRuleRow.insertCell(0);
+ cjobName.innerHTML = allJobs[job];
+ var cjobduration = jobRuleRow.insertCell(1);
+ var jobDuration = document.createElement("input");
+ jobDuration.setAttribute("type", "text");
+ jobDuration.setAttribute("id", "jobDuration");
+ jobDuration.setAttribute("class", "durationpick");
+ if(data.jobRules) {
+ jobDuration.setAttribute("value", data.jobRules[job].duration);
+ }
+ cjobduration.appendChild(jobDuration);
+
+ var emailAct = jobRuleRow.insertCell(2);
+ var checkEmailAct = document.createElement("input");
+ checkEmailAct.setAttribute("type", "checkbox");
+ emailAct.appendChild(checkEmailAct);
+ var killAct = jobRuleRow.insertCell(3);
+ var checkKillAct = document.createElement("input");
+ checkKillAct.setAttribute("type", "checkbox");
+ killAct.appendChild(checkKillAct);
+ }
+ $('.durationpick').timepicker({hourMax: 99});
+ }
+ },
+ "json"
+ );
+
+ $('#slaModalBackground').show();
+ $('#sla-options').show();
+
+// this.schedFlowOptions = sched.flowOptions
+ console.log("Loaded schedule info. Ready to set SLA.");
+
+ },
+ handleRemoveSla: function(evt) {
+ console.log("Clicked remove sla button");
+ var scheduleURL = contextURL + "/schedule"
+ var redirectURL = contextURL + "/schedule"
+ $.post(
+ scheduleURL,
+ {"action":"removeSla", "projectId":this.projectId, "flowName":this.flowName},
+ function(data) {
+ if (data.error) {
+ $('#errorMsg').text(data.error)
+ }
+ else {
+ window.location = redirectURL
+ }
+ "json"
+ }
+ );
+
+ },
+ handleSetSla: function(evt) {
+
+ var slaEmails = $('#slaEmails').val();
+
+// var flowRules = {};
+ var flowRulesTbl = document.getElementById("flowRulesTbl").tBodies[0];
+ var flowRuleRow = flowRulesTbl.rows[0];
+// flowRules["flowDuration"] = flowRuleRow.cells[1].firstChild.value;
+// flowRules["flowEmailAction"] = flowRuleRow.cells[2].firstChild.value;
+// flowRules["flowKillAction"] = flowRuleRow.cells[3].firstChild.value;
+ var flowRules = flowRuleRow.cells[1].firstChild.value + ',' + flowRuleRow.cells[2].firstChild.value + ',' + flowRuleRow.cells[3].firstChild.value;
+
+ var jobRules = {};
+ var jobRulesTbl = document.getElementById("jobRulesTbl").tBodies[0];
+ console.log(jobRulesTbl.rows.length);
+ for(var row = 0; row < jobRulesTbl.rows.length; row++) {
+
+ var jobRow = jobRulesTbl.rows[row];
+ var jobRule = {};
+
+ console.log(row);
+ console.log(jobRow.cells[0].firstChild.value);
+// jobRule["jobDuration"] = jobRow.cells[1].firstChild.value;
+// jobRule["jobEmailAction"] = jobRow.cells[2].firstChild.value;
+// jobRule["jobKillAction"] = jobRow.cells[3].firstChild.value;
+// jobRules[jobRow.cells[0].innerHTML] = jobRule;
+ jobRules[jobRow.cells[0].innerHTML] = jobRow.cells[1].firstChild.value + ',' + jobRow.cells[2].firstChild.value + ',' + jobRow.cells[3].firstChild.value;
+ }
+
+ var slaData = {
+ projectId: this.projectId,
+ flowName: this.flowName,
+ ajax: "setSla",
+ slaEmails: slaEmails,
+ flowRules: flowRules,
+ jobRules: jobRules
+ };
+
+ $.get(
+ this.scheduleURL,
+ slaData,
+ function(data) {
+ if (data.error) {
+ alert(data.error);
+ }
+ else {
+ window.location.href = this.scheduleURL;
+ }
+ },
+ "json"
+ );
+ },
+ handleEditColumn : function(evt) {
+ var curTarget = evt.currentTarget;
+
+ if (this.editingTarget != curTarget) {
+ this.closeEditingTarget();
+
+ var text = $(curTarget).children(".spanValue").text();
+ $(curTarget).empty();
+
+ var input = document.createElement("input");
+ $(input).attr("type", "text");
+ $(input).css("width", "100%");
+ $(input).val(text);
+ $(curTarget).addClass("editing");
+ $(curTarget).append(input);
+ $(input).focus();
+ this.editingTarget = curTarget;
+ }
+ },
+ handleRemoveColumn : function(evt) {
+ var curTarget = evt.currentTarget;
+ // Should be the table
+ var row = curTarget.parentElement.parentElement;
+ $(row).remove();
+ },
+ closeEditingTarget: function(evt) {
+ if (this.editingTarget != null && this.editingTarget != evt.target && this.editingTarget != evt.target.parentElement ) {
+ var input = $(this.editingTarget).children("input")[0];
+ var text = $(input).val();
+ $(input).remove();
+
+ var valueData = document.createElement("span");
+ $(valueData).addClass("spanValue");
+ $(valueData).text(text);
+
+ if ($(this.editingTarget).hasClass("name")) {
+ var icon = document.createElement("span");
+ $(icon).addClass("removeIcon");
+ $(this.editingTarget).append(icon);
+ }
+
+ $(this.editingTarget).removeClass("editing");
+ $(this.editingTarget).append(valueData);
+ this.editingTarget = null;
+ }
+ }
+});
+
+var slaView;
+
+$(function() {
+ var selected;
+
+
+ slaView = new azkaban.ChangeSlaView({el:$('#sla-options')});
+
+// var requestURL = contextURL + "/manager";
+
+ // Set up the Flow options view. Create a new one every time :p
+// $('#addSlaBtn').click( function() {
+// slaView.show();
+// });
+
+
+
+});
\ No newline at end of file
diff --git a/unit/java/azkaban/scheduler/JdbcScheduleLoaderTest.java b/unit/java/azkaban/scheduler/JdbcScheduleLoaderTest.java
index 84dd074..43d3067 100644
--- a/unit/java/azkaban/scheduler/JdbcScheduleLoaderTest.java
+++ b/unit/java/azkaban/scheduler/JdbcScheduleLoaderTest.java
@@ -7,6 +7,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import javax.sql.DataSource;
@@ -15,6 +16,7 @@ import junit.framework.Assert;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
+import org.joda.time.DateTimeZone;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -27,7 +29,7 @@ public class JdbcScheduleLoaderTest {
private static boolean testDBExists;
private static final String host = "localhost";
private static final int port = 3306;
- private static final String database = "azkaban";
+ private static final String database = "azkaban2";
private static final String user = "azkaban";
private static final String password = "azkaban";
private static final int numConnections = 10;
@@ -95,39 +97,6 @@ public class JdbcScheduleLoaderTest {
}
}
-// @Test
-// public void testLoadSchedule() {
-// if (!testDBExists) {
-// return;
-// }
-//
-// DataSource dataSource = DataSourceUtils.getMySQLDataSource(host, port, database, user, password, numConnections);
-// Connection connection = null;
-// try {
-// connection = dataSource.getConnection();
-// } catch (SQLException e) {
-// e.printStackTrace();
-// testDBExists = false;
-// DbUtils.closeQuietly(connection);
-// return;
-// }
-//
-//// CountHandler countHandler = new CountHandler();
-// QueryRunner runner = new QueryRunner();
-// try {
-// int count = runner.update(connection, "DELETE FROM schedules");
-//
-// } catch (SQLException e) {
-// e.printStackTrace();
-// testDBExists = false;
-// DbUtils.closeQuietly(connection);
-// return;
-// }
-// finally {
-// DbUtils.closeQuietly(connection);
-// }
-// }
-
@Test
public void testInsertAndLoadSchedule() throws ScheduleManagerException {
if (!isTestSetup()) {
@@ -137,12 +106,26 @@ public class JdbcScheduleLoaderTest {
JdbcScheduleLoader loader = createLoader();
- Schedule s1 = new Schedule(1, "proj1", "flow1", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu");
- Schedule s2 = new Schedule(1, "proj1", "flow2", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "ccc");
- Schedule s3 = new Schedule(2, "proj1", "flow1", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu");
- Schedule s4 = new Schedule(3, "proj2", "flow1", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu");
- Schedule s5 = new Schedule(3, "proj2", "flow2", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu");
- Schedule s6 = new Schedule(3, "proj2", "flow3", "error", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu");
+ Map<String, Object> scheduleOptions = new HashMap<String, Object>();
+ List<String> disabled = new ArrayList<String>();
+ disabled.add("job1");
+ disabled.add("job2");
+ disabled.add("job3");
+ List<String> failEmails = new ArrayList<String>();
+ failEmails.add("email1");
+ failEmails.add("email2");
+ failEmails.add("email3");
+ boolean hasSla = true;
+ scheduleOptions.put("disabled", disabled);
+ scheduleOptions.put("failEmails", failEmails);
+ scheduleOptions.put("hasSla", hasSla);
+
+ Schedule s1 = new Schedule(1, "proj1", "flow1", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu", scheduleOptions);
+ Schedule s2 = new Schedule(1, "proj1", "flow2", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "ccc", scheduleOptions);
+ Schedule s3 = new Schedule(2, "proj1", "flow1", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu", scheduleOptions);
+ Schedule s4 = new Schedule(3, "proj2", "flow1", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu", scheduleOptions);
+ Schedule s5 = new Schedule(3, "proj2", "flow2", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu", scheduleOptions);
+ Schedule s6 = new Schedule(3, "proj2", "flow3", "error", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu", scheduleOptions);
loader.insertSchedule(s1);
loader.insertSchedule(s2);
@@ -157,7 +140,53 @@ public class JdbcScheduleLoaderTest {
Assert.assertEquals("America/Los_Angeles", schedules.get(0).getTimezone().getID());
Assert.assertEquals(44444, schedules.get(0).getSubmitTime());
Assert.assertEquals("1d", Schedule.createPeriodString(schedules.get(0).getPeriod()));
+ System.out.println("the options are " + schedules.get(0).getSchedOptions());
+ Assert.assertEquals(true, schedules.get(0).getSchedOptions().get("hasSla"));
+ }
+
+ @Test
+ public void testInsertAndUpdateSchedule() throws ScheduleManagerException {
+ if (!isTestSetup()) {
+ return;
+ }
+ clearDB();
+
+ JdbcScheduleLoader loader = createLoader();
+ Map<String, Object> scheduleOptions = new HashMap<String, Object>();
+ List<String> disabled = new ArrayList<String>();
+ disabled.add("job1");
+ disabled.add("job2");
+ disabled.add("job3");
+ List<String> failEmails = new ArrayList<String>();
+ failEmails.add("email1");
+ failEmails.add("email2");
+ failEmails.add("email3");
+ boolean hasSla = true;
+ scheduleOptions.put("disabled", disabled);
+ scheduleOptions.put("failEmails", failEmails);
+ scheduleOptions.put("hasSla", hasSla);
+
+ System.out.println("the options are " + scheduleOptions);
+ Schedule s1 = new Schedule(1, "proj1", "flow1", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu", scheduleOptions);
+
+ loader.insertSchedule(s1);
+
+ hasSla = false;
+ scheduleOptions.put("hasSla", hasSla);
+
+ Schedule s2 = new Schedule(1, "proj1", "flow1", "ready", 11112, "America/Los_Angeles", "2M", 22223, 33334, 44445, "cyu", scheduleOptions);
+
+ loader.updateSchedule(s2);
+
+ List<Schedule> schedules = loader.loadSchedules();
+
+ Assert.assertEquals(1, schedules.size());
+ Assert.assertEquals("America/Los_Angeles", schedules.get(0).getTimezone().getID());
+ Assert.assertEquals(44445, schedules.get(0).getSubmitTime());
+ Assert.assertEquals("2M", Schedule.createPeriodString(schedules.get(0).getPeriod()));
+ System.out.println("the options are " + schedules.get(0).getSchedOptions());
+ Assert.assertEquals(false, schedules.get(0).getSchedOptions().get("hasSla"));
}
@Test
@@ -172,11 +201,25 @@ public class JdbcScheduleLoaderTest {
List<Schedule> schedules = new ArrayList<Schedule>();
- int stress = 100;
+ int stress = 10;
for(int i=0; i<stress; i++)
{
- Schedule s = new Schedule(i+1, "proj"+(i+1), "flow1", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu");
+ Map<String, Object> scheduleOptions = new HashMap<String, Object>();
+ List<String> disabled = new ArrayList<String>();
+ disabled.add("job1");
+ disabled.add("job2");
+ disabled.add("job3");
+ List<String> failEmails = new ArrayList<String>();
+ failEmails.add("email1");
+ failEmails.add("email2");
+ failEmails.add("email3");
+ boolean hasSla = true;
+ scheduleOptions.put("disabled", disabled);
+ scheduleOptions.put("failEmails", failEmails);
+ scheduleOptions.put("hasSla", hasSla);
+
+ Schedule s = new Schedule(i+1, "proj"+(i+1), "flow1", "ready", 11111, "America/Los_Angeles", "1d", 22222, 33333, 44444, "cyu", scheduleOptions);
schedules.add(s);
try {
loader.insertSchedule(s);