org.apache.hadoop.hdfs.qjournal.client.TestQJMWriteRead.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hdfs.qjournal.client.TestQJMWriteRead.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.hdfs.qjournal.client;

import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.*;
import static org.apache.hadoop.hdfs.qjournal.QJMTestUtil.writeRandomSegment;

import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hdfs.qjournal.MiniJournalCluster;
import org.apache.hadoop.hdfs.qjournal.QJMTestUtil;
import org.apache.hadoop.hdfs.qjournal.protocol.JournalConfigKeys;
import org.apache.hadoop.hdfs.server.common.HdfsConstants.Transition;
import org.apache.hadoop.hdfs.server.namenode.EditLogInputStream;
import org.apache.hadoop.hdfs.server.namenode.EditLogOutputStream;
import org.apache.hadoop.hdfs.server.namenode.FSEditLogOp;
import org.apache.hadoop.hdfs.server.namenode.FSEditLogTestUtil;
import org.apache.hadoop.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.google.common.collect.Lists;

/**
 * Functional tests for QuorumJournalManager. For true unit tests, see
 * {@link TestQuorumJournalManagerUnit}.
 */
public class TestQJMWriteRead {
    private static final Log LOG = LogFactory.getLog(TestQuorumJournalManager.class);

    private MiniJournalCluster cluster;
    private Configuration conf;
    private QuorumJournalManager qjm;

    @Before
    public void setup() throws Exception {
        conf = new Configuration();
        // Don't retry connections - it just slows down the tests.
        conf.setInt("ipc.client.connect.max.retries", 0);
        conf.setLong(JournalConfigKeys.DFS_QJOURNAL_CONNECT_TIMEOUT_KEY, 100);

        cluster = new MiniJournalCluster.Builder(conf).build();

        qjm = MiniJournalCluster.createSpyingQJM(conf, cluster);
        qjm.transitionJournal(QJMTestUtil.FAKE_NSINFO, Transition.FORMAT, null);
        qjm.recoverUnfinalizedSegments();
        assertEquals(1, qjm.getLoggerSetForTests().getEpoch());
    }

    @After
    public void shutdown() throws IOException {
        if (cluster != null) {
            cluster.shutdown();
        }
    }

    @Test
    public void testReaderWhileAnotherWritesFinalized() throws Exception {
        QuorumJournalManager readerQjm = MiniJournalCluster.createSpyingQJM(conf, cluster);
        List<EditLogInputStream> streams = Lists.newArrayList();
        readerQjm.selectInputStreams(streams, 0, false, true);
        assertEquals(0, streams.size());
        int numTxns = 10;

        List<FSEditLogOp> txns = new ArrayList<FSEditLogOp>();
        writeRandomSegment(cluster, qjm, 0, numTxns, true, txns);

        readerQjm.selectInputStreams(streams, 0, false, true);
        try {
            assertEquals(1, streams.size());
            // Validate the actual stream contents.
            EditLogInputStream stream = streams.get(0);
            assertEquals(0, stream.getFirstTxId());
            assertEquals(numTxns - 1, stream.getLastTxId());

            verifyEdits(streams, 0, numTxns - 1, txns, false);
            assertNull(stream.readOp());
        } finally {
            IOUtils.cleanup(LOG, streams.toArray(new Closeable[0]));
            streams.clear();
        }
    }

    @Test
    public void testReaderWhileAnotherWritesInProgress() throws Exception {
        QuorumJournalManager readerQjm = MiniJournalCluster.createSpyingQJM(conf, cluster);
        List<EditLogInputStream> streams = Lists.newArrayList();
        readerQjm.selectInputStreams(streams, 0, false, true);
        assertEquals(0, streams.size());
        int numTxns = 10;

        List<FSEditLogOp> txns = new ArrayList<FSEditLogOp>();
        writeRandomSegment(cluster, qjm, 0, numTxns, false, txns);

        // allow in-progress stream
        readerQjm.selectInputStreams(streams, 0, true, false);
        try {
            assertEquals(1, streams.size());
            // Validate the actual stream contents.
            EditLogInputStream stream = streams.get(0);
            assertEquals(0, stream.getFirstTxId());

            // for inprogress, we can read only up to second last one
            assertEquals(numTxns - 2, stream.getLastTxId());

            verifyEdits(streams, 0, numTxns - 1, txns, true);
            assertNull(stream.readOp());
        } finally {
            IOUtils.cleanup(LOG, streams.toArray(new Closeable[0]));
            streams.clear();
        }
    }

    // /////////////

    @Test
    public void testTailing() throws Exception {
        // Unlike the other unit test, numEdits here is constant as this is
        // a longer running test
        final int numEdits = 1000;
        final AtomicBoolean finishedProducing = new AtomicBoolean(false);
        final EditLogOutputStream out = qjm.startLogSegment(0);

        Callable<Void> producerThread = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                try {
                    for (int i = 0; i < numEdits; i++) {
                        FSEditLogOp op = FSEditLogTestUtil.getNoOpInstance();
                        // Set an increasing transaction id to verify correctness
                        op.setTransactionId(i);
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Writing " + op);
                        }

                        FSEditLogTestUtil.writeToStreams(op, out);

                        if (i % 50 == 0) {
                            Thread.sleep(100);
                            FSEditLogTestUtil.flushStreams(out);
                        }
                    }

                    FSEditLogTestUtil.flushStreams(out);
                    FSEditLogTestUtil.closeStreams(out);
                } finally {
                    // Let the producer know that we've reached the end.
                    finishedProducing.set(true);
                }
                return null;
            }
        };
        Callable<Void> consumerThread = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                List<EditLogInputStream> streams = Lists.newArrayList();
                qjm.selectInputStreams(streams, 0, true, false);
                EditLogInputStream in = streams.get(0);

                long numOps = 0;
                long maxTxId = -1;
                FSEditLogOp op;
                long lastPos = in.getPosition();
                do {
                    op = in.readOp();
                    if (op == null) { // If we've reached the end prematurely...
                        Thread.sleep(200);
                        LOG.info("Refreshing to " + lastPos);

                        in.refresh(lastPos, maxTxId); // Then refresh to last known good position
                    } else {
                        long txId = op.getTransactionId();
                        if (txId > maxTxId) {
                            // Standby ingest contains similar logic: transactions
                            // with ids lower than what is already read are ignored.
                            numOps++;
                            maxTxId = txId;
                        }

                        // Remember the last known safe position that we can refresh to
                        lastPos = in.getPosition();
                    }
                } while (op != null || !finishedProducing.get());
                Thread.sleep(1000);

                // finalize the segment, so we can read to the end
                qjm.finalizeLogSegment(0, numEdits - 1);

                // Once producer is shutdown, scan again from last known good position
                // until the end of the ledger. This mirrors the Ingest logic (last
                // read when being quiesced).
                in.refresh(lastPos, maxTxId);
                do {
                    op = in.readOp();
                    if (op != null) {
                        long txId = op.getTransactionId();
                        if (txId > maxTxId) {
                            numOps++;
                            maxTxId = txId;
                        }
                    }
                } while (op != null);

                assertEquals("Must have read " + numEdits + " edits", numEdits, numOps);
                assertEquals("Must end at txid = " + (numEdits - 1), numEdits - 1, maxTxId);
                return null;
            }
        };
        // Allow producer and consumer to run concurrently
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Future<Void> producerFuture = executor.submit(producerThread);
        Future<Void> consumerFuture = executor.submit(consumerThread);

        // Calling a .get() on the future will rethrow any exceptions thrown in
        // the future.
        producerFuture.get();
        consumerFuture.get();
    }

    // /////////////

    public static void verifyEdits(List<EditLogInputStream> streams, int firstTxnId, int lastTxnId,
            List<FSEditLogOp> writtenTxns, boolean inProgress) throws IOException {

        Iterator<EditLogInputStream> iter = streams.iterator();
        assertTrue(iter.hasNext());
        EditLogInputStream stream = iter.next();

        long position = stream.getPosition();
        if (inProgress) {
            // we are one transaction behind
            lastTxnId--;
        }

        for (int expected = firstTxnId; expected <= lastTxnId; expected++) {
            if (inProgress) { // otherwise we cannot call refresh
                stream.refresh(position, expected - 1);
            }
            FSEditLogOp op = stream.readOp();
            position = stream.getPosition();

            while (op == null) {
                assertTrue(
                        "Expected to find txid " + expected + ", " + "but no more streams available to read from",
                        iter.hasNext());
                stream = iter.next();
                op = stream.readOp();
            }

            assertEquals(expected, op.getTransactionId());
            assertEquals(expected, writtenTxns.get(expected).getTransactionId());
            assertEquals(op.opCode, writtenTxns.get(expected).opCode);
        }

        assertNull(stream.readOp());
        assertFalse("Expected no more txns after " + lastTxnId + " but more streams are available", iter.hasNext());
    }
}