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