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