azkaban-memoizeit
Changes
src/java/azkaban/database/AzkabanDatabaseSetup.java 341(+341 -0)
src/java/azkaban/database/DataSourceUtils.java 159(+159 -0)
src/java/azkaban/utils/FileIOUtils.java 27(+27 -0)
src/sql/create.active_sla.sql 0(+0 -0)
src/sql/create.execution_flows.sql 2(+1 -1)
src/sql/create.execution_jobs.sql 0(+0 -0)
src/sql/create.execution_logs.sql 0(+0 -0)
src/sql/create.project_events.sql 0(+0 -0)
src/sql/create.project_files.sql 0(+0 -0)
src/sql/create.project_flows.sql 2(+1 -1)
src/sql/create.project_versions.sql 2(+1 -1)
src/sql/create.projects.sql 2(+1 -1)
src/sql/create.properties.sql 7(+7 -0)
src/sql/create.schedules.sql 0(+0 -0)
src/sql/database.properties 1(+1 -0)
src/sql/update.projects.2.1.sql 2(+2 -0)
src/sql/update.schedules.2.1.sql 2(+2 -0)
unit/sql/create.active_sla.sql 2(+1 -1)
unit/sql/create.execution_flows.sql 13(+7 -6)
unit/sql/create.execution_jobs.sql 11(+6 -5)
unit/sql/create.execution_logs.sql 9(+5 -4)
unit/sql/create.project_events.sql 7(+4 -3)
unit/sql/create.project_files.sql 7(+4 -3)
unit/sql/create.project_flows.sql 6(+3 -3)
unit/sql/create.project_versions.sql 7(+4 -3)
unit/sql/create.projects.sql 7(+4 -3)
unit/sql/create.properties.sql 7(+7 -0)
unit/sql/create.schedules.sql 3(+1 -2)
unit/sql/database.properties 1(+1 -0)
unit/sql/update.projects.2.1.sql 2(+2 -0)
unit/sql/update.schedules.2.1.sql 2(+2 -0)
Details
src/java/azkaban/database/AzkabanDatabaseSetup.java 341(+341 -0)
diff --git a/src/java/azkaban/database/AzkabanDatabaseSetup.java b/src/java/azkaban/database/AzkabanDatabaseSetup.java
new file mode 100644
index 0000000..24adcfe
--- /dev/null
+++ b/src/java/azkaban/database/AzkabanDatabaseSetup.java
@@ -0,0 +1,341 @@
+package azkaban.database;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+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.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.dbutils.QueryRunner;
+import org.apache.commons.dbutils.ResultSetHandler;
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.Logger;
+
+import azkaban.database.DataSourceUtils.PropertyType;
+import azkaban.utils.FileIOUtils;
+import azkaban.utils.Props;
+
+public class AzkabanDatabaseSetup {
+ private static final Logger logger = Logger.getLogger(AzkabanDatabaseSetup.class);
+ private static final String SCRIPT_PATH_PARAM = "sql.script.path";
+ private static final String DEFAULT_SCRIPT_PATH = "sql";
+ private static final String CREATE_SCRIPT_PREFIX = "create.";
+ private static final String UPDATE_SCRIPT_PREFIX = "update.";
+ private static final String SQL_SCRIPT_SUFFIX = ".sql";
+
+ private static String FETCH_PROPERTY_BY_TYPE = "SELECT name, value FROM properties WHERE type=?";
+ private static final String INSERT_DB_PROPERTY = "INSERT INTO properties (name, type, value, modified_time) values (?,?,?,?)";
+ private static final String UPDATE_DB_PROPERTY = "UPDATE properties SET value=?,modified_time=? WHERE name=? AND type=?";
+
+ private AzkabanDataSource dataSource;
+ private Map<String, String> tables = new HashMap<String, String>();
+ private Map<String, String> installedVersions = new HashMap<String, String>();
+
+ private String scriptPath = null;
+
+ public AzkabanDatabaseSetup(Props props) {
+ this(DataSourceUtils.getDataSource(props));
+ this.scriptPath = props.getString(SCRIPT_PATH_PARAM, DEFAULT_SCRIPT_PATH);
+ }
+
+ public AzkabanDatabaseSetup(AzkabanDataSource ds) {
+ this.dataSource = ds;
+ if (scriptPath == null) {
+ scriptPath = DEFAULT_SCRIPT_PATH;
+ }
+ }
+
+ public void checkTableVersion(boolean autoCreate, boolean autoUpdate) throws IOException, SQLException {
+ loadInstalledTables();
+ // Loads from the table properties
+ loadTableVersion();
+ if (!tables.isEmpty()) {
+ logger.info("The following are installed tables");
+ for (Map.Entry<String, String> installedTable: tables.entrySet()) {
+ logger.info(" " + installedTable.getKey() + " version:" + installedTable.getValue());
+ }
+ }
+ else {
+ logger.info("No Installed tables found.");
+ }
+
+ Props dbProps = loadDBProps();
+ String version = dbProps.getString("version");
+ logger.info("The current version of Azkaban DB is " + version);
+
+ Set<String> missingTables = findMissingTables();
+ if (!missingTables.isEmpty()) {
+ logger.info("The following tables need to be created.");
+ for (String table: missingTables) {
+ logger.info(" " + table);
+ }
+ }
+ else {
+ logger.info("No tables need to be created");
+ }
+
+ Map<String, List<String>> upgradeList = findOutOfDateTables();
+ if (!upgradeList.isEmpty()) {
+ logger.info("The following tables need to be updated.");
+ for (Map.Entry<String, List<String>> upgradeTable: upgradeList.entrySet()) {
+ String tableInfo = " " + upgradeTable.getKey() + " versions:";
+ for (String upVersion: upgradeTable.getValue()) {
+ tableInfo += upVersion + ",";
+ }
+
+ logger.info(tableInfo);
+ }
+ }
+ else {
+ logger.info("No tables need to be updated.");
+ }
+
+ if (autoCreate && !missingTables.isEmpty()) {
+ createNewTables(missingTables, version);
+ }
+ if (autoUpdate && !upgradeList.isEmpty()) {
+ updateTables(upgradeList);
+ }
+ }
+
+ private Props loadDBProps() throws IOException {
+ File dbPropsFile = new File(this.scriptPath, "database.properties");
+
+ if (!dbPropsFile.exists()) {
+ throw new IOException("Cannot find 'database.properties' file in " + dbPropsFile.getPath());
+ }
+
+ Props props = new Props(null, dbPropsFile);
+ return props;
+ }
+
+ private void loadTableVersion() throws SQLException {
+ logger.info("Searching for table versions in the properties table");
+ if (tables.containsKey("properties")) {
+ // Load version from settings
+ QueryRunner runner = new QueryRunner(dataSource);
+ Map<String,String> map = runner.query(FETCH_PROPERTY_BY_TYPE, new PropertiesHandler(), PropertyType.DB.getNumVal());
+ for (String key: map.keySet()) {
+ String value = map.get(key);
+ if (key.endsWith(".version")) {
+ String tableName = key.substring(0, key.length() - ".version".length());
+ installedVersions.put(tableName, value);
+ if (tables.containsKey(tableName)) {
+ tables.put(tableName, value);
+ }
+ }
+ }
+ }
+ else {
+ logger.info("Properties table doesn't exist.");
+ }
+ }
+
+ private void loadInstalledTables() throws SQLException {
+ logger.info("Searching for installed tables");
+ Connection conn = null;
+ try {
+ conn = dataSource.getConnection();
+ ResultSet rs = conn.getMetaData().getTables(conn.getCatalog(), null, null, new String[]{"TABLE"});
+
+ while(rs.next()) {
+ tables.put(rs.getString("TABLE_NAME").toLowerCase(), "2.1");
+ }
+ }
+ finally {
+ conn.close();
+ }
+ }
+
+ private Set<String> findMissingTables() {
+ HashSet<String> missingTables = new HashSet<String>();
+ File directory = new File(scriptPath);
+ File[] createScripts = directory.listFiles(new FileIOUtils.PrefixSuffixFileFilter(CREATE_SCRIPT_PREFIX, SQL_SCRIPT_SUFFIX));
+
+ for (File script: createScripts) {
+ String name = script.getName();
+ String[] nameSplit = name.split("\\.");
+ String tableName = nameSplit[1];
+
+ if (!tables.containsKey(tableName)) {
+ missingTables.add(tableName);
+ }
+ }
+
+ return missingTables;
+ }
+
+ private Map<String, List<String>> findOutOfDateTables() {
+ Map<String, List<String>> tablesToUpgrade = new HashMap<String, List<String>>();
+
+ for (String key : tables.keySet()) {
+ String version = tables.get(key);
+
+ List<String> upgradeVersions = findOutOfDateTable(key, version);
+ if (upgradeVersions != null && !upgradeVersions.isEmpty()) {
+ tablesToUpgrade.put(key, upgradeVersions);
+ }
+ }
+
+ return tablesToUpgrade;
+ }
+
+ private List<String> findOutOfDateTable(String table, String version) {
+ File directory = new File(scriptPath);
+ ArrayList<String> versions = new ArrayList<String>();
+
+ File[] createScripts = directory.listFiles(new FileIOUtils.PrefixSuffixFileFilter(UPDATE_SCRIPT_PREFIX + table, SQL_SCRIPT_SUFFIX));
+ if (createScripts.length == 0) {
+ return null;
+ }
+
+ String updateFileNameVersion = UPDATE_SCRIPT_PREFIX + table + "." + version;
+ for (File file: createScripts) {
+ String fileName = file.getName();
+ if (fileName.compareTo(updateFileNameVersion) > 0) {
+ String[] split = fileName.split("\\.");
+ String versionNum = "";
+
+ for (int i = 2; i < split.length - 1; ++i) {
+ try {
+ Integer.parseInt(split[i]);
+ versionNum += split[i] + ".";
+ }
+ catch (NumberFormatException e) {
+ break;
+ }
+ }
+ if (versionNum.endsWith(".")) {
+ versionNum = versionNum.substring(0, versionNum.length() - 1);
+
+ if (versionNum.compareTo(version) > 0) {
+ versions.add(versionNum);
+ }
+ }
+ }
+ }
+
+ Collections.sort(versions);
+ return versions;
+ }
+
+ public void createNewTables(Set<String> missingTables, String version) throws SQLException, IOException {
+ Connection conn = dataSource.getConnection();
+ conn.setAutoCommit(false);
+ try {
+ // Make sure that properties table is created first.
+ if (missingTables.contains("properties")) {
+ runTableScripts(conn, "properties", version, dataSource.getDBType(), false);
+ }
+ for (String table: missingTables) {
+ if (!table.equals("properties")) {
+ runTableScripts(conn, table, version, dataSource.getDBType(), false);
+ }
+ }
+ }
+ finally {
+ conn.close();
+ }
+ }
+
+ public void updateTables(Map<String, List<String>> updateTables) throws SQLException, IOException {
+ Connection conn = dataSource.getConnection();
+ conn.setAutoCommit(false);
+ try {
+ // Make sure that properties table is created first.
+ if (updateTables.containsKey("properties")) {
+ for (String version: updateTables.get("properties")) {
+ runTableScripts(conn, "properties", version, dataSource.getDBType(), true);
+ }
+ }
+ for (String table: updateTables.keySet()) {
+ if (!table.equals("properties")) {
+ for (String version: updateTables.get(table)) {
+ runTableScripts(conn, table, version, dataSource.getDBType(), true);
+ }
+ }
+ }
+ }
+ finally {
+ conn.close();
+ }
+ }
+
+ private void runTableScripts(Connection conn, String table, String version, String dbType, boolean update) throws IOException, SQLException {
+ String scriptName = "";
+ if (update) {
+ scriptName = "update." + table + "." + version;
+ logger.info("Update table " + table + " to version " + version);
+ }
+ else {
+ scriptName = "create." + table;
+ logger.info("Creating new table " + table + " version " + version);
+ }
+
+ String dbSpecificScript = scriptName + "." + dbType + ".sql";
+
+ File script = new File(scriptPath, dbSpecificScript);
+ if (!script.exists()) {
+ String dbScript = scriptName + ".sql";
+ script = new File(scriptPath, dbScript);
+
+ if (!script.exists()) {
+ throw new IOException("Creation files do not exist for table " + table);
+ }
+ }
+
+ BufferedInputStream buff = null;
+ try {
+ buff = new BufferedInputStream(new FileInputStream(script));
+ String queryStr = IOUtils.toString(buff);
+
+ String[] splitQuery = queryStr.split(";\\s*\n");
+
+ QueryRunner runner = new QueryRunner();
+
+ for (String query: splitQuery) {
+ runner.update(conn, query);
+ }
+
+ // If it's properties, then we want to commit the table before we update it
+ if (table.equals("properties")) {
+ conn.commit();
+ }
+
+ String propertyName = table + ".version";
+ if (!installedVersions.containsKey(table)) {
+ runner.update(conn, INSERT_DB_PROPERTY, propertyName, DataSourceUtils.PropertyType.DB.getNumVal(), version, System.currentTimeMillis());
+ }
+ else {
+ runner.update(conn, UPDATE_DB_PROPERTY, version, System.currentTimeMillis(), propertyName, DataSourceUtils.PropertyType.DB.getNumVal());
+ }
+ conn.commit();
+ }
+ finally {
+ IOUtils.closeQuietly(buff);
+ }
+ }
+
+ public static class PropertiesHandler implements ResultSetHandler<Map<String, String>> {
+ @Override
+ public Map<String, String> handle(ResultSet rs) throws SQLException {
+ Map<String, String> results = new HashMap<String, String>();
+ while(rs.next()) {
+ String key = rs.getString(1);
+ String value = rs.getString(2);
+ results.put(key, value);
+ }
+
+ return results;
+ }
+ }
+}
diff --git a/src/java/azkaban/database/AzkabanDataSource.java b/src/java/azkaban/database/AzkabanDataSource.java
new file mode 100644
index 0000000..077e6cd
--- /dev/null
+++ b/src/java/azkaban/database/AzkabanDataSource.java
@@ -0,0 +1,9 @@
+package azkaban.database;
+
+import org.apache.commons.dbcp.BasicDataSource;
+
+public abstract class AzkabanDataSource extends BasicDataSource {
+ public abstract boolean allowsOnDuplicateKey();
+
+ public abstract String getDBType();
+}
\ No newline at end of file
src/java/azkaban/database/DataSourceUtils.java 159(+159 -0)
diff --git a/src/java/azkaban/database/DataSourceUtils.java b/src/java/azkaban/database/DataSourceUtils.java
new file mode 100644
index 0000000..9c4b74a
--- /dev/null
+++ b/src/java/azkaban/database/DataSourceUtils.java
@@ -0,0 +1,159 @@
+
+package azkaban.database;
+
+import java.sql.SQLException;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.dbutils.QueryRunner;
+
+import azkaban.utils.Props;
+
+public class DataSourceUtils {
+
+ /**
+ * Property types
+ */
+ public static enum PropertyType {
+ DB(1);
+
+ private int numVal;
+
+ PropertyType(int numVal) {
+ this.numVal = numVal;
+ }
+
+ public int getNumVal() {
+ return numVal;
+ }
+
+ public static PropertyType fromInteger(int x) {
+ switch (x) {
+ case 1:
+ return DB;
+ default:
+ return DB;
+ }
+ }
+ }
+
+ /**
+ * Create Datasource from parameters in the properties
+ *
+ * @param props
+ * @return
+ */
+ public static AzkabanDataSource getDataSource(Props props) {
+ String databaseType = props.getString("database.type");
+
+ AzkabanDataSource dataSource = null;
+ if (databaseType.equals("mysql")) {
+ int port = props.getInt("mysql.port");
+ String host = props.getString("mysql.host");
+ String database = props.getString("mysql.database");
+ String user = props.getString("mysql.user");
+ String password = props.getString("mysql.password");
+ int numConnections = props.getInt("mysql.numconnections");
+
+ dataSource = getMySQLDataSource(host, port, database, user, password, numConnections);
+ }
+ else if (databaseType.equals("h2")) {
+ String path = props.getString("h2.path");
+ dataSource = getH2DataSource(path);
+ }
+
+ return dataSource;
+ }
+
+ /**
+ * Create a MySQL DataSource
+ *
+ * @param host
+ * @param port
+ * @param dbName
+ * @param user
+ * @param password
+ * @param numConnections
+ * @return
+ */
+ public static AzkabanDataSource getMySQLDataSource(String host, Integer port, String dbName, String user, String password, Integer numConnections) {
+ return new MySQLBasicDataSource(host, port, dbName, user, password, numConnections);
+ }
+
+ /**
+ * Create H2 DataSource
+ * @param file
+ * @return
+ */
+ public static AzkabanDataSource getH2DataSource(String file) {
+ return new EmbeddedH2BasicDataSource(file);
+ }
+
+ /**
+ * Hidden datasource
+ */
+ private DataSourceUtils() {
+ }
+
+ /**
+ * MySQL data source based on AzkabanDataSource
+ *
+ */
+ public static class MySQLBasicDataSource extends AzkabanDataSource {
+ private MySQLBasicDataSource(String host, int port, String dbName, String user, String password, int numConnections) {
+ super();
+
+ String url = "jdbc:mysql://" + (host + ":" + port + "/" + dbName);
+ setDriverClassName("com.mysql.jdbc.Driver");
+ setUsername(user);
+ setPassword(password);
+ setUrl(url);
+ setMaxActive(numConnections);
+ setValidationQuery("/* ping */ select 1");
+ setTestOnBorrow(true);
+ }
+
+ @Override
+ public boolean allowsOnDuplicateKey() {
+ return true;
+ }
+
+ @Override
+ public String getDBType() {
+ return "mysql";
+ }
+ }
+
+ /**
+ * H2 Datasource
+ *
+ */
+ public static class EmbeddedH2BasicDataSource extends AzkabanDataSource {
+ private EmbeddedH2BasicDataSource(String filePath) {
+ super();
+ String url = "jdbc:h2:file:" + filePath;
+ setDriverClassName("org.h2.Driver");
+ setUrl(url);
+ }
+
+ @Override
+ public boolean allowsOnDuplicateKey() {
+ return false;
+ }
+
+ @Override
+ public String getDBType() {
+ return "h2";
+ }
+ }
+
+ public static void testConnection(DataSource ds) throws SQLException {
+ QueryRunner runner = new QueryRunner(ds);
+ runner.update("SHOW TABLES");
+ }
+
+ public static void testMySQLConnection(String host, Integer port, String dbName, String user, String password, Integer numConnections) throws SQLException {
+ DataSource ds = new MySQLBasicDataSource(host, port, dbName, user, password, numConnections);
+ testConnection(ds);
+ }
+}
diff --git a/src/java/azkaban/executor/JdbcExecutorLoader.java b/src/java/azkaban/executor/JdbcExecutorLoader.java
index 912c338..3390074 100644
--- a/src/java/azkaban/executor/JdbcExecutorLoader.java
+++ b/src/java/azkaban/executor/JdbcExecutorLoader.java
@@ -22,7 +22,7 @@ import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
-import azkaban.utils.db.AbstractJdbcLoader;
+import azkaban.database.AbstractJdbcLoader;
import azkaban.utils.FileIOUtils;
import azkaban.utils.FileIOUtils.LogData;
import azkaban.utils.GZIPUtils;
diff --git a/src/java/azkaban/project/JdbcProjectLoader.java b/src/java/azkaban/project/JdbcProjectLoader.java
index 972b531..7825694 100644
--- a/src/java/azkaban/project/JdbcProjectLoader.java
+++ b/src/java/azkaban/project/JdbcProjectLoader.java
@@ -27,7 +27,7 @@ import azkaban.flow.Flow;
import azkaban.project.ProjectLogEvent.EventType;
import azkaban.user.Permission;
import azkaban.user.User;
-import azkaban.utils.db.AbstractJdbcLoader;
+import azkaban.database.AbstractJdbcLoader;
import azkaban.utils.GZIPUtils;
import azkaban.utils.JSONUtils;
import azkaban.utils.Md5Hasher;
diff --git a/src/java/azkaban/scheduler/JdbcScheduleLoader.java b/src/java/azkaban/scheduler/JdbcScheduleLoader.java
index 4b70fbc..39a6cf5 100644
--- a/src/java/azkaban/scheduler/JdbcScheduleLoader.java
+++ b/src/java/azkaban/scheduler/JdbcScheduleLoader.java
@@ -32,7 +32,7 @@ import org.apache.log4j.Logger;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadablePeriod;
-import azkaban.utils.db.AbstractJdbcLoader;
+import azkaban.database.AbstractJdbcLoader;
import azkaban.utils.GZIPUtils;
import azkaban.utils.JSONUtils;
import azkaban.utils.Props;
diff --git a/src/java/azkaban/sla/JdbcSLALoader.java b/src/java/azkaban/sla/JdbcSLALoader.java
index ffa4221..367dec3 100644
--- a/src/java/azkaban/sla/JdbcSLALoader.java
+++ b/src/java/azkaban/sla/JdbcSLALoader.java
@@ -15,7 +15,7 @@ import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import azkaban.sla.SLA.SlaRule;
-import azkaban.utils.db.AbstractJdbcLoader;
+import azkaban.database.AbstractJdbcLoader;
import azkaban.utils.GZIPUtils;
import azkaban.utils.JSONUtils;
import azkaban.utils.Props;
src/java/azkaban/utils/FileIOUtils.java 27(+27 -0)
diff --git a/src/java/azkaban/utils/FileIOUtils.java b/src/java/azkaban/utils/FileIOUtils.java
index 6f1c0c5..d1b275f 100644
--- a/src/java/azkaban/utils/FileIOUtils.java
+++ b/src/java/azkaban/utils/FileIOUtils.java
@@ -3,6 +3,7 @@ package azkaban.utils;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -20,6 +21,32 @@ import org.apache.commons.io.IOUtils;
* Runs a few unix commands. Created this so that I can move to JNI in the future.
*/
public class FileIOUtils {
+
+ public static class PrefixSuffixFileFilter implements FileFilter {
+ private String prefix;
+ private String suffix;
+
+ public PrefixSuffixFileFilter(String prefix, String suffix) {
+ this.prefix = prefix;
+ this.suffix = suffix;
+ }
+
+ @Override
+ public boolean accept(File pathname) {
+ if (!pathname.isFile() || pathname.isHidden()) {
+ return false;
+ }
+
+ String name = pathname.getName();
+ int length = name.length();
+ if (suffix.length() > length || prefix.length() > length ) {
+ return false;
+ }
+
+ return name.startsWith(prefix) && name.endsWith(suffix);
+ }
+ }
+
public static String getSourcePathFromClass(Class<?> containedClass) {
File file = new File(containedClass.getProtectionDomain().getCodeSource().getLocation().getPath());
diff --git a/src/java/azkaban/webapp/AzkabanWebServer.java b/src/java/azkaban/webapp/AzkabanWebServer.java
index c8f6244..24501c7 100644
--- a/src/java/azkaban/webapp/AzkabanWebServer.java
+++ b/src/java/azkaban/webapp/AzkabanWebServer.java
@@ -67,7 +67,6 @@ import azkaban.sla.SLAManager;
import azkaban.sla.SLAManagerException;
import azkaban.user.UserManager;
import azkaban.user.XmlUserManager;
-import azkaban.utils.db.AbstractJdbcLoader;
import azkaban.utils.FileIOUtils;
import azkaban.utils.Props;
import azkaban.utils.PropsUtils;
@@ -397,8 +396,6 @@ public class AzkabanWebServer implements AzkabanServer {
return;
}
- AbstractJdbcLoader.setupTables(azkabanSettings);
-
int maxThreads = azkabanSettings.getInt("jetty.maxThreads", DEFAULT_THREAD_NUMBER);
int port;
boolean usingSSL = false;
diff --git a/src/java/azkaban/webapp/servlet/admin/InitialSetupServlet.java b/src/java/azkaban/webapp/servlet/admin/InitialSetupServlet.java
index c087f86..6914f04 100644
--- a/src/java/azkaban/webapp/servlet/admin/InitialSetupServlet.java
+++ b/src/java/azkaban/webapp/servlet/admin/InitialSetupServlet.java
@@ -39,7 +39,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
-import azkaban.utils.db.DataSourceUtils;
+import azkaban.database.DataSourceUtils;
import azkaban.utils.Props;
import azkaban.utils.Utils;
import azkaban.webapp.AzkabanAdminServer;
diff --git a/src/package/execserver/bin/azkaban-executor-shutdown.sh b/src/package/execserver/bin/azkaban-executor-shutdown.sh
new file mode 100644
index 0000000..21fea3c
--- /dev/null
+++ b/src/package/execserver/bin/azkaban-executor-shutdown.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+executorport=`cat conf/azkaban.properties | grep executor.port | cut -d = -f 2`
+echo "Shutting down current running AzkabanExecutorServer at port $executorport"
+
+proc=`cat currentpid`
+
+kill $proc
+
+cat /dev/null > currentpid
diff --git a/src/package/execserver/bin/azkaban-executor-start.sh b/src/package/execserver/bin/azkaban-executor-start.sh
new file mode 100644
index 0000000..56c7dc9
--- /dev/null
+++ b/src/package/execserver/bin/azkaban-executor-start.sh
@@ -0,0 +1,41 @@
+azkaban_dir=$(dirname $0)/..
+base_dir=$1
+tmpdir=
+
+if [[ -z "$tmpdir" ]]; then
+echo "temp directory must be set!"
+exit
+fi
+
+for file in $azkaban_dir/lib/*.jar;
+do
+ CLASSPATH=$CLASSPATH:$file
+done
+
+for file in $azkaban_dir/extlib/*.jar;
+do
+ CLASSPATH=$CLASSPATH:$file
+done
+
+for file in $base_dir/plugins/*/*.jar;
+do
+ CLASSPATH=$CLASSPATH:$file
+done
+
+echo $azkaban_dir;
+echo $base_dir;
+echo $CLASSPATH;
+
+executorport=`cat conf/azkaban.properties | grep executor.port | cut -d = -f 2`
+echo "Starting AzkabanExecutorServer on port $executorport ..."
+serverpath=`pwd`
+
+if [ -z $AZKABAN_OPTS ]; then
+ AZKABAN_OPTS=-Xmx3G
+fi
+AZKABAN_OPTS=$AZKABAN_OPTS -server -Dcom.sun.management.jmxremote -Djava.io.tmpdir=$tmpdir -Dexecutorport=$executorport -Dserverpath=$serverpath
+
+java $AZKABAN_OPTS -cp $CLASSPATH azkaban.webapp.AzkabanExecutorServer -conf $base_dir/conf $@ &
+
+echo $! > currentpid
+
diff --git a/src/package/execserver/conf/azkaban.private.properties b/src/package/execserver/conf/azkaban.private.properties
new file mode 100644
index 0000000..cce1792
--- /dev/null
+++ b/src/package/execserver/conf/azkaban.private.properties
@@ -0,0 +1 @@
+# Optional Properties that are hidden to the executions
\ No newline at end of file
diff --git a/src/package/execserver/conf/azkaban.properties b/src/package/execserver/conf/azkaban.properties
new file mode 100644
index 0000000..f42ea60
--- /dev/null
+++ b/src/package/execserver/conf/azkaban.properties
@@ -0,0 +1,19 @@
+#Azkaban
+default.timezone.id=America/Los_Angeles
+
+#Loader for projects
+executor.global.properties=conf/global.properties
+azkaban.project.dir=projects
+
+database.type=mysql
+mysql.port=3306
+mysql.host=localhost
+mysql.database=azkaban2
+mysql.user=azkaban
+mysql.password=azkaban
+mysql.numconnections=100
+
+# Azkaban Executor settings
+executor.maxThreads=50
+executor.port=12321
+executor.flow.threads=30
\ No newline at end of file
diff --git a/src/package/execserver/conf/global.properties b/src/package/execserver/conf/global.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/package/execserver/conf/global.properties
diff --git a/src/package/soloserver/bin/azkaban-solo-shutdown.sh b/src/package/soloserver/bin/azkaban-solo-shutdown.sh
new file mode 100644
index 0000000..75efe2e
--- /dev/null
+++ b/src/package/soloserver/bin/azkaban-solo-shutdown.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+proc=`cat currentpid`
+echo "killing AzkabanSingleServer"
+kill $proc
+
+cat /dev/null > currentpid
diff --git a/src/package/soloserver/bin/azkaban-solo-start.sh b/src/package/soloserver/bin/azkaban-solo-start.sh
new file mode 100644
index 0000000..54704a9
--- /dev/null
+++ b/src/package/soloserver/bin/azkaban-solo-start.sh
@@ -0,0 +1,40 @@
+azkaban_dir=$(dirname $0)/..
+base_dir=$1
+tmpdir=
+
+if [[ -z "$tmpdir" ]]; then
+echo "temp directory must be set!"
+exit
+fi
+
+for file in $azkaban_dir/lib/*.jar;
+do
+ CLASSPATH=$CLASSPATH:$file
+done
+
+for file in $azkaban_dir/extlib/*.jar;
+do
+ CLASSPATH=$CLASSPATH:$file
+done
+
+for file in $base_dir/plugins/*/*.jar;
+do
+ CLASSPATH=$CLASSPATH:$file
+done
+
+echo $azkaban_dir;
+echo $base_dir;
+echo $CLASSPATH;
+
+executorport=`cat conf/azkaban.properties | grep executor.port | cut -d = -f 2`
+serverpath=`pwd`
+
+if [ -z $AZKABAN_OPTS ]; then
+ AZKABAN_OPTS=-Xmx3G
+fi
+AZKABAN_OPTS=$AZKABAN_OPTS -server -Dcom.sun.management.jmxremote -Djava.io.tmpdir=$tmpdir -Dexecutorport=$executorport -Dserverpath=$serverpath
+
+java $AZKABAN_OPTS -cp $CLASSPATH azkaban.webapp.AzkabanSingleServer -conf $base_dir/conf $@ &
+
+echo $! > currentpid
+
diff --git a/src/package/soloserver/conf/azkaban.private.properties b/src/package/soloserver/conf/azkaban.private.properties
new file mode 100644
index 0000000..cce1792
--- /dev/null
+++ b/src/package/soloserver/conf/azkaban.private.properties
@@ -0,0 +1 @@
+# Optional Properties that are hidden to the executions
\ No newline at end of file
diff --git a/src/package/soloserver/conf/azkaban.properties b/src/package/soloserver/conf/azkaban.properties
new file mode 100644
index 0000000..df46e5d
--- /dev/null
+++ b/src/package/soloserver/conf/azkaban.properties
@@ -0,0 +1,41 @@
+#Azkaban Personalization Settings
+azkaban.name=Local
+azkaban.label=My Local Azkaban
+azkaban.color=#FF3601
+web.resource.dir=web/
+default.timezone.id=America/Los_Angeles
+
+#Azkaban UserManager class
+user.manager.class=azkaban.user.XmlUserManager
+user.manager.xml.file=conf/azkaban-users.xml
+
+#Loader for projects
+executor.global.properties=conf/global.properties
+azkaban.project.dir=projects
+
+#H2 DB setup
+database.auto.create.tables=true
+database.type=h2
+h2.path=data/azkaban
+h2.create.tables=true
+
+# Velocity dev mode
+velocity.dev.mode=false
+
+# Azkaban Jetty server properties. Ignored in tomcat
+jetty.use.ssl=false
+jetty.maxThreads=25
+jetty.port=8081
+
+# Azkaban Executor settings
+executor.maxThreads=50
+executor.port=12321
+executor.flow.threads=30
+
+# mail settings
+mail.sender=
+mail.host=
+job.failure.email=
+job.success.email=
+
+lockdown.create.projects=false
\ No newline at end of file
diff --git a/src/package/soloserver/conf/azkaban-users.xml b/src/package/soloserver/conf/azkaban-users.xml
new file mode 100644
index 0000000..b30da41
--- /dev/null
+++ b/src/package/soloserver/conf/azkaban-users.xml
@@ -0,0 +1,5 @@
+<azkaban-users>
+ <user username="azkaban" password="azkaban" roles="admin" groups="azkaban" />
+
+ <role name="admin" permissions="ADMIN" />
+</azkaban-users>
diff --git a/src/package/soloserver/conf/global.properties b/src/package/soloserver/conf/global.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/package/soloserver/conf/global.properties
diff --git a/src/package/webserver/bin/azkaban-web-shutdown.sh b/src/package/webserver/bin/azkaban-web-shutdown.sh
new file mode 100644
index 0000000..662e4a6
--- /dev/null
+++ b/src/package/webserver/bin/azkaban-web-shutdown.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+proc=`cat currentpid`
+echo "killing AzkabanWebServer"
+kill $proc
+
+cat /dev/null > currentpid
diff --git a/src/package/webserver/bin/azkaban-web-start.sh b/src/package/webserver/bin/azkaban-web-start.sh
new file mode 100644
index 0000000..1df0f4f
--- /dev/null
+++ b/src/package/webserver/bin/azkaban-web-start.sh
@@ -0,0 +1,40 @@
+azkaban_dir=$(dirname $0)/..
+base_dir=$1
+tmpdir=
+
+if [[ -z "$tmpdir" ]]; then
+echo "temp directory must be set!"
+exit
+fi
+
+for file in $azkaban_dir/lib/*.jar;
+do
+ CLASSPATH=$CLASSPATH:$file
+done
+
+for file in $azkaban_dir/extlib/*.jar;
+do
+ CLASSPATH=$CLASSPATH:$file
+done
+
+for file in $base_dir/plugins/*/*.jar;
+do
+ CLASSPATH=$CLASSPATH:$file
+done
+
+echo $azkaban_dir;
+echo $base_dir;
+echo $CLASSPATH;
+
+executorport=`cat conf/azkaban.properties | grep executor.port | cut -d = -f 2`
+serverpath=`pwd`
+
+if [ -z $AZKABAN_OPTS ]; then
+ AZKABAN_OPTS=-Xmx3G
+fi
+AZKABAN_OPTS=$AZKABAN_OPTS -server -Dcom.sun.management.jmxremote -Djava.io.tmpdir=$tmpdir -Dexecutorport=$executorport -Dserverpath=$serverpath
+
+java $AZKABAN_OPTS -cp $CLASSPATH azkaban.webapp.AzkabanWebServer -conf $base_dir/conf $@ &
+
+echo $! > currentpid
+
diff --git a/src/package/webserver/conf/azkaban.properties b/src/package/webserver/conf/azkaban.properties
new file mode 100644
index 0000000..c9aed8d
--- /dev/null
+++ b/src/package/webserver/conf/azkaban.properties
@@ -0,0 +1,46 @@
+#Azkaban Personalization Settings
+azkaban.name=Local
+azkaban.label=My Local Azkaban
+azkaban.color=#FF3601
+web.resource.dir=web/
+default.timezone.id=America/Los_Angeles
+
+#Azkaban UserManager class
+user.manager.class=azkaban.user.XmlUserManager
+user.manager.xml.file=conf/azkaban-users.xml
+
+#Loader for projects
+executor.global.properties=conf/global.properties
+azkaban.project.dir=projects
+
+database.type=mysql
+mysql.port=3306
+mysql.host=localhost
+mysql.database=azkaban
+mysql.user=azkaban
+mysql.password=azkaban
+mysql.numconnections=100
+
+# Velocity dev mode
+velocity.dev.mode=false
+
+# Azkaban Jetty server properties.
+jetty.maxThreads=25
+jetty.ssl.port=8443
+jetty.port=8081
+jetty.keystore=keystore
+jetty.password=password
+jetty.keypassword=password
+jetty.truststore=keystore
+jetty.trustpassword=password
+
+# Azkaban Executor settings
+executor.port=12321
+
+# mail settings
+mail.sender=
+mail.host=
+job.failure.email=
+job.success.email=
+
+lockdown.create.projects=false
\ No newline at end of file
diff --git a/src/package/webserver/conf/azkaban-users.xml b/src/package/webserver/conf/azkaban-users.xml
new file mode 100644
index 0000000..b30da41
--- /dev/null
+++ b/src/package/webserver/conf/azkaban-users.xml
@@ -0,0 +1,5 @@
+<azkaban-users>
+ <user username="azkaban" password="azkaban" roles="admin" groups="azkaban" />
+
+ <role name="admin" permissions="ADMIN" />
+</azkaban-users>
src/sql/create.properties.sql 7(+7 -0)
diff --git a/src/sql/create.properties.sql b/src/sql/create.properties.sql
new file mode 100644
index 0000000..4a62c51
--- /dev/null
+++ b/src/sql/create.properties.sql
@@ -0,0 +1,7 @@
+CREATE TABLE properties (
+ name VARCHAR(64) NOT NULL,
+ type INT NOT NULL,
+ modified_time BIGINT NOT NULL,
+ value VARCHAR(256),
+ PRIMARY KEY (id, type)
+);
\ No newline at end of file
src/sql/database.properties 1(+1 -0)
diff --git a/src/sql/database.properties b/src/sql/database.properties
new file mode 100644
index 0000000..7b323f0
--- /dev/null
+++ b/src/sql/database.properties
@@ -0,0 +1 @@
+version=2.2
diff --git a/src/sql/update.execution_jobs.2.1.sql b/src/sql/update.execution_jobs.2.1.sql
new file mode 100644
index 0000000..b7313ad
--- /dev/null
+++ b/src/sql/update.execution_jobs.2.1.sql
@@ -0,0 +1,4 @@
+ALTER TABLE execution_jobs ADD COLUMN attempt INT DEFAULT 0;
+ALTER TABLE execution_jobs DROP PRIMARY KEY;
+ALTER TABLE execution_jobs ADD PRIMARY KEY(exec_id, job_id, attempt);
+ALTER TABLE execution_jobs ADD INDEX exec_job (exec_id, job_id);
diff --git a/src/sql/update.execution_logs.2.1.sql b/src/sql/update.execution_logs.2.1.sql
new file mode 100644
index 0000000..5c2dc0b
--- /dev/null
+++ b/src/sql/update.execution_logs.2.1.sql
@@ -0,0 +1,7 @@
+ALTER TABLE execution_logs ADD COLUMN attempt INT DEFAULT 0;
+ALTER TABLE execution_logs ADD COLUMN upload_time BIGINT DEFAULT 1420099200000;
+UPDATE execution_logs SET upload_time=(UNIX_TIMESTAMP()*1000) WHERE upload_time=1420099200000;
+
+ALTER TABLE execution_logs DROP PRIMARY KEY;
+ALTER TABLE execution_logs ADD PRIMARY KEY(exec_id, name, attempt, start_byte);
+ALTER TABLE execution_logs ADD INDEX ex_log_attempt (exec_id, name, attempt)
diff --git a/src/sql/update.project_events.2.1.sql b/src/sql/update.project_events.2.1.sql
new file mode 100644
index 0000000..14d7554
--- /dev/null
+++ b/src/sql/update.project_events.2.1.sql
@@ -0,0 +1 @@
+ALTER TABLE project_events MODIFY COLUMN message VARCHAR(512);
src/sql/update.projects.2.1.sql 2(+2 -0)
diff --git a/src/sql/update.projects.2.1.sql b/src/sql/update.projects.2.1.sql
new file mode 100644
index 0000000..a2a6e48
--- /dev/null
+++ b/src/sql/update.projects.2.1.sql
@@ -0,0 +1,2 @@
+ALTER TABLE projects ADD COLUMN enc_type TINYINT;
+ALTER TABLE projects ADD COLUMN settings_blob LONGBLOB;
src/sql/update.schedules.2.1.sql 2(+2 -0)
diff --git a/src/sql/update.schedules.2.1.sql b/src/sql/update.schedules.2.1.sql
new file mode 100644
index 0000000..fbf38ce
--- /dev/null
+++ b/src/sql/update.schedules.2.1.sql
@@ -0,0 +1,2 @@
+ALTER TABLE schedules ADD COLUMN enc_type TINYINT;
+ALTER TABLE schedules ADD COLUMN schedule_options LONGBLOB;
diff --git a/unit/java/azkaban/scheduler/JdbcScheduleLoaderTest.java b/unit/java/azkaban/scheduler/JdbcScheduleLoaderTest.java
index 7ead267..9bd48b1 100644
--- a/unit/java/azkaban/scheduler/JdbcScheduleLoaderTest.java
+++ b/unit/java/azkaban/scheduler/JdbcScheduleLoaderTest.java
@@ -22,7 +22,7 @@ import azkaban.sla.SLA.SlaAction;
import azkaban.sla.SLA.SlaRule;
import azkaban.sla.SLA.SlaSetting;
import azkaban.sla.SlaOptions;
-import azkaban.utils.db.DataSourceUtils;
+import azkaban.database.DataSourceUtils;
import azkaban.utils.Props;
public class JdbcScheduleLoaderTest {
diff --git a/unit/java/azkaban/test/database/AzkabanDatabaseSetupTest.java b/unit/java/azkaban/test/database/AzkabanDatabaseSetupTest.java
new file mode 100644
index 0000000..75ca74f
--- /dev/null
+++ b/unit/java/azkaban/test/database/AzkabanDatabaseSetupTest.java
@@ -0,0 +1,97 @@
+package azkaban.test.database;
+
+import java.io.File;
+import java.io.IOException;
+import java.sql.SQLException;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.dbutils.QueryRunner;
+import org.apache.commons.io.FileUtils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import azkaban.database.AzkabanDatabaseSetup;
+import azkaban.database.DataSourceUtils;
+import azkaban.utils.Props;
+
+public class AzkabanDatabaseSetupTest {
+ @BeforeClass
+ public static void setupDB() throws IOException, SQLException {
+ File dbDir = new File("h2dbtest");
+ if (dbDir.exists()) {
+ FileUtils.deleteDirectory(dbDir);
+ }
+
+ dbDir.mkdir();
+
+ clearUnitTestDB();
+ }
+
+ @AfterClass
+ public static void teardownDB() {
+ }
+
+ @Test
+ public void testH2Query() throws Exception {
+ Props h2Props = getH2Props();
+ AzkabanDatabaseSetup setup = new AzkabanDatabaseSetup(h2Props);
+ setup.checkTableVersion(true, true);
+
+ setup.checkTableVersion(true, true);
+ }
+
+ @Test
+ public void testMySQLQuery() throws Exception {
+ Props mysqlProps = getMySQLProps();
+ AzkabanDatabaseSetup setup = new AzkabanDatabaseSetup(mysqlProps);
+ setup.checkTableVersion(true, true);
+
+ setup.checkTableVersion(true, true);
+ }
+
+ private static Props getH2Props() {
+ Props props = new Props();
+ props.put("database.type", "h2");
+ props.put("h2.path", "h2dbtest/h2db");
+ props.put("sql.script.path", "unit/sql");
+
+ return props;
+ }
+
+ private static Props getMySQLProps() {
+ Props props = new Props();
+
+ props.put("database.type", "mysql");
+ props.put("mysql.port", "3306");
+ props.put("mysql.host", "localhost");
+ props.put("mysql.database", "azkabanunittest");
+ props.put("mysql.user", "root");
+ props.put("sql.script.path", "unit/sql");
+ props.put("mysql.password", "");
+ props.put("mysql.numconnections", 10);
+
+ return props;
+ }
+
+ private static void clearUnitTestDB() throws SQLException {
+ Props props = new Props();
+
+ props.put("database.type", "mysql");
+ props.put("mysql.host", "localhost");
+ props.put("mysql.port", "3306");
+ props.put("mysql.database", "");
+ props.put("mysql.user", "root");
+ props.put("mysql.password", "");
+ props.put("mysql.numconnections", 10);
+
+ DataSource datasource = DataSourceUtils.getDataSource(props);
+ QueryRunner runner = new QueryRunner(datasource);
+ try {
+ runner.update("drop database azkabanunittest");
+ } catch (SQLException e) {
+ }
+ runner.update("create database azkabanunittest");
+ }
+}
\ No newline at end of file
diff --git a/unit/java/azkaban/test/executor/JdbcExecutorLoaderTest.java b/unit/java/azkaban/test/executor/JdbcExecutorLoaderTest.java
index 3603608..ba89a20 100644
--- a/unit/java/azkaban/test/executor/JdbcExecutorLoaderTest.java
+++ b/unit/java/azkaban/test/executor/JdbcExecutorLoaderTest.java
@@ -31,7 +31,7 @@ import azkaban.executor.JdbcExecutorLoader;
import azkaban.executor.Status;
import azkaban.flow.Flow;
-import azkaban.utils.db.DataSourceUtils;
+import azkaban.database.DataSourceUtils;
import azkaban.utils.FileIOUtils.LogData;
import azkaban.utils.JSONUtils;
import azkaban.utils.Pair;
diff --git a/unit/java/azkaban/test/project/JdbcProjectLoaderTest.java b/unit/java/azkaban/test/project/JdbcProjectLoaderTest.java
index 008a812..85f874c 100644
--- a/unit/java/azkaban/test/project/JdbcProjectLoaderTest.java
+++ b/unit/java/azkaban/test/project/JdbcProjectLoaderTest.java
@@ -30,7 +30,7 @@ import azkaban.project.ProjectLogEvent.EventType;
import azkaban.project.ProjectManagerException;
import azkaban.user.Permission;
import azkaban.user.User;
-import azkaban.utils.db.DataSourceUtils;
+import azkaban.database.DataSourceUtils;
import azkaban.utils.Pair;
import azkaban.utils.Props;
import azkaban.utils.PropsUtils;
diff --git a/unit/java/azkaban/test/sla/JdbcSLALoaderTest.java b/unit/java/azkaban/test/sla/JdbcSLALoaderTest.java
index 23473f7..373bfac 100644
--- a/unit/java/azkaban/test/sla/JdbcSLALoaderTest.java
+++ b/unit/java/azkaban/test/sla/JdbcSLALoaderTest.java
@@ -22,11 +22,9 @@ import azkaban.sla.SLA;
import azkaban.sla.SLA.SlaAction;
import azkaban.sla.SLA.SlaRule;
import azkaban.sla.SLALoader;
-import azkaban.utils.db.DataSourceUtils;
+import azkaban.database.DataSourceUtils;
import azkaban.utils.Props;
-
-
public class JdbcSLALoaderTest {
private static boolean testDBExists;
//@TODO remove this and turn into local host.
unit/sql/create.properties.sql 7(+7 -0)
diff --git a/unit/sql/create.properties.sql b/unit/sql/create.properties.sql
new file mode 100644
index 0000000..aaa37ec
--- /dev/null
+++ b/unit/sql/create.properties.sql
@@ -0,0 +1,7 @@
+CREATE TABLE properties (
+ name VARCHAR(64) NOT NULL,
+ type INT NOT NULL,
+ modified_time BIGINT NOT NULL,
+ value VARCHAR(256),
+ PRIMARY KEY (name, type)
+);
\ No newline at end of file
unit/sql/database.properties 1(+1 -0)
diff --git a/unit/sql/database.properties b/unit/sql/database.properties
new file mode 100644
index 0000000..7b323f0
--- /dev/null
+++ b/unit/sql/database.properties
@@ -0,0 +1 @@
+version=2.2
diff --git a/unit/sql/update.execution_jobs.2.1.sql b/unit/sql/update.execution_jobs.2.1.sql
new file mode 100644
index 0000000..b7313ad
--- /dev/null
+++ b/unit/sql/update.execution_jobs.2.1.sql
@@ -0,0 +1,4 @@
+ALTER TABLE execution_jobs ADD COLUMN attempt INT DEFAULT 0;
+ALTER TABLE execution_jobs DROP PRIMARY KEY;
+ALTER TABLE execution_jobs ADD PRIMARY KEY(exec_id, job_id, attempt);
+ALTER TABLE execution_jobs ADD INDEX exec_job (exec_id, job_id);
diff --git a/unit/sql/update.execution_jobs.2.3.sql b/unit/sql/update.execution_jobs.2.3.sql
new file mode 100644
index 0000000..8c43495
--- /dev/null
+++ b/unit/sql/update.execution_jobs.2.3.sql
@@ -0,0 +1 @@
+INSERT INTO properties (name,value,modified_time,type) VALUES ('test4', 'value1', 0, 99);
\ No newline at end of file
diff --git a/unit/sql/update.execution_logs.2.1.sql b/unit/sql/update.execution_logs.2.1.sql
new file mode 100644
index 0000000..5c2dc0b
--- /dev/null
+++ b/unit/sql/update.execution_logs.2.1.sql
@@ -0,0 +1,7 @@
+ALTER TABLE execution_logs ADD COLUMN attempt INT DEFAULT 0;
+ALTER TABLE execution_logs ADD COLUMN upload_time BIGINT DEFAULT 1420099200000;
+UPDATE execution_logs SET upload_time=(UNIX_TIMESTAMP()*1000) WHERE upload_time=1420099200000;
+
+ALTER TABLE execution_logs DROP PRIMARY KEY;
+ALTER TABLE execution_logs ADD PRIMARY KEY(exec_id, name, attempt, start_byte);
+ALTER TABLE execution_logs ADD INDEX ex_log_attempt (exec_id, name, attempt)
diff --git a/unit/sql/update.execution_logs.2.4.sql b/unit/sql/update.execution_logs.2.4.sql
new file mode 100644
index 0000000..f0a7aae
--- /dev/null
+++ b/unit/sql/update.execution_logs.2.4.sql
@@ -0,0 +1 @@
+INSERT INTO properties (name,value,modified_time,type) VALUES ('test', 'value1', 0, 99);
\ No newline at end of file
diff --git a/unit/sql/update.execution_logs.2.7.sql b/unit/sql/update.execution_logs.2.7.sql
new file mode 100644
index 0000000..9974cc7
--- /dev/null
+++ b/unit/sql/update.execution_logs.2.7.sql
@@ -0,0 +1 @@
+INSERT INTO properties (name,value,modified_time,type) VALUES ('test1','value1', 0, 99);
\ No newline at end of file
diff --git a/unit/sql/update.project_events.2.1.sql b/unit/sql/update.project_events.2.1.sql
new file mode 100644
index 0000000..14d7554
--- /dev/null
+++ b/unit/sql/update.project_events.2.1.sql
@@ -0,0 +1 @@
+ALTER TABLE project_events MODIFY COLUMN message VARCHAR(512);
unit/sql/update.projects.2.1.sql 2(+2 -0)
diff --git a/unit/sql/update.projects.2.1.sql b/unit/sql/update.projects.2.1.sql
new file mode 100644
index 0000000..a2a6e48
--- /dev/null
+++ b/unit/sql/update.projects.2.1.sql
@@ -0,0 +1,2 @@
+ALTER TABLE projects ADD COLUMN enc_type TINYINT;
+ALTER TABLE projects ADD COLUMN settings_blob LONGBLOB;
unit/sql/update.schedules.2.1.sql 2(+2 -0)
diff --git a/unit/sql/update.schedules.2.1.sql b/unit/sql/update.schedules.2.1.sql
new file mode 100644
index 0000000..fbf38ce
--- /dev/null
+++ b/unit/sql/update.schedules.2.1.sql
@@ -0,0 +1,2 @@
+ALTER TABLE schedules ADD COLUMN enc_type TINYINT;
+ALTER TABLE schedules ADD COLUMN schedule_options LONGBLOB;