/** * 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 org.junit.Assert.*; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.util.Random; 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.client.AsyncLogger; import org.apache.hadoop.hdfs.qjournal.client.QuorumJournalManager; import org.apache.hadoop.hdfs.server.protocol.NamespaceInfo; import org.junit.Test; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; public class TestEpochsAreUnique { private static final Log LOG = LogFactory.getLog(TestEpochsAreUnique.class); private static final String JID = "testEpochsAreUnique-jid"; private static final NamespaceInfo FAKE_NSINFO = new NamespaceInfo( 12345, "mycluster", "my-bp", 0L); private Random r = new Random(); @Test public void testSingleThreaded() throws IOException { Configuration conf = new Configuration(); MiniJournalCluster cluster = new MiniJournalCluster.Builder(conf).build(); URI uri = cluster.getQuorumJournalURI(JID); QuorumJournalManager qjm = new QuorumJournalManager( conf, uri, FAKE_NSINFO); try { qjm.format(FAKE_NSINFO); } finally { qjm.close(); } try { // With no failures or contention, epochs should increase one-by-one for (int i = 0; i < 5; i++) { qjm = new QuorumJournalManager( conf, uri, FAKE_NSINFO); try { qjm.createNewUniqueEpoch(); assertEquals(i + 1, qjm.getLoggerSetForTests().getEpoch()); } finally { qjm.close(); } } long prevEpoch = 5; // With some failures injected, it should still always increase, perhaps // skipping some for (int i = 0; i < 20; i++) { long newEpoch = -1; while (true) { qjm = new QuorumJournalManager( conf, uri, FAKE_NSINFO, new FaultyLoggerFactory()); try { qjm.createNewUniqueEpoch(); newEpoch = qjm.getLoggerSetForTests().getEpoch(); break; } catch (IOException ioe) { // It's OK to fail to create an epoch, since we randomly inject // faults. It's possible we'll inject faults in too many of the // underlying nodes, and a failure is expected in that case } finally { qjm.close(); } } LOG.info("Created epoch " + newEpoch); assertTrue("New epoch " + newEpoch + " should be greater than previous " + prevEpoch, newEpoch > prevEpoch); prevEpoch = newEpoch; } } finally { cluster.shutdown(); } } private class FaultyLoggerFactory implements AsyncLogger.Factory { @Override public AsyncLogger createLogger(Configuration conf, NamespaceInfo nsInfo, String journalId, InetSocketAddress addr) { AsyncLogger ch = IPCLoggerChannel.FACTORY.createLogger( conf, nsInfo, journalId, addr); AsyncLogger spy = Mockito.spy(ch); Mockito.doAnswer(new SometimesFaulty<Long>(0.10f)) .when(spy).getJournalState(); Mockito.doAnswer(new SometimesFaulty<Void>(0.40f)) .when(spy).newEpoch(Mockito.anyLong()); return spy; } } private class SometimesFaulty<T> implements Answer<ListenableFuture<T>> { private float faultProbability; public SometimesFaulty(float faultProbability) { this.faultProbability = faultProbability; } @SuppressWarnings("unchecked") @Override public ListenableFuture<T> answer(InvocationOnMock invocation) throws Throwable { if (r.nextFloat() < faultProbability) { return Futures.immediateFailedFuture( new IOException("Injected fault")); } return (ListenableFuture<T>)invocation.callRealMethod(); } } }