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