Skip to content

Commit

Permalink
Merge pull request #39588 from cescoffier/management-random-port
Browse files Browse the repository at this point in the history
  • Loading branch information
cescoffier authored Mar 22, 2024
2 parents 88887b3 + d0d5ef5 commit 64ddfe0
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 27 deletions.
21 changes: 21 additions & 0 deletions docs/src/main/asciidoc/management-interface-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,24 @@ quarkus.management.auth.permission.metrics.policy=authenticated
----

More details about Basic authentication in Quarkus can be found in the xref:security-basic-authentication-howto.adoc[Basic authentication guide].

== Injecting management URL in tests

When testing your application, you can inject the management URL using the `@TestHTTPResource` annotation:

[source,java]
----
@TestHTTPResource(value="/management", management=true)
URL management;
----

The `management` attribute is set to `true` to indicate that the injected URL is for the management interface.
The `context-root` is automatically added.
Thus, in the previous example, the injected URL is `http://localhost:9001/q/management`.

`@TestHTTPResource` is particularly useful when setting the management `test-port` to 0, which indicates that the system will assign a random port to the management interface:

[source, properties]
----]
quarkus.management.test-port=0
----
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package io.quarkus.vertx.http.management;

import java.net.URL;
import java.util.function.Consumer;

import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.builder.BuildChainBuilder;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.restassured.RestAssured;
import io.vertx.core.Handler;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;

public class ManagementAndPrimaryOnPortZeroTest {
private static final String APP_PROPS = """
quarkus.management.enabled=true
quarkus.management.test-port=0
quarkus.http.test-port=0
""";

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addAsResource(new StringAsset(APP_PROPS), "application.properties")
.addClasses(MyObserver.class))
.addBuildChainCustomizer(buildCustomizer());

static Consumer<BuildChainBuilder> buildCustomizer() {
return new Consumer<BuildChainBuilder>() {
@Override
public void accept(BuildChainBuilder builder) {
builder.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
NonApplicationRootPathBuildItem buildItem = context.consume(NonApplicationRootPathBuildItem.class);
context.produce(buildItem.routeBuilder()
.management()
.route("management")
.handler(new MyHandler())
.blockingRoute()
.build());
}
}).produces(RouteBuildItem.class)
.consumes(NonApplicationRootPathBuildItem.class)
.build();
}
};
}

public static class MyHandler implements Handler<RoutingContext> {
@Override
public void handle(RoutingContext routingContext) {
routingContext.response()
.setStatusCode(200)
.end("Hello management");
}
}

@TestHTTPResource(value = "/route")
URL url;

@TestHTTPResource(value = "/management", management = true)
URL management;

@ConfigProperty(name = "quarkus.management.test-port")
int managementPort;

@ConfigProperty(name = "quarkus.http.test-port")
int primaryPort;

@Test
public void test() {
Assertions.assertNotEquals(url.getPort(), management.getPort());
Assertions.assertEquals(url.getPort(), primaryPort);
Assertions.assertEquals(management.getPort(), managementPort);

for (int i = 0; i < 10; i++) {
RestAssured.given().get(url.toExternalForm()).then().body(Matchers.is("Hello primary"));
}

for (int i = 0; i < 10; i++) {
RestAssured.given().get(management.toExternalForm()).then().body(Matchers.is("Hello management"));
}

}

@Singleton
static class MyObserver {

void register(@Observes Router router) {
router.get("/route").handler(rc -> rc.response().end("Hello primary"));
}

void test(@Observes String event) {
//Do Nothing
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
import io.vertx.ext.web.RoutingContext;

public class ManagementAndRootPathTest {
private static final String APP_PROPS = "" +
"quarkus.management.enabled=true\n" +
"quarkus.management.root-path=/management\n";
private static final String APP_PROPS = """
quarkus.management.enabled=true
quarkus.management.root-path=/management
""";

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
package io.quarkus.vertx.http.runtime;

import static io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle.setContextSafe;
import static io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils.RANDOM_PORT_MAIN_HTTP;
import static io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils.RANDOM_PORT_MANAGEMENT;
import static io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils.getInsecureRequestStrategy;

import java.io.File;
import java.io.IOException;
import java.net.BindException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.*;
import java.util.regex.Pattern;
Expand All @@ -35,7 +48,12 @@
import io.quarkus.netty.runtime.virtual.VirtualAddress;
import io.quarkus.netty.runtime.virtual.VirtualChannel;
import io.quarkus.netty.runtime.virtual.VirtualServerChannel;
import io.quarkus.runtime.*;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.LiveReloadConfig;
import io.quarkus.runtime.QuarkusBindException;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.ShutdownContext;
import io.quarkus.runtime.ThreadPoolConfig;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.ConfigInstantiator;
import io.quarkus.runtime.configuration.ConfigUtils;
Expand Down Expand Up @@ -64,6 +82,7 @@
import io.smallrye.common.vertx.VertxContext;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.AsyncResult;
import io.vertx.core.Closeable;
import io.vertx.core.Context;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Handler;
Expand Down Expand Up @@ -172,6 +191,7 @@ private boolean uriValid(HttpServerRequest httpServerRequest) {
private static HttpServerOptions httpManagementServerOptions;

private static final List<Long> refresTaskIds = new CopyOnWriteArrayList<>();

final HttpBuildTimeConfig httpBuildTimeConfig;
final ManagementInterfaceBuildTimeConfig managementBuildTimeConfig;
final RuntimeValue<HttpConfiguration> httpConfiguration;
Expand Down Expand Up @@ -629,7 +649,6 @@ private static CompletableFuture<HttpServer> initializeManagementInterface(Vertx
}

if (httpManagementServerOptions != null) {

vertx.createHttpServer(httpManagementServerOptions)
.requestHandler(managementRouter)
.listen(ar -> {
Expand All @@ -647,9 +666,21 @@ private static CompletableFuture<HttpServer> initializeManagementInterface(Vertx
}

actualManagementPort = ar.result().actualPort();
if (actualManagementPort != httpManagementServerOptions.getPort()) {
var managementPortSystemProperties = new PortSystemProperties();
managementPortSystemProperties.set("management", actualManagementPort, launchMode);
((VertxInternal) vertx).addCloseHook(new Closeable() {
@Override
public void close(Promise<Void> completion) {
managementPortSystemProperties.restore();
completion.complete();
}
});
}
managementInterfaceFuture.complete(ar.result());
}
});

} else {
managementInterfaceFuture.complete(null);
}
Expand All @@ -672,8 +703,7 @@ private static CompletableFuture<String> initializeMainHttpServer(Vertx vertx, H
httpMainDomainSocketOptions = createDomainSocketOptions(httpBuildTimeConfig, httpConfiguration,
websocketSubProtocols);
HttpServerOptions tmpSslConfig = HttpServerOptionsUtils.createSslOptions(httpBuildTimeConfig, httpConfiguration,
launchMode,
websocketSubProtocols);
launchMode, websocketSubProtocols);

// Customize
if (Arc.container() != null) {
Expand Down Expand Up @@ -891,7 +921,7 @@ private static void setHttpServerTiming(boolean httpDisabled, HttpServerOptions
if (managementConfig != null) {
serverListeningMessage.append(
String.format(". Management interface listening on http%s://%s:%s.", managementConfig.isSsl() ? "s" : "",
managementConfig.getHost(), managementConfig.getPort()));
managementConfig.getHost(), actualManagementPort));
}

Timing.setHttpServer(serverListeningMessage.toString(), auxiliaryApplication);
Expand All @@ -906,7 +936,7 @@ private static HttpServerOptions createHttpServerOptions(
// TODO other config properties
HttpServerOptions options = new HttpServerOptions();
int port = httpConfiguration.determinePort(launchMode);
options.setPort(port == 0 ? -1 : port);
options.setPort(port == 0 ? RANDOM_PORT_MAIN_HTTP : port);

HttpServerOptionsUtils.applyCommonOptions(options, buildTimeConfig, httpConfiguration, websocketSubProtocols);

Expand All @@ -921,7 +951,7 @@ private static HttpServerOptions createHttpServerOptionsForManagementInterface(
}
HttpServerOptions options = new HttpServerOptions();
int port = httpConfiguration.determinePort(launchMode);
options.setPort(port == 0 ? -1 : port);
options.setPort(port == 0 ? RANDOM_PORT_MANAGEMENT : port);

HttpServerOptionsUtils.applyCommonOptionsForManagementInterface(options, buildTimeConfig, httpConfiguration,
websocketSubProtocols);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@
@SuppressWarnings("OptionalIsPresent")
public class HttpServerOptionsUtils {

/**
* When the http port is set to 0, replace it by this value to let Vert.x choose a random port
*/
public static final int RANDOM_PORT_MAIN_HTTP = -1;

/**
* When the https port is set to 0, replace it by this value to let Vert.x choose a random port
*/
public static final int RANDOM_PORT_MAIN_TLS = -2;

/**
* When the management port is set to 0, replace it by this value to let Vert.x choose a random port
*/
public static final int RANDOM_PORT_MANAGEMENT = -3;

/**
* Get an {@code HttpServerOptions} for this server configuration, or null if SSL should not be enabled
*/
Expand Down Expand Up @@ -108,7 +123,7 @@ public static HttpServerOptions createSslOptions(HttpBuildTimeConfig buildTimeCo
serverOptions.setSni(sslConfig.sni);
int sslPort = httpConfiguration.determineSslPort(launchMode);
// -2 instead of -1 (see http) to have vert.x assign two different random ports if both http and https shall be random
serverOptions.setPort(sslPort == 0 ? -2 : sslPort);
serverOptions.setPort(sslPort == 0 ? RANDOM_PORT_MAIN_TLS : sslPort);
serverOptions.setClientAuth(buildTimeConfig.tlsClientAuth);

applyCommonOptions(serverOptions, buildTimeConfig, httpConfiguration, websocketSubProtocols);
Expand Down Expand Up @@ -194,8 +209,8 @@ public static HttpServerOptions createSslOptionsForManagementInterface(Managemen
serverOptions.setSsl(true);
serverOptions.setSni(sslConfig.sni);
int sslPort = httpConfiguration.determinePort(launchMode);
// -2 instead of -1 (see http) to have vert.x assign two different random ports if both http and https shall be random
serverOptions.setPort(sslPort == 0 ? -2 : sslPort);

serverOptions.setPort(sslPort == 0 ? RANDOM_PORT_MANAGEMENT : sslPort);
serverOptions.setClientAuth(buildTimeConfig.tlsClientAuth);

applyCommonOptionsForManagementInterface(serverOptions, buildTimeConfig, httpConfiguration, websocketSubProtocols);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,20 @@ public class TestHTTPConfigSourceProvider implements ConfigSourceProvider {
static final String TEST_URL_VALUE = "http://${quarkus.http.host:localhost}:${quarkus.http.test-port:8081}${quarkus.http.root-path:${quarkus.servlet.context-path:}}";
static final String TEST_URL_KEY = "test.url";

static final String TEST_MANAGEMENT_URL_VALUE = "http://${quarkus.management.host:localhost}:${quarkus.management.test-port:9001}${quarkus.management.root-path:/q}";
static final String TEST_MANAGEMENT_URL_KEY = "test.management.url";

static final String TEST_URL_SSL_VALUE = "https://${quarkus.http.host:localhost}:${quarkus.http.test-ssl-port:8444}${quarkus.http.root-path:${quarkus.servlet.context-path:}}";
static final String TEST_URL_SSL_KEY = "test.url.ssl";

static final Map<String, String> entries = Map.of(TEST_URL_KEY, sanitizeURL(TEST_URL_VALUE),
static final String TEST_MANAGEMENT_URL_SSL_VALUE = "https://${quarkus.management.host:localhost}:${quarkus.management.test-ssl-port:9001}${quarkus.management.root-path:/q}";
static final String TEST_MANAGEMENT_URL_SSL_KEY = "test.management.url.ssl";

static final Map<String, String> entries = Map.of(
TEST_URL_KEY, sanitizeURL(TEST_URL_VALUE),
TEST_URL_SSL_KEY, sanitizeURL(TEST_URL_SSL_VALUE),
TEST_MANAGEMENT_URL_KEY, sanitizeURL(TEST_MANAGEMENT_URL_VALUE),
TEST_MANAGEMENT_URL_SSL_KEY, sanitizeURL(TEST_MANAGEMENT_URL_SSL_VALUE),
"%dev." + TEST_URL_KEY, sanitizeURL(
"http://${quarkus.http.host:localhost}:${quarkus.http.test-port:8080}${quarkus.http.root-path:${quarkus.servlet.context-path:}}"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,34 @@
/**
* Indicates that a field should be injected with a resource that is pre-configured
* to use the correct test URL.
*
* <p>
* This could be a String or URL object, or some other HTTP/Websocket based client.
*
* <p>
* This mechanism is pluggable, via {@link TestHTTPResourceProvider}
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TestHTTPResource {

/**
*
* @return The path part of the URL
*/
String value() default "";

/**
*
* @return If the URL should use the HTTPS protocol and SSL port
* @deprecated use #tls instead
*/
@Deprecated
boolean ssl() default false;

/**
* @return if the url should use the management interface
*/
boolean management() default false;

/**
* @return If the URL should use the HTTPS protocol and TLS port
*/
boolean tls() default false;
}
Loading

0 comments on commit 64ddfe0

Please sign in to comment.