/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hdfs.server.namenode;
import java.util.*;
import java.io.*;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hdfs.server.common.HdfsConstants;
import org.apache.hadoop.hdfs.server.common.StorageInfo;
import org.apache.hadoop.hdfs.server.namenode.NNStorage.StorageLocationType;
import org.apache.hadoop.hdfs.server.protocol.RemoteEditLog;
import org.apache.hadoop.hdfs.util.MD5FileUtils;
import org.apache.hadoop.io.MD5Hash;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.util.DataTransferThrottler;
import org.apache.hadoop.util.StringUtils;
/**
* This class is used in Namesystem's jetty to retrieve a file.
* Typically used by the Secondary NameNode to retrieve image and
* edit file for periodic checkpointing.
*/
public class GetImageServlet extends HttpServlet {
private static final long serialVersionUID = -7669068179452648952L;
public final static String CONTENT_DISPOSITION = "Content-Disposition";
public final static String HADOOP_IMAGE_EDITS_HEADER = "X-Image-Edits-Name";
private static final Log LOG = LogFactory.getLog(GetImageServlet.class);
private static final String TXID_PARAM = "txid";
private static final String START_TXID_PARAM = "startTxId";
private static final String END_TXID_PARAM = "endTxId";
private static final String STORAGEINFO_PARAM = "storageInfo";
private static final String THROTTLE_PARAM = "disableThrottler";
private static Set<Long> currentlyDownloadingCheckpoints =
Collections.<Long>synchronizedSet(new HashSet<Long>());
public void doGet(final HttpServletRequest request,
final HttpServletResponse response
) throws ServletException, IOException {
try {
ServletContext context = getServletContext();
final FSImage nnImage = (FSImage)context.getAttribute("name.system.image");
final GetImageParams parsedParams = new GetImageParams(request, response);
final Configuration conf =
(Configuration)getServletContext().getAttribute("name.conf");
String myStorageInfoString = nnImage.storage.toColonSeparatedString();
String theirStorageInfoString = parsedParams.getStorageInfoString();
if (theirStorageInfoString != null &&
!myStorageInfoString.equals(theirStorageInfoString)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"This namenode has storage info " + myStorageInfoString +
" but the secondary expected " + theirStorageInfoString);
LOG.warn("Received an invalid request file transfer request " +
"from a secondary with storage info " + theirStorageInfoString);
return;
}
if (parsedParams.isGetImage()) {
long txid = parsedParams.getTxId();
File imageFile = nnImage.storage.getFsImageName(StorageLocationType.LOCAL, txid);
if (imageFile == null) {
throw new IOException("Could not find image with txid " + txid);
}
setVerificationHeaders(response, imageFile);
// send fsImage
TransferFsImage.getFileServer(response.getOutputStream(), imageFile,
getThrottler(conf, parsedParams.isThrottlerDisabled()));
} else if (parsedParams.isGetEdit()) {
long startTxId = parsedParams.getStartTxId();
long endTxId = parsedParams.getEndTxId();
File editFile = nnImage.storage
.findFinalizedEditsFile(startTxId, endTxId);
setVerificationHeaders(response, editFile);
// send edits
TransferFsImage.getFileServer(response.getOutputStream(), editFile,
getThrottler(conf, parsedParams.isThrottlerDisabled()));
} else if (parsedParams.isPutImage()) {
final long txid = parsedParams.getTxId();
if (! currentlyDownloadingCheckpoints.add(txid)) {
throw new IOException(
"Another checkpointer is already in the process of uploading a" +
" checkpoint made at transaction ID " + txid);
}
try {
if (nnImage.storage.findImageFile(txid) != null) {
throw new IOException(
"Another checkpointer already uploaded an checkpoint " +
"for txid " + txid);
}
MD5Hash downloadImageDigest = TransferFsImage.downloadImageToStorage(
parsedParams.getInfoServer(), txid,
nnImage, true);
nnImage.checkpointUploadDone(txid, downloadImageDigest);
} finally {
currentlyDownloadingCheckpoints.remove(txid);
}
}
} catch (Exception ie) {
String errMsg = "GetImage failed. " + StringUtils.stringifyException(ie);
response.sendError(HttpServletResponse.SC_GONE, errMsg);
throw new IOException(errMsg);
} finally {
response.getOutputStream().close();
}
}
/**
* Construct a throttler from conf
* @param conf configuration
* @return a data transfer throttler
*/
public static final DataTransferThrottler getThrottler(Configuration conf,
boolean disableThrottler) {
if (disableThrottler) {
return null;
}
long transferBandwidth =
conf.getLong(HdfsConstants.DFS_IMAGE_TRANSFER_RATE_KEY,
HdfsConstants.DFS_IMAGE_TRANSFER_RATE_DEFAULT);
DataTransferThrottler throttler = null;
if (transferBandwidth > 0) {
throttler = new DataTransferThrottler(transferBandwidth);
}
return throttler;
}
/**
* Set headers for content length, and, if available, md5.
* @throws IOException
*/
public static void setVerificationHeaders(HttpServletResponse response, File file)
throws IOException {
response.setHeader(TransferFsImage.CONTENT_LENGTH,
String.valueOf(file.length()));
MD5Hash hash = MD5FileUtils.readStoredMd5ForFile(file);
if (hash != null) {
response.setHeader(TransferFsImage.MD5_HEADER, hash.toString());
}
}
public static void setFileNameHeaders(HttpServletResponse response,
File file) {
response.setHeader(CONTENT_DISPOSITION, "attachment; filename=" +
file.getName());
response.setHeader(HADOOP_IMAGE_EDITS_HEADER, file.getName());
}
static String getParamStringForImage(long txid,
StorageInfo remoteStorageInfo, boolean throttle) {
return "getimage=1&" + TXID_PARAM + "=" + txid
+ "&" + STORAGEINFO_PARAM + "=" +
remoteStorageInfo.toColonSeparatedString()
+ "&" + THROTTLE_PARAM + "=" + throttle;
}
static String getParamStringForLog(RemoteEditLog log,
StorageInfo remoteStorageInfo, boolean throttle) {
return "getedit" + "=1&" + START_TXID_PARAM + "=" + log.getStartTxId()
+ "&" + END_TXID_PARAM + "=" + log.getEndTxId()
+ "&" + STORAGEINFO_PARAM + "=" +
remoteStorageInfo.toColonSeparatedString()
+ "&" + THROTTLE_PARAM + "=" + throttle;
}
static String getParamStringToPutImage(long txid,
String machine, int port, NNStorage storage) {
return "putimage=1" +
"&" + TXID_PARAM + "=" + txid +
"&port=" + port +
"&machine=" + machine
+ "&" + STORAGEINFO_PARAM + "=" +
storage.toColonSeparatedString();
}
public static class GetImageParams {
private boolean isGetImage;
private boolean isGetEdit;
private boolean isPutImage;
private int remoteport;
private String machineName;
private long startTxId, endTxId, txId;
private String storageInfoString;
private boolean disableThrottler;
/**
* @param request the object from which this servlet reads the url contents
* @param response the object into which this servlet writes the url contents
* @throws IOException if the request is bad
*/
public GetImageParams(HttpServletRequest request,
HttpServletResponse response
) throws IOException {
@SuppressWarnings("unchecked")
Map<String, String[]> pmap = request.getParameterMap();
isGetImage = isGetEdit = isPutImage = false;
remoteport = 0;
machineName = null;
disableThrottler = false;
for (Map.Entry<String, String[]> entry : pmap.entrySet()) {
String key = entry.getKey();
String[] val = entry.getValue();
if (key.equals("getimage")) {
isGetImage = true;
txId = parseLongParam(request, TXID_PARAM);
} else if (key.equals("getedit")) {
isGetEdit = true;
startTxId = parseLongParam(request, START_TXID_PARAM);
endTxId = parseLongParam(request, END_TXID_PARAM);
} else if (key.equals("putimage")) {
isPutImage = true;
txId = parseLongParam(request, TXID_PARAM);
} else if (key.equals("port")) {
remoteport = new Integer(val[0]).intValue();
} else if (key.equals("machine")) {
machineName = val[0];
} else if (key.equals(STORAGEINFO_PARAM)) {
storageInfoString = val[0];
} else if (key.equals(THROTTLE_PARAM)) {
disableThrottler = parseBooleanParam(request, THROTTLE_PARAM);
}
}
int numGets = (isGetImage?1:0) + (isGetEdit?1:0);
if ((numGets > 1) || (numGets == 0) && !isPutImage) {
throw new IOException("Illegal parameters to TransferFsImage");
}
}
public String getStorageInfoString() {
return storageInfoString;
}
public long getTxId() {
assert isGetImage || isPutImage;
return txId;
}
public long getStartTxId() {
assert isGetEdit;
return startTxId;
}
public long getEndTxId() {
assert isGetEdit;
return endTxId;
}
boolean isGetEdit() {
return isGetEdit;
}
public boolean isGetImage() {
return isGetImage;
}
boolean isPutImage() {
return isPutImage;
}
public boolean isThrottlerDisabled() {
return disableThrottler;
}
String getInfoServer() throws IOException{
if (machineName == null || remoteport == 0) {
throw new IOException ("MachineName and port undefined");
}
return NetUtils.toIpPort(machineName, remoteport);
}
private static String getParam(HttpServletRequest request, String param)
throws IOException {
String paramStr = request.getParameter(param);
if (paramStr == null) {
throw new IOException("Invalid request has no " + param + " parameter");
}
return paramStr;
}
private static long parseLongParam(HttpServletRequest request, String param)
throws IOException {
// Parse the 'txid' parameter which indicates which image is to be
// fetched.
String paramStr = getParam(request, param);
return Long.valueOf(paramStr);
}
private static boolean parseBooleanParam(HttpServletRequest request, String param)
throws IOException {
String paramStr = getParam(request, param);
return Boolean.valueOf(paramStr);
}
}
}