All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.application.process.ProcessLauncherImpl Maven / Gradle / Ivy

/*
 * SonarQube
 * Copyright (C) 2009-2018 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.application.process;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Supplier;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.application.command.AbstractCommand;
import org.sonar.application.command.EsScriptCommand;
import org.sonar.application.command.JavaCommand;
import org.sonar.application.command.JvmOptions;
import org.sonar.application.es.EsInstallation;
import org.sonar.process.ProcessId;
import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
import org.sonar.process.sharedmemoryfile.ProcessCommands;

import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_KEY;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT_MS;

public class ProcessLauncherImpl implements ProcessLauncher {
  private static final Logger LOG = LoggerFactory.getLogger(ProcessLauncherImpl.class);

  private final File tempDir;
  private final AllProcessesCommands allProcessesCommands;
  private final Supplier processBuilderSupplier;

  public ProcessLauncherImpl(File tempDir) {
    this(tempDir, new AllProcessesCommands(tempDir), JavaLangProcessBuilder::new);
  }

  ProcessLauncherImpl(File tempDir, AllProcessesCommands allProcessesCommands, Supplier processBuilderSupplier) {
    this.tempDir = tempDir;
    this.allProcessesCommands = allProcessesCommands;
    this.processBuilderSupplier = processBuilderSupplier;
  }

  @Override
  public void close() {
    allProcessesCommands.close();
  }

  public ProcessMonitor launch(AbstractCommand command) {
    EsInstallation fileSystem = command.getEsInstallation();
    if (fileSystem != null) {
      cleanupOutdatedEsData(fileSystem);
      writeConfFiles(fileSystem);
    }

    Process process;
    if (command instanceof EsScriptCommand) {
      process = launchExternal((EsScriptCommand) command);
    } else if (command instanceof JavaCommand) {
      process = launchJava((JavaCommand) command);
    } else {
      throw new IllegalStateException("Unexpected type of command: " + command.getClass());
    }

    ProcessId processId = command.getProcessId();
    try {
      if (processId == ProcessId.ELASTICSEARCH) {
        return new EsProcessMonitor(process, processId, command.getEsInstallation(), new EsConnectorImpl());
      } else {
        ProcessCommands commands = allProcessesCommands.createAfterClean(processId.getIpcIndex());
        return new ProcessCommandsProcessMonitor(process, processId, commands);
      }
    } catch (Exception e) {
      // just in case
      if (process != null) {
        process.destroyForcibly();
      }
      throw new IllegalStateException(format("Fail to launch monitor of process [%s]", processId.getKey()), e);
    }
  }

  private Process launchExternal(EsScriptCommand esScriptCommand) {
    try {
      ProcessBuilder processBuilder = create(esScriptCommand);
      logLaunchedCommand(esScriptCommand, processBuilder);
      return processBuilder.start();
    } catch (Exception e) {
      throw new IllegalStateException(format("Fail to launch process [%s]", esScriptCommand.getProcessId().getKey()), e);
    }
  }

  private static void cleanupOutdatedEsData(EsInstallation esInstallation) {
    esInstallation.getOutdatedSearchDirectories().forEach(outdatedDir -> {
      if (outdatedDir.exists()) {
        LOG.info("Deleting outdated search index data directory {}", outdatedDir.getAbsolutePath());
        try {
          FileUtils.deleteDirectory(outdatedDir);
        } catch (IOException e) {
          LOG.info("Failed to delete outdated search index data directory {}", outdatedDir.getAbsolutePath(), e);
        }
      }
    });
  }

  private static void writeConfFiles(EsInstallation esInstallation) {
    File confDir = esInstallation.getConfDirectory();
    if (!confDir.exists() && !confDir.mkdirs()) {
      String error = format("Failed to create temporary configuration directory [%s]", confDir.getAbsolutePath());
      LOG.error(error);
      throw new IllegalStateException(error);
    }

    try {
      esInstallation.getEsYmlSettings().writeToYmlSettingsFile(esInstallation.getElasticsearchYml());
      esInstallation.getEsJvmOptions().writeToJvmOptionFile(esInstallation.getJvmOptions());
      esInstallation.getLog4j2Properties().store(new FileOutputStream(esInstallation.getLog4j2PropertiesLocation()), "log4j2 properties file for ES bundled in SonarQube");
    } catch (IOException e) {
      throw new IllegalStateException("Failed to write ES configuration files", e);
    }
  }

  private  Process launchJava(JavaCommand javaCommand) {
    ProcessId processId = javaCommand.getProcessId();
    try {
      ProcessBuilder processBuilder = create(javaCommand);
      logLaunchedCommand(javaCommand, processBuilder);
      return processBuilder.start();
    } catch (Exception e) {
      throw new IllegalStateException(format("Fail to launch process [%s]", processId.getKey()), e);
    }
  }

  private static  void logLaunchedCommand(AbstractCommand command, ProcessBuilder processBuilder) {
    if (LOG.isInfoEnabled()) {
      LOG.info("Launch process[{}] from [{}]: {}",
        command.getProcessId(),
        command.getWorkDir().getAbsolutePath(),
        String.join(" ", processBuilder.command()));
    }
  }

  private ProcessBuilder create(EsScriptCommand esScriptCommand) {
    List commands = new ArrayList<>();
    EsInstallation esInstallation = esScriptCommand.getEsInstallation();
    requireNonNull(esInstallation, () -> "No Elasticsearch installation configuration is available for the command of type " + esScriptCommand.getClass());
    commands.add(esInstallation.getExecutable().getAbsolutePath());
    commands.addAll(esScriptCommand.getOptions());

    return create(esScriptCommand, commands);
  }

  private  ProcessBuilder create(JavaCommand javaCommand) {
    List commands = new ArrayList<>();
    commands.add(buildJavaPath());
    commands.addAll(javaCommand.getJvmOptions().getAll());
    commands.addAll(buildClasspath(javaCommand));
    commands.add(javaCommand.getClassName());
    if (javaCommand.getReadsArgumentsFromFile()) {
      commands.add(buildPropertiesFile(javaCommand).getAbsolutePath());
    } else {
      javaCommand.getArguments().forEach((key, value) -> {
        if (value != null && !value.isEmpty()) {
          commands.add("-E" + key + "=" + value);
        }
      });
    }

    return create(javaCommand, commands);
  }

  private ProcessBuilder create(AbstractCommand javaCommand, List commands) {
    ProcessBuilder processBuilder = processBuilderSupplier.get();
    processBuilder.command(commands);
    processBuilder.directory(javaCommand.getWorkDir());
    Map environment = processBuilder.environment();
    environment.putAll(javaCommand.getEnvVariables());
    javaCommand.getSuppressedEnvVariables().forEach(environment::remove);
    processBuilder.redirectErrorStream(true);
    return processBuilder;
  }

  private static String buildJavaPath() {
    String separator = System.getProperty("file.separator");
    return new File(new File(System.getProperty("java.home")), "bin" + separator + "java").getAbsolutePath();
  }

  private static  List buildClasspath(JavaCommand javaCommand) {
    String pathSeparator = System.getProperty("path.separator");
    return Arrays.asList("-cp", String.join(pathSeparator, javaCommand.getClasspath()));
  }

  private File buildPropertiesFile(JavaCommand javaCommand) {
    File propertiesFile = null;
    try {
      propertiesFile = File.createTempFile("sq-process", "properties", tempDir);
      Properties props = new Properties();
      props.putAll(javaCommand.getArguments());
      props.setProperty(PROPERTY_PROCESS_KEY, javaCommand.getProcessId().getKey());
      props.setProperty(PROPERTY_PROCESS_INDEX, Integer.toString(javaCommand.getProcessId().getIpcIndex()));
      props.setProperty(PROPERTY_TERMINATION_TIMEOUT_MS, "60000");
      props.setProperty(PROPERTY_SHARED_PATH, tempDir.getAbsolutePath());
      try (OutputStream out = new FileOutputStream(propertiesFile)) {
        props.store(out, format("Temporary properties file for command [%s]", javaCommand.getProcessId().getKey()));
      }
      return propertiesFile;
    } catch (Exception e) {
      throw new IllegalStateException("Cannot write temporary settings to " + propertiesFile, e);
    }
  }

  /**
   * An interface of the methods of {@link java.lang.ProcessBuilder} that we use in {@link ProcessLauncherImpl}.
   * 

Allows testing creating processes without actualling creating them at OS level

*/ public interface ProcessBuilder { List command(); ProcessBuilder command(List commands); ProcessBuilder directory(File dir); Map environment(); ProcessBuilder redirectErrorStream(boolean b); Process start() throws IOException; } private static class JavaLangProcessBuilder implements ProcessBuilder { private final java.lang.ProcessBuilder builder = new java.lang.ProcessBuilder(); /** * @see java.lang.ProcessBuilder#command() */ @Override public List command() { return builder.command(); } /** * @see java.lang.ProcessBuilder#command(List) */ @Override public ProcessBuilder command(List commands) { builder.command(commands); return this; } /** * @see java.lang.ProcessBuilder#directory(File) */ @Override public ProcessBuilder directory(File dir) { builder.directory(dir); return this; } /** * @see java.lang.ProcessBuilder#environment() */ @Override public Map environment() { return builder.environment(); } /** * @see java.lang.ProcessBuilder#redirectErrorStream(boolean) */ @Override public ProcessBuilder redirectErrorStream(boolean b) { builder.redirectErrorStream(b); return this; } /** * @see java.lang.ProcessBuilder#start() */ @Override public Process start() throws IOException { return builder.start(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy