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