/**
 * 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.server.namenode;

import java.io.File;
import java.io.IOException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.HdfsConfiguration;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.hdfs.server.common.HdfsConstants.StartupOption;
import org.apache.hadoop.hdfs.server.namenode.FSImage.CheckpointStates;
import org.apache.hadoop.hdfs.DFSConfigKeys;

import junit.framework.TestCase;

public class TestBackupNode extends TestCase {
  public static final Log LOG = LogFactory.getLog(TestBackupNode.class);

  static final String BASE_DIR = MiniDFSCluster.getBaseDirectory();

  protected void setUp() throws Exception {
    super.setUp();
    File baseDir = new File(BASE_DIR);
    if(baseDir.exists())
      if(!(FileUtil.fullyDelete(baseDir)))
        throw new IOException("Cannot remove directory: " + baseDir);
    File dirC = new File(getBackupNodeDir(StartupOption.CHECKPOINT, 1));
    dirC.mkdirs();
    File dirB = new File(getBackupNodeDir(StartupOption.BACKUP, 1));
    dirB.mkdirs();
    dirB = new File(getBackupNodeDir(StartupOption.BACKUP, 2));
    dirB.mkdirs();
  }

  protected void tearDown() throws Exception {
    super.tearDown();
    File baseDir = new File(BASE_DIR);
    if(!(FileUtil.fullyDelete(baseDir)))
      throw new IOException("Cannot remove directory: " + baseDir);
  }

  static void writeFile(FileSystem fileSys, Path name, int repl)
  throws IOException {
    TestCheckpoint.writeFile(fileSys, name, repl);
  }


  static void checkFile(FileSystem fileSys, Path name, int repl)
  throws IOException {
    TestCheckpoint.checkFile(fileSys, name, repl);
  }

  void cleanupFile(FileSystem fileSys, Path name)
  throws IOException {
    TestCheckpoint.cleanupFile(fileSys, name);
  }

  static String getBackupNodeDir(StartupOption t, int i) {
    return BASE_DIR + "name" + t.getName() + i + "/";
  }

  BackupNode startBackupNode(Configuration conf,
                             StartupOption t, int i) throws IOException {
    Configuration c = new HdfsConfiguration(conf);
    String dirs = getBackupNodeDir(t, i);
    c.set(DFSConfigKeys.DFS_NAMENODE_NAME_DIR_KEY, dirs);
    c.set(DFSConfigKeys.DFS_NAMENODE_EDITS_DIR_KEY,
        "${" + DFSConfigKeys.DFS_NAMENODE_NAME_DIR_KEY + "}");
    return (BackupNode)NameNode.createNameNode(new String[]{t.getName()}, c);
  }

  void waitCheckpointDone(BackupNode backup) {
    do {
      try {
        LOG.info("Waiting checkpoint to complete...");
        Thread.sleep(1000);
      } catch (Exception e) {}
    } while(backup.getCheckpointState() != CheckpointStates.START);
  }

  public void testCheckpoint() throws IOException {
    testCheckpoint(StartupOption.CHECKPOINT);
    testCheckpoint(StartupOption.BACKUP);
  }

  void testCheckpoint(StartupOption op) throws IOException {
    Path file1 = new Path("checkpoint.dat");
    Path file2 = new Path("checkpoint2.dat");

    Configuration conf = new HdfsConfiguration();
    short replication = (short)conf.getInt(DFSConfigKeys.DFS_REPLICATION_KEY, 3);
    conf.set(DFSConfigKeys.DFS_BLOCKREPORT_INITIAL_DELAY_KEY, "0");
    conf.setInt(DFSConfigKeys.DFS_DATANODE_SCAN_PERIOD_HOURS_KEY, -1); // disable block scanner
    int numDatanodes = Math.max(3, replication);
    MiniDFSCluster cluster = null;
    FileSystem fileSys = null;
    BackupNode backup = null;

    try {
      cluster = new MiniDFSCluster.Builder(conf)
                                  .numDataNodes(numDatanodes).build();
      fileSys = cluster.getFileSystem();
      //
      // verify that 'format' really blew away all pre-existing files
      //
      assertTrue(!fileSys.exists(file1));
      assertTrue(!fileSys.exists(file2));

      //
      // Create file1
      //
      writeFile(fileSys, file1, replication);
      checkFile(fileSys, file1, replication);

      //
      // Take a checkpoint
      //
      backup = startBackupNode(conf, op, 1);
      waitCheckpointDone(backup);
    } catch(IOException e) {
      LOG.error("Error in TestBackupNode:", e);
      assertTrue(e.getLocalizedMessage(), false);
    } finally {
      if(backup != null) backup.stop();
      if(fileSys != null) fileSys.close();
      if(cluster != null) cluster.shutdown();
    }
    File imageFileNN = new File(BASE_DIR, "name1/current/fsimage");
    File imageFileBN = new File(getBackupNodeDir(op, 1), "/current/fsimage");
    LOG.info("NameNode fsimage length = " + imageFileNN.length());
    LOG.info("Backup Node fsimage length = " + imageFileBN.length());
    assertTrue(imageFileNN.length() == imageFileBN.length());

    try {
      //
      // Restart cluster and verify that file1 still exist.
      //
      cluster = new MiniDFSCluster.Builder(conf).numDataNodes(numDatanodes)
                                                .format(false).build();
      fileSys = cluster.getFileSystem();
      // check that file1 still exists
      checkFile(fileSys, file1, replication);
      cleanupFile(fileSys, file1);

      // create new file file2
      writeFile(fileSys, file2, replication);
      checkFile(fileSys, file2, replication);

      //
      // Take a checkpoint
      //
      backup = startBackupNode(conf, op, 1);
      waitCheckpointDone(backup);
    } catch(IOException e) {
      LOG.error("Error in TestBackupNode:", e);
      assertTrue(e.getLocalizedMessage(), false);
    } finally {
      if(backup != null) backup.stop();
      if(fileSys != null) fileSys.close();
      if(cluster != null) cluster.shutdown();
    }
    LOG.info("NameNode fsimage length = " + imageFileNN.length());
    LOG.info("Backup Node fsimage length = " + imageFileBN.length());
    assertTrue(imageFileNN.length() == imageFileBN.length());

    try {
      //
      // Restart cluster and verify that file2 exists and
      // file1 does not exist.
      //
      cluster = new MiniDFSCluster.Builder(conf).numDataNodes(numDatanodes).format(false).build();
      fileSys = cluster.getFileSystem();

      assertTrue(!fileSys.exists(file1));

      // verify that file2 exists
      checkFile(fileSys, file2, replication);
    } catch(IOException e) {
      LOG.error("Error in TestBackupNode:", e);
      assertTrue(e.getLocalizedMessage(), false);
    } finally {
      fileSys.close();
      cluster.shutdown();
    }
  }

  /**
   * Test that only one backup node can register.
   * @throws IOException
   */
  public void testBackupRegistration() throws IOException {
    Configuration conf1 = new HdfsConfiguration();
    Configuration conf2 = null;
    MiniDFSCluster cluster = null;
    BackupNode backup1 = null;
    BackupNode backup2 = null;
    try {
      // start name-node and backup node 1
      cluster = new MiniDFSCluster.Builder(conf1).numDataNodes(0).build();
      conf1.set(DFSConfigKeys.DFS_NAMENODE_BACKUP_ADDRESS_KEY, "0.0.0.0:7771");
      conf1.set(DFSConfigKeys.DFS_NAMENODE_BACKUP_HTTP_ADDRESS_KEY, "0.0.0.0:7775");
      backup1 = startBackupNode(conf1, StartupOption.BACKUP, 1);
      // try to start backup node 2
      conf2 = new HdfsConfiguration(conf1);
      conf2.set(DFSConfigKeys.DFS_NAMENODE_BACKUP_ADDRESS_KEY, "0.0.0.0:7772");
      conf2.set(DFSConfigKeys.DFS_NAMENODE_BACKUP_HTTP_ADDRESS_KEY, "0.0.0.0:7776");
      try {
        backup2 = startBackupNode(conf2, StartupOption.BACKUP, 2);
        backup2.stop();
        backup2 = null;
        assertTrue("Only one backup node should be able to start", false);
      } catch(IOException e) {
        assertTrue(
            e.getLocalizedMessage().contains("Registration is not allowed"));
        // should fail - doing good
      }
      // stop backup node 1; backup node 2 should be able to start
      backup1.stop();
      backup1 = null;
      try {
        backup2 = startBackupNode(conf2, StartupOption.BACKUP, 2);
      } catch(IOException e) {
        assertTrue("Backup node 2 should be able to start", false);
      }
    } catch(IOException e) {
      LOG.error("Error in TestBackupNode:", e);
      assertTrue(e.getLocalizedMessage(), false);
    } finally {
      if(backup1 != null) backup1.stop();
      if(backup2 != null) backup2.stop();
      if(cluster != null) cluster.shutdown();
    }
  }
}
