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