summaryrefslogtreecommitdiffstats
path: root/dozentenmodulserver
diff options
context:
space:
mode:
authorSimon Rettberg2024-05-08 18:38:31 +0200
committerSimon Rettberg2024-05-08 18:38:31 +0200
commit50866f7ef328bac854bd076ca48a3aa08aa3c29c (patch)
tree6415b45dc9c5344c76fc161ec22966d6c5898fc0 /dozentenmodulserver
parent[server] DbImage: Add log messages (diff)
downloadtutor-module-50866f7ef328bac854bd076ca48a3aa08aa3c29c.tar.gz
tutor-module-50866f7ef328bac854bd076ca48a3aa08aa3c29c.tar.xz
tutor-module-50866f7ef328bac854bd076ca48a3aa08aa3c29c.zip
[server] Add support for CoW sessions
Diffstat (limited to 'dozentenmodulserver')
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecture.java51
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/FileServer.java18
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/IncomingDataTransfer.java17
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/ImageValidCheck.java2
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Formatter.java6
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserEntryXml.java8
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserListXml.java8
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebServer.java223
8 files changed, 293 insertions, 40 deletions
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<XmlFilterEntry> 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<NetShare> 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<String, OutgoingDataTransfer> 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<XmlFilterEntry> ldapFilters) {
+ String osDisplayName, String icon, boolean isForThisLocation, boolean isTemplate,
+ List<XmlFilterEntry> 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<VmChooserEntryXml> 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<String, String> 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<String, String> 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<NetShare> list) {
@@ -247,9 +415,30 @@ public class WebServer extends NanoHTTPD {
*/
private Response serveVmChooserList(Map<String, String> 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() {