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.coprocessor;
019
020import static org.junit.Assert.fail;
021
022import java.io.IOException;
023import org.apache.hadoop.conf.Configuration;
024import org.apache.hadoop.hbase.CoprocessorEnvironment;
025import org.apache.hadoop.hbase.HBaseClassTestRule;
026import org.apache.hadoop.hbase.HBaseTestingUtility;
027import org.apache.hadoop.hbase.HConstants;
028import org.apache.hadoop.hbase.MiniHBaseCluster;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.Waiter.Predicate;
031import org.apache.hadoop.hbase.client.Durability;
032import org.apache.hadoop.hbase.client.Put;
033import org.apache.hadoop.hbase.client.Table;
034import org.apache.hadoop.hbase.regionserver.HRegionServer;
035import org.apache.hadoop.hbase.testclassification.CoprocessorTests;
036import org.apache.hadoop.hbase.testclassification.MediumTests;
037import org.apache.hadoop.hbase.util.Bytes;
038import org.apache.hadoop.hbase.wal.WALEdit;
039import org.junit.Assert;
040import org.junit.ClassRule;
041import org.junit.Test;
042import org.junit.experimental.categories.Category;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046/**
047 * Tests unhandled exceptions thrown by coprocessors running on a regionserver..
048 * Expected result is that the regionserver will abort with an informative
049 * error message describing the set of its loaded coprocessors for crash
050 * diagnosis. (HBASE-4014).
051 */
052@Category({CoprocessorTests.class, MediumTests.class})
053public class TestRegionServerCoprocessorExceptionWithAbort {
054
055  @ClassRule
056  public static final HBaseClassTestRule CLASS_RULE =
057      HBaseClassTestRule.forClass(TestRegionServerCoprocessorExceptionWithAbort.class);
058
059  private static final Logger LOG = LoggerFactory.getLogger(
060    TestRegionServerCoprocessorExceptionWithAbort.class);
061  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
062  private static final TableName TABLE_NAME = TableName.valueOf("observed_table");
063
064  @Test
065  public void testExceptionDuringInitialization() throws Exception {
066    Configuration conf = TEST_UTIL.getConfiguration();
067    conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 2);  // Let's fail fast.
068    conf.setBoolean(CoprocessorHost.ABORT_ON_ERROR_KEY, true);
069    conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, "");
070    TEST_UTIL.startMiniCluster(2);
071    try {
072      MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
073      // Trigger one regionserver to fail as if it came up with a coprocessor
074      // that fails during initialization
075      final HRegionServer regionServer = cluster.getRegionServer(0);
076      conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
077        FailedInitializationObserver.class.getName());
078      regionServer.getRegionServerCoprocessorHost().loadSystemCoprocessors(conf,
079        CoprocessorHost.REGION_COPROCESSOR_CONF_KEY);
080      TEST_UTIL.waitFor(10000, 1000, new Predicate<Exception>() {
081        @Override
082        public boolean evaluate() throws Exception {
083          return regionServer.isAborted();
084        }
085      });
086    } finally {
087      TEST_UTIL.shutdownMiniCluster();
088    }
089  }
090
091  @Test
092  public void testExceptionFromCoprocessorDuringPut() throws Exception {
093    // set configure to indicate which cp should be loaded
094    Configuration conf = TEST_UTIL.getConfiguration();
095    conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 2);  // Let's fail fast.
096    conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, BuggyRegionObserver.class.getName());
097    conf.setBoolean(CoprocessorHost.ABORT_ON_ERROR_KEY, true);
098    TEST_UTIL.startMiniCluster(2);
099    try {
100      // When we try to write to TEST_TABLE, the buggy coprocessor will
101      // cause a NullPointerException, which will cause the regionserver (which
102      // hosts the region we attempted to write to) to abort.
103      final byte[] TEST_FAMILY = Bytes.toBytes("aaa");
104
105      Table table = TEST_UTIL.createMultiRegionTable(TABLE_NAME, TEST_FAMILY);
106      TEST_UTIL.waitUntilAllRegionsAssigned(TABLE_NAME);
107
108      // Note which regionServer will abort (after put is attempted).
109      final HRegionServer regionServer = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME);
110
111      try {
112        final byte[] ROW = Bytes.toBytes("aaa");
113        Put put = new Put(ROW);
114        put.addColumn(TEST_FAMILY, ROW, ROW);
115        table.put(put);
116      } catch (IOException e) {
117        // The region server is going to be aborted.
118        // We may get an exception if we retry,
119        // which is not guaranteed.
120      }
121
122      // Wait 10 seconds for the regionserver to abort: expected result is that
123      // it will abort.
124      boolean aborted = false;
125      for (int i = 0; i < 10; i++) {
126        aborted = regionServer.isAborted();
127        if (aborted) {
128          break;
129        }
130        try {
131          Thread.sleep(1000);
132        } catch (InterruptedException e) {
133          fail("InterruptedException while waiting for regionserver " +
134            "zk node to be deleted.");
135        }
136      }
137      Assert.assertTrue("The region server should have aborted", aborted);
138      table.close();
139    } finally {
140      TEST_UTIL.shutdownMiniCluster();
141    }
142  }
143
144  public static class FailedInitializationObserver implements RegionServerCoprocessor {
145    @SuppressWarnings("null")
146    @Override
147    public void start(CoprocessorEnvironment e) throws IOException {
148      // Trigger a NPE to fail the coprocessor
149      Integer i = null;
150      i = i + 1;
151    }
152  }
153
154  public static class BuggyRegionObserver extends SimpleRegionObserver {
155    @SuppressWarnings("null")
156    @Override
157    public void prePut(final ObserverContext<RegionCoprocessorEnvironment> c,
158                       final Put put, final WALEdit edit,
159                       final Durability durability) {
160      String tableName =
161          c.getEnvironment().getRegion().getRegionInfo().getTable().getNameAsString();
162      if (tableName.equals("observed_table")) {
163        // Trigger a NPE to fail the coprocessor
164        Integer i = null;
165        i = i + 1;
166      }
167    }
168  }
169}