001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.errorhandling;
019
020import static org.junit.jupiter.api.Assertions.assertTrue;
021import static org.junit.jupiter.api.Assertions.fail;
022
023import org.apache.hadoop.hbase.testclassification.MasterTests;
024import org.apache.hadoop.hbase.testclassification.SmallTests;
025import org.junit.jupiter.api.Tag;
026import org.junit.jupiter.api.Test;
027import org.mockito.Mockito;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031/**
032 * Test that we propagate errors through an dispatcher exactly once via different failure injection
033 * mechanisms.
034 */
035@Tag(MasterTests.TAG)
036@Tag(SmallTests.TAG)
037public class TestForeignExceptionDispatcher {
038
039  private static final Logger LOG = LoggerFactory.getLogger(TestForeignExceptionDispatcher.class);
040
041  /**
042   * Exception thrown from the test
043   */
044  final ForeignException EXTEXN =
045    new ForeignException("FORTEST", new IllegalArgumentException("FORTEST"));
046  final ForeignException EXTEXN2 =
047    new ForeignException("FORTEST2", new IllegalArgumentException("FORTEST2"));
048
049  /**
050   * Tests that a dispatcher only dispatches only the first exception, and does not propagate
051   * subsequent exceptions.
052   */
053  @Test
054  public void testErrorPropagation() {
055    ForeignExceptionListener listener1 = Mockito.mock(ForeignExceptionListener.class);
056    ForeignExceptionListener listener2 = Mockito.mock(ForeignExceptionListener.class);
057    ForeignExceptionDispatcher dispatcher = new ForeignExceptionDispatcher();
058
059    // add the listeners
060    dispatcher.addListener(listener1);
061    dispatcher.addListener(listener2);
062
063    // create an artificial error
064    dispatcher.receive(EXTEXN);
065
066    // make sure the listeners got the error
067    Mockito.verify(listener1, Mockito.times(1)).receive(EXTEXN);
068    Mockito.verify(listener2, Mockito.times(1)).receive(EXTEXN);
069
070    // make sure that we get an exception
071    try {
072      dispatcher.rethrowException();
073      fail("Monitor should have thrown an exception after getting error.");
074    } catch (ForeignException ex) {
075      assertTrue(ex.getCause() == EXTEXN.getCause(), "Got an unexpected exception:" + ex);
076      LOG.debug("Got the testing exception!");
077    }
078
079    // push another error, which should be not be passed to listeners
080    dispatcher.receive(EXTEXN2);
081    Mockito.verify(listener1, Mockito.never()).receive(EXTEXN2);
082    Mockito.verify(listener2, Mockito.never()).receive(EXTEXN2);
083  }
084
085  @Test
086  public void testSingleDispatcherWithTimer() {
087    ForeignExceptionListener listener1 = Mockito.mock(ForeignExceptionListener.class);
088    ForeignExceptionListener listener2 = Mockito.mock(ForeignExceptionListener.class);
089
090    ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher();
091
092    // add the listeners
093    monitor.addListener(listener1);
094    monitor.addListener(listener2);
095
096    TimeoutExceptionInjector timer = new TimeoutExceptionInjector(monitor, 1000);
097    timer.start();
098    timer.trigger();
099
100    assertTrue(monitor.hasException(), "Monitor didn't get timeout");
101
102    // verify that that we propagated the error
103    Mockito.verify(listener1).receive(Mockito.any());
104    Mockito.verify(listener2).receive(Mockito.any());
105  }
106
107  /**
108   * Test that the dispatcher can receive an error via the timer mechanism.
109   */
110  @Test
111  public void testAttemptTimer() {
112    ForeignExceptionListener listener1 = Mockito.mock(ForeignExceptionListener.class);
113    ForeignExceptionListener listener2 = Mockito.mock(ForeignExceptionListener.class);
114    ForeignExceptionDispatcher orchestrator = new ForeignExceptionDispatcher();
115
116    // add the listeners
117    orchestrator.addListener(listener1);
118    orchestrator.addListener(listener2);
119
120    // now create a timer and check for that error
121    TimeoutExceptionInjector timer = new TimeoutExceptionInjector(orchestrator, 1000);
122    timer.start();
123    timer.trigger();
124    // make sure that we got the timer error
125    Mockito.verify(listener1, Mockito.times(1)).receive(Mockito.any());
126    Mockito.verify(listener2, Mockito.times(1)).receive(Mockito.any());
127  }
128}