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
077   * were missing the hregioninfo because of error were being left behind messing up any
078   * subsequent table 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,
140        final TableDescriptor desc,
141        final RegionInfo[] regions) throws IOException {
142      // the AccessController test, some times calls only and directly the
143      // postCompletedCreateTableAction()
144      if (tableCreationLatch != null) {
145        tableCreationLatch.countDown();
146      }
147    }
148
149    @Override
150    public void postCompletedDeleteTableAction(
151        final ObserverContext<MasterCoprocessorEnvironment> ctx,
152        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(HBaseTestingUtil testUtil, TableDescriptor tableDescriptor,
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(tableDescriptor, splitKeys);
171    } else {
172      admin.createTable(tableDescriptor);
173    }
174    observer.tableCreationLatch.await();
175    observer.tableCreationLatch = null;
176    testUtil.waitUntilAllRegionsAssigned(tableDescriptor.getTableName());
177  }
178
179  public static void deleteTable(HBaseTestingUtil 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()
184      .getMasterCoprocessorHost().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}