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

org.terracotta.angela.common.distribution.Distribution102Controller Maven / Gradle / Ivy

/*
 * Copyright Terracotta, Inc.
 *
 * Licensed 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.terracotta.angela.common.distribution;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terracotta.angela.common.AngelaProperties;
import org.terracotta.angela.common.TerracottaCommandLineEnvironment;
import org.terracotta.angela.common.TerracottaManagementServerInstance.TerracottaManagementServerInstanceProcess;
import org.terracotta.angela.common.TerracottaManagementServerState;
import org.terracotta.angela.common.TerracottaServerHandle;
import org.terracotta.angela.common.TerracottaServerState;
import org.terracotta.angela.common.TerracottaVoter;
import org.terracotta.angela.common.TerracottaVoterInstance.TerracottaVoterInstanceProcess;
import org.terracotta.angela.common.ToolExecutionResult;
import org.terracotta.angela.common.provider.ConfigurationManager;
import org.terracotta.angela.common.provider.TcConfigManager;
import org.terracotta.angela.common.tcconfig.License;
import org.terracotta.angela.common.tcconfig.SecureTcConfig;
import org.terracotta.angela.common.tcconfig.SecurityRootDirectory;
import org.terracotta.angela.common.tcconfig.ServerSymbolicName;
import org.terracotta.angela.common.tcconfig.TcConfig;
import org.terracotta.angela.common.tcconfig.TerracottaServer;
import org.terracotta.angela.common.tms.security.config.TmsServerSecurityConfig;
import org.terracotta.angela.common.topology.PackageType;
import org.terracotta.angela.common.topology.Topology;
import org.terracotta.angela.common.util.ExternalLoggers;
import org.terracotta.angela.common.util.HostPort;
import org.terracotta.angela.common.util.OS;
import org.terracotta.angela.common.util.ProcessUtil;
import org.terracotta.angela.common.util.RetryUtils;
import org.terracotta.angela.common.util.TriggeringOutputStream;
import org.zeroturnaround.exec.ProcessExecutor;
import org.zeroturnaround.exec.ProcessResult;
import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static java.io.File.separator;
import static java.lang.Integer.parseInt;
import static java.util.regex.Pattern.compile;
import static org.terracotta.angela.common.util.HostAndIpValidator.isValidHost;
import static org.terracotta.angela.common.util.HostAndIpValidator.isValidIPv4;
import static org.terracotta.angela.common.util.HostAndIpValidator.isValidIPv6;

/**
 * @author Aurelien Broszniowski
 */
public class Distribution102Controller extends DistributionController {
  private final static Logger logger = LoggerFactory.getLogger(Distribution102Controller.class);
  private final boolean tsaFullLogging = AngelaProperties.TSA_FULL_LOGGING.getBooleanValue();
  private final boolean tmsFullLogging = AngelaProperties.TMS_FULL_LOGGING.getBooleanValue();

  Distribution102Controller(Distribution distribution) {
    super(distribution);
  }

  @Override
  public TerracottaServerHandle createTsa(TerracottaServer terracottaServer, File kitDir, File workingDir,
                                          Topology topology, Map proxiedPorts,
                                          TerracottaCommandLineEnvironment tcEnv, Map envOverrides,
                                          List startUpArgs, Duration inactivityKillerDelay) {
    Map env = tcEnv.buildEnv(envOverrides);
    AtomicReference stateRef = new AtomicReference<>(TerracottaServerState.STOPPED);
    AtomicInteger javaPid = new AtomicInteger(-1);

    TriggeringOutputStream serverLogOutputStream = TriggeringOutputStream
        .triggerOn(
            compile("^.*\\QTerracotta Server instance has started up as ACTIVE\\E.*$"),
            mr -> stateRef.set(TerracottaServerState.STARTED_AS_ACTIVE))
        .andTriggerOn(
            compile("^.*\\QMoved to State[ PASSIVE-STANDBY ]\\E.*$"),
            mr -> stateRef.set(TerracottaServerState.STARTED_AS_PASSIVE))
        .andTriggerOn(
            compile("^.*\\QL2 Exiting\\E.*$"),
            mr -> stateRef.set(TerracottaServerState.STOPPED))
        .andTriggerOn(
            compile("^.*PID is (\\d+).*$"), mr -> {
              javaPid.set(parseInt(mr.group(1)));
              stateRef.compareAndSet(TerracottaServerState.STOPPED, TerracottaServerState.STARTING);
            });
    serverLogOutputStream = tsaFullLogging ?
        serverLogOutputStream.andForward(line -> ExternalLoggers.tsaLogger.info("[{}] {}", terracottaServer.getServerSymbolicName().getSymbolicName(), line)) :
        serverLogOutputStream.andTriggerOn(compile("^.*(WARN|ERROR).*$"), mr -> ExternalLoggers.tsaLogger.info("[{}] {}", terracottaServer.getServerSymbolicName().getSymbolicName(), mr.group()));

    WatchedProcess watchedProcess = new WatchedProcess<>(
        new ProcessExecutor()
            .command(createTsaCommand(terracottaServer.getServerSymbolicName(), terracottaServer.getId(), topology, proxiedPorts, kitDir, workingDir, startUpArgs))
            .directory(workingDir)
            .environment(env)
            .redirectErrorStream(true)
            .redirectOutput(serverLogOutputStream),
        stateRef,
        TerracottaServerState.STOPPED);

    while (javaPid.get() == -1 && watchedProcess.isAlive()) {
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
    }

    if (!watchedProcess.isAlive()) {
      throw new RuntimeException("Terracotta server process died in its infancy : " + terracottaServer.getServerSymbolicName());
    }

    return new TerracottaServerHandle() {
      @Override
      public TerracottaServerState getState() {
        return stateRef.get();
      }

      @Override
      public int getJavaPid() {
        return javaPid.get();
      }

      @Override
      public boolean isAlive() {
        return watchedProcess.isAlive();
      }

      @Override
      public void stop() {
        try {
          ProcessUtil.destroyGracefullyOrForcefullyAndWait(javaPid.get());
        } catch (Exception e) {
          throw new RuntimeException("Could not destroy TC server process with PID " + watchedProcess.getPid(), e);
        }
        try {
          ProcessUtil.destroyGracefullyOrForcefullyAndWait(watchedProcess.getPid());
        } catch (Exception e) {
          throw new RuntimeException("Could not destroy TC server process with PID " + watchedProcess.getPid(), e);
        }
        final int maxWaitTimeMillis = 30000;
        if (!RetryUtils.waitFor(() -> getState() == TerracottaServerState.STOPPED, maxWaitTimeMillis)) {
          throw new RuntimeException(
              String.format(
                  "Tried for %dms, but server %s did not get the state %s [remained at state %s]",
                  maxWaitTimeMillis,
                  terracottaServer.getServerSymbolicName().getSymbolicName(),
                  TerracottaServerState.STOPPED,
                  getState()
              )
          );
        }
      }
    };
  }

  @Override
  public ToolExecutionResult configureCluster(File kitDir, File workingDir, Topology topology, Map proxyTsaPorts, License license, SecurityRootDirectory securityDir,
                                              TerracottaCommandLineEnvironment env, Map envOverrides, String... arguments) {
    List command = createClusterConfigureCommand(kitDir, workingDir, topology, proxyTsaPorts, license, securityDir, arguments);
    return executeCommand(command, env, workingDir, envOverrides);
  }

  @Override
  public ToolExecutionResult invokeClusterTool(File kitDir, File workingDir, SecurityRootDirectory securityDir,
                                               TerracottaCommandLineEnvironment env, Map envOverrides, String... arguments) {
    List command = createClusterToolCommand(kitDir, workingDir, securityDir, arguments);
    return executeCommand(command, env, workingDir, envOverrides);
  }

  @Override
  public ToolExecutionResult invokeConfigTool(File kitDir, File workingDir, SecurityRootDirectory securityDir,
                                              TerracottaCommandLineEnvironment env, Map envOverrides, String... arguments) {
    throw new UnsupportedOperationException("Running config tool is not supported in this distribution version");
  }

  @Override
  public ToolExecutionResult activateCluster(File kitDir, File workingDir, License license, SecurityRootDirectory securityDir,
                                             TerracottaCommandLineEnvironment env, Map envOverrides, String... arguments) {
    throw new UnsupportedOperationException("Running config tool is not supported in this distribution version");
  }

  @Override
  public TerracottaManagementServerInstanceProcess startTms(File kitDir, File workingDir, TerracottaCommandLineEnvironment tcEnv, Map envOverrides) {
    Map env = tcEnv.buildEnv(envOverrides);

    AtomicReference stateRef = new AtomicReference<>(TerracottaManagementServerState.STOPPED);
    AtomicInteger javaPid = new AtomicInteger(-1);

    TriggeringOutputStream outputStream = TriggeringOutputStream
        .triggerOn(
            compile("^.*\\Qstarted on port\\E.*$"),
            mr -> stateRef.set(TerracottaManagementServerState.STARTED))
        .andTriggerOn(
            compile("^.*\\QStarting TmsApplication\\E.*with PID (\\d+).*$"),
            mr -> javaPid.set(parseInt(mr.group(1))));
    outputStream = tmsFullLogging ?
        outputStream.andForward(ExternalLoggers.tmsLogger::info) :
        outputStream.andTriggerOn(compile("^.*(WARN|ERROR).*$"), mr -> ExternalLoggers.tmsLogger.info(mr.group()));

    WatchedProcess watchedProcess = new WatchedProcess<>(new ProcessExecutor()
        .command(startTmsCommand(kitDir))
        .directory(workingDir)
        .environment(env)
        .redirectErrorStream(true)
        .redirectOutput(outputStream), stateRef, TerracottaManagementServerState.STOPPED);

    while ((javaPid.get() == -1 || stateRef.get() == TerracottaManagementServerState.STOPPED) && watchedProcess.isAlive()) {
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
    }
    if (!watchedProcess.isAlive()) {
      throw new RuntimeException("TMS process died before reaching STARTED state");
    }

    int wrapperPid = watchedProcess.getPid();
    int javaProcessPid = javaPid.get();
    return new TerracottaManagementServerInstanceProcess(stateRef, wrapperPid, javaProcessPid);
  }

  @Override
  public void stopTms(File installLocation, TerracottaManagementServerInstanceProcess terracottaServerInstanceProcess, TerracottaCommandLineEnvironment tcEnv) {
    logger.debug("Destroying TMS process");
    for (Number pid : terracottaServerInstanceProcess.getPids()) {
      try {
        ProcessUtil.destroyGracefullyOrForcefullyAndWait(pid.intValue());
      } catch (Exception e) {
        logger.error("Could not destroy TMS process {}", pid, e);
      }
    }
  }

  @Override
  public TerracottaVoterInstanceProcess startVoter(TerracottaVoter terracottaVoter, File kitDir, File workingDir,
                                                   SecurityRootDirectory securityDir, TerracottaCommandLineEnvironment tcEnv, Map envOverrides) {
    throw new UnsupportedOperationException("Running voter is not supported in this distribution version");
  }

  @Override
  public void stopVoter(TerracottaVoterInstanceProcess terracottaVoterInstanceProcess) {
    throw new UnsupportedOperationException("Running voter is not supported in this distribution version");
  }

  @Override
  public URI tsaUri(Collection servers, Map proxyTsaPorts) {
    return URI.create(servers
        .stream()
        .map(s -> new HostPort(s.getHostName(), proxyTsaPorts.getOrDefault(s.getServerSymbolicName(), s.getTsaPort())).getHostPort())
        .collect(Collectors.joining(",", "terracotta://", "")));
  }

  @Override
  public String clientJarsRootFolderName(Distribution distribution) {
    if (distribution.getPackageType() == PackageType.KIT) {
      return "client";
    } else if (distribution.getPackageType() == PackageType.SAG_INSTALLER) {
      return "common" + separator + "lib";
    }
    throw new UnsupportedOperationException();
  }

  @Override
  public String pluginJarsRootFolderName(Distribution distribution) {
    return "server" + separator + "plugins" + separator + "lib";
  }

  @Override
  public String terracottaInstallationRoot() {
    return "TerracottaDB";
  }

  @Override
  public void prepareTMS(File kitDir, File workingDir, TmsServerSecurityConfig tmsServerSecurityConfig) {
    if (!AngelaProperties.KIT_COPY.getBooleanValue()) {
      throw new IllegalStateException(AngelaProperties.KIT_COPY.getPropertyName() + "=true is required with the kit version used");
    }
    File tmcPropertiesInput = new File(kitDir, "tools/management/conf/tmc.properties");
    Properties properties = new Properties();
    if (tmcPropertiesInput.exists()) {
      try (InputStream inputStream = new FileInputStream(tmcPropertiesInput)) {
        properties.load(inputStream);
      } catch (IOException ex) {
        throw new UncheckedIOException(ex);
      }
    }

    File tmcPropertiesOutput = new File(workingDir, "tools/management/conf/tmc.properties");
    prepareTMS(properties, tmcPropertiesOutput, tmsServerSecurityConfig, workingDir);
  }

  private List createClusterToolCommand(File installLocation, File workingDir, SecurityRootDirectory securityDir, String[] arguments) {
    List command = new ArrayList<>();
    command.add(getClusterToolExecutable(installLocation));
    if (securityDir != null) {
      Path securityDirPath = workingDir.toPath().resolve("cluster-tool-security-dir");
      securityDir.createSecurityRootDirectory(securityDirPath);
      command.add("-srd");
      command.add(securityDirPath.toString());
    }
    command.addAll(Arrays.asList(arguments));
    return command;
  }

  private List createClusterConfigureCommand(File installLocation, File workingDir, Topology topology, Map proxyTsaPort, License license, SecurityRootDirectory securityDir, String[] arguments) {
    List command = createClusterToolCommand(installLocation, workingDir, securityDir, arguments);
    if (license != null) {
      Path licensePath = workingDir.toPath().resolve(license.getFilename());
      command.add("-l");
      command.add(licensePath.toString());
    }
    command.addAll(addConfigureRelatedCommands(topology, proxyTsaPort));
    return command;
  }

  private List addConfigureRelatedCommands(Topology topology, Map proxiedTsaPorts) {
    List commands = new ArrayList<>();
    File tmpConfigDir = new File(FileUtils.getTempDirectory(), "tmp-tc-configs");

    if (!tmpConfigDir.mkdir() && !tmpConfigDir.isDirectory()) {
      throw new RuntimeException("Error creating temporary cluster tool TC config folder : " + tmpConfigDir);
    }
    ConfigurationManager configurationProvider = topology.getConfigurationManager();
    TcConfigManager tcConfigProvider = (TcConfigManager) configurationProvider;
    List tcConfigs = tcConfigProvider.getTcConfigs();
    List modifiedConfigs = new ArrayList<>();
    for (TcConfig tcConfig : tcConfigs) {
      TcConfig modifiedConfig = tcConfig.copy();
      if (topology.isNetDisruptionEnabled()) {
        modifiedConfig.updateServerTsaPort(proxiedTsaPorts);
      }
      modifiedConfig.writeTcConfigFile(tmpConfigDir);
      modifiedConfigs.add(modifiedConfig);
    }

    for (TcConfig tcConfig : modifiedConfigs) {
      commands.add(tcConfig.getPath());
    }
    return commands;
  }

  private ToolExecutionResult executeCommand(List command, TerracottaCommandLineEnvironment env,
                                             File workingDir, Map envOverrides) {
    try {
      logger.debug("Cluster tool command: {}", command);
      ProcessResult processResult = new ProcessExecutor(command)
          .directory(workingDir)
          .environment(env.buildEnv(envOverrides))
          .readOutput(true)
          .redirectOutputAlsoTo(Slf4jStream.of(ExternalLoggers.clusterToolLogger).asInfo())
          .redirectErrorStream(true)
          .execute();
      return new ToolExecutionResult(processResult.getExitValue(), processResult.getOutput().getLines());
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  private String getClusterToolExecutable(File installLocation) {
    String execPath = "tools" + separator + "cluster-tool" + separator + "bin" + separator + "cluster-tool" + OS.INSTANCE.getShellExtension();

    if (distribution.getPackageType() == PackageType.KIT) {
      return installLocation.getAbsolutePath() + separator + execPath;
    } else if (distribution.getPackageType() == PackageType.SAG_INSTALLER) {
      return installLocation.getAbsolutePath() + separator + terracottaInstallationRoot() + separator + execPath;
    }
    throw new IllegalStateException("Can not define cluster tool command for distribution: " + distribution);
  }

  private List startTmsCommand(File kitDir) {
    List options = new ArrayList<>();
    // start command
    options.add(getStartTmsExecutable(kitDir));

    StringBuilder sb = new StringBuilder();
    for (String option : options) {
      sb.append(option).append(" ");
    }
    logger.debug(" Start TMS command = {}", sb.toString());

    return options;
  }

  private String getStartTmsExecutable(File installLocation) {
    String execPath = "tools" + separator + "management" + separator + "bin" + separator + "start" + OS.INSTANCE.getShellExtension();
    if (distribution.getPackageType() == PackageType.KIT) {
      return installLocation.getAbsolutePath() + separator + execPath;
    } else if (distribution.getPackageType() == PackageType.SAG_INSTALLER) {
      return installLocation.getAbsolutePath() + separator + terracottaInstallationRoot() + separator + execPath;
    }
    throw new IllegalStateException("Can not define TMS Start Command for distribution: " + distribution);
  }

  private List createTsaCommand(ServerSymbolicName serverSymbolicName, UUID serverId, Topology topology,
                                        Map proxiedPorts, File kitLocation, File installLocation,
                                        List startUpArgs) {
    List options = new ArrayList<>();
    // start command
    options.add(getTsaCreateExecutable(kitLocation));

    String symbolicName = serverSymbolicName.getSymbolicName();
    if (isValidHost(symbolicName) || isValidIPv4(symbolicName) || isValidIPv6(symbolicName) || symbolicName.isEmpty()) {
      // add -n if applicable
      options.add("-n");
      options.add(symbolicName);
    }

    TcConfigManager configurationProvider = (TcConfigManager) topology.getConfigurationManager();
    TcConfig tcConfig = configurationProvider.findTcConfig(serverId);
    SecurityRootDirectory securityRootDirectory = null;
    if (tcConfig instanceof SecureTcConfig) {
      SecureTcConfig secureTcConfig = (SecureTcConfig) tcConfig;
      securityRootDirectory = secureTcConfig.securityRootDirectoryFor(serverSymbolicName);
    }
    TcConfig modifiedConfig = configurationProvider.findTcConfig(serverId).copy();
    configurationProvider.setUpInstallation(modifiedConfig, serverSymbolicName, serverId, proxiedPorts, installLocation, securityRootDirectory);

    // add -f if applicable
    if (modifiedConfig.getPath() != null) {
      options.add("-f");
      options.add(modifiedConfig.getPath());
    }

    options.addAll(startUpArgs);

    StringBuilder sb = new StringBuilder();
    for (String option : options) {
      sb.append(option).append(" ");
    }
    logger.debug("Create TSA command = {}", sb.toString());

    return options;
  }

  private String getTsaCreateExecutable(File kitLocation) {
    String execPath = "server" + separator + "bin" + separator + "start-tc-server" + OS.INSTANCE.getShellExtension();
    if (distribution.getPackageType() == PackageType.KIT) {
      return kitLocation.getAbsolutePath() + separator + execPath;
    } else if (distribution.getPackageType() == PackageType.SAG_INSTALLER) {
      return kitLocation.getAbsolutePath() + separator + terracottaInstallationRoot() + separator + execPath;
    }
    throw new IllegalStateException("Can not define Terracotta server Start Command for distribution: " + distribution);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy