-
Notifications
You must be signed in to change notification settings - Fork 188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proxy download from Mesos slave over Singularity. #1817
Changes from 4 commits
cc69460
0ad34c5
ec9500c
c2d8871
d74d500
a76151f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,6 +5,8 @@ | |||||||||||||
import static com.hubspot.singularity.WebExceptions.checkNotFound; | ||||||||||||||
import static com.hubspot.singularity.WebExceptions.notFound; | ||||||||||||||
|
||||||||||||||
import java.io.InputStream; | ||||||||||||||
import java.nio.file.Paths; | ||||||||||||||
import java.util.Collections; | ||||||||||||||
import java.util.List; | ||||||||||||||
|
||||||||||||||
|
@@ -18,6 +20,8 @@ | |||||||||||||
import javax.ws.rs.Produces; | ||||||||||||||
import javax.ws.rs.QueryParam; | ||||||||||||||
import javax.ws.rs.WebApplicationException; | ||||||||||||||
import javax.ws.rs.client.Client; | ||||||||||||||
import javax.ws.rs.client.ClientBuilder; | ||||||||||||||
import javax.ws.rs.core.Context; | ||||||||||||||
import javax.ws.rs.core.MediaType; | ||||||||||||||
import javax.ws.rs.core.Response; | ||||||||||||||
|
@@ -609,4 +613,30 @@ public List<SingularityTaskShellCommandUpdate> getShellCommandHisotryUpdates( | |||||||||||||
SingularityTaskId taskIdObj = getTaskIdFromStr(taskId); | ||||||||||||||
return taskManager.getTaskShellCommandUpdates(new SingularityTaskShellCommandRequestId(taskIdObj, commandName, commandTimestamp)); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
@GET | ||||||||||||||
@Path("/download/{httpPrefix}/{slaveHostname}/port/{port}/path/{path}") | ||||||||||||||
@Produces(MediaType.APPLICATION_OCTET_STREAM) | ||||||||||||||
@Operation(summary = "Proxy a file download from a Mesos Slave through Singularity") | ||||||||||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. -- whoops -- left this there from when I had it as a |
||||||||||||||
public Response downloadFileOverProxy( | ||||||||||||||
@Parameter(required = true, description = "Https (if available) or http") @PathParam("httpPrefix") String httpPrefix, | ||||||||||||||
@Parameter(required = true, description = "Mesos slave hostname") @PathParam("slaveHostname") String slaveHostname, | ||||||||||||||
@Parameter(required = true, description = "Http port for Mesos slave") @PathParam("port") String httpPort, | ||||||||||||||
@Parameter(required = true, description = "Full file path to file on Mesos slave to be downloaded") @PathParam("path") String fileFullPath | ||||||||||||||
) { | ||||||||||||||
|
||||||||||||||
Client client = ClientBuilder.newClient(); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe we want to inject & use the Singularity/SingularityService/src/main/java/com/hubspot/singularity/data/SandboxManager.java Lines 58 to 63 in c2d8871
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah neat, will do. We already have one being injected too which is awesome since creating these clients are expensive operations. |
||||||||||||||
String url = String.format("%s://%s:%s/files/download.json?path=%s", | ||||||||||||||
httpPrefix, slaveHostname, httpPort, fileFullPath); | ||||||||||||||
final InputStream responseStream = client.target(url).request().get(InputStream.class); | ||||||||||||||
|
||||||||||||||
// Strip file path down to just a file name if we can | ||||||||||||||
java.nio.file.Path filePath = Paths.get(fileFullPath).getFileName(); | ||||||||||||||
String fileName = filePath != null ? filePath.toString() : fileFullPath; | ||||||||||||||
|
||||||||||||||
final String headerValue = String.format("attachment; filename=\"%s\"", fileName); | ||||||||||||||
return Response.ok(responseStream).header("Content-Disposition", headerValue).build(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -178,9 +178,9 @@ class TaskDetail extends Component { | |
file.uiPath = file.name; | ||
} | ||
|
||
file.fullPath = `${files.fullPathToRoot}/${files.currentDirectory}/${file.name}`; | ||
file.downloadLink = `${httpPrefix}://${files.slaveHostname}:${httpPort}/files/download.json?path=${file.fullPath}`; | ||
file.fullPath = encodeURIComponent(`${files.fullPathToRoot}/${files.currentDirectory}/${file.name}`); | ||
|
||
file.downloadLink = `${config.apiRoot}/tasks/download/${httpPrefix}/${files.slaveHostname}/port/${httpPort}/path/${file.fullPath}`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The value of |
||
file.isRecentlyModified = Date.now() / 1000 - file.mtime <= RECENTLY_MODIFIED_SECONDS; | ||
|
||
if (!file.isDirectory) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sort of a nit, but a better API design here could be something like
GET /download?slaveHostname=x&path=y
(since the actual resource being retrieved is adownload
). The rest of our API doesn't stick to these guidelines religiously, though, so probably not a huge deal. I'll let @ssalinas weigh in as well.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to have them both as
PathParam
s instead ofQueryParam
s since both the slave hostname and the path are both required fields? I did change the URL a bit to better match entity class hierarchy standards. See top answer and comments on this StackOverflow post.