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