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

import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.Arrays;

import org.apache.blur.MiniCluster;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.BlockLocation;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class BlurBlockPlacementPolicyDefaultTest {

  private static final String DFS_BLOCKREPORT_INTERVAL_MSEC = "dfs.blockreport.intervalMsec";
  private static final String DFS_BLOCK_REPLICATOR_CLASSNAME = "dfs.block.replicator.classname";
  private static MiniCluster _miniCluster;

  @BeforeClass
  public static void setupClass() {
    _miniCluster = new MiniCluster();
    Configuration conf = new Configuration();
    conf.set(DFS_BLOCK_REPLICATOR_CLASSNAME, BlurBlockPlacementPolicyDefault.class.getName());
    conf.set(BlurBlockPlacementPolicyDefault.BLUR_BLOCK_PLACEMENT_SERVER_LOOKUP, TestServerLookup.class.getName());
    conf.setLong(DFS_BLOCKREPORT_INTERVAL_MSEC, 10 * 1000);
    boolean format = true;
    String path = "./target/tmp/BlurBlockPlacementPolicyDefaultTest";
    String[] racks = new String[] { "/r1", "/r1", "/r2", "/r2", "/r3", "/r3" };
    _miniCluster.startDfs(conf, format, path, racks);
  }

  @AfterClass
  public static void teardownClass() {
    _miniCluster.shutdownDfs();
  }

  @Test
  public void test1() throws IOException, InterruptedException {
    FileSystem fileSystem = _miniCluster.getFileSystem();
    String rootStr = fileSystem.getUri().toString();
    Path root = new Path(rootStr + "/");
    fileSystem.mkdirs(new Path(root, "/test/table/shard-00000000"));
    Path p = writeFile(fileSystem, "/test/table/shard-00000000/test1");
    String shardServer = "host4.foo.com";
    assertBlocksExistOnShardServer(fileSystem, p, shardServer);
    setReplication(fileSystem, p, 4);
    assertBlocksExistOnShardServer(fileSystem, p, shardServer);
    setReplication(fileSystem, p, 5);
    assertBlocksExistOnShardServer(fileSystem, p, shardServer);
    setReplication(fileSystem, p, 1);
    assertBlocksExistOnShardServer(fileSystem, p, shardServer);
  }

  @Test
  public void test2() throws IOException, InterruptedException {
    FileSystem fileSystem = _miniCluster.getFileSystem();
    String rootStr = fileSystem.getUri().toString();
    Path root = new Path(rootStr + "/");
    fileSystem.mkdirs(new Path(root, "/test/table/shard-00000000"));

    String shardServer = "host4.foo.com";
    Path p = writeFileNotOnShardServer(fileSystem, "/testfile", shardServer);
    Path dst = new Path(root, "/test/table/shard-00000000/test2");
    fileSystem.rename(p, dst);
    p = dst;

    setReplication(fileSystem, p, 2);

    assertBlocksExistOnShardServer(fileSystem, p, shardServer);
    setReplication(fileSystem, p, 4);
    assertBlocksExistOnShardServer(fileSystem, p, shardServer);
    setReplication(fileSystem, p, 5);
    assertBlocksExistOnShardServer(fileSystem, p, shardServer);
    setReplication(fileSystem, p, 1);
    assertBlocksExistOnShardServer(fileSystem, p, shardServer);
  }

  private Path writeFileNotOnShardServer(FileSystem fileSystem, String path, String shardServer) throws IOException {
    String rootStr = fileSystem.getUri().toString();
    Path p = new Path(rootStr + path);
    boolean fail = true;
    OUTER: while (fail) {
      fail = false;
      FSDataOutputStream outputStream = fileSystem.create(p, (short) 1);
      byte[] buf = new byte[1000];
      for (int i = 0; i < 1000; i++) {
        outputStream.write(buf);
      }
      outputStream.close();
      FileStatus fileStatus = fileSystem.getFileStatus(p);
      BlockLocation[] blockLocations = fileSystem.getFileBlockLocations(p, 0, fileStatus.getLen());
      for (BlockLocation blockLocation : blockLocations) {
        fail = Arrays.asList(blockLocation.getHosts()).contains(shardServer);
        if (fail) {
          continue OUTER;
        }
      }
    }
    return p;
  }

  private void setReplication(FileSystem fileSystem, Path p, int rep) throws IOException, InterruptedException {
    fileSystem.setReplication(p, (short) rep);
    waitForReplication(fileSystem, p, rep);
  }

  private void waitForReplication(FileSystem fileSystem, Path p, int replicas) throws IOException, InterruptedException {
    FileStatus fileStatus = fileSystem.getFileStatus(p);
    boolean fail = true;
    while (fail) {
      fail = false;
      BlockLocation[] blockLocations = fileSystem.getFileBlockLocations(p, 0, fileStatus.getLen());
      for (BlockLocation blockLocation : blockLocations) {
        System.out.println(blockLocation);
        String[] hosts = blockLocation.getHosts();
        if (hosts.length != replicas) {
          fail = true;
        }
      }
      Thread.sleep(1000);
    }
  }

  private void assertBlocksExistOnShardServer(FileSystem fileSystem, Path p, String shardServer) throws IOException {
    FileStatus fileStatus = fileSystem.getFileStatus(p);
    BlockLocation[] blockLocations = fileSystem.getFileBlockLocations(p, 0, fileStatus.getLen());
    for (BlockLocation blockLocation : blockLocations) {
      System.out.println(blockLocation);
      String[] hosts = blockLocation.getHosts();
      assertTrue(Arrays.asList(hosts).contains(shardServer));
    }
  }

  private Path writeFile(FileSystem fileSystem, String path) throws IOException {
    String rootStr = fileSystem.getUri().toString();
    Path p = new Path(rootStr + path);
    FSDataOutputStream outputStream = fileSystem.create(p);
    byte[] buf = new byte[1000];
    for (int i = 0; i < 1000; i++) {
      outputStream.write(buf);
    }
    outputStream.close();
    return p;
  }

}