content: /**
   * Copyright The Apache Software Foundation
   *
   * 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 com.alibaba.wasp.util;
  
  import com.alibaba.wasp.fserver.FServer;
  import com.alibaba.wasp.master.FMaster;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  import org.apache.hadoop.conf.Configuration;
  import org.apache.hadoop.hbase.util.Threads;
  
  import java.io.IOException;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationTargetException;
  import java.util.List;
  
  /**
   * Utility used running a cluster all in the one JVM.
   */
  public class JVMClusterUtil {
    private static final Log LOG = LogFactory.getLog(JVMClusterUtil.class);
  
    /**
     * Datastructure to hold FServer Thread and FServer instance
     */
    public static class FServerThread extends Thread {
      private final FServer fserver;
  
      public FServerThread(final FServer r, final int index) {
        super(r, "FServer:" + index + ";" + r.getServerName());
        this.fserver = r;
      }
  
      /** @return the fserver */
      public FServer getFServer() {
        return this.fserver;
      }
  
      /**
       * Block until the fserver has come online, indicating it is ready
       * to be used.
       */
      public void waitForServerOnline() {
        // The server is marked online after the init method completes inside of
        // the fserver#run method. fserver#init can fail for whatever entityGroup.
        // In those cases, we'll jump out of the run without setting online flag.
        // Check stopRequested so we don't wait here a flag that will never be
        // flipped.
        fserver.waitForServerOnline();
      }
    }
  
    /**
     * Creates a {@link FServerThread}.
     * Call 'start' on the returned thread to make it run.
     * @param c Configuration to use.
     * @param hrsc Class to create.
     * @param index Used distinguishing the object returned.
     * @throws java.io.IOException
     * @return FServer added.
     */
    public static JVMClusterUtil.FServerThread createFServerThread(
        final Configuration c, final Class<? extends FServer> hrsc,
        final int index)
    throws IOException {
      FServer server;
      try {
        Constructor<? extends FServer> ctor = hrsc.getConstructor(Configuration.class);
        ctor.setAccessible(true);
        server = ctor.newInstance(c);
      } catch (InvocationTargetException ite) {
        Throwable target = ite.getTargetException();
        throw new RuntimeException("Failed construction of FServer: " +
          hrsc.toString() + ((target.getCause() != null)?
            target.getCause().getMessage(): ""), target);
      } catch (Exception e) {
        IOException ioe = new IOException();
        ioe.initCause(e);
        throw ioe;
      }
      return new JVMClusterUtil.FServerThread(server, index);
    }
  
  
    /**
     * Datastructure to hold Master Thread and Master instance
     */
    public static class MasterThread extends Thread {
      private final FMaster master;
  
      public MasterThread(final FMaster m, final int index) {
        super(m, "Master:" + index + ";" + m.getServerName());
        this.master = m;
      }
  
      /** @return the master */
      public FMaster getMaster() {
        return this.master;
      }
    }
  
    /**
     * Creates a {@link MasterThread}.
     * Call 'start' on the returned thread to make it run.
     * @param c Configuration to use.
     * @param hmc Class to create.
     * @param index Used distinguishing the object returned.
     * @throws java.io.IOException
     * @return Master added.
     */
    public static JVMClusterUtil.MasterThread createMasterThread(
        final Configuration c, final Class<? extends FMaster> hmc,
        final int index)
    throws IOException {
      FMaster server;
      try {
        server = hmc.getConstructor(Configuration.class).newInstance(c);
      } catch (InvocationTargetException ite) {
        Throwable target = ite.getTargetException();
        throw new RuntimeException("Failed construction of Master: " +
          hmc.toString() + ((target.getCause() != null)?
            target.getCause().getMessage(): ""), target);
      } catch (Exception e) {
        IOException ioe = new IOException();
        ioe.initCause(e);
        throw ioe;
      }
      return new JVMClusterUtil.MasterThread(server, index);
    }
  
    private static JVMClusterUtil.MasterThread findActiveMaster(
      List<MasterThread> masters) {
      for (JVMClusterUtil.MasterThread t : masters) {
        if (t.master.isActiveMaster()) {
          return t;
        }
      }
  
      return null;
    }
  
    /**
     * Start the cluster.  Waits until there is a primary master initialized
     * and returns its address.
     * @param masters
     * @param fservers
     * @return Address to use contacting primary master.
     */
    public static String startup(final List<MasterThread> masters,
        final List<FServerThread> fservers) throws IOException {
  
      if (masters == null || masters.isEmpty()) {
        return null;
      }
  
      for (JVMClusterUtil.MasterThread t : masters) {
        t.start();
      }
  
      // Wait for an active master
      // having an active master before starting the fserver threads allows
      //  then to succeed on their connection to master
      long startTime = System.currentTimeMillis();
      while (findActiveMaster(masters) == null) {
        try {
          Thread.sleep(100);
        } catch (InterruptedException ignored) {
        }
        if (System.currentTimeMillis() > startTime + 30000) {
          throw new RuntimeException("Master not active after 30 seconds");
        }
      }
  
      if (fservers != null) {
        for (JVMClusterUtil.FServerThread t: fservers) {
          t.start();
        }
      }
  
      // Wait for an active master to be initialized (implies being master)
      //  with this, when we return the cluster is complete
      startTime = System.currentTimeMillis();
      while (true) {
        JVMClusterUtil.MasterThread t = findActiveMaster(masters);
        if (t != null && t.master.isInitialized()) {
          return t.master.getServerName().toString();
        }
        if (System.currentTimeMillis() > startTime + 200000) {
          throw new RuntimeException("Master not initialized after 200 seconds");
        }
        try {
          Thread.sleep(100);
        } catch (InterruptedException ignored) {
          // Keep waiting
        }
      }
    }
  
    /**
     * @param masters
     * @param fservers
     */
    public static void shutdown(final List<MasterThread> masters,
        final List<FServerThread> fservers) throws IOException {
      LOG.debug("Shutting down HBase Cluster");
      if (masters != null) {
        // Do backups first.
        JVMClusterUtil.MasterThread activeMaster = null;
        for (JVMClusterUtil.MasterThread t : masters) {
          if (!t.master.isActiveMaster()) {
            t.master.stopMaster();
          } else {
            activeMaster = t;
          }
        }
        // Do active after.
        if (activeMaster != null) activeMaster.master.shutdown();
      }
      if (fservers != null) {
        for (FServerThread t : fservers) {
          if (t.isAlive()) {
            try {
              t.getFServer().stop("Shutdown requested");
              t.join();
            } catch (InterruptedException e) {
              // continue
            }
          }
        }
      }
      if (masters != null) {
        for (JVMClusterUtil.MasterThread t : masters) {
          while (t.master.isAlive()) {
            try {
              // The below has been replaced to debug sometime hangs on end of
              // tests.
              // this.master.join():
              Threads.threadDumpingIsAlive(t.master.getThread());
            } catch(InterruptedException e) {
              // continue
            }
          }
        }
      }
      LOG.info("Shutdown of " +
        ((masters != null) ? masters.size() : "0") + " master(s) and " +
        ((fservers != null) ? fservers.size() : "0") +
        " fserver(s) complete");
    }
  }
  