
com.apple.foundationdb.relational.yamltests.server.ExternalServer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of yaml-tests Show documentation
Show all versions of yaml-tests Show documentation
Tests of the Relational project driven off of YAML specifications.
The newest version!
/*
* ExternalServer.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2021-2024 Apple Inc. and the FoundationDB project authors
*
* 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 com.apple.foundationdb.relational.yamltests.server;
import com.apple.foundationdb.relational.util.BuildVersion;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.Assertions;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
/**
* Class to manage running an external server.
*/
public class ExternalServer {
private static final Logger logger = LogManager.getLogger(ExternalServer.class);
public static final String EXTERNAL_SERVER_PROPERTY_NAME = "yaml_testing_external_server";
private static final boolean SAVE_SERVER_OUTPUT = false;
@Nullable
private final File serverJar;
private final int grpcPort;
private final int httpPort;
private SemanticVersion version;
private Process serverProcess;
@Nullable
private final String clusterFile;
/**
* Create a new instance that will run a specific jar.
*
* @param serverJar the path to the jar to run
*/
public ExternalServer(@Nullable File serverJar, final int grpcPort, final int httpPort) {
this(serverJar, grpcPort, httpPort, null);
}
/**
* Create a new instance that will run a specific jar.
*
* @param serverJar the path to the jar to run
*/
public ExternalServer(@Nullable File serverJar, final int grpcPort, final int httpPort,
@Nullable final String clusterFile) {
this.serverJar = serverJar;
this.grpcPort = grpcPort;
this.httpPort = httpPort;
this.clusterFile = clusterFile;
}
static {
final String serverPath = Objects.requireNonNull(System.getProperty(EXTERNAL_SERVER_PROPERTY_NAME));
// kill all existing processes
ProcessHandle.allProcesses().filter(process ->
process.info().arguments().map(arguments ->
Arrays.stream(arguments).anyMatch(argument ->
argument.startsWith(serverPath)))
.orElse(false) &&
process.info().command().map(command ->
command.endsWith("/java"))
.orElse(false))
.forEach(process -> {
if (logger.isInfoEnabled()) {
logger.info("Killing existing server: pid=" + process.pid() + " " + process.info());
}
process.destroy();
});
}
/**
* Get the port to use when connecting.
*
* @return the grpc port that the server is listening to
*/
public int getPort() {
return grpcPort;
}
/**
* Get the version of the server.
*
* @return the version of the server being run.
*/
public SemanticVersion getVersion() {
return version;
}
public void start() throws Exception {
File jar;
if (serverJar == null) {
final List externalServers = getAvailableServers();
Assertions.assertEquals(1, externalServers.size());
jar = externalServers.get(0);
} else {
jar = serverJar;
}
Assertions.assertTrue(jar.exists(), "Jar could not be found " + jar.getAbsolutePath());
this.version = getVersion(jar);
ProcessBuilder processBuilder = new ProcessBuilder("java",
// TODO add support for debugging by adding, but need to take care with ports
// "-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n",
"-jar", jar.getAbsolutePath(),
"--grpcPort", Integer.toString(grpcPort), "--httpPort", Integer.toString(httpPort));
ProcessBuilder.Redirect out = SAVE_SERVER_OUTPUT ?
ProcessBuilder.Redirect.to(File.createTempFile("JdbcServerOut-" + grpcPort, ".log")) :
ProcessBuilder.Redirect.DISCARD;
ProcessBuilder.Redirect err = SAVE_SERVER_OUTPUT ?
ProcessBuilder.Redirect.to(File.createTempFile("JdbcServerErr-" + grpcPort, ".log")) :
ProcessBuilder.Redirect.DISCARD;
processBuilder.redirectOutput(out);
processBuilder.redirectError(err);
if (clusterFile != null) {
processBuilder.environment().put("FDB_CLUSTER_FILE", clusterFile);
}
if (!startServer(processBuilder)) {
Assertions.fail("Failed to start the external server");
}
logger.info("Started {} Version: {}", jar, version);
}
/**
* Get a list of available servers in the download folder.
*
* @return a list of jar {@link File}s available to run
*/
public static List getAvailableServers() {
final File externalDirectory = new File(Objects.requireNonNull(System.getProperty(EXTERNAL_SERVER_PROPERTY_NAME)));
final File[] externalServers = Objects.requireNonNull(externalDirectory.listFiles(file -> file.getName().endsWith(".jar")));
return List.of(externalServers);
}
private static SemanticVersion getVersion(File jar) throws IOException {
try (JarFile jarFile = new JarFile(jar)) {
final Manifest manifest = jarFile.getManifest();
final Attributes mainAttributes = manifest.getMainAttributes();
String version = mainAttributes.getValue("Specification-Version");
if (version != null) {
if (version.equals(BuildVersion.getInstance().getVersion())) {
// One of the external servers is locally built. In order for conditional execution
// in the test assertions to be executed correctly, it needs to be registered
// as such.
//
// Ideally, it would be nice if the two versions aligned, potentially by having
// SemanticVersion.current() return a version based on the BuildVersion.
// See: https://github.com/FoundationDB/fdb-record-layer/issues/3208 for more details
return SemanticVersion.current();
} else {
return SemanticVersion.parse(version);
}
} else {
return Assertions.fail("Server does not specify a version in the manifest: " + jar.getAbsolutePath());
}
}
}
private boolean startServer(ProcessBuilder processBuilder) throws IOException, InterruptedException {
try {
serverProcess = processBuilder.start();
// TODO: There should be a better way to figure out that the server is fully up and running
Thread.sleep(3000);
if (!serverProcess.isAlive()) {
throw new Exception("Failed to start server once - retrying");
}
return true;
} catch (Exception ex) {
// Try once more
serverProcess = processBuilder.start();
// TODO: There should be a better way to figure out that the server is fully up and running
Thread.sleep(3000);
}
return serverProcess.isAlive();
}
public void stop() {
if ((serverProcess != null) && serverProcess.isAlive()) {
serverProcess.destroy();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy