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.TestHRegion.assertGet;
021import static org.apache.hadoop.hbase.regionserver.TestHRegion.putData;
022import static org.apache.hadoop.hbase.regionserver.TestHRegion.verifyData;
023import static org.junit.jupiter.api.Assertions.assertEquals;
024import static org.junit.jupiter.api.Assertions.assertFalse;
025import static org.junit.jupiter.api.Assertions.assertNotNull;
026import static org.junit.jupiter.api.Assertions.assertNull;
027import static org.junit.jupiter.api.Assertions.assertTrue;
028import static org.junit.jupiter.api.Assertions.fail;
029import static org.mockito.ArgumentMatchers.any;
030import static org.mockito.Mockito.mock;
031import static org.mockito.Mockito.spy;
032import static org.mockito.Mockito.times;
033import static org.mockito.Mockito.verify;
034import static org.mockito.Mockito.when;
035
036import java.io.FileNotFoundException;
037import java.io.IOException;
038import java.util.ArrayList;
039import java.util.List;
040import java.util.Map;
041import java.util.Objects;
042import org.apache.hadoop.conf.Configuration;
043import org.apache.hadoop.fs.FSDataOutputStream;
044import org.apache.hadoop.fs.Path;
045import org.apache.hadoop.hbase.Cell;
046import org.apache.hadoop.hbase.CellBuilderType;
047import org.apache.hadoop.hbase.CellUtil;
048import org.apache.hadoop.hbase.ExtendedCellBuilderFactory;
049import org.apache.hadoop.hbase.HBaseTestingUtil;
050import org.apache.hadoop.hbase.HConstants;
051import org.apache.hadoop.hbase.KeyValue;
052import org.apache.hadoop.hbase.ServerName;
053import org.apache.hadoop.hbase.TableName;
054import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
055import org.apache.hadoop.hbase.client.Durability;
056import org.apache.hadoop.hbase.client.Get;
057import org.apache.hadoop.hbase.client.Put;
058import org.apache.hadoop.hbase.client.RegionInfo;
059import org.apache.hadoop.hbase.client.RegionInfoBuilder;
060import org.apache.hadoop.hbase.client.TableDescriptor;
061import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
062import org.apache.hadoop.hbase.executor.ExecutorService;
063import org.apache.hadoop.hbase.executor.ExecutorType;
064import org.apache.hadoop.hbase.io.hfile.HFile;
065import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
066import org.apache.hadoop.hbase.regionserver.HRegion.FlushResultImpl;
067import org.apache.hadoop.hbase.regionserver.HRegion.PrepareFlushResult;
068import org.apache.hadoop.hbase.regionserver.throttle.NoLimitThroughputController;
069import org.apache.hadoop.hbase.testclassification.LargeTests;
070import org.apache.hadoop.hbase.util.Bytes;
071import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
072import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper;
073import org.apache.hadoop.hbase.util.FSUtils;
074import org.apache.hadoop.hbase.util.Pair;
075import org.apache.hadoop.hbase.util.Strings;
076import org.apache.hadoop.hbase.wal.AbstractFSWALProvider;
077import org.apache.hadoop.hbase.wal.NoEOFWALStreamReader;
078import org.apache.hadoop.hbase.wal.WAL;
079import org.apache.hadoop.hbase.wal.WALEdit;
080import org.apache.hadoop.hbase.wal.WALFactory;
081import org.apache.hadoop.hbase.wal.WALKeyImpl;
082import org.apache.hadoop.hbase.wal.WALSplitUtil.MutationReplay;
083import org.apache.hadoop.hbase.wal.WALStreamReader;
084import org.junit.jupiter.api.AfterAll;
085import org.junit.jupiter.api.AfterEach;
086import org.junit.jupiter.api.BeforeAll;
087import org.junit.jupiter.api.BeforeEach;
088import org.junit.jupiter.api.Tag;
089import org.junit.jupiter.api.Test;
090import org.junit.jupiter.api.TestInfo;
091import org.mockito.Mockito;
092import org.slf4j.Logger;
093import org.slf4j.LoggerFactory;
094
095import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
096import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
097
098import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
099import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.MutationProto.MutationType;
100import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.BulkLoadDescriptor;
101import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.CompactionDescriptor;
102import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.FlushDescriptor;
103import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.FlushDescriptor.FlushAction;
104import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.FlushDescriptor.StoreFlushDescriptor;
105import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.RegionEventDescriptor;
106import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.RegionEventDescriptor.EventType;
107import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.StoreDescriptor;
108
109/**
110 * Tests of HRegion methods for replaying flush, compaction, region open, etc events for secondary
111 * region replicas
112 */
113@SuppressWarnings("deprecation")
114@Tag(LargeTests.TAG)
115public class TestHRegionReplayEvents {
116
117  private static final Logger LOG = LoggerFactory.getLogger(TestHRegionReplayEvents.class);
118  private String name;
119
120  private static HBaseTestingUtil TEST_UTIL;
121
122  public static Configuration CONF;
123  private String dir;
124
125  private byte[][] families =
126    new byte[][] { Bytes.toBytes("cf1"), Bytes.toBytes("cf2"), Bytes.toBytes("cf3") };
127
128  // Test names
129  protected byte[] tableName;
130  protected String method;
131  protected final byte[] row = Bytes.toBytes("rowA");
132  protected final byte[] row2 = Bytes.toBytes("rowB");
133  protected byte[] cq = Bytes.toBytes("cq");
134
135  // per test fields
136  private Path rootDir;
137  private TableDescriptor htd;
138  private RegionServerServices rss;
139  private RegionInfo primaryHri, secondaryHri;
140  private HRegion primaryRegion, secondaryRegion;
141  private WAL walPrimary, walSecondary;
142  private WALStreamReader reader;
143
144  @BeforeAll
145  public static void setUpBeforeClass() throws Exception {
146    TEST_UTIL = new HBaseTestingUtil();
147    TEST_UTIL.startMiniDFSCluster(1);
148  }
149
150  @AfterAll
151  public static void tearDownAfterClass() throws Exception {
152    LOG.info("Cleaning test directory: " + TEST_UTIL.getDataTestDir());
153    TEST_UTIL.cleanupTestDir();
154    TEST_UTIL.shutdownMiniDFSCluster();
155  }
156
157  @BeforeEach
158  public void setUp(TestInfo testInfo) throws Exception {
159    this.name = testInfo.getTestMethod().get().getName();
160    CONF = TEST_UTIL.getConfiguration();
161    dir = TEST_UTIL.getDataTestDir("TestHRegionReplayEvents").toString();
162    method = name;
163    tableName = Bytes.toBytes(name);
164    rootDir = new Path(dir + method);
165    TEST_UTIL.getConfiguration().set(HConstants.HBASE_DIR, rootDir.toString());
166    method = name;
167    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(TableName.valueOf(method));
168    for (byte[] family : families) {
169      builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(family));
170    }
171    htd = builder.build();
172
173    long time = EnvironmentEdgeManager.currentTime();
174    ChunkCreator.initialize(MemStoreLAB.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null,
175      MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT);
176    primaryHri =
177      RegionInfoBuilder.newBuilder(htd.getTableName()).setRegionId(time).setReplicaId(0).build();
178    secondaryHri =
179      RegionInfoBuilder.newBuilder(htd.getTableName()).setRegionId(time).setReplicaId(1).build();
180
181    WALFactory wals = TestHRegion.createWALFactory(CONF, rootDir);
182    walPrimary = wals.getWAL(primaryHri);
183    walSecondary = wals.getWAL(secondaryHri);
184
185    rss = mock(RegionServerServices.class);
186    when(rss.getServerName()).thenReturn(ServerName.valueOf("foo", 1, 1));
187    when(rss.getConfiguration()).thenReturn(CONF);
188    when(rss.getRegionServerAccounting()).thenReturn(new RegionServerAccounting(CONF));
189    String string =
190      org.apache.hadoop.hbase.executor.EventType.RS_COMPACTED_FILES_DISCHARGER.toString();
191    ExecutorService es = new ExecutorService(string);
192    es.startExecutorService(es.new ExecutorConfig().setCorePoolSize(1)
193      .setExecutorType(ExecutorType.RS_COMPACTED_FILES_DISCHARGER));
194    when(rss.getExecutorService()).thenReturn(es);
195    primaryRegion = HRegion.createHRegion(primaryHri, rootDir, CONF, htd, walPrimary);
196    primaryRegion.close();
197    List<HRegion> regions = new ArrayList<>();
198    regions.add(primaryRegion);
199    Mockito.doReturn(regions).when(rss).getRegions();
200
201    primaryRegion = HRegion.openHRegion(rootDir, primaryHri, htd, walPrimary, CONF, rss, null);
202    secondaryRegion = HRegion.openHRegion(secondaryHri, htd, null, CONF, rss, null);
203
204    reader = null;
205  }
206
207  @AfterEach
208  public void tearDown() throws Exception {
209    if (reader != null) {
210      reader.close();
211    }
212
213    if (primaryRegion != null) {
214      HBaseTestingUtil.closeRegionAndWAL(primaryRegion);
215    }
216    if (secondaryRegion != null) {
217      HBaseTestingUtil.closeRegionAndWAL(secondaryRegion);
218    }
219
220    EnvironmentEdgeManagerTestHelper.reset();
221  }
222
223  String getName() {
224    return name;
225  }
226
227  // Some of the test cases are as follows:
228  // 1. replay flush start marker again
229  // 2. replay flush with smaller seqId than what is there in memstore snapshot
230  // 3. replay flush with larger seqId than what is there in memstore snapshot
231  // 4. replay flush commit without flush prepare. non droppable memstore
232  // 5. replay flush commit without flush prepare. droppable memstore
233  // 6. replay open region event
234  // 7. replay open region event after flush start
235  // 8. replay flush form an earlier seqId (test ignoring seqIds)
236  // 9. start flush does not prevent region from closing.
237
238  @Test
239  public void testRegionReplicaSecondaryCannotFlush() throws IOException {
240    // load some data and flush ensure that the secondary replica will not execute the flush
241
242    // load some data to secondary by replaying
243    putDataByReplay(secondaryRegion, 0, 1000, cq, families);
244
245    verifyData(secondaryRegion, 0, 1000, cq, families);
246
247    // flush region
248    FlushResultImpl flush = (FlushResultImpl) secondaryRegion.flush(true);
249    assertEquals(FlushResultImpl.Result.CANNOT_FLUSH, flush.result);
250
251    verifyData(secondaryRegion, 0, 1000, cq, families);
252
253    // close the region, and inspect that it has not flushed
254    Map<byte[], List<HStoreFile>> files = secondaryRegion.close(false);
255    // assert that there are no files (due to flush)
256    for (List<HStoreFile> f : files.values()) {
257      assertTrue(f.isEmpty());
258    }
259  }
260
261  /**
262   * Tests a case where we replay only a flush start marker, then the region is closed. This region
263   * should not block indefinitely
264   */
265  @Test
266  public void testOnlyReplayingFlushStartDoesNotHoldUpRegionClose() throws IOException {
267    // load some data to primary and flush
268    int start = 0;
269    LOG.info("-- Writing some data to primary from " + start + " to " + (start + 100));
270    putData(primaryRegion, Durability.SYNC_WAL, start, 100, cq, families);
271    LOG.info("-- Flushing primary, creating 3 files for 3 stores");
272    primaryRegion.flush(true);
273
274    // now replay the edits and the flush marker
275    reader = createWALReaderForPrimary();
276
277    LOG.info("-- Replaying edits and flush events in secondary");
278    while (true) {
279      WAL.Entry entry = reader.next();
280      if (entry == null) {
281        break;
282      }
283      FlushDescriptor flushDesc = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
284      if (flushDesc != null) {
285        if (flushDesc.getAction() == FlushAction.START_FLUSH) {
286          LOG.info("-- Replaying flush start in secondary");
287          secondaryRegion.replayWALFlushStartMarker(flushDesc);
288        } else if (flushDesc.getAction() == FlushAction.COMMIT_FLUSH) {
289          LOG.info("-- NOT Replaying flush commit in secondary");
290        }
291      } else {
292        replayEdit(secondaryRegion, entry);
293      }
294    }
295
296    assertTrue(rss.getRegionServerAccounting().getGlobalMemStoreDataSize() > 0);
297    // now close the region which should not cause hold because of un-committed flush
298    secondaryRegion.close();
299
300    // verify that the memstore size is back to what it was
301    assertEquals(0, rss.getRegionServerAccounting().getGlobalMemStoreDataSize());
302  }
303
304  static int replayEdit(HRegion region, WAL.Entry entry) throws IOException {
305    if (WALEdit.isMetaEditFamily(entry.getEdit().getCells().get(0))) {
306      return 0; // handled elsewhere
307    }
308    Put put = new Put(CellUtil.cloneRow(entry.getEdit().getCells().get(0)));
309    for (Cell cell : entry.getEdit().getCells())
310      put.add(cell);
311    put.setDurability(Durability.SKIP_WAL);
312    MutationReplay mutation = new MutationReplay(MutationType.PUT, put, 0, 0);
313    region.batchReplay(new MutationReplay[] { mutation }, entry.getKey().getSequenceId());
314    return Integer.parseInt(Bytes.toString(put.getRow()));
315  }
316
317  private WALStreamReader createWALReaderForPrimary() throws FileNotFoundException, IOException {
318    return NoEOFWALStreamReader.create(TEST_UTIL.getTestFileSystem(),
319      AbstractFSWALProvider.getCurrentFileName(walPrimary), TEST_UTIL.getConfiguration());
320  }
321
322  @Test
323  public void testBatchReplayWithMultipleNonces() throws IOException {
324    try {
325      MutationReplay[] mutations = new MutationReplay[100];
326      for (int i = 0; i < 100; i++) {
327        Put put = new Put(Bytes.toBytes(i));
328        put.setDurability(Durability.SYNC_WAL);
329        for (byte[] familly : this.families) {
330          put.addColumn(familly, this.cq, null);
331          long nonceNum = i / 10;
332          mutations[i] = new MutationReplay(MutationType.PUT, put, nonceNum, nonceNum);
333        }
334      }
335      primaryRegion.batchReplay(mutations, 20);
336    } catch (Exception e) {
337      String msg = "Error while replay of batch with multiple nonces. ";
338      LOG.error(msg, e);
339      fail(msg + e.getMessage());
340    }
341  }
342
343  @Test
344  public void testReplayFlushesAndCompactions() throws IOException {
345    // initiate a secondary region with some data.
346
347    // load some data to primary and flush. 3 flushes and some more unflushed data
348    putDataWithFlushes(primaryRegion, 100, 300, 100);
349
350    // compaction from primary
351    LOG.info("-- Compacting primary, only 1 store");
352    primaryRegion.compactStore(Bytes.toBytes("cf1"), NoLimitThroughputController.INSTANCE);
353
354    // now replay the edits and the flush marker
355    reader = createWALReaderForPrimary();
356
357    LOG.info("-- Replaying edits and flush events in secondary");
358    int lastReplayed = 0;
359    int expectedStoreFileCount = 0;
360    while (true) {
361      WAL.Entry entry = reader.next();
362      if (entry == null) {
363        break;
364      }
365      FlushDescriptor flushDesc = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
366      CompactionDescriptor compactionDesc =
367        WALEdit.getCompaction(entry.getEdit().getCells().get(0));
368      if (flushDesc != null) {
369        // first verify that everything is replayed and visible before flush event replay
370        verifyData(secondaryRegion, 0, lastReplayed, cq, families);
371        HStore store = secondaryRegion.getStore(Bytes.toBytes("cf1"));
372        long storeMemstoreSize = store.getMemStoreSize().getHeapSize();
373        long regionMemstoreSize = secondaryRegion.getMemStoreDataSize();
374        MemStoreSize mss = store.getFlushableSize();
375        long storeSize = store.getSize();
376        long storeSizeUncompressed = store.getStoreSizeUncompressed();
377        if (flushDesc.getAction() == FlushAction.START_FLUSH) {
378          LOG.info("-- Replaying flush start in secondary");
379          PrepareFlushResult result = secondaryRegion.replayWALFlushStartMarker(flushDesc);
380          assertNull(result.result);
381          assertEquals(result.flushOpSeqId, flushDesc.getFlushSequenceNumber());
382
383          // assert that the store memstore is smaller now
384          long newStoreMemstoreSize = store.getMemStoreSize().getHeapSize();
385          LOG.info("Memstore size reduced by:"
386            + Strings.humanReadableInt(newStoreMemstoreSize - storeMemstoreSize));
387          assertTrue(storeMemstoreSize > newStoreMemstoreSize);
388
389        } else if (flushDesc.getAction() == FlushAction.COMMIT_FLUSH) {
390          LOG.info("-- Replaying flush commit in secondary");
391          secondaryRegion.replayWALFlushCommitMarker(flushDesc);
392
393          // assert that the flush files are picked
394          expectedStoreFileCount++;
395          for (HStore s : secondaryRegion.getStores()) {
396            assertEquals(expectedStoreFileCount, s.getStorefilesCount());
397          }
398          MemStoreSize newMss = store.getFlushableSize();
399          assertTrue(mss.getHeapSize() > newMss.getHeapSize());
400
401          // assert that the region memstore is smaller now
402          long newRegionMemstoreSize = secondaryRegion.getMemStoreDataSize();
403          assertTrue(regionMemstoreSize > newRegionMemstoreSize);
404
405          // assert that the store sizes are bigger
406          assertTrue(store.getSize() > storeSize);
407          assertTrue(store.getStoreSizeUncompressed() > storeSizeUncompressed);
408          assertEquals(store.getSize(), store.getStorefilesSize());
409        }
410        // after replay verify that everything is still visible
411        verifyData(secondaryRegion, 0, lastReplayed + 1, cq, families);
412      } else if (compactionDesc != null) {
413        secondaryRegion.replayWALCompactionMarker(compactionDesc, true, false, Long.MAX_VALUE);
414
415        // assert that the compaction is applied
416        for (HStore store : secondaryRegion.getStores()) {
417          if (store.getColumnFamilyName().equals("cf1")) {
418            assertEquals(1, store.getStorefilesCount());
419          } else {
420            assertEquals(expectedStoreFileCount, store.getStorefilesCount());
421          }
422        }
423      } else {
424        lastReplayed = replayEdit(secondaryRegion, entry);
425      }
426    }
427
428    assertEquals(400 - 1, lastReplayed);
429    LOG.info("-- Verifying edits from secondary");
430    verifyData(secondaryRegion, 0, 400, cq, families);
431
432    LOG.info("-- Verifying edits from primary. Ensuring that files are not deleted");
433    verifyData(primaryRegion, 0, lastReplayed, cq, families);
434    for (HStore store : primaryRegion.getStores()) {
435      if (store.getColumnFamilyName().equals("cf1")) {
436        assertEquals(1, store.getStorefilesCount());
437      } else {
438        assertEquals(expectedStoreFileCount, store.getStorefilesCount());
439      }
440    }
441  }
442
443  /**
444   * Tests cases where we prepare a flush with some seqId and we receive other flush start markers
445   * equal to, greater or less than the previous flush start marker.
446   */
447  @Test
448  public void testReplayFlushStartMarkers() throws IOException {
449    // load some data to primary and flush. 1 flush and some more unflushed data
450    putDataWithFlushes(primaryRegion, 100, 100, 100);
451    int numRows = 200;
452
453    // now replay the edits and the flush marker
454    reader = createWALReaderForPrimary();
455
456    LOG.info("-- Replaying edits and flush events in secondary");
457
458    FlushDescriptor startFlushDesc = null;
459
460    int lastReplayed = 0;
461    while (true) {
462      WAL.Entry entry = reader.next();
463      if (entry == null) {
464        break;
465      }
466      FlushDescriptor flushDesc = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
467      if (flushDesc != null) {
468        // first verify that everything is replayed and visible before flush event replay
469        HStore store = secondaryRegion.getStore(Bytes.toBytes("cf1"));
470        long storeMemstoreSize = store.getMemStoreSize().getHeapSize();
471        long regionMemstoreSize = secondaryRegion.getMemStoreDataSize();
472        MemStoreSize mss = store.getFlushableSize();
473
474        if (flushDesc.getAction() == FlushAction.START_FLUSH) {
475          startFlushDesc = flushDesc;
476          LOG.info("-- Replaying flush start in secondary");
477          PrepareFlushResult result = secondaryRegion.replayWALFlushStartMarker(startFlushDesc);
478          assertNull(result.result);
479          assertEquals(result.flushOpSeqId, startFlushDesc.getFlushSequenceNumber());
480          assertTrue(regionMemstoreSize > 0);
481          assertTrue(mss.getHeapSize() > 0);
482
483          // assert that the store memstore is smaller now
484          long newStoreMemstoreSize = store.getMemStoreSize().getHeapSize();
485          LOG.info("Memstore size reduced by:"
486            + Strings.humanReadableInt(newStoreMemstoreSize - storeMemstoreSize));
487          assertTrue(storeMemstoreSize > newStoreMemstoreSize);
488          verifyData(secondaryRegion, 0, lastReplayed + 1, cq, families);
489
490        }
491        // after replay verify that everything is still visible
492        verifyData(secondaryRegion, 0, lastReplayed + 1, cq, families);
493      } else {
494        lastReplayed = replayEdit(secondaryRegion, entry);
495      }
496    }
497
498    // at this point, there should be some data (rows 0-100) in memstore snapshot
499    // and some more data in memstores (rows 100-200)
500
501    verifyData(secondaryRegion, 0, numRows, cq, families);
502
503    // Test case 1: replay the same flush start marker again
504    LOG.info("-- Replaying same flush start in secondary again");
505    PrepareFlushResult result = secondaryRegion.replayWALFlushStartMarker(startFlushDesc);
506    assertNull(result); // this should return null. Ignoring the flush start marker
507    // assert that we still have prepared flush with the previous setup.
508    assertNotNull(secondaryRegion.getPrepareFlushResult());
509    assertEquals(secondaryRegion.getPrepareFlushResult().flushOpSeqId,
510      startFlushDesc.getFlushSequenceNumber());
511    assertTrue(secondaryRegion.getMemStoreDataSize() > 0); // memstore is not empty
512    verifyData(secondaryRegion, 0, numRows, cq, families);
513
514    // Test case 2: replay a flush start marker with a smaller seqId
515    FlushDescriptor startFlushDescSmallerSeqId =
516      clone(startFlushDesc, startFlushDesc.getFlushSequenceNumber() - 50);
517    LOG.info("-- Replaying same flush start in secondary again " + startFlushDescSmallerSeqId);
518    result = secondaryRegion.replayWALFlushStartMarker(startFlushDescSmallerSeqId);
519    assertNull(result); // this should return null. Ignoring the flush start marker
520    // assert that we still have prepared flush with the previous setup.
521    assertNotNull(secondaryRegion.getPrepareFlushResult());
522    assertEquals(secondaryRegion.getPrepareFlushResult().flushOpSeqId,
523      startFlushDesc.getFlushSequenceNumber());
524    assertTrue(secondaryRegion.getMemStoreDataSize() > 0); // memstore is not empty
525    verifyData(secondaryRegion, 0, numRows, cq, families);
526
527    // Test case 3: replay a flush start marker with a larger seqId
528    FlushDescriptor startFlushDescLargerSeqId =
529      clone(startFlushDesc, startFlushDesc.getFlushSequenceNumber() + 50);
530    LOG.info("-- Replaying same flush start in secondary again " + startFlushDescLargerSeqId);
531    result = secondaryRegion.replayWALFlushStartMarker(startFlushDescLargerSeqId);
532    assertNull(result); // this should return null. Ignoring the flush start marker
533    // assert that we still have prepared flush with the previous setup.
534    assertNotNull(secondaryRegion.getPrepareFlushResult());
535    assertEquals(secondaryRegion.getPrepareFlushResult().flushOpSeqId,
536      startFlushDesc.getFlushSequenceNumber());
537    assertTrue(secondaryRegion.getMemStoreDataSize() > 0); // memstore is not empty
538    verifyData(secondaryRegion, 0, numRows, cq, families);
539
540    LOG.info("-- Verifying edits from secondary");
541    verifyData(secondaryRegion, 0, numRows, cq, families);
542
543    LOG.info("-- Verifying edits from primary.");
544    verifyData(primaryRegion, 0, numRows, cq, families);
545  }
546
547  /**
548   * Tests the case where we prepare a flush with some seqId and we receive a flush commit marker
549   * less than the previous flush start marker.
550   */
551  @Test
552  public void testReplayFlushCommitMarkerSmallerThanFlushStartMarker() throws IOException {
553    // load some data to primary and flush. 2 flushes and some more unflushed data
554    putDataWithFlushes(primaryRegion, 100, 200, 100);
555    int numRows = 300;
556
557    // now replay the edits and the flush marker
558    reader = createWALReaderForPrimary();
559
560    LOG.info("-- Replaying edits and flush events in secondary");
561    FlushDescriptor startFlushDesc = null;
562    FlushDescriptor commitFlushDesc = null;
563
564    int lastReplayed = 0;
565    while (true) {
566      System.out.println(lastReplayed);
567      WAL.Entry entry = reader.next();
568      if (entry == null) {
569        break;
570      }
571      FlushDescriptor flushDesc = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
572      if (flushDesc != null) {
573        if (flushDesc.getAction() == FlushAction.START_FLUSH) {
574          // don't replay the first flush start marker, hold on to it, replay the second one
575          if (startFlushDesc == null) {
576            startFlushDesc = flushDesc;
577          } else {
578            LOG.info("-- Replaying flush start in secondary");
579            startFlushDesc = flushDesc;
580            PrepareFlushResult result = secondaryRegion.replayWALFlushStartMarker(startFlushDesc);
581            assertNull(result.result);
582          }
583        } else if (flushDesc.getAction() == FlushAction.COMMIT_FLUSH) {
584          // do not replay any flush commit yet
585          if (commitFlushDesc == null) {
586            commitFlushDesc = flushDesc; // hold on to the first flush commit marker
587          }
588        }
589        // after replay verify that everything is still visible
590        verifyData(secondaryRegion, 0, lastReplayed + 1, cq, families);
591      } else {
592        lastReplayed = replayEdit(secondaryRegion, entry);
593      }
594    }
595
596    // at this point, there should be some data (rows 0-200) in memstore snapshot
597    // and some more data in memstores (rows 200-300)
598    verifyData(secondaryRegion, 0, numRows, cq, families);
599
600    // no store files in the region
601    int expectedStoreFileCount = 0;
602    for (HStore s : secondaryRegion.getStores()) {
603      assertEquals(expectedStoreFileCount, s.getStorefilesCount());
604    }
605    long regionMemstoreSize = secondaryRegion.getMemStoreDataSize();
606
607    // Test case 1: replay the a flush commit marker smaller than what we have prepared
608    LOG.info("Testing replaying flush COMMIT " + commitFlushDesc + " on top of flush START"
609      + startFlushDesc);
610    assertTrue(commitFlushDesc.getFlushSequenceNumber() < startFlushDesc.getFlushSequenceNumber());
611
612    LOG.info("-- Replaying flush commit in secondary" + commitFlushDesc);
613    secondaryRegion.replayWALFlushCommitMarker(commitFlushDesc);
614
615    // assert that the flush files are picked
616    expectedStoreFileCount++;
617    for (HStore s : secondaryRegion.getStores()) {
618      assertEquals(expectedStoreFileCount, s.getStorefilesCount());
619    }
620    HStore store = secondaryRegion.getStore(Bytes.toBytes("cf1"));
621    MemStoreSize mss = store.getFlushableSize();
622    assertTrue(mss.getHeapSize() > 0); // assert that the memstore is not dropped
623
624    // assert that the region memstore is same as before
625    long newRegionMemstoreSize = secondaryRegion.getMemStoreDataSize();
626    assertEquals(regionMemstoreSize, newRegionMemstoreSize);
627
628    assertNotNull(secondaryRegion.getPrepareFlushResult()); // not dropped
629
630    LOG.info("-- Verifying edits from secondary");
631    verifyData(secondaryRegion, 0, numRows, cq, families);
632
633    LOG.info("-- Verifying edits from primary.");
634    verifyData(primaryRegion, 0, numRows, cq, families);
635  }
636
637  /**
638   * Tests the case where we prepare a flush with some seqId and we receive a flush commit marker
639   * larger than the previous flush start marker.
640   */
641  @Test
642  public void testReplayFlushCommitMarkerLargerThanFlushStartMarker() throws IOException {
643    // load some data to primary and flush. 1 flush and some more unflushed data
644    putDataWithFlushes(primaryRegion, 100, 100, 100);
645    int numRows = 200;
646
647    // now replay the edits and the flush marker
648    reader = createWALReaderForPrimary();
649
650    LOG.info("-- Replaying edits and flush events in secondary");
651    FlushDescriptor startFlushDesc = null;
652    FlushDescriptor commitFlushDesc = null;
653
654    int lastReplayed = 0;
655    while (true) {
656      WAL.Entry entry = reader.next();
657      if (entry == null) {
658        break;
659      }
660      FlushDescriptor flushDesc = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
661      if (flushDesc != null) {
662        if (flushDesc.getAction() == FlushAction.START_FLUSH) {
663          if (startFlushDesc == null) {
664            LOG.info("-- Replaying flush start in secondary");
665            startFlushDesc = flushDesc;
666            PrepareFlushResult result = secondaryRegion.replayWALFlushStartMarker(startFlushDesc);
667            assertNull(result.result);
668          }
669        } else if (flushDesc.getAction() == FlushAction.COMMIT_FLUSH) {
670          // do not replay any flush commit yet
671          // hold on to the flush commit marker but simulate a larger
672          // flush commit seqId
673          commitFlushDesc = FlushDescriptor.newBuilder(flushDesc)
674            .setFlushSequenceNumber(flushDesc.getFlushSequenceNumber() + 50).build();
675        }
676        // after replay verify that everything is still visible
677        verifyData(secondaryRegion, 0, lastReplayed + 1, cq, families);
678      } else {
679        lastReplayed = replayEdit(secondaryRegion, entry);
680      }
681    }
682
683    // at this point, there should be some data (rows 0-100) in memstore snapshot
684    // and some more data in memstores (rows 100-200)
685    verifyData(secondaryRegion, 0, numRows, cq, families);
686
687    // no store files in the region
688    int expectedStoreFileCount = 0;
689    for (HStore s : secondaryRegion.getStores()) {
690      assertEquals(expectedStoreFileCount, s.getStorefilesCount());
691    }
692    long regionMemstoreSize = secondaryRegion.getMemStoreDataSize();
693
694    // Test case 1: replay the a flush commit marker larger than what we have prepared
695    LOG.info("Testing replaying flush COMMIT " + commitFlushDesc + " on top of flush START"
696      + startFlushDesc);
697    assertTrue(commitFlushDesc.getFlushSequenceNumber() > startFlushDesc.getFlushSequenceNumber());
698
699    LOG.info("-- Replaying flush commit in secondary" + commitFlushDesc);
700    secondaryRegion.replayWALFlushCommitMarker(commitFlushDesc);
701
702    // assert that the flush files are picked
703    expectedStoreFileCount++;
704    for (HStore s : secondaryRegion.getStores()) {
705      assertEquals(expectedStoreFileCount, s.getStorefilesCount());
706    }
707    HStore store = secondaryRegion.getStore(Bytes.toBytes("cf1"));
708    MemStoreSize mss = store.getFlushableSize();
709    assertTrue(mss.getHeapSize() > 0); // assert that the memstore is not dropped
710
711    // assert that the region memstore is smaller than before, but not empty
712    long newRegionMemstoreSize = secondaryRegion.getMemStoreDataSize();
713    assertTrue(newRegionMemstoreSize > 0);
714    assertTrue(regionMemstoreSize > newRegionMemstoreSize);
715
716    assertNull(secondaryRegion.getPrepareFlushResult()); // prepare snapshot should be dropped
717
718    LOG.info("-- Verifying edits from secondary");
719    verifyData(secondaryRegion, 0, numRows, cq, families);
720
721    LOG.info("-- Verifying edits from primary.");
722    verifyData(primaryRegion, 0, numRows, cq, families);
723  }
724
725  /**
726   * Tests the case where we receive a flush commit before receiving any flush prepare markers. The
727   * memstore edits should be dropped after the flush commit replay since they should be in flushed
728   * files
729   */
730  @Test
731  public void testReplayFlushCommitMarkerWithoutFlushStartMarkerDroppableMemstore()
732    throws IOException {
733    testReplayFlushCommitMarkerWithoutFlushStartMarker(true);
734  }
735
736  /**
737   * Tests the case where we receive a flush commit before receiving any flush prepare markers. The
738   * memstore edits should be not dropped after the flush commit replay since not every edit will be
739   * in flushed files (based on seqId)
740   */
741  @Test
742  public void testReplayFlushCommitMarkerWithoutFlushStartMarkerNonDroppableMemstore()
743    throws IOException {
744    testReplayFlushCommitMarkerWithoutFlushStartMarker(false);
745  }
746
747  /**
748   * Tests the case where we receive a flush commit before receiving any flush prepare markers
749   */
750  public void testReplayFlushCommitMarkerWithoutFlushStartMarker(boolean droppableMemstore)
751    throws IOException {
752    // load some data to primary and flush. 1 flushes and some more unflushed data.
753    // write more data after flush depending on whether droppableSnapshot
754    putDataWithFlushes(primaryRegion, 100, 100, droppableMemstore ? 0 : 100);
755    int numRows = droppableMemstore ? 100 : 200;
756
757    // now replay the edits and the flush marker
758    reader = createWALReaderForPrimary();
759
760    LOG.info("-- Replaying edits and flush events in secondary");
761    FlushDescriptor commitFlushDesc = null;
762
763    int lastReplayed = 0;
764    while (true) {
765      WAL.Entry entry = reader.next();
766      if (entry == null) {
767        break;
768      }
769      FlushDescriptor flushDesc = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
770      if (flushDesc != null) {
771        if (flushDesc.getAction() == FlushAction.START_FLUSH) {
772          // do not replay flush start marker
773        } else if (flushDesc.getAction() == FlushAction.COMMIT_FLUSH) {
774          commitFlushDesc = flushDesc; // hold on to the flush commit marker
775        }
776        // after replay verify that everything is still visible
777        verifyData(secondaryRegion, 0, lastReplayed + 1, cq, families);
778      } else {
779        lastReplayed = replayEdit(secondaryRegion, entry);
780      }
781    }
782
783    // at this point, there should be some data (rows 0-200) in the memstore without snapshot
784    // and some more data in memstores (rows 100-300)
785    verifyData(secondaryRegion, 0, numRows, cq, families);
786
787    // no store files in the region
788    int expectedStoreFileCount = 0;
789    for (HStore s : secondaryRegion.getStores()) {
790      assertEquals(expectedStoreFileCount, s.getStorefilesCount());
791    }
792    long regionMemstoreSize = secondaryRegion.getMemStoreDataSize();
793
794    // Test case 1: replay a flush commit marker without start flush marker
795    assertNull(secondaryRegion.getPrepareFlushResult());
796    assertTrue(commitFlushDesc.getFlushSequenceNumber() > 0);
797
798    // ensure all files are visible in secondary
799    for (HStore store : secondaryRegion.getStores()) {
800      assertTrue(store.getMaxSequenceId().orElse(0L) <= secondaryRegion.getReadPoint(null));
801    }
802
803    LOG.info("-- Replaying flush commit in secondary" + commitFlushDesc);
804    secondaryRegion.replayWALFlushCommitMarker(commitFlushDesc);
805
806    // assert that the flush files are picked
807    expectedStoreFileCount++;
808    for (HStore s : secondaryRegion.getStores()) {
809      assertEquals(expectedStoreFileCount, s.getStorefilesCount());
810    }
811    HStore store = secondaryRegion.getStore(Bytes.toBytes("cf1"));
812    MemStoreSize mss = store.getFlushableSize();
813    if (droppableMemstore) {
814      // assert that the memstore is dropped
815      assertTrue(mss.getHeapSize() == MutableSegment.DEEP_OVERHEAD);
816    } else {
817      assertTrue(mss.getHeapSize() > 0); // assert that the memstore is not dropped
818    }
819
820    // assert that the region memstore is same as before (we could not drop)
821    long newRegionMemstoreSize = secondaryRegion.getMemStoreDataSize();
822    if (droppableMemstore) {
823      assertTrue(0 == newRegionMemstoreSize);
824    } else {
825      assertTrue(regionMemstoreSize == newRegionMemstoreSize);
826    }
827
828    LOG.info("-- Verifying edits from secondary");
829    verifyData(secondaryRegion, 0, numRows, cq, families);
830
831    LOG.info("-- Verifying edits from primary.");
832    verifyData(primaryRegion, 0, numRows, cq, families);
833  }
834
835  private FlushDescriptor clone(FlushDescriptor flush, long flushSeqId) {
836    return FlushDescriptor.newBuilder(flush).setFlushSequenceNumber(flushSeqId).build();
837  }
838
839  /**
840   * Tests replaying region open markers from primary region. Checks whether the files are picked up
841   */
842  @Test
843  public void testReplayRegionOpenEvent() throws IOException {
844    putDataWithFlushes(primaryRegion, 100, 0, 100); // no flush
845    int numRows = 100;
846
847    // close the region and open again.
848    primaryRegion.close();
849    primaryRegion = HRegion.openHRegion(rootDir, primaryHri, htd, walPrimary, CONF, rss, null);
850
851    // now replay the edits and the flush marker
852    reader = createWALReaderForPrimary();
853    List<RegionEventDescriptor> regionEvents = Lists.newArrayList();
854
855    LOG.info("-- Replaying edits and region events in secondary");
856    while (true) {
857      WAL.Entry entry = reader.next();
858      if (entry == null) {
859        break;
860      }
861      FlushDescriptor flushDesc = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
862      RegionEventDescriptor regionEventDesc =
863        WALEdit.getRegionEventDescriptor(entry.getEdit().getCells().get(0));
864
865      if (flushDesc != null) {
866        // don't replay flush events
867      } else if (regionEventDesc != null) {
868        regionEvents.add(regionEventDesc);
869      } else {
870        // don't replay edits
871      }
872    }
873
874    // we should have 1 open, 1 close and 1 open event
875    assertEquals(3, regionEvents.size());
876
877    // replay the first region open event.
878    secondaryRegion.replayWALRegionEventMarker(regionEvents.get(0));
879
880    // replay the close event as well
881    secondaryRegion.replayWALRegionEventMarker(regionEvents.get(1));
882
883    // no store files in the region
884    int expectedStoreFileCount = 0;
885    for (HStore s : secondaryRegion.getStores()) {
886      assertEquals(expectedStoreFileCount, s.getStorefilesCount());
887    }
888    long regionMemstoreSize = secondaryRegion.getMemStoreDataSize();
889    assertTrue(regionMemstoreSize == 0);
890
891    // now replay the region open event that should contain new file locations
892    LOG.info("Testing replaying region open event " + regionEvents.get(2));
893    secondaryRegion.replayWALRegionEventMarker(regionEvents.get(2));
894
895    // assert that the flush files are picked
896    expectedStoreFileCount++;
897    for (HStore s : secondaryRegion.getStores()) {
898      assertEquals(expectedStoreFileCount, s.getStorefilesCount());
899    }
900    HStore store = secondaryRegion.getStore(Bytes.toBytes("cf1"));
901    MemStoreSize mss = store.getFlushableSize();
902    assertTrue(mss.getHeapSize() == MutableSegment.DEEP_OVERHEAD);
903
904    // assert that the region memstore is empty
905    long newRegionMemstoreSize = secondaryRegion.getMemStoreDataSize();
906    assertTrue(newRegionMemstoreSize == 0);
907
908    assertNull(secondaryRegion.getPrepareFlushResult()); // prepare snapshot should be dropped if
909                                                         // any
910
911    LOG.info("-- Verifying edits from secondary");
912    verifyData(secondaryRegion, 0, numRows, cq, families);
913
914    LOG.info("-- Verifying edits from primary.");
915    verifyData(primaryRegion, 0, numRows, cq, families);
916  }
917
918  /**
919   * Tests the case where we replay a region open event after a flush start but before receiving
920   * flush commit
921   */
922  @Test
923  public void testReplayRegionOpenEventAfterFlushStart() throws IOException {
924    putDataWithFlushes(primaryRegion, 100, 100, 100);
925    int numRows = 200;
926
927    // close the region and open again.
928    primaryRegion.close();
929    primaryRegion = HRegion.openHRegion(rootDir, primaryHri, htd, walPrimary, CONF, rss, null);
930
931    // now replay the edits and the flush marker
932    reader = createWALReaderForPrimary();
933    List<RegionEventDescriptor> regionEvents = Lists.newArrayList();
934
935    LOG.info("-- Replaying edits and region events in secondary");
936    while (true) {
937      WAL.Entry entry = reader.next();
938      if (entry == null) {
939        break;
940      }
941      FlushDescriptor flushDesc = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
942      RegionEventDescriptor regionEventDesc =
943        WALEdit.getRegionEventDescriptor(entry.getEdit().getCells().get(0));
944
945      if (flushDesc != null) {
946        // only replay flush start
947        if (flushDesc.getAction() == FlushAction.START_FLUSH) {
948          secondaryRegion.replayWALFlushStartMarker(flushDesc);
949        }
950      } else if (regionEventDesc != null) {
951        regionEvents.add(regionEventDesc);
952      } else {
953        replayEdit(secondaryRegion, entry);
954      }
955    }
956
957    // at this point, there should be some data (rows 0-100) in the memstore snapshot
958    // and some more data in memstores (rows 100-200)
959    verifyData(secondaryRegion, 0, numRows, cq, families);
960
961    // we should have 1 open, 1 close and 1 open event
962    assertEquals(3, regionEvents.size());
963
964    // no store files in the region
965    int expectedStoreFileCount = 0;
966    for (HStore s : secondaryRegion.getStores()) {
967      assertEquals(expectedStoreFileCount, s.getStorefilesCount());
968    }
969
970    // now replay the region open event that should contain new file locations
971    LOG.info("Testing replaying region open event " + regionEvents.get(2));
972    secondaryRegion.replayWALRegionEventMarker(regionEvents.get(2));
973
974    // assert that the flush files are picked
975    expectedStoreFileCount = 2; // two flushes happened
976    for (HStore s : secondaryRegion.getStores()) {
977      assertEquals(expectedStoreFileCount, s.getStorefilesCount());
978    }
979    HStore store = secondaryRegion.getStore(Bytes.toBytes("cf1"));
980    MemStoreSize newSnapshotSize = store.getSnapshotSize();
981    assertTrue(newSnapshotSize.getDataSize() == 0);
982
983    // assert that the region memstore is empty
984    long newRegionMemstoreSize = secondaryRegion.getMemStoreDataSize();
985    assertTrue(newRegionMemstoreSize == 0);
986
987    assertNull(secondaryRegion.getPrepareFlushResult()); // prepare snapshot should be dropped if
988                                                         // any
989
990    LOG.info("-- Verifying edits from secondary");
991    verifyData(secondaryRegion, 0, numRows, cq, families);
992
993    LOG.info("-- Verifying edits from primary.");
994    verifyData(primaryRegion, 0, numRows, cq, families);
995  }
996
997  /**
998   * Tests whether edits coming in for replay are skipped which have smaller seq id than the seqId
999   * of the last replayed region open event.
1000   */
1001  @Test
1002  public void testSkippingEditsWithSmallerSeqIdAfterRegionOpenEvent() throws IOException {
1003    putDataWithFlushes(primaryRegion, 100, 100, 0);
1004    int numRows = 100;
1005
1006    // close the region and open again.
1007    primaryRegion.close();
1008    primaryRegion = HRegion.openHRegion(rootDir, primaryHri, htd, walPrimary, CONF, rss, null);
1009
1010    // now replay the edits and the flush marker
1011    reader = createWALReaderForPrimary();
1012    List<RegionEventDescriptor> regionEvents = Lists.newArrayList();
1013    List<WAL.Entry> edits = Lists.newArrayList();
1014
1015    LOG.info("-- Replaying edits and region events in secondary");
1016    while (true) {
1017      WAL.Entry entry = reader.next();
1018      if (entry == null) {
1019        break;
1020      }
1021      FlushDescriptor flushDesc = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
1022      RegionEventDescriptor regionEventDesc =
1023        WALEdit.getRegionEventDescriptor(entry.getEdit().getCells().get(0));
1024
1025      if (flushDesc != null) {
1026        // don't replay flushes
1027      } else if (regionEventDesc != null) {
1028        regionEvents.add(regionEventDesc);
1029      } else {
1030        edits.add(entry);
1031      }
1032    }
1033
1034    // replay the region open of first open, but with the seqid of the second open
1035    // this way non of the flush files will be picked up.
1036    secondaryRegion.replayWALRegionEventMarker(RegionEventDescriptor.newBuilder(regionEvents.get(0))
1037      .setLogSequenceNumber(regionEvents.get(2).getLogSequenceNumber()).build());
1038
1039    // replay edits from the before region close. If replay does not
1040    // skip these the following verification will NOT fail.
1041    for (WAL.Entry entry : edits) {
1042      replayEdit(secondaryRegion, entry);
1043    }
1044
1045    boolean expectedFail = false;
1046    try {
1047      verifyData(secondaryRegion, 0, numRows, cq, families);
1048    } catch (AssertionError e) {
1049      expectedFail = true; // expected
1050    }
1051    if (!expectedFail) {
1052      fail("Should have failed this verification");
1053    }
1054  }
1055
1056  @Test
1057  public void testReplayFlushSeqIds() throws IOException {
1058    // load some data to primary and flush
1059    int start = 0;
1060    LOG.info("-- Writing some data to primary from " + start + " to " + (start + 100));
1061    putData(primaryRegion, Durability.SYNC_WAL, start, 100, cq, families);
1062    LOG.info("-- Flushing primary, creating 3 files for 3 stores");
1063    primaryRegion.flush(true);
1064
1065    // now replay the flush marker
1066    reader = createWALReaderForPrimary();
1067
1068    long flushSeqId = -1;
1069    LOG.info("-- Replaying flush events in secondary");
1070    while (true) {
1071      WAL.Entry entry = reader.next();
1072      if (entry == null) {
1073        break;
1074      }
1075      FlushDescriptor flushDesc = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
1076      if (flushDesc != null) {
1077        if (flushDesc.getAction() == FlushAction.START_FLUSH) {
1078          LOG.info("-- Replaying flush start in secondary");
1079          secondaryRegion.replayWALFlushStartMarker(flushDesc);
1080          flushSeqId = flushDesc.getFlushSequenceNumber();
1081        } else if (flushDesc.getAction() == FlushAction.COMMIT_FLUSH) {
1082          LOG.info("-- Replaying flush commit in secondary");
1083          secondaryRegion.replayWALFlushCommitMarker(flushDesc);
1084          assertEquals(flushSeqId, flushDesc.getFlushSequenceNumber());
1085        }
1086      }
1087      // else do not replay
1088    }
1089
1090    // TODO: what to do with this?
1091    // assert that the newly picked up flush file is visible
1092    long readPoint = secondaryRegion.getMVCC().getReadPoint();
1093    assertEquals(flushSeqId, readPoint);
1094
1095    // after replay verify that everything is still visible
1096    verifyData(secondaryRegion, 0, 100, cq, families);
1097  }
1098
1099  @Test
1100  public void testSeqIdsFromReplay() throws IOException {
1101    // test the case where seqId's coming from replayed WALEdits are made persisted with their
1102    // original seqIds and they are made visible through mvcc read point upon replay
1103    String method = name;
1104    byte[] tableName = Bytes.toBytes(method);
1105    byte[] family = Bytes.toBytes("family");
1106
1107    HRegion region = initHRegion(tableName, family);
1108    try {
1109      // replay an entry that is bigger than current read point
1110      long readPoint = region.getMVCC().getReadPoint();
1111      long origSeqId = readPoint + 100;
1112
1113      Put put = new Put(row).addColumn(family, row, row);
1114      put.setDurability(Durability.SKIP_WAL); // we replay with skip wal
1115      replay(region, put, origSeqId);
1116
1117      // read point should have advanced to this seqId
1118      assertGet(region, family, row);
1119
1120      // region seqId should have advanced at least to this seqId
1121      assertEquals(origSeqId, region.getReadPoint(null));
1122
1123      // replay an entry that is smaller than current read point
1124      // caution: adding an entry below current read point might cause partial dirty reads. Normal
1125      // replay does not allow reads while replay is going on.
1126      put = new Put(row2).addColumn(family, row2, row2);
1127      put.setDurability(Durability.SKIP_WAL);
1128      replay(region, put, origSeqId - 50);
1129
1130      assertGet(region, family, row2);
1131    } finally {
1132      region.close();
1133    }
1134  }
1135
1136  /**
1137   * Tests that a region opened in secondary mode would not write region open / close events to its
1138   * WAL.
1139   */
1140  @Test
1141  public void testSecondaryRegionDoesNotWriteRegionEventsToWAL() throws IOException {
1142    secondaryRegion.close();
1143    walSecondary = spy(walSecondary);
1144
1145    // test for region open and close
1146    secondaryRegion = HRegion.openHRegion(secondaryHri, htd, walSecondary, CONF, rss, null);
1147    verify(walSecondary, times(0)).appendData(any(RegionInfo.class), any(WALKeyImpl.class),
1148      any(WALEdit.class));
1149
1150    // test for replay prepare flush
1151    putDataByReplay(secondaryRegion, 0, 10, cq, families);
1152    secondaryRegion.replayWALFlushStartMarker(FlushDescriptor.newBuilder()
1153      .setFlushSequenceNumber(10)
1154      .setTableName(UnsafeByteOperations
1155        .unsafeWrap(primaryRegion.getTableDescriptor().getTableName().getName()))
1156      .setAction(FlushAction.START_FLUSH)
1157      .setEncodedRegionName(
1158        UnsafeByteOperations.unsafeWrap(primaryRegion.getRegionInfo().getEncodedNameAsBytes()))
1159      .setRegionName(UnsafeByteOperations.unsafeWrap(primaryRegion.getRegionInfo().getRegionName()))
1160      .build());
1161
1162    verify(walSecondary, times(0)).appendData(any(RegionInfo.class), any(WALKeyImpl.class),
1163      any(WALEdit.class));
1164
1165    secondaryRegion.close();
1166    verify(walSecondary, times(0)).appendData(any(RegionInfo.class), any(WALKeyImpl.class),
1167      any(WALEdit.class));
1168  }
1169
1170  /**
1171   * Tests the reads enabled flag for the region. When unset all reads should be rejected
1172   */
1173  @Test
1174  public void testRegionReadsEnabledFlag() throws IOException {
1175
1176    putDataByReplay(secondaryRegion, 0, 100, cq, families);
1177
1178    verifyData(secondaryRegion, 0, 100, cq, families);
1179
1180    // now disable reads
1181    secondaryRegion.setReadsEnabled(false);
1182    try {
1183      verifyData(secondaryRegion, 0, 100, cq, families);
1184      fail("Should have failed with IOException");
1185    } catch (IOException ex) {
1186      // expected
1187    }
1188
1189    // verify that we can still replay data
1190    putDataByReplay(secondaryRegion, 100, 100, cq, families);
1191
1192    // now enable reads again
1193    secondaryRegion.setReadsEnabled(true);
1194    verifyData(secondaryRegion, 0, 200, cq, families);
1195  }
1196
1197  /**
1198   * Tests the case where a request for flush cache is sent to the region, but region cannot flush.
1199   * It should write the flush request marker instead.
1200   */
1201  @Test
1202  public void testWriteFlushRequestMarker() throws IOException {
1203    // primary region is empty at this point. Request a flush with writeFlushRequestWalMarker=false
1204    FlushResultImpl result = primaryRegion.flushcache(true, false, FlushLifeCycleTracker.DUMMY);
1205    assertNotNull(result);
1206    assertEquals(FlushResultImpl.Result.CANNOT_FLUSH_MEMSTORE_EMPTY, result.result);
1207    assertFalse(result.wroteFlushWalMarker);
1208
1209    // request flush again, but this time with writeFlushRequestWalMarker = true
1210    result = primaryRegion.flushcache(true, true, FlushLifeCycleTracker.DUMMY);
1211    assertNotNull(result);
1212    assertEquals(FlushResultImpl.Result.CANNOT_FLUSH_MEMSTORE_EMPTY, result.result);
1213    assertTrue(result.wroteFlushWalMarker);
1214
1215    List<FlushDescriptor> flushes = Lists.newArrayList();
1216    reader = createWALReaderForPrimary();
1217    while (true) {
1218      WAL.Entry entry = reader.next();
1219      if (entry == null) {
1220        break;
1221      }
1222      FlushDescriptor flush = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
1223      if (flush != null) {
1224        flushes.add(flush);
1225      }
1226    }
1227
1228    assertEquals(1, flushes.size());
1229    assertNotNull(flushes.get(0));
1230    assertEquals(FlushDescriptor.FlushAction.CANNOT_FLUSH, flushes.get(0).getAction());
1231  }
1232
1233  /**
1234   * Test the case where the secondary region replica is not in reads enabled state because it is
1235   * waiting for a flush or region open marker from primary region. Replaying CANNOT_FLUSH flush
1236   * marker entry should restore the reads enabled status in the region and allow the reads to
1237   * continue.
1238   */
1239  @Test
1240  public void testReplayingFlushRequestRestoresReadsEnabledState() throws IOException {
1241    disableReads(secondaryRegion);
1242
1243    // Test case 1: Test that replaying CANNOT_FLUSH request marker assuming this came from
1244    // triggered flush restores readsEnabled
1245    primaryRegion.flushcache(true, true, FlushLifeCycleTracker.DUMMY);
1246    reader = createWALReaderForPrimary();
1247    while (true) {
1248      WAL.Entry entry = reader.next();
1249      if (entry == null) {
1250        break;
1251      }
1252      FlushDescriptor flush = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
1253      if (flush != null) {
1254        secondaryRegion.replayWALFlushMarker(flush, entry.getKey().getSequenceId());
1255      }
1256    }
1257
1258    // now reads should be enabled
1259    secondaryRegion.get(new Get(Bytes.toBytes(0)));
1260  }
1261
1262  /**
1263   * Test the case where the secondary region replica is not in reads enabled state because it is
1264   * waiting for a flush or region open marker from primary region. Replaying flush start and commit
1265   * entries should restore the reads enabled status in the region and allow the reads to continue.
1266   */
1267  @Test
1268  public void testReplayingFlushRestoresReadsEnabledState() throws IOException {
1269    // Test case 2: Test that replaying FLUSH_START and FLUSH_COMMIT markers assuming these came
1270    // from triggered flush restores readsEnabled
1271    disableReads(secondaryRegion);
1272
1273    // put some data in primary
1274    putData(primaryRegion, Durability.SYNC_WAL, 0, 100, cq, families);
1275    primaryRegion.flush(true);
1276    // I seem to need to push more edits through so the WAL flushes on local fs. This was not
1277    // needed before HBASE-15028. Not sure whats up. I can see that we have not flushed if I
1278    // look at the WAL if I pause the test here and then use WALPrettyPrinter to look at content..
1279    // Doing same check before HBASE-15028 I can see all edits flushed to the WAL. Somethings up
1280    // but can't figure it... and this is only test that seems to suffer this flush issue.
1281    // St.Ack 20160201
1282    putData(primaryRegion, Durability.SYNC_WAL, 0, 100, cq, families);
1283
1284    reader = createWALReaderForPrimary();
1285    while (true) {
1286      WAL.Entry entry = reader.next();
1287      LOG.info(Objects.toString(entry));
1288      if (entry == null) {
1289        break;
1290      }
1291      FlushDescriptor flush = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
1292      if (flush != null) {
1293        secondaryRegion.replayWALFlushMarker(flush, entry.getKey().getSequenceId());
1294      } else {
1295        replayEdit(secondaryRegion, entry);
1296      }
1297    }
1298
1299    // now reads should be enabled
1300    verifyData(secondaryRegion, 0, 100, cq, families);
1301  }
1302
1303  /**
1304   * Test the case where the secondary region replica is not in reads enabled state because it is
1305   * waiting for a flush or region open marker from primary region. Replaying flush start and commit
1306   * entries should restore the reads enabled status in the region and allow the reads to continue.
1307   */
1308  @Test
1309  public void testReplayingFlushWithEmptyMemstoreRestoresReadsEnabledState() throws IOException {
1310    // Test case 2: Test that replaying FLUSH_START and FLUSH_COMMIT markers assuming these came
1311    // from triggered flush restores readsEnabled
1312    disableReads(secondaryRegion);
1313
1314    // put some data in primary
1315    putData(primaryRegion, Durability.SYNC_WAL, 0, 100, cq, families);
1316    primaryRegion.flush(true);
1317
1318    reader = createWALReaderForPrimary();
1319    while (true) {
1320      WAL.Entry entry = reader.next();
1321      if (entry == null) {
1322        break;
1323      }
1324      FlushDescriptor flush = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
1325      if (flush != null) {
1326        secondaryRegion.replayWALFlushMarker(flush, entry.getKey().getSequenceId());
1327      }
1328    }
1329
1330    // now reads should be enabled
1331    verifyData(secondaryRegion, 0, 100, cq, families);
1332  }
1333
1334  /**
1335   * Test the case where the secondary region replica is not in reads enabled state because it is
1336   * waiting for a flush or region open marker from primary region. Replaying region open event
1337   * entry from primary should restore the reads enabled status in the region and allow the reads to
1338   * continue.
1339   */
1340  @Test
1341  public void testReplayingRegionOpenEventRestoresReadsEnabledState() throws IOException {
1342    // Test case 3: Test that replaying region open event markers restores readsEnabled
1343    disableReads(secondaryRegion);
1344
1345    primaryRegion.close();
1346    primaryRegion = HRegion.openHRegion(rootDir, primaryHri, htd, walPrimary, CONF, rss, null);
1347
1348    reader = createWALReaderForPrimary();
1349    while (true) {
1350      WAL.Entry entry = reader.next();
1351      if (entry == null) {
1352        break;
1353      }
1354
1355      RegionEventDescriptor regionEventDesc =
1356        WALEdit.getRegionEventDescriptor(entry.getEdit().getCells().get(0));
1357
1358      if (regionEventDesc != null) {
1359        secondaryRegion.replayWALRegionEventMarker(regionEventDesc);
1360      }
1361    }
1362
1363    // now reads should be enabled
1364    secondaryRegion.get(new Get(Bytes.toBytes(0)));
1365  }
1366
1367  @Test
1368  public void testRefresStoreFiles() throws IOException {
1369    assertEquals(0, primaryRegion.getStoreFileList(families).size());
1370    assertEquals(0, secondaryRegion.getStoreFileList(families).size());
1371
1372    // Test case 1: refresh with an empty region
1373    secondaryRegion.refreshStoreFiles();
1374    assertEquals(0, secondaryRegion.getStoreFileList(families).size());
1375
1376    // do one flush
1377    putDataWithFlushes(primaryRegion, 100, 100, 0);
1378    int numRows = 100;
1379
1380    // refresh the store file list, and ensure that the files are picked up.
1381    secondaryRegion.refreshStoreFiles();
1382    assertPathListsEqual(primaryRegion.getStoreFileList(families),
1383      secondaryRegion.getStoreFileList(families));
1384    assertEquals(families.length, secondaryRegion.getStoreFileList(families).size());
1385
1386    LOG.info("-- Verifying edits from secondary");
1387    verifyData(secondaryRegion, 0, numRows, cq, families);
1388
1389    // Test case 2: 3 some more flushes
1390    putDataWithFlushes(primaryRegion, 100, 300, 0);
1391    numRows = 300;
1392
1393    // refresh the store file list, and ensure that the files are picked up.
1394    secondaryRegion.refreshStoreFiles();
1395    assertPathListsEqual(primaryRegion.getStoreFileList(families),
1396      secondaryRegion.getStoreFileList(families));
1397    assertEquals(families.length * 4, secondaryRegion.getStoreFileList(families).size());
1398
1399    LOG.info("-- Verifying edits from secondary");
1400    verifyData(secondaryRegion, 0, numRows, cq, families);
1401
1402    if (FSUtils.WINDOWS) {
1403      // compaction cannot move files while they are open in secondary on windows. Skip remaining.
1404      return;
1405    }
1406
1407    // Test case 3: compact primary files
1408    primaryRegion.compactStores();
1409    List<HRegion> regions = new ArrayList<>();
1410    regions.add(primaryRegion);
1411    Mockito.doReturn(regions).when(rss).getRegions();
1412    CompactedHFilesDischarger cleaner = new CompactedHFilesDischarger(100, null, rss, false);
1413    cleaner.chore();
1414    secondaryRegion.refreshStoreFiles();
1415    assertPathListsEqual(primaryRegion.getStoreFileList(families),
1416      secondaryRegion.getStoreFileList(families));
1417    assertEquals(families.length, secondaryRegion.getStoreFileList(families).size());
1418
1419    LOG.info("-- Verifying edits from secondary");
1420    verifyData(secondaryRegion, 0, numRows, cq, families);
1421
1422    LOG.info("-- Replaying edits in secondary");
1423
1424    // Test case 4: replay some edits, ensure that memstore is dropped.
1425    assertTrue(secondaryRegion.getMemStoreDataSize() == 0);
1426    putDataWithFlushes(primaryRegion, 400, 400, 0);
1427    numRows = 400;
1428
1429    reader = createWALReaderForPrimary();
1430    while (true) {
1431      WAL.Entry entry = reader.next();
1432      if (entry == null) {
1433        break;
1434      }
1435      FlushDescriptor flush = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0));
1436      if (flush != null) {
1437        // do not replay flush
1438      } else {
1439        replayEdit(secondaryRegion, entry);
1440      }
1441    }
1442
1443    assertTrue(secondaryRegion.getMemStoreDataSize() > 0);
1444
1445    secondaryRegion.refreshStoreFiles();
1446
1447    assertTrue(secondaryRegion.getMemStoreDataSize() == 0);
1448
1449    LOG.info("-- Verifying edits from primary");
1450    verifyData(primaryRegion, 0, numRows, cq, families);
1451    LOG.info("-- Verifying edits from secondary");
1452    verifyData(secondaryRegion, 0, numRows, cq, families);
1453  }
1454
1455  /**
1456   * Paths can be qualified or not. This does the assertion using String->Path conversion.
1457   */
1458  private void assertPathListsEqual(List<String> list1, List<String> list2) {
1459    List<Path> l1 = new ArrayList<>(list1.size());
1460    for (String path : list1) {
1461      l1.add(Path.getPathWithoutSchemeAndAuthority(new Path(path)));
1462    }
1463    List<Path> l2 = new ArrayList<>(list2.size());
1464    for (String path : list2) {
1465      l2.add(Path.getPathWithoutSchemeAndAuthority(new Path(path)));
1466    }
1467    assertEquals(l1, l2);
1468  }
1469
1470  private void disableReads(HRegion region) {
1471    region.setReadsEnabled(false);
1472    try {
1473      verifyData(region, 0, 1, cq, families);
1474      fail("Should have failed with IOException");
1475    } catch (IOException ex) {
1476      // expected
1477    }
1478  }
1479
1480  private void replay(HRegion region, Put put, long replaySeqId) throws IOException {
1481    put.setDurability(Durability.SKIP_WAL);
1482    MutationReplay mutation = new MutationReplay(MutationType.PUT, put, 0, 0);
1483    region.batchReplay(new MutationReplay[] { mutation }, replaySeqId);
1484  }
1485
1486  /**
1487   * Tests replaying region open markers from primary region. Checks whether the files are picked up
1488   */
1489  @Test
1490  public void testReplayBulkLoadEvent() throws IOException {
1491    LOG.info("testReplayBulkLoadEvent starts");
1492    putDataWithFlushes(primaryRegion, 100, 0, 100); // no flush
1493
1494    // close the region and open again.
1495    primaryRegion.close();
1496    primaryRegion = HRegion.openHRegion(rootDir, primaryHri, htd, walPrimary, CONF, rss, null);
1497
1498    // bulk load a file into primary region
1499    byte[] randomValues = new byte[20];
1500    Bytes.random(randomValues);
1501    Path testPath = TEST_UTIL.getDataTestDirOnTestFS();
1502
1503    List<Pair<byte[], String>> familyPaths = new ArrayList<>();
1504    int expectedLoadFileCount = 0;
1505    for (byte[] family : families) {
1506      familyPaths.add(new Pair<>(family, createHFileForFamilies(testPath, family, randomValues)));
1507      expectedLoadFileCount++;
1508    }
1509    primaryRegion.bulkLoadHFiles(familyPaths, false, null);
1510
1511    // now replay the edits and the bulk load marker
1512    reader = createWALReaderForPrimary();
1513
1514    LOG.info("-- Replaying edits and region events in secondary");
1515    BulkLoadDescriptor bulkloadEvent = null;
1516    while (true) {
1517      WAL.Entry entry = reader.next();
1518      if (entry == null) {
1519        break;
1520      }
1521      bulkloadEvent = WALEdit.getBulkLoadDescriptor(entry.getEdit().getCells().get(0));
1522      if (bulkloadEvent != null) {
1523        break;
1524      }
1525    }
1526
1527    // we should have 1 bulk load event
1528    assertTrue(bulkloadEvent != null);
1529    assertEquals(expectedLoadFileCount, bulkloadEvent.getStoresCount());
1530
1531    // replay the bulk load event
1532    secondaryRegion.replayWALBulkLoadEventMarker(bulkloadEvent);
1533
1534    List<String> storeFileName = new ArrayList<>();
1535    for (StoreDescriptor storeDesc : bulkloadEvent.getStoresList()) {
1536      storeFileName.addAll(storeDesc.getStoreFileList());
1537    }
1538    // assert that the bulk loaded files are picked
1539    for (HStore s : secondaryRegion.getStores()) {
1540      for (HStoreFile sf : s.getStorefiles()) {
1541        storeFileName.remove(sf.getPath().getName());
1542      }
1543    }
1544    assertTrue(storeFileName.isEmpty(), "Found some store file isn't loaded:" + storeFileName);
1545
1546    LOG.info("-- Verifying edits from secondary");
1547    for (byte[] family : families) {
1548      assertGet(secondaryRegion, family, randomValues);
1549    }
1550  }
1551
1552  @Test
1553  public void testReplayingFlushCommitWithFileAlreadyDeleted() throws IOException {
1554    // tests replaying flush commit marker, but the flush file has already been compacted
1555    // from primary and also deleted from the archive directory
1556    secondaryRegion.replayWALFlushCommitMarker(FlushDescriptor.newBuilder()
1557      .setFlushSequenceNumber(Long.MAX_VALUE)
1558      .setTableName(UnsafeByteOperations
1559        .unsafeWrap(primaryRegion.getTableDescriptor().getTableName().getName()))
1560      .setAction(FlushAction.COMMIT_FLUSH)
1561      .setEncodedRegionName(
1562        UnsafeByteOperations.unsafeWrap(primaryRegion.getRegionInfo().getEncodedNameAsBytes()))
1563      .setRegionName(UnsafeByteOperations.unsafeWrap(primaryRegion.getRegionInfo().getRegionName()))
1564      .addStoreFlushes(StoreFlushDescriptor.newBuilder()
1565        .setFamilyName(UnsafeByteOperations.unsafeWrap(families[0]))
1566        .setStoreHomeDir("/store_home_dir").addFlushOutput("/foo/baz/123").build())
1567      .build());
1568  }
1569
1570  @Test
1571  public void testReplayingCompactionWithFileAlreadyDeleted() throws IOException {
1572    // tests replaying compaction marker, but the compaction output file has already been compacted
1573    // from primary and also deleted from the archive directory
1574    secondaryRegion
1575      .replayWALCompactionMarker(
1576        CompactionDescriptor.newBuilder()
1577          .setTableName(UnsafeByteOperations
1578            .unsafeWrap(primaryRegion.getTableDescriptor().getTableName().getName()))
1579          .setEncodedRegionName(
1580            UnsafeByteOperations.unsafeWrap(primaryRegion.getRegionInfo().getEncodedNameAsBytes()))
1581          .setFamilyName(UnsafeByteOperations.unsafeWrap(families[0])).addCompactionInput("/123")
1582          .addCompactionOutput("/456").setStoreHomeDir("/store_home_dir")
1583          .setRegionName(
1584            UnsafeByteOperations.unsafeWrap(primaryRegion.getRegionInfo().getRegionName()))
1585          .build(),
1586        true, true, Long.MAX_VALUE);
1587  }
1588
1589  @Test
1590  public void testReplayingRegionOpenEventWithFileAlreadyDeleted() throws IOException {
1591    // tests replaying region open event marker, but the region files have already been compacted
1592    // from primary and also deleted from the archive directory
1593    secondaryRegion.replayWALRegionEventMarker(RegionEventDescriptor.newBuilder()
1594      .setTableName(UnsafeByteOperations
1595        .unsafeWrap(primaryRegion.getTableDescriptor().getTableName().getName()))
1596      .setEncodedRegionName(
1597        UnsafeByteOperations.unsafeWrap(primaryRegion.getRegionInfo().getEncodedNameAsBytes()))
1598      .setRegionName(UnsafeByteOperations.unsafeWrap(primaryRegion.getRegionInfo().getRegionName()))
1599      .setEventType(EventType.REGION_OPEN)
1600      .setServer(ProtobufUtil.toServerName(ServerName.valueOf("foo", 1, 1)))
1601      .setLogSequenceNumber(Long.MAX_VALUE)
1602      .addStores(
1603        StoreDescriptor.newBuilder().setFamilyName(UnsafeByteOperations.unsafeWrap(families[0]))
1604          .setStoreHomeDir("/store_home_dir").addStoreFile("/123").build())
1605      .build());
1606  }
1607
1608  @Test
1609  public void testReplayingBulkLoadEventWithFileAlreadyDeleted() throws IOException {
1610    // tests replaying bulk load event marker, but the bulk load files have already been compacted
1611    // from primary and also deleted from the archive directory
1612    secondaryRegion.replayWALBulkLoadEventMarker(BulkLoadDescriptor.newBuilder()
1613      .setTableName(
1614        ProtobufUtil.toProtoTableName(primaryRegion.getTableDescriptor().getTableName()))
1615      .setEncodedRegionName(
1616        UnsafeByteOperations.unsafeWrap(primaryRegion.getRegionInfo().getEncodedNameAsBytes()))
1617      .setBulkloadSeqNum(Long.MAX_VALUE)
1618      .addStores(
1619        StoreDescriptor.newBuilder().setFamilyName(UnsafeByteOperations.unsafeWrap(families[0]))
1620          .setStoreHomeDir("/store_home_dir").addStoreFile("/123").build())
1621      .build());
1622  }
1623
1624  private String createHFileForFamilies(Path testPath, byte[] family, byte[] valueBytes)
1625    throws IOException {
1626    HFile.WriterFactory hFileFactory = HFile.getWriterFactoryNoCache(TEST_UTIL.getConfiguration());
1627    // TODO We need a way to do this without creating files
1628    Path testFile = new Path(testPath, TEST_UTIL.getRandomUUID().toString());
1629    FSDataOutputStream out = TEST_UTIL.getTestFileSystem().create(testFile);
1630    try {
1631      hFileFactory.withOutputStream(out);
1632      hFileFactory.withFileContext(new HFileContextBuilder().build());
1633      HFile.Writer writer = hFileFactory.create();
1634      try {
1635        writer.append(new KeyValue(ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY)
1636          .setRow(valueBytes).setFamily(family).setQualifier(valueBytes).setTimestamp(0L)
1637          .setType(KeyValue.Type.Put.getCode()).setValue(valueBytes).build()));
1638      } finally {
1639        writer.close();
1640      }
1641    } finally {
1642      out.close();
1643    }
1644    return testFile.toString();
1645  }
1646
1647  /**
1648   * Puts a total of numRows + numRowsAfterFlush records indexed with numeric row keys. Does a flush
1649   * every flushInterval number of records. Then it puts numRowsAfterFlush number of more rows but
1650   * does not execute flush after
1651   */
1652  private void putDataWithFlushes(HRegion region, int flushInterval, int numRows,
1653    int numRowsAfterFlush) throws IOException {
1654    int start = 0;
1655    for (; start < numRows; start += flushInterval) {
1656      LOG.info("-- Writing some data to primary from " + start + " to " + (start + flushInterval));
1657      putData(region, Durability.SYNC_WAL, start, flushInterval, cq, families);
1658      LOG.info("-- Flushing primary, creating 3 files for 3 stores");
1659      region.flush(true);
1660    }
1661    LOG.info("-- Writing some more data to primary, not flushing");
1662    putData(region, Durability.SYNC_WAL, start, numRowsAfterFlush, cq, families);
1663  }
1664
1665  private void putDataByReplay(HRegion region, int startRow, int numRows, byte[] qf,
1666    byte[]... families) throws IOException {
1667    for (int i = startRow; i < startRow + numRows; i++) {
1668      Put put = new Put(Bytes.toBytes("" + i));
1669      put.setDurability(Durability.SKIP_WAL);
1670      for (byte[] family : families) {
1671        put.addColumn(family, qf, EnvironmentEdgeManager.currentTime(), null);
1672      }
1673      replay(region, put, i + 1);
1674    }
1675  }
1676
1677  private static HRegion initHRegion(byte[] tableName, byte[]... families) throws IOException {
1678    return TEST_UTIL.createLocalHRegion(TableName.valueOf(tableName), HConstants.EMPTY_START_ROW,
1679      HConstants.EMPTY_END_ROW, CONF, false, Durability.SYNC_WAL, null, families);
1680  }
1681}