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.security.SecureRandom;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.Date;
029import java.util.Iterator;
030import java.util.List;
031import java.util.NavigableSet;
032import java.util.Optional;
033import java.util.concurrent.ConcurrentSkipListSet;
034import javax.crypto.spec.SecretKeySpec;
035import org.apache.hadoop.conf.Configuration;
036import org.apache.hadoop.fs.FileSystem;
037import org.apache.hadoop.fs.Path;
038import org.apache.hadoop.hbase.ArrayBackedTag;
039import org.apache.hadoop.hbase.Cell;
040import org.apache.hadoop.hbase.CellComparatorImpl;
041import org.apache.hadoop.hbase.CellUtil;
042import org.apache.hadoop.hbase.HBaseClassTestRule;
043import org.apache.hadoop.hbase.HBaseConfiguration;
044import org.apache.hadoop.hbase.HBaseTestingUtility;
045import org.apache.hadoop.hbase.HColumnDescriptor;
046import org.apache.hadoop.hbase.HConstants;
047import org.apache.hadoop.hbase.HRegionInfo;
048import org.apache.hadoop.hbase.HTableDescriptor;
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.Get;
054import org.apache.hadoop.hbase.client.Scan;
055import org.apache.hadoop.hbase.io.crypto.KeyProviderForTesting;
056import org.apache.hadoop.hbase.io.crypto.aes.AES;
057import org.apache.hadoop.hbase.io.hfile.HFile;
058import org.apache.hadoop.hbase.mob.MobConstants;
059import org.apache.hadoop.hbase.mob.MobUtils;
060import org.apache.hadoop.hbase.monitoring.MonitoredTask;
061import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext;
062import org.apache.hadoop.hbase.regionserver.compactions.CompactionLifeCycleTracker;
063import org.apache.hadoop.hbase.regionserver.throttle.NoLimitThroughputController;
064import org.apache.hadoop.hbase.security.EncryptionUtil;
065import org.apache.hadoop.hbase.security.User;
066import org.apache.hadoop.hbase.testclassification.MediumTests;
067import org.apache.hadoop.hbase.util.Bytes;
068import org.apache.hadoop.hbase.util.FSUtils;
069import org.apache.hadoop.hbase.wal.WALFactory;
070import org.junit.Assert;
071import org.junit.Before;
072import org.junit.ClassRule;
073import org.junit.Rule;
074import org.junit.Test;
075import org.junit.experimental.categories.Category;
076import org.junit.rules.TestName;
077import org.mockito.Mockito;
078import org.slf4j.Logger;
079import org.slf4j.LoggerFactory;
080
081@Category(MediumTests.class)
082public class TestHMobStore {
083
084  @ClassRule
085  public static final HBaseClassTestRule CLASS_RULE =
086      HBaseClassTestRule.forClass(TestHMobStore.class);
087
088  public static final Logger LOG = LoggerFactory.getLogger(TestHMobStore.class);
089  @Rule public TestName name = new TestName();
090
091  private HMobStore store;
092  private HRegion region;
093  private HColumnDescriptor hcd;
094  private FileSystem fs;
095  private byte [] table = Bytes.toBytes("table");
096  private byte [] family = Bytes.toBytes("family");
097  private byte [] row = Bytes.toBytes("row");
098  private byte [] row2 = Bytes.toBytes("row2");
099  private byte [] qf1 = Bytes.toBytes("qf1");
100  private byte [] qf2 = Bytes.toBytes("qf2");
101  private byte [] qf3 = Bytes.toBytes("qf3");
102  private byte [] qf4 = Bytes.toBytes("qf4");
103  private byte [] qf5 = Bytes.toBytes("qf5");
104  private byte [] qf6 = Bytes.toBytes("qf6");
105  private byte[] value = Bytes.toBytes("value");
106  private byte[] value2 = Bytes.toBytes("value2");
107  private Path mobFilePath;
108  private Date currentDate = new Date();
109  private Cell seekKey1;
110  private Cell seekKey2;
111  private Cell seekKey3;
112  private NavigableSet<byte[]> qualifiers = new ConcurrentSkipListSet<>(Bytes.BYTES_COMPARATOR);
113  private List<Cell> expected = new ArrayList<>();
114  private long id = System.currentTimeMillis();
115  private Get get = new Get(row);
116  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
117  private final String DIR = TEST_UTIL.getDataTestDir("TestHMobStore").toString();
118
119  /**
120   * Setup
121   * @throws Exception
122   */
123  @Before
124  public void setUp() throws Exception {
125    qualifiers.add(qf1);
126    qualifiers.add(qf3);
127    qualifiers.add(qf5);
128
129    Iterator<byte[]> iter = qualifiers.iterator();
130    while(iter.hasNext()){
131      byte [] next = iter.next();
132      expected.add(new KeyValue(row, family, next, 1, value));
133      get.addColumn(family, next);
134      get.setMaxVersions(); // all versions.
135    }
136  }
137
138  private void init(String methodName, Configuration conf, boolean testStore)
139  throws IOException {
140    hcd = new HColumnDescriptor(family);
141    hcd.setMobEnabled(true);
142    hcd.setMobThreshold(3L);
143    hcd.setMaxVersions(4);
144    init(methodName, conf, hcd, testStore);
145  }
146
147  private void init(String methodName, Configuration conf,
148      HColumnDescriptor hcd, boolean testStore) throws IOException {
149    HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(table));
150    init(methodName, conf, htd, hcd, testStore);
151  }
152
153  private void init(String methodName, Configuration conf, HTableDescriptor htd,
154      HColumnDescriptor hcd, boolean testStore) throws IOException {
155    //Setting up tje Region and Store
156    Path basedir = new Path(DIR+methodName);
157    Path tableDir = FSUtils.getTableDir(basedir, htd.getTableName());
158    String logName = "logs";
159    Path logdir = new Path(basedir, logName);
160    FileSystem fs = FileSystem.get(conf);
161    fs.delete(logdir, true);
162
163    htd.addFamily(hcd);
164    HRegionInfo info = new HRegionInfo(htd.getTableName(), null, null, false);
165    ChunkCreator.initialize(MemStoreLABImpl.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null);
166    final Configuration walConf = new Configuration(conf);
167    FSUtils.setRootDir(walConf, basedir);
168    final WALFactory wals = new WALFactory(walConf, methodName);
169    region = new HRegion(tableDir, wals.getWAL(info), fs, conf, info, htd, null);
170    store = new HMobStore(region, hcd, conf);
171    if(testStore) {
172      init(conf, hcd);
173    }
174  }
175
176  private void init(Configuration conf, HColumnDescriptor hcd)
177      throws IOException {
178    Path basedir = FSUtils.getRootDir(conf);
179    fs = FileSystem.get(conf);
180    Path homePath = new Path(basedir, Bytes.toString(family) + Path.SEPARATOR
181        + Bytes.toString(family));
182    fs.mkdirs(homePath);
183
184    KeyValue key1 = new KeyValue(row, family, qf1, 1, value);
185    KeyValue key2 = new KeyValue(row, family, qf2, 1, value);
186    KeyValue key3 = new KeyValue(row2, family, qf3, 1, value2);
187    KeyValue[] keys = new KeyValue[] { key1, key2, key3 };
188    int maxKeyCount = keys.length;
189    StoreFileWriter mobWriter = store.createWriterInTmp(currentDate, maxKeyCount,
190        hcd.getCompactionCompressionType(), region.getRegionInfo().getStartKey(), false);
191    mobFilePath = mobWriter.getPath();
192
193    mobWriter.append(key1);
194    mobWriter.append(key2);
195    mobWriter.append(key3);
196    mobWriter.close();
197
198    String targetPathName = MobUtils.formatDate(currentDate);
199    byte[] referenceValue = Bytes.toBytes(targetPathName + Path.SEPARATOR + mobFilePath.getName());
200    Tag tableNameTag = new ArrayBackedTag(TagType.MOB_TABLE_NAME_TAG_TYPE,
201        store.getTableName().getName());
202    KeyValue kv1 = new KeyValue(row, family, qf1, Long.MAX_VALUE, referenceValue);
203    KeyValue kv2 = new KeyValue(row, family, qf2, Long.MAX_VALUE, referenceValue);
204    KeyValue kv3 = new KeyValue(row2, family, qf3, Long.MAX_VALUE, referenceValue);
205    seekKey1 = MobUtils.createMobRefCell(kv1, referenceValue, tableNameTag);
206    seekKey2 = MobUtils.createMobRefCell(kv2, referenceValue, tableNameTag);
207    seekKey3 = MobUtils.createMobRefCell(kv3, referenceValue, tableNameTag);
208  }
209
210  /**
211   * Getting data from memstore
212   * @throws IOException
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()),
230        0);
231
232    List<Cell> results = new ArrayList<>();
233    scanner.next(results);
234    Collections.sort(results, CellComparatorImpl.COMPARATOR);
235    scanner.close();
236
237    //Compare
238    Assert.assertEquals(expected.size(), results.size());
239    for(int i=0; i<results.size(); i++) {
240      // Verify the values
241      Assert.assertEquals(expected.get(i), results.get(i));
242    }
243  }
244
245  /**
246   * Getting MOB data from files
247   * @throws IOException
248   */
249  @Test
250  public void testGetFromFiles() throws IOException {
251    final Configuration conf = TEST_UTIL.getConfiguration();
252    init(name.getMethodName(), conf, false);
253
254    //Put data in memstore
255    this.store.add(new KeyValue(row, family, qf1, 1, value), null);
256    this.store.add(new KeyValue(row, family, qf2, 1, value), null);
257    //flush
258    flush(1);
259
260    //Add more data
261    this.store.add(new KeyValue(row, family, qf3, 1, value), null);
262    this.store.add(new KeyValue(row, family, qf4, 1, value), null);
263    //flush
264    flush(2);
265
266    //Add more data
267    this.store.add(new KeyValue(row, family, qf5, 1, value), null);
268    this.store.add(new KeyValue(row, family, qf6, 1, value), null);
269    //flush
270    flush(3);
271
272    Scan scan = new Scan(get);
273    InternalScanner scanner = (InternalScanner) store.getScanner(scan,
274        scan.getFamilyMap().get(store.getColumnFamilyDescriptor().getName()),
275        0);
276
277    List<Cell> results = new ArrayList<>();
278    scanner.next(results);
279    Collections.sort(results, CellComparatorImpl.COMPARATOR);
280    scanner.close();
281
282    //Compare
283    Assert.assertEquals(expected.size(), results.size());
284    for(int i=0; i<results.size(); i++) {
285      Assert.assertEquals(expected.get(i), results.get(i));
286    }
287  }
288
289  /**
290   * Getting the reference data from files
291   * @throws IOException
292   */
293  @Test
294  public void testGetReferencesFromFiles() throws IOException {
295    final Configuration conf = HBaseConfiguration.create();
296    init(name.getMethodName(), conf, false);
297
298    //Put data in memstore
299    this.store.add(new KeyValue(row, family, qf1, 1, value), null);
300    this.store.add(new KeyValue(row, family, qf2, 1, value), null);
301    //flush
302    flush(1);
303
304    //Add more data
305    this.store.add(new KeyValue(row, family, qf3, 1, value), null);
306    this.store.add(new KeyValue(row, family, qf4, 1, value), null);
307    //flush
308    flush(2);
309
310    //Add more data
311    this.store.add(new KeyValue(row, family, qf5, 1, value), null);
312    this.store.add(new KeyValue(row, family, qf6, 1, value), null);
313    //flush
314    flush(3);
315
316    Scan scan = new Scan(get);
317    scan.setAttribute(MobConstants.MOB_SCAN_RAW, Bytes.toBytes(Boolean.TRUE));
318    InternalScanner scanner = (InternalScanner) store.getScanner(scan,
319      scan.getFamilyMap().get(store.getColumnFamilyDescriptor().getName()),
320      0);
321
322    List<Cell> results = new ArrayList<>();
323    scanner.next(results);
324    Collections.sort(results, CellComparatorImpl.COMPARATOR);
325    scanner.close();
326
327    //Compare
328    Assert.assertEquals(expected.size(), results.size());
329    for(int i=0; i<results.size(); i++) {
330      Cell cell = results.get(i);
331      Assert.assertTrue(MobUtils.isMobReferenceCell(cell));
332    }
333  }
334
335  /**
336   * Getting data from memstore and files
337   * @throws IOException
338   */
339  @Test
340  public void testGetFromMemStoreAndFiles() throws IOException {
341
342    final Configuration conf = HBaseConfiguration.create();
343
344    init(name.getMethodName(), conf, false);
345
346    //Put data in memstore
347    this.store.add(new KeyValue(row, family, qf1, 1, value), null);
348    this.store.add(new KeyValue(row, family, qf2, 1, value), null);
349    //flush
350    flush(1);
351
352    //Add more data
353    this.store.add(new KeyValue(row, family, qf3, 1, value), null);
354    this.store.add(new KeyValue(row, family, qf4, 1, value), null);
355    //flush
356    flush(2);
357
358    //Add more data
359    this.store.add(new KeyValue(row, family, qf5, 1, value), null);
360    this.store.add(new KeyValue(row, family, qf6, 1, value), null);
361
362    Scan scan = new Scan(get);
363    InternalScanner scanner = (InternalScanner) store.getScanner(scan,
364        scan.getFamilyMap().get(store.getColumnFamilyDescriptor().getName()),
365        0);
366
367    List<Cell> results = new ArrayList<>();
368    scanner.next(results);
369    Collections.sort(results, CellComparatorImpl.COMPARATOR);
370    scanner.close();
371
372    //Compare
373    Assert.assertEquals(expected.size(), results.size());
374    for(int i=0; i<results.size(); i++) {
375      Assert.assertEquals(expected.get(i), results.get(i));
376    }
377  }
378
379  /**
380   * Getting data from memstore and files
381   * @throws IOException
382   */
383  @Test
384  public void testMobCellSizeThreshold() throws IOException {
385
386    final Configuration conf = HBaseConfiguration.create();
387
388    HColumnDescriptor hcd;
389    hcd = new HColumnDescriptor(family);
390    hcd.setMobEnabled(true);
391    hcd.setMobThreshold(100);
392    hcd.setMaxVersions(4);
393    init(name.getMethodName(), conf, hcd, false);
394
395    //Put data in memstore
396    this.store.add(new KeyValue(row, family, qf1, 1, value), null);
397    this.store.add(new KeyValue(row, family, qf2, 1, value), null);
398    //flush
399    flush(1);
400
401    //Add more data
402    this.store.add(new KeyValue(row, family, qf3, 1, value), null);
403    this.store.add(new KeyValue(row, family, qf4, 1, value), null);
404    //flush
405    flush(2);
406
407    //Add more data
408    this.store.add(new KeyValue(row, family, qf5, 1, value), null);
409    this.store.add(new KeyValue(row, family, qf6, 1, value), null);
410    //flush
411    flush(3);
412
413    Scan scan = new Scan(get);
414    scan.setAttribute(MobConstants.MOB_SCAN_RAW, Bytes.toBytes(Boolean.TRUE));
415    InternalScanner scanner = (InternalScanner) store.getScanner(scan,
416      scan.getFamilyMap().get(store.getColumnFamilyDescriptor().getName()),
417      0);
418
419    List<Cell> results = new ArrayList<>();
420    scanner.next(results);
421    Collections.sort(results, CellComparatorImpl.COMPARATOR);
422    scanner.close();
423
424    //Compare
425    Assert.assertEquals(expected.size(), results.size());
426    for(int i=0; i<results.size(); i++) {
427      Cell cell = results.get(i);
428      //this is not mob reference cell.
429      Assert.assertFalse(MobUtils.isMobReferenceCell(cell));
430      Assert.assertEquals(expected.get(i), results.get(i));
431      Assert.assertEquals(100, store.getColumnFamilyDescriptor().getMobThreshold());
432    }
433  }
434
435  @Test
436  public void testCommitFile() throws Exception {
437    final Configuration conf = HBaseConfiguration.create();
438    init(name.getMethodName(), conf, true);
439    String targetPathName = MobUtils.formatDate(new Date());
440    Path targetPath = new Path(store.getPath(), (targetPathName
441        + Path.SEPARATOR + mobFilePath.getName()));
442    fs.delete(targetPath, true);
443    Assert.assertFalse(fs.exists(targetPath));
444    //commit file
445    store.commitFile(mobFilePath, targetPath);
446    Assert.assertTrue(fs.exists(targetPath));
447  }
448
449  @Test
450  public void testResolve() throws Exception {
451    final Configuration conf = HBaseConfiguration.create();
452    init(name.getMethodName(), conf, true);
453    String targetPathName = MobUtils.formatDate(currentDate);
454    Path targetPath = new Path(store.getPath(), targetPathName);
455    store.commitFile(mobFilePath, targetPath);
456    //resolve
457    Cell resultCell1 = store.resolve(seekKey1, false);
458    Cell resultCell2 = store.resolve(seekKey2, false);
459    Cell resultCell3 = store.resolve(seekKey3, false);
460    //compare
461    Assert.assertEquals(Bytes.toString(value),
462        Bytes.toString(CellUtil.cloneValue(resultCell1)));
463    Assert.assertEquals(Bytes.toString(value),
464        Bytes.toString(CellUtil.cloneValue(resultCell2)));
465    Assert.assertEquals(Bytes.toString(value2),
466        Bytes.toString(CellUtil.cloneValue(resultCell3)));
467  }
468
469  /**
470   * Flush the memstore
471   * @param storeFilesSize
472   * @throws IOException
473   */
474  private void flush(int storeFilesSize) throws IOException{
475    this.store.snapshot();
476    flushStore(store, id++);
477    Assert.assertEquals(storeFilesSize, this.store.getStorefiles().size());
478    Assert.assertEquals(0, ((AbstractMemStore)this.store.memstore).getActive().getCellsCount());
479  }
480
481  /**
482   * Flush the memstore
483   * @param store
484   * @param id
485   * @throws IOException
486   */
487  private static void flushStore(HMobStore store, long id) throws IOException {
488    StoreFlushContext storeFlushCtx = store.createFlushContext(id, FlushLifeCycleTracker.DUMMY);
489    storeFlushCtx.prepare();
490    storeFlushCtx.flushCache(Mockito.mock(MonitoredTask.class));
491    storeFlushCtx.commit(Mockito.mock(MonitoredTask.class));
492  }
493
494  @Test
495  public void testMOBStoreEncryption() throws Exception {
496    final Configuration conf = TEST_UTIL.getConfiguration();
497
498    conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, KeyProviderForTesting.class.getName());
499    conf.set(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, "hbase");
500    SecureRandom rng = new SecureRandom();
501    byte[] keyBytes = new byte[AES.KEY_LENGTH];
502    rng.nextBytes(keyBytes);
503    String algorithm = conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES);
504    Key cfKey = new SecretKeySpec(keyBytes, algorithm);
505
506    HColumnDescriptor hcd = new HColumnDescriptor(family);
507    hcd.setMobEnabled(true);
508    hcd.setMobThreshold(100);
509    hcd.setMaxVersions(4);
510    hcd.setEncryptionType(algorithm);
511    hcd.setEncryptionKey(EncryptionUtil.wrapKey(conf,
512      conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, User.getCurrent().getShortName()),cfKey));
513
514    init(name.getMethodName(), conf, hcd, false);
515
516    this.store.add(new KeyValue(row, family, qf1, 1, value), null);
517    this.store.add(new KeyValue(row, family, qf2, 1, value), null);
518    this.store.add(new KeyValue(row, family, qf3, 1, value), null);
519    flush(1);
520
521    this.store.add(new KeyValue(row, family, qf4, 1, value), null);
522    this.store.add(new KeyValue(row, family, qf5, 1, value), null);
523    this.store.add(new KeyValue(row, family, qf6, 1, value), null);
524    flush(2);
525
526    Collection<HStoreFile> storefiles = this.store.getStorefiles();
527    checkMobHFileEncrytption(storefiles);
528
529    // Scan the values
530    Scan scan = new Scan(get);
531    InternalScanner scanner = (InternalScanner) store.getScanner(scan,
532        scan.getFamilyMap().get(store.getColumnFamilyDescriptor().getName()),
533        0);
534
535    List<Cell> results = new ArrayList<>();
536    scanner.next(results);
537    Collections.sort(results, CellComparatorImpl.COMPARATOR);
538    scanner.close();
539    Assert.assertEquals(expected.size(), results.size());
540    for(int i=0; i<results.size(); i++) {
541      Assert.assertEquals(expected.get(i), results.get(i));
542    }
543
544    // Trigger major compaction
545    this.store.triggerMajorCompaction();
546    Optional<CompactionContext> requestCompaction =
547        this.store.requestCompaction(PRIORITY_USER, CompactionLifeCycleTracker.DUMMY, null);
548    this.store.compact(requestCompaction.get(), NoLimitThroughputController.INSTANCE, null);
549    Assert.assertEquals(1, this.store.getStorefiles().size());
550
551    //Check encryption after compaction
552    checkMobHFileEncrytption(this.store.getStorefiles());
553  }
554
555  private void checkMobHFileEncrytption(Collection<HStoreFile> storefiles) {
556    HStoreFile storeFile = storefiles.iterator().next();
557    HFile.Reader reader = storeFile.getReader().getHFileReader();
558    byte[] encryptionKey = reader.getTrailer().getEncryptionKey();
559    Assert.assertTrue(null != encryptionKey);
560    Assert.assertTrue(reader.getFileContext().getEncryptionContext().getCipher().getName()
561        .equals(HConstants.CIPHER_AES));
562  }
563
564}