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.namespace;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertNull;
024import static org.junit.Assert.assertTrue;
025import static org.junit.Assert.fail;
026
027import java.io.IOException;
028import java.util.Collections;
029import java.util.List;
030import java.util.Optional;
031import java.util.concurrent.CountDownLatch;
032import java.util.concurrent.ExecutionException;
033import java.util.concurrent.Future;
034import java.util.concurrent.TimeUnit;
035import org.apache.hadoop.conf.Configuration;
036import org.apache.hadoop.fs.FileSystem;
037import org.apache.hadoop.fs.Path;
038import org.apache.hadoop.hbase.Coprocessor;
039import org.apache.hadoop.hbase.CoprocessorEnvironment;
040import org.apache.hadoop.hbase.HBaseClassTestRule;
041import org.apache.hadoop.hbase.HBaseTestingUtility;
042import org.apache.hadoop.hbase.HColumnDescriptor;
043import org.apache.hadoop.hbase.HConstants;
044import org.apache.hadoop.hbase.HRegionInfo;
045import org.apache.hadoop.hbase.HTableDescriptor;
046import org.apache.hadoop.hbase.MiniHBaseCluster;
047import org.apache.hadoop.hbase.NamespaceDescriptor;
048import org.apache.hadoop.hbase.StartMiniClusterOption;
049import org.apache.hadoop.hbase.TableName;
050import org.apache.hadoop.hbase.Waiter;
051import org.apache.hadoop.hbase.client.Admin;
052import org.apache.hadoop.hbase.client.CompactionState;
053import org.apache.hadoop.hbase.client.Connection;
054import org.apache.hadoop.hbase.client.ConnectionFactory;
055import org.apache.hadoop.hbase.client.DoNotRetryRegionException;
056import org.apache.hadoop.hbase.client.RegionInfo;
057import org.apache.hadoop.hbase.client.RegionLocator;
058import org.apache.hadoop.hbase.client.Table;
059import org.apache.hadoop.hbase.client.TableDescriptor;
060import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
061import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
062import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
063import org.apache.hadoop.hbase.coprocessor.MasterObserver;
064import org.apache.hadoop.hbase.coprocessor.ObserverContext;
065import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor;
066import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
067import org.apache.hadoop.hbase.coprocessor.RegionObserver;
068import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessor;
069import org.apache.hadoop.hbase.coprocessor.RegionServerObserver;
070import org.apache.hadoop.hbase.master.HMaster;
071import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
072import org.apache.hadoop.hbase.master.TableNamespaceManager;
073import org.apache.hadoop.hbase.quotas.MasterQuotaManager;
074import org.apache.hadoop.hbase.quotas.QuotaExceededException;
075import org.apache.hadoop.hbase.quotas.QuotaUtil;
076import org.apache.hadoop.hbase.regionserver.HRegion;
077import org.apache.hadoop.hbase.regionserver.Store;
078import org.apache.hadoop.hbase.regionserver.StoreFile;
079import org.apache.hadoop.hbase.regionserver.compactions.CompactionLifeCycleTracker;
080import org.apache.hadoop.hbase.regionserver.compactions.CompactionRequest;
081import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
082import org.apache.hadoop.hbase.testclassification.LargeTests;
083import org.apache.hadoop.hbase.util.Bytes;
084import org.apache.hadoop.hbase.util.CommonFSUtils;
085import org.apache.zookeeper.KeeperException;
086import org.junit.After;
087import org.junit.AfterClass;
088import org.junit.BeforeClass;
089import org.junit.ClassRule;
090import org.junit.Test;
091import org.junit.experimental.categories.Category;
092import org.slf4j.Logger;
093import org.slf4j.LoggerFactory;
094
095@Category(LargeTests.class)
096public class TestNamespaceAuditor {
097
098  @ClassRule
099  public static final HBaseClassTestRule CLASS_RULE =
100      HBaseClassTestRule.forClass(TestNamespaceAuditor.class);
101
102  private static final Logger LOG = LoggerFactory.getLogger(TestNamespaceAuditor.class);
103  private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
104  private static Admin ADMIN;
105  private String prefix = "TestNamespaceAuditor";
106
107  @BeforeClass
108  public static void before() throws Exception {
109    Configuration conf = UTIL.getConfiguration();
110    conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, CustomObserver.class.getName());
111    conf.setStrings(
112      CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
113      MasterSyncObserver.class.getName(), CPMasterObserver.class.getName());
114    conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 5);
115    conf.setBoolean(QuotaUtil.QUOTA_CONF_KEY, true);
116    conf.setClass("hbase.coprocessor.regionserver.classes", CPRegionServerObserver.class,
117      RegionServerObserver.class);
118    StartMiniClusterOption option = StartMiniClusterOption.builder().numMasters(2).build();
119    UTIL.startMiniCluster(option);
120    waitForQuotaInitialize(UTIL);
121    ADMIN = UTIL.getAdmin();
122  }
123
124  @AfterClass
125  public static void tearDown() throws Exception {
126    UTIL.shutdownMiniCluster();
127  }
128
129  @After
130  public void cleanup() throws Exception, KeeperException {
131    for (HTableDescriptor table : ADMIN.listTables()) {
132      ADMIN.disableTable(table.getTableName());
133      deleteTable(table.getTableName());
134    }
135    for (NamespaceDescriptor ns : ADMIN.listNamespaceDescriptors()) {
136      if (ns.getName().startsWith(prefix)) {
137        ADMIN.deleteNamespace(ns.getName());
138      }
139    }
140    assertTrue("Quota manager not initialized", UTIL.getHBaseCluster().getMaster()
141        .getMasterQuotaManager().isQuotaInitialized());
142  }
143
144  @Test
145  public void testTableOperations() throws Exception {
146    String nsp = prefix + "_np2";
147    NamespaceDescriptor nspDesc =
148        NamespaceDescriptor.create(nsp).addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "5")
149            .addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "2").build();
150    ADMIN.createNamespace(nspDesc);
151    assertNotNull("Namespace descriptor found null.", ADMIN.getNamespaceDescriptor(nsp));
152    assertEquals(3, ADMIN.listNamespaceDescriptors().length);
153    HColumnDescriptor fam1 = new HColumnDescriptor("fam1");
154
155    HTableDescriptor tableDescOne =
156        new HTableDescriptor(TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table1"));
157    tableDescOne.addFamily(fam1);
158    HTableDescriptor tableDescTwo =
159        new HTableDescriptor(TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table2"));
160    tableDescTwo.addFamily(fam1);
161    HTableDescriptor tableDescThree =
162        new HTableDescriptor(TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table3"));
163    tableDescThree.addFamily(fam1);
164    ADMIN.createTable(tableDescOne);
165    boolean constraintViolated = false;
166    try {
167      ADMIN.createTable(tableDescTwo, Bytes.toBytes("AAA"), Bytes.toBytes("ZZZ"), 5);
168    } catch (Exception exp) {
169      assertTrue(exp instanceof IOException);
170      constraintViolated = true;
171    } finally {
172      assertTrue("Constraint not violated for table " + tableDescTwo.getTableName(),
173        constraintViolated);
174    }
175    ADMIN.createTable(tableDescTwo, Bytes.toBytes("AAA"), Bytes.toBytes("ZZZ"), 4);
176    NamespaceTableAndRegionInfo nspState = getQuotaManager().getState(nsp);
177    assertNotNull(nspState);
178    assertTrue(nspState.getTables().size() == 2);
179    assertTrue(nspState.getRegionCount() == 5);
180    constraintViolated = false;
181    try {
182      ADMIN.createTable(tableDescThree);
183    } catch (Exception exp) {
184      assertTrue(exp instanceof IOException);
185      constraintViolated = true;
186    } finally {
187      assertTrue("Constraint not violated for table " + tableDescThree.getTableName(),
188        constraintViolated);
189    }
190  }
191
192  @Test
193  public void testValidQuotas() throws Exception {
194    boolean exceptionCaught = false;
195    FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
196    Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
197    NamespaceDescriptor nspDesc =
198        NamespaceDescriptor.create(prefix + "vq1")
199            .addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "hihdufh")
200            .addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "2").build();
201    try {
202      ADMIN.createNamespace(nspDesc);
203    } catch (Exception exp) {
204      LOG.warn(exp.toString(), exp);
205      exceptionCaught = true;
206    } finally {
207      assertTrue(exceptionCaught);
208      assertFalse(fs.exists(CommonFSUtils.getNamespaceDir(rootDir, nspDesc.getName())));
209    }
210    nspDesc =
211        NamespaceDescriptor.create(prefix + "vq2")
212            .addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "-456")
213            .addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "2").build();
214    try {
215      ADMIN.createNamespace(nspDesc);
216    } catch (Exception exp) {
217      LOG.warn(exp.toString(), exp);
218      exceptionCaught = true;
219    } finally {
220      assertTrue(exceptionCaught);
221      assertFalse(fs.exists(CommonFSUtils.getNamespaceDir(rootDir, nspDesc.getName())));
222    }
223    nspDesc =
224        NamespaceDescriptor.create(prefix + "vq3")
225            .addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "10")
226            .addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "sciigd").build();
227    try {
228      ADMIN.createNamespace(nspDesc);
229    } catch (Exception exp) {
230      LOG.warn(exp.toString(), exp);
231      exceptionCaught = true;
232    } finally {
233      assertTrue(exceptionCaught);
234      assertFalse(fs.exists(CommonFSUtils.getNamespaceDir(rootDir, nspDesc.getName())));
235    }
236    nspDesc =
237        NamespaceDescriptor.create(prefix + "vq4")
238            .addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "10")
239            .addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "-1500").build();
240    try {
241      ADMIN.createNamespace(nspDesc);
242    } catch (Exception exp) {
243      LOG.warn(exp.toString(), exp);
244      exceptionCaught = true;
245    } finally {
246      assertTrue(exceptionCaught);
247      assertFalse(fs.exists(CommonFSUtils.getNamespaceDir(rootDir, nspDesc.getName())));
248    }
249  }
250
251  @Test
252  public void testDeleteTable() throws Exception {
253    String namespace = prefix + "_dummy";
254    NamespaceDescriptor nspDesc =
255        NamespaceDescriptor.create(namespace)
256            .addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "100")
257            .addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "3").build();
258    ADMIN.createNamespace(nspDesc);
259    assertNotNull("Namespace descriptor found null.", ADMIN.getNamespaceDescriptor(namespace));
260    NamespaceTableAndRegionInfo stateInfo = getNamespaceState(nspDesc.getName());
261    assertNotNull("Namespace state found null for " + namespace, stateInfo);
262    HColumnDescriptor fam1 = new HColumnDescriptor("fam1");
263    HTableDescriptor tableDescOne =
264        new HTableDescriptor(TableName.valueOf(namespace + TableName.NAMESPACE_DELIM + "table1"));
265    tableDescOne.addFamily(fam1);
266    HTableDescriptor tableDescTwo =
267        new HTableDescriptor(TableName.valueOf(namespace + TableName.NAMESPACE_DELIM + "table2"));
268    tableDescTwo.addFamily(fam1);
269    ADMIN.createTable(tableDescOne);
270    ADMIN.createTable(tableDescTwo, Bytes.toBytes("AAA"), Bytes.toBytes("ZZZ"), 5);
271    stateInfo = getNamespaceState(nspDesc.getName());
272    assertNotNull("Namespace state found to be null.", stateInfo);
273    assertEquals(2, stateInfo.getTables().size());
274    assertEquals(5, stateInfo.getRegionCountOfTable(tableDescTwo.getTableName()));
275    assertEquals(6, stateInfo.getRegionCount());
276    ADMIN.disableTable(tableDescOne.getTableName());
277    deleteTable(tableDescOne.getTableName());
278    stateInfo = getNamespaceState(nspDesc.getName());
279    assertNotNull("Namespace state found to be null.", stateInfo);
280    assertEquals(5, stateInfo.getRegionCount());
281    assertEquals(1, stateInfo.getTables().size());
282    ADMIN.disableTable(tableDescTwo.getTableName());
283    deleteTable(tableDescTwo.getTableName());
284    ADMIN.deleteNamespace(namespace);
285    stateInfo = getNamespaceState(namespace);
286    assertNull("Namespace state not found to be null.", stateInfo);
287  }
288
289  public static class CPRegionServerObserver
290      implements RegionServerCoprocessor, RegionServerObserver {
291    private volatile boolean shouldFailMerge = false;
292
293    public void failMerge(boolean fail) {
294      shouldFailMerge = fail;
295    }
296
297    private boolean triggered = false;
298
299    public synchronized void waitUtilTriggered() throws InterruptedException {
300      while (!triggered) {
301        wait();
302      }
303    }
304
305    @Override
306    public Optional<RegionServerObserver> getRegionServerObserver() {
307      return Optional.of(this);
308    }
309  }
310
311  public static class CPMasterObserver implements MasterCoprocessor, MasterObserver {
312    private volatile boolean shouldFailMerge = false;
313
314    public void failMerge(boolean fail) {
315      shouldFailMerge = fail;
316    }
317
318    @Override
319    public Optional<MasterObserver> getMasterObserver() {
320      return Optional.of(this);
321    }
322
323    @Override
324    public synchronized void preMergeRegionsAction(
325        final ObserverContext<MasterCoprocessorEnvironment> ctx,
326        final RegionInfo[] regionsToMerge) throws IOException {
327      notifyAll();
328      if (shouldFailMerge) {
329        throw new IOException("fail merge");
330      }
331    }
332  }
333
334  @Test
335  public void testRegionMerge() throws Exception {
336    String nsp1 = prefix + "_regiontest";
337    final int initialRegions = 3;
338    NamespaceDescriptor nspDesc =
339        NamespaceDescriptor.create(nsp1)
340            .addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "" + initialRegions)
341            .addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "2").build();
342    ADMIN.createNamespace(nspDesc);
343    final TableName tableTwo = TableName.valueOf(nsp1 + TableName.NAMESPACE_DELIM + "table2");
344    byte[] columnFamily = Bytes.toBytes("info");
345    HTableDescriptor tableDescOne = new HTableDescriptor(tableTwo);
346    tableDescOne.addFamily(new HColumnDescriptor(columnFamily));
347    ADMIN.createTable(tableDescOne, Bytes.toBytes("0"), Bytes.toBytes("9"), initialRegions);
348    Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
349    try (Table table = connection.getTable(tableTwo)) {
350      UTIL.loadNumericRows(table, Bytes.toBytes("info"), 1000, 1999);
351    }
352    ADMIN.flush(tableTwo);
353    List<RegionInfo> hris = ADMIN.getRegions(tableTwo);
354    assertEquals(initialRegions, hris.size());
355    Collections.sort(hris, RegionInfo.COMPARATOR);
356    Future<?> f = ADMIN.mergeRegionsAsync(
357      hris.get(0).getEncodedNameAsBytes(),
358      hris.get(1).getEncodedNameAsBytes(),
359      false);
360    f.get(10, TimeUnit.SECONDS);
361
362    hris = ADMIN.getRegions(tableTwo);
363    assertEquals(initialRegions - 1, hris.size());
364    Collections.sort(hris, RegionInfo.COMPARATOR);
365    byte[] splitKey = Bytes.toBytes("3");
366    HRegion regionToSplit = UTIL.getMiniHBaseCluster().getRegions(tableTwo).stream()
367      .filter(r -> r.getRegionInfo().containsRow(splitKey)).findFirst().get();
368    regionToSplit.compact(true);
369    // Waiting for compaction to finish
370    UTIL.waitFor(30000, new Waiter.Predicate<Exception>() {
371      @Override
372      public boolean evaluate() throws Exception {
373        return (CompactionState.NONE == ADMIN
374            .getCompactionStateForRegion(regionToSplit.getRegionInfo().getRegionName()));
375      }
376    });
377
378    // Cleaning compacted references for split to proceed
379    regionToSplit.getStores().stream().forEach(s -> {
380      try {
381        s.closeAndArchiveCompactedFiles();
382      } catch (IOException e1) {
383        LOG.error("Error whiling cleaning compacted file");
384      }
385    });
386    // the above compact may quit immediately if there is a compaction ongoing, so here we need to
387    // wait a while to let the ongoing compaction finish.
388    UTIL.waitFor(10000, regionToSplit::isSplittable);
389    ADMIN.splitRegionAsync(regionToSplit.getRegionInfo().getRegionName(), splitKey).get(10,
390      TimeUnit.SECONDS);
391    hris = ADMIN.getRegions(tableTwo);
392    assertEquals(initialRegions, hris.size());
393    Collections.sort(hris, RegionInfo.COMPARATOR);
394
395    // Fail region merge through Coprocessor hook
396    MiniHBaseCluster cluster = UTIL.getHBaseCluster();
397    MasterCoprocessorHost cpHost = cluster.getMaster().getMasterCoprocessorHost();
398    Coprocessor coprocessor = cpHost.findCoprocessor(CPMasterObserver.class);
399    CPMasterObserver masterObserver = (CPMasterObserver) coprocessor;
400    masterObserver.failMerge(true);
401
402    f = ADMIN.mergeRegionsAsync(
403      hris.get(1).getEncodedNameAsBytes(),
404      hris.get(2).getEncodedNameAsBytes(),
405      false);
406    try {
407      f.get(10, TimeUnit.SECONDS);
408      fail("Merge was supposed to fail!");
409    } catch (ExecutionException ee) {
410      // Expected.
411    }
412    hris = ADMIN.getRegions(tableTwo);
413    assertEquals(initialRegions, hris.size());
414    Collections.sort(hris, RegionInfo.COMPARATOR);
415    // verify that we cannot split
416    try {
417      ADMIN.split(tableTwo, Bytes.toBytes("6"));
418      fail();
419    } catch (DoNotRetryRegionException e) {
420      // Expected
421    }
422    Thread.sleep(2000);
423    assertEquals(initialRegions, ADMIN.getRegions(tableTwo).size());
424  }
425
426  /*
427   * Create a table and make sure that the table creation fails after adding this table entry into
428   * namespace quota cache. Now correct the failure and recreate the table with same name.
429   * HBASE-13394
430   */
431  @Test
432  public void testRecreateTableWithSameNameAfterFirstTimeFailure() throws Exception {
433    String nsp1 = prefix + "_testRecreateTable";
434    NamespaceDescriptor nspDesc =
435        NamespaceDescriptor.create(nsp1)
436            .addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "20")
437            .addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "1").build();
438    ADMIN.createNamespace(nspDesc);
439    final TableName tableOne = TableName.valueOf(nsp1 + TableName.NAMESPACE_DELIM + "table1");
440    byte[] columnFamily = Bytes.toBytes("info");
441    HTableDescriptor tableDescOne = new HTableDescriptor(tableOne);
442    tableDescOne.addFamily(new HColumnDescriptor(columnFamily));
443    MasterSyncObserver.throwExceptionInPreCreateTableAction = true;
444    try {
445      try {
446        ADMIN.createTable(tableDescOne);
447        fail("Table " + tableOne.toString() + "creation should fail.");
448      } catch (Exception exp) {
449        LOG.error(exp.toString(), exp);
450      }
451      assertFalse(ADMIN.tableExists(tableOne));
452
453      NamespaceTableAndRegionInfo nstate = getNamespaceState(nsp1);
454      assertEquals("First table creation failed in namespace so number of tables in namespace "
455          + "should be 0.", 0, nstate.getTables().size());
456
457      MasterSyncObserver.throwExceptionInPreCreateTableAction = false;
458      try {
459        ADMIN.createTable(tableDescOne);
460      } catch (Exception e) {
461        fail("Table " + tableOne.toString() + "creation should succeed.");
462        LOG.error(e.toString(), e);
463      }
464      assertTrue(ADMIN.tableExists(tableOne));
465      nstate = getNamespaceState(nsp1);
466      assertEquals("First table was created successfully so table size in namespace should "
467          + "be one now.", 1, nstate.getTables().size());
468    } finally {
469      MasterSyncObserver.throwExceptionInPreCreateTableAction = false;
470      if (ADMIN.tableExists(tableOne)) {
471        ADMIN.disableTable(tableOne);
472        deleteTable(tableOne);
473      }
474      ADMIN.deleteNamespace(nsp1);
475    }
476  }
477
478  private NamespaceTableAndRegionInfo getNamespaceState(String namespace) throws KeeperException,
479      IOException {
480    return getQuotaManager().getState(namespace);
481  }
482
483  public static class CustomObserver implements RegionCoprocessor, RegionObserver {
484    volatile CountDownLatch postCompact;
485
486    @Override
487    public void postCompact(ObserverContext<RegionCoprocessorEnvironment> e, Store store,
488        StoreFile resultFile, CompactionLifeCycleTracker tracker, CompactionRequest request)
489        throws IOException {
490      postCompact.countDown();
491    }
492
493    @Override
494    public void start(CoprocessorEnvironment e) throws IOException {
495      postCompact = new CountDownLatch(1);
496    }
497
498    @Override
499    public Optional<RegionObserver> getRegionObserver() {
500      return Optional.of(this);
501    }
502  }
503
504  @Test
505  public void testStatePreserve() throws Exception {
506    final String nsp1 = prefix + "_testStatePreserve";
507    NamespaceDescriptor nspDesc = NamespaceDescriptor.create(nsp1)
508        .addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "20")
509        .addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "10").build();
510    ADMIN.createNamespace(nspDesc);
511    TableName tableOne = TableName.valueOf(nsp1 + TableName.NAMESPACE_DELIM + "table1");
512    TableName tableTwo = TableName.valueOf(nsp1 + TableName.NAMESPACE_DELIM + "table2");
513    TableName tableThree = TableName.valueOf(nsp1 + TableName.NAMESPACE_DELIM + "table3");
514    HColumnDescriptor fam1 = new HColumnDescriptor("fam1");
515    HTableDescriptor tableDescOne = new HTableDescriptor(tableOne);
516    tableDescOne.addFamily(fam1);
517    HTableDescriptor tableDescTwo = new HTableDescriptor(tableTwo);
518    tableDescTwo.addFamily(fam1);
519    HTableDescriptor tableDescThree = new HTableDescriptor(tableThree);
520    tableDescThree.addFamily(fam1);
521    ADMIN.createTable(tableDescOne, Bytes.toBytes("1"), Bytes.toBytes("1000"), 3);
522    ADMIN.createTable(tableDescTwo, Bytes.toBytes("1"), Bytes.toBytes("1000"), 3);
523    ADMIN.createTable(tableDescThree, Bytes.toBytes("1"), Bytes.toBytes("1000"), 4);
524    ADMIN.disableTable(tableThree);
525    deleteTable(tableThree);
526    // wait for chore to complete
527    UTIL.waitFor(1000, new Waiter.Predicate<Exception>() {
528      @Override
529      public boolean evaluate() throws Exception {
530        return (getNamespaceState(nsp1).getTables().size() == 2);
531      }
532    });
533    NamespaceTableAndRegionInfo before = getNamespaceState(nsp1);
534    killActiveMaster();
535    NamespaceTableAndRegionInfo after = getNamespaceState(nsp1);
536    assertEquals("Expected: " + before.getTables() + " Found: " + after.getTables(), before
537        .getTables().size(), after.getTables().size());
538  }
539
540  public static void waitForQuotaInitialize(final HBaseTestingUtility util) throws Exception {
541    util.waitFor(60000, new Waiter.Predicate<Exception>() {
542      @Override
543      public boolean evaluate() throws Exception {
544        HMaster master = util.getHBaseCluster().getMaster();
545        if (master == null) {
546          return false;
547        }
548        MasterQuotaManager quotaManager = master.getMasterQuotaManager();
549        return quotaManager != null && quotaManager.isQuotaInitialized();
550      }
551    });
552  }
553
554  private void killActiveMaster() throws Exception {
555    UTIL.getHBaseCluster().getMaster(0).stop("Stopping to start again");
556    UTIL.getHBaseCluster().waitOnMaster(0);
557    waitForQuotaInitialize(UTIL);
558  }
559
560  private NamespaceAuditor getQuotaManager() {
561    return UTIL.getHBaseCluster().getMaster()
562        .getMasterQuotaManager().getNamespaceQuotaManager();
563  }
564
565  public static class MasterSyncObserver implements MasterCoprocessor, MasterObserver {
566    volatile CountDownLatch tableDeletionLatch;
567    static boolean throwExceptionInPreCreateTableAction;
568
569    @Override
570    public Optional<MasterObserver> getMasterObserver() {
571      return Optional.of(this);
572    }
573
574    @Override
575    public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
576        TableName tableName) throws IOException {
577      tableDeletionLatch = new CountDownLatch(1);
578    }
579
580    @Override
581    public void postCompletedDeleteTableAction(
582        final ObserverContext<MasterCoprocessorEnvironment> ctx,
583        final TableName tableName) throws IOException {
584      tableDeletionLatch.countDown();
585    }
586
587    @Override
588    public void preCreateTableAction(ObserverContext<MasterCoprocessorEnvironment> ctx,
589        TableDescriptor desc, RegionInfo[] regions) throws IOException {
590      if (throwExceptionInPreCreateTableAction) {
591        throw new IOException("Throw exception as it is demanded.");
592      }
593    }
594  }
595
596  private void deleteTable(final TableName tableName) throws Exception {
597    // NOTE: We need a latch because admin is not sync,
598    // so the postOp coprocessor method may be called after the admin operation returned.
599    MasterSyncObserver observer = UTIL.getHBaseCluster().getMaster()
600      .getMasterCoprocessorHost().findCoprocessor(MasterSyncObserver.class);
601    ADMIN.deleteTable(tableName);
602    observer.tableDeletionLatch.await();
603  }
604
605  @Test(expected = QuotaExceededException.class)
606  public void testExceedTableQuotaInNamespace() throws Exception {
607    String nsp = prefix + "_testExceedTableQuotaInNamespace";
608    NamespaceDescriptor nspDesc =
609        NamespaceDescriptor.create(nsp).addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "1")
610            .build();
611    ADMIN.createNamespace(nspDesc);
612    assertNotNull("Namespace descriptor found null.", ADMIN.getNamespaceDescriptor(nsp));
613    assertEquals(3, ADMIN.listNamespaceDescriptors().length);
614    HColumnDescriptor fam1 = new HColumnDescriptor("fam1");
615    HTableDescriptor tableDescOne =
616        new HTableDescriptor(TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table1"));
617    tableDescOne.addFamily(fam1);
618    HTableDescriptor tableDescTwo =
619        new HTableDescriptor(TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table2"));
620    tableDescTwo.addFamily(fam1);
621    ADMIN.createTable(tableDescOne);
622    ADMIN.createTable(tableDescTwo, Bytes.toBytes("AAA"), Bytes.toBytes("ZZZ"), 4);
623  }
624
625  @Test(expected = QuotaExceededException.class)
626  public void testCloneSnapshotQuotaExceed() throws Exception {
627    String nsp = prefix + "_testTableQuotaExceedWithCloneSnapshot";
628    NamespaceDescriptor nspDesc =
629        NamespaceDescriptor.create(nsp).addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "1")
630            .build();
631    ADMIN.createNamespace(nspDesc);
632    assertNotNull("Namespace descriptor found null.", ADMIN.getNamespaceDescriptor(nsp));
633    TableName tableName = TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table1");
634    TableName cloneTableName = TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table2");
635    HColumnDescriptor fam1 = new HColumnDescriptor("fam1");
636    HTableDescriptor tableDescOne = new HTableDescriptor(tableName);
637    tableDescOne.addFamily(fam1);
638    ADMIN.createTable(tableDescOne);
639    String snapshot = "snapshot_testTableQuotaExceedWithCloneSnapshot";
640    ADMIN.snapshot(snapshot, tableName);
641    ADMIN.cloneSnapshot(snapshot, cloneTableName);
642    ADMIN.deleteSnapshot(snapshot);
643  }
644
645  @Test
646  public void testCloneSnapshot() throws Exception {
647    String nsp = prefix + "_testCloneSnapshot";
648    NamespaceDescriptor nspDesc =
649        NamespaceDescriptor.create(nsp).addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "2")
650            .addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "20").build();
651    ADMIN.createNamespace(nspDesc);
652    assertNotNull("Namespace descriptor found null.", ADMIN.getNamespaceDescriptor(nsp));
653    TableName tableName = TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table1");
654    TableName cloneTableName = TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table2");
655
656    HColumnDescriptor fam1 = new HColumnDescriptor("fam1");
657    HTableDescriptor tableDescOne = new HTableDescriptor(tableName);
658    tableDescOne.addFamily(fam1);
659
660    ADMIN.createTable(tableDescOne, Bytes.toBytes("AAA"), Bytes.toBytes("ZZZ"), 4);
661    String snapshot = "snapshot_testCloneSnapshot";
662    ADMIN.snapshot(snapshot, tableName);
663    ADMIN.cloneSnapshot(snapshot, cloneTableName);
664
665    int tableLength;
666    try (RegionLocator locator = ADMIN.getConnection().getRegionLocator(tableName)) {
667      tableLength = locator.getStartKeys().length;
668    }
669    assertEquals(tableName.getNameAsString() + " should have four regions.", 4, tableLength);
670
671    try (RegionLocator locator = ADMIN.getConnection().getRegionLocator(cloneTableName)) {
672      tableLength = locator.getStartKeys().length;
673    }
674    assertEquals(cloneTableName.getNameAsString() + " should have four regions.", 4, tableLength);
675
676    NamespaceTableAndRegionInfo nstate = getNamespaceState(nsp);
677    assertEquals("Total tables count should be 2.", 2, nstate.getTables().size());
678    assertEquals("Total regions count should be.", 8, nstate.getRegionCount());
679
680    ADMIN.deleteSnapshot(snapshot);
681  }
682
683  @Test
684  public void testRestoreSnapshot() throws Exception {
685    String nsp = prefix + "_testRestoreSnapshot";
686    NamespaceDescriptor nspDesc =
687        NamespaceDescriptor.create(nsp)
688            .addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "10").build();
689    ADMIN.createNamespace(nspDesc);
690    assertNotNull("Namespace descriptor found null.", ADMIN.getNamespaceDescriptor(nsp));
691    TableName tableName1 = TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table1");
692    HTableDescriptor tableDescOne = new HTableDescriptor(tableName1);
693    HColumnDescriptor fam1 = new HColumnDescriptor("fam1");
694    tableDescOne.addFamily(fam1);
695    ADMIN.createTable(tableDescOne, Bytes.toBytes("AAA"), Bytes.toBytes("ZZZ"), 4);
696
697    NamespaceTableAndRegionInfo nstate = getNamespaceState(nsp);
698    assertEquals("Intial region count should be 4.", 4, nstate.getRegionCount());
699
700    String snapshot = "snapshot_testRestoreSnapshot";
701    ADMIN.snapshot(snapshot, tableName1);
702
703    List<HRegionInfo> regions = ADMIN.getTableRegions(tableName1);
704    Collections.sort(regions);
705
706    ADMIN.split(tableName1, Bytes.toBytes("JJJ"));
707    Thread.sleep(2000);
708    assertEquals("Total regions count should be 5.", 5, nstate.getRegionCount());
709
710    ADMIN.disableTable(tableName1);
711    ADMIN.restoreSnapshot(snapshot);
712
713    assertEquals("Total regions count should be 4 after restore.", 4, nstate.getRegionCount());
714
715    ADMIN.enableTable(tableName1);
716    ADMIN.deleteSnapshot(snapshot);
717  }
718
719  @Test
720  public void testRestoreSnapshotQuotaExceed() throws Exception {
721    String nsp = prefix + "_testRestoreSnapshotQuotaExceed";
722    NamespaceDescriptor nspDesc =
723        NamespaceDescriptor.create(nsp)
724            .addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "10").build();
725    ADMIN.createNamespace(nspDesc);
726    NamespaceDescriptor ndesc = ADMIN.getNamespaceDescriptor(nsp);
727    assertNotNull("Namespace descriptor found null.", ndesc);
728    TableName tableName1 = TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table1");
729    HTableDescriptor tableDescOne = new HTableDescriptor(tableName1);
730    HColumnDescriptor fam1 = new HColumnDescriptor("fam1");
731    tableDescOne.addFamily(fam1);
732
733    ADMIN.createTable(tableDescOne, Bytes.toBytes("AAA"), Bytes.toBytes("ZZZ"), 4);
734
735    NamespaceTableAndRegionInfo nstate = getNamespaceState(nsp);
736    assertEquals("Intial region count should be 4.", 4, nstate.getRegionCount());
737
738    String snapshot = "snapshot_testRestoreSnapshotQuotaExceed";
739    // snapshot has 4 regions
740    ADMIN.snapshot(snapshot, tableName1);
741    // recreate table with 1 region and set max regions to 3 for namespace
742    ADMIN.disableTable(tableName1);
743    ADMIN.deleteTable(tableName1);
744    ADMIN.createTable(tableDescOne);
745    ndesc.setConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "3");
746    ADMIN.modifyNamespace(ndesc);
747
748    ADMIN.disableTable(tableName1);
749    try {
750      ADMIN.restoreSnapshot(snapshot);
751      fail("Region quota is exceeded so QuotaExceededException should be thrown but HBaseAdmin"
752          + " wraps IOException into RestoreSnapshotException");
753    } catch (RestoreSnapshotException ignore) {
754      assertTrue(ignore.getCause() instanceof QuotaExceededException);
755    }
756    assertEquals(1, getNamespaceState(nsp).getRegionCount());
757    ADMIN.enableTable(tableName1);
758    ADMIN.deleteSnapshot(snapshot);
759  }
760}