From 50866f7ef328bac854bd076ca48a3aa08aa3c29c Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 8 May 2024 18:38:31 +0200 Subject: [server] Add support for CoW sessions --- .../bwlp/sat/database/mappers/DbLecture.java | 51 ++++- .../org/openslx/bwlp/sat/fileserv/FileServer.java | 18 +- .../bwlp/sat/fileserv/IncomingDataTransfer.java | 17 +- .../bwlp/sat/maintenance/ImageValidCheck.java | 2 +- .../java/org/openslx/bwlp/sat/util/Formatter.java | 6 +- .../openslx/bwlp/sat/web/VmChooserEntryXml.java | 8 +- .../org/openslx/bwlp/sat/web/VmChooserListXml.java | 8 + .../java/org/openslx/bwlp/sat/web/WebServer.java | 223 +++++++++++++++++++-- 8 files changed, 293 insertions(+), 40 deletions(-) (limited to 'dozentenmodulserver') 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 ee24f8af..abe30a98 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 @@ -485,7 +485,7 @@ public class DbLecture { } } - public static VmChooserListXml getUsableListXml(boolean exams, String locationsString) + public static VmChooserListXml getUsableListXml(boolean exams, String locationsString, UserInfo user) throws SQLException { // Sanitize and clean locations string // Input is in the form of "1 2 3 4" or "1" or " 1 4 5" @@ -512,27 +512,40 @@ public class DbLecture { cleanLocations = "0"; } } + // Handle user + String userFields = ""; + String userJoin = ""; + if (user != null) { + userFields = " b.candownloaddefault, b.caneditdefault, b.canadmindefault," + + " ip.candownload, ip.canedit, ip.canadmin,"; + userJoin = " LEFT JOIN imagepermission ip ON (b.imagebaseid = ip.imagebaseid AND ip.userid = :userid)"; + } // Query try (MysqlConnection connection = Database.getConnection()) { MysqlStatement stmt = connection.prepareStatement("SELECT" + " l.lectureid, l.displayname AS lecturename, l.description," + " l.islocationprivate, loc.lectureid AS loctest," + " l.endtime, l.usecount, o.displayname AS osname, v.virtname, b.istemplate," + + userFields + " v.virtid, ov.virtoskeyword, i.filepath" + " FROM lecture l " + " INNER JOIN imageversion i USING (imageversionid)" + " INNER JOIN imagebase b USING (imagebaseid)" + " INNER JOIN operatingsystem o USING (osid)" + " INNER JOIN virtualizer v USING (virtid)" + + userJoin + " LEFT JOIN os_x_virt ov USING (osid, virtid)" + " LEFT JOIN (" + " SELECT DISTINCT lectureid FROM lecture_x_location WHERE locationid IN (" + cleanLocations - + ")" + + " )" + " ) loc USING (lectureid)" + " WHERE l.isenabled = 1 AND l.isprivate = 0 AND l.isexam = :isexam" + " AND l.starttime < UNIX_TIMESTAMP() AND l.endtime > UNIX_TIMESTAMP() AND i.isvalid = 1"); stmt.setBoolean("isexam", exams); + if (user != null) { + stmt.setString("userid", user.userId); + } ResultSet rs = stmt.executeQuery(); VmChooserListXml list = new VmChooserListXml(true); while (rs.next()) { @@ -542,12 +555,33 @@ public class DbLecture { String lectureId = rs.getString("lectureid"); boolean isTemplate = rs.getBoolean("istemplate"); int prio = 100; + // Check permissions + int allowEdit = 0; + if (user != null) { + boolean admin; + boolean download; + boolean edit; + if (rs.getString("canadmin") != null) { + admin = rs.getBoolean("canadmin"); + edit = rs.getBoolean("canedit"); + download = rs.getBoolean("candownload"); + } else { + admin = rs.getBoolean("canadmindefault"); + edit = rs.getBoolean("caneditdefault"); + download = rs.getBoolean("candownloaddefault"); + } + if (admin) { + allowEdit = 3; + } else { + allowEdit = (download ? 2 : 0) | (edit ? 1 : 0); + } + } // Get ldap filters List ldapFilters = DbLectureFilter.getFiltersXml(connection, lectureId); list.add(new VmChooserEntryXml(rs.getString("filepath"), prio, "-", rs.getString("lecturename"), rs.getString("description"), lectureId, rs.getString("virtid"), rs.getString("virtname"), rs.getString("virtoskeyword"), - rs.getString("osname"), "", isForThisLocation, isTemplate, ldapFilters)); + rs.getString("osname"), "", isForThisLocation, isTemplate, ldapFilters, allowEdit)); } return list; } catch (SQLException e) { @@ -565,7 +599,8 @@ public class DbLecture { // Get required data about lecture and used image MysqlStatement stmt = connection.prepareStatement("SELECT" + " l.displayname AS lecturename, l.starttime, l.endtime, l.isenabled, l.hasusbaccess," - + " l.runscript, b.osid, o.virtoskeyword, i.virtualizerconfig" + + " l.runscript, b.osid, o.virtoskeyword, i.virtualizerconfig," + + " i.filepath, i.isrestricted, b.displayname AS vmname, b.imagebaseid" + " FROM lecture l " + " INNER JOIN imageversion i USING (imageversionid)" + " INNER JOIN imagebase b USING (imagebaseid)" @@ -610,6 +645,10 @@ public class DbLecture { retval.configuration = configuration; retval.legacyRunScript = rs.getString("runscript"); + retval.imagePath = rs.getString("filepath"); + retval.vmName = rs.getString("vmname"); + retval.restricted = rs.getBoolean("isrestricted"); + retval.imageBaseId = rs.getString("imagebaseid"); retval.netShares = DbLectureNetshare.getCombinedForLecture(connection, lectureId); retval.runScript = DbRunScript.getRunScriptsForLaunch(connection, lectureId, rs.getInt("osid")); @@ -723,6 +762,10 @@ public class DbLecture { } public static class LaunchData { + public String vmName; + public boolean restricted; + public String imageBaseId; + public String imagePath; public byte[] configuration; public List netShares; public String legacyRunScript; diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/FileServer.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/FileServer.java index 04212c42..a1fd82d7 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/FileServer.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/FileServer.java @@ -30,6 +30,7 @@ import org.openslx.filetransfer.Downloader; import org.openslx.filetransfer.IncomingEvent; import org.openslx.filetransfer.Listener; import org.openslx.filetransfer.Uploader; +import org.openslx.filetransfer.util.FileChunk; import org.openslx.thrifthelper.Comparators; import org.openslx.util.GrowingThreadPoolExecutor; import org.openslx.util.PrioThreadFactory; @@ -59,6 +60,12 @@ public class FileServer implements IncomingEvent { */ private final Map downloads = new ConcurrentHashMap<>(); + /** + * Refuse transfers if they don't fit while keeping this additional space + * free. + */ + static final long MIN_FREE_SPACE_BYTES = FileChunk.CHUNK_SIZE * (2 + Constants.MAX_UPLOADS); + private static final FileServer globalInstance = new FileServer(); private FileServer() { @@ -171,7 +178,8 @@ public class FileServer implements IncomingEvent { throw new TTransferRejectedException("Destination file not writable!"); } - LOGGER.info(owner.getFirstName() + " " + owner.getLastName() + " (" + owner.getUserId() + ") started upload " + uploadkey + " of '" + image.getImageName() + "'"); + LOGGER.info(owner.getFirstName() + " " + owner.getLastName() + " (" + owner.getUserId() + + ") started upload " + uploadkey + " of '" + image.getImageName() + "'"); uploads.put(uploadkey, upload); return upload; } @@ -238,7 +246,8 @@ public class FileServer implements IncomingEvent { throw new TTransferRejectedException(errorMessage); } String key = UUID.randomUUID().toString(); - OutgoingDataTransfer transfer = new OutgoingDataTransfer(key, srcFile, getPlainPort(), getSslPort(), localImageData.imageVersionId); + OutgoingDataTransfer transfer = new OutgoingDataTransfer(key, srcFile, getPlainPort(), getSslPort(), + localImageData.imageVersionId); downloads.put(key, transfer); return transfer; } @@ -246,7 +255,7 @@ public class FileServer implements IncomingEvent { public Status getStatus() { return new Status(); } - + /** * Check whether the given imageVersionId refers to an active transfer. */ @@ -254,7 +263,8 @@ public class FileServer implements IncomingEvent { long now = System.currentTimeMillis(); if (versionId != null) { for (OutgoingDataTransfer odt : downloads.values()) { - if (versionId != null && versionId.equals(odt.getVersionId()) && !odt.isComplete(now) && odt.isActive()) + if (versionId != null && versionId.equals(odt.getVersionId()) && !odt.isComplete(now) + && odt.isActive()) return true; } } 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 2b1e3d18..61a2cfc4 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 @@ -2,7 +2,6 @@ package org.openslx.bwlp.sat.fileserv; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -47,8 +46,6 @@ public class IncomingDataTransfer extends IncomingTransferBase { private static final Logger LOGGER = LogManager.getLogger(IncomingDataTransfer.class); - private static final long MIN_FREE_SPACE_BYTES = FileChunk.CHUNK_SIZE * (2 + Constants.MAX_UPLOADS); - /** * User owning this uploaded file. */ @@ -305,20 +302,12 @@ public class IncomingDataTransfer extends IncomingTransferBase { return false; } // Dump CRC32 list - byte[] dnbd3Crc32List = null; try { - dnbd3Crc32List = getChunks().getDnbd3Crc32List(); + String crcfile = destination.getAbsolutePath() + ".crc"; + getChunks().writeCrc32List(crcfile); } catch (Exception e) { LOGGER.warn("Could not get CRC32 list for upload of " + image.getImageName(), e); } - if (dnbd3Crc32List != null) { - String crcfile = destination.getAbsolutePath() + ".crc"; - try (FileOutputStream fos = new FileOutputStream(crcfile)) { - fos.write(dnbd3Crc32List); - } catch (Exception e) { - LOGGER.warn("Could not write CRC32 list for DNBD3 at " + crcfile, e); - } - } return true; } @@ -368,7 +357,7 @@ public class IncomingDataTransfer extends IncomingTransferBase { @Override protected boolean hasEnoughFreeSpace() { - return FileSystem.getAvailableStorageBytes() > MIN_FREE_SPACE_BYTES; + return FileSystem.getAvailableStorageBytes() > FileServer.MIN_FREE_SPACE_BYTES; } @Override 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 9a1114ba..ef54bffa 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 @@ -351,7 +351,7 @@ public class ImageValidCheck implements Runnable { sem.release(); } } - }, HashChecker.BLOCKING | HashChecker.CALC_HASH); + }, HashChecker.BLOCKING | HashChecker.CHECK_SHA1); numChecked += 1; startOffset += FileChunk.CHUNK_SIZE; } diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Formatter.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Formatter.java index 0bdc0ab7..b1cbf4c3 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Formatter.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Formatter.java @@ -42,14 +42,14 @@ public class Formatter { /** * Generate a file name for the given VM based on owner and display name. * - * @param ts Timestamp of upload + * @param tsMillis Timestamp of upload * @param user The user associated with the VM, e.g. the owner * @param imageName Name of the VM * @param ext * @return File name for the VM derived from the function's input */ - public static String vmName(long ts, UserInfo user, String imageName, String ext) { - return cleanFileName(vmNameDateFormat.print(ts) + "_" + user.lastName + "_" + public static String vmName(long tsMillis, UserInfo user, String imageName, String ext) { + return cleanFileName(vmNameDateFormat.print(tsMillis) + "_" + user.lastName + "_" + imageName + "." + ext).toLowerCase(); } diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserEntryXml.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserEntryXml.java index fd4e0505..a03771e6 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserEntryXml.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserEntryXml.java @@ -38,10 +38,13 @@ public class VmChooserEntryXml { private VmChooserParamXml is_template; @Element private VmChooserListXml filters; + @Element(required = false) + private VmChooserParamXml allow_edit; public VmChooserEntryXml(String imageFilePath, int priority, String creator, String short_description, String long_description, String uuid, String virtId, String virtualizerName, String osVirtName, - String osDisplayName, String icon, boolean isForThisLocation, boolean isTemplate, List ldapFilters) { + String osDisplayName, String icon, boolean isForThisLocation, boolean isTemplate, + List ldapFilters, int allowEdit) { this.image_name = new VmChooserParamXml(imageFilePath); this.priority = new VmChooserParamXml(priority); this.creator = new VmChooserParamXml(creator); @@ -56,6 +59,9 @@ public class VmChooserEntryXml { this.for_location = new VmChooserParamXml(isForThisLocation); this.is_template = new VmChooserParamXml(isTemplate); this.filters = new VmChooserListXml(ldapFilters); + if (allowEdit != 0) { + this.allow_edit = new VmChooserParamXml(allowEdit); + } } private static class VmChooserParamXml { diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserListXml.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserListXml.java index 47ca0e1e..7d37ada7 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserListXml.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserListXml.java @@ -3,6 +3,7 @@ package org.openslx.bwlp.sat.web; import java.util.ArrayList; import java.util.List; +import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Root; @@ -11,6 +12,9 @@ public class VmChooserListXml { @ElementList(inline = true, name = "eintrag") private List entries; + + @Element(required = false) + private String error; public VmChooserListXml(boolean createEmptyList) { if (createEmptyList) { @@ -21,5 +25,9 @@ public class VmChooserListXml { public void add(VmChooserEntryXml vmChooserEntryXml) { entries.add(vmChooserEntryXml); } + + public void setError(String msg) { + this.error = msg; + } } diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebServer.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebServer.java index c3655572..eed9651c 100644 --- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebServer.java +++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebServer.java @@ -2,6 +2,7 @@ package org.openslx.bwlp.sat.web; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.sql.SQLException; @@ -16,16 +17,24 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.openslx.bwlp.sat.RuntimeConfig; import org.openslx.bwlp.sat.database.mappers.DbImage; import org.openslx.bwlp.sat.database.mappers.DbLecture; import org.openslx.bwlp.sat.database.mappers.DbLecture.LaunchData; import org.openslx.bwlp.sat.database.mappers.DbLecture.RunScript; import org.openslx.bwlp.sat.fileserv.FileServer; +import org.openslx.bwlp.sat.fileserv.cow.CowSession; +import org.openslx.bwlp.sat.fileserv.cow.CowSessionManager; +import org.openslx.bwlp.sat.permissions.User; +import org.openslx.bwlp.sat.thrift.SessionManager; +import org.openslx.bwlp.sat.util.BashVars; import org.openslx.bwlp.sat.util.Configuration; +import org.openslx.bwlp.thrift.iface.AuthorizationError; import org.openslx.bwlp.thrift.iface.NetRule; import org.openslx.bwlp.thrift.iface.NetShare; import org.openslx.bwlp.thrift.iface.NetShareAuth; import org.openslx.bwlp.thrift.iface.TNotFoundException; +import org.openslx.bwlp.thrift.iface.UserInfo; import org.openslx.util.GrowingThreadPoolExecutor; import org.openslx.util.Json; import org.openslx.util.PrioThreadFactory; @@ -73,9 +82,13 @@ public class WebServer extends NanoHTTPD { private Response handle(IHTTPSession session, String uri) { // Our special stuff + // REMEMBER: Call session.parseBody() if you need post params in session.getParms() + // Normalize during split String[] parts = uri.replaceFirst("^/+", "").split("/+"); + if (parts.length < 2) + return notFound(); // /vmchooser/* - if (parts.length > 1 && parts[0].equals("vmchooser")) { + if (parts[0].equals("vmchooser")) { if (parts[1].equals("list")) { try { return serveVmChooserList(session.getParms()); @@ -88,7 +101,7 @@ public class WebServer extends NanoHTTPD { if (parts.length < 4) return badRequest("Bad Request"); if (parts[3].equals("metadata")) - return serveMetaData(parts[2]); + return serveMetaData(parts[2], session.getParms()); if (parts[3].equals("netrules")) return serveLectureNetRules(parts[2]); if (parts[3].equals("imagemeta")) @@ -96,6 +109,38 @@ public class WebServer extends NanoHTTPD { } return notFound(); } + // /cow/* + if (parts[0].equals("cow")) { + if (parts.length < 3) + return badRequest("Bad request"); + if (session.getMethod() == Method.POST) { + try { + session.parseBody(); + } catch (IOException | ResponseException e) { + LOGGER.debug("could not parse request body", e); + return internalServerError(); + } + } + // Requests by dnbd3-fuse + if (parts[1].equals("v1") && parts[2].equals("file")) { + if (parts.length < 4) + return badRequest("No action"); + if (parts[3].equals("merge")) + return cowMerge(session.getParms()); + if (parts[3].equals("update")) + return cowUploadCluster(session); + return badRequest("No such API endpoint: " + parts[3]); + } + // Requests by the wrapper program + if (parts[1].equals("status")) + return cowServeStatus(parts[2]); + if (parts[1].equals("finish")) + return cowFinish(parts[2]); + if (parts[1].equals("abort")) + return cowAbort(parts[2], session.getMethod()); + // Huh? + return badRequest("No such API endpoint"); + } if (uri.startsWith("/bwlp/container/clusterimages")) { return serverContainerImages(); } @@ -111,7 +156,7 @@ public class WebServer extends NanoHTTPD { } if (session.getMethod() == Method.POST && uri.startsWith("/do/")) { try { - session.parseBody(null); + session.parseBody(); } catch (IOException | ResponseException e) { LOGGER.debug("could not parse request body", e); return internalServerError(); @@ -122,6 +167,114 @@ public class WebServer extends NanoHTTPD { return notFound(); } + private Response cowUploadCluster(IHTTPSession session) { + if (session.getMethod() != Method.POST && session.getMethod() != Method.PUT) + return badRequest("Not PUT or POST"); + InputStream is = session.getInputStream(); + if (is == null) + return internalServerError("Cannot get input stream"); + String uuid = session.getParms().get("uuid"); + String sIdx = session.getParms().get("clusterindex"); + if (uuid == null || sIdx == null) + return badRequest("uuid or clusterindex missing"); + long clusterIndex = Util.parseLong(sIdx, -1); + if (clusterIndex < 0) + return badRequest("Invalid clusterindex"); + CowSession cowSession = CowSessionManager.get(uuid); + if (cowSession == null) + return notFound("Invalid session ID"); + byte[] data; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(9001)) { + int n; + byte[] b = new byte[65536]; + while ((n = is.read(b)) > 0) { + baos.write(b, 0, n); + } + data = baos.toByteArray(); + } catch (IOException e) { + LOGGER.warn("Cannot read cluster", e); + return internalServerError("Cannot read cluster data from http stream"); + } + int cowRet = cowSession.addCluster(clusterIndex, data); + if (cowRet == 0) + return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/plain; charset=utf-8", "OK"); + // Error? + if (cowRet < 0) + return internalServerError("Error writing received chunk to disk"); + // Throttle request + Response reply = new NanoHTTPD.Response(NanoHTTPD.Response.Status.SERVICE_UNAVAILABLE, + "text/plain; charset=utf-8", "Slow down!"); + reply.addHeader("Retry-After", Integer.toString(cowRet)); + return reply; + } + + /** + * dnbd3-fuse is requesting merge. We take that as signal that the upload is + * done. + */ + private Response cowMerge(Map parms) { + String uuid = parms.get("uuid"); + String newSz = parms.get("newFileSize"); + if (uuid == null || newSz == null) + return badRequest("uuid or newFileSize missing"); + CowSession session = CowSessionManager.get(uuid); + if (session == null) + return notFound("Invalid session ID"); + long finalSize = Util.parseLong(newSz, -1); + long limit = (RuntimeConfig.getVmSizeLimit() * 12) / 10; + if (limit <= 0) { + limit = 300_000_000_000l; + } + if (finalSize < 0 || finalSize > limit) + return badRequest("Illegal final file size"); + LOGGER.info("Got upload finished for CoW session"); + try { + session.uploadFinished(finalSize); + } catch (Exception e) { + return badRequest("Cannot finish upload: " + e.getMessage()); + } + return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/plain; charset=utf-8", "OK"); + } + + /** + * User-facing tool on the client was used to confirm keeping the modified + * image. + */ + private Response cowFinish(String sessionId) { + CowSession session = CowSessionManager.get(sessionId); + if (session == null) + return notFound("Invalid session ID"); + LOGGER.info("User requested finalization for CoW session"); + try { + session.requestFinalization(); + } catch (Exception e) { + return badRequest("Cannot finalize image version: " + e.getMessage()); + } + return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/plain; charset=utf-8", "OK"); + } + + /** + * Abort a COW session, delete temporary files. + */ + private Response cowAbort(String sessionId, Method method) { + if (method != Method.POST) + return badRequest("Need abort request as POST"); + CowSession session = CowSessionManager.get(sessionId); + if (session == null) + return notFound("Invalid session ID"); + LOGGER.info("User sent abort request for CoW session"); + session.abort(); + return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/plain; charset=utf-8", "OK"); + } + + private Response cowServeStatus(String sessionId) { + CowSession session = CowSessionManager.get(sessionId); + if (session == null) + return notFound("Invalid session ID"); + return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "application/json; charset=utf-8", + session.getStatusJson()); + } + private Response serveStatus() { return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "application/json; charset=utf-8", Json.serialize(FileServer.instance().getStatus())); @@ -129,13 +282,11 @@ public class WebServer extends NanoHTTPD { /** * Return meta data (eg. *.vmx) required to start the given lecture. - * - * @param lectureId - * @return */ - private Response serveMetaData(final String lectureId) { + private Response serveMetaData(final String lectureId, Map parms) { PipedInputStream sink = new PipedInputStream(10000); try { + final BashVars vars = new BashVars(); final TarArchiveWriter tarArchiveWriter = new TarArchiveWriter(new PipedOutputStream(sink)); final LaunchData ld; try { @@ -146,19 +297,35 @@ public class WebServer extends NanoHTTPD { } catch (SQLException e) { return internalServerError(); } + vars.addVar("DMSD_IMAGE_PATH", ld.imagePath); + // See if a CoW session is requested + try { + String sessionType = parms.get("cow-type"); + String cowUserId = parms.get("cow-user"); + if (cowUserId != null) { + String cowSession = CowSessionManager.create(cowUserId, ld, sessionType); + vars.addVar("DMSD_COW_SESSION", cowSession); + } + } catch (Exception e) { + LOGGER.warn("Error creating cow session for " + ld.imageBaseId + ", " + ld.imagePath, e); + return internalServerError("Cannot create COW session: " + e.getMessage()); + } // Meta is required, everything else is optional tpe.execute(new Runnable() { @Override public void run() { try { tarArchiveWriter.writeFile("vmx", ld.configuration); + tarArchiveWriter.writeFile("config.inc", vars.toString()); tarArchiveWriter.writeFile("runscript", ld.legacyRunScript); tarArchiveWriter.writeFile("netshares", serializeNetShares(ld.netShares)); if (ld.runScript != null) { int cnt = 0; for (RunScript rs : ld.runScript) { - tarArchiveWriter.writeFile(String.format("adminrun/%04d-%d-%d.%s", cnt++, rs.visibility, - rs.passCreds ? 1 : 0, rs.extension), rs.content); + tarArchiveWriter.writeFile( + String.format("adminrun/%04d-%d-%d.%s", cnt++, rs.visibility, + rs.passCreds ? 1 : 0, rs.extension), + rs.content); } } } catch (IOException e) { @@ -206,7 +373,8 @@ public class WebServer extends NanoHTTPD { sb.append("IN * 0 REJECT\n"); sb.append("OUT * 0 REJECT\n"); } - return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/plain; charset=utf-8", sb.toString()); + return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/plain; charset=utf-8", + sb.toString()); } private String serializeNetShares(List list) { @@ -247,9 +415,30 @@ public class WebServer extends NanoHTTPD { */ private Response serveVmChooserList(Map params) throws Exception { String locations = params.get("locations"); + String userToken = params.get("cow-user"); boolean exams = params.containsKey("exams"); + String addUserError = null; + UserInfo user = null; + + if (!Util.isEmptyString(userToken)) { + user = SessionManager.get(userToken); + if (user == null || user.userId == null) { + addUserError = "Invalid session token, edit mode not available\n" + + "Ungültiges Sitzungstoken, Editiermodus nicht verfügbar"; + } else { + AuthorizationError err = User.canLogin(user); + if (err != null) { + user = null; + addUserError = "You are not allowed to edit VMs\n" + + "Sie haben keine Berechtigung, um VMs zu bearbeiten."; + } + } + } - VmChooserListXml listXml = DbLecture.getUsableListXml(exams, locations); + VmChooserListXml listXml = DbLecture.getUsableListXml(exams, locations, user); + if (addUserError != null) { + listXml.setError(addUserError); + } ByteArrayOutputStream baos = new ByteArrayOutputStream(); serializer.write(listXml, baos); return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/xml; charset=utf-8", @@ -273,7 +462,14 @@ public class WebServer extends NanoHTTPD { * Helper for returning "404 Not Found" Status */ public static Response notFound() { - return new NanoHTTPD.Response(NanoHTTPD.Response.Status.NOT_FOUND, "text/plain", "Nicht gefunden!"); + return notFound("Nicht gefunden"); + } + + /** + * Helper for returning "404 Not Found" Status with custom body + */ + private static Response notFound(String string) { + return new NanoHTTPD.Response(NanoHTTPD.Response.Status.NOT_FOUND, "text/plain", string); } /** @@ -287,7 +483,8 @@ public class WebServer extends NanoHTTPD { } /** - * create a json response with information about existing container images in + * create a json response with information about existing container images + * in * bwlehrpool */ private Response serverContainerImages() { -- cgit v1.2.3-55-g7522