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.regionserver;
019
020import static org.apache.hadoop.hbase.regionserver.Store.PRIORITY_USER;
021
022import java.io.IOException;
023import java.security.Key;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.Date;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.List;
031import java.util.NavigableSet;
032import java.util.Optional;
033import java.util.Set;
034import java.util.concurrent.ConcurrentSkipListSet;
035import javax.crypto.spec.SecretKeySpec;
036import org.apache.hadoop.conf.Configuration;
037import org.apache.hadoop.fs.FileStatus;
038import org.apache.hadoop.fs.FileSystem;
039import org.apache.hadoop.fs.Path;
040import org.apache.hadoop.hbase.ArrayBackedTag;
041import org.apache.hadoop.hbase.Cell;
042import org.apache.hadoop.hbase.CellComparatorImpl;
043import org.apache.hadoop.hbase.CellUtil;
044import org.apache.hadoop.hbase.ExtendedCell;
045import org.apache.hadoop.hbase.HBaseClassTestRule;
046import org.apache.hadoop.hbase.HBaseConfiguration;
047import org.apache.hadoop.hbase.HBaseTestingUtil;
048import org.apache.hadoop.hbase.HConstants;
049import org.apache.hadoop.hbase.KeyValue;
050import org.apache.hadoop.hbase.TableName;
051import org.apache.hadoop.hbase.Tag;
052import org.apache.hadoop.hbase.TagType;
053import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
054import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
055import org.apache.hadoop.hbase.client.Get;
056import org.apache.hadoop.hbase.client.RegionInfo;
057import org.apache.hadoop.hbase.client.RegionInfoBuilder;
058import org.apache.hadoop.hbase.client.Scan;
059import org.apache.hadoop.hbase.client.TableDescriptor;
060import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
061import org.apache.hadoop.hbase.io.crypto.MockAesKeyProvider;
062import org.apache.hadoop.hbase.io.crypto.aes.AES;
063import org.apache.hadoop.hbase.io.hfile.HFile;
064import org.apache.hadoop.hbase.mob.MobConstants;
065import org.apache.hadoop.hbase.mob.MobFileCache;
066import org.apache.hadoop.hbase.mob.MobUtils;
067import org.apache.hadoop.hbase.monitoring.MonitoredTask;
068import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext;
069import org.apache.hadoop.hbase.regionserver.compactions.CompactionLifeCycleTracker;
070import org.apache.hadoop.hbase.regionserver.throttle.NoLimitThroughputController;
071import org.apache.hadoop.hbase.security.EncryptionUtil;
072import org.apache.hadoop.hbase.security.User;
073import org.apache.hadoop.hbase.testclassification.MediumTests;
074import org.apache.hadoop.hbase.util.Bytes;
075import org.apache.hadoop.hbase.util.CommonFSUtils;
076import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
077import org.apache.hadoop.hbase.wal.WALFactory;
078import org.junit.Assert;
079import org.junit.Before;
080import org.junit.ClassRule;
081import org.junit.Rule;
082import org.junit.Test;
083import org.junit.experimental.categories.Category;
084import org.junit.rules.TestName;
085import org.mockito.Mockito;
086import org.slf4j.Logger;
087import org.slf4j.LoggerFactory;
088
089@Category(MediumTests.class)
090public class TestHMobStore {
091
092  @ClassRule
093  public static final HBaseClassTestRule CLASS_RULE =
094    HBaseClassTestRule.forClass(TestHMobStore.class);
095
096  public static final Logger LOG = LoggerFactory.getLogger(TestHMobStore.class);
097  @Rule
098  public TestName name = new TestName();
099
100  private HMobStore store;
101  private HRegion region;
102  private FileSystem fs;
103  private byte[] table = Bytes.toBytes("table");
104  private byte[] family = Bytes.toBytes("family");
105  private byte[] row = Bytes.toBytes("row");
106  private byte[] row2 = Bytes.toBytes("row2");
107  private byte[] qf1 = Bytes.toBytes("qf1");
108  private byte[] qf2 = Bytes.toBytes("qf2");
109  private byte[] qf3 = Bytes.toBytes("qf3");
110  private byte[] qf4 = Bytes.toBytes("qf4");
111  private byte[] qf5 = Bytes.toBytes("qf5");
112  private byte[] qf6 = Bytes.toBytes("qf6");
113  private byte[] value = Bytes.toBytes("value");
114  private byte[] value2 = Bytes.toBytes("value2");
115  private Path mobFilePath;
116  private Date currentDate = new Date();
117  private ExtendedCell seekKey1;
118  private ExtendedCell seekKey2;
119  private ExtendedCell seekKey3;
120  private NavigableSet<byte[]> qualifiers = new ConcurrentSkipListSet<>(Bytes.BYTES_COMPARATOR);
121  private List<ExtendedCell> expected = new ArrayList<>();
122  private long id = EnvironmentEdgeManager.currentTime();
123  private Get get = new Get(row);
124  private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
125  private final String DIR = TEST_UTIL.getDataTestDir("TestHMobStore").toString();
126
127  /**
128   * Setup
129   */
130  @Before
131  public void setUp() throws Exception {
132    qualifiers.add(qf1);
133    qualifiers.add(qf3);
134    qualifiers.add(qf5);
135
136    Iterator<byte[]> iter = qualifiers.iterator();
137    while (iter.hasNext()) {
138      byte[] next = iter.next();
139      expected.add(new KeyValue(row, family, next, 1, value));
140      get.addColumn(family, next);
141      get.readAllVersions();
142    }
143  }
144
145  private void init(String methodName, Configuration conf, boolean testStore) throws IOException {
146    ColumnFamilyDescriptor cfd = ColumnFamilyDescriptorBuilder.newBuilder(family)
147      .setMobEnabled(true).setMobThreshold(3L).setMaxVersions(4).build();
148    init(methodName, conf, cfd, testStore);
149  }
150
151  private void init(String methodName, Configuration conf, ColumnFamilyDescriptor cfd,
152    boolean testStore) throws IOException {
153    TableDescriptor td =
154      TableDescriptorBuilder.newBuilder(TableName.valueOf(table)).setColumnFamily(cfd).build();
155
156    // Setting up tje Region and Store
157    Path basedir = new Path(DIR + methodName);
158    Path tableDir = CommonFSUtils.getTableDir(basedir, td.getTableName());
159    String logName = "logs";
160    Path logdir = new Path(basedir, logName);
161    FileSystem fs = FileSystem.get(conf);
162    fs.delete(logdir, true);
163
164    RegionInfo info = RegionInfoBuilder.newBuilder(td.getTableName()).build();
165    ChunkCreator.initialize(MemStoreLAB.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null,
166      MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT);
167    final Configuration walConf = new Configuration(conf);
168    CommonFSUtils.setRootDir(walConf, basedir);
169    final WALFactory wals = new WALFactory(walConf, methodName);
170    region = new HRegion(tableDir, wals.getWAL(info), fs, conf, info, td, null);
171    region.setMobFileCache(new MobFileCache(conf));
172    store = new HMobStore(region, cfd, conf, false);
173    if (testStore) {
174      init(conf, cfd);
175    }
176  }
177
178  private void init(Configuration conf, ColumnFamilyDescriptor cfd) throws IOException {
179    Path basedir = CommonFSUtils.getRootDir(conf);
180    fs = FileSystem.get(conf);
181    Path homePath =
182      new Path(basedir, Bytes.toString(family) + Path.SEPARATOR + Bytes.toString(family));
183    fs.mkdirs(homePath);
184
185    KeyValue key1 = new KeyValue(row, family, qf1, 1, value);
186    KeyValue key2 = new KeyValue(row, family, qf2, 1, value);
187    KeyValue key3 = new KeyValue(row2, family, qf3, 1, value2);
188    KeyValue[] keys = new KeyValue[] { key1, key2, key3 };
189    int maxKeyCount = keys.length;
190    StoreFileWriter mobWriter = store.createWriterInTmp(currentDate, maxKeyCount,
191      cfd.getCompactionCompressionType(), region.getRegionInfo().getStartKey(), false);
192    mobFilePath = mobWriter.getPath();
193
194    mobWriter.append(key1);
195    mobWriter.append(key2);
196    mobWriter.append(key3);
197    mobWriter.close();
198
199    String targetPathName = MobUtils.formatDate(currentDate);
200    byte[] referenceValue = Bytes.toBytes(targetPathName + Path.SEPARATOR + mobFilePath.getName());
201    Tag tableNameTag =
202      new ArrayBackedTag(TagType.MOB_TABLE_NAME_TAG_TYPE, store.getTableName().getName());
203    KeyValue kv1 = new KeyValue(row, family, qf1, Long.MAX_VALUE, referenceValue);
204    KeyValue kv2 = new KeyValue(row, family, qf2, Long.MAX_VALUE, referenceValue);
205    KeyValue kv3 = new KeyValue(row2, family, qf3, Long.MAX_VALUE, referenceValue);
206    seekKey1 = MobUtils.createMobRefCell(kv1, referenceValue, tableNameTag);
207    seekKey2 = MobUtils.createMobRefCell(kv2, referenceValue, tableNameTag);
208    seekKey3 = MobUtils.createMobRefCell(kv3, referenceValue, tableNameTag);
209  }
210
211  /**
212   * Getting data from memstore
213   */
214  @Test
215  public void testGetFromMemStore() throws IOException {
216    final Configuration conf = HBaseConfiguration.create();
217    init(name.getMethodName(), conf, false);
218
219    // Put data in memstore
220    this.store.add(new KeyValue(row, family, qf1, 1, value), null);
221    this.store.add(new KeyValue(row, family, qf2, 1, value), null);
222    this.store.add(new KeyValue(row, family, qf3, 1, value), null);
223    this.store.add(new KeyValue(row, family, qf4, 1, value), null);
224    this.store.add(new KeyValue(row, family, qf5, 1, value), null);
225    this.store.add(new KeyValue(row, family, qf6, 1, value), null);
226
227    Scan scan = new Scan(get);
228    InternalScanner scanner = (InternalScanner) store.getScanner(scan,
229      scan.getFamilyMap().get(store.getColumnFamilyDescriptor().getName()), 0);
230
231    List<Cell> results = new ArrayList<>();
232    scanner.next(results);
233    Collections.sort(results, CellComparatorImpl.COMPARATOR);
234    scanner.close();
235
236    // Compare
237    Assert.assertEquals(expected.size(), results.size());
238    for (int i = 0; i < results.size(); i++) {
239      // Verify the values
240      Assert.assertEquals(expected.get(i), results.get(i));
241    }
242  }
243
244  /**
245   * Getting MOB data from files
246   */
247  @Test
248  public void testGetFromFiles() throws IOException {
249    final Configuration conf = TEST_UTIL.getConfiguration();
250    init(name.getMethodName(), conf, false);
251
252    // Put data in memstore
253    this.store.add(new KeyValue(row, family, qf1, 1, value), null);
254    this.store.add(new KeyValue(row, family, qf2, 1, value), null);
255    // flush
256    flush(1);
257
258    // Add more data
259    this.store.add(new KeyValue(row, family, qf3, 1, value), null);
260    this.store.add(new KeyValue(row, family, qf4, 1, value), null);
261    // flush
262    flush(2);
263
264    // Add more data
265    this.store.add(new KeyValue(row, family, qf5, 1, value), null);
266    this.store.add(new KeyValue(row, family, qf6, 1, value), null);
267    // flush
268    flush(3);
269
270    Scan scan = new Scan(get);
271    InternalScanner scanner = (InternalScanner) store.getScanner(scan,
272      scan.getFamilyMap().get(store.getColumnFamilyDescriptor().getName()), 0);
273
274    List<Cell> results = new ArrayList<>();
275    scanner.next(results);
276    Collections.sort(results, CellComparatorImpl.COMPARATOR);
277    scanner.close();
278
279    // Compare
280    Assert.assertEquals(expected.size(), results.size());
281    for (int i = 0; i < results.size(); i++) {
282      Assert.assertEquals(expected.get(i), results.get(i));
283    }
284  }
285
286  /**
287   * Getting the reference data from files
288   */
289  @Test
290  public void testGetReferencesFromFiles() throws IOException {
291    final Configuration conf = HBaseConfiguration.create();
292    init(name.getMethodName(), conf, false);
293
294    // Put data in memstore
295    this.store.add(new KeyValue(row, family, qf1, 1, value), null);
296    this.store.add(new KeyValue(row, family, qf2, 1, value), null);
297    // flush
298    flush(1);
299
300    // Add more data
301    this.store.add(new KeyValue(row, family, qf3, 1, value), null);
302    this.store.add(new KeyValue(row, family, qf4, 1, value), null);
303    // flush
304    flush(2);
305
306    // Add more data
307    this.store.add(new KeyValue(row, family, qf5, 1, value), null);
308    this.store.add(new KeyValue(row, family, qf6, 1, value), null);
309    // flush
310    flush(3);
311
312    Scan scan = new Scan(get);
313    scan.setAttribute(MobConstants.MOB_SCAN_RAW, Bytes.toBytes(Boolean.TRUE));
314    InternalScanner scanner = (InternalScanner) store.getScanner(scan,
315      scan.getFamilyMap().get(store.getColumnFamilyDescriptor().getName()), 0);
316
317    List<ExtendedCell> results = new ArrayList<>();
318    scanner.next(results);
319    Collections.sort(results, CellComparatorImpl.COMPARATOR);
320    scanner.close();
321
322    // Compare
323    Assert.assertEquals(expected.size(), results.size());
324    for (int i = 0; i < results.size(); i++) {
325      ExtendedCell cell = results.get(i);
326      Assert.assertTrue(MobUtils.isMobReferenceCell(cell));
327    }
328  }
329
330  /**
331   * Getting data from memstore and files
332   */
333  @Test
334  public void testGetFromMemStoreAndFiles() throws IOException {
335
336    final Configuration conf = HBaseConfiguration.create();
337
338    init(name.getMethodName(), conf, false);
339
340    // Put data in memstore
341    this.store.add(new KeyValue(row, family, qf1, 1, value), null);
342    this.store.add(new KeyValue(row, family, qf2, 1, value), null);
343    // flush
344    flush(1);
345
346    // Add more data
347    this.store.add(new KeyValue(row, family, qf3, 1, value), null);
348    this.store.add(new KeyValue(row, family, qf4, 1, value), null);
349    // flush
350    flush(2);
351
352    // Add more data
353    this.store.add(new KeyValue(row, family, qf5, 1, value), null);
354    this.store.add(new KeyValue(row, family, qf6, 1, value), null);
355
356    Scan scan = new Scan(get);
357    InternalScanner scanner = (InternalScanner) store.getScanner(scan,
358      scan.getFamilyMap().get(store.getColumnFamilyDescriptor().getName()), 0);
359
360    List<Cell> results = new ArrayList<>();
361    scanner.next(results);
362    Collections.sort(results, CellComparatorImpl.COMPARATOR);
363    scanner.close();
364
365    // Compare
366    Assert.assertEquals(expected.size(), results.size());
367    for (int i = 0; i < results.size(); i++) {
368      Assert.assertEquals(expected.get(i), results.get(i));
369    }
370  }
371
372  /**
373   * Getting data from memstore and files
374   */
375  @Test
376  public void testMobCellSizeThreshold() throws IOException {
377    final Configuration conf = HBaseConfiguration.create();
378    ColumnFamilyDescriptor cfd = ColumnFamilyDescriptorBuilder.newBuilder(family)
379      .setMobEnabled(true).setMobThreshold(100).setMaxVersions(4).build();
380    init(name.getMethodName(), conf, cfd, false);
381
382    // Put data in memstore
383    this.store.add(new KeyValue(row, family, qf1, 1, value), null);
384    this.store.add(new KeyValue(row, family, qf2, 1, value), null);
385    // flush
386    flush(1);
387
388    // Add more data
389    this.store.add(new KeyValue(row, family, qf3, 1, value), null);
390    this.store.add(new KeyValue(row, family, qf4, 1, value), null);
391    // flush
392    flush(2);
393
394    // Add more data
395    this.store.add(new KeyValue(row, family, qf5, 1, value), null);
396    this.store.add(new KeyValue(row, family, qf6, 1, value), null);
397    // flush
398    flush(3);
399
400    Scan scan = new Scan(get);
401    scan.setAttribute(MobConstants.MOB_SCAN_RAW, Bytes.toBytes(Boolean.TRUE));
402    InternalScanner scanner = (InternalScanner) store.getScanner(scan,
403      scan.getFamilyMap().get(store.getColumnFamilyDescriptor().getName()), 0);
404
405    List<ExtendedCell> results = new ArrayList<>();
406    scanner.next(results);
407    Collections.sort(results, CellComparatorImpl.COMPARATOR);
408    scanner.close();
409
410    // Compare
411    Assert.assertEquals(expected.size(), results.size());
412    for (int i = 0; i < results.size(); i++) {
413      ExtendedCell cell = results.get(i);
414      // this is not mob reference cell.
415      Assert.assertFalse(MobUtils.isMobReferenceCell(cell));
416      Assert.assertEquals(expected.get(i), results.get(i));
417      Assert.assertEquals(100, store.getColumnFamilyDescriptor().getMobThreshold());
418    }
419  }
420
421  @Test
422  public void testCommitFile() throws Exception {
423    final Configuration conf = HBaseConfiguration.create();
424    init(name.getMethodName(), conf, true);
425    String targetPathName = MobUtils.formatDate(new Date());
426    Path targetPath =
427      new Path(store.getPath(), (targetPathName + Path.SEPARATOR + mobFilePath.getName()));
428    fs.delete(targetPath, true);
429    Assert.assertFalse(fs.exists(targetPath));
430    // commit file
431    store.commitFile(mobFilePath, targetPath);
432    Assert.assertTrue(fs.exists(targetPath));
433  }
434
435  @Test
436  public void testResolve() throws Exception {
437    final Configuration conf = HBaseConfiguration.create();
438    init(name.getMethodName(), conf, true);
439    String targetPathName = MobUtils.formatDate(currentDate);
440    Path targetPath = new Path(store.getPath(), targetPathName);
441    store.commitFile(mobFilePath, targetPath);
442    // resolve
443    Cell resultCell1 = store.resolve(seekKey1, false).getCell();
444    Cell resultCell2 = store.resolve(seekKey2, false).getCell();
445    Cell resultCell3 = store.resolve(seekKey3, false).getCell();
446    // compare
447    Assert.assertEquals(Bytes.toString(value), Bytes.toString(CellUtil.cloneValue(resultCell1)));
448    Assert.assertEquals(Bytes.toString(value), Bytes.toString(CellUtil.cloneValue(resultCell2)));
449    Assert.assertEquals(Bytes.toString(value2), Bytes.toString(CellUtil.cloneValue(resultCell3)));
450  }
451
452  @Test
453  public void testMobStoreScannerGetFilesRead() throws IOException {
454    doTestMobStoreScannerGetFilesRead(false);
455  }
456
457  @Test
458  public void testReversedMobStoreScannerGetFilesRead() throws IOException {
459    doTestMobStoreScannerGetFilesRead(true);
460  }
461
462  /**
463   * Utility method for getFilesRead tests on MOB store scanners. Uses values above mob threshold so
464   * DefaultMobStoreFlusher creates the mob file and refs.
465   */
466  private void doTestMobStoreScannerGetFilesRead(boolean reversed) throws IOException {
467    // Setup: conf, root dir, and MOB store init (mob threshold causes large values to go to MOB).
468    final Configuration conf = HBaseConfiguration.create();
469    Path basedir = new Path(DIR + name.getMethodName());
470    CommonFSUtils.setRootDir(conf, basedir);
471    init(name.getMethodName(), conf, false);
472
473    // Add values above MOB threshold and flush so DefaultMobStoreFlusher creates mob file and refs.
474    byte[] valueAboveThreshold = Bytes.toBytes("value"); // threshold in setup is 3 bytes
475    this.store.add(new KeyValue(row, family, qf1, 1, valueAboveThreshold), null);
476    this.store.add(new KeyValue(row, family, qf2, 1, valueAboveThreshold), null);
477    this.store.add(new KeyValue(row2, family, qf3, 1, valueAboveThreshold), null);
478    flush(1);
479
480    // Collect expected paths: store files (refs) plus actual MOB files under mob family path.
481    FileSystem storeFs = store.getFileSystem();
482    Set<Path> expectedFilePaths = new HashSet<>();
483    for (HStoreFile storeFile : this.store.getStorefiles()) {
484      expectedFilePaths.add(storeFs.makeQualified(storeFile.getPath()));
485    }
486    Path mobFamilyPath =
487      MobUtils.getMobFamilyPath(conf, TableName.valueOf(table), Bytes.toString(family));
488    if (storeFs.exists(mobFamilyPath)) {
489      FileStatus[] mobFiles = storeFs.listStatus(mobFamilyPath);
490      for (FileStatus f : mobFiles) {
491        if (!f.isDirectory()) {
492          expectedFilePaths.add(storeFs.makeQualified(f.getPath()));
493        }
494      }
495    }
496    Assert.assertTrue("Should have at least one store file and one mob file",
497      expectedFilePaths.size() >= 2);
498
499    // Build scan (optionally reversed) and target columns; get store scanner and verify type.
500    Scan scan = new Scan();
501    if (reversed) {
502      scan.setReversed(true);
503    }
504    scan.addColumn(family, qf1);
505    scan.addColumn(family, qf2);
506    scan.addColumn(family, qf3);
507    NavigableSet<byte[]> targetCols = new ConcurrentSkipListSet<>(Bytes.BYTES_COMPARATOR);
508    targetCols.add(qf1);
509    targetCols.add(qf2);
510    targetCols.add(qf3);
511
512    KeyValueScanner kvScanner = store.getScanner(scan, targetCols, 0);
513    if (reversed) {
514      Assert.assertTrue("Store scanner should be ReversedMobStoreScanner",
515        kvScanner instanceof ReversedMobStoreScanner);
516    } else {
517      Assert.assertTrue("Store scanner should be MobStoreScanner",
518        kvScanner instanceof MobStoreScanner);
519    }
520
521    // Before close: getFilesRead must be empty; then drain scanner to resolve MOB refs.
522    try {
523      Set<Path> filesReadBeforeClose = kvScanner.getFilesRead();
524      Assert.assertTrue("Should return empty set before closing", filesReadBeforeClose.isEmpty());
525      Assert.assertEquals("Should have 0 files before closing", 0, filesReadBeforeClose.size());
526
527      List<Cell> results = new ArrayList<>();
528      InternalScanner storeScanner = (InternalScanner) kvScanner;
529      while (storeScanner.next(results)) {
530        results.clear();
531      }
532
533      // Still before close: set must remain empty until scanner is closed.
534      filesReadBeforeClose = kvScanner.getFilesRead();
535      Assert.assertTrue("Should return empty set before closing even after reading",
536        filesReadBeforeClose.isEmpty());
537    } finally {
538      kvScanner.close();
539    }
540
541    // After close: set must contain exactly the expected store + MOB file paths.
542    Set<Path> filesReadAfterClose = kvScanner.getFilesRead();
543    Assert.assertEquals("Should have exact file count after closing", expectedFilePaths.size(),
544      filesReadAfterClose.size());
545    Assert.assertEquals("Should contain all expected file paths", expectedFilePaths,
546      filesReadAfterClose);
547  }
548
549  /**
550   * Flush the memstore
551   */
552  private void flush(int storeFilesSize) throws IOException {
553    flushStore(store, id++);
554    Assert.assertEquals(storeFilesSize, this.store.getStorefiles().size());
555    Assert.assertEquals(0, ((AbstractMemStore) this.store.memstore).getActive().getCellsCount());
556  }
557
558  /**
559   * Flush the memstore
560   */
561  private static void flushStore(HMobStore store, long id) throws IOException {
562    StoreFlushContext storeFlushCtx = store.createFlushContext(id, FlushLifeCycleTracker.DUMMY);
563    storeFlushCtx.prepare();
564    storeFlushCtx.flushCache(Mockito.mock(MonitoredTask.class));
565    storeFlushCtx.commit(Mockito.mock(MonitoredTask.class));
566  }
567
568  @Test
569  public void testMOBStoreEncryption() throws Exception {
570    final Configuration conf = TEST_UTIL.getConfiguration();
571
572    conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, MockAesKeyProvider.class.getName());
573    conf.set(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, "hbase");
574    byte[] keyBytes = new byte[AES.KEY_LENGTH];
575    Bytes.secureRandom(keyBytes);
576    String algorithm = conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES);
577    Key cfKey = new SecretKeySpec(keyBytes, algorithm);
578
579    ColumnFamilyDescriptor cfd = ColumnFamilyDescriptorBuilder.newBuilder(family)
580      .setMobEnabled(true).setMobThreshold(100).setMaxVersions(4).setEncryptionType(algorithm)
581      .setEncryptionKey(EncryptionUtil.wrapKey(conf,
582        conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, User.getCurrent().getShortName()),
583        cfKey))
584      .build();
585    init(name.getMethodName(), conf, cfd, false);
586
587    this.store.add(new KeyValue(row, family, qf1, 1, value), null);
588    this.store.add(new KeyValue(row, family, qf2, 1, value), null);
589    this.store.add(new KeyValue(row, family, qf3, 1, value), null);
590    flush(1);
591
592    this.store.add(new KeyValue(row, family, qf4, 1, value), null);
593    this.store.add(new KeyValue(row, family, qf5, 1, value), null);
594    this.store.add(new KeyValue(row, family, qf6, 1, value), null);
595    flush(2);
596
597    Collection<HStoreFile> storefiles = this.store.getStorefiles();
598    checkMobHFileEncrytption(storefiles);
599
600    // Scan the values
601    Scan scan = new Scan(get);
602    StoreScanner scanner = (StoreScanner) store.getScanner(scan,
603      scan.getFamilyMap().get(store.getColumnFamilyDescriptor().getName()), 0);
604
605    List<Cell> results = new ArrayList<>();
606    scanner.next(results);
607    Collections.sort(results, CellComparatorImpl.COMPARATOR);
608    scanner.close();
609    Assert.assertEquals(expected.size(), results.size());
610    for (int i = 0; i < results.size(); i++) {
611      Assert.assertEquals(expected.get(i), results.get(i));
612    }
613
614    // Trigger major compaction
615    this.store.triggerMajorCompaction();
616    Optional<CompactionContext> requestCompaction =
617      this.store.requestCompaction(PRIORITY_USER, CompactionLifeCycleTracker.DUMMY, null);
618    this.store.compact(requestCompaction.get(), NoLimitThroughputController.INSTANCE, null);
619    Assert.assertEquals(1, this.store.getStorefiles().size());
620
621    // Check encryption after compaction
622    checkMobHFileEncrytption(this.store.getStorefiles());
623  }
624
625  private void checkMobHFileEncrytption(Collection<HStoreFile> storefiles) {
626    HStoreFile storeFile = storefiles.iterator().next();
627    HFile.Reader reader = storeFile.getReader().getHFileReader();
628    byte[] encryptionKey = reader.getTrailer().getEncryptionKey();
629    Assert.assertTrue(null != encryptionKey);
630    Assert.assertTrue(reader.getFileContext().getEncryptionContext().getCipher().getName()
631      .equals(HConstants.CIPHER_AES));
632  }
633
634}