Skip to content

Commit

Permalink
Assign random ports in Config
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez committed Sep 12, 2024
1 parent 0364ef8 commit bc85cc0
Show file tree
Hide file tree
Showing 22 changed files with 440 additions and 213 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ public boolean equals(Object obj) {
@Deprecated(forRemoval = true)
@JsonIgnore
public boolean isMixedModule() {
return "io.quarkus".equals(groupId) && ("quarkus-core".equals(artifactId) || "quarkus-messaging".equals(artifactId));
return "io.quarkus".equals(groupId) && ("quarkus-core".equals(artifactId) ||
"quarkus-vertx-http".equals(artifactId) ||
"quarkus-grpc".equals(artifactId) ||
"quarkus-messaging".equals(artifactId));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.runtime.configuration;

import static java.net.InetAddress.getByName;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

public class RandomPorts {
private static final Map<Integer, Integer> randomPorts = new HashMap<>();

public static int get(final String host, final int port) {
return randomPorts.computeIfAbsent(port, new Function<Integer, Integer>() {
@Override
public Integer apply(final Integer socketAddress) {
try (ServerSocket serverSocket = new ServerSocket(0, 0, getByName(host))) {
serverSocket.setReuseAddress(false);
return serverSocket.getLocalPort();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.RunTimeConfigBuilderBuildItem;
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
Expand All @@ -79,6 +80,7 @@
import io.quarkus.grpc.runtime.GrpcContainer;
import io.quarkus.grpc.runtime.GrpcServerRecorder;
import io.quarkus.grpc.runtime.ServerInterceptorStorage;
import io.quarkus.grpc.runtime.config.GrpcConfigBuilder;
import io.quarkus.grpc.runtime.config.GrpcConfiguration;
import io.quarkus.grpc.runtime.config.GrpcServerBuildTimeConfig;
import io.quarkus.grpc.runtime.health.GrpcHealthEndpoint;
Expand Down Expand Up @@ -106,6 +108,11 @@ public class GrpcServerProcessor {
private static final String KEY_STORE = SSL_PREFIX + "key-store";
private static final String TRUST_STORE = SSL_PREFIX + "trust-store";

@BuildStep
void config(BuildProducer<RunTimeConfigBuilderBuildItem> runtimeConfigBuilder) {
runtimeConfigBuilder.produce(new RunTimeConfigBuilderBuildItem(GrpcConfigBuilder.class));
}

@BuildStep
MinNettyAllocatorMaxOrderBuildItem setMinimalNettyMaxOrderSize() {
return new MinNettyAllocatorMaxOrderBuildItem(3);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.ShutdownContext;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.vertx.http.runtime.PortSystemProperties;
import io.quarkus.virtual.threads.VirtualThreadsRecorder;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.AsyncResult;
Expand Down Expand Up @@ -606,7 +605,6 @@ private class GrpcServerVerticle extends AbstractVerticle {
private final LaunchMode launchMode;
private final Map<String, List<String>> blockingMethodsPerService;
private final Map<String, List<String>> virtualMethodsPerService;
private volatile PortSystemProperties portSystemProperties;

private Server grpcServer;

Expand Down Expand Up @@ -645,15 +643,6 @@ public void start(Promise<Void> startPromise) {
}
startPromise.fail(effectiveCause);
} else {
try {
int actualPort = grpcServer.getPort();
if (actualPort != portToServer.getKey()) {
portSystemProperties = new PortSystemProperties();
portSystemProperties.set("grpc.server", actualPort, launchMode);
}
} catch (Exception e) {
// Ignore, port reused.
}
startPromise.complete();
grpcVerticleCount.incrementAndGet();
}
Expand All @@ -663,11 +652,6 @@ public void start(Promise<Void> startPromise) {
vertx.executeBlocking(() -> {
try {
grpcServer.start();
int actualPort = grpcServer.getPort();
if (actualPort != portToServer.getKey()) {
portSystemProperties = new PortSystemProperties();
portSystemProperties.set("grpc.server", actualPort, launchMode);
}
startPromise.complete();
} catch (Exception e) {
LOGGER.error("Unable to start gRPC server", e);
Expand All @@ -692,9 +676,6 @@ public void stop(Promise<Void> stopPromise) {
stopPromise.complete();
grpcVerticleCount.decrementAndGet();
}
if (portSystemProperties != null) {
portSystemProperties.restore();
}
});
} else {
try {
Expand All @@ -707,10 +688,6 @@ public void stop(Promise<Void> stopPromise) {
} catch (Exception e) {
LOGGER.errorf(e, "Unable to stop the gRPC server gracefully");
stopPromise.fail(e);
} finally {
if (portSystemProperties != null) {
portSystemProperties.restore();
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package io.quarkus.grpc.runtime.config;

import static io.quarkus.runtime.LaunchMode.TEST;
import static io.quarkus.runtime.annotations.ConfigPhase.RUN_TIME;

import java.util.Map;
import java.util.OptionalInt;

import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.annotations.ConfigDocIgnore;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.ConfigValue;
import io.smallrye.config.WithDefault;
import io.smallrye.config.WithName;
import io.smallrye.config.WithParentName;

@ConfigMapping(prefix = "quarkus.grpc")
@ConfigRoot(phase = RUN_TIME)
public interface GrpcConfig {
@ConfigDocIgnore
GrpcServer server();

@ConfigDocIgnore
Map<String, GrpcClient> clients();

@ConfigDocIgnore
@WithParentName
Map<String, String> properties();

interface GrpcServer {
@WithDefault("true")
boolean useSeparateServer();

@ConfigDocIgnore
@WithDefault("9000")
int port();

@ConfigDocIgnore
@WithDefault("9001")
int testPort();

default int determinePort(LaunchMode mode) {
return mode.equals(TEST) ? testPort() : port();
}

@ConfigDocIgnore
@WithDefault("0.0.0.0")
String host();

@ConfigDocIgnore
@WithDefault("true")
boolean plainText();

@ConfigDocIgnore
Map<String, String> ssl();

@ConfigDocIgnore
@WithParentName
Map<String, String> properties();
}

interface GrpcClient {
@ConfigDocIgnore
@WithDefault("9000")
int port();

/**
* A duplicate mapping of {@link GrpcClient#port()}, to retrieve the full configuration key. We may need
* the original property name to reassign the port, and because the client configuration contains a dynamic path
* segment, we can avoid reconstructing the original property name from the
* {@link GrpcConfig#clients()} <code>Map</code>.
*/
@ConfigDocIgnore
@WithName("port")
ConfigValue portName();

@ConfigDocIgnore
OptionalInt testPort();

/**
* A duplicate mapping of {@link GrpcClient#testPort()}, to retrieve the full configuration key. We may need
* the original property name to reassign the port, and because the client configuration contains a dynamic path
* segment, we can avoid reconstructing the original property name from the
* {@link GrpcConfig#clients()} <code>Map</code>.
*/
@ConfigDocIgnore
@WithName("test-port")
ConfigValue testPortName();

default int determinePort(LaunchMode mode, int defaultPort) {
return mode.equals(TEST) ? testPort().orElse(defaultPort) : port();
}

@ConfigDocIgnore
@WithDefault("localhost")
String host();

@ConfigDocIgnore
@WithParentName
Map<String, String> properties();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.quarkus.grpc.runtime.config;

import static io.quarkus.runtime.LaunchMode.TEST;
import static java.lang.Integer.MAX_VALUE;
import static java.util.Collections.emptyList;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.microprofile.config.spi.ConfigSource;

import io.quarkus.grpc.runtime.GrpcTestPortUtils;
import io.quarkus.grpc.runtime.config.GrpcConfig.GrpcClient;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ConfigBuilder;
import io.quarkus.runtime.configuration.RandomPorts;
import io.quarkus.vertx.http.runtime.HttpConfig;
import io.smallrye.config.ConfigSourceContext;
import io.smallrye.config.ConfigSourceFactory;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigBuilder;
import io.smallrye.config.common.MapBackedConfigSource;

public class GrpcConfigBuilder implements ConfigBuilder {
@Override
public SmallRyeConfigBuilder configBuilder(final SmallRyeConfigBuilder builder) {
return builder.withSources(new RandomPortConfigSourceFactory());
}

private static class RandomPortConfigSourceFactory implements ConfigSourceFactory {
@Override
public Iterable<ConfigSource> getConfigSources(final ConfigSourceContext context) {
Map<String, String> randomPorts = new HashMap<>();

SmallRyeConfig config = new SmallRyeConfigBuilder()
.withSources(new ConfigSourceContext.ConfigSourceContextConfigSource(context))
.withSources(context.getConfigSources())
.withMapping(HttpConfig.class)
.withMapping(GrpcConfig.class)
.build();

GrpcConfig grpcConfig = config.getConfigMapping(GrpcConfig.class);

int port = grpcConfig.server().determinePort(LaunchMode.current());
if (port <= 0) {
String randomPort = RandomPorts.get(grpcConfig.server().host(), port) + "";
randomPorts.put("quarkus.grpc.server.port", randomPort);
if (LaunchMode.current().equals(TEST)) {
randomPorts.put("quarkus.grpc.server.test-port", randomPort);
}
}

HttpConfig httpConfig = config.getConfigMapping(HttpConfig.class);
for (Map.Entry<String, GrpcClient> client : grpcConfig.clients().entrySet()) {
int clientPort = client.getValue().determinePort(LaunchMode.current(),
testPort(grpcConfig.server(), httpConfig));
if (clientPort <= 0) {
String randomPort = RandomPorts.get(client.getValue().host(), clientPort) + "";
randomPorts.put(client.getValue().portName().getName(), randomPort);
if (LaunchMode.current().equals(TEST)) {
randomPorts.put(client.getValue().testPortName().getName(), randomPort);
}
}
}

return randomPorts.isEmpty() ? emptyList()
: List.of(new MapBackedConfigSource("Quarkus GRPC Random Ports", randomPorts, MAX_VALUE - 1000) {
});
}
}

/**
* Mostly a copy of {@link GrpcTestPortUtils#testPort(GrpcServerConfiguration)}, with some changes, because it
* seems that the original implementation returns the same value in different if branches.
* <p>
* Still, we should validate if this is really what we want to do.
*/
private static int testPort(GrpcConfig.GrpcServer server, HttpConfig httpConfig) {
if (server.useSeparateServer()) {
return server.testPort();
}

if (!server.ssl().isEmpty() || !server.plainText()) {
return httpConfig.determineSslPort(TEST);
}

return httpConfig.determinePort(TEST);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.util.Map;
import java.util.Optional;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.rest.client.ext.QueryParamStyle;

import io.quarkus.runtime.annotations.ConfigDocDefault;
Expand All @@ -13,11 +12,8 @@
import io.quarkus.runtime.annotations.ConfigRoot;
import io.quarkus.runtime.configuration.MemorySize;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.ConfigValue;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.WithDefault;
import io.smallrye.config.WithDefaults;
import io.smallrye.config.WithName;
import io.smallrye.config.WithParentName;

@ConfigMapping(prefix = "quarkus.rest-client")
Expand Down Expand Up @@ -373,40 +369,12 @@ interface RestClientConfig {
*/
Optional<String> url();

/**
* Duplicate mapping of {@link RestClientConfig#url()} to keep a reference of the name used to retrieve the
* <code>url</code>. We need this to reload the <code>url</code> configuration in case it contains an expression
* to <code>${quarkus.http.port}</code>, which is only set after we load the config.
*/
@ConfigDocIgnore
@WithName("url")
ConfigValue urlValue();

default Optional<String> urlReload() {
SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
return config.getOptionalValue(urlValue().getName(), String.class);
}

/**
* The base URI to use for this service. This property or the `url` property is considered required, unless
* the `baseUri` attribute is configured in the `@RegisterRestClient` annotation.
*/
Optional<String> uri();

/**
* Duplicate mapping of {@link RestClientConfig#uri()} to keep a reference of the name used to retrieve the
* <code>uri</code>. We need this to reload the <code>uri</code> configuration in case it contains an expression
* to <code>${quarkus.http.port}</code>, which is only set after we load the config.
*/
@ConfigDocIgnore
@WithName("uri")
ConfigValue uriValue();

default Optional<String> uriReload() {
SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
return config.getOptionalValue(uriValue().getName(), String.class);
}

/**
* This property is only meant to be set by advanced configurations to override whatever value was set for the uri or
* url.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ class RestClientRandomPortTest {
void config() {
RestClientConfig echoClientConfig = restClientsConfig.getClient(EchoClient.class);
assertTrue(echoClientConfig.url().isPresent());
assertEquals("http://localhost:0", echoClientConfig.url().get());
assertNotEquals("http://localhost:0", echoClientConfig.urlReload());
assertNotEquals("http://localhost:0", echoClientConfig.url().get());
}

@Test
Expand Down
Loading

0 comments on commit bc85cc0

Please sign in to comment.