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.client;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.fail;
022
023import java.io.IOException;
024import java.util.Optional;
025import java.util.concurrent.CountDownLatch;
026import org.apache.hadoop.hbase.HBaseTestingUtil;
027import org.apache.hadoop.hbase.HConstants;
028import org.apache.hadoop.hbase.MetaTableAccessor;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
031import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
032import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
033import org.apache.hadoop.hbase.coprocessor.MasterObserver;
034import org.apache.hadoop.hbase.coprocessor.ObserverContext;
035import org.apache.hadoop.hbase.testclassification.MasterTests;
036import org.apache.hadoop.hbase.testclassification.MediumTests;
037import org.apache.hadoop.hbase.util.Bytes;
038import org.junit.jupiter.api.AfterEach;
039import org.junit.jupiter.api.BeforeEach;
040import org.junit.jupiter.api.Tag;
041import org.junit.jupiter.api.Test;
042import org.junit.jupiter.api.TestInfo;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046@Tag(MasterTests.TAG)
047@Tag(MediumTests.TAG)
048public class TestEnableTable {
049
050  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
051  private static final Logger LOG = LoggerFactory.getLogger(TestEnableTable.class);
052  private static final byte[] FAMILYNAME = Bytes.toBytes("fam");
053
054  @BeforeEach
055  public void setUp() throws Exception {
056    TEST_UTIL.getConfiguration().set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
057      MasterSyncObserver.class.getName());
058    TEST_UTIL.startMiniCluster(1);
059  }
060
061  @AfterEach
062  public void tearDown() throws Exception {
063    TEST_UTIL.shutdownMiniCluster();
064  }
065
066  /**
067   * We were only clearing rows that had a hregioninfo column in hbase:meta. Mangled rows that were
068   * missing the hregioninfo because of error were being left behind messing up any subsequent table
069   * made with the same name. HBASE-12980
070   */
071  @Test
072  public void testDeleteForSureClearsAllTableRowsFromMeta(TestInfo testInfo)
073    throws IOException, InterruptedException {
074    final TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName());
075    final Admin admin = TEST_UTIL.getAdmin();
076    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(tableName)
077      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILYNAME)).build();
078    try {
079      createTable(TEST_UTIL, tableDescriptor, HBaseTestingUtil.KEYS_FOR_HBA_CREATE_TABLE);
080    } catch (Exception e) {
081      LOG.error("", e);
082      fail("Got an exception while creating " + tableName);
083    }
084    // Now I have a nice table, mangle it by removing the HConstants.REGIONINFO_QUALIFIER_STR
085    // content from a few of the rows.
086    try (Table metaTable = TEST_UTIL.getConnection().getTable(TableName.META_TABLE_NAME)) {
087      try (ResultScanner scanner = metaTable.getScanner(
088        MetaTableAccessor.getScanForTableName(TEST_UTIL.getConfiguration(), tableName))) {
089        for (Result result : scanner) {
090          // Just delete one row.
091          Delete d = new Delete(result.getRow());
092          d.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
093          LOG.info("Mangled: " + d);
094          metaTable.delete(d);
095          break;
096        }
097      }
098      admin.disableTable(tableName);
099      TEST_UTIL.waitTableDisabled(tableName.getName());
100      // Rely on the coprocessor based latch to make the operation synchronous.
101      try {
102        deleteTable(TEST_UTIL, tableName);
103      } catch (Exception e) {
104        LOG.error("", e);
105        fail("Got an exception while deleting " + tableName);
106      }
107      int rowCount = 0;
108      try (ResultScanner scanner = metaTable.getScanner(
109        MetaTableAccessor.getScanForTableName(TEST_UTIL.getConfiguration(), tableName))) {
110        for (Result result : scanner) {
111          LOG.info("Found when none expected: " + result);
112          rowCount++;
113        }
114      }
115      assertEquals(0, rowCount);
116    }
117  }
118
119  public static class MasterSyncObserver implements MasterCoprocessor, MasterObserver {
120    volatile CountDownLatch tableCreationLatch = null;
121    volatile CountDownLatch tableDeletionLatch = null;
122
123    @Override
124    public Optional<MasterObserver> getMasterObserver() {
125      return Optional.of(this);
126    }
127
128    @Override
129    public void postCompletedCreateTableAction(
130      final ObserverContext<MasterCoprocessorEnvironment> ctx, final TableDescriptor desc,
131      final RegionInfo[] regions) throws IOException {
132      // the AccessController test, some times calls only and directly the
133      // postCompletedCreateTableAction()
134      if (tableCreationLatch != null) {
135        tableCreationLatch.countDown();
136      }
137    }
138
139    @Override
140    public void postCompletedDeleteTableAction(
141      final ObserverContext<MasterCoprocessorEnvironment> ctx, final TableName tableName)
142      throws IOException {
143      // the AccessController test, some times calls only and directly the postDeleteTableHandler()
144      if (tableDeletionLatch != null) {
145        tableDeletionLatch.countDown();
146      }
147    }
148  }
149
150  public static void createTable(HBaseTestingUtil testUtil, TableDescriptor tableDescriptor,
151    byte[][] splitKeys) throws Exception {
152    // NOTE: We need a latch because admin is not sync,
153    // so the postOp coprocessor method may be called after the admin operation returned.
154    MasterSyncObserver observer = testUtil.getHBaseCluster().getMaster().getMasterCoprocessorHost()
155      .findCoprocessor(MasterSyncObserver.class);
156    observer.tableCreationLatch = new CountDownLatch(1);
157    Admin admin = testUtil.getAdmin();
158    if (splitKeys != null) {
159      admin.createTable(tableDescriptor, splitKeys);
160    } else {
161      admin.createTable(tableDescriptor);
162    }
163    observer.tableCreationLatch.await();
164    observer.tableCreationLatch = null;
165    testUtil.waitUntilAllRegionsAssigned(tableDescriptor.getTableName());
166  }
167
168  public static void deleteTable(HBaseTestingUtil testUtil, TableName tableName) throws Exception {
169    // NOTE: We need a latch because admin is not sync,
170    // so the postOp coprocessor method may be called after the admin operation returned.
171    MasterSyncObserver observer = testUtil.getHBaseCluster().getMaster().getMasterCoprocessorHost()
172      .findCoprocessor(MasterSyncObserver.class);
173    observer.tableDeletionLatch = new CountDownLatch(1);
174    Admin admin = testUtil.getAdmin();
175    try {
176      admin.disableTable(tableName);
177    } catch (Exception e) {
178      LOG.debug("Table: " + tableName + " already disabled, so just deleting it.");
179    }
180    admin.deleteTable(tableName);
181    observer.tableDeletionLatch.await();
182    observer.tableDeletionLatch = null;
183  }
184}