Package org.apache.hadoop.hdfs.qjournal.client

Source Code of org.apache.hadoop.hdfs.qjournal.client.TestQJMWriteRead

/**
* 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());
  }
}
TOP

Related Classes of org.apache.hadoop.hdfs.qjournal.client.TestQJMWriteRead

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.