YARN-5191. Renamed the newly added “download=true” option for getting logs via NMWebServices and AHSWebServices to be a better "format" option. (Xuan Gong via vinodkv)
(cherry picked from commit 9378d9428f
)
This commit is contained in:
parent
aef6e455b1
commit
f6cd1bcf89
|
@ -24,6 +24,7 @@ import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.hadoop.classification.InterfaceAudience.Private;
|
import org.apache.hadoop.classification.InterfaceAudience.Private;
|
||||||
|
@ -400,4 +401,21 @@ public class WebAppUtils {
|
||||||
}
|
}
|
||||||
return aid;
|
return aid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getSupportedLogContentType(String format) {
|
||||||
|
if (format.equalsIgnoreCase("text")) {
|
||||||
|
return "text/plain";
|
||||||
|
} else if (format.equalsIgnoreCase("octet-stream")) {
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getDefaultLogContentType() {
|
||||||
|
return "text/plain";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> listSupportedLogContentType() {
|
||||||
|
return Arrays.asList("text", "octet-stream");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@ import org.apache.hadoop.yarn.server.webapp.dao.ContainersInfo;
|
||||||
import org.apache.hadoop.yarn.util.Times;
|
import org.apache.hadoop.yarn.util.Times;
|
||||||
import org.apache.hadoop.yarn.util.timeline.TimelineUtils;
|
import org.apache.hadoop.yarn.util.timeline.TimelineUtils;
|
||||||
import org.apache.hadoop.yarn.webapp.BadRequestException;
|
import org.apache.hadoop.yarn.webapp.BadRequestException;
|
||||||
|
import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
|
@ -212,7 +213,7 @@ public class AHSWebServices extends WebServices {
|
||||||
@Context HttpServletResponse res,
|
@Context HttpServletResponse res,
|
||||||
@PathParam("containerid") String containerIdStr,
|
@PathParam("containerid") String containerIdStr,
|
||||||
@PathParam("filename") String filename,
|
@PathParam("filename") String filename,
|
||||||
@QueryParam("download") String download,
|
@QueryParam("format") String format,
|
||||||
@QueryParam("size") String size) {
|
@QueryParam("size") String size) {
|
||||||
init(res);
|
init(res);
|
||||||
ContainerId containerId;
|
ContainerId containerId;
|
||||||
|
@ -223,9 +224,6 @@ public class AHSWebServices extends WebServices {
|
||||||
"Invalid ContainerId: " + containerIdStr);
|
"Invalid ContainerId: " + containerIdStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean downloadFile = parseBooleanParam(download);
|
|
||||||
|
|
||||||
|
|
||||||
final long length = parseLongParam(size);
|
final long length = parseLongParam(size);
|
||||||
|
|
||||||
ApplicationId appId = containerId.getApplicationAttemptId()
|
ApplicationId appId = containerId.getApplicationAttemptId()
|
||||||
|
@ -236,7 +234,7 @@ public class AHSWebServices extends WebServices {
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
// directly find logs from HDFS.
|
// directly find logs from HDFS.
|
||||||
return sendStreamOutputResponse(appId, null, null, containerIdStr,
|
return sendStreamOutputResponse(appId, null, null, containerIdStr,
|
||||||
filename, downloadFile, length);
|
filename, format, length);
|
||||||
}
|
}
|
||||||
String appOwner = appInfo.getUser();
|
String appOwner = appInfo.getUser();
|
||||||
|
|
||||||
|
@ -250,7 +248,7 @@ public class AHSWebServices extends WebServices {
|
||||||
if (isFinishedState(appInfo.getAppState())) {
|
if (isFinishedState(appInfo.getAppState())) {
|
||||||
// directly find logs from HDFS.
|
// directly find logs from HDFS.
|
||||||
return sendStreamOutputResponse(appId, appOwner, null, containerIdStr,
|
return sendStreamOutputResponse(appId, appOwner, null, containerIdStr,
|
||||||
filename, downloadFile, length);
|
filename, format, length);
|
||||||
}
|
}
|
||||||
return createBadResponse(Status.INTERNAL_SERVER_ERROR,
|
return createBadResponse(Status.INTERNAL_SERVER_ERROR,
|
||||||
"Can not get ContainerInfo for the container: " + containerId);
|
"Can not get ContainerInfo for the container: " + containerId);
|
||||||
|
@ -270,7 +268,7 @@ public class AHSWebServices extends WebServices {
|
||||||
return response.build();
|
return response.build();
|
||||||
} else if (isFinishedState(appInfo.getAppState())) {
|
} else if (isFinishedState(appInfo.getAppState())) {
|
||||||
return sendStreamOutputResponse(appId, appOwner, nodeId,
|
return sendStreamOutputResponse(appId, appOwner, nodeId,
|
||||||
containerIdStr, filename, downloadFile, length);
|
containerIdStr, filename, format, length);
|
||||||
} else {
|
} else {
|
||||||
return createBadResponse(Status.NOT_FOUND,
|
return createBadResponse(Status.NOT_FOUND,
|
||||||
"The application is not at Running or Finished State.");
|
"The application is not at Running or Finished State.");
|
||||||
|
@ -293,13 +291,19 @@ public class AHSWebServices extends WebServices {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean parseBooleanParam(String param) {
|
|
||||||
return ("true").equalsIgnoreCase(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Response sendStreamOutputResponse(ApplicationId appId,
|
private Response sendStreamOutputResponse(ApplicationId appId,
|
||||||
String appOwner, String nodeId, String containerIdStr,
|
String appOwner, String nodeId, String containerIdStr,
|
||||||
String fileName, boolean downloadFile, long bytes) {
|
String fileName, String format, long bytes) {
|
||||||
|
String contentType = WebAppUtils.getDefaultLogContentType();
|
||||||
|
if (format != null && !format.isEmpty()) {
|
||||||
|
contentType = WebAppUtils.getSupportedLogContentType(format);
|
||||||
|
if (contentType == null) {
|
||||||
|
String errorMessage = "The valid values for the parameter : format "
|
||||||
|
+ "are " + WebAppUtils.listSupportedLogContentType();
|
||||||
|
return Response.status(Status.BAD_REQUEST).entity(errorMessage)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
StreamingOutput stream = null;
|
StreamingOutput stream = null;
|
||||||
try {
|
try {
|
||||||
stream = getStreamingOutput(appId, appOwner, nodeId,
|
stream = getStreamingOutput(appId, appOwner, nodeId,
|
||||||
|
@ -313,9 +317,11 @@ public class AHSWebServices extends WebServices {
|
||||||
"Can not get log for container: " + containerIdStr);
|
"Can not get log for container: " + containerIdStr);
|
||||||
}
|
}
|
||||||
ResponseBuilder response = Response.ok(stream);
|
ResponseBuilder response = Response.ok(stream);
|
||||||
if (downloadFile) {
|
response.header("Content-Type", contentType);
|
||||||
response.header("Content-Type", "application/octet-stream");
|
// Sending the X-Content-Type-Options response header with the value
|
||||||
}
|
// nosniff will prevent Internet Explorer from MIME-sniffing a response
|
||||||
|
// away from the declared content-type.
|
||||||
|
response.header("X-Content-Type-Options", "nosniff");
|
||||||
return response.build();
|
return response.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -206,6 +206,10 @@ public class NMWebServices {
|
||||||
* The container ID
|
* The container ID
|
||||||
* @param filename
|
* @param filename
|
||||||
* The name of the log file
|
* The name of the log file
|
||||||
|
* @param format
|
||||||
|
* The content type
|
||||||
|
* @param size
|
||||||
|
* the size of the log file
|
||||||
* @return
|
* @return
|
||||||
* The contents of the container's log file
|
* The contents of the container's log file
|
||||||
*/
|
*/
|
||||||
|
@ -216,7 +220,7 @@ public class NMWebServices {
|
||||||
@Unstable
|
@Unstable
|
||||||
public Response getLogs(@PathParam("containerid") String containerIdStr,
|
public Response getLogs(@PathParam("containerid") String containerIdStr,
|
||||||
@PathParam("filename") String filename,
|
@PathParam("filename") String filename,
|
||||||
@QueryParam("download") String download,
|
@QueryParam("format") String format,
|
||||||
@QueryParam("size") String size) {
|
@QueryParam("size") String size) {
|
||||||
ContainerId containerId;
|
ContainerId containerId;
|
||||||
try {
|
try {
|
||||||
|
@ -234,8 +238,18 @@ public class NMWebServices {
|
||||||
} catch (YarnException ex) {
|
} catch (YarnException ex) {
|
||||||
return Response.serverError().entity(ex.getMessage()).build();
|
return Response.serverError().entity(ex.getMessage()).build();
|
||||||
}
|
}
|
||||||
boolean downloadFile = parseBooleanParam(download);
|
|
||||||
final long bytes = parseLongParam(size);
|
final long bytes = parseLongParam(size);
|
||||||
|
String contentType = WebAppUtils.getDefaultLogContentType();
|
||||||
|
if (format != null && !format.isEmpty()) {
|
||||||
|
contentType = WebAppUtils.getSupportedLogContentType(format);
|
||||||
|
if (contentType == null) {
|
||||||
|
String errorMessage = "The valid values for the parameter : format "
|
||||||
|
+ "are " + WebAppUtils.listSupportedLogContentType();
|
||||||
|
return Response.status(Status.BAD_REQUEST).entity(errorMessage)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final FileInputStream fis = ContainerLogsUtils.openLogFileForRead(
|
final FileInputStream fis = ContainerLogsUtils.openLogFileForRead(
|
||||||
containerIdStr, logFile, nmContext);
|
containerIdStr, logFile, nmContext);
|
||||||
|
@ -288,22 +302,17 @@ public class NMWebServices {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ResponseBuilder resp = Response.ok(stream);
|
ResponseBuilder resp = Response.ok(stream);
|
||||||
if (downloadFile) {
|
resp.header("Content-Type", contentType);
|
||||||
resp.header("Content-Type", "application/octet-stream");
|
// Sending the X-Content-Type-Options response header with the value
|
||||||
}
|
// nosniff will prevent Internet Explorer from MIME-sniffing a response
|
||||||
|
// away from the declared content-type.
|
||||||
|
resp.header("X-Content-Type-Options", "nosniff");
|
||||||
return resp.build();
|
return resp.build();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
return Response.serverError().entity(ex.getMessage()).build();
|
return Response.serverError().entity(ex.getMessage()).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean parseBooleanParam(String param) {
|
|
||||||
if (param != null) {
|
|
||||||
return ("true").equalsIgnoreCase(param);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long parseLongParam(String bytes) {
|
private long parseLongParam(String bytes) {
|
||||||
if (bytes == null || bytes.isEmpty()) {
|
if (bytes == null || bytes.isEmpty()) {
|
||||||
return Long.MAX_VALUE;
|
return Long.MAX_VALUE;
|
||||||
|
|
|
@ -57,6 +57,7 @@ import org.apache.hadoop.yarn.webapp.GenericExceptionHandler;
|
||||||
import org.apache.hadoop.yarn.webapp.JerseyTestBase;
|
import org.apache.hadoop.yarn.webapp.JerseyTestBase;
|
||||||
import org.apache.hadoop.yarn.webapp.WebApp;
|
import org.apache.hadoop.yarn.webapp.WebApp;
|
||||||
import org.apache.hadoop.yarn.webapp.WebServicesTestUtils;
|
import org.apache.hadoop.yarn.webapp.WebServicesTestUtils;
|
||||||
|
import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
|
||||||
import org.codehaus.jettison.json.JSONException;
|
import org.codehaus.jettison.json.JSONException;
|
||||||
import org.codehaus.jettison.json.JSONObject;
|
import org.codehaus.jettison.json.JSONObject;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
@ -389,18 +390,30 @@ public class TestNMWebServices extends JerseyTestBase {
|
||||||
.queryParam("size", "-10000")
|
.queryParam("size", "-10000")
|
||||||
.accept(MediaType.TEXT_PLAIN).get(ClientResponse.class);
|
.accept(MediaType.TEXT_PLAIN).get(ClientResponse.class);
|
||||||
responseText = response.getEntity(String.class);
|
responseText = response.getEntity(String.class);
|
||||||
|
assertEquals("text/plain", response.getType().toString());
|
||||||
assertEquals(fullTextSize, responseText.getBytes().length);
|
assertEquals(fullTextSize, responseText.getBytes().length);
|
||||||
assertEquals(logMessage, responseText);
|
assertEquals(logMessage, responseText);
|
||||||
|
|
||||||
// ask and download it
|
// ask and download it
|
||||||
response = r.path("ws").path("v1").path("node").path("containerlogs")
|
response = r.path("ws").path("v1").path("node").path("containerlogs")
|
||||||
.path(containerIdStr).path(filename).queryParam("download", "true")
|
.path(containerIdStr).path(filename)
|
||||||
|
.queryParam("format", "octet-stream")
|
||||||
.accept(MediaType.TEXT_PLAIN).get(ClientResponse.class);
|
.accept(MediaType.TEXT_PLAIN).get(ClientResponse.class);
|
||||||
responseText = response.getEntity(String.class);
|
responseText = response.getEntity(String.class);
|
||||||
assertEquals(logMessage, responseText);
|
assertEquals(logMessage, responseText);
|
||||||
assertEquals(200, response.getStatus());
|
assertEquals(200, response.getStatus());
|
||||||
assertEquals("application/octet-stream", response.getType().toString());
|
assertEquals("application/octet-stream", response.getType().toString());
|
||||||
|
|
||||||
|
// specify a invalid format value
|
||||||
|
response = r.path("ws").path("v1").path("node").path("containerlogs")
|
||||||
|
.path(containerIdStr).path(filename)
|
||||||
|
.queryParam("format", "123")
|
||||||
|
.accept(MediaType.TEXT_PLAIN).get(ClientResponse.class);
|
||||||
|
responseText = response.getEntity(String.class);
|
||||||
|
assertEquals("The valid values for the parameter : format are "
|
||||||
|
+ WebAppUtils.listSupportedLogContentType(), responseText);
|
||||||
|
assertEquals(400, response.getStatus());
|
||||||
|
|
||||||
// ask for file that doesn't exist
|
// ask for file that doesn't exist
|
||||||
response = r.path("ws").path("v1").path("node")
|
response = r.path("ws").path("v1").path("node")
|
||||||
.path("containerlogs").path(containerIdStr).path("uhhh")
|
.path("containerlogs").path(containerIdStr).path("uhhh")
|
||||||
|
|
Loading…
Reference in New Issue