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.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023import static org.junit.Assert.fail; 024 025import java.io.IOException; 026import java.util.Optional; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.hbase.Abortable; 029import org.apache.hadoop.hbase.CoprocessorEnvironment; 030import org.apache.hadoop.hbase.HBaseClassTestRule; 031import org.apache.hadoop.hbase.HBaseTestingUtil; 032import org.apache.hadoop.hbase.HConstants; 033import org.apache.hadoop.hbase.SingleProcessHBaseCluster; 034import org.apache.hadoop.hbase.TableName; 035import org.apache.hadoop.hbase.client.Admin; 036import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 037import org.apache.hadoop.hbase.client.RegionInfo; 038import org.apache.hadoop.hbase.client.TableDescriptor; 039import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 040import org.apache.hadoop.hbase.master.HMaster; 041import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 042import org.apache.hadoop.hbase.testclassification.CoprocessorTests; 043import org.apache.hadoop.hbase.testclassification.MediumTests; 044import org.apache.hadoop.hbase.util.Bytes; 045import org.apache.hadoop.hbase.zookeeper.ZKNodeTracker; 046import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 047import org.junit.AfterClass; 048import org.junit.BeforeClass; 049import org.junit.ClassRule; 050import org.junit.Test; 051import org.junit.experimental.categories.Category; 052 053/** 054 * Tests unhandled exceptions thrown by coprocessors running on master. Expected result is that the 055 * master will abort with an informative error message describing the set of its loaded coprocessors 056 * for crash diagnosis. (HBASE-4014). 057 */ 058@Category({ CoprocessorTests.class, MediumTests.class }) 059public class TestMasterCoprocessorExceptionWithAbort { 060 061 @ClassRule 062 public static final HBaseClassTestRule CLASS_RULE = 063 HBaseClassTestRule.forClass(TestMasterCoprocessorExceptionWithAbort.class); 064 065 public static class MasterTracker extends ZKNodeTracker { 066 public boolean masterZKNodeWasDeleted = false; 067 068 public MasterTracker(ZKWatcher zkw, String masterNode, Abortable abortable) { 069 super(zkw, masterNode, abortable); 070 } 071 072 @Override 073 public synchronized void nodeDeleted(String path) { 074 if (path.equals("/hbase/master")) { 075 masterZKNodeWasDeleted = true; 076 } 077 } 078 } 079 080 public static class CreateTableThread extends Thread { 081 HBaseTestingUtil UTIL; 082 083 public CreateTableThread(HBaseTestingUtil UTIL) { 084 this.UTIL = UTIL; 085 } 086 087 @Override 088 public void run() { 089 // create a table : master coprocessor will throw an exception and not 090 // catch it. 091 TableDescriptor tableDescriptor = 092 TableDescriptorBuilder.newBuilder(TableName.valueOf(TEST_TABLE)) 093 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(TEST_FAMILY)).build(); 094 try { 095 Admin admin = UTIL.getAdmin(); 096 admin.createTable(tableDescriptor); 097 fail("BuggyMasterObserver failed to throw an exception."); 098 } catch (IOException e) { 099 assertEquals("HBaseAdmin threw an interrupted IOException as expected.", 100 "java.io.InterruptedIOException", e.getClass().getName()); 101 } 102 } 103 } 104 105 public static class BuggyMasterObserver implements MasterCoprocessor, MasterObserver { 106 private boolean preCreateTableCalled; 107 private boolean postCreateTableCalled; 108 private boolean startCalled; 109 private boolean postStartMasterCalled; 110 111 @Override 112 public Optional<MasterObserver> getMasterObserver() { 113 return Optional.of(this); 114 } 115 116 @Override 117 public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> env, 118 TableDescriptor desc, RegionInfo[] regions) throws IOException { 119 // cause a NullPointerException and don't catch it: this will cause the 120 // master to abort(). 121 Integer i; 122 i = null; 123 i = i++; 124 } 125 126 public boolean wasCreateTableCalled() { 127 return preCreateTableCalled && postCreateTableCalled; 128 } 129 130 @Override 131 public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx) 132 throws IOException { 133 postStartMasterCalled = true; 134 } 135 136 public boolean wasStartMasterCalled() { 137 return postStartMasterCalled; 138 } 139 140 @Override 141 public void start(CoprocessorEnvironment env) throws IOException { 142 startCalled = true; 143 } 144 145 public boolean wasStarted() { 146 return startCalled; 147 } 148 } 149 150 private static HBaseTestingUtil UTIL = new HBaseTestingUtil(); 151 private static byte[] TEST_TABLE = Bytes.toBytes("observed_table"); 152 private static byte[] TEST_FAMILY = Bytes.toBytes("fam1"); 153 154 @BeforeClass 155 public static void setupBeforeClass() throws Exception { 156 Configuration conf = UTIL.getConfiguration(); 157 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, BuggyMasterObserver.class.getName()); 158 conf.setBoolean(CoprocessorHost.ABORT_ON_ERROR_KEY, true); 159 conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 2); // Fail fast 160 UTIL.startMiniCluster(); 161 } 162 163 @AfterClass 164 public static void teardownAfterClass() throws Exception { 165 UTIL.shutdownMiniCluster(); 166 } 167 168 @Test 169 public void testExceptionFromCoprocessorWhenCreatingTable() throws IOException { 170 SingleProcessHBaseCluster cluster = UTIL.getHBaseCluster(); 171 172 HMaster master = cluster.getMaster(); 173 MasterCoprocessorHost host = master.getMasterCoprocessorHost(); 174 BuggyMasterObserver cp = host.findCoprocessor(BuggyMasterObserver.class); 175 assertFalse("No table created yet", cp.wasCreateTableCalled()); 176 177 // set a watch on the zookeeper /hbase/master node. If the master dies, 178 // the node will be deleted. 179 ZKWatcher zkw = new ZKWatcher(UTIL.getConfiguration(), "unittest", new Abortable() { 180 @Override 181 public void abort(String why, Throwable e) { 182 throw new RuntimeException("Fatal ZK error: " + why, e); 183 } 184 185 @Override 186 public boolean isAborted() { 187 return false; 188 } 189 }); 190 191 MasterTracker masterTracker = new MasterTracker(zkw, "/hbase/master", new Abortable() { 192 @Override 193 public void abort(String why, Throwable e) { 194 throw new RuntimeException("Fatal ZK master tracker error, why=", e); 195 } 196 197 @Override 198 public boolean isAborted() { 199 return false; 200 } 201 }); 202 203 masterTracker.start(); 204 zkw.registerListener(masterTracker); 205 206 // Test (part of the) output that should have be printed by master when it aborts: 207 // (namely the part that shows the set of loaded coprocessors). 208 // In this test, there is only a single coprocessor (BuggyMasterObserver). 209 assertTrue(HMaster.getLoadedCoprocessors() 210 .contains(TestMasterCoprocessorExceptionWithAbort.BuggyMasterObserver.class.getName())); 211 212 CreateTableThread createTableThread = new CreateTableThread(UTIL); 213 214 // Attempting to create a table (using createTableThread above) triggers an NPE in 215 // BuggyMasterObserver. 216 // Master will then abort and the /hbase/master zk node will be deleted. 217 createTableThread.start(); 218 219 // Wait up to 30 seconds for master's /hbase/master zk node to go away after master aborts. 220 for (int i = 0; i < 30; i++) { 221 if (masterTracker.masterZKNodeWasDeleted == true) { 222 break; 223 } 224 try { 225 Thread.sleep(1000); 226 } catch (InterruptedException e) { 227 fail("InterruptedException while waiting for master zk node to " + "be deleted."); 228 } 229 } 230 231 assertTrue("Master aborted on coprocessor exception, as expected.", 232 masterTracker.masterZKNodeWasDeleted); 233 234 createTableThread.interrupt(); 235 try { 236 createTableThread.join(1000); 237 } catch (InterruptedException e) { 238 assertTrue("Ignoring InterruptedException while waiting for " + " createTableThread.join().", 239 true); 240 } 241 } 242 243}