/* * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0 * (the "License"). You may not use this work except in compliance with the License, which is * available at www.apache.org/licenses/LICENSE-2.0 * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied, as more fully set forth in the License. * * See the NOTICE file distributed with this work for information regarding copyright ownership. */ package alluxio.master.journal; import static org.mockito.Matchers.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.times; import alluxio.Configuration; import alluxio.ConfigurationTestUtils; import alluxio.PropertyKey; import alluxio.proto.journal.Journal.JournalEntry; import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import java.io.IOException; /** * Unit tests for {@link AsyncJournalWriter}. */ public class AsyncJournalWriterTest { private JournalWriter mMockJournalWriter; private AsyncJournalWriter mAsyncJournalWriter; @After public void after() throws Exception { ConfigurationTestUtils.resetConfiguration(); } private void setupAsyncJournalWriter(boolean batchingEnabled) throws Exception { if (batchingEnabled) { Configuration.set(PropertyKey.MASTER_JOURNAL_FLUSH_BATCH_TIME_MS, 500); } else { Configuration.set(PropertyKey.MASTER_JOURNAL_FLUSH_BATCH_TIME_MS, 0); } mMockJournalWriter = PowerMockito.mock(JournalWriter.class); doNothing().when(mMockJournalWriter).write(any(JournalEntry.class)); doNothing().when(mMockJournalWriter).flush(); mAsyncJournalWriter = new AsyncJournalWriter(mMockJournalWriter); } /** * Simulation for normal write and flush. * * @param batchingEnabled whether to use a batch flush or not */ public void writesAndFlushesInternal(boolean batchingEnabled) throws Exception { setupAsyncJournalWriter(batchingEnabled); int entries = 5; for (int i = 0; i < entries; i++) { long flushCounter = mAsyncJournalWriter.appendEntry(JournalEntry.getDefaultInstance()); // Assuming the flush counter starts from 0. Assert.assertEquals(i + 1, flushCounter); } for (int i = 1; i <= entries; i++) { mAsyncJournalWriter.flush(i); } if (batchingEnabled) { Mockito.verify(mMockJournalWriter, atLeastOnce()).flush(); } else { Mockito.verify(mMockJournalWriter, times(entries)).flush(); } } @Test(timeout = 10000) public void writesAndFlushes() throws Exception { writesAndFlushesInternal(false); } @Test(timeout = 10000) public void writesAndFlushesWithBatching() throws Exception { writesAndFlushesInternal(true); } /** * Simulates that exception occurs when writing {@link JournalEntry}. * * @param batchingEnabled whether to use a batch flush or not */ public void failedWriteInternal(boolean batchingEnabled) throws Exception { setupAsyncJournalWriter(batchingEnabled); int entries = 5; for (int i = 0; i < entries; i++) { long flushCounter = mAsyncJournalWriter.appendEntry(JournalEntry.getDefaultInstance()); // Assuming the flush counter starts from 0 Assert.assertEquals(i + 1, flushCounter); } // Start failing journal writes. doThrow(new IOException("entry write failed")).when(mMockJournalWriter) .write(any(JournalEntry.class)); // Flushes should fail. for (int i = 1; i <= entries; i++) { try { mAsyncJournalWriter.flush(1); Assert.fail("journal flush should not succeed if journal write fails."); } catch (IOException e) { // This is expected. } } // Allow journal writes to succeed. doNothing().when(mMockJournalWriter).write(any(JournalEntry.class)); // Flushes should succeed. for (int i = 1; i <= entries; i++) { mAsyncJournalWriter.flush(i); } if (batchingEnabled) { Mockito.verify(mMockJournalWriter, atLeastOnce()).flush(); } else { Mockito.verify(mMockJournalWriter, times(entries)).flush(); } } @Test(timeout = 10000) public void failedWrite() throws Exception { failedWriteInternal(false); } @Test(timeout = 10000) public void failedWriteWithBatching() throws Exception { failedWriteInternal(true); } /** * Simulates that exception occurs when flush. * * @param batchingEnabled whether to use a batch flush or not */ public void failedFlushInternal(boolean batchingEnabled) throws Exception { setupAsyncJournalWriter(batchingEnabled); int entries = 5; for (int i = 0; i < entries; i++) { long flushCounter = mAsyncJournalWriter.appendEntry(JournalEntry.getDefaultInstance()); // Assuming the flush counter starts from 0 Assert.assertEquals(i + 1, flushCounter); } // Start failing journal flushes. doThrow(new IOException("flush failed")).when(mMockJournalWriter).flush(); // Flushes should fail. for (int i = 1; i <= entries; i++) { try { mAsyncJournalWriter.flush(1); Assert.fail("journal flush should not succeed if journal flush fails."); } catch (IOException e) { // This is expected. } } // Allow journal flushes to succeed. doNothing().when(mMockJournalWriter).flush(); // Flushes should succeed. for (int i = 1; i <= entries; i++) { mAsyncJournalWriter.flush(i); } if (batchingEnabled) { Mockito.verify(mMockJournalWriter, atLeastOnce()).flush(); } else { // The first half of the calls were the failed flush calls. Mockito.verify(mMockJournalWriter, times(2 * entries)).flush(); } } @Test(timeout = 10000) public void failedFlush() throws Exception { failedFlushInternal(false); } @Test(timeout = 10000) public void failedFlushWithBatching() throws Exception { failedFlushInternal(true); } }