/** * 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.notifier.server; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.concurrent.ConcurrentLinkedQueue; import junit.framework.Assert; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileUtil; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.fs.permission.PermissionStatus; import org.apache.hadoop.hdfs.notifier.NamespaceNotification; import org.apache.hadoop.hdfs.protocol.Block; import org.apache.hadoop.hdfs.server.common.Util; import org.apache.hadoop.hdfs.server.namenode.EditLogFileOutputStream; import org.apache.hadoop.hdfs.server.namenode.BlocksMap.BlockInfo; import org.apache.hadoop.hdfs.server.namenode.FSEditLogOp; import org.apache.hadoop.hdfs.server.namenode.INodeId; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TestPreTransactionalServerLogReader { static private Logger LOG = LoggerFactory.getLogger(TestPreTransactionalServerLogReader.class); private static final String base_dir = System.getProperty("test.build.data", "build/test/data"); static Configuration conf; @BeforeClass public static void initConf() { Configuration.addDefaultResource("namespace-notifier-server-default.xml"); Configuration.addDefaultResource("hdfs-default.xml"); conf = new Configuration(); conf.addResource("namespace-notifier-server-site.xml"); conf.addResource("hdfs-site.xml"); conf = new Configuration(); } /** * Creates the edits directory. * @return edits dir * @throws IOException */ private File createEditsDir() throws IOException { File editsDir = new File(base_dir, "edits"); File currentDir = new File(editsDir, "current"); FileUtil.fullyDelete(editsDir); if (!editsDir.mkdirs() || !currentDir.mkdirs()) { throw new IOException("Can't create directory: " + editsDir); } return editsDir; } public static EditLogFileOutputStream initEdits(File editsDir) throws IOException { File edits = getFileWithCurrent(editsDir, "edits"); File fstime = getFileWithCurrent(editsDir, "fstime"); if (!edits.createNewFile()) throw new IOException("Failed to create edits file"); EditLogFileOutputStream out = new EditLogFileOutputStream(edits, null); out.create(); if (!fstime.createNewFile()) throw new IOException("Failed to create fstime file"); return out; } static File getFileWithCurrent(File editsDir, String name) { File currentDir = new File(editsDir, "current"); return new File(currentDir, name); } private EditLogFileOutputStream beginRoll(File editsDir, EditLogFileOutputStream editsOutput) throws IOException { File editsNew = getFileWithCurrent(editsDir, "edits.new"); editsOutput.close(); if (!editsNew.createNewFile()) throw new IOException("Failed to create edits.new file"); EditLogFileOutputStream out = new EditLogFileOutputStream(editsNew, null); out.create(); Assert.assertTrue(editsNew.exists()); return out; } private void endRoll(File editsDir) throws IOException { File edits = getFileWithCurrent(editsDir, "edits"); File editsNew = getFileWithCurrent(editsDir, "edits.new"); File fstime = getFileWithCurrent(editsDir, "fstime"); Assert.assertTrue(editsNew.exists()); Assert.assertTrue(fstime.exists()); if (!editsNew.renameTo(edits)) { edits.delete(); if (!editsNew.renameTo(edits)) throw new IOException(); } fstime.delete(); DataOutputStream fstimeOutput = new DataOutputStream(new FileOutputStream(fstime)); fstimeOutput.writeLong(System.currentTimeMillis()); fstimeOutput.flush(); fstimeOutput.close(); } private void writeOperation(EditLogFileOutputStream out, long txId, boolean forceSync) throws IOException { FSEditLogOp.AddOp op = FSEditLogOp.AddOp.getUniqueInstance(); op.setTransactionId(txId); op.set(INodeId.GRANDFATHER_INODE_ID, "/a/b", (short)3, 100L, 100L, 100L, new BlockInfo[0], PermissionStatus.createImmutable("x", "y", FsPermission.getDefault()), "x", "y"); out.write(op); LOG.info("Wrote operation " + txId); if (txId % 10 == 0 || forceSync) { out.setReadyToFlush(); out.flush(); LOG.info("Flushed operation " + txId); } } @Test public void testOneOperation() throws Exception { File editsDir = createEditsDir(); DummyServerCore core = new DummyServerCore(); EditLogFileOutputStream out = initEdits(editsDir); ServerLogReaderPreTransactional logReader = new ServerLogReaderPreTransactional(core, Util.stringAsURI(editsDir.getAbsolutePath())); core.logReader = logReader; Thread coreThread, logReaderThread; coreThread = new Thread(core); logReaderThread = new Thread(logReader); logReaderThread.start(); coreThread.start(); writeOperation(out, 1000, true); Thread.sleep(500); core.shutdown(); logReaderThread.join(); coreThread.join(); Assert.assertEquals(1, core.notifications.size()); Assert.assertEquals(1000, core.notifications.poll().txId); } @Test public void testMultipleOperations() throws Exception { File editsDir = createEditsDir(); DummyServerCore core = new DummyServerCore(); EditLogFileOutputStream out = initEdits(editsDir); ServerLogReaderPreTransactional logReader = new ServerLogReaderPreTransactional(core, Util.stringAsURI(editsDir.getAbsolutePath())); core.logReader = logReader; Thread coreThread, logReaderThread; long txCount = 1000; coreThread = new Thread(core); logReaderThread = new Thread(logReader); logReaderThread.start(); coreThread.start(); for (long txId = 0; txId < txCount; txId ++) { writeOperation(out, txId, false); } // flush out.setReadyToFlush(); out.flush(); Thread.sleep(500); core.shutdown(); logReaderThread.join(); coreThread.join(); Assert.assertEquals(1000, core.notifications.size()); for (long txId = 0; txId < txCount; txId ++) Assert.assertEquals(txId, core.notifications.poll().txId); } @Test public void testTwoOperationsRoll() throws Exception { File editsDir = createEditsDir(); DummyServerCore core = new DummyServerCore(); EditLogFileOutputStream out = initEdits(editsDir); ServerLogReaderPreTransactional logReader = new ServerLogReaderPreTransactional(core, Util.stringAsURI(editsDir.getAbsolutePath())); core.logReader = logReader; Thread coreThread, logReaderThread; coreThread = new Thread(core); logReaderThread = new Thread(logReader); coreThread.start(); Thread.sleep(1000); logReaderThread.start(); writeOperation(out, 1000, true); out = beginRoll(editsDir, out); writeOperation(out, 1001, true); Thread.sleep(500); endRoll(editsDir); Thread.sleep(500); core.shutdown(); logReaderThread.join(); coreThread.join(); Assert.assertEquals(2, core.notifications.size()); Assert.assertEquals(1000, core.notifications.poll().txId); Assert.assertEquals(1001, core.notifications.poll().txId); } private void testMultipleOperationsRoll(long txPerNormalPhase, long txPerRollPhase, long txCount) throws Exception { File editsDir = createEditsDir(); DummyServerCore core = new DummyServerCore(); EditLogFileOutputStream out = initEdits(editsDir); ServerLogReaderPreTransactional logReader = new ServerLogReaderPreTransactional(core, Util.stringAsURI(editsDir.getAbsolutePath())); core.logReader = logReader; Thread coreThread, logReaderThread; boolean rollPhase = false; coreThread = new Thread(core); logReaderThread = new Thread(logReader); logReaderThread.start(); coreThread.start(); Thread.sleep(1000); long count = 0; for (long txId = 0; txId < txCount; txId ++) { if (rollPhase) { count --; if (count == 0) { rollPhase = false; count = txPerNormalPhase; endRoll(editsDir); } } else { count --; if (count == 0) { rollPhase = true; count = txPerRollPhase; beginRoll(editsDir, out); } } writeOperation(out, txId, false); if (txId % 10 == 0) { Thread.sleep(1); } } // flush out.setReadyToFlush(); out.flush(); Thread.sleep(1000); core.shutdown(); logReaderThread.join(); coreThread.join(); Assert.assertEquals(txCount, core.notifications.size()); for (long txId = 0; txId < txCount; txId ++) Assert.assertEquals(txId, core.notifications.poll().txId); } @Test public void testMultipleOperationsBigRoll() throws Exception { testMultipleOperationsRoll(3, 1000, 10000); } @Test public void testMultipleOperationsNormalRoll() throws Exception { testMultipleOperationsRoll(1000, 1000, 10000); } @Test public void testMultipleOperationsSmallRoll() throws Exception { testMultipleOperationsRoll(1000, 3, 10000); } public static class DummyServerCore extends EmptyServerCore { ConcurrentLinkedQueue<NamespaceNotification> notifications = new ConcurrentLinkedQueue<NamespaceNotification>(); IServerLogReader logReader; Configuration config = conf; @Override public void handleNotification(NamespaceNotification n) { notifications.add(n); } @Override public void run() { try { while (!shutdownPending()) { NamespaceNotification n = logReader.getNamespaceNotification(); if (n != null) handleNotification(n); } } catch (IOException e) { LOG.error("DummyServerCore failed reading notifications", e); } finally { shutdown(); } } public void setConfiguration(Configuration conf) { this.config = conf; } @Override public Configuration getConfiguration() { return config; } } }