/* * Copyright © 2014-2015 Cask Data, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package co.cask.cdap.data2.util.hbase; import co.cask.cdap.api.common.Bytes; import co.cask.cdap.common.conf.CConfiguration; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.common.utils.Tasks; import co.cask.cdap.data.hbase.HBaseTestBase; import co.cask.cdap.data.hbase.HBaseTestFactory; import co.cask.cdap.data2.util.TableId; import co.cask.cdap.proto.Id; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.constraint.ConstraintException; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; /** * */ public abstract class AbstractHBaseTableUtilTest { @ClassRule public static final TemporaryFolder TMP_FOLDER = new TemporaryFolder(); @ClassRule public static final HBaseTestBase TEST_HBASE = new HBaseTestFactory().get(); protected static CConfiguration cConf; private static HBaseAdmin hAdmin; @BeforeClass public static void beforeClass() throws Exception { hAdmin = new HBaseAdmin(TEST_HBASE.getConfiguration()); cConf = CConfiguration.create(); } @AfterClass public static void afterClass() throws Exception { hAdmin.close(); } protected abstract HBaseTableUtil getTableUtil(); protected abstract HTableNameConverter getNameConverter(); protected abstract String getTableNameAsString(TableId tableId); protected abstract boolean namespacesSupported(); @Test public void testTableSizeMetrics() throws Exception { HBaseTableUtil tableUtil = getTableUtil(); // namespace should not exist if (namespacesSupported()) { Assert.assertFalse(tableUtil.hasNamespace(hAdmin, Id.Namespace.from("namespace"))); } Assert.assertNull(getTableStats("namespace", "table1")); Assert.assertNull(getTableStats("namespace", "table2")); Assert.assertNull(getTableStats("namespace", "table3")); if (namespacesSupported()) { createNamespace("namespace"); createNamespace("namespace2"); Assert.assertTrue(tableUtil.hasNamespace(hAdmin, Id.Namespace.from("namespace"))); } Futures.allAsList( createAsync(TableId.from("namespace", "table1")), createAsync(TableId.from("namespace2", "table1")), createAsync(TableId.from("namespace", "table2")), createAsync(TableId.from("namespace", "table3")) ).get(60, TimeUnit.SECONDS); Assert.assertTrue(exists("namespace", "table1")); Assert.assertTrue(exists("namespace2", "table1")); Assert.assertTrue(exists("namespace", "table2")); Assert.assertTrue(exists("namespace", "table3")); Tasks.waitFor(true, new Callable<Boolean>() { @Override public Boolean call() throws Exception { try { Assert.assertEquals(0, getTableStats("namespace", "table1").getTotalSizeMB()); Assert.assertEquals(0, getTableStats("namespace2", "table1").getTotalSizeMB()); Assert.assertEquals(0, getTableStats("namespace", "table2").getTotalSizeMB()); Assert.assertEquals(0, getTableStats("namespace", "table3").getTotalSizeMB()); return true; } catch (Throwable t) { return false; } } }, 5, TimeUnit.SECONDS, 100, TimeUnit.MILLISECONDS); writeSome("namespace2", "table1"); writeSome("namespace", "table2"); writeSome("namespace", "table3"); Tasks.waitFor(true, new Callable<Boolean>() { @Override public Boolean call() throws Exception { try { Assert.assertEquals(0, getTableStats("namespace", "table1").getTotalSizeMB()); Assert.assertTrue(getTableStats("namespace2", "table1").getTotalSizeMB() > 0); Assert.assertTrue(getTableStats("namespace", "table2").getTotalSizeMB() > 0); Assert.assertTrue(getTableStats("namespace", "table3").getTotalSizeMB() > 0); return true; } catch (Throwable t) { return false; } } }, 5, TimeUnit.SECONDS, 100, TimeUnit.MILLISECONDS); drop("namespace", "table1"); Assert.assertFalse(exists("namespace", "table1")); //TODO: TestHBase methods should eventually accept namespace as a param, but will add them incrementally TEST_HBASE.forceRegionFlush(Bytes.toBytes(getTableNameAsString(TableId.from("namespace", "table2")))); truncate("namespace", "table3"); Tasks.waitFor(true, new Callable<Boolean>() { @Override public Boolean call() throws Exception { try { Assert.assertNull(getTableStats("namespace", "table1")); Assert.assertTrue(getTableStats("namespace", "table2").getTotalSizeMB() > 0); Assert.assertTrue(getTableStats("namespace", "table2").getStoreFileSizeMB() > 0); Assert.assertEquals(0, getTableStats("namespace", "table3").getTotalSizeMB()); return true; } catch (Throwable t) { return false; } } }, 5, TimeUnit.SECONDS, 100, TimeUnit.MILLISECONDS); // modify HTableDescriptor desc = getTableDescriptor("namespace2", "table1"); HTableDescriptorBuilder newDesc = getTableUtil().buildHTableDescriptor(desc); newDesc.setValue("mykey", "myvalue"); disable("namespace2", "table1"); getTableUtil().modifyTable(hAdmin, newDesc.build()); desc = getTableDescriptor("namespace2", "table1"); Assert.assertTrue(desc.getValue("mykey").equals("myvalue")); enable("namespace2", "table1"); desc = getTableDescriptor("namespace", "table2"); Assert.assertNull(desc.getValue("myKey")); if (namespacesSupported()) { try { deleteNamespace("namespace"); Assert.fail("Should not be able to delete a non-empty namespace."); } catch (ConstraintException e) { // Expected exception } } Futures.allAsList( dropAsync(TableId.from("namespace2", "table1")), dropAsync(TableId.from("namespace", "table2")), dropAsync(TableId.from("namespace", "table3")) ).get(60, TimeUnit.SECONDS); if (namespacesSupported()) { deleteNamespace("namespace"); deleteNamespace("namespace2"); Assert.assertFalse(tableUtil.hasNamespace(hAdmin, Id.Namespace.from("namespace"))); Assert.assertFalse(tableUtil.hasNamespace(hAdmin, Id.Namespace.from("namespace2"))); } } @Test public void testBackwardCompatibility() throws IOException, InterruptedException { HBaseTableUtil tableUtil = getTableUtil(); String tablePrefix = cConf.get(Constants.Dataset.TABLE_PREFIX); TableId tableId = TableId.from("default", "my.dataset"); create(tableId); Assert.assertEquals("default", getNameConverter().toHBaseNamespace(tablePrefix, tableId.getNamespace())); Assert.assertEquals("cdap.user.my.dataset", getNameConverter().getHBaseTableName(tablePrefix, tableId)); Assert.assertEquals(getTableNameAsString(tableId), Bytes.toString(tableUtil.createHTable(TEST_HBASE.getConfiguration(), tableId).getTableName())); drop(tableId); tableId = TableId.from("default", "system.queue.config"); create(tableId); Assert.assertEquals("default", getNameConverter().toHBaseNamespace(tablePrefix, tableId.getNamespace())); Assert.assertEquals("cdap.system.queue.config", getNameConverter().getHBaseTableName(tablePrefix, tableId)); Assert.assertEquals(getTableNameAsString(tableId), Bytes.toString(tableUtil.createHTable(TEST_HBASE.getConfiguration(), tableId).getTableName())); drop(tableId); tableId = TableId.from("myspace", "could.be.any.table.name"); createNamespace("myspace"); create(tableId); Assert.assertEquals("cdap_myspace", getNameConverter().toHBaseNamespace(tablePrefix, tableId.getNamespace())); Assert.assertEquals("could.be.any.table.name", getNameConverter().getHBaseTableName(tablePrefix, tableId)); Assert.assertEquals(getTableNameAsString(tableId), Bytes.toString(tableUtil.createHTable(TEST_HBASE.getConfiguration(), tableId).getTableName())); drop(tableId); deleteNamespace("myspace"); } @Test public void testListAllInNamespace() throws Exception { HBaseTableUtil tableUtil = getTableUtil(); Set<TableId> fooNamespaceTableIds = ImmutableSet.of(TableId.from("foo", "some.table1"), TableId.from("foo", "other.table"), TableId.from("foo", "some.table2")); createNamespace("foo"); createNamespace("foo_bar"); TableId tableIdInOtherNamespace = TableId.from("foo_bar", "my.dataset"); List<ListenableFuture<TableId>> createFutures = new ArrayList<>(); for (TableId tableId : fooNamespaceTableIds) { createFutures.add(createAsync(tableId)); } createFutures.add(createAsync(tableIdInOtherNamespace)); Futures.allAsList(createFutures).get(60, TimeUnit.SECONDS); Set<TableId> retrievedTableIds = ImmutableSet.copyOf(tableUtil.listTablesInNamespace(hAdmin, Id.Namespace.from("foo"))); Assert.assertEquals(fooNamespaceTableIds, retrievedTableIds); Set<TableId> allTableIds = ImmutableSet.<TableId>builder().addAll(fooNamespaceTableIds).add(tableIdInOtherNamespace).build(); Assert.assertEquals(allTableIds, ImmutableSet.copyOf(tableUtil.listTables(hAdmin))); Assert.assertEquals(4, hAdmin.listTables().length); tableUtil.deleteAllInNamespace(hAdmin, Id.Namespace.from("foo")); Assert.assertEquals(1, hAdmin.listTables().length); drop(tableIdInOtherNamespace); Assert.assertEquals(0, hAdmin.listTables().length); deleteNamespace("foo_bar"); } @Test public void testDropAllInDefaultNamespace() throws Exception { HBaseTableUtil tableUtil = getTableUtil(); TableId tableIdInOtherNamespace = TableId.from("default2", "my.dataset"); createNamespace("default2"); Futures.allAsList( createAsync(TableId.from("default", "some.table1")), createAsync(TableId.from("default", "other.table")), createAsync(TableId.from("default", "some.table2")), createAsync(tableIdInOtherNamespace) ).get(60, TimeUnit.SECONDS); Assert.assertEquals(4, hAdmin.listTables().length); tableUtil.deleteAllInNamespace(hAdmin, Id.Namespace.DEFAULT); Assert.assertEquals(1, hAdmin.listTables().length); drop(tableIdInOtherNamespace); Assert.assertEquals(0, hAdmin.listTables().length); deleteNamespace("default2"); } @Test public void testDropAllInOtherNamespaceWithPrefix() throws Exception { HBaseTableUtil tableUtil = getTableUtil(); createNamespace("foonamespace"); createNamespace("barnamespace"); TableId tableIdWithOtherPrefix = TableId.from("foonamespace", "other.table"); TableId tableIdInOtherNamespace = TableId.from("default", "some.table1"); Futures.allAsList( createAsync(TableId.from("foonamespace", "some.table1")), createAsync(tableIdWithOtherPrefix), createAsync(TableId.from("foonamespace", "some.table2")), createAsync(tableIdInOtherNamespace) ).get(60, TimeUnit.SECONDS); Assert.assertEquals(4, hAdmin.listTables().length); tableUtil.deleteAllInNamespace(hAdmin, Id.Namespace.from("foonamespace"), new Predicate<TableId>() { @Override public boolean apply(TableId input) { return input.getTableName().startsWith("some"); } }); Assert.assertEquals(2, hAdmin.listTables().length); Futures.allAsList( dropAsync(tableIdInOtherNamespace), dropAsync(tableIdWithOtherPrefix) ).get(60, TimeUnit.SECONDS); Assert.assertEquals(0, hAdmin.listTables().length); deleteNamespace("foonamespace"); deleteNamespace("barnamespace"); } private void writeSome(String namespace, String tableName) throws IOException { try (HTable table = getTableUtil().createHTable(TEST_HBASE.getConfiguration(), TableId.from(namespace, tableName))) { // writing at least couple megs to reflect in "megabyte"-based metrics for (int i = 0; i < 8; i++) { Put put = new Put(Bytes.toBytes("row" + i)); put.add(Bytes.toBytes("d"), Bytes.toBytes("col" + i), new byte[1024 * 1024]); table.put(put); } } } private void createNamespace(String namespace) throws IOException { getTableUtil().createNamespaceIfNotExists(hAdmin, Id.Namespace.from(namespace)); } private void deleteNamespace(String namespace) throws IOException { getTableUtil().deleteNamespaceIfExists(hAdmin, Id.Namespace.from(namespace)); } private void create(String namespace, String tableName) throws IOException { create(TableId.from(namespace, tableName)); } private void create(TableId tableId) throws IOException { HBaseTableUtil tableUtil = getTableUtil(); HTableDescriptorBuilder desc = tableUtil.buildHTableDescriptor(tableId); desc.addFamily(new HColumnDescriptor("d")); tableUtil.createTableIfNotExists(hAdmin, tableId, desc.build()); } private ListenableFuture<TableId> createAsync(final TableId tableId) { final SettableFuture<TableId> future = SettableFuture.create(); Thread t = new Thread() { @Override public void run() { try { create(tableId); future.set(tableId); } catch (Throwable t) { future.setException(t); } } }; t.start(); return future; } private boolean exists(String namespace, String tableName) throws IOException { return exists(TableId.from(namespace, tableName)); } private boolean exists(TableId tableId) throws IOException { HBaseTableUtil tableUtil = getTableUtil(); return tableUtil.tableExists(hAdmin, tableId); } private HTableDescriptor getTableDescriptor(String namespace, String name) throws IOException { return getTableUtil().getHTableDescriptor(hAdmin, TableId.from(namespace, name)); } private void truncate(String namespace, String tableName) throws IOException { HBaseTableUtil tableUtil = getTableUtil(); TableId tableId = TableId.from(namespace, tableName); tableUtil.truncateTable(hAdmin, tableId); } private void disable(String namespace, String tableName) throws IOException { getTableUtil().disableTable(hAdmin, TableId.from(namespace, tableName)); } private void enable(String namespace, String tableName) throws IOException { getTableUtil().enableTable(hAdmin, TableId.from(namespace, tableName)); } private void drop(String namespace, String tableName) throws IOException { drop(TableId.from(namespace, tableName)); } private void drop(TableId tableId) throws IOException { HBaseTableUtil tableUtil = getTableUtil(); tableUtil.dropTable(hAdmin, tableId); } private ListenableFuture<TableId> dropAsync(final TableId tableId) { final SettableFuture<TableId> future = SettableFuture.create(); Thread t = new Thread() { @Override public void run() { try { drop(tableId); future.set(tableId); } catch (Throwable t) { future.setException(t); } } }; t.start(); return future; } private HBaseTableUtil.TableStats getTableStats(String namespace, String tableName) throws IOException { HBaseTableUtil tableUtil = getTableUtil(); // todo : should support custom table-prefix TableId tableId = TableId.from(namespace, tableName); Map<TableId, HBaseTableUtil.TableStats> statsMap = tableUtil.getTableStats(hAdmin); return statsMap.get(tableId); } }