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