<reponame>gagneet/hadoop-20
/**
 * 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;

import java.io.FileNotFoundException;
import java.io.IOException;

import org.junit.Test;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.AfterClass;
import static org.junit.Assert.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.ContentSummary;
import org.apache.hadoop.fs.RemoteIterator;
import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
import org.apache.hadoop.hdfs.util.InjectionEvent;
import org.apache.hadoop.hdfs.util.InjectionHandler;

import static org.apache.hadoop.hdfs.server.namenode.TestListCorruptFileBlocks.
  countPaths;

public class TestAvatarAPI {
  final static Log LOG = LogFactory.getLog(TestAvatarAPI.class);

  private static final String FILE_PATH = "/dir1/dir2/myfile";
  private static final String DIR_PATH = "/dir1/dir2";
  private static final long FILE_LEN = 512L * 1024L;
  private static final long MAX_WAIT = 30000L;

  private Configuration conf;
  private MiniAvatarCluster cluster;
  private DistributedAvatarFileSystem dafs;
  private Path path;
  private Path dirPath;

  @BeforeClass
  public static void setUpClass() throws Exception {
    MiniAvatarCluster.createAndStartZooKeeper();
  }

  /**
   * Set up MiniAvatarCluster.
   */
  public void setUp(boolean federation, String name) throws Exception {
    LOG.info("------------------- test: " + name + " START ----------------");
    conf = new Configuration();
    // populate repl queues on standby (in safe mode)
    conf.setFloat("dfs.namenode.replqueue.threshold-pct", 0f);
    conf.setLong("fs.avatar.standbyfs.initinterval", 1000);
    conf.setLong("fs.avatar.standbyfs.checkinterval", 1000);
    if (!federation) {
      cluster = new MiniAvatarCluster(conf, 3, true, null, null);
      dafs = cluster.getFileSystem();
    } else {
      cluster = new MiniAvatarCluster(conf, 3, true, null, null, 1, true);
      dafs = cluster.getFileSystem(0);
    }

    
    path = new Path(FILE_PATH);
    dirPath = new Path(DIR_PATH);
    DFSTestUtil.createFile(dafs, path, FILE_LEN, (short) 1, 0L);
    DFSTestUtil.waitReplication(dafs, path, (short) 1);
    try {
      Thread.sleep(4000);
    } catch (InterruptedException ignore) {
    }
  }

  private void checkPrimary() throws Exception {
    FileStatus fs = dafs.getFileStatus(path, false);
    FileStatus[] dir = dafs.listStatus(dirPath, false);
    RemoteIterator<Path> cfb =
      dafs.listCorruptFileBlocks(dirPath, false);
    assertTrue("DAFS file status has the wrong length",
               fs != null && fs.getLen() == FILE_LEN);
    assertTrue("DAFS directory listing has the wrong length",
               dir != null && dir.length == 1);
    assertTrue("DAFS expected 0 corrupt file blocks",
               countPaths(cfb) == 0);

    ContentSummary cs = dafs.getContentSummary(path, false);
    DatanodeInfo[] di = dafs.getDataNodeStats(false);
    assertTrue("DAFS datanode info should contain 3 data nodes",
               di.length == 3);
  }

  private void checkStandby() throws Exception {
    FileStatus fs = null;
    FileStatus[] dir = null;
    long startTime = System.currentTimeMillis();
    while ((fs == null || dir == null || dir.length == 0 ||
            dafs.getDataNodeStats(true).length != 3) &&
           (System.currentTimeMillis() < startTime + MAX_WAIT)) {
      try {
        fs = dafs.getFileStatus(path, true);
        dir = dafs.listStatus(dirPath, true);
      } catch (FileNotFoundException fe) {
        LOG.info("DAFS File not found on standby avatar, retrying.");
      }

      if (fs == null) {
        LOG.info("DAFS file " + path.toString() + " not found");
      }
      if (dir == null) {
        LOG.info("DAFS dir " + dirPath.toString() + " not found");
      } else {
        if (dir.length == 0) {
          LOG.info("DAFS dir " + dirPath.toString() +
                   " is empty");
        }
      }

      try {
        Thread.sleep(100);
      } catch (InterruptedException ignore) {
      }
    }
    assertTrue("DAFS file status has the wrong length",
               fs != null && fs.getLen() == FILE_LEN);
    assertTrue("DAFS directory listing has the wrong length",
               dir != null && dir.length == 1);

    ContentSummary cs = dafs.getContentSummary(path, true);
    DatanodeInfo[] di = dafs.getDataNodeStats(true);
    assertEquals("DAFS datanode info should contain 3 data nodes",
               3, di.length);
  }
  
  @Test
  public void testPrimaryShutdownFailure() throws Exception {
    setUp(false, "testPrimaryShutdownFailure");
    InjectionHandler.set(new TestAvatarAPIHandler());
    LOG.info("DAFS testPrimary");
    try {
      cluster.killPrimary();
      fail("Shutting down the node should fail");
    } catch (IOException e) {
      assertTrue(e.toString().contains("number of required edit streams"));
    }
    InjectionHandler.clear();
  }
  
  /**
   * Read from primary avatar.
   */
  @Test
  public void testPrimary() throws Exception {
    setUp(false, "testPrimary");
    LOG.info("DAFS testPrimary");
    cluster.killStandby();
    checkPrimary();
  }
  
  /**
   * Read from standby avatar.
   */
  @Test
  public void testStandby() throws Exception {
    setUp(false, "testStandby");
    LOG.info("DAFS testStandby");
    long lastTxId = getLastTxId();
    cluster.killPrimary(false);
    // make sure to consume all transactions.
    cluster.getStandbyAvatar(0).avatar.quiesceStandby(lastTxId);
    checkStandby();
  }
  
  /**
   * Test if we can still read standby after failover.
   */
  @Test
  public void testFailOver() throws Exception {
    setUp(false, "testFailOver");
    LOG.info("DAFS testFailOver");
    long lastTxId = getLastTxId();
    cluster.killPrimary();
    cluster.failOver();
    cluster.restartStandby();
    cluster.waitAvatarNodesActive();
    cluster.waitDataNodesActive();
    // make sure to consume all transactions.
    cluster.getStandbyAvatar(0).avatar.quiesceStandby(lastTxId);
    
    try {
      Thread.sleep(2000);
    } catch (InterruptedException ignore) {
    }

    checkStandby();
    checkPrimary();
  }
  
  @Test
  public void testStandbyWithFederation() throws Exception {
    setUp(true, "testStandbyWithFederation");
    LOG.info("DAFS testStandby");
    long lastTxId = getLastTxId();
    cluster.killPrimary(0, false);
    // make sure to consume all transactions
    cluster.getStandbyAvatar(0).avatar.quiesceStandby(lastTxId);
    checkStandby();
  }
  
  @Test
  public void testPrimaryWithFederation() throws Exception {
    setUp(true, "testPrimaryWithFederation");
    LOG.info("DAFS testPrimary");
    cluster.killStandby(0);
    checkPrimary();
  }
  
  @Test
  public void testFailOverWithFederation() throws Exception {
    setUp(true, "testFailoverWithFederation");
    LOG.info("DAFS testFailOver");
    long lastTxId = getLastTxId();
    cluster.killPrimary(0);
    cluster.failOver(0);
    cluster.restartStandby(0);
    cluster.waitAvatarNodesActive(0);
    cluster.waitDataNodesActive(0);
    // make sure to consume all transactions.
    cluster.getStandbyAvatar(0).avatar.quiesceStandby(lastTxId);

    try {
      Thread.sleep(2000);
    } catch (InterruptedException ignore) {
    }

    checkStandby();
    checkPrimary();
  }
  
  private long getLastTxId(){
    return cluster.getPrimaryAvatar(0).avatar.getFSImage().getEditLog()
        .getCurrentTxId() - 1;
  }

  @After
  public void shutDown() throws Exception {
    dafs.close();
    cluster.shutDown();
  }

  @AfterClass
  public static void shutDownClass() throws Exception {
    MiniAvatarCluster.shutDownZooKeeper();
  }
  
  private static class TestAvatarAPIHandler extends InjectionHandler {
    @Override
    public boolean _falseCondition(InjectionEvent event, Object... args) {
      if (event == InjectionEvent.AVATARNODE_CHECKEDITSTREAMS) {
        return true;
      }
      return false;
    }
  }
}
