1   /*
2    * Copyright 2011 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.coprocessor;
22  
23  import java.io.IOException;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.hadoop.conf.Configuration;
28  import org.apache.hadoop.hbase.*;
29  import org.apache.hadoop.hbase.client.HTable;
30  import org.apache.hadoop.hbase.client.Put;
31  import org.apache.hadoop.hbase.regionserver.HRegionServer;
32  import org.apache.hadoop.hbase.util.Bytes;
33  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
34  import org.apache.hadoop.hbase.zookeeper.ZooKeeperListener;
35  import org.apache.hadoop.hbase.zookeeper.ZooKeeperNodeTracker;
36  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
37  import org.junit.AfterClass;
38  import org.junit.BeforeClass;
39  import org.junit.Test;
40  import org.junit.experimental.categories.Category;
41  
42  import static org.junit.Assert.*;
43  
44  /**
45   * Tests unhandled exceptions thrown by coprocessors running on a regionserver..
46   * Expected result is that the regionserver will abort with an informative
47   * error message describing the set of its loaded coprocessors for crash
48   * diagnosis. (HBASE-4014).
49   */
50  @Category(MediumTests.class)
51  public class TestRegionServerCoprocessorExceptionWithAbort {
52    static final Log LOG = LogFactory.getLog(TestRegionObserverInterface.class);
53  
54    private class zkwAbortable implements Abortable {
55      @Override
56      public void abort(String why, Throwable e) {
57        throw new RuntimeException("Fatal ZK rs tracker error, why=", e);
58      }
59      @Override
60      public boolean isAborted() {
61        return false;
62      }
63    };
64  
65    private class RSTracker extends ZooKeeperNodeTracker {
66      public boolean regionZKNodeWasDeleted = false;
67      public String rsNode;
68      private Thread mainThread;
69  
70      public RSTracker(ZooKeeperWatcher zkw, String rsNode, Thread mainThread) {
71        super(zkw, rsNode, new zkwAbortable());
72        this.rsNode = rsNode;
73        this.mainThread = mainThread;
74      }
75  
76      @Override
77      public synchronized void nodeDeleted(String path) {
78        if (path.equals(rsNode)) {
79          regionZKNodeWasDeleted = true;
80          mainThread.interrupt();
81        }
82      }
83    }
84    private static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
85    static final int timeout = 30000;
86  
87    @BeforeClass
88    public static void setupBeforeClass() throws Exception {
89      // set configure to indicate which cp should be loaded
90      Configuration conf = TEST_UTIL.getConfiguration();
91      conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
92          BuggyRegionObserver.class.getName());
93      conf.set("hbase.coprocessor.abortonerror", "true");
94      TEST_UTIL.startMiniCluster(2);
95    }
96  
97    @AfterClass
98    public static void teardownAfterClass() throws Exception {
99      TEST_UTIL.shutdownMiniCluster();
100   }
101 
102   @Test
103   public void testExceptionFromCoprocessorDuringPut()
104       throws IOException {
105     // When we try to write to TEST_TABLE, the buggy coprocessor will
106     // cause a NullPointerException, which will cause the regionserver (which
107     // hosts the region we attempted to write to) to abort.
108     byte[] TEST_TABLE = Bytes.toBytes("observed_table");
109     byte[] TEST_FAMILY = Bytes.toBytes("aaa");
110 
111     HTable table = TEST_UTIL.createTable(TEST_TABLE, TEST_FAMILY);
112     TEST_UTIL.waitUntilAllRegionsAssigned(
113         TEST_UTIL.createMultiRegions(table, TEST_FAMILY));
114 
115     // Note which regionServer will abort (after put is attempted).
116     final HRegionServer regionServer =
117         TEST_UTIL.getRSForFirstRegionInTable(TEST_TABLE);
118 
119     // add watch so we can know when this regionserver aborted.
120     ZooKeeperWatcher zkw = new ZooKeeperWatcher(TEST_UTIL.getConfiguration(),
121         "unittest", new zkwAbortable());
122 
123     RSTracker rsTracker = new RSTracker(zkw,
124         "/hbase/rs/"+regionServer.getServerName(), Thread.currentThread());
125     rsTracker.start();
126     zkw.registerListener(rsTracker);
127 
128     boolean caughtInterruption = false;
129     try {
130       final byte[] ROW = Bytes.toBytes("aaa");
131       Put put = new Put(ROW);
132       put.add(TEST_FAMILY, ROW, ROW);
133       table.put(put);
134     } catch (IOException e) {
135       // Depending on exact timing of the threads involved, zkw's interruption
136       // might be caught here ...
137       if (e.getCause().getClass().equals(InterruptedException.class)) {
138 	LOG.debug("caught interruption here (during put()).");
139         caughtInterruption = true;
140       } else {
141         fail("put() failed: " + e);
142       }
143     }
144     if (caughtInterruption == false) {
145       try {
146         Thread.sleep(timeout);
147         fail("RegionServer did not abort within 30 seconds.");
148       } catch (InterruptedException e) {
149         // .. or it might be caught here.
150 	LOG.debug("caught interruption here (during sleep()).");
151         caughtInterruption = true;
152       }
153     }
154     assertTrue("Main thread caught interruption.",caughtInterruption);
155     assertTrue("RegionServer aborted on coprocessor exception, as expected.",
156         rsTracker.regionZKNodeWasDeleted);
157     table.close();
158   }
159 
160   public static class BuggyRegionObserver extends SimpleRegionObserver {
161     @Override
162     public void prePut(final ObserverContext<RegionCoprocessorEnvironment> c,
163                        final Put put, final WALEdit edit,
164                        final boolean writeToWAL) {
165       String tableName =
166           c.getEnvironment().getRegion().getRegionInfo().getTableNameAsString();
167       if (tableName.equals("observed_table")) {
168         Integer i = null;
169         i = i + 1;
170       }
171     }
172   }
173 
174   @org.junit.Rule
175   public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
176     new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
177 }
178