Package org.sonatype.nexus.bundle.launcher.support

Source Code of org.sonatype.nexus.bundle.launcher.support.DefaultNexusBundle$ConfigurationStrategy

/*
* Sonatype Nexus (TM) Open Source Version
* Copyright (c) 2007-2014 Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
* which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
* of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
* Eclipse Foundation. All other trademarks are the property of their respective owners.
*/
package org.sonatype.nexus.bundle.launcher.support;

import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Map;
import java.util.Properties;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;

import org.sonatype.nexus.bootstrap.Launcher;
import org.sonatype.nexus.bootstrap.monitor.CommandMonitorTalker;
import org.sonatype.nexus.bootstrap.monitor.CommandMonitorThread;
import org.sonatype.nexus.bootstrap.monitor.KeepAliveThread;
import org.sonatype.nexus.bootstrap.monitor.commands.ExitCommand;
import org.sonatype.nexus.bootstrap.monitor.commands.HaltCommand;
import org.sonatype.nexus.bootstrap.monitor.commands.PingCommand;
import org.sonatype.nexus.bootstrap.monitor.commands.StopApplicationCommand;
import org.sonatype.nexus.bootstrap.monitor.commands.StopMonitorCommand;
import org.sonatype.nexus.bundle.launcher.NexusBundle;
import org.sonatype.nexus.bundle.launcher.NexusBundleConfiguration;
import org.sonatype.sisu.bl.support.DefaultWebBundle;
import org.sonatype.sisu.bl.support.RunningBundles;
import org.sonatype.sisu.bl.support.TimedCondition;
import org.sonatype.sisu.bl.support.port.PortReservationService;
import org.sonatype.sisu.filetasks.FileTaskBuilder;
import org.sonatype.sisu.goodies.common.Time;

import org.apache.commons.io.FileUtils;
import org.apache.tools.ant.taskdefs.condition.Os;

import static org.sonatype.nexus.bootstrap.monitor.CommandMonitorThread.LOCALHOST;
import static org.sonatype.nexus.bundle.launcher.support.DefaultNexusBundleConfiguration.DEFAULT_START_TIMEOUT;
import static org.sonatype.sisu.filetasks.FileTaskRunner.onDirectory;
import static org.sonatype.sisu.filetasks.builder.FileRef.path;
import static org.sonatype.sisu.goodies.common.SimpleFormat.format;

/**
* Default Nexus bundle implementation.
*
* @since 2.0
*/
@Named
public class DefaultNexusBundle
    extends DefaultWebBundle<NexusBundle, NexusBundleConfiguration>
    implements NexusBundle
{
  /**
   * File task builder.
   * Cannot be null.
   */
  private final FileTaskBuilder fileTaskBuilder;

  /**
   * Port on which Nexus Command Monitor is running. 0 (zero) if application is not running.
   */
  private int commandMonitorPort;

  /**
   * Port on which Nexus Keep Alive is running. 0 (zero) if application is not running.
   */
  private int keepAlivePort;

  /**
   * Keep alive thread. Null if server is not started.
   */
  private CommandMonitorThread keepAliveThread;

  private ConfigurationStrategy strategy;

  @Inject
  public DefaultNexusBundle(final Provider<NexusBundleConfiguration> configurationProvider,
                            final RunningBundles runningBundles,
                            final FileTaskBuilder fileTaskBuilder,
                            final PortReservationService portReservationService)
  {
    super("nexus", configurationProvider, runningBundles, fileTaskBuilder, portReservationService);
    this.fileTaskBuilder = fileTaskBuilder;
  }

  private void installStopShutdownHook(final int commandPort) {
    Thread stopShutdownHook = new Thread("Nexus Sanity Stopper")
    {
      @Override
      public void run() {
        terminateRemoteNexus(commandPort);
      }
    };

    Runtime.getRuntime().addShutdownHook(stopShutdownHook);
    log.debug("Installed stop shutdown hook");
  }

  private void sendStopToNexus(final int commandPort) {
    log.debug("Sending stop command to Nexus");
    try {
      // FIXME HOSTNAME should be configurable
      new CommandMonitorTalker(LOCALHOST, commandPort).send(StopApplicationCommand.NAME);
    }
    catch (Exception e) {
      log.debug(
          "Skipping exception got while sending stop command to Nexus {}:{}",
          e.getClass().getName(), e.getMessage()
      );
    }
  }

  // FIXME accept host to terminate
  private void terminateRemoteNexus(final int commandPort) {
    log.debug("attempting to terminate gracefully at {}", commandPort);

    // First attempt graceful shutdown
    sendStopToNexus(commandPort);

    // FIXME LOCALHOST should be getHostName
    CommandMonitorTalker talker = new CommandMonitorTalker(LOCALHOST, commandPort);
    long started = System.currentTimeMillis();
    long max = 5 * 60 * 1000; // wait 5 minutes for NX to shutdown, before attempting to halt it
    long period = 1000;

    // Then ping for a bit and finally give up and ask it to halt
    while (true) {
      try {
        talker.send(PingCommand.NAME);
      }
      catch (ConnectException e) {
        // likely its shutdown already
        break;
      }
      catch (Exception e) {
        // ignore, not sure there is much we can do
      }

      // If we have waited long enough, then ask remote to halt
      if (System.currentTimeMillis() - started > max) {
        try {
          talker.send(HaltCommand.NAME);
          break;
        }
        catch (Exception e) {
          // ignore, not sure there is much we can do
          break;
        }
      }

      // Wait a wee bit and try again
      try {
        Thread.sleep(period);
      }
      catch (InterruptedException e) {
        // ignore
      }
    }
  }

  private void sendStopToKeepAlive(final int commandPort) {
    log.debug("Sending stop command to keep alive thread");
    try {
      // FIXME replace LOCALHOST with getHostName
      new CommandMonitorTalker(LOCALHOST, commandPort).send(StopMonitorCommand.NAME);
    }
    catch (Exception e) {
      log.debug(
          "Skipping exception got while sending stop command to keep alive thread {}:{}",
          e.getClass().getName(), e.getMessage()
      );
    }
  }

  /**
   * Additionally <br/>
   * - configures Nexus/Jetty port<br/>
   * - installs command monitor<br/>
   * - installs keep alive monitor<br/>
   * - configure remote debugging if requested
   * <p/>
   * {@inheritDoc}
   *
   * @since 2.0
   */
  @Override
  protected void configure()
      throws Exception
  {
    super.configure();

    commandMonitorPort = getPortReservationService().reservePort();
    keepAlivePort = getPortReservationService().reservePort();

    strategy = determineConfigurationStrategy();

    configureNexusProperties(strategy);
  }

  /**
   * {@inheritDoc}
   *
   * @since 1.0
   */
  @Override
  protected void unconfigure() {
    super.unconfigure();

    if (commandMonitorPort > 0) {
      getPortReservationService().cancelPort(commandMonitorPort);
      commandMonitorPort = 0;
    }
    if (keepAlivePort > 0) {
      getPortReservationService().cancelPort(keepAlivePort);
      keepAlivePort = 0;
    }
    strategy = null;
  }

  /**
   * Starts Nexus.
   * <p/>
   * {@inheritDoc}
   *
   * @since 2.0
   */
  @Override
  protected void startApplication() {
    try {
      keepAliveThread = new CommandMonitorThread(
          keepAlivePort,
          new PingCommand(),
          new StopMonitorCommand(),
          new ExitCommand(),
          new HaltCommand()
      );
      keepAliveThread.start();
    }
    catch (IOException e) {
      throw new RuntimeException("Could not start keep alive thread", e);
    }
    installStopShutdownHook(commandMonitorPort);

    final File nexusDir = getNexusDirectory();

    makeExecutable(nexusDir, "bin/*");

    // log whenever ports are configured to aid solving test port conflicts
    log.info("{} ({}) spawned env [{}={},{}={}]", getName(), getConfiguration().getId(),
        strategy.commandMonitorProperty(), commandMonitorPort, strategy.keepAliveProperty(), keepAlivePort);
    onDirectory(nexusDir).apply(
        fileTaskBuilder.exec().spawn()
            .script(path("bin/nexus" + (Os.isFamily(Os.FAMILY_WINDOWS) ? ".bat" : "")))
            .withArgument("start")
            .withEnv(strategy.commandMonitorProperty(), String.valueOf(commandMonitorPort))
            .withEnv(strategy.keepAliveProperty(), String.valueOf(keepAlivePort))
    );

    if (getConfiguration().isSuspendOnStart()) {
      // verify the debugger socket has been opened and is waiting for a debugger to connect
      // command monitor thread is not started while suspended so this is the best we can do
      final boolean jvmSuspended = new TimedCondition()
      {
        @Override
        protected boolean isSatisfied()
            throws Exception
        {
          Socket socket = new Socket();
          socket.setSoTimeout(5000);
          socket.connect(
              new InetSocketAddress(getConfiguration().getHostName(), getConfiguration().getDebugPort()));
          return true;
        }
      }.await(Time.seconds(10), Time.seconds(DEFAULT_START_TIMEOUT), Time.seconds(1));
      if (jvmSuspended) {
        log.info("{} ({}) suspended for debugging at {}:{}", getName(), getConfiguration().getId(),
            getConfiguration().getHostName(), getConfiguration().getDebugPort());
      }
      else {
        throw new RuntimeException(
            format(
                "%s (%s) no open socket for debugging at %s:%s within 10 seconds", getName(),
                getConfiguration().getId(), getConfiguration().getHostName(),
                getConfiguration().getDebugPort()
            )
        );
      }
    }
    else {
      // when not suspending, we expect the internal command monitor thread to start well before bundle is ready
      // so we only give it 10 seconds to be available
      log.info("{} ({}) pinging command monitor at {}:{}", getName(), getConfiguration().getId(),
          getConfiguration().getHostName(), commandMonitorPort);
      final boolean monitorInstalled = new TimedCondition()
      {
        @Override
        protected boolean isSatisfied()
            throws Exception
        {
          // FIXME replace LOCALHOST with getHostName() after making default hostname be 127.0.0.1
          new CommandMonitorTalker(LOCALHOST, commandMonitorPort).send(PingCommand.NAME);
          return true;
        }
      }.await(Time.seconds(10), Time.seconds(DEFAULT_START_TIMEOUT), Time.seconds(1));
      if (monitorInstalled) {
        log.debug("{} ({}) command monitor detected at {}:{}", getName(), getConfiguration().getId(),
            getConfiguration().getHostName(), commandMonitorPort);
      }
      else {
        throw new RuntimeException(
            format("%s (%s) no command monitor detected at %s:%s within 10 seconds", getName(),
                getConfiguration().getId(), getConfiguration().getHostName(),
                commandMonitorPort
            )
        );
      }
    }
  }

  /**
   * Stops Nexus.
   * <p/>
   * {@inheritDoc}
   *
   * @since 2.0
   */
  @Override
  protected void stopApplication() {
    // application may be in suspended state waiting for debugger to attach, if we can reasonably guess this is
    // the case, then we should resume the vm so that we can ask command monitor to immediately halt
    try {
      if (getConfiguration().isSuspendOnStart()) {

        boolean isSuspended = new TimedCondition()
        {
          @Override
          protected boolean isSatisfied()
              throws Exception
          {
            Socket socket = new Socket();
            socket.setSoTimeout(5000);
            socket.connect(
                new InetSocketAddress(getConfiguration().getHostName(),
                    getConfiguration().getDebugPort()));
            return true;
          }
        }.await(Time.seconds(1), Time.seconds(10), Time.seconds(1));

        if (isSuspended) {
          // FIXME avoiding the compile time dependency for now on jdi classes (DebuggerUtils)
          throw new RuntimeException(
              format(
                  "%s (%s) looks suspended at {}:{}, CANNOT STOP THIS BUNDLE!", getName(),
                  getConfiguration().getId(), getConfiguration().getHostName(),
                  getConfiguration().getDebugPort()
              )
          );
        }
      }

      terminateRemoteNexus(commandMonitorPort);

      try {
        // clear transient cache to avoid filesystem bloat when running ITs
        FileUtils.deleteDirectory(new File(getNexusDirectory(), "data/cache"));
      }
      catch (IOException e) {
        // couldn't delete directory, too bad
      }
    }
    finally {
      // Stop the launcher-controller-side monitor thread if there is one
      if (keepAliveThread != null) {
        sendStopToKeepAlive(keepAlivePort);
        keepAliveThread = null;
      }
    }
  }

  /**
   * Checks if Nexus is alive by using REST status service.
   *
   * @return true if Nexus is alive
   * @since 2.0
   */
  @Override
  protected boolean applicationAlive() {
    return RequestUtils.isNexusRESTStarted(getUrl().toExternalForm());
  }

  /**
   * Configure Nexus properties using provided configuration strategy.
   *
   * @param strategy configuration strategy
   */
  private static void configureNexusProperties(final ConfigurationStrategy strategy) {
    strategy.configureNexus();
  }

  /**
   * Determines a configuration strategy based on version of Nexus to be started.
   *
   * @return configuration strategy. Never null.
   */
  private ConfigurationStrategy determineConfigurationStrategy() {
    return new NexusOnKaraf();
  }

  @Override
  protected String composeApplicationURL() {
    return String.format("http://%s:%s/", getConfiguration().getHostName(), getPort());
  }

  @Override
  public File getWorkDirectory() {
    return new File(getConfiguration().getTargetDirectory(), "sonatype-work/nexus");
  }

  public File getNexusDirectory() {
    return new File(getConfiguration().getTargetDirectory(), "nexus");
  }

  @Override
  public File getNexusLog() {
    return new File(getWorkDirectory(), "logs/nexus.log");
  }

  @Override
  public File getLauncherLog() {
    return new File(getNexusDirectory(), "data/log/karaf.log");
  }

  @Override
  protected String generateId() {
    return "nx"; // TODO? use a system property if we should or not add: + "-" + System.currentTimeMillis();
  }

  private void makeExecutable(final File baseDir,
                              final String scriptName)
  {
    if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
      onDirectory(baseDir).apply(
          fileTaskBuilder.chmod(path("/"))
              .include("**/" + scriptName)
              .permissions("u+x")
      );
    }
  }

  private static interface ConfigurationStrategy
  {
    String commandMonitorProperty();

    String keepAliveProperty();

    void configureNexus();
  }

  private class NexusOnKaraf
      implements ConfigurationStrategy
  {
    @Override
    public String commandMonitorProperty() {
      return Launcher.COMMAND_MONITOR_PORT;
    }

    @Override
    public String keepAliveProperty() {
      return KeepAliveThread.KEEP_ALIVE_PORT;
    }

    @Override
    public void configureNexus() {
      final Properties nexusProperties = new Properties();

      nexusProperties.setProperty("application-port", String.valueOf(getPort()));

      final Map<String, String> systemProperties = getConfiguration().getSystemProperties();
      if (!systemProperties.isEmpty()) {
        for (final Map.Entry<String, String> entry : systemProperties.entrySet()) {
          nexusProperties.setProperty(entry.getKey(), entry.getValue() == null ? "true" : entry.getValue());
        }
      }

      onDirectory(getNexusDirectory()).apply(
          fileTaskBuilder.properties(path("etc/nexus-test.properties"))
              .properties(nexusProperties)
      );
    }

//TODO:KARAF customize Karaf settings for debug+JMX
//  @Override
//  public void configureJSW(final JSWConfig jswConfig) {
//    // configure remote debug if requested
//    if (getConfiguration().getDebugPort() > 0) {
//      jswConfig.addJavaStartupParameter("-Xdebug");
//      jswConfig.addJavaStartupParameter("-Xnoagent");
//      jswConfig.addJavaStartupParameter(
//          "-Xrunjdwp:transport=dt_socket,server=y,suspend="
//              + (getConfiguration().isSuspendOnStart() ? "y" : "n")
//              + ",address=" + getConfiguration().getDebugPort()
//      );
//      jswConfig.addJavaSystemProperty("java.compiler", "NONE");
//    }
//
//    JMXConfiguration jmxConfig = getConfiguration().getJmxConfiguration();
//    if (jmxConfig.getRemotePort() != null) {
//      Map<String, String> jmxProps = jmxConfig.getSystemProperties();
//      jmxProps.put(JMXConfiguration.PROP_COM_SUN_MANAGEMENT_JMXREMOTE_PORT, Integer.toString(getJmxRemotePort()));
//      jswConfig.addJavaSystemProperties(jmxProps);
//    }
//  }
  }

}
TOP

Related Classes of org.sonatype.nexus.bundle.launcher.support.DefaultNexusBundle$ConfigurationStrategy

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.