diff options
author | Simon Rettberg | 2015-08-22 23:04:10 +0200 |
---|---|---|
committer | Simon Rettberg | 2015-08-22 23:04:10 +0200 |
commit | cb84995967e523443b2590f169af0694e3107c90 (patch) | |
tree | 5ceb1b7d38b070a4ac35663a01a95b9d15ca2bab /dozentenmodulserver | |
parent | [server] Foundations for the maintenance module (diff) | |
download | tutor-module-cb84995967e523443b2590f169af0694e3107c90.tar.gz tutor-module-cb84995967e523443b2590f169af0694e3107c90.tar.xz tutor-module-cb84995967e523443b2590f169af0694e3107c90.zip |
[server] Work on version deletion/change/validity logic
Diffstat (limited to 'dozentenmodulserver')
6 files changed, 150 insertions, 29 deletions
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/App.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/App.java index 2f913ba0..54a25de3 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/App.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/App.java @@ -15,6 +15,7 @@ import org.apache.thrift.transport.TTransportException; import org.openslx.bwlp.sat.database.Database; import org.openslx.bwlp.sat.database.mappers.DbImage; import org.openslx.bwlp.sat.fileserv.FileServer; +import org.openslx.bwlp.sat.maintenance.DeleteOldImages; import org.openslx.bwlp.sat.thrift.BinaryListener; import org.openslx.bwlp.sat.thrift.cache.OperatingSystemList; import org.openslx.bwlp.sat.thrift.cache.OrganizationList; @@ -92,6 +93,10 @@ public class App { LOGGER.error("Could not start internal file server."); return; } + + // Set up maintenance tasks + DeleteOldImages.init(); + // Start Thrift Server Thread t; // Plain diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/RuntimeConfig.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/RuntimeConfig.java index e73251fb..20b73549 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/RuntimeConfig.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/RuntimeConfig.java @@ -10,11 +10,12 @@ public class RuntimeConfig { private static final SatelliteConfig satConfig; static { + // TODO: Periodically read from DB satConfig = new SatelliteConfig(); satConfig.setDefaultImagePermissions(new ImagePermissions(true, true, false, false)); satConfig.setDefaultLecturePermissions(new LecturePermissions(false, false)); satConfig.setMaxImageValidityDays(200); - satConfig.setMaxLectureValidityDays(100); + satConfig.setMaxLectureValidityDays(200); satConfig.setPageSize(Paginator.PER_PAGE); } @@ -29,5 +30,14 @@ public class RuntimeConfig { public static long getMaxLectureValiditySeconds() { return satConfig.getMaxLectureValidityDays() * 86400l; } + + /** + * How long a version that is not the latest version of an image will be kept. + * + * @return maximum lifetime in seconds + */ + public static long getOldVersionExpireSeconds() { + return 8 * 86400; + } } diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImage.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImage.java index 23a71228..0f925371 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImage.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImage.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.UUID; import org.apache.log4j.Logger; +import org.openslx.bwlp.sat.RuntimeConfig; import org.openslx.bwlp.sat.database.Database; import org.openslx.bwlp.sat.database.MysqlConnection; import org.openslx.bwlp.sat.database.MysqlStatement; @@ -15,6 +16,7 @@ import org.openslx.bwlp.sat.database.Paginator; import org.openslx.bwlp.sat.database.models.LocalImageVersion; import org.openslx.bwlp.sat.fileserv.FileServer; import org.openslx.bwlp.sat.permissions.User; +import org.openslx.bwlp.sat.util.Util; import org.openslx.bwlp.thrift.iface.ImageBaseWrite; import org.openslx.bwlp.thrift.iface.ImageDetailsRead; import org.openslx.bwlp.thrift.iface.ImagePermissions; @@ -402,14 +404,15 @@ public class DbImage { + " v.isrestricted = :isrestricted" + " WHERE v.imageversionid = :versionid"); stmtVersion.setString("versionid", imageVersionId); stmtVersion.setBoolean("isrestricted", image.isRestricted); - stmtVersion.executeUpdate(); - // Then base table - MysqlStatement stmtBase = connection.prepareStatement("UPDATE imagebase b SET" - + " b.updaterid = :userid, b.updatetime = UNIX_TIMESTAMP()" - + " WHERE b.imagebaseid = :baseid"); - stmtBase.setString("userid", user.userId); - stmtBase.setString("baseid", baseId); - stmtBase.executeUpdate(); + if (stmtVersion.executeUpdate() != 0) { + // Then base table + MysqlStatement stmtBase = connection.prepareStatement("UPDATE imagebase b SET" + + " b.updaterid = :userid, b.updatetime = UNIX_TIMESTAMP()" + + " WHERE b.imagebaseid = :baseid"); + stmtBase.setString("userid", user.userId); + stmtBase.setString("baseid", baseId); + stmtBase.executeUpdate(); + } connection.commit(); } catch (SQLException e) { LOGGER.error("Query failed in DbImage.updateImageVersion()", e); @@ -431,7 +434,7 @@ public class DbImage { try (MysqlConnection connection = Database.getConnection()) { // Disable version in question MysqlStatement disableStmt = connection.prepareStatement("UPDATE imageversion SET" - + " expiretime = UNIX_TIMESTAMP() - 86400 * 5, isvalid = 0" + + " expiretime = UNIX_TIMESTAMP() - 86400, isvalid = 0" + " WHERE imageversionid = :versionid"); disableStmt.setString("versionid", imageVersionId); disableStmt.executeUpdate(); @@ -461,7 +464,8 @@ public class DbImage { long fileSize, String filePath, ImageVersionWrite versionSettings, ChunkList chunks, byte[] machineDescription) throws SQLException { try (MysqlConnection connection = Database.getConnection()) { - final long nowSecs = System.currentTimeMillis() / 1000; + final long nowSecs = Util.unixTime(); + final long expireTime = nowSecs + RuntimeConfig.getMaxImageValiditySeconds(); MysqlStatement verStmt = connection.prepareStatement("INSERT INTO imageversion" + " (imageversionid, imagebaseid, createtime, expiretime, filesize, filepath, uploaderid," + " isrestricted, isvalid, isprocessed, mastersha1, virtualizerconfig)" @@ -471,7 +475,7 @@ public class DbImage { verStmt.setString("imageversionid", imageVersionId); verStmt.setString("imagebaseid", imageBaseId); verStmt.setLong("createtime", nowSecs); - verStmt.setLong("expiretime", nowSecs + 86400 * 365); // TODO: Config! + verStmt.setLong("expiretime", expireTime); verStmt.setLong("filesize", fileSize); verStmt.setString("filepath", filePath); verStmt.setString("uploaderid", owner.userId); @@ -488,7 +492,9 @@ public class DbImage { baseStmt.setString("imageversionid", imageVersionId); baseStmt.setString("imagebaseid", imageBaseId); baseStmt.executeUpdate(); - DbLecture.autoUpdateUsedImage(connection, imageBaseId, imageVersionId); + LocalImageVersion liv = new LocalImageVersion(imageVersionId, imageBaseId, filePath, fileSize, nowSecs, expireTime, true); + DbLecture.autoUpdateUsedImage(connection, imageBaseId, liv); + setLatestVersion(connection, imageBaseId, liv); connection.commit(); } catch (SQLException e) { LOGGER.error("Query failed in DbImage.createImageVersion()", e); @@ -496,7 +502,7 @@ public class DbImage { } } - public static void markValid(MysqlConnection connection, boolean valid, LocalImageVersion... imageVersion) + protected static void markValid(MysqlConnection connection, boolean valid, LocalImageVersion... imageVersion) throws SQLException { if (imageVersion == null || imageVersion.length == 0) return; @@ -527,6 +533,19 @@ public class DbImage { } } + public static void deletePermanently(LocalImageVersion image) throws SQLException { + try (MysqlConnection connection = Database.getConnection()) { + MysqlStatement stmt = connection.prepareStatement("DELETE FROM imageversion" + + " WHERE imageversionid = :imageversionid"); + stmt.setString("imageversionid", image.imageVersionId); + stmt.executeUpdate(); + connection.commit(); + } catch (SQLException e) { + LOGGER.error("Query failed in DbImage.deletePermanently()", e); + throw e; + } + } + private static void updateLatestVersionAsync(final LocalImageVersion... changingVersion) { if (changingVersion == null || changingVersion.length == 0) return; @@ -548,7 +567,7 @@ public class DbImage { @Override public void fire() { try (MysqlConnection connection = Database.getConnection()) { - updateLatestVersion(connection, changingImageVersionId, null); + versionValidityChanged(connection, changingImageVersionId, null); connection.commit(); } catch (SQLException | TNotFoundException e) { LOGGER.error("Query failed in DbImage.updateLatestVersionAsync()", e); @@ -564,7 +583,7 @@ public class DbImage { return; for (LocalImageVersion version : versions) { try { - updateLatestVersion(connection, version.imageVersionId, + versionValidityChanged(connection, version.imageVersionId, version.imageBaseId); } catch (TNotFoundException e) { // Swallow - logging happens in called method @@ -584,7 +603,7 @@ public class DbImage { * @throws TNotFoundException * @throws SQLException */ - private static void updateLatestVersion(MysqlConnection connection, String changingImageVersionId, + private static void versionValidityChanged(MysqlConnection connection, String changingImageVersionId, String imageBaseId) throws TNotFoundException, SQLException { if (imageBaseId == null) { imageBaseId = DbImage.getBaseIdForVersionId(connection, changingImageVersionId); @@ -613,16 +632,57 @@ public class DbImage { } else { // Switch any lectures linking to this version if applicable if (oldVersion.isValid) { - DbLecture.autoUpdateUsedImage(connection, imageBaseId, newVersion.imageVersionId); + DbLecture.autoUpdateUsedImage(connection, imageBaseId, newVersion); } else { DbLecture.forcefullySwitchUsedImage(connection, oldVersion, newVersion); } } // Now update the latestversionid of the baseimage if applicable + if (setLatestVersion(connection, imageBaseId, newVersion)) { + // TODO: Latest version changed -> mail + //Something.versionDeleted(imageBaseId, oldVersion, newVersion); + } + } + + /** + * Set the latest version id of the given base image. Returns true if and + * only if the latest version id of the base image did actually change through + * this call <b>and</b> the new latest version is <b>not null</b>. + * + * @param connection mysql connection to use + * @param imageBaseId base id of image in question + * @param latest image version that is to become the latest version, or + * <code>null</code> if there is no valid version + * @return true if changed to a different, non-null image + * @throws SQLException + */ + private static boolean setLatestVersion(MysqlConnection connection, String imageBaseId, LocalImageVersion latest) throws SQLException { + // Update latestversionid reference in imagebase table MysqlStatement latestStmt = connection.prepareStatement("UPDATE imagebase SET latestversionid = :newversionid" + " WHERE imagebaseid = :imagebaseid"); - latestStmt.setString("newversionid", newVersion == null ? null : newVersion.imageVersionId); + latestStmt.setString("newversionid", latest == null ? null : latest.imageVersionId); latestStmt.setString("imagebaseid", imageBaseId); - latestStmt.executeUpdate(); + // If nothing changed, or the latest version was set to NULL, bail out + if (latestStmt.executeUpdate() == 0 || latest == null) + return false; + // Latest version changed - update expire dates of related versions + // Set short expire date for versions that are NOT the latest version but are still marked valid + long shortExpire = Util.unixTime() + RuntimeConfig.getOldVersionExpireSeconds(); + MysqlStatement oldStmt = connection.prepareStatement("UPDATE imageversion SET" + + " expiretime = If(expiretime < :shortexpire, expiretime, :shortexpire)" + + " WHERE imagebaseid = :imagebaseid AND imageversionid <> :imageversionid AND isvalid = 1"); + oldStmt.setString("imageversionid", latest.imageVersionId); + oldStmt.setString("imagebaseid", imageBaseId); + oldStmt.setLong("shortexpire", shortExpire); + oldStmt.executeUpdate(); + // Now set a long expire date for the latest version, as it might have been shortened before + MysqlStatement newStmt = connection.prepareStatement("UPDATE imageversion SET" + + " expiretime = If(createtime + :maxvalid > expiretime, createtime + :maxvalid, expiretime)" + + " WHERE imageversionid = :imageversionid"); + newStmt.setString("imageversionid", latest.imageVersionId); + newStmt.setLong("maxvalid", RuntimeConfig.getMaxImageValiditySeconds()); + newStmt.executeUpdate(); + return true; } + } diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecture.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecture.java index bfd1513a..2623a9b6 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecture.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecture.java @@ -315,19 +315,20 @@ public class DbLecture { * Called when a new version for an image is uploaded. Update all lectures * using the same base image which have the autoUpdate-flag set. * - * @param connection - * @param imageBaseId - * @param imageVersionId + * @param connection mysql connection to use + * @param imageBaseId base image that got a new version + * @param newVersion the latest (valid) version * @throws SQLException */ protected static void autoUpdateUsedImage(MysqlConnection connection, String imageBaseId, - String imageVersionId) throws SQLException { + LocalImageVersion newVersion) throws SQLException { // TODO: select first so we can email + // TODO: If new version is null, do not change, but send warning mail MysqlStatement stmt = connection.prepareStatement("UPDATE lecture l, imageversion v SET" + " l.imageversionid = :imageversionid" + " WHERE v.imageversionid = l.imageversionid AND v.imagebaseid = :imagebaseid" + " AND l.autoupdate = 1"); - stmt.setString("imageversionid", imageVersionId); + stmt.setString("imageversionid", newVersion.imageVersionId); stmt.setString("imagebaseid", imageBaseId); stmt.executeUpdate(); } diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/DeleteOldImages.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/DeleteOldImages.java index 90e7e80d..9a308dcd 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/DeleteOldImages.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/DeleteOldImages.java @@ -1,14 +1,16 @@ package org.openslx.bwlp.sat.maintenance; import java.sql.SQLException; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; +import org.joda.time.DateTime; import org.openslx.bwlp.sat.database.mappers.DbImage; import org.openslx.bwlp.sat.database.models.LocalImageVersion; import org.openslx.bwlp.sat.fileserv.FileServer; +import org.openslx.util.QuickTimer; +import org.openslx.util.QuickTimer.Task; /** * Delete old image versions (images that reached their expire time). @@ -16,9 +18,41 @@ import org.openslx.bwlp.sat.fileserv.FileServer; public class DeleteOldImages implements Runnable { private static final Logger LOGGER = Logger.getLogger(DeleteOldImages.class); + + private static final DeleteOldImages instance = new DeleteOldImages(); + + private static long blockedUntil = 0; + + /** + * Initialize the delete task. This schedules a timer that runs + * every 5 minutes. If the hour of day reaches 3, it will fire + * the task, and block it from running for the next 12 hours. + */ + public synchronized static void init() { + if (blockedUntil != 0) + return; + blockedUntil = 1; + QuickTimer.scheduleAtFixedRate(new Task() { + @Override + public void fire() { + if (blockedUntil > System.currentTimeMillis()) + return; + DateTime now = DateTime.now(); + if (now.getHourOfDay() != 3 || now.getMinuteOfHour() > 15) + return; + if (Maintenance.trySubmit(instance)) { + blockedUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(12); + } + } + }, TimeUnit.MINUTES.toMillis(5), TimeUnit.MINUTES.toMillis(5)); + } + + private DeleteOldImages() { + } @Override public void run() { + LOGGER.info("Looking for old image versions to delete"); List<LocalImageVersion> versions; try { versions = DbImage.getExpiringLocalImageVersions(0); @@ -31,16 +65,23 @@ public class DeleteOldImages implements Runnable { DbImage.markValid(false, false, versions.toArray(new LocalImageVersion[versions.size()])); } catch (SQLException e) { LOGGER.error("Could not mark images to be deleted as invalid. Cleanup of old images failed."); + return; } // Delete them permanently only if they expired (at least) one day ago - Set<String> deleteVersionIds = new HashSet<>(); + int hardDeleteCount = 0; final long hardDelete = (System.currentTimeMillis() / 1000) - 86400; for (LocalImageVersion version : versions) { if (version.expireTime < hardDelete) { - deleteVersionIds.add(version.imageVersionId); + hardDeleteCount++; FileServer.composeAbsolutePath(version).delete(); + try { + DbImage.deletePermanently(version); + } catch (SQLException e) { + // Logging done in method + } } } + LOGGER.info("Deletion done. Soft: " + versions.size() + ", hard: " + hardDeleteCount); } } diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Util.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Util.java index 7e3c825a..e2135e64 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Util.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Util.java @@ -59,5 +59,9 @@ public class Util { public static boolean isEmptyString(String string) { return !nonSpaceMatcher.matcher(string).find(); } + + public static long unixTime() { + return System.currentTimeMillis() / 1000; + } } |