diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/main/java/org/apache/hadoop/yarn/client/api/impl/YarnClientImpl.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/main/java/org/apache/hadoop/yarn/client/api/impl/YarnClientImpl.java index e406862..c5e7330 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/main/java/org/apache/hadoop/yarn/client/api/impl/YarnClientImpl.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/main/java/org/apache/hadoop/yarn/client/api/impl/YarnClientImpl.java @@ -376,6 +376,21 @@ private void addTimelineDelegationToken( return null; } throw e; + } catch (NoClassDefFoundError e) { + if (timelineServiceBestEffort) { + LOG.warn("Ignore a NoClassDefFoundError when attempting to get" + + " delegation token from the timeline server: " + e.getMessage()); + return null; + } + NoClassDefFoundError wrappedError = new NoClassDefFoundError( + e.getMessage() + ". It appears that the timeline client " + + "failed to initiate because an incompatible dependency " + + "in classpath. If timeline service is optional to this " + + "client, try to work around by setting " + + YarnConfiguration.TIMELINE_SERVICE_CLIENT_BEST_EFFORT + + " to true."); + wrappedError.setStackTrace(e.getStackTrace()); + throw wrappedError; } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestYarnClient.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestYarnClient.java index 240f31c..970a78a 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestYarnClient.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/api/impl/TestYarnClient.java @@ -156,26 +156,6 @@ public void testClientStop() { } @Test - public void testStartWithTimelineV15Failure() throws Exception{ - Configuration conf = new Configuration(); - conf.setBoolean(YarnConfiguration.TIMELINE_SERVICE_ENABLED, true); - conf.setFloat(YarnConfiguration.TIMELINE_SERVICE_VERSION, 1.5f); - conf.setBoolean(YarnConfiguration.TIMELINE_SERVICE_CLIENT_BEST_EFFORT, - true); - YarnClient client = YarnClient.createYarnClient(); - if(client instanceof YarnClientImpl) { - YarnClientImpl impl = (YarnClientImpl) client; - YarnClientImpl spyClient = spy(impl); - when(spyClient.createTimelineClient()).thenThrow( - new IOException("ATS v1.5 client initialization failed. ")); - spyClient.init(conf); - spyClient.start(); - spyClient.getTimelineDelegationToken(); - spyClient.stop(); - } - } - - @Test public void testStartWithTimelineV15() throws Exception { Configuration conf = new Configuration(); conf.setBoolean(YarnConfiguration.TIMELINE_SERVICE_ENABLED, true); @@ -186,6 +166,93 @@ public void testStartWithTimelineV15() throws Exception { client.stop(); } + @Test + public void testStartTimelineClientWithErrors() + throws Exception { + // When timeline best effort is false, a NoClassDefFoundError + // should be wrapped with an informative error message + testCreateTimelineClientWithError( + 1.5f, + false, + new NoClassDefFoundError("Mock a NoClassDefFoundError"), + new CreateTimelineClientErrorVerifier(1) { + @Override + public void verifyError(Throwable e) { + Assert.assertTrue(e instanceof NoClassDefFoundError); + Assert.assertTrue(e.getMessage() != null && + e.getMessage().contains( + YarnConfiguration.TIMELINE_SERVICE_CLIENT_BEST_EFFORT)); + } + }); + + // When best effort is true, NoClassDefFoundError is not a fatal error + // for yarn client + testCreateTimelineClientWithError( + 1.5f, + true, + new NoClassDefFoundError("Mock a NoClassDefFoundError"), + new CreateTimelineClientErrorVerifier(0) { + @Override public void verifyError(Throwable e) { + Assert.fail("NoClassDefFoundError while creating timeline client" + + "should be tolerated when best effort is true"); + } + } + ); + + // Do not swallow any error other than NoClassDefFoundError + testCreateTimelineClientWithError( + 1.5f, + false, + new OutOfMemoryError("Mock an OOM"), + new CreateTimelineClientErrorVerifier(1) { + @Override + public void verifyError(Throwable e) { + Assert.assertTrue(e instanceof OutOfMemoryError); + } + } + ); + + // Best effort doesn't attempt to bypass any other type of errors + testCreateTimelineClientWithError( + 1.5f, + true, + new OutOfMemoryError("Mock an OOM"), + new CreateTimelineClientErrorVerifier(1) { + @Override + public void verifyError(Throwable e) { + Assert.assertTrue(e instanceof OutOfMemoryError); + } + } + ); + + // Exceptions are not swallowed + testCreateTimelineClientWithError( + 1.5f, + false, + new IOException("ATS v1.5 client initialization failed."), + new CreateTimelineClientErrorVerifier(1) { + @Override + public void verifyError(Throwable e) { + Assert.assertTrue(e instanceof IOException); + } + } + ); + + // Enable best effort to bypass an IOException + testCreateTimelineClientWithError( + 1.5f, + true, + new IOException("ATS v1.5 client initialization failed."), + new CreateTimelineClientErrorVerifier(0) { + @Override + public void verifyError(Throwable e) { + Assert.fail("IOException while creating timeline client" + + "should be tolerated when best effort is true"); + } + } + ); + } + @SuppressWarnings("deprecation") @Test (timeout = 30000) public void testSubmitApplication() throws Exception { @@ -1680,4 +1747,51 @@ public void testSignalContainer() throws Exception { Assert.assertEquals(containerId, request.getContainerId()); Assert.assertEquals(command, request.getCommand()); } + + private void testCreateTimelineClientWithError( + float timelineVersion, + boolean bestEffort, + Throwable mockErr, + CreateTimelineClientErrorVerifier errVerifier) throws Exception { + Configuration conf = new Configuration(); + conf.setBoolean(YarnConfiguration.TIMELINE_SERVICE_ENABLED, true); + conf.setFloat(YarnConfiguration.TIMELINE_SERVICE_VERSION, timelineVersion); + conf.setBoolean(YarnConfiguration.TIMELINE_SERVICE_CLIENT_BEST_EFFORT, + bestEffort); + YarnClient client = YarnClient.createYarnClient(); + if (client instanceof YarnClientImpl) { + YarnClientImpl impl = (YarnClientImpl) client; + YarnClientImpl spyClient = spy(impl); + when(spyClient.createTimelineClient()).thenThrow(mockErr); + CreateTimelineClientErrorVerifier verifier = spy(errVerifier); + spyClient.init(conf); + spyClient.start(); + try { + spyClient.getTimelineDelegationToken(); + } catch (Throwable e) { + verifier.verifyError(e); + } finally { + // Make sure the verifier runs with expected times + // This is required because in case throwable is swallowed + // and verifyError never gets the chance to run + verify(verifier, times(verifier.getExpectedTimes())) + .verifyError(any(Throwable.class)); + spyClient.stop(); + } + } + } + + private abstract class CreateTimelineClientErrorVerifier { + // Verify verifyError gets executed with expected times + private int times = 0; + protected CreateTimelineClientErrorVerifier(int times) { + this.times = times; + } + public int getExpectedTimes() { + return this.times; + } + // Verification a throwable is in desired state + // E.g verify type and error message + public abstract void verifyError(Throwable e); + } }