diff options
author | Simon Rettberg | 2018-05-11 17:36:45 +0200 |
---|---|---|
committer | Simon Rettberg | 2018-05-11 17:36:45 +0200 |
commit | b3557ec2e121f91ca842ca37f0e4b582556046a4 (patch) | |
tree | 21f6feb3f678db1da60b6f72fea6ef1cbb72fee0 /dozentenmodulserver/src | |
parent | [client] Fix inverted logic for slowed down hashing (diff) | |
download | tutor-module-b3557ec2e121f91ca842ca37f0e4b582556046a4.tar.gz tutor-module-b3557ec2e121f91ca842ca37f0e4b582556046a4.tar.xz tutor-module-b3557ec2e121f91ca842ca37f0e4b582556046a4.zip |
[server] Support server side chunk copying
Diffstat (limited to 'dozentenmodulserver/src')
9 files changed, 314 insertions, 60 deletions
diff --git a/dozentenmodulserver/src/main/java/fi/iki/elonen/NanoHTTPD.java b/dozentenmodulserver/src/main/java/fi/iki/elonen/NanoHTTPD.java index b50bdb72..ecb6039b 100644 --- a/dozentenmodulserver/src/main/java/fi/iki/elonen/NanoHTTPD.java +++ b/dozentenmodulserver/src/main/java/fi/iki/elonen/NanoHTTPD.java @@ -63,12 +63,14 @@ import java.util.StringTokenizer; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.log4j.Logger; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; +import org.openslx.bwlp.sat.util.PrioThreadFactory; import org.openslx.util.GrowingThreadPoolExecutor; /** @@ -458,7 +460,7 @@ public abstract class NanoHTTPD implements Runnable { */ public static class DefaultAsyncRunner implements AsyncRunner { private ExecutorService pool = new GrowingThreadPoolExecutor(2, 16, 1, TimeUnit.MINUTES, - new ArrayBlockingQueue<Runnable>(4)); + new ArrayBlockingQueue<Runnable>(4), new PrioThreadFactory("httpd", Thread.NORM_PRIORITY)); @Override public void exec(Runnable code) { diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/SupportedFeatures.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/SupportedFeatures.java index b2ff68e7..2587661b 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/SupportedFeatures.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/SupportedFeatures.java @@ -10,6 +10,7 @@ public class SupportedFeatures { registerFeature(Feature.EXTEND_EXPIRED_VM); registerFeature(Feature.NETWORK_SHARES); registerFeature(Feature.MULTIPLE_HYPERVISORS); + registerFeature(Feature.SERVER_SIDE_COPY); } public static String getFeatureString() { diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImageBlock.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImageBlock.java index 4e68da6d..56685c12 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImageBlock.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImageBlock.java @@ -11,7 +11,9 @@ import org.apache.log4j.Logger; import org.openslx.bwlp.sat.database.Database; import org.openslx.bwlp.sat.database.MysqlConnection; import org.openslx.bwlp.sat.database.MysqlStatement; +import org.openslx.bwlp.sat.util.FileSystem; import org.openslx.filetransfer.FileRange; +import org.openslx.filetransfer.LocalChunkSource.ChunkSource; import org.openslx.filetransfer.util.ChunkStatus; import org.openslx.filetransfer.util.FileChunk; @@ -36,6 +38,10 @@ public class DbImageBlock { private static class AsyncThread extends Thread { private final ArrayBlockingQueue<ChunkUpdate> queue = new ArrayBlockingQueue<>(100); + + public AsyncThread() { + super("DbBlockUpdater"); + } public void put(ChunkUpdate chunk) throws InterruptedException { queue.put(chunk); @@ -174,4 +180,32 @@ public class DbImageBlock { } } + public static List<ChunkSource> getBlocksWithHash(List<byte[]> sums) throws SQLException { + List<ChunkSource> list = null; + try (MysqlConnection connection = Database.getConnection()) { + MysqlStatement stmt = connection.prepareStatement("SELECT startbyte, blocksize, filepath FROM imageblock" + + " INNER JOIN imageversion USING (imageversionid)" + + " WHERE blocksha1 = :sha1 GROUP BY imageversionid"); + for (byte[] sha1 : sums) { + stmt.setBinary("sha1", sha1); + ResultSet rs = stmt.executeQuery(); + if (!rs.next()) + continue; + ChunkSource cs = new ChunkSource(sha1); + do { + cs.addFile(FileSystem.composeAbsolutePath(rs.getString("filepath")).getAbsolutePath(), + rs.getLong("startbyte"), rs.getInt("blocksize")); + } while (rs.next()); + if (list == null) { + list = new ArrayList<>(); + } + list.add(cs); + } + } catch (SQLException e) { + LOGGER.error("Query failed in DbImageBlock.getBlocksWithHash()", e); + throw e; + } + return list; + } + } diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/IncomingDataTransfer.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/IncomingDataTransfer.java index bb4765c1..15e9485b 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/IncomingDataTransfer.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/IncomingDataTransfer.java @@ -26,6 +26,7 @@ import org.openslx.bwlp.thrift.iface.ImageVersionWrite; import org.openslx.bwlp.thrift.iface.TNotFoundException; import org.openslx.bwlp.thrift.iface.TransferInformation; import org.openslx.bwlp.thrift.iface.TransferState; +import org.openslx.bwlp.thrift.iface.UploadOptions; import org.openslx.bwlp.thrift.iface.UserInfo; import org.openslx.filetransfer.Downloader; import org.openslx.filetransfer.util.ChunkStatus; @@ -82,7 +83,7 @@ public class IncomingDataTransfer extends IncomingTransferBase { public IncomingDataTransfer(String uploadId, UserInfo owner, ImageDetailsRead image, File destinationFile, long fileSize, List<byte[]> sha1Sums, byte[] machineDescription, boolean repairUpload) throws FileNotFoundException { - super(uploadId, destinationFile, fileSize, sha1Sums); + super(uploadId, destinationFile, fileSize, sha1Sums, StorageChunkSource.instance); this.owner = repairUpload ? null : owner; this.image = image; this.machineDescription = machineDescription; @@ -93,7 +94,7 @@ public class IncomingDataTransfer extends IncomingTransferBase { public IncomingDataTransfer(ImagePublishData publishData, File tmpFile, TransferInformation transferInfo, boolean repairUpload) throws FileNotFoundException { super(UUID.randomUUID().toString(), tmpFile, publishData.fileSize, - ThriftUtil.unwrapByteBufferList(transferInfo.blockHashes)); + ThriftUtil.unwrapByteBufferList(transferInfo.blockHashes), StorageChunkSource.instance); ImageDetailsRead idr = new ImageDetailsRead(); idr.setCreateTime(publishData.createTime); idr.setDescription(publishData.description); @@ -389,4 +390,16 @@ public class IncomingDataTransfer extends IncomingTransferBase { return errorMessage; } + /** + * Alter options of this upload. Returns new effective options. + */ + public UploadOptions setOptions(UploadOptions options) { + if (options != null) { + if (options.isSetServerSideCopying()) { + super.enableServerSideCopying(options.serverSideCopying); + } + } + return new UploadOptions(super.isServerSideCopyingEnabled()); + } + } diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/StorageChunkSource.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/StorageChunkSource.java new file mode 100644 index 00000000..01652fec --- /dev/null +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/StorageChunkSource.java @@ -0,0 +1,22 @@ +package org.openslx.bwlp.sat.fileserv; + +import java.sql.SQLException; +import java.util.List; + +import org.openslx.bwlp.sat.database.mappers.DbImageBlock; +import org.openslx.filetransfer.LocalChunkSource; + +public class StorageChunkSource implements LocalChunkSource { + + public static final StorageChunkSource instance = new StorageChunkSource(); + + @Override + public List<ChunkSource> getCloneSources(List<byte[]> sums) { + try { + return DbImageBlock.getBlocksWithHash(sums); + } catch (SQLException e) { + } + return null; + } + +} diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/ImageValidCheck.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/ImageValidCheck.java index 050f598d..b544a708 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/ImageValidCheck.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/ImageValidCheck.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; +import java.util.Map.Entry; import java.util.Queue; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -33,17 +34,34 @@ import org.openslx.util.Util; public class ImageValidCheck implements Runnable { - public enum CheckResult { + public static enum CheckResult { + + QUEUED(0), + NO_SUCH_JOB(0), + WAITING_FOR_STORE(1), + WORKING(2), + DONE(100), + VERSION_EXPIRED(100), + DATABASE_PATH_MISSING(100), + DATABASE_PATH_INVALID(100), + FILE_NOT_FOUND(100), + FILE_ACCESS_ERROR(100), + FILE_SIZE_MISMATCH(100), + FILE_CORRUPT(100), + UNKNOWN_VERSIONID(100), + INTERNAL_ERROR(100); + public final int stage; + private CheckResult(int stage) { + this.stage = stage; + } + } + + public static enum SubmitResult { + QUEUED, NULL_POINTER_EXCEPTION, ALREADY_IN_PROGRESS, TOO_MANY_QUEUED_JOBS, - SUBMITTED, REJECTED_BY_SCHEDULER, - WORKING, - DONE, - FILE_NOT_FOUND, - FILE_ACCESS_ERROR, - OTHER_ERROR, } private static final Logger LOGGER = Logger.getLogger(ImageValidCheck.class); @@ -57,9 +75,13 @@ public class ImageValidCheck implements Runnable { private final String versionId; private final boolean integrity; + private final boolean updateState; + + /** + * Status of this check job. Must never be null. + */ + private CheckResult result = CheckResult.QUEUED; - // TODO: Set appropriately in various places; make it possible to query from RPC - private CheckResult result = CheckResult.DONE; // Hash checking @@ -84,26 +106,31 @@ public class ImageValidCheck implements Runnable { // End hash checking - public static CheckResult check(String versionId, boolean integrity) { + public static SubmitResult check(String versionId, boolean integrity, boolean updateState) { if (versionId == null) - return CheckResult.NULL_POINTER_EXCEPTION; + return SubmitResult.NULL_POINTER_EXCEPTION; synchronized (inProgress) { + synchronized (done) { + if (done.containsKey(versionId)) { + done.remove(versionId); + } + } if (inProgress.containsKey(versionId)) - return CheckResult.ALREADY_IN_PROGRESS; + return SubmitResult.ALREADY_IN_PROGRESS; if (inProgress.size() >= MAX_CONCURRENT_CHECKS) { if (queue.size() > 1000) { - return CheckResult.TOO_MANY_QUEUED_JOBS; + return SubmitResult.TOO_MANY_QUEUED_JOBS; } - queue.add(new ImageValidCheck(versionId, integrity)); - return CheckResult.SUBMITTED; + queue.add(new ImageValidCheck(versionId, integrity, updateState)); + return SubmitResult.QUEUED; } - ImageValidCheck check = new ImageValidCheck(versionId, integrity); + ImageValidCheck check = new ImageValidCheck(versionId, integrity, updateState); if (Maintenance.trySubmit(check)) { inProgress.put(versionId, check); - return CheckResult.SUBMITTED; + return SubmitResult.QUEUED; } } - return CheckResult.REJECTED_BY_SCHEDULER; + return SubmitResult.REJECTED_BY_SCHEDULER; } public static void checkForWork() { @@ -127,48 +154,132 @@ public class ImageValidCheck implements Runnable { } } - private ImageValidCheck(String versionId, boolean integrity) { + /** + * Get current status of check for given versionId. + * Never returns null. + * + * @param versionId VERSIONSID DES IMAGES + * @return state/result + */ + public static CheckResult getStatus(String versionId) { + ImageValidCheck i; + synchronized (inProgress) { + i = inProgress.get(versionId); + } + if (i != null) + return i.result; + synchronized (done) { + i = done.get(versionId); + } + if (i != null) + return i.result; + return CheckResult.NO_SUCH_JOB; + } + + /** + * Get status/result of all known check jobs. + * + * @return MAP + */ + public static Map<String, CheckResult> getAll() { + Map<String, CheckResult> res = new HashMap<>(); + synchronized (inProgress) { + for (Entry<String, ImageValidCheck> i : inProgress.entrySet()) { + res.put(i.getKey(), i.getValue().result); + } + } + synchronized (done) { + for (Entry<String, ImageValidCheck> i : done.getImmutableSnapshot().entrySet()) { + res.put(i.getKey(), i.getValue().result); + } + } + return res; + } + + // + // Instance + // + + private ImageValidCheck(String versionId, boolean integrity, boolean updateState) { this.versionId = versionId; this.integrity = integrity; + this.updateState = updateState; + } + + private void setState(CheckResult cr) { + if (cr.stage > result.stage) { + result = cr; + } else { + LOGGER.debug("Ingoring state update from " + result.name() + " to " + cr.name()); + } } @Override public void run() { try { + setState(CheckResult.WAITING_FOR_STORE); if (!FileSystem.waitForStorage()) { LOGGER.warn("Will not check " + versionId + ": Storage not online"); + setState(CheckResult.INTERNAL_ERROR); return; } + setState(CheckResult.WORKING); LocalImageVersion imageVersion; try { imageVersion = DbImage.getLocalImageData(versionId); - } catch (SQLException e) { - return; } catch (TNotFoundException e) { LOGGER.warn("Cannot check validity of image version - not found: " + versionId); + setState(CheckResult.UNKNOWN_VERSIONID); + return; + } catch (Exception e) { + LOGGER.warn("Cannot get local image data", e); + setState(CheckResult.INTERNAL_ERROR); return; } + // Found image in DB + // Simple checks first boolean valid = checkValid(imageVersion); if (valid && integrity) { + // Check block hashes try { valid = checkBlockHashes(imageVersion); } catch (IOException e) { - result = CheckResult.FILE_ACCESS_ERROR; + LOGGER.warn("IO error for " + versionId, e); + setState(CheckResult.FILE_ACCESS_ERROR); valid = false; } catch (Exception e) { - result = CheckResult.OTHER_ERROR; + LOGGER.warn("Cannot check block hashes of " + versionId, e); + setState(CheckResult.INTERNAL_ERROR); } } - if (imageVersion.isValid == valid) - return; // nothing changed + if (imageVersion.isValid == valid) { + // nothing changed + if (valid) { + setState(CheckResult.DONE); + } + return; + } // Update try { - DbImage.markValid(valid, false, imageVersion); + if (updateState) { + DbImage.markValid(valid, false, imageVersion); + } + if (valid) { + setState(CheckResult.DONE); + } } catch (SQLException e) { + setState(CheckResult.INTERNAL_ERROR); } } finally { + if (result == CheckResult.WORKING) { + setState(CheckResult.INTERNAL_ERROR); + } + ImageValidCheck ivc; synchronized (inProgress) { - inProgress.remove(this.versionId); + synchronized (done) { + ivc = inProgress.remove(this.versionId); + done.put(this.versionId, ivc); + } } checkForWork(); } @@ -184,11 +295,12 @@ public class ImageValidCheck implements Runnable { versionDetails = DbImage.getVersionDetails(versionId); } catch (TNotFoundException e) { LOGGER.warn("Cannot check hash of image version - not found: " + versionId); + setState(CheckResult.UNKNOWN_VERSIONID); return false; } catch (SQLException e) { + setState(CheckResult.INTERNAL_ERROR); return false; } - // TODO if (versionDetails.sha1sums == null || versionDetails.sha1sums.isEmpty()) { LOGGER.info("Image does not have block hashes -- assuming ok"); return true; @@ -216,24 +328,27 @@ public class ImageValidCheck implements Runnable { hashChecker.queue(chunk, buffer, new HashCheckCallback() { @Override public void hashCheckDone(HashResult result, byte[] data, FileChunk chunk) { - if (result == HashResult.FAILURE) { - // Hashing failed, cannot tell whether OK or not :( - } else { - if (result == HashResult.INVALID) { - fileOk.set(false); - ((StandaloneFileChunk) chunk).overrideStatus(ChunkStatus.MISSING); + try { + if (result == HashResult.FAILURE) { + // Hashing failed, cannot tell whether OK or not :( } else { - // >:( - ((StandaloneFileChunk) chunk).overrideStatus(ChunkStatus.COMPLETE); - } - try { - // We don't know what the state was in DB before, so just fire updates - DbImageBlock.asyncUpdate(imageVersion.imageVersionId, chunk); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + if (result == HashResult.INVALID) { + fileOk.set(false); + ((StandaloneFileChunk) chunk).overrideStatus(ChunkStatus.MISSING); + } else { + // >:( + ((StandaloneFileChunk) chunk).overrideStatus(ChunkStatus.COMPLETE); + } + try { + // We don't know what the state was in DB before, so just fire updates + DbImageBlock.asyncUpdate(imageVersion.imageVersionId, chunk); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } } + } finally { + sem.release(); } - sem.release(); } }, HashChecker.BLOCKING | HashChecker.CALC_HASH); numChecked += 1; @@ -245,7 +360,11 @@ public class ImageValidCheck implements Runnable { } // Wait until the last callback fired sem.acquire(numChecked); - return fileOk.get(); + if (fileOk.get()) { + return true; + } + setState(CheckResult.FILE_CORRUPT); + return false; } /** @@ -256,28 +375,34 @@ public class ImageValidCheck implements Runnable { return false; if (imageVersion.expireTime < Util.unixTime()) { LOGGER.info(versionId + ": expired"); + setState(CheckResult.VERSION_EXPIRED); return false; } if (imageVersion.filePath == null || imageVersion.filePath.isEmpty()) { LOGGER.info(versionId + ": DB does not contain a path"); + setState(CheckResult.DATABASE_PATH_MISSING); return false; } File path = FileSystem.composeAbsoluteImagePath(imageVersion); if (path == null) { LOGGER.info(versionId + ": path from DB is not valid"); + setState(CheckResult.DATABASE_PATH_INVALID); return false; } if (!path.exists()) { LOGGER.info(versionId + ": File does not exist (" + path.getAbsolutePath() + ")"); + setState(CheckResult.FILE_NOT_FOUND); return false; } if (!path.canRead()) { LOGGER.info(versionId + ": File exists but not readable (" + path.getAbsolutePath() + ")"); + setState(CheckResult.FILE_ACCESS_ERROR); return false; } if (path.length() != imageVersion.fileSize) { LOGGER.info(versionId + ": File exists but has wrong size (expected: " + imageVersion.fileSize + ", found: " + path.length() + ")"); + setState(CheckResult.FILE_SIZE_MISMATCH); return false; } return true; diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/thrift/ServerHandler.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/thrift/ServerHandler.java index df127736..f97231f8 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/thrift/ServerHandler.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/thrift/ServerHandler.java @@ -64,10 +64,12 @@ import org.openslx.bwlp.thrift.iface.TNotFoundException; import org.openslx.bwlp.thrift.iface.TTransferRejectedException; import org.openslx.bwlp.thrift.iface.TransferInformation; import org.openslx.bwlp.thrift.iface.TransferStatus; +import org.openslx.bwlp.thrift.iface.UploadOptions; import org.openslx.bwlp.thrift.iface.UserInfo; import org.openslx.bwlp.thrift.iface.Virtualizer; import org.openslx.bwlp.thrift.iface.WhoamiInfo; import org.openslx.sat.thrift.version.Version; +import org.openslx.thrifthelper.Comparators; import org.openslx.thrifthelper.ThriftManager; import org.openslx.util.ThriftUtil; @@ -124,8 +126,9 @@ public class ServerHandler implements SatelliteServer.Iface { } @Override - public void updateBlockHashes(String uploadToken, List<ByteBuffer> blockHashes) + public void updateBlockHashes(String uploadToken, List<ByteBuffer> blockHashes, String userToken) throws TInvalidTokenException { + // TODO: Validate user token some time in the future IncomingDataTransfer upload = fileServer.getUploadByToken(uploadToken); if (upload == null) throw new TInvalidTokenException(); @@ -134,6 +137,20 @@ public class ServerHandler implements SatelliteServer.Iface { } @Override + public UploadOptions setUploadOptions(String userToken, String uploadToken, UploadOptions options) + throws TAuthorizationException, TInvalidTokenException, TException { + IncomingDataTransfer upload = fileServer.getUploadByToken(uploadToken); + if (upload == null) + throw new TInvalidTokenException(); + if (options == null) // Query only -- don't validate user + return upload.setOptions(null); + UserInfo user = SessionManager.getOrFail(userToken); + if (Comparators.user.compare(user, upload.getOwner()) != 0) + throw new TAuthorizationException(AuthorizationError.NO_PERMISSION, "This isn't your upload"); + return upload.setOptions(options); + } + + @Override public void cancelUpload(String uploadToken) { IncomingDataTransfer upload = fileServer.getUploadByToken(uploadToken); if (upload != null) { @@ -762,4 +779,5 @@ public class ServerHandler implements SatelliteServer.Iface { "Database failure when setting the virtualizer config for '" + imageVersionId + "'."); } } + } diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/FileSystem.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/FileSystem.java index fa72056a..95cbcfe5 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/FileSystem.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/FileSystem.java @@ -31,7 +31,7 @@ public class FileSystem { * Delete given file on the {@link QuickTimer} thread, preventing the * calling thread from freezing/hanging on I/O problems. * - * @param file the file to delete + * @param sourceCandidates the file to delete */ public static void deleteAsync(final File... files) { if (files == null || files.length == 0) @@ -126,14 +126,32 @@ public class FileSystem { * <code>null</code> if the path would not be valid */ public static File composeAbsoluteImagePath(LocalImageVersion localImageData) { - String rel = localImageData.filePath == null ? null - : FilenameUtils.normalize(localImageData.filePath); - if (rel == null || rel.startsWith("/")) { - LOGGER.warn("Invalid path for local image " + localImageData.imageVersionId + ": " - + localImageData.filePath); + if (localImageData == null) return null; + File path = composeAbsolutePath(localImageData.filePath); + if (path == null) { + LOGGER.warn("ImageVersionId is " + localImageData.imageVersionId); } - return new File(Configuration.getVmStoreBasePath(), rel); + return path; + } + + /** + * Given a local relative path for an image, return a {@link File} object + * that holds the full path to the image file in the file system. + * + * @param relativePath + * @return {@link File} representing the physical image file, or + * <code>null</code> if the path would not be valid + */ + public static File composeAbsolutePath(String relativePath) { + if (relativePath != null) { + relativePath = FilenameUtils.normalize(relativePath); + } + if (relativePath == null || relativePath.startsWith("/")) { + LOGGER.warn("Invalid path for local image: " + relativePath); + return null; + } + return new File(Configuration.getVmStoreBasePath(), relativePath); } private static long lastStorageFailLog = 0; diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebRpc.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebRpc.java index 8018e86a..0e47994a 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebRpc.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebRpc.java @@ -22,6 +22,8 @@ import org.openslx.bwlp.sat.mail.SmtpMailer.EncryptionMode; import org.openslx.bwlp.sat.maintenance.DeleteOldImages; import org.openslx.bwlp.sat.maintenance.ImageValidCheck; import org.openslx.bwlp.sat.maintenance.ImageValidCheck.CheckResult; +import org.openslx.bwlp.sat.maintenance.ImageValidCheck.SubmitResult; +import org.openslx.util.Json; import org.openslx.util.Util; import fi.iki.elonen.NanoHTTPD; @@ -36,9 +38,12 @@ public class WebRpc { if (uri.equals("delete-images")) { return deleteImages(); } - if (uri.equals("check-image")) { + if (uri.equals("start-image-check")) { return checkImage(params); } + if (uri.equals("query-image-check")) { + return queryImageCheck(params); + } if (uri.equals("reset-mail-templates")) { return resetMailTemplates(); } @@ -52,12 +57,28 @@ public class WebRpc { private static Response checkImage(Map<String, String> params) { String versionId = params.get("versionid"); - boolean checkHashes = Boolean.valueOf(params.get("hash")); if (versionId == null) return WebServer.badRequest("Missing versionid param"); - CheckResult res = ImageValidCheck.check(versionId, checkHashes); - return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/plain; charset=utf-8", - res.name()); + versionId = versionId.toLowerCase(); + boolean checkHashes = Boolean.valueOf(params.get("hash")); + boolean updateState = Boolean.valueOf(params.get("update")); + SubmitResult res = ImageValidCheck.check(versionId, checkHashes, updateState); + return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/plain; charset=utf-8", res.name()); + } + + private static Response queryImageCheck(Map<String, String> params) { + String versionId = params.get("versionid"); + Map<String, CheckResult> result; + if (versionId == null) { + result = ImageValidCheck.getAll(); + } else { + versionId = versionId.toLowerCase(); + CheckResult res = ImageValidCheck.getStatus(versionId); + result = new HashMap<>(); + result.put(versionId, res); + } + return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "application/json; charset=utf-8", + Json.serialize(result)); } /** |