diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/AppController.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/AppController.java
index 305ec7e29a..e30e1b907d 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/AppController.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/AppController.java
@@ -323,6 +323,40 @@ public void conf() {
render(confPage());
}
+ /**
+ * Handle requests to download the job configuration.
+ */
+ public void downloadConf() {
+ try {
+ requireJob();
+ } catch (Exception e) {
+ renderText(e.getMessage());
+ return;
+ }
+ writeJobConf();
+ }
+
+ private void writeJobConf() {
+ String jobId = $(JOB_ID);
+ assert(!jobId.isEmpty());
+
+ JobId jobID = MRApps.toJobID($(JOB_ID));
+ Job job = app.context.getJob(jobID);
+ assert(job != null);
+
+ try {
+ Configuration jobConf = job.loadConfFile();
+ response().setContentType("text/xml");
+ response().setHeader("Content-Disposition",
+ "attachment; filename=" + jobId + ".xml");
+ jobConf.writeXml(writer());
+ } catch (IOException e) {
+ LOG.error("Error reading/writing job" +
+ " conf file for job: " + jobId, e);
+ renderText(e.getMessage());
+ }
+ }
+
/**
* Render a BAD_REQUEST error.
* @param s the error message to include.
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/ConfBlock.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/ConfBlock.java
index 4cb79bf37e..532c2bd4fa 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/ConfBlock.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/ConfBlock.java
@@ -70,7 +70,7 @@ public class ConfBlock extends HtmlBlock {
try {
ConfInfo info = new ConfInfo(job);
- html.div().h3(confPath.toString())._();
+ html.div().a("/jobhistory/downloadconf/" + jid, confPath.toString());
TBODY
> tbody = html.
// Tasks table
table("#conf").
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/webapp/TestAppController.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/webapp/TestAppController.java
index 92786e38e0..3f685b0714 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/webapp/TestAppController.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/webapp/TestAppController.java
@@ -24,6 +24,7 @@
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
import org.apache.hadoop.mapreduce.JobACL;
import org.apache.hadoop.mapreduce.v2.api.records.JobId;
import org.apache.hadoop.mapreduce.v2.api.records.TaskId;
@@ -59,6 +60,8 @@ public void setUp() throws IOException {
Task task = mock(Task.class);
when(job.getTask(any(TaskId.class))).thenReturn(task);
+ when(job.loadConfFile()).thenReturn(new Configuration());
+ when(job.getConfFile()).thenReturn(new Path("/"));
JobId jobID = MRApps.toJobID("job_01_01");
when(context.getJob(jobID)).thenReturn(job);
@@ -265,6 +268,17 @@ public void testConfiguration() {
assertEquals(JobConfPage.class, appController.getClazz());
}
+ /**
+ * Test downloadConf request handling.
+ */
+ @Test
+ public void testDownloadConfiguration() {
+ appController.downloadConf();
+ String jobConfXml = appController.getData();
+ assertTrue("Error downloading the job configuration file.",
+ !jobConfXml.contains("Error"));
+ }
+
/**
* Test method 'conf'. Should set AttemptsPage class for rendering or print information about error
*/
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/webapp/HsWebApp.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/webapp/HsWebApp.java
index ebc6d46c1f..d130910267 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/webapp/HsWebApp.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/webapp/HsWebApp.java
@@ -50,6 +50,8 @@ public void setup() {
route("/app", HsController.class);
route(pajoin("/job", JOB_ID), HsController.class, "job");
route(pajoin("/conf", JOB_ID), HsController.class, "conf");
+ routeWithoutDefaultView(pajoin("/downloadconf", JOB_ID),
+ HsController.class, "downloadConf");
route(pajoin("/jobcounters", JOB_ID), HsController.class, "jobCounters");
route(pajoin("/singlejobcounter",JOB_ID, COUNTER_GROUP, COUNTER_NAME),
HsController.class, "singleJobCounter");
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Router.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Router.java
index c46b50ea02..f2eca04176 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Router.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Router.java
@@ -74,17 +74,32 @@ static class Dest {
final TreeMap routes = Maps.newTreeMap(); // path->dest
+ synchronized Dest add(WebApp.HTTP httpMethod, String path,
+ Class extends Controller> cls,
+ String action, List names){
+ return addWithOptionalDefaultView(
+ httpMethod, path, cls, action, names, true);
+ }
+
+ synchronized Dest addWithoutDefaultView(WebApp.HTTP httpMethod,
+ String path, Class extends Controller> cls, String action,
+ List names){
+ return addWithOptionalDefaultView(httpMethod, path, cls, action,
+ names, false);
+ }
/**
* Add a route to the router.
* e.g., add(GET, "/foo/show", FooController.class, "show", [name...]);
* The name list is from /foo/show/:name/...
*/
- synchronized Dest add(WebApp.HTTP httpMethod, String path,
- Class extends Controller> cls,
- String action, List names) {
+ synchronized Dest addWithOptionalDefaultView(WebApp.HTTP httpMethod,
+ String path, Class extends Controller> cls,
+ String action, List names, boolean defaultViewNeeded) {
LOG.debug("adding {}({})->{}#{}", new Object[]{path, names, cls, action});
Dest dest = addController(httpMethod, path, cls, action, names);
- addDefaultView(dest);
+ if (defaultViewNeeded) {
+ addDefaultView(dest);
+ }
return dest;
}
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApp.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApp.java
index fe800f0852..de6a52bda5 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApp.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApp.java
@@ -210,6 +210,19 @@ public void route(HTTP method, String pathSpec,
res.subList(R_PARAMS, res.size()));
}
+ /**
+ * Setup of a webapp serving route without default views added to the page.
+ * @param pathSpec the path spec in the form of /controller/action/:args etc.
+ * @param cls the controller class
+ * @param action the controller method
+ */
+ public void routeWithoutDefaultView(String pathSpec,
+ Class extends Controller> cls, String action) {
+ List res = parseRoute(pathSpec);
+ router.addWithoutDefaultView(HTTP.GET, res.get(R_PATH), cls, action,
+ res.subList(R_PARAMS, res.size()));
+ }
+
public void route(String pathSpec, Class extends Controller> cls,
String action) {
route(HTTP.GET, pathSpec, cls, action);