org.apache.hadoop.hbase.regionserver.wal.TestHLog.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.regionserver.wal.TestHLog.java

Source

/**
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.hbase.regionserver.wal;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.BindException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.lang.mutable.MutableBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.impl.Log4JLogger;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.CellScanner;
import org.apache.hadoop.hbase.Coprocessor;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.KeyValueUtil;
import org.apache.hadoop.hbase.LargeTests;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
import org.apache.hadoop.hbase.coprocessor.SampleRegionWALObserver;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.regionserver.wal.HLog.Reader;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.EnvironmentEdge;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.hbase.util.FSUtils;
import org.apache.hadoop.hbase.util.Threads;
import org.apache.hadoop.hdfs.DFSClient;
import org.apache.hadoop.hdfs.DistributedFileSystem;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.hdfs.protocol.FSConstants;
import org.apache.hadoop.hdfs.server.datanode.DataNode;
import org.apache.hadoop.hdfs.server.namenode.LeaseManager;
import org.apache.log4j.Level;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;

/** JUnit test case for HLog */
@Category(LargeTests.class)
@SuppressWarnings("deprecation")
public class TestHLog {
    private static final Log LOG = LogFactory.getLog(TestHLog.class);
    {
        ((Log4JLogger) DataNode.LOG).getLogger().setLevel(Level.ALL);
        ((Log4JLogger) LeaseManager.LOG).getLogger().setLevel(Level.ALL);
        ((Log4JLogger) LogFactory.getLog("org.apache.hadoop.hdfs.server.namenode.FSNamesystem")).getLogger()
                .setLevel(Level.ALL);
        ((Log4JLogger) DFSClient.LOG).getLogger().setLevel(Level.ALL);
        ((Log4JLogger) HLog.LOG).getLogger().setLevel(Level.ALL);
    }

    private static Configuration conf;
    private static FileSystem fs;
    private static Path dir;
    private static MiniDFSCluster cluster;
    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
    private static Path hbaseDir;
    private static Path oldLogDir;

    @Before
    public void setUp() throws Exception {
        FileStatus[] entries = fs.listStatus(new Path("/"));
        for (FileStatus dir : entries) {
            fs.delete(dir.getPath(), true);
        }
    }

    @After
    public void tearDown() throws Exception {
    }

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        // Make block sizes small.
        TEST_UTIL.getConfiguration().setInt("dfs.blocksize", 1024 * 1024);
        // needed for testAppendClose()
        TEST_UTIL.getConfiguration().setBoolean("dfs.support.broken.append", true);
        TEST_UTIL.getConfiguration().setBoolean("dfs.support.append", true);
        // quicker heartbeat interval for faster DN death notification
        TEST_UTIL.getConfiguration().setInt("dfs.namenode.heartbeat.recheck-interval", 5000);
        TEST_UTIL.getConfiguration().setInt("dfs.heartbeat.interval", 1);
        TEST_UTIL.getConfiguration().setInt("dfs.client.socket-timeout", 5000);

        // faster failover with cluster.shutdown();fs.close() idiom
        TEST_UTIL.getConfiguration().setInt("ipc.client.connect.max.retries", 1);
        TEST_UTIL.getConfiguration().setInt("dfs.client.block.recovery.retries", 1);
        TEST_UTIL.getConfiguration().setInt("ipc.client.connection.maxidletime", 500);
        TEST_UTIL.getConfiguration().set(CoprocessorHost.WAL_COPROCESSOR_CONF_KEY,
                SampleRegionWALObserver.class.getName());
        TEST_UTIL.startMiniDFSCluster(3);

        conf = TEST_UTIL.getConfiguration();
        cluster = TEST_UTIL.getDFSCluster();
        fs = cluster.getFileSystem();

        hbaseDir = TEST_UTIL.createRootDir();
        oldLogDir = new Path(hbaseDir, HConstants.HREGION_OLDLOGDIR_NAME);
        dir = new Path(hbaseDir, getName());
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception {
        TEST_UTIL.shutdownMiniCluster();
    }

    private static String getName() {
        // TODO Auto-generated method stub
        return "TestHLog";
    }

    /**
     * Test flush for sure has a sequence id that is beyond the last edit appended.  We do this
     * by slowing appends in the background ring buffer thread while in foreground we call
     * flush.  The addition of the sync over HRegion in flush should fix an issue where flush was
     * returning before all of its appends had made it out to the WAL (HBASE-11109).
     * @throws IOException 
     * @see HBASE-11109
     */
    @Test
    public void testFlushSequenceIdIsGreaterThanAllEditsInHFile() throws IOException {
        String testName = "testFlushSequenceIdIsGreaterThanAllEditsInHFile";
        final TableName tableName = TableName.valueOf(testName);
        final HRegionInfo hri = new HRegionInfo(tableName);
        final byte[] rowName = tableName.getName();
        final HTableDescriptor htd = new HTableDescriptor(tableName);
        htd.addFamily(new HColumnDescriptor("f"));
        HRegion r = HRegion.createHRegion(hri, TEST_UTIL.getDefaultRootDirPath(), TEST_UTIL.getConfiguration(),
                htd);
        HRegion.closeHRegion(r);
        final int countPerFamily = 10;
        final MutableBoolean goslow = new MutableBoolean(false);
        // Bypass factory so I can subclass and doctor a method.
        FSHLog wal = new FSHLog(FileSystem.get(conf), TEST_UTIL.getDefaultRootDirPath(), testName, conf) {
            @Override
            void atHeadOfRingBufferEventHandlerAppend() {
                if (goslow.isTrue()) {
                    Threads.sleep(100);
                    LOG.debug("Sleeping before appending 100ms");
                }
                super.atHeadOfRingBufferEventHandlerAppend();
            }
        };
        HRegion region = HRegion.openHRegion(TEST_UTIL.getConfiguration(), TEST_UTIL.getTestFileSystem(),
                TEST_UTIL.getDefaultRootDirPath(), hri, htd, wal);
        EnvironmentEdge ee = EnvironmentEdgeManager.getDelegate();
        try {
            List<Put> puts = null;
            for (HColumnDescriptor hcd : htd.getFamilies()) {
                puts = TestWALReplay.addRegionEdits(rowName, hcd.getName(), countPerFamily, ee, region, "x");
            }

            // Now assert edits made it in.
            final Get g = new Get(rowName);
            Result result = region.get(g);
            assertEquals(countPerFamily * htd.getFamilies().size(), result.size());

            // Construct a WALEdit and add it a few times to the WAL.
            WALEdit edits = new WALEdit();
            for (Put p : puts) {
                CellScanner cs = p.cellScanner();
                while (cs.advance()) {
                    edits.add(KeyValueUtil.ensureKeyValue(cs.current()));
                }
            }
            // Add any old cluster id.
            List<UUID> clusterIds = new ArrayList<UUID>();
            clusterIds.add(UUID.randomUUID());
            // Now make appends run slow.
            goslow.setValue(true);
            for (int i = 0; i < countPerFamily; i++) {
                wal.appendNoSync(region.getRegionInfo(), tableName, edits, clusterIds, System.currentTimeMillis(),
                        htd, region.getSequenceId(), true, -1, -1);
            }
            region.flushcache();
            // FlushResult.flushSequenceId is not visible here so go get the current sequence id.
            long currentSequenceId = region.getSequenceId().get();
            // Now release the appends
            goslow.setValue(false);
            synchronized (goslow) {
                goslow.notifyAll();
            }
            assertTrue(currentSequenceId >= region.getSequenceId().get());
        } finally {
            region.close(true);
            wal.close();
        }
    }

    /**
     * Write to a log file with three concurrent threads and verifying all data is written.
     * @throws Exception
     */
    @Test
    public void testConcurrentWrites() throws Exception {
        // Run the HPE tool with three threads writing 3000 edits each concurrently.
        // When done, verify that all edits were written.
        int errCode = HLogPerformanceEvaluation.innerMain(new Configuration(TEST_UTIL.getConfiguration()),
                new String[] { "-threads", "3", "-verify", "-noclosefs", "-iterations", "3000" });
        assertEquals(0, errCode);
    }

    /**
     * Just write multiple logs then split.  Before fix for HADOOP-2283, this
     * would fail.
     * @throws IOException
     */
    @Test
    public void testSplit() throws IOException {
        final TableName tableName = TableName.valueOf(getName());
        final byte[] rowName = tableName.getName();
        Path logdir = new Path(hbaseDir, HConstants.HREGION_LOGDIR_NAME);
        HLog log = HLogFactory.createHLog(fs, hbaseDir, HConstants.HREGION_LOGDIR_NAME, conf);
        final int howmany = 3;
        HRegionInfo[] infos = new HRegionInfo[3];
        Path tabledir = FSUtils.getTableDir(hbaseDir, tableName);
        fs.mkdirs(tabledir);
        for (int i = 0; i < howmany; i++) {
            infos[i] = new HRegionInfo(tableName, Bytes.toBytes("" + i), Bytes.toBytes("" + (i + 1)), false);
            fs.mkdirs(new Path(tabledir, infos[i].getEncodedName()));
            LOG.info("allo " + new Path(tabledir, infos[i].getEncodedName()).toString());
        }
        HTableDescriptor htd = new HTableDescriptor(tableName);
        htd.addFamily(new HColumnDescriptor("column"));

        // Add edits for three regions.
        final AtomicLong sequenceId = new AtomicLong(1);
        try {
            for (int ii = 0; ii < howmany; ii++) {
                for (int i = 0; i < howmany; i++) {

                    for (int j = 0; j < howmany; j++) {
                        WALEdit edit = new WALEdit();
                        byte[] family = Bytes.toBytes("column");
                        byte[] qualifier = Bytes.toBytes(Integer.toString(j));
                        byte[] column = Bytes.toBytes("column:" + Integer.toString(j));
                        edit.add(new KeyValue(rowName, family, qualifier, System.currentTimeMillis(), column));
                        LOG.info("Region " + i + ": " + edit);
                        log.append(infos[i], tableName, edit, System.currentTimeMillis(), htd, sequenceId);
                    }
                }
                log.rollWriter();
            }
            log.close();
            List<Path> splits = HLogSplitter.split(hbaseDir, logdir, oldLogDir, fs, conf);
            verifySplits(splits, howmany);
            log = null;
        } finally {
            if (log != null) {
                log.closeAndDelete();
            }
        }
    }

    /**
     * Test new HDFS-265 sync.
     * @throws Exception
     */
    @Test
    public void Broken_testSync() throws Exception {
        TableName tableName = TableName.valueOf(getName());
        // First verify that using streams all works.
        Path p = new Path(dir, getName() + ".fsdos");
        FSDataOutputStream out = fs.create(p);
        out.write(tableName.getName());
        Method syncMethod = null;
        try {
            syncMethod = out.getClass().getMethod("hflush", new Class<?>[] {});
        } catch (NoSuchMethodException e) {
            try {
                syncMethod = out.getClass().getMethod("sync", new Class<?>[] {});
            } catch (NoSuchMethodException ex) {
                fail("This version of Hadoop supports neither Syncable.sync() " + "nor Syncable.hflush().");
            }
        }
        syncMethod.invoke(out, new Object[] {});
        FSDataInputStream in = fs.open(p);
        assertTrue(in.available() > 0);
        byte[] buffer = new byte[1024];
        int read = in.read(buffer);
        assertEquals(tableName.getName().length, read);
        out.close();
        in.close();

        HLog wal = HLogFactory.createHLog(fs, dir, "hlogdir", conf);
        final AtomicLong sequenceId = new AtomicLong(1);
        final int total = 20;
        HLog.Reader reader = null;

        try {
            HRegionInfo info = new HRegionInfo(tableName, null, null, false);
            HTableDescriptor htd = new HTableDescriptor();
            htd.addFamily(new HColumnDescriptor(tableName.getName()));

            for (int i = 0; i < total; i++) {
                WALEdit kvs = new WALEdit();
                kvs.add(new KeyValue(Bytes.toBytes(i), tableName.getName(), tableName.getName()));
                wal.append(info, tableName, kvs, System.currentTimeMillis(), htd, sequenceId);
            }
            // Now call sync and try reading.  Opening a Reader before you sync just
            // gives you EOFE.
            wal.sync();
            // Open a Reader.
            Path walPath = ((FSHLog) wal).computeFilename();
            reader = HLogFactory.createReader(fs, walPath, conf);
            int count = 0;
            HLog.Entry entry = new HLog.Entry();
            while ((entry = reader.next(entry)) != null)
                count++;
            assertEquals(total, count);
            reader.close();
            // Add test that checks to see that an open of a Reader works on a file
            // that has had a sync done on it.
            for (int i = 0; i < total; i++) {
                WALEdit kvs = new WALEdit();
                kvs.add(new KeyValue(Bytes.toBytes(i), tableName.getName(), tableName.getName()));
                wal.append(info, tableName, kvs, System.currentTimeMillis(), htd, sequenceId);
            }
            reader = HLogFactory.createReader(fs, walPath, conf);
            count = 0;
            while ((entry = reader.next(entry)) != null)
                count++;
            assertTrue(count >= total);
            reader.close();
            // If I sync, should see double the edits.
            wal.sync();
            reader = HLogFactory.createReader(fs, walPath, conf);
            count = 0;
            while ((entry = reader.next(entry)) != null)
                count++;
            assertEquals(total * 2, count);
            // Now do a test that ensures stuff works when we go over block boundary,
            // especially that we return good length on file.
            final byte[] value = new byte[1025 * 1024]; // Make a 1M value.
            for (int i = 0; i < total; i++) {
                WALEdit kvs = new WALEdit();
                kvs.add(new KeyValue(Bytes.toBytes(i), tableName.getName(), value));
                wal.append(info, tableName, kvs, System.currentTimeMillis(), htd, sequenceId);
            }
            // Now I should have written out lots of blocks.  Sync then read.
            wal.sync();
            reader = HLogFactory.createReader(fs, walPath, conf);
            count = 0;
            while ((entry = reader.next(entry)) != null)
                count++;
            assertEquals(total * 3, count);
            reader.close();
            // Close it and ensure that closed, Reader gets right length also.
            wal.close();
            reader = HLogFactory.createReader(fs, walPath, conf);
            count = 0;
            while ((entry = reader.next(entry)) != null)
                count++;
            assertEquals(total * 3, count);
            reader.close();
        } finally {
            if (wal != null)
                wal.closeAndDelete();
            if (reader != null)
                reader.close();
        }
    }

    private void verifySplits(List<Path> splits, final int howmany) throws IOException {
        assertEquals(howmany * howmany, splits.size());
        for (int i = 0; i < splits.size(); i++) {
            LOG.info("Verifying=" + splits.get(i));
            HLog.Reader reader = HLogFactory.createReader(fs, splits.get(i), conf);
            try {
                int count = 0;
                String previousRegion = null;
                long seqno = -1;
                HLog.Entry entry = new HLog.Entry();
                while ((entry = reader.next(entry)) != null) {
                    HLogKey key = entry.getKey();
                    String region = Bytes.toString(key.getEncodedRegionName());
                    // Assert that all edits are for same region.
                    if (previousRegion != null) {
                        assertEquals(previousRegion, region);
                    }
                    LOG.info("oldseqno=" + seqno + ", newseqno=" + key.getLogSeqNum());
                    assertTrue(seqno < key.getLogSeqNum());
                    seqno = key.getLogSeqNum();
                    previousRegion = region;
                    count++;
                }
                assertEquals(howmany, count);
            } finally {
                reader.close();
            }
        }
    }

    /*
     * We pass different values to recoverFileLease() so that different code paths are covered
     *
     * For this test to pass, requires:
     * 1. HDFS-200 (append support)
     * 2. HDFS-988 (SafeMode should freeze file operations
     *              [FSNamesystem.nextGenerationStampForBlock])
     * 3. HDFS-142 (on restart, maintain pendingCreates)
     */
    @Test(timeout = 300000)
    public void testAppendClose() throws Exception {
        TableName tableName = TableName.valueOf(getName());
        HRegionInfo regioninfo = new HRegionInfo(tableName, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW,
                false);

        HLog wal = HLogFactory.createHLog(fs, dir, "hlogdir", "hlogdir_archive", conf);
        final AtomicLong sequenceId = new AtomicLong(1);
        final int total = 20;

        HTableDescriptor htd = new HTableDescriptor();
        htd.addFamily(new HColumnDescriptor(tableName.getName()));

        for (int i = 0; i < total; i++) {
            WALEdit kvs = new WALEdit();
            kvs.add(new KeyValue(Bytes.toBytes(i), tableName.getName(), tableName.getName()));
            wal.append(regioninfo, tableName, kvs, System.currentTimeMillis(), htd, sequenceId);
        }
        // Now call sync to send the data to HDFS datanodes
        wal.sync();
        int namenodePort = cluster.getNameNodePort();
        final Path walPath = ((FSHLog) wal).computeFilename();

        // Stop the cluster.  (ensure restart since we're sharing MiniDFSCluster)
        try {
            DistributedFileSystem dfs = (DistributedFileSystem) cluster.getFileSystem();
            dfs.setSafeMode(FSConstants.SafeModeAction.SAFEMODE_ENTER);
            TEST_UTIL.shutdownMiniDFSCluster();
            try {
                // wal.writer.close() will throw an exception,
                // but still call this since it closes the LogSyncer thread first
                wal.close();
            } catch (IOException e) {
                LOG.info(e);
            }
            fs.close(); // closing FS last so DFSOutputStream can't call close
            LOG.info("STOPPED first instance of the cluster");
        } finally {
            // Restart the cluster
            while (cluster.isClusterUp()) {
                LOG.error("Waiting for cluster to go down");
                Thread.sleep(1000);
            }
            assertFalse(cluster.isClusterUp());
            cluster = null;
            for (int i = 0; i < 100; i++) {
                try {
                    cluster = TEST_UTIL.startMiniDFSClusterForTestHLog(namenodePort);
                    break;
                } catch (BindException e) {
                    LOG.info("Sleeping.  BindException bringing up new cluster");
                    Threads.sleep(1000);
                }
            }
            cluster.waitActive();
            fs = cluster.getFileSystem();
            LOG.info("STARTED second instance.");
        }

        // set the lease period to be 1 second so that the
        // namenode triggers lease recovery upon append request
        Method setLeasePeriod = cluster.getClass().getDeclaredMethod("setLeasePeriod",
                new Class[] { Long.TYPE, Long.TYPE });
        setLeasePeriod.setAccessible(true);
        setLeasePeriod.invoke(cluster, 1000L, 1000L);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            LOG.info(e);
        }

        // Now try recovering the log, like the HMaster would do
        final FileSystem recoveredFs = fs;
        final Configuration rlConf = conf;

        class RecoverLogThread extends Thread {
            public Exception exception = null;

            public void run() {
                try {
                    FSUtils.getInstance(fs, rlConf).recoverFileLease(recoveredFs, walPath, rlConf, null);
                } catch (IOException e) {
                    exception = e;
                }
            }
        }

        RecoverLogThread t = new RecoverLogThread();
        t.start();
        // Timeout after 60 sec. Without correct patches, would be an infinite loop
        t.join(60 * 1000);
        if (t.isAlive()) {
            t.interrupt();
            throw new Exception("Timed out waiting for HLog.recoverLog()");
        }

        if (t.exception != null)
            throw t.exception;

        // Make sure you can read all the content
        HLog.Reader reader = HLogFactory.createReader(fs, walPath, conf);
        int count = 0;
        HLog.Entry entry = new HLog.Entry();
        while (reader.next(entry) != null) {
            count++;
            assertTrue("Should be one KeyValue per WALEdit", entry.getEdit().getKeyValues().size() == 1);
        }
        assertEquals(total, count);
        reader.close();

        // Reset the lease period
        setLeasePeriod.invoke(cluster, new Object[] { new Long(60000), new Long(3600000) });
    }

    /**
     * Tests that we can write out an edit, close, and then read it back in again.
     * @throws IOException
     */
    @Test
    public void testEditAdd() throws IOException {
        final int COL_COUNT = 10;
        final TableName tableName = TableName.valueOf("tablename");
        final byte[] row = Bytes.toBytes("row");
        HLog.Reader reader = null;
        HLog log = null;
        try {
            log = HLogFactory.createHLog(fs, hbaseDir, getName(), conf);
            final AtomicLong sequenceId = new AtomicLong(1);

            // Write columns named 1, 2, 3, etc. and then values of single byte
            // 1, 2, 3...
            long timestamp = System.currentTimeMillis();
            WALEdit cols = new WALEdit();
            for (int i = 0; i < COL_COUNT; i++) {
                cols.add(new KeyValue(row, Bytes.toBytes("column"), Bytes.toBytes(Integer.toString(i)), timestamp,
                        new byte[] { (byte) (i + '0') }));
            }
            HRegionInfo info = new HRegionInfo(tableName, row, Bytes.toBytes(Bytes.toString(row) + "1"), false);
            HTableDescriptor htd = new HTableDescriptor();
            htd.addFamily(new HColumnDescriptor("column"));

            log.append(info, tableName, cols, System.currentTimeMillis(), htd, sequenceId);
            log.startCacheFlush(info.getEncodedNameAsBytes());
            log.completeCacheFlush(info.getEncodedNameAsBytes());
            log.close();
            Path filename = ((FSHLog) log).computeFilename();
            log = null;
            // Now open a reader on the log and assert append worked.
            reader = HLogFactory.createReader(fs, filename, conf);
            // Above we added all columns on a single row so we only read one
            // entry in the below... thats why we have '1'.
            for (int i = 0; i < 1; i++) {
                HLog.Entry entry = reader.next(null);
                if (entry == null)
                    break;
                HLogKey key = entry.getKey();
                WALEdit val = entry.getEdit();
                assertTrue(Bytes.equals(info.getEncodedNameAsBytes(), key.getEncodedRegionName()));
                assertTrue(tableName.equals(key.getTablename()));
                KeyValue kv = val.getKeyValues().get(0);
                assertTrue(Bytes.equals(row, kv.getRow()));
                assertEquals((byte) (i + '0'), kv.getValue()[0]);
                System.out.println(key + " " + val);
            }
        } finally {
            if (log != null) {
                log.closeAndDelete();
            }
            if (reader != null) {
                reader.close();
            }
        }
    }

    /**
     * @throws IOException
     */
    @Test
    public void testAppend() throws IOException {
        final int COL_COUNT = 10;
        final TableName tableName = TableName.valueOf("tablename");
        final byte[] row = Bytes.toBytes("row");
        Reader reader = null;
        HLog log = HLogFactory.createHLog(fs, hbaseDir, getName(), conf);
        final AtomicLong sequenceId = new AtomicLong(1);
        try {
            // Write columns named 1, 2, 3, etc. and then values of single byte
            // 1, 2, 3...
            long timestamp = System.currentTimeMillis();
            WALEdit cols = new WALEdit();
            for (int i = 0; i < COL_COUNT; i++) {
                cols.add(new KeyValue(row, Bytes.toBytes("column"), Bytes.toBytes(Integer.toString(i)), timestamp,
                        new byte[] { (byte) (i + '0') }));
            }
            HRegionInfo hri = new HRegionInfo(tableName, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
            HTableDescriptor htd = new HTableDescriptor();
            htd.addFamily(new HColumnDescriptor("column"));
            log.append(hri, tableName, cols, System.currentTimeMillis(), htd, sequenceId);
            log.startCacheFlush(hri.getEncodedNameAsBytes());
            log.completeCacheFlush(hri.getEncodedNameAsBytes());
            log.close();
            Path filename = ((FSHLog) log).computeFilename();
            log = null;
            // Now open a reader on the log and assert append worked.
            reader = HLogFactory.createReader(fs, filename, conf);
            HLog.Entry entry = reader.next();
            assertEquals(COL_COUNT, entry.getEdit().size());
            int idx = 0;
            for (KeyValue val : entry.getEdit().getKeyValues()) {
                assertTrue(Bytes.equals(hri.getEncodedNameAsBytes(), entry.getKey().getEncodedRegionName()));
                assertTrue(tableName.equals(entry.getKey().getTablename()));
                assertTrue(Bytes.equals(row, val.getRow()));
                assertEquals((byte) (idx + '0'), val.getValue()[0]);
                System.out.println(entry.getKey() + " " + val);
                idx++;
            }
        } finally {
            if (log != null) {
                log.closeAndDelete();
            }
            if (reader != null) {
                reader.close();
            }
        }
    }

    /**
     * Test that we can visit entries before they are appended
     * @throws Exception
     */
    @Test
    public void testVisitors() throws Exception {
        final int COL_COUNT = 10;
        final TableName tableName = TableName.valueOf("tablename");
        final byte[] row = Bytes.toBytes("row");
        HLog log = HLogFactory.createHLog(fs, hbaseDir, getName(), conf);
        final AtomicLong sequenceId = new AtomicLong(1);
        try {
            DumbWALActionsListener visitor = new DumbWALActionsListener();
            log.registerWALActionsListener(visitor);
            long timestamp = System.currentTimeMillis();
            HTableDescriptor htd = new HTableDescriptor();
            htd.addFamily(new HColumnDescriptor("column"));

            HRegionInfo hri = new HRegionInfo(tableName, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
            for (int i = 0; i < COL_COUNT; i++) {
                WALEdit cols = new WALEdit();
                cols.add(new KeyValue(row, Bytes.toBytes("column"), Bytes.toBytes(Integer.toString(i)), timestamp,
                        new byte[] { (byte) (i + '0') }));
                log.append(hri, tableName, cols, System.currentTimeMillis(), htd, sequenceId);
            }
            assertEquals(COL_COUNT, visitor.increments);
            log.unregisterWALActionsListener(visitor);
            WALEdit cols = new WALEdit();
            cols.add(new KeyValue(row, Bytes.toBytes("column"), Bytes.toBytes(Integer.toString(11)), timestamp,
                    new byte[] { (byte) (11 + '0') }));
            log.append(hri, tableName, cols, System.currentTimeMillis(), htd, sequenceId);
            assertEquals(COL_COUNT, visitor.increments);
        } finally {
            if (log != null)
                log.closeAndDelete();
        }
    }

    @Test
    public void testLogCleaning() throws Exception {
        LOG.info("testLogCleaning");
        final TableName tableName = TableName.valueOf("testLogCleaning");
        final TableName tableName2 = TableName.valueOf("testLogCleaning2");

        HLog log = HLogFactory.createHLog(fs, hbaseDir, getName(), conf);
        final AtomicLong sequenceId = new AtomicLong(1);
        try {
            HRegionInfo hri = new HRegionInfo(tableName, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
            HRegionInfo hri2 = new HRegionInfo(tableName2, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);

            // Add a single edit and make sure that rolling won't remove the file
            // Before HBASE-3198 it used to delete it
            addEdits(log, hri, tableName, 1, sequenceId);
            log.rollWriter();
            assertEquals(1, ((FSHLog) log).getNumRolledLogFiles());

            // See if there's anything wrong with more than 1 edit
            addEdits(log, hri, tableName, 2, sequenceId);
            log.rollWriter();
            assertEquals(2, ((FSHLog) log).getNumRolledLogFiles());

            // Now mix edits from 2 regions, still no flushing
            addEdits(log, hri, tableName, 1, sequenceId);
            addEdits(log, hri2, tableName2, 1, sequenceId);
            addEdits(log, hri, tableName, 1, sequenceId);
            addEdits(log, hri2, tableName2, 1, sequenceId);
            log.rollWriter();
            assertEquals(3, ((FSHLog) log).getNumRolledLogFiles());

            // Flush the first region, we expect to see the first two files getting
            // archived. We need to append something or writer won't be rolled.
            addEdits(log, hri2, tableName2, 1, sequenceId);
            log.startCacheFlush(hri.getEncodedNameAsBytes());
            log.completeCacheFlush(hri.getEncodedNameAsBytes());
            log.rollWriter();
            assertEquals(2, ((FSHLog) log).getNumRolledLogFiles());

            // Flush the second region, which removes all the remaining output files
            // since the oldest was completely flushed and the two others only contain
            // flush information
            addEdits(log, hri2, tableName2, 1, sequenceId);
            log.startCacheFlush(hri2.getEncodedNameAsBytes());
            log.completeCacheFlush(hri2.getEncodedNameAsBytes());
            log.rollWriter();
            assertEquals(0, ((FSHLog) log).getNumRolledLogFiles());
        } finally {
            if (log != null)
                log.closeAndDelete();
        }
    }

    @Test(expected = IOException.class)
    public void testFailedToCreateHLogIfParentRenamed() throws IOException {
        FSHLog log = (FSHLog) HLogFactory.createHLog(fs, hbaseDir, "testFailedToCreateHLogIfParentRenamed", conf);
        long filenum = System.currentTimeMillis();
        Path path = log.computeFilename(filenum);
        HLogFactory.createWALWriter(fs, path, conf);
        Path parent = path.getParent();
        path = log.computeFilename(filenum + 1);
        Path newPath = new Path(parent.getParent(), parent.getName() + "-splitting");
        fs.rename(parent, newPath);
        HLogFactory.createWALWriter(fs, path, conf);
        fail("It should fail to create the new WAL");
    }

    @Test
    public void testGetServerNameFromHLogDirectoryName() throws IOException {
        ServerName sn = ServerName.valueOf("hn", 450, 1398);
        String hl = FSUtils.getRootDir(conf) + "/" + HLogUtil.getHLogDirectoryName(sn.toString());

        // Must not throw exception
        Assert.assertNull(HLogUtil.getServerNameFromHLogDirectoryName(conf, null));
        Assert.assertNull(
                HLogUtil.getServerNameFromHLogDirectoryName(conf, FSUtils.getRootDir(conf).toUri().toString()));
        Assert.assertNull(HLogUtil.getServerNameFromHLogDirectoryName(conf, ""));
        Assert.assertNull(HLogUtil.getServerNameFromHLogDirectoryName(conf, "                  "));
        Assert.assertNull(HLogUtil.getServerNameFromHLogDirectoryName(conf, hl));
        Assert.assertNull(HLogUtil.getServerNameFromHLogDirectoryName(conf, hl + "qdf"));
        Assert.assertNull(HLogUtil.getServerNameFromHLogDirectoryName(conf, "sfqf" + hl + "qdf"));

        final String wals = "/WALs/";
        ServerName parsed = HLogUtil.getServerNameFromHLogDirectoryName(conf,
                FSUtils.getRootDir(conf).toUri().toString() + wals + sn
                        + "/localhost%2C32984%2C1343316388997.1343316390417");
        Assert.assertEquals("standard", sn, parsed);

        parsed = HLogUtil.getServerNameFromHLogDirectoryName(conf, hl + "/qdf");
        Assert.assertEquals("subdir", sn, parsed);

        parsed = HLogUtil.getServerNameFromHLogDirectoryName(conf, FSUtils.getRootDir(conf).toUri().toString()
                + wals + sn + "-splitting/localhost%3A57020.1340474893931");
        Assert.assertEquals("split", sn, parsed);
    }

    /**
     * A loaded WAL coprocessor won't break existing HLog test cases.
     */
    @Test
    public void testWALCoprocessorLoaded() throws Exception {
        // test to see whether the coprocessor is loaded or not.
        HLog log = HLogFactory.createHLog(fs, hbaseDir, getName(), conf);
        try {
            WALCoprocessorHost host = log.getCoprocessorHost();
            Coprocessor c = host.findCoprocessor(SampleRegionWALObserver.class.getName());
            assertNotNull(c);
        } finally {
            if (log != null)
                log.closeAndDelete();
        }
    }

    private void addEdits(HLog log, HRegionInfo hri, TableName tableName, int times, AtomicLong sequenceId)
            throws IOException {
        HTableDescriptor htd = new HTableDescriptor();
        htd.addFamily(new HColumnDescriptor("row"));

        final byte[] row = Bytes.toBytes("row");
        for (int i = 0; i < times; i++) {
            long timestamp = System.currentTimeMillis();
            WALEdit cols = new WALEdit();
            cols.add(new KeyValue(row, row, row, timestamp, row));
            log.append(hri, tableName, cols, timestamp, htd, sequenceId);
        }
    }

    /**
     * @throws IOException
     */
    @Test
    public void testReadLegacyLog() throws IOException {
        final int columnCount = 5;
        final int recordCount = 5;
        final TableName tableName = TableName.valueOf("tablename");
        final byte[] row = Bytes.toBytes("row");
        long timestamp = System.currentTimeMillis();
        Path path = new Path(dir, "temphlog");
        SequenceFileLogWriter sflw = null;
        HLog.Reader reader = null;
        try {
            HRegionInfo hri = new HRegionInfo(tableName, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
            HTableDescriptor htd = new HTableDescriptor(tableName);
            fs.mkdirs(dir);
            // Write log in pre-PB format.
            sflw = new SequenceFileLogWriter();
            sflw.init(fs, path, conf, false);
            for (int i = 0; i < recordCount; ++i) {
                HLogKey key = new HLogKey(hri.getEncodedNameAsBytes(), tableName, i, timestamp,
                        HConstants.DEFAULT_CLUSTER_ID);
                WALEdit edit = new WALEdit();
                for (int j = 0; j < columnCount; ++j) {
                    if (i == 0) {
                        htd.addFamily(new HColumnDescriptor("column" + j));
                    }
                    String value = i + "" + j;
                    edit.add(new KeyValue(row, row, row, timestamp, Bytes.toBytes(value)));
                }
                sflw.append(new HLog.Entry(key, edit));
            }
            sflw.sync();
            sflw.close();

            // Now read the log using standard means.
            reader = HLogFactory.createReader(fs, path, conf);
            assertTrue(reader instanceof SequenceFileLogReader);
            for (int i = 0; i < recordCount; ++i) {
                HLog.Entry entry = reader.next();
                assertNotNull(entry);
                assertEquals(columnCount, entry.getEdit().size());
                assertArrayEquals(hri.getEncodedNameAsBytes(), entry.getKey().getEncodedRegionName());
                assertEquals(tableName, entry.getKey().getTablename());
                int idx = 0;
                for (KeyValue val : entry.getEdit().getKeyValues()) {
                    assertTrue(Bytes.equals(row, val.getRow()));
                    String value = i + "" + idx;
                    assertArrayEquals(Bytes.toBytes(value), val.getValue());
                    idx++;
                }
            }
            HLog.Entry entry = reader.next();
            assertNull(entry);
        } finally {
            if (sflw != null) {
                sflw.close();
            }
            if (reader != null) {
                reader.close();
            }
        }
    }

    /**
     * Reads the WAL with and without WALTrailer.
     * @throws IOException
     */
    @Test
    public void testWALTrailer() throws IOException {
        // read With trailer.
        doRead(true);
        // read without trailer
        doRead(false);
    }

    /**
     * Appends entries in the WAL and reads it.
     * @param withTrailer If 'withTrailer' is true, it calls a close on the WALwriter before reading
     *          so that a trailer is appended to the WAL. Otherwise, it starts reading after the sync
     *          call. This means that reader is not aware of the trailer. In this scenario, if the
     *          reader tries to read the trailer in its next() call, it returns false from
     *          ProtoBufLogReader.
     * @throws IOException
     */
    private void doRead(boolean withTrailer) throws IOException {
        final int columnCount = 5;
        final int recordCount = 5;
        final TableName tableName = TableName.valueOf("tablename");
        final byte[] row = Bytes.toBytes("row");
        long timestamp = System.currentTimeMillis();
        Path path = new Path(dir, "temphlog");
        // delete the log if already exists, for test only
        fs.delete(path, true);
        HLog.Writer writer = null;
        HLog.Reader reader = null;
        try {
            HRegionInfo hri = new HRegionInfo(tableName, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
            HTableDescriptor htd = new HTableDescriptor(tableName);
            fs.mkdirs(dir);
            // Write log in pb format.
            writer = HLogFactory.createWALWriter(fs, path, conf);
            for (int i = 0; i < recordCount; ++i) {
                HLogKey key = new HLogKey(hri.getEncodedNameAsBytes(), tableName, i, timestamp,
                        HConstants.DEFAULT_CLUSTER_ID);
                WALEdit edit = new WALEdit();
                for (int j = 0; j < columnCount; ++j) {
                    if (i == 0) {
                        htd.addFamily(new HColumnDescriptor("column" + j));
                    }
                    String value = i + "" + j;
                    edit.add(new KeyValue(row, row, row, timestamp, Bytes.toBytes(value)));
                }
                writer.append(new HLog.Entry(key, edit));
            }
            writer.sync();
            if (withTrailer)
                writer.close();

            // Now read the log using standard means.
            reader = HLogFactory.createReader(fs, path, conf);
            assertTrue(reader instanceof ProtobufLogReader);
            if (withTrailer) {
                assertNotNull(reader.getWALTrailer());
            } else {
                assertNull(reader.getWALTrailer());
            }
            for (int i = 0; i < recordCount; ++i) {
                HLog.Entry entry = reader.next();
                assertNotNull(entry);
                assertEquals(columnCount, entry.getEdit().size());
                assertArrayEquals(hri.getEncodedNameAsBytes(), entry.getKey().getEncodedRegionName());
                assertEquals(tableName, entry.getKey().getTablename());
                int idx = 0;
                for (KeyValue val : entry.getEdit().getKeyValues()) {
                    assertTrue(Bytes.equals(row, val.getRow()));
                    String value = i + "" + idx;
                    assertArrayEquals(Bytes.toBytes(value), val.getValue());
                    idx++;
                }
            }
            HLog.Entry entry = reader.next();
            assertNull(entry);
        } finally {
            if (writer != null) {
                writer.close();
            }
            if (reader != null) {
                reader.close();
            }
        }
    }

    /**
     * tests the log comparator. Ensure that we are not mixing meta logs with non-meta logs (throws
     * exception if we do). Comparison is based on the timestamp present in the wal name.
     * @throws Exception
     */
    @Test
    public void testHLogComparator() throws Exception {
        HLog hlog1 = null;
        HLog hlogMeta = null;
        try {
            hlog1 = HLogFactory.createHLog(fs, FSUtils.getRootDir(conf), dir.toString(), conf);
            LOG.debug("Log obtained is: " + hlog1);
            Comparator<Path> comp = ((FSHLog) hlog1).LOG_NAME_COMPARATOR;
            Path p1 = ((FSHLog) hlog1).computeFilename(11);
            Path p2 = ((FSHLog) hlog1).computeFilename(12);
            // comparing with itself returns 0
            assertTrue(comp.compare(p1, p1) == 0);
            // comparing with different filenum.
            assertTrue(comp.compare(p1, p2) < 0);
            hlogMeta = HLogFactory.createMetaHLog(fs, FSUtils.getRootDir(conf), dir.toString(), conf, null, null);
            Comparator<Path> compMeta = ((FSHLog) hlogMeta).LOG_NAME_COMPARATOR;

            Path p1WithMeta = ((FSHLog) hlogMeta).computeFilename(11);
            Path p2WithMeta = ((FSHLog) hlogMeta).computeFilename(12);
            assertTrue(compMeta.compare(p1WithMeta, p1WithMeta) == 0);
            assertTrue(compMeta.compare(p1WithMeta, p2WithMeta) < 0);
            // mixing meta and non-meta logs gives error
            boolean ex = false;
            try {
                comp.compare(p1WithMeta, p2);
            } catch (Exception e) {
                ex = true;
            }
            assertTrue("Comparator doesn't complain while checking meta log files", ex);
            boolean exMeta = false;
            try {
                compMeta.compare(p1WithMeta, p2);
            } catch (Exception e) {
                exMeta = true;
            }
            assertTrue("Meta comparator doesn't complain while checking log files", exMeta);
        } finally {
            if (hlog1 != null)
                hlog1.close();
            if (hlogMeta != null)
                hlogMeta.close();
        }
    }

    /**
     * Tests wal archiving by adding data, doing flushing/rolling and checking we archive old logs
     * and also don't archive "live logs" (that is, a log with un-flushed entries).
     * <p>
     * This is what it does:
     * It creates two regions, and does a series of inserts along with log rolling.
     * Whenever a WAL is rolled, FSHLog checks previous wals for archiving. A wal is eligible for
     * archiving if for all the regions which have entries in that wal file, have flushed - past
     * their maximum sequence id in that wal file.
     * <p>
     * @throws IOException
     */
    @Test
    public void testWALArchiving() throws IOException {
        LOG.debug("testWALArchiving");
        TableName table1 = TableName.valueOf("t1");
        TableName table2 = TableName.valueOf("t2");
        HLog hlog = HLogFactory.createHLog(fs, FSUtils.getRootDir(conf), dir.toString(), conf);
        try {
            assertEquals(0, ((FSHLog) hlog).getNumRolledLogFiles());
            HRegionInfo hri1 = new HRegionInfo(table1, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
            HRegionInfo hri2 = new HRegionInfo(table2, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
            // ensure that we don't split the regions.
            hri1.setSplit(false);
            hri2.setSplit(false);
            // variables to mock region sequenceIds.
            final AtomicLong sequenceId1 = new AtomicLong(1);
            final AtomicLong sequenceId2 = new AtomicLong(1);
            // start with the testing logic: insert a waledit, and roll writer
            addEdits(hlog, hri1, table1, 1, sequenceId1);
            hlog.rollWriter();
            // assert that the wal is rolled
            assertEquals(1, ((FSHLog) hlog).getNumRolledLogFiles());
            // add edits in the second wal file, and roll writer.
            addEdits(hlog, hri1, table1, 1, sequenceId1);
            hlog.rollWriter();
            // assert that the wal is rolled
            assertEquals(2, ((FSHLog) hlog).getNumRolledLogFiles());
            // add a waledit to table1, and flush the region.
            addEdits(hlog, hri1, table1, 3, sequenceId1);
            flushRegion(hlog, hri1.getEncodedNameAsBytes());
            // roll log; all old logs should be archived.
            hlog.rollWriter();
            assertEquals(0, ((FSHLog) hlog).getNumRolledLogFiles());
            // add an edit to table2, and roll writer
            addEdits(hlog, hri2, table2, 1, sequenceId2);
            hlog.rollWriter();
            assertEquals(1, ((FSHLog) hlog).getNumRolledLogFiles());
            // add edits for table1, and roll writer
            addEdits(hlog, hri1, table1, 2, sequenceId1);
            hlog.rollWriter();
            assertEquals(2, ((FSHLog) hlog).getNumRolledLogFiles());
            // add edits for table2, and flush hri1.
            addEdits(hlog, hri2, table2, 2, sequenceId2);
            flushRegion(hlog, hri1.getEncodedNameAsBytes());
            // the log : region-sequenceId map is
            // log1: region2 (unflushed)
            // log2: region1 (flushed)
            // log3: region2 (unflushed)
            // roll the writer; log2 should be archived.
            hlog.rollWriter();
            assertEquals(2, ((FSHLog) hlog).getNumRolledLogFiles());
            // flush region2, and all logs should be archived.
            addEdits(hlog, hri2, table2, 2, sequenceId2);
            flushRegion(hlog, hri2.getEncodedNameAsBytes());
            hlog.rollWriter();
            assertEquals(0, ((FSHLog) hlog).getNumRolledLogFiles());
        } finally {
            if (hlog != null)
                hlog.close();
        }
    }

    /**
     * On rolling a wal after reaching the threshold, {@link HLog#rollWriter()} returns the list of
     * regions which should be flushed in order to archive the oldest wal file.
     * <p>
     * This method tests this behavior by inserting edits and rolling the wal enough times to reach
     * the max number of logs threshold. It checks whether we get the "right regions" for flush on
     * rolling the wal.
     * @throws Exception
     */
    @Test
    public void testFindMemStoresEligibleForFlush() throws Exception {
        LOG.debug("testFindMemStoresEligibleForFlush");
        Configuration conf1 = HBaseConfiguration.create(conf);
        conf1.setInt("hbase.regionserver.maxlogs", 1);
        HLog hlog = HLogFactory.createHLog(fs, FSUtils.getRootDir(conf1), dir.toString(), conf1);
        TableName t1 = TableName.valueOf("t1");
        TableName t2 = TableName.valueOf("t2");
        HRegionInfo hri1 = new HRegionInfo(t1, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
        HRegionInfo hri2 = new HRegionInfo(t2, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
        // variables to mock region sequenceIds
        final AtomicLong sequenceId1 = new AtomicLong(1);
        final AtomicLong sequenceId2 = new AtomicLong(1);
        // add edits and roll the wal
        try {
            addEdits(hlog, hri1, t1, 2, sequenceId1);
            hlog.rollWriter();
            // add some more edits and roll the wal. This would reach the log number threshold
            addEdits(hlog, hri1, t1, 2, sequenceId1);
            hlog.rollWriter();
            // with above rollWriter call, the max logs limit is reached.
            assertTrue(((FSHLog) hlog).getNumRolledLogFiles() == 2);

            // get the regions to flush; since there is only one region in the oldest wal, it should
            // return only one region.
            byte[][] regionsToFlush = ((FSHLog) hlog).findRegionsToForceFlush();
            assertEquals(1, regionsToFlush.length);
            assertEquals(hri1.getEncodedNameAsBytes(), regionsToFlush[0]);
            // insert edits in second region
            addEdits(hlog, hri2, t2, 2, sequenceId2);
            // get the regions to flush, it should still read region1.
            regionsToFlush = ((FSHLog) hlog).findRegionsToForceFlush();
            assertEquals(regionsToFlush.length, 1);
            assertEquals(hri1.getEncodedNameAsBytes(), regionsToFlush[0]);
            // flush region 1, and roll the wal file. Only last wal which has entries for region1 should
            // remain.
            flushRegion(hlog, hri1.getEncodedNameAsBytes());
            hlog.rollWriter();
            // only one wal should remain now (that is for the second region).
            assertEquals(1, ((FSHLog) hlog).getNumRolledLogFiles());
            // flush the second region
            flushRegion(hlog, hri2.getEncodedNameAsBytes());
            hlog.rollWriter(true);
            // no wal should remain now.
            assertEquals(0, ((FSHLog) hlog).getNumRolledLogFiles());
            // add edits both to region 1 and region 2, and roll.
            addEdits(hlog, hri1, t1, 2, sequenceId1);
            addEdits(hlog, hri2, t2, 2, sequenceId2);
            hlog.rollWriter();
            // add edits and roll the writer, to reach the max logs limit.
            assertEquals(1, ((FSHLog) hlog).getNumRolledLogFiles());
            addEdits(hlog, hri1, t1, 2, sequenceId1);
            hlog.rollWriter();
            // it should return two regions to flush, as the oldest wal file has entries
            // for both regions.
            regionsToFlush = ((FSHLog) hlog).findRegionsToForceFlush();
            assertEquals(2, regionsToFlush.length);
            // flush both regions
            flushRegion(hlog, hri1.getEncodedNameAsBytes());
            flushRegion(hlog, hri2.getEncodedNameAsBytes());
            hlog.rollWriter(true);
            assertEquals(0, ((FSHLog) hlog).getNumRolledLogFiles());
            // Add an edit to region1, and roll the wal.
            addEdits(hlog, hri1, t1, 2, sequenceId1);
            // tests partial flush: roll on a partial flush, and ensure that wal is not archived.
            hlog.startCacheFlush(hri1.getEncodedNameAsBytes());
            hlog.rollWriter();
            hlog.completeCacheFlush(hri1.getEncodedNameAsBytes());
            assertEquals(1, ((FSHLog) hlog).getNumRolledLogFiles());
        } finally {
            if (hlog != null)
                hlog.close();
        }
    }

    /**
     * Simulates HLog append ops for a region and tests
     * {@link FSHLog#areAllRegionsFlushed(Map, Map, Map)} API.
     * It compares the region sequenceIds with oldestFlushing and oldestUnFlushed entries.
     * If a region's entries are larger than min of (oldestFlushing, oldestUnFlushed), then the
     * region should be flushed before archiving this WAL.
    */
    @Test
    public void testAllRegionsFlushed() {
        LOG.debug("testAllRegionsFlushed");
        Map<byte[], Long> oldestFlushingSeqNo = new HashMap<byte[], Long>();
        Map<byte[], Long> oldestUnFlushedSeqNo = new HashMap<byte[], Long>();
        Map<byte[], Long> seqNo = new HashMap<byte[], Long>();
        // create a table
        TableName t1 = TableName.valueOf("t1");
        // create a region
        HRegionInfo hri1 = new HRegionInfo(t1, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
        // variables to mock region sequenceIds
        final AtomicLong sequenceId1 = new AtomicLong(1);
        // test empty map
        assertTrue(FSHLog.areAllRegionsFlushed(seqNo, oldestFlushingSeqNo, oldestUnFlushedSeqNo));
        // add entries in the region
        seqNo.put(hri1.getEncodedNameAsBytes(), sequenceId1.incrementAndGet());
        oldestUnFlushedSeqNo.put(hri1.getEncodedNameAsBytes(), sequenceId1.get());
        // should say region1 is not flushed.
        assertFalse(FSHLog.areAllRegionsFlushed(seqNo, oldestFlushingSeqNo, oldestUnFlushedSeqNo));
        // test with entries in oldestFlushing map.
        oldestUnFlushedSeqNo.clear();
        oldestFlushingSeqNo.put(hri1.getEncodedNameAsBytes(), sequenceId1.get());
        assertFalse(FSHLog.areAllRegionsFlushed(seqNo, oldestFlushingSeqNo, oldestUnFlushedSeqNo));
        // simulate region flush, i.e., clear oldestFlushing and oldestUnflushed maps
        oldestFlushingSeqNo.clear();
        oldestUnFlushedSeqNo.clear();
        assertTrue(FSHLog.areAllRegionsFlushed(seqNo, oldestFlushingSeqNo, oldestUnFlushedSeqNo));
        // insert some large values for region1
        oldestUnFlushedSeqNo.put(hri1.getEncodedNameAsBytes(), 1000l);
        seqNo.put(hri1.getEncodedNameAsBytes(), 1500l);
        assertFalse(FSHLog.areAllRegionsFlushed(seqNo, oldestFlushingSeqNo, oldestUnFlushedSeqNo));

        // tests when oldestUnFlushed/oldestFlushing contains larger value.
        // It means region is flushed.
        oldestFlushingSeqNo.put(hri1.getEncodedNameAsBytes(), 1200l);
        oldestUnFlushedSeqNo.clear();
        seqNo.put(hri1.getEncodedNameAsBytes(), 1199l);
        assertTrue(FSHLog.areAllRegionsFlushed(seqNo, oldestFlushingSeqNo, oldestUnFlushedSeqNo));
    }

    /**
     * helper method to simulate region flush for a WAL.
     * @param hlog
     * @param regionEncodedName
     */
    private void flushRegion(HLog hlog, byte[] regionEncodedName) {
        hlog.startCacheFlush(regionEncodedName);
        hlog.completeCacheFlush(regionEncodedName);
    }

    static class DumbWALActionsListener implements WALActionsListener {
        int increments = 0;

        @Override
        public void visitLogEntryBeforeWrite(HRegionInfo info, HLogKey logKey, WALEdit logEdit) {
            increments++;
        }

        @Override
        public void visitLogEntryBeforeWrite(HTableDescriptor htd, HLogKey logKey, WALEdit logEdit) {
            //To change body of implemented methods use File | Settings | File Templates.
            increments++;
        }

        @Override
        public void preLogRoll(Path oldFile, Path newFile) {
            // TODO Auto-generated method stub
        }

        @Override
        public void postLogRoll(Path oldFile, Path newFile) {
            // TODO Auto-generated method stub
        }

        @Override
        public void preLogArchive(Path oldFile, Path newFile) {
            // TODO Auto-generated method stub
        }

        @Override
        public void postLogArchive(Path oldFile, Path newFile) {
            // TODO Auto-generated method stub
        }

        @Override
        public void logRollRequested() {
            // TODO Auto-generated method stub

        }

        @Override
        public void logCloseRequested() {
            // not interested
        }
    }

}