001/** 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019package org.apache.hadoop.hbase.util; 020 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.fail; 023 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.EnumSet; 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.Optional; 032import java.util.concurrent.CountDownLatch; 033import java.util.concurrent.ExecutorService; 034import java.util.concurrent.ScheduledThreadPoolExecutor; 035import org.apache.hadoop.conf.Configuration; 036import org.apache.hadoop.fs.FileStatus; 037import org.apache.hadoop.fs.FileSystem; 038import org.apache.hadoop.fs.Path; 039import org.apache.hadoop.hbase.ClusterMetrics; 040import org.apache.hadoop.hbase.ClusterMetrics.Option; 041import org.apache.hadoop.hbase.HBaseTestingUtility; 042import org.apache.hadoop.hbase.HColumnDescriptor; 043import org.apache.hadoop.hbase.HConstants; 044import org.apache.hadoop.hbase.HRegionLocation; 045import org.apache.hadoop.hbase.HTableDescriptor; 046import org.apache.hadoop.hbase.ServerName; 047import org.apache.hadoop.hbase.TableName; 048import org.apache.hadoop.hbase.client.Admin; 049import org.apache.hadoop.hbase.client.ClusterConnection; 050import org.apache.hadoop.hbase.client.Connection; 051import org.apache.hadoop.hbase.client.ConnectionFactory; 052import org.apache.hadoop.hbase.client.Delete; 053import org.apache.hadoop.hbase.client.Put; 054import org.apache.hadoop.hbase.client.RegionInfo; 055import org.apache.hadoop.hbase.client.RegionLocator; 056import org.apache.hadoop.hbase.client.Scan; 057import org.apache.hadoop.hbase.client.Table; 058import org.apache.hadoop.hbase.client.TableDescriptor; 059import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor; 060import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; 061import org.apache.hadoop.hbase.coprocessor.MasterObserver; 062import org.apache.hadoop.hbase.coprocessor.ObserverContext; 063import org.apache.hadoop.hbase.master.assignment.AssignmentManager; 064import org.apache.hadoop.hbase.master.assignment.RegionStates; 065import org.apache.hadoop.hbase.mob.MobFileName; 066import org.apache.hadoop.hbase.mob.MobUtils; 067import org.apache.hadoop.hbase.regionserver.HRegionFileSystem; 068import org.apache.hadoop.hbase.util.hbck.HFileCorruptionChecker; 069import org.apache.zookeeper.KeeperException; 070import org.junit.rules.TestName; 071import org.slf4j.Logger; 072import org.slf4j.LoggerFactory; 073 074import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 075import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos; 076 077/** 078 * This is the base class for HBaseFsck's ability to detect reasons for inconsistent tables. 079 * 080 * Actual tests are in : 081 * TestHBaseFsckTwoRS 082 * TestHBaseFsckOneRS 083 * TestHBaseFsckMOB 084 * TestHBaseFsckReplicas 085 */ 086public class BaseTestHBaseFsck { 087 static final int POOL_SIZE = 7; 088 protected static final Logger LOG = LoggerFactory.getLogger(BaseTestHBaseFsck.class); 089 protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 090 protected final static Configuration conf = TEST_UTIL.getConfiguration(); 091 protected final static String FAM_STR = "fam"; 092 protected final static byte[] FAM = Bytes.toBytes(FAM_STR); 093 protected final static int REGION_ONLINE_TIMEOUT = 800; 094 protected static AssignmentManager assignmentManager; 095 protected static RegionStates regionStates; 096 protected static ExecutorService tableExecutorService; 097 protected static ScheduledThreadPoolExecutor hbfsckExecutorService; 098 protected static ClusterConnection connection; 099 protected static Admin admin; 100 101 // for the instance, reset every test run 102 protected Table tbl; 103 protected final static byte[][] SPLITS = new byte[][] { Bytes.toBytes("A"), 104 Bytes.toBytes("B"), Bytes.toBytes("C") }; 105 // one row per region. 106 protected final static byte[][] ROWKEYS= new byte[][] { 107 Bytes.toBytes("00"), Bytes.toBytes("50"), Bytes.toBytes("A0"), Bytes.toBytes("A5"), 108 Bytes.toBytes("B0"), Bytes.toBytes("B5"), Bytes.toBytes("C0"), Bytes.toBytes("C5") }; 109 110 /** 111 * Debugging method to dump the contents of meta. 112 */ 113 protected void dumpMeta(TableName tableName) throws IOException { 114 List<byte[]> metaRows = TEST_UTIL.getMetaTableRows(tableName); 115 for (byte[] row : metaRows) { 116 LOG.info(Bytes.toString(row)); 117 } 118 } 119 120 /** 121 * This method is used to undeploy a region -- close it and attempt to 122 * remove its state from the Master. 123 */ 124 protected void undeployRegion(Connection conn, ServerName sn, 125 RegionInfo hri) throws IOException, InterruptedException { 126 try { 127 HBaseFsckRepair.closeRegionSilentlyAndWait(conn, sn, hri); 128 if (!hri.isMetaRegion()) { 129 admin.offline(hri.getRegionName()); 130 } 131 } catch (IOException ioe) { 132 LOG.warn("Got exception when attempting to offline region " 133 + Bytes.toString(hri.getRegionName()), ioe); 134 } 135 } 136 /** 137 * Delete a region from assignments, meta, or completely from hdfs. 138 * @param unassign if true unassign region if assigned 139 * @param metaRow if true remove region's row from META 140 * @param hdfs if true remove region's dir in HDFS 141 */ 142 protected void deleteRegion(Configuration conf, final HTableDescriptor htd, 143 byte[] startKey, byte[] endKey, boolean unassign, boolean metaRow, 144 boolean hdfs) throws IOException, InterruptedException { 145 deleteRegion(conf, htd, startKey, endKey, unassign, metaRow, hdfs, false, 146 RegionInfo.DEFAULT_REPLICA_ID); 147 } 148 149 /** 150 * Delete a region from assignments, meta, or completely from hdfs. 151 * @param unassign if true unassign region if assigned 152 * @param metaRow if true remove region's row from META 153 * @param hdfs if true remove region's dir in HDFS 154 * @param regionInfoOnly if true remove a region dir's .regioninfo file 155 * @param replicaId replica id 156 */ 157 protected void deleteRegion(Configuration conf, final HTableDescriptor htd, 158 byte[] startKey, byte[] endKey, boolean unassign, boolean metaRow, 159 boolean hdfs, boolean regionInfoOnly, int replicaId) 160 throws IOException, InterruptedException { 161 LOG.info("** Before delete:"); 162 dumpMeta(htd.getTableName()); 163 164 List<HRegionLocation> locations; 165 try(RegionLocator rl = connection.getRegionLocator(tbl.getName())) { 166 locations = rl.getAllRegionLocations(); 167 } 168 169 for (HRegionLocation location : locations) { 170 RegionInfo hri = location.getRegionInfo(); 171 ServerName hsa = location.getServerName(); 172 if (Bytes.compareTo(hri.getStartKey(), startKey) == 0 173 && Bytes.compareTo(hri.getEndKey(), endKey) == 0 174 && hri.getReplicaId() == replicaId) { 175 176 LOG.info("RegionName: " +hri.getRegionNameAsString()); 177 byte[] deleteRow = hri.getRegionName(); 178 179 if (unassign) { 180 LOG.info("Undeploying region " + hri + " from server " + hsa); 181 undeployRegion(connection, hsa, hri); 182 } 183 184 if (regionInfoOnly) { 185 LOG.info("deleting hdfs .regioninfo data: " + hri.toString() + hsa.toString()); 186 Path rootDir = CommonFSUtils.getRootDir(conf); 187 FileSystem fs = rootDir.getFileSystem(conf); 188 Path p = new Path(CommonFSUtils.getTableDir(rootDir, htd.getTableName()), 189 hri.getEncodedName()); 190 Path hriPath = new Path(p, HRegionFileSystem.REGION_INFO_FILE); 191 fs.delete(hriPath, true); 192 } 193 194 if (hdfs) { 195 LOG.info("deleting hdfs data: " + hri.toString() + hsa.toString()); 196 Path rootDir = CommonFSUtils.getRootDir(conf); 197 FileSystem fs = rootDir.getFileSystem(conf); 198 Path p = new Path(CommonFSUtils.getTableDir(rootDir, htd.getTableName()), 199 hri.getEncodedName()); 200 HBaseFsck.debugLsr(conf, p); 201 boolean success = fs.delete(p, true); 202 LOG.info("Deleted " + p + " sucessfully? " + success); 203 HBaseFsck.debugLsr(conf, p); 204 } 205 206 if (metaRow) { 207 try (Table meta = connection.getTable(TableName.META_TABLE_NAME, tableExecutorService)) { 208 Delete delete = new Delete(deleteRow); 209 meta.delete(delete); 210 } 211 } 212 } 213 LOG.info(hri.toString() + hsa.toString()); 214 } 215 216 TEST_UTIL.getMetaTableRows(htd.getTableName()); 217 LOG.info("*** After delete:"); 218 dumpMeta(htd.getTableName()); 219 } 220 221 /** 222 * Setup a clean table before we start mucking with it. 223 * 224 * It will set tbl which needs to be closed after test 225 * 226 * @throws IOException 227 * @throws InterruptedException 228 * @throws KeeperException 229 */ 230 void setupTable(TableName tablename) throws Exception { 231 setupTableWithRegionReplica(tablename, 1); 232 } 233 234 /** 235 * Setup a clean table with a certain region_replica count 236 * 237 * It will set tbl which needs to be closed after test 238 * 239 * @throws Exception 240 */ 241 void setupTableWithRegionReplica(TableName tablename, int replicaCount) throws Exception { 242 HTableDescriptor desc = new HTableDescriptor(tablename); 243 desc.setRegionReplication(replicaCount); 244 HColumnDescriptor hcd = new HColumnDescriptor(Bytes.toString(FAM)); 245 desc.addFamily(hcd); // If a table has no CF's it doesn't get checked 246 createTable(TEST_UTIL, desc, SPLITS); 247 248 tbl = connection.getTable(tablename, tableExecutorService); 249 List<Put> puts = new ArrayList<>(ROWKEYS.length); 250 for (byte[] row : ROWKEYS) { 251 Put p = new Put(row); 252 p.addColumn(FAM, Bytes.toBytes("val"), row); 253 puts.add(p); 254 } 255 tbl.put(puts); 256 } 257 258 /** 259 * Setup a clean table with a mob-enabled column. 260 * 261 * @param tablename The name of a table to be created. 262 * @throws Exception 263 */ 264 void setupMobTable(TableName tablename) throws Exception { 265 HTableDescriptor desc = new HTableDescriptor(tablename); 266 HColumnDescriptor hcd = new HColumnDescriptor(Bytes.toString(FAM)); 267 hcd.setMobEnabled(true); 268 hcd.setMobThreshold(0); 269 desc.addFamily(hcd); // If a table has no CF's it doesn't get checked 270 createTable(TEST_UTIL, desc, SPLITS); 271 272 tbl = connection.getTable(tablename, tableExecutorService); 273 List<Put> puts = new ArrayList<>(ROWKEYS.length); 274 for (byte[] row : ROWKEYS) { 275 Put p = new Put(row); 276 p.addColumn(FAM, Bytes.toBytes("val"), row); 277 puts.add(p); 278 } 279 tbl.put(puts); 280 } 281 282 /** 283 * Counts the number of rows to verify data loss or non-dataloss. 284 */ 285 int countRows() throws IOException { 286 return TEST_UTIL.countRows(tbl); 287 } 288 289 /** 290 * Counts the number of rows to verify data loss or non-dataloss. 291 */ 292 int countRows(byte[] start, byte[] end) throws IOException { 293 return TEST_UTIL.countRows(tbl, new Scan(start, end)); 294 } 295 296 /** 297 * delete table in preparation for next test 298 * 299 * @param tablename 300 * @throws IOException 301 */ 302 void cleanupTable(TableName tablename) throws Exception { 303 if (tbl != null) { 304 tbl.close(); 305 tbl = null; 306 } 307 308 ((ClusterConnection) connection).clearRegionLocationCache(); 309 deleteTable(TEST_UTIL, tablename); 310 } 311 312 /** 313 * Get region info from local cluster. 314 */ 315 Map<ServerName, List<String>> getDeployedHRIs(final Admin admin) throws IOException { 316 ClusterMetrics status = admin.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS)); 317 Collection<ServerName> regionServers = status.getLiveServerMetrics().keySet(); 318 Map<ServerName, List<String>> mm = new HashMap<>(); 319 for (ServerName hsi : regionServers) { 320 AdminProtos.AdminService.BlockingInterface server = connection.getAdmin(hsi); 321 322 // list all online regions from this region server 323 List<RegionInfo> regions = ProtobufUtil.getOnlineRegions(server); 324 List<String> regionNames = new ArrayList<>(regions.size()); 325 for (RegionInfo hri : regions) { 326 regionNames.add(hri.getRegionNameAsString()); 327 } 328 mm.put(hsi, regionNames); 329 } 330 return mm; 331 } 332 333 /** 334 * Returns the HSI a region info is on. 335 */ 336 ServerName findDeployedHSI(Map<ServerName, List<String>> mm, RegionInfo hri) { 337 for (Map.Entry<ServerName,List <String>> e : mm.entrySet()) { 338 if (e.getValue().contains(hri.getRegionNameAsString())) { 339 return e.getKey(); 340 } 341 } 342 return null; 343 } 344 345 public void deleteTableDir(TableName table) throws IOException { 346 Path rootDir = CommonFSUtils.getRootDir(conf); 347 FileSystem fs = rootDir.getFileSystem(conf); 348 Path p = CommonFSUtils.getTableDir(rootDir, table); 349 HBaseFsck.debugLsr(conf, p); 350 boolean success = fs.delete(p, true); 351 LOG.info("Deleted " + p + " sucessfully? " + success); 352 } 353 354 /** 355 * We don't have an easy way to verify that a flush completed, so we loop until we find a 356 * legitimate hfile and return it. 357 * @param fs 358 * @param table 359 * @return Path of a flushed hfile. 360 * @throws IOException 361 */ 362 Path getFlushedHFile(FileSystem fs, TableName table) throws IOException { 363 Path tableDir= CommonFSUtils.getTableDir(CommonFSUtils.getRootDir(conf), table); 364 Path regionDir = FSUtils.getRegionDirs(fs, tableDir).get(0); 365 Path famDir = new Path(regionDir, FAM_STR); 366 367 // keep doing this until we get a legit hfile 368 while (true) { 369 FileStatus[] hfFss = fs.listStatus(famDir); 370 if (hfFss.length == 0) { 371 continue; 372 } 373 for (FileStatus hfs : hfFss) { 374 if (!hfs.isDirectory()) { 375 return hfs.getPath(); 376 } 377 } 378 } 379 } 380 381 /** 382 * Gets flushed mob files. 383 * @param fs The current file system. 384 * @param table The current table name. 385 * @return Path of a flushed hfile. 386 * @throws IOException 387 */ 388 Path getFlushedMobFile(FileSystem fs, TableName table) throws IOException { 389 Path famDir = MobUtils.getMobFamilyPath(conf, table, FAM_STR); 390 391 // keep doing this until we get a legit hfile 392 while (true) { 393 FileStatus[] hfFss = fs.listStatus(famDir); 394 if (hfFss.length == 0) { 395 continue; 396 } 397 for (FileStatus hfs : hfFss) { 398 if (!hfs.isDirectory()) { 399 return hfs.getPath(); 400 } 401 } 402 } 403 } 404 405 /** 406 * Creates a new mob file name by the old one. 407 * @param oldFileName The old mob file name. 408 * @return The new mob file name. 409 */ 410 String createMobFileName(String oldFileName) { 411 MobFileName mobFileName = MobFileName.create(oldFileName); 412 String startKey = mobFileName.getStartKey(); 413 String date = mobFileName.getDate(); 414 return MobFileName.create(startKey, date, 415 TEST_UTIL.getRandomUUID().toString().replaceAll("-", "")) 416 .getFileName(); 417 } 418 419 420 421 422 /** 423 * Test that use this should have a timeout, because this method could potentially wait forever. 424 */ 425 protected void doQuarantineTest(TableName table, HBaseFsck hbck, int check, 426 int corrupt, int fail, int quar, int missing) throws Exception { 427 try { 428 setupTable(table); 429 assertEquals(ROWKEYS.length, countRows()); 430 admin.flush(table); // flush is async. 431 432 // Mess it up by leaving a hole in the assignment, meta, and hdfs data 433 admin.disableTable(table); 434 435 String[] args = {"-sidelineCorruptHFiles", "-repairHoles", "-ignorePreCheckPermission", 436 table.getNameAsString()}; 437 HBaseFsck res = hbck.exec(hbfsckExecutorService, args); 438 439 HFileCorruptionChecker hfcc = res.getHFilecorruptionChecker(); 440 assertEquals(hfcc.getHFilesChecked(), check); 441 assertEquals(hfcc.getCorrupted().size(), corrupt); 442 assertEquals(hfcc.getFailures().size(), fail); 443 assertEquals(hfcc.getQuarantined().size(), quar); 444 assertEquals(hfcc.getMissing().size(), missing); 445 446 // its been fixed, verify that we can enable 447 admin.enableTableAsync(table); 448 while (!admin.isTableEnabled(table)) { 449 try { 450 Thread.sleep(250); 451 } catch (InterruptedException e) { 452 e.printStackTrace(); 453 fail("Interrupted when trying to enable table " + table); 454 } 455 } 456 } finally { 457 cleanupTable(table); 458 } 459 } 460 461 462 static class MockErrorReporter implements HbckErrorReporter { 463 static int calledCount = 0; 464 465 @Override 466 public void clear() { 467 calledCount++; 468 } 469 470 @Override 471 public void report(String message) { 472 calledCount++; 473 } 474 475 @Override 476 public void reportError(String message) { 477 calledCount++; 478 } 479 480 @Override 481 public void reportError(ERROR_CODE errorCode, String message) { 482 calledCount++; 483 } 484 485 @Override 486 public void reportError(ERROR_CODE errorCode, String message, HbckTableInfo table) { 487 calledCount++; 488 } 489 490 @Override 491 public void reportError(ERROR_CODE errorCode, 492 String message, HbckTableInfo table, HbckRegionInfo info) { 493 calledCount++; 494 } 495 496 @Override 497 public void reportError(ERROR_CODE errorCode, String message, 498 HbckTableInfo table, HbckRegionInfo info1, HbckRegionInfo info2) { 499 calledCount++; 500 } 501 502 @Override 503 public int summarize() { 504 return ++calledCount; 505 } 506 507 @Override 508 public void detail(String details) { 509 calledCount++; 510 } 511 512 @Override 513 public ArrayList<ERROR_CODE> getErrorList() { 514 calledCount++; 515 return new ArrayList<>(); 516 } 517 518 @Override 519 public void progress() { 520 calledCount++; 521 } 522 523 @Override 524 public void print(String message) { 525 calledCount++; 526 } 527 528 @Override 529 public void resetErrors() { 530 calledCount++; 531 } 532 533 @Override 534 public boolean tableHasErrors(HbckTableInfo table) { 535 calledCount++; 536 return false; 537 } 538 } 539 540 541 protected void deleteMetaRegion(Configuration conf, boolean unassign, boolean hdfs, 542 boolean regionInfoOnly) throws IOException, InterruptedException { 543 HRegionLocation metaLocation = connection.getRegionLocator(TableName.META_TABLE_NAME) 544 .getRegionLocation(HConstants.EMPTY_START_ROW); 545 ServerName hsa = metaLocation.getServerName(); 546 RegionInfo hri = metaLocation.getRegionInfo(); 547 if (unassign) { 548 LOG.info("Undeploying meta region " + hri + " from server " + hsa); 549 try (Connection unmanagedConnection = ConnectionFactory.createConnection(conf)) { 550 undeployRegion(unmanagedConnection, hsa, hri); 551 } 552 } 553 554 if (regionInfoOnly) { 555 LOG.info("deleting hdfs .regioninfo data: " + hri.toString() + hsa.toString()); 556 Path rootDir = CommonFSUtils.getRootDir(conf); 557 FileSystem fs = rootDir.getFileSystem(conf); 558 Path p = new Path(rootDir + "/" + TableName.META_TABLE_NAME.getNameAsString(), 559 hri.getEncodedName()); 560 Path hriPath = new Path(p, HRegionFileSystem.REGION_INFO_FILE); 561 fs.delete(hriPath, true); 562 } 563 564 if (hdfs) { 565 LOG.info("deleting hdfs data: " + hri.toString() + hsa.toString()); 566 Path rootDir = CommonFSUtils.getRootDir(conf); 567 FileSystem fs = rootDir.getFileSystem(conf); 568 Path p = new Path(rootDir + "/" + TableName.META_TABLE_NAME.getNameAsString(), 569 hri.getEncodedName()); 570 HBaseFsck.debugLsr(conf, p); 571 boolean success = fs.delete(p, true); 572 LOG.info("Deleted " + p + " sucessfully? " + success); 573 HBaseFsck.debugLsr(conf, p); 574 } 575 } 576 577 @org.junit.Rule 578 public TestName name = new TestName(); 579 580 public static class MasterSyncCoprocessor implements MasterCoprocessor, MasterObserver { 581 volatile CountDownLatch tableCreationLatch = null; 582 volatile CountDownLatch tableDeletionLatch = null; 583 584 @Override 585 public Optional<MasterObserver> getMasterObserver() { 586 return Optional.of(this); 587 } 588 589 @Override 590 public void postCompletedCreateTableAction( 591 final ObserverContext<MasterCoprocessorEnvironment> ctx, 592 final TableDescriptor desc, 593 final RegionInfo[] regions) throws IOException { 594 // the AccessController test, some times calls only and directly the 595 // postCompletedCreateTableAction() 596 if (tableCreationLatch != null) { 597 tableCreationLatch.countDown(); 598 } 599 } 600 601 @Override 602 public void postCompletedDeleteTableAction( 603 final ObserverContext<MasterCoprocessorEnvironment> ctx, 604 final TableName tableName) throws IOException { 605 // the AccessController test, some times calls only and directly the 606 // postCompletedDeleteTableAction() 607 if (tableDeletionLatch != null) { 608 tableDeletionLatch.countDown(); 609 } 610 } 611 } 612 613 public static void createTable(HBaseTestingUtility testUtil, HTableDescriptor htd, 614 byte [][] splitKeys) throws Exception { 615 // NOTE: We need a latch because admin is not sync, 616 // so the postOp coprocessor method may be called after the admin operation returned. 617 MasterSyncCoprocessor coproc = testUtil.getHBaseCluster().getMaster() 618 .getMasterCoprocessorHost().findCoprocessor(MasterSyncCoprocessor.class); 619 coproc.tableCreationLatch = new CountDownLatch(1); 620 if (splitKeys != null) { 621 admin.createTable(htd, splitKeys); 622 } else { 623 admin.createTable(htd); 624 } 625 coproc.tableCreationLatch.await(); 626 coproc.tableCreationLatch = null; 627 testUtil.waitUntilAllRegionsAssigned(htd.getTableName()); 628 } 629 630 public static void deleteTable(HBaseTestingUtility testUtil, TableName tableName) 631 throws Exception { 632 // NOTE: We need a latch because admin is not sync, 633 // so the postOp coprocessor method may be called after the admin operation returned. 634 MasterSyncCoprocessor coproc = testUtil.getHBaseCluster().getMaster() 635 .getMasterCoprocessorHost().findCoprocessor(MasterSyncCoprocessor.class); 636 coproc.tableDeletionLatch = new CountDownLatch(1); 637 try { 638 admin.disableTable(tableName); 639 } catch (Exception e) { 640 LOG.debug("Table: " + tableName + " already disabled, so just deleting it."); 641 } 642 admin.deleteTable(tableName); 643 coproc.tableDeletionLatch.await(); 644 coproc.tableDeletionLatch = null; 645 } 646}