From 5f79f180f6e5fa938a50c52478cdf99bc6d55512 Mon Sep 17 00:00:00 2001 From: Mahadev Konar Date: Wed, 11 Jan 2012 23:59:23 +0000 Subject: [PATCH] MAPREDUCE-3553. Add support for data returned when exceptions thrown from web service apis to be in either xml or in JSON. (Thomas Graves via mahadev) git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1230330 13f79535-47bb-0310-9956-ffa450edef68 --- hadoop-mapreduce-project/CHANGES.txt | 3 + .../v2/app/webapp/JAXBContextResolver.java | 3 +- .../v2/app/webapp/TestAMWebServicesJobs.java | 69 ++++++++++++-- .../v2/hs/webapp/JAXBContextResolver.java | 4 +- .../v2/hs/webapp/TestHsWebServicesJobs.java | 72 +++++++++++++-- .../yarn/webapp/GenericExceptionHandler.java | 27 ++---- .../yarn/webapp/RemoteExceptionData.java | 63 +++++++++++++ .../webapp/JAXBContextResolver.java | 14 +-- .../webapp/TestNMWebServicesApps.java | 91 +++++++++++++++++-- .../webapp/JAXBContextResolver.java | 4 +- .../webapp/TestRMWebServicesNodes.java | 81 +++++++++++++++-- 11 files changed, 372 insertions(+), 59 deletions(-) create mode 100644 hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/RemoteExceptionData.java diff --git a/hadoop-mapreduce-project/CHANGES.txt b/hadoop-mapreduce-project/CHANGES.txt index f100c30580..754b622f93 100644 --- a/hadoop-mapreduce-project/CHANGES.txt +++ b/hadoop-mapreduce-project/CHANGES.txt @@ -178,6 +178,9 @@ Release 0.23.1 - Unreleased Improved the earlier patch to not to JobHistoryServer repeatedly. (Anupam Seth via vinodkv) + MAPREDUCE-3553. Add support for data returned when exceptions thrown from web + service apis to be in either xml or in JSON. (Thomas Graves via mahadev) + OPTIMIZATIONS MAPREDUCE-3567. Extraneous JobConf objects in AM heap. (Vinod Kumar diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/JAXBContextResolver.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/JAXBContextResolver.java index 2c44baae2c..b0da4c2253 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/JAXBContextResolver.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/JAXBContextResolver.java @@ -49,6 +49,7 @@ import org.apache.hadoop.mapreduce.v2.app.webapp.dao.TaskCounterInfo; import org.apache.hadoop.mapreduce.v2.app.webapp.dao.TaskInfo; import org.apache.hadoop.mapreduce.v2.app.webapp.dao.TasksInfo; +import org.apache.hadoop.yarn.webapp.RemoteExceptionData; @Singleton @Provider @@ -64,7 +65,7 @@ public class JAXBContextResolver implements ContextResolver { JobCounterInfo.class, TaskCounterInfo.class, CounterGroupInfo.class, JobInfo.class, JobsInfo.class, ReduceTaskAttemptInfo.class, TaskAttemptInfo.class, TaskInfo.class, TasksInfo.class, - TaskAttemptsInfo.class, ConfEntryInfo.class}; + TaskAttemptsInfo.class, ConfEntryInfo.class, RemoteExceptionData.class}; public JAXBContextResolver() throws Exception { this.types = new HashSet(Arrays.asList(cTypes)); diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/webapp/TestAMWebServicesJobs.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/webapp/TestAMWebServicesJobs.java index 40da3bc00b..0f22b65750 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/webapp/TestAMWebServicesJobs.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/java/org/apache/hadoop/mapreduce/v2/app/webapp/TestAMWebServicesJobs.java @@ -345,6 +345,29 @@ public void testJobIdNonExist() throws JSONException, Exception { public void testJobIdInvalid() throws JSONException, Exception { WebResource r = resource(); + try { + r.path("ws").path("v1").path("mapreduce").path("jobs").path("job_foo") + .accept(MediaType.APPLICATION_JSON).get(JSONObject.class); + fail("should have thrown exception on invalid uri"); + } catch (UniformInterfaceException ue) { + ClientResponse response = ue.getResponse(); + assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus()); + assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); + JSONObject msg = response.getEntity(JSONObject.class); + JSONObject exception = msg.getJSONObject("RemoteException"); + assertEquals("incorrect number of elements", 3, exception.length()); + String message = exception.getString("message"); + String type = exception.getString("exception"); + String classname = exception.getString("javaClassName"); + verifyJobIdInvalid(message, type, classname); + } + } + + // verify the exception output default is JSON + @Test + public void testJobIdInvalidDefault() throws JSONException, Exception { + WebResource r = resource(); + try { r.path("ws").path("v1").path("mapreduce").path("jobs").path("job_foo") .get(JSONObject.class); @@ -359,15 +382,49 @@ public void testJobIdInvalid() throws JSONException, Exception { String message = exception.getString("message"); String type = exception.getString("exception"); String classname = exception.getString("javaClassName"); - WebServicesTestUtils.checkStringMatch("exception message", - "For input string: \"foo\"", message); - WebServicesTestUtils.checkStringMatch("exception type", - "NumberFormatException", type); - WebServicesTestUtils.checkStringMatch("exception classname", - "java.lang.NumberFormatException", classname); + verifyJobIdInvalid(message, type, classname); } } + // test that the exception output works in XML + @Test + public void testJobIdInvalidXML() throws JSONException, Exception { + WebResource r = resource(); + + try { + r.path("ws").path("v1").path("mapreduce").path("jobs").path("job_foo") + .accept(MediaType.APPLICATION_XML).get(JSONObject.class); + fail("should have thrown exception on invalid uri"); + } catch (UniformInterfaceException ue) { + ClientResponse response = ue.getResponse(); + assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus()); + assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType()); + String msg = response.getEntity(String.class); + System.out.println(msg); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(msg)); + Document dom = db.parse(is); + NodeList nodes = dom.getElementsByTagName("RemoteException"); + Element element = (Element) nodes.item(0); + String message = WebServicesTestUtils.getXmlString(element, "message"); + String type = WebServicesTestUtils.getXmlString(element, "exception"); + String classname = WebServicesTestUtils.getXmlString(element, + "javaClassName"); + verifyJobIdInvalid(message, type, classname); + } + } + + private void verifyJobIdInvalid(String message, String type, String classname) { + WebServicesTestUtils.checkStringMatch("exception message", + "For input string: \"foo\"", message); + WebServicesTestUtils.checkStringMatch("exception type", + "NumberFormatException", type); + WebServicesTestUtils.checkStringMatch("exception classname", + "java.lang.NumberFormatException", classname); + } + @Test public void testJobIdInvalidBogus() throws JSONException, Exception { WebResource r = resource(); diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/webapp/JAXBContextResolver.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/webapp/JAXBContextResolver.java index c24fefc129..e03d27fe0b 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/webapp/JAXBContextResolver.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/webapp/JAXBContextResolver.java @@ -49,6 +49,7 @@ import org.apache.hadoop.mapreduce.v2.hs.webapp.dao.HistoryInfo; import org.apache.hadoop.mapreduce.v2.hs.webapp.dao.JobInfo; import org.apache.hadoop.mapreduce.v2.hs.webapp.dao.JobsInfo; +import org.apache.hadoop.yarn.webapp.RemoteExceptionData; @Singleton @Provider @@ -64,7 +65,8 @@ public class JAXBContextResolver implements ContextResolver { JobTaskAttemptCounterInfo.class, TaskCounterInfo.class, JobCounterInfo.class, ReduceTaskAttemptInfo.class, TaskAttemptInfo.class, TaskAttemptsInfo.class, CounterGroupInfo.class, - TaskCounterGroupInfo.class, AMAttemptInfo.class, AMAttemptsInfo.class }; + TaskCounterGroupInfo.class, AMAttemptInfo.class, AMAttemptsInfo.class, + RemoteExceptionData.class }; public JAXBContextResolver() throws Exception { this.types = new HashSet(Arrays.asList(cTypes)); diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/java/org/apache/hadoop/mapreduce/v2/hs/webapp/TestHsWebServicesJobs.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/java/org/apache/hadoop/mapreduce/v2/hs/webapp/TestHsWebServicesJobs.java index 79c4daf5a3..24926045b1 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/java/org/apache/hadoop/mapreduce/v2/hs/webapp/TestHsWebServicesJobs.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/java/org/apache/hadoop/mapreduce/v2/hs/webapp/TestHsWebServicesJobs.java @@ -392,6 +392,31 @@ public void testJobIdNonExist() throws JSONException, Exception { public void testJobIdInvalid() throws JSONException, Exception { WebResource r = resource(); + try { + r.path("ws").path("v1").path("history").path("mapreduce").path("jobs") + .path("job_foo").accept(MediaType.APPLICATION_JSON) + .get(JSONObject.class); + fail("should have thrown exception on invalid uri"); + } catch (UniformInterfaceException ue) { + ClientResponse response = ue.getResponse(); + assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus()); + assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); + JSONObject msg = response.getEntity(JSONObject.class); + JSONObject exception = msg.getJSONObject("RemoteException"); + assertEquals("incorrect number of elements", 3, exception.length()); + String message = exception.getString("message"); + String type = exception.getString("exception"); + String classname = exception.getString("javaClassName"); + verifyJobIdInvalid(message, type, classname); + + } + } + + // verify the exception output default is JSON + @Test + public void testJobIdInvalidDefault() throws JSONException, Exception { + WebResource r = resource(); + try { r.path("ws").path("v1").path("history").path("mapreduce").path("jobs") .path("job_foo").get(JSONObject.class); @@ -406,15 +431,50 @@ public void testJobIdInvalid() throws JSONException, Exception { String message = exception.getString("message"); String type = exception.getString("exception"); String classname = exception.getString("javaClassName"); - WebServicesTestUtils.checkStringMatch("exception message", - "For input string: \"foo\"", message); - WebServicesTestUtils.checkStringMatch("exception type", - "NumberFormatException", type); - WebServicesTestUtils.checkStringMatch("exception classname", - "java.lang.NumberFormatException", classname); + verifyJobIdInvalid(message, type, classname); } } + // test that the exception output works in XML + @Test + public void testJobIdInvalidXML() throws JSONException, Exception { + WebResource r = resource(); + + try { + r.path("ws").path("v1").path("history").path("mapreduce").path("jobs") + .path("job_foo").accept(MediaType.APPLICATION_XML) + .get(JSONObject.class); + fail("should have thrown exception on invalid uri"); + } catch (UniformInterfaceException ue) { + ClientResponse response = ue.getResponse(); + assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus()); + assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType()); + String msg = response.getEntity(String.class); + System.out.println(msg); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(msg)); + Document dom = db.parse(is); + NodeList nodes = dom.getElementsByTagName("RemoteException"); + Element element = (Element) nodes.item(0); + String message = WebServicesTestUtils.getXmlString(element, "message"); + String type = WebServicesTestUtils.getXmlString(element, "exception"); + String classname = WebServicesTestUtils.getXmlString(element, + "javaClassName"); + verifyJobIdInvalid(message, type, classname); + } + } + + private void verifyJobIdInvalid(String message, String type, String classname) { + WebServicesTestUtils.checkStringMatch("exception message", + "For input string: \"foo\"", message); + WebServicesTestUtils.checkStringMatch("exception type", + "NumberFormatException", type); + WebServicesTestUtils.checkStringMatch("exception classname", + "java.lang.NumberFormatException", classname); + } + @Test public void testJobIdInvalidBogus() throws JSONException, Exception { WebResource r = resource(); diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/GenericExceptionHandler.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/GenericExceptionHandler.java index 8fc886536f..61da11d593 100644 --- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/GenericExceptionHandler.java +++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/GenericExceptionHandler.java @@ -19,12 +19,9 @@ import java.io.FileNotFoundException; import java.io.IOException; -import java.util.Map; -import java.util.TreeMap; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; @@ -33,19 +30,12 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.ipc.RemoteException; import org.apache.hadoop.security.authorize.AuthorizationException; -import org.mortbay.util.ajax.JSON; import com.google.inject.Singleton; /** - * Handle webservices jersey exceptions and create json response in the format: - * { "RemoteException" : - * { - * "exception" : , - * "javaClassName" : , - * "message" : - * } - * } + * Handle webservices jersey exceptions and create json or xml response + * with the ExceptionData. */ @Singleton @Provider @@ -100,16 +90,11 @@ public Response toResponse(Exception e) { s = Response.Status.INTERNAL_SERVER_ERROR; } - // convert to json - final Map m = new TreeMap(); - m.put("exception", e.getClass().getSimpleName()); - m.put("message", e.getMessage()); - m.put("javaClassName", e.getClass().getName()); - final Map m2 = new TreeMap(); - m2.put(RemoteException.class.getSimpleName(), m); - final String js = JSON.toString(m2); + // let jaxb handle marshalling data out in the same format requested + RemoteExceptionData exception = new RemoteExceptionData(e.getClass().getSimpleName(), + e.getMessage(), e.getClass().getName()); - return Response.status(s).type(MediaType.APPLICATION_JSON).entity(js) + return Response.status(s).entity(exception) .build(); } } diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/RemoteExceptionData.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/RemoteExceptionData.java new file mode 100644 index 0000000000..eb25b5526b --- /dev/null +++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/RemoteExceptionData.java @@ -0,0 +1,63 @@ +/** + * 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.yarn.webapp; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * Contains the exception information from an exception thrown + * by the web service REST API's. + * Fields include: + * exception - exception type + * javaClassName - java class name of the exception + * message - a detailed message explaining the exception + * + */ +@XmlRootElement(name = "RemoteException") +@XmlAccessorType(XmlAccessType.FIELD) +public class RemoteExceptionData { + + private String exception; + private String message; + private String javaClassName; + + public RemoteExceptionData() { + } + + public RemoteExceptionData(String excep, String message, String className) { + this.exception = excep; + this.message = message; + this.javaClassName = className; + } + + public String getException() { + return exception; + } + + public String getMessage() { + return message; + } + + public String getJavaClassName() { + return javaClassName; + } + +} diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/JAXBContextResolver.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/JAXBContextResolver.java index 37423bab97..46d3928c6a 100644 --- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/JAXBContextResolver.java +++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/JAXBContextResolver.java @@ -35,6 +35,7 @@ import org.apache.hadoop.yarn.server.nodemanager.webapp.dao.ContainerInfo; import org.apache.hadoop.yarn.server.nodemanager.webapp.dao.ContainersInfo; import org.apache.hadoop.yarn.server.nodemanager.webapp.dao.NodeInfo; +import org.apache.hadoop.yarn.webapp.RemoteExceptionData; @Singleton @Provider @@ -42,19 +43,20 @@ public class JAXBContextResolver implements ContextResolver { private JAXBContext context; private final Set types; - + // you have to specify all the dao classes here - private final Class[] cTypes = {AppInfo.class, AppsInfo.class, - ContainerInfo.class, ContainersInfo.class, NodeInfo.class}; - + private final Class[] cTypes = {AppInfo.class, AppsInfo.class, + ContainerInfo.class, ContainersInfo.class, NodeInfo.class, + RemoteExceptionData.class}; + public JAXBContextResolver() throws Exception { this.types = new HashSet(Arrays.asList(cTypes)); - // sets the json configuration so that the json output looks like + // sets the json configuration so that the json output looks like // the xml output this.context = new JSONJAXBContext(JSONConfiguration.natural(). rootUnwrapping(false).build(), cTypes); } - + @Override public JAXBContext getContext(Class objectType) { return (types.contains(objectType)) ? context : null; diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServicesApps.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServicesApps.java index fce38d2b58..6b6278ccba 100644 --- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServicesApps.java +++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServicesApps.java @@ -382,18 +382,91 @@ public void testNodeAppsStateInvalid() throws JSONException, Exception { String message = exception.getString("message"); String type = exception.getString("exception"); String classname = exception.getString("javaClassName"); - WebServicesTestUtils - .checkStringMatch( - "exception message", - "No enum const class org.apache.hadoop.yarn.server.nodemanager.containermanager.application.ApplicationState.FOO_STATE", - message); - WebServicesTestUtils.checkStringMatch("exception type", - "IllegalArgumentException", type); - WebServicesTestUtils.checkStringMatch("exception classname", - "java.lang.IllegalArgumentException", classname); + verifyStatInvalidException(message, type, classname); } } + // verify the exception object default format is JSON + @Test + public void testNodeAppsStateInvalidDefault() throws JSONException, Exception { + WebResource r = resource(); + Application app = new MockApp(1); + nmContext.getApplications().put(app.getAppId(), app); + addAppContainers(app); + Application app2 = new MockApp("foo", 1234, 2); + nmContext.getApplications().put(app2.getAppId(), app2); + addAppContainers(app2); + + try { + r.path("ws").path("v1").path("node").path("apps") + .queryParam("state", "FOO_STATE").get(JSONObject.class); + fail("should have thrown exception on invalid user query"); + } catch (UniformInterfaceException ue) { + ClientResponse response = ue.getResponse(); + + assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus()); + assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); + JSONObject msg = response.getEntity(JSONObject.class); + JSONObject exception = msg.getJSONObject("RemoteException"); + assertEquals("incorrect number of elements", 3, exception.length()); + String message = exception.getString("message"); + String type = exception.getString("exception"); + String classname = exception.getString("javaClassName"); + verifyStatInvalidException(message, type, classname); + } + } + + // test that the exception output also returns XML + @Test + public void testNodeAppsStateInvalidXML() throws JSONException, Exception { + WebResource r = resource(); + Application app = new MockApp(1); + nmContext.getApplications().put(app.getAppId(), app); + addAppContainers(app); + Application app2 = new MockApp("foo", 1234, 2); + nmContext.getApplications().put(app2.getAppId(), app2); + addAppContainers(app2); + + try { + r.path("ws").path("v1").path("node").path("apps") + .queryParam("state", "FOO_STATE").accept(MediaType.APPLICATION_XML) + .get(JSONObject.class); + fail("should have thrown exception on invalid user query"); + } catch (UniformInterfaceException ue) { + ClientResponse response = ue.getResponse(); + + assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus()); + assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType()); + String msg = response.getEntity(String.class); + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(msg)); + Document dom = db.parse(is); + NodeList nodes = dom.getElementsByTagName("RemoteException"); + Element element = (Element) nodes.item(0); + String message = WebServicesTestUtils.getXmlString(element, "message"); + String type = WebServicesTestUtils.getXmlString(element, "exception"); + String classname = WebServicesTestUtils.getXmlString(element, + "javaClassName"); + verifyStatInvalidException(message, type, classname); + } + } + + private void verifyStatInvalidException(String message, String type, + String classname) { + WebServicesTestUtils + .checkStringMatch( + "exception message", + "No enum const class org.apache.hadoop.yarn.server.nodemanager.containermanager.application.ApplicationState.FOO_STATE", + message); + WebServicesTestUtils.checkStringMatch("exception type", + "IllegalArgumentException", type); + WebServicesTestUtils.checkStringMatch("exception classname", + "java.lang.IllegalArgumentException", classname); + } + @Test public void testNodeSingleApps() throws JSONException, Exception { testNodeSingleAppHelper(MediaType.APPLICATION_JSON); diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/JAXBContextResolver.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/JAXBContextResolver.java index 44e6c8c3ca..edeb1bab04 100644 --- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/JAXBContextResolver.java +++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/JAXBContextResolver.java @@ -42,6 +42,7 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerInfo; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerTypeInfo; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.UserMetricsInfo; +import org.apache.hadoop.yarn.webapp.RemoteExceptionData; @Singleton @Provider @@ -55,7 +56,8 @@ public class JAXBContextResolver implements ContextResolver { CapacitySchedulerQueueInfo.class, FifoSchedulerInfo.class, SchedulerTypeInfo.class, NodeInfo.class, UserMetricsInfo.class, CapacitySchedulerInfo.class, ClusterMetricsInfo.class, - SchedulerInfo.class, AppsInfo.class, NodesInfo.class }; + SchedulerInfo.class, AppsInfo.class, NodesInfo.class, + RemoteExceptionData.class}; public JAXBContextResolver() throws Exception { this.types = new HashSet(Arrays.asList(cTypes)); diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesNodes.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesNodes.java index 2c2e9fd8cf..8a52ac153d 100644 --- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesNodes.java +++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesNodes.java @@ -19,6 +19,7 @@ package org.apache.hadoop.yarn.server.resourcemanager.webapp; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.StringReader; @@ -404,20 +405,84 @@ public void testNonexistNode() throws JSONException, Exception { String message = exception.getString("message"); String type = exception.getString("exception"); String classname = exception.getString("javaClassName"); - WebServicesTestUtils - .checkStringMatch("exception message", - "java.lang.Exception: nodeId, node_invalid:99, is not found", - message); - WebServicesTestUtils.checkStringMatch("exception type", - "NotFoundException", type); - WebServicesTestUtils.checkStringMatch("exception classname", - "org.apache.hadoop.yarn.webapp.NotFoundException", classname); + verifyNonexistNodeException(message, type, classname); } finally { rm.stop(); } } + // test that the exception output defaults to JSON + @Test + public void testNonexistNodeDefault() throws JSONException, Exception { + rm.registerNode("h1:1234", 5120); + rm.registerNode("h2:1235", 5121); + WebResource r = resource(); + try { + r.path("ws").path("v1").path("cluster").path("nodes") + .path("node_invalid:99").get(JSONObject.class); + + fail("should have thrown exception on non-existent nodeid"); + } catch (UniformInterfaceException ue) { + ClientResponse response = ue.getResponse(); + assertEquals(Status.NOT_FOUND, response.getClientResponseStatus()); + assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); + JSONObject msg = response.getEntity(JSONObject.class); + JSONObject exception = msg.getJSONObject("RemoteException"); + assertEquals("incorrect number of elements", 3, exception.length()); + String message = exception.getString("message"); + String type = exception.getString("exception"); + String classname = exception.getString("javaClassName"); + verifyNonexistNodeException(message, type, classname); + } finally { + rm.stop(); + } + } + + // test that the exception output works in XML + @Test + public void testNonexistNodeXML() throws JSONException, Exception { + rm.registerNode("h1:1234", 5120); + rm.registerNode("h2:1235", 5121); + WebResource r = resource(); + try { + r.path("ws").path("v1").path("cluster").path("nodes") + .path("node_invalid:99").accept(MediaType.APPLICATION_XML) + .get(JSONObject.class); + + fail("should have thrown exception on non-existent nodeid"); + } catch (UniformInterfaceException ue) { + ClientResponse response = ue.getResponse(); + assertEquals(Status.NOT_FOUND, response.getClientResponseStatus()); + assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType()); + String msg = response.getEntity(String.class); + System.out.println(msg); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(msg)); + Document dom = db.parse(is); + NodeList nodes = dom.getElementsByTagName("RemoteException"); + Element element = (Element) nodes.item(0); + String message = WebServicesTestUtils.getXmlString(element, "message"); + String type = WebServicesTestUtils.getXmlString(element, "exception"); + String classname = WebServicesTestUtils.getXmlString(element, + "javaClassName"); + verifyNonexistNodeException(message, type, classname); + } finally { + rm.stop(); + } + } + + private void verifyNonexistNodeException(String message, String type, String classname) { + assertTrue("exception message incorrect", + "java.lang.Exception: nodeId, node_invalid:99, is not found" + .matches(message)); + assertTrue("exception type incorrect", "NotFoundException".matches(type)); + assertTrue("exception className incorrect", + "org.apache.hadoop.yarn.webapp.NotFoundException".matches(classname)); + } + @Test public void testInvalidNode() throws JSONException, Exception { rm.registerNode("h1:1234", 5120);