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