blob: 2a4cf61c8000d2eda529b2a6598668db8adf42e0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.hadoop.hbase.coprocessor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.Optional;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Abortable;
import org.apache.hadoop.hbase.CoprocessorEnvironment;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.MiniHBaseCluster;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.master.HMaster;
import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
import org.apache.hadoop.hbase.testclassification.CoprocessorTests;
import org.apache.hadoop.hbase.testclassification.MediumTests;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.zookeeper.ZKNodeTracker;
import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
/**
* Tests unhandled exceptions thrown by coprocessors running on master.
* Expected result is that the master will abort with an informative
* error message describing the set of its loaded coprocessors for crash diagnosis.
* (HBASE-4014).
*/
@Category({CoprocessorTests.class, MediumTests.class})
public class TestMasterCoprocessorExceptionWithAbort {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestMasterCoprocessorExceptionWithAbort.class);
public static class MasterTracker extends ZKNodeTracker {
public boolean masterZKNodeWasDeleted = false;
public MasterTracker(ZKWatcher zkw, String masterNode, Abortable abortable) {
super(zkw, masterNode, abortable);
}
@Override
public synchronized void nodeDeleted(String path) {
if (path.equals("/hbase/master")) {
masterZKNodeWasDeleted = true;
}
}
}
public static class CreateTableThread extends Thread {
HBaseTestingUtility UTIL;
public CreateTableThread(HBaseTestingUtility UTIL) {
this.UTIL = UTIL;
}
@Override
public void run() {
// create a table : master coprocessor will throw an exception and not
// catch it.
TableDescriptor tableDescriptor =
TableDescriptorBuilder.newBuilder(TableName.valueOf(TEST_TABLE))
.setColumnFamily(ColumnFamilyDescriptorBuilder.of(TEST_FAMILY)).build();
try {
Admin admin = UTIL.getAdmin();
admin.createTable(tableDescriptor);
fail("BuggyMasterObserver failed to throw an exception.");
} catch (IOException e) {
assertEquals("HBaseAdmin threw an interrupted IOException as expected.",
"java.io.InterruptedIOException", e.getClass().getName());
}
}
}
public static class BuggyMasterObserver implements MasterCoprocessor, MasterObserver {
private boolean preCreateTableCalled;
private boolean postCreateTableCalled;
private boolean startCalled;
private boolean postStartMasterCalled;
@Override
public Optional<MasterObserver> getMasterObserver() {
return Optional.of(this);
}
@Override
public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> env,
TableDescriptor desc, RegionInfo[] regions) throws IOException {
// cause a NullPointerException and don't catch it: this will cause the
// master to abort().
Integer i;
i = null;
i = i++;
}
public boolean wasCreateTableCalled() {
return preCreateTableCalled && postCreateTableCalled;
}
@Override
public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
throws IOException {
postStartMasterCalled = true;
}
public boolean wasStartMasterCalled() {
return postStartMasterCalled;
}
@Override
public void start(CoprocessorEnvironment env) throws IOException {
startCalled = true;
}
public boolean wasStarted() {
return startCalled;
}
}
private static HBaseTestingUtility UTIL = new HBaseTestingUtility();
private static byte[] TEST_TABLE = Bytes.toBytes("observed_table");
private static byte[] TEST_FAMILY = Bytes.toBytes("fam1");
@BeforeClass
public static void setupBeforeClass() throws Exception {
Configuration conf = UTIL.getConfiguration();
conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
BuggyMasterObserver.class.getName());
conf.setBoolean(CoprocessorHost.ABORT_ON_ERROR_KEY, true);
conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 2); // Fail fast
UTIL.startMiniCluster();
}
@AfterClass
public static void teardownAfterClass() throws Exception {
UTIL.shutdownMiniCluster();
}
@Test
public void testExceptionFromCoprocessorWhenCreatingTable()
throws IOException {
MiniHBaseCluster cluster = UTIL.getHBaseCluster();
HMaster master = cluster.getMaster();
MasterCoprocessorHost host = master.getMasterCoprocessorHost();
BuggyMasterObserver cp = host.findCoprocessor(BuggyMasterObserver.class);
assertFalse("No table created yet", cp.wasCreateTableCalled());
// set a watch on the zookeeper /hbase/master node. If the master dies,
// the node will be deleted.
ZKWatcher zkw = new ZKWatcher(UTIL.getConfiguration(),
"unittest", new Abortable() {
@Override
public void abort(String why, Throwable e) {
throw new RuntimeException("Fatal ZK error: " + why, e);
}
@Override
public boolean isAborted() {
return false;
}
});
MasterTracker masterTracker = new MasterTracker(zkw,"/hbase/master",
new Abortable() {
@Override
public void abort(String why, Throwable e) {
throw new RuntimeException("Fatal ZK master tracker error, why=", e);
}
@Override
public boolean isAborted() {
return false;
}
});
masterTracker.start();
zkw.registerListener(masterTracker);
// Test (part of the) output that should have be printed by master when it aborts:
// (namely the part that shows the set of loaded coprocessors).
// In this test, there is only a single coprocessor (BuggyMasterObserver).
assertTrue(HMaster.getLoadedCoprocessors().
contains(TestMasterCoprocessorExceptionWithAbort.BuggyMasterObserver.class.getName()));
CreateTableThread createTableThread = new CreateTableThread(UTIL);
// Attempting to create a table (using createTableThread above) triggers an NPE in BuggyMasterObserver.
// Master will then abort and the /hbase/master zk node will be deleted.
createTableThread.start();
// Wait up to 30 seconds for master's /hbase/master zk node to go away after master aborts.
for (int i = 0; i < 30; i++) {
if (masterTracker.masterZKNodeWasDeleted == true) {
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("InterruptedException while waiting for master zk node to "
+ "be deleted.");
}
}
assertTrue("Master aborted on coprocessor exception, as expected.",
masterTracker.masterZKNodeWasDeleted);
createTableThread.interrupt();
try {
createTableThread.join(1000);
} catch (InterruptedException e) {
assertTrue("Ignoring InterruptedException while waiting for " +
" createTableThread.join().", true);
}
}
}