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