/** * Licensed to Cloudera, Inc. under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. Cloudera, Inc. 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 com.cloudera.flume.handlers.debug; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.Test; import com.cloudera.flume.core.EventImpl; import com.cloudera.flume.core.EventSink; import com.cloudera.flume.reporter.ReportEvent; import com.cloudera.flume.util.MockClock; import com.cloudera.util.BackoffPolicy; import com.cloudera.util.CappedExponentialBackoff; import com.cloudera.util.Clock; /** * This tests the insistent opener (it tries many times before giving up) */ public class TestInsistentOpen { /** * Test that IOD retries the correct number of times when opening a sink that * fails twice and then succeeds. */ @Test public void testInsistent() throws IOException { // TODO(henry): this test relies on real clocks, and shouldn't. See below. EventSink fail2x = mock(EventSink.Base.class); // two exceptions then some success doThrow(new IOException("mock2")).doThrow(new IOException("mock")) .doNothing().when(fail2x).open(); doReturn(new ReportEvent("stub")).when(fail2x).getReport(); // max 5s, backoff initially at 10ms // WARNING! This test relies on being able to sleep for ~10ms and be woken // up three times in a 5s period. Seems plausible! But if you are looking at // this comment, it's probably because the test is failing on a loaded // machine... BackoffPolicy bop = new CappedExponentialBackoff(10, 5000); InsistentOpenDecorator<EventSink> sink = new InsistentOpenDecorator<EventSink>( fail2x, bop); sink.open(); sink.append(new EventImpl("test".getBytes())); sink.close(); fail2x.getReport(); ReportEvent rpt = sink.getReport(); assertEquals(new Long(1), rpt .getLongMetric(InsistentOpenDecorator.A_REQUESTS)); assertEquals(new Long(3), rpt .getLongMetric(InsistentOpenDecorator.A_ATTEMPTS)); assertEquals(new Long(1), rpt .getLongMetric(InsistentOpenDecorator.A_SUCCESSES)); assertEquals(new Long(2), rpt .getLongMetric(InsistentOpenDecorator.A_RETRIES)); System.out.println(rpt.toText()); } /** * Test that an IOD tries the correct number of times to reopen a failing * sink, then gives up. */ @Test public void testInsistentRetry() { final MockClock m = new MockClock(0); EventSink failWhale = new EventSink.Base() { public ReportEvent getReport() { return new ReportEvent("failwhale-report"); } @Override public void open() throws IOException { // Forward by 100ms, should cause IOD to try eleven times then give up m.forward(100); throw new IOException("fail open"); } }; Clock.setClock(m); // max 1s, backoff initially at 10ms, with a maxSingleSleep 1000ms and a // cumulative cap of 1000ms InsistentOpenDecorator<EventSink> sink = new InsistentOpenDecorator<EventSink>( failWhale, 1000, 10, 1000); try { sink.open(); } catch (IOException e1) { ReportEvent rpt = sink.getReport(); assertEquals(new Long(1), rpt .getLongMetric(InsistentOpenDecorator.A_REQUESTS)); assertEquals(new Long(0), rpt .getLongMetric(InsistentOpenDecorator.A_SUCCESSES)); // 11 attempts - one each at 100 * x for x in [0,1,2,3,4,5,6,7,8,9,10] // Retry trigger in IOD only fails when time > max, but passes when time = // max. assertEquals(new Long(11), rpt .getLongMetric(InsistentOpenDecorator.A_ATTEMPTS)); assertEquals(new Long(11), rpt .getLongMetric(InsistentOpenDecorator.A_RETRIES)); Clock.resetDefault(); return; // success } fail("Ehr? Somehow the failwhale succeeded!"); } /** * Tests to a thread cancel that forces a InterruptedException throw. Normally * an insistentOpen will never return if the subsink's open always fails. * interrupt forces an InterruptedException which the insistent open * translates into a IOException. * * (Ideally it should propagate the InterruptedException, but I think that * change is pervasive and will wait for the next major version) */ @Test public void testInsistentOpenCancel() throws IOException, InterruptedException { // TODO(henry): this test relies on real clocks, and shouldn't. See below. EventSink fail4eva = mock(EventSink.Base.class); // two exceptions then some success doThrow(new IOException("mock")).when(fail4eva).open(); doReturn(new ReportEvent("stub")).when(fail4eva).getReport(); final CountDownLatch done = new CountDownLatch(1); // max 5s, backoff initially at 10ms BackoffPolicy bop = new CappedExponentialBackoff(10, 5000); final InsistentOpenDecorator<EventSink> sink = new InsistentOpenDecorator<EventSink>( fail4eva, bop); Thread t = new Thread() { @Override public void run() { try { sink.open(); } catch (IOException e) { // insistent translates interruptions into io exceptions done.countDown(); } } }; t.start(); Clock.sleep(1000); // let the insistent open try a few times. t.interrupt(); // signal an interruption assertTrue("Timed out", done.await(1000, TimeUnit.MILLISECONDS)); } }