HDFS-4373. Merging change r1502328 from trunk to branch-2.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/branch-2@1502423 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
5039a956ff
commit
9f5667fe27
|
@ -96,6 +96,8 @@ Release 2.1.0-beta - 2013-07-02
|
||||||
|
|
||||||
HDFS-4372. Track NameNode startup progress. (cnauroth)
|
HDFS-4372. Track NameNode startup progress. (cnauroth)
|
||||||
|
|
||||||
|
HDFS-4373. Add HTTP API for querying NameNode startup progress. (cnauroth)
|
||||||
|
|
||||||
IMPROVEMENTS
|
IMPROVEMENTS
|
||||||
|
|
||||||
HDFS-4461. DirectoryScanner: volume path prefix takes up memory for every
|
HDFS-4461. DirectoryScanner: volume path prefix takes up memory for every
|
||||||
|
|
|
@ -194,6 +194,8 @@ public class NameNodeHttpServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setupServlets(HttpServer httpServer, Configuration conf) {
|
private static void setupServlets(HttpServer httpServer, Configuration conf) {
|
||||||
|
httpServer.addInternalServlet("startupProgress",
|
||||||
|
StartupProgressServlet.PATH_SPEC, StartupProgressServlet.class);
|
||||||
httpServer.addInternalServlet("getDelegationToken",
|
httpServer.addInternalServlet("getDelegationToken",
|
||||||
GetDelegationTokenServlet.PATH_SPEC,
|
GetDelegationTokenServlet.PATH_SPEC,
|
||||||
GetDelegationTokenServlet.class, true);
|
GetDelegationTokenServlet.class, true);
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
/**
|
||||||
|
* 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.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hdfs.server.namenode.startupprogress.Phase;
|
||||||
|
import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress;
|
||||||
|
import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgressView;
|
||||||
|
import org.apache.hadoop.hdfs.server.namenode.startupprogress.Step;
|
||||||
|
import org.apache.hadoop.hdfs.server.namenode.startupprogress.StepType;
|
||||||
|
import org.apache.hadoop.io.IOUtils;
|
||||||
|
import org.codehaus.jackson.JsonFactory;
|
||||||
|
import org.codehaus.jackson.JsonGenerator;
|
||||||
|
|
||||||
|
import org.apache.hadoop.classification.InterfaceAudience;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Servlet that provides a JSON representation of the namenode's current startup
|
||||||
|
* progress.
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class StartupProgressServlet extends DfsServlet {
|
||||||
|
|
||||||
|
private static final String COUNT = "count";
|
||||||
|
private static final String ELAPSED_TIME = "elapsedTime";
|
||||||
|
private static final String FILE = "file";
|
||||||
|
private static final String NAME = "name";
|
||||||
|
private static final String PERCENT_COMPLETE = "percentComplete";
|
||||||
|
private static final String PHASES = "phases";
|
||||||
|
private static final String SIZE = "size";
|
||||||
|
private static final String STATUS = "status";
|
||||||
|
private static final String STEPS = "steps";
|
||||||
|
private static final String TOTAL = "total";
|
||||||
|
|
||||||
|
public static final String PATH_SPEC = "/startupProgress";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||||
|
throws IOException {
|
||||||
|
resp.setContentType("application/json; charset=UTF-8");
|
||||||
|
StartupProgress prog = NameNodeHttpServer.getStartupProgressFromContext(
|
||||||
|
getServletContext());
|
||||||
|
StartupProgressView view = prog.createView();
|
||||||
|
JsonGenerator json = new JsonFactory().createJsonGenerator(resp.getWriter());
|
||||||
|
try {
|
||||||
|
json.writeStartObject();
|
||||||
|
json.writeNumberField(ELAPSED_TIME, view.getElapsedTime());
|
||||||
|
json.writeNumberField(PERCENT_COMPLETE, view.getPercentComplete());
|
||||||
|
json.writeArrayFieldStart(PHASES);
|
||||||
|
|
||||||
|
for (Phase phase: view.getPhases()) {
|
||||||
|
json.writeStartObject();
|
||||||
|
json.writeStringField(NAME, phase.getName());
|
||||||
|
json.writeStringField(STATUS, view.getStatus(phase).toString());
|
||||||
|
json.writeNumberField(PERCENT_COMPLETE, view.getPercentComplete(phase));
|
||||||
|
json.writeNumberField(ELAPSED_TIME, view.getElapsedTime(phase));
|
||||||
|
writeStringFieldIfNotNull(json, FILE, view.getFile(phase));
|
||||||
|
writeNumberFieldIfDefined(json, SIZE, view.getSize(phase));
|
||||||
|
json.writeArrayFieldStart(STEPS);
|
||||||
|
|
||||||
|
for (Step step: view.getSteps(phase)) {
|
||||||
|
json.writeStartObject();
|
||||||
|
StepType type = step.getType();
|
||||||
|
String name = type != null ? type.getName() : null;
|
||||||
|
writeStringFieldIfNotNull(json, NAME, name);
|
||||||
|
json.writeNumberField(COUNT, view.getCount(phase, step));
|
||||||
|
writeStringFieldIfNotNull(json, FILE, step.getFile());
|
||||||
|
writeNumberFieldIfDefined(json, SIZE, step.getSize());
|
||||||
|
json.writeNumberField(TOTAL, view.getTotal(phase, step));
|
||||||
|
json.writeNumberField(PERCENT_COMPLETE, view.getPercentComplete(phase,
|
||||||
|
step));
|
||||||
|
json.writeNumberField(ELAPSED_TIME, view.getElapsedTime(phase, step));
|
||||||
|
json.writeEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
json.writeEndArray();
|
||||||
|
json.writeEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
json.writeEndArray();
|
||||||
|
json.writeEndObject();
|
||||||
|
} finally {
|
||||||
|
IOUtils.cleanup(LOG, json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a JSON number field only if the value is defined.
|
||||||
|
*
|
||||||
|
* @param json JsonGenerator to receive output
|
||||||
|
* @param key String key to put
|
||||||
|
* @param value long value to put
|
||||||
|
* @throws IOException if there is an I/O error
|
||||||
|
*/
|
||||||
|
private static void writeNumberFieldIfDefined(JsonGenerator json, String key,
|
||||||
|
long value) throws IOException {
|
||||||
|
if (value != Long.MIN_VALUE) {
|
||||||
|
json.writeNumberField(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a JSON string field only if the value is non-null.
|
||||||
|
*
|
||||||
|
* @param json JsonGenerator to receive output
|
||||||
|
* @param key String key to put
|
||||||
|
* @param value String value to put
|
||||||
|
* @throws IOException if there is an I/O error
|
||||||
|
*/
|
||||||
|
private static void writeStringFieldIfNotNull(JsonGenerator json, String key,
|
||||||
|
String value) throws IOException {
|
||||||
|
if (value != null) {
|
||||||
|
json.writeStringField(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,247 @@
|
||||||
|
/**
|
||||||
|
* 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 static org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgressTestHelper.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mortbay.util.ajax.JSON;
|
||||||
|
|
||||||
|
public class TestStartupProgressServlet {
|
||||||
|
|
||||||
|
private HttpServletRequest req;
|
||||||
|
private HttpServletResponse resp;
|
||||||
|
private ByteArrayOutputStream respOut;
|
||||||
|
private StartupProgress startupProgress;
|
||||||
|
private StartupProgressServlet servlet;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
startupProgress = new StartupProgress();
|
||||||
|
ServletContext context = mock(ServletContext.class);
|
||||||
|
when(context.getAttribute(NameNodeHttpServer.STARTUP_PROGRESS_ATTRIBUTE_KEY))
|
||||||
|
.thenReturn(startupProgress);
|
||||||
|
servlet = mock(StartupProgressServlet.class);
|
||||||
|
when(servlet.getServletContext()).thenReturn(context);
|
||||||
|
doCallRealMethod().when(servlet).doGet(any(HttpServletRequest.class),
|
||||||
|
any(HttpServletResponse.class));
|
||||||
|
req = mock(HttpServletRequest.class);
|
||||||
|
respOut = new ByteArrayOutputStream();
|
||||||
|
PrintWriter writer = new PrintWriter(respOut);
|
||||||
|
resp = mock(HttpServletResponse.class);
|
||||||
|
when(resp.getWriter()).thenReturn(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitialState() throws Exception {
|
||||||
|
String respBody = doGetAndReturnResponseBody();
|
||||||
|
assertNotNull(respBody);
|
||||||
|
|
||||||
|
Map<String, Object> expected = ImmutableMap.<String, Object>builder()
|
||||||
|
.put("percentComplete", 0.0f)
|
||||||
|
.put("phases", Arrays.<Object>asList(
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "LoadingFsImage")
|
||||||
|
.put("status", "PENDING")
|
||||||
|
.put("percentComplete", 0.0f)
|
||||||
|
.put("steps", Collections.emptyList())
|
||||||
|
.build(),
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "LoadingEdits")
|
||||||
|
.put("status", "PENDING")
|
||||||
|
.put("percentComplete", 0.0f)
|
||||||
|
.put("steps", Collections.emptyList())
|
||||||
|
.build(),
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "SavingCheckpoint")
|
||||||
|
.put("status", "PENDING")
|
||||||
|
.put("percentComplete", 0.0f)
|
||||||
|
.put("steps", Collections.emptyList())
|
||||||
|
.build(),
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "SafeMode")
|
||||||
|
.put("status", "PENDING")
|
||||||
|
.put("percentComplete", 0.0f)
|
||||||
|
.put("steps", Collections.emptyList())
|
||||||
|
.build()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertEquals(JSON.toString(expected), filterJson(respBody));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunningState() throws Exception {
|
||||||
|
setStartupProgressForRunningState(startupProgress);
|
||||||
|
String respBody = doGetAndReturnResponseBody();
|
||||||
|
assertNotNull(respBody);
|
||||||
|
|
||||||
|
Map<String, Object> expected = ImmutableMap.<String, Object>builder()
|
||||||
|
.put("percentComplete", 0.375f)
|
||||||
|
.put("phases", Arrays.<Object>asList(
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "LoadingFsImage")
|
||||||
|
.put("status", "COMPLETE")
|
||||||
|
.put("percentComplete", 1.0f)
|
||||||
|
.put("steps", Collections.<Object>singletonList(
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "Inodes")
|
||||||
|
.put("count", 100L)
|
||||||
|
.put("total", 100L)
|
||||||
|
.put("percentComplete", 1.0f)
|
||||||
|
.build()
|
||||||
|
))
|
||||||
|
.build(),
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "LoadingEdits")
|
||||||
|
.put("status", "RUNNING")
|
||||||
|
.put("percentComplete", 0.5f)
|
||||||
|
.put("steps", Collections.<Object>singletonList(
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("count", 100L)
|
||||||
|
.put("file", "file")
|
||||||
|
.put("size", 1000L)
|
||||||
|
.put("total", 200L)
|
||||||
|
.put("percentComplete", 0.5f)
|
||||||
|
.build()
|
||||||
|
))
|
||||||
|
.build(),
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "SavingCheckpoint")
|
||||||
|
.put("status", "PENDING")
|
||||||
|
.put("percentComplete", 0.0f)
|
||||||
|
.put("steps", Collections.emptyList())
|
||||||
|
.build(),
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "SafeMode")
|
||||||
|
.put("status", "PENDING")
|
||||||
|
.put("percentComplete", 0.0f)
|
||||||
|
.put("steps", Collections.emptyList())
|
||||||
|
.build()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertEquals(JSON.toString(expected), filterJson(respBody));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFinalState() throws Exception {
|
||||||
|
setStartupProgressForFinalState(startupProgress);
|
||||||
|
String respBody = doGetAndReturnResponseBody();
|
||||||
|
assertNotNull(respBody);
|
||||||
|
|
||||||
|
Map<String, Object> expected = ImmutableMap.<String, Object>builder()
|
||||||
|
.put("percentComplete", 1.0f)
|
||||||
|
.put("phases", Arrays.<Object>asList(
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "LoadingFsImage")
|
||||||
|
.put("status", "COMPLETE")
|
||||||
|
.put("percentComplete", 1.0f)
|
||||||
|
.put("steps", Collections.<Object>singletonList(
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "Inodes")
|
||||||
|
.put("count", 100L)
|
||||||
|
.put("total", 100L)
|
||||||
|
.put("percentComplete", 1.0f)
|
||||||
|
.build()
|
||||||
|
))
|
||||||
|
.build(),
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "LoadingEdits")
|
||||||
|
.put("status", "COMPLETE")
|
||||||
|
.put("percentComplete", 1.0f)
|
||||||
|
.put("steps", Collections.<Object>singletonList(
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("count", 200L)
|
||||||
|
.put("file", "file")
|
||||||
|
.put("size", 1000L)
|
||||||
|
.put("total", 200L)
|
||||||
|
.put("percentComplete", 1.0f)
|
||||||
|
.build()
|
||||||
|
))
|
||||||
|
.build(),
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "SavingCheckpoint")
|
||||||
|
.put("status", "COMPLETE")
|
||||||
|
.put("percentComplete", 1.0f)
|
||||||
|
.put("steps", Collections.<Object>singletonList(
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "Inodes")
|
||||||
|
.put("count", 300L)
|
||||||
|
.put("total", 300L)
|
||||||
|
.put("percentComplete", 1.0f)
|
||||||
|
.build()
|
||||||
|
))
|
||||||
|
.build(),
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "SafeMode")
|
||||||
|
.put("status", "COMPLETE")
|
||||||
|
.put("percentComplete", 1.0f)
|
||||||
|
.put("steps", Collections.<Object>singletonList(
|
||||||
|
ImmutableMap.<String, Object>builder()
|
||||||
|
.put("name", "AwaitingReportedBlocks")
|
||||||
|
.put("count", 400L)
|
||||||
|
.put("total", 400L)
|
||||||
|
.put("percentComplete", 1.0f)
|
||||||
|
.build()
|
||||||
|
))
|
||||||
|
.build()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertEquals(JSON.toString(expected), filterJson(respBody));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls doGet on the servlet, captures the response body as a string, and
|
||||||
|
* returns it to the caller.
|
||||||
|
*
|
||||||
|
* @return String response body
|
||||||
|
* @throws IOException thrown if there is an I/O error
|
||||||
|
*/
|
||||||
|
private String doGetAndReturnResponseBody() throws IOException {
|
||||||
|
servlet.doGet(req, resp);
|
||||||
|
return new String(respOut.toByteArray(), "UTF-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the given JSON response body, removing elements that would impede
|
||||||
|
* testing. Specifically, it removes elapsedTime fields, because we cannot
|
||||||
|
* predict the exact values.
|
||||||
|
*
|
||||||
|
* @param str String to filter
|
||||||
|
* @return String filtered value
|
||||||
|
*/
|
||||||
|
private String filterJson(String str) {
|
||||||
|
return str.replaceAll("\"elapsedTime\":\\d+\\,", "")
|
||||||
|
.replaceAll("\\,\"elapsedTime\":\\d+", "");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue