diff --git a/README.md b/README.md
index 3cece90f4..fb3f68a08 100644
--- a/README.md
+++ b/README.md
@@ -349,6 +349,13 @@ Exclusions: XML test. Reason: https://quarkus.io/blog/resteasy-reactive/#what-ja
Verifies HTTP endpoints validation using `quarkus-hibernate-validator` works correctly in Resteasy Classic and Resteasy Reactive.
This module will setup a simple endpoint and will validate the right message format is set when there are validation errors.
+### `http/vertx`
+Verifies that you can deploy a simple Vert.X-based HTTP endpoint to OpenShift, access it and retrieve metrics for it.
+It also verifies multiple deployment strategies like:
+- Serverless
+- Using OpenShift quarkus extension
+- Using OpenShift quarkus extension and Docker Build strategy
+
#### Additions
* *@Deprecated* annotation has been added for test regression purposes to ensure `java.lang` annotations are allowed for resources
* Resource with multipart body support, provided parts are text, image and binary data, charset checked with `us-ascii` and `utf-8`
diff --git a/http/vertx/pom.xml b/http/vertx/pom.xml
new file mode 100644
index 000000000..1d2addb2f
--- /dev/null
+++ b/http/vertx/pom.xml
@@ -0,0 +1,38 @@
+
+
+ 4.0.0
+
+ io.quarkus.ts.qe
+ parent
+ 1.0.0-SNAPSHOT
+ ../..
+
+ vertx
+ jar
+ Quarkus QE TS: HTTP: Vert.X
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-jackson
+
+
+ io.quarkus
+ quarkus-vertx
+
+
+ io.quarkus
+ quarkus-micrometer-registry-prometheus
+
+
+
+
+ deploy-to-openshift-using-extension
+
+
+ io.quarkus
+ quarkus-openshift
+
+
+
+
+
diff --git a/http/vertx/src/main/java/io/quarkus/ts/vertx/Hello.java b/http/vertx/src/main/java/io/quarkus/ts/vertx/Hello.java
new file mode 100644
index 000000000..8560f7554
--- /dev/null
+++ b/http/vertx/src/main/java/io/quarkus/ts/vertx/Hello.java
@@ -0,0 +1,14 @@
+package io.quarkus.ts.vertx;
+
+public class Hello {
+
+ private final String content;
+
+ public Hello(String content) {
+ this.content = content;
+ }
+
+ public String getContent() {
+ return content;
+ }
+}
diff --git a/http/vertx/src/main/java/io/quarkus/ts/vertx/HelloResource.java b/http/vertx/src/main/java/io/quarkus/ts/vertx/HelloResource.java
new file mode 100644
index 000000000..f65db94cc
--- /dev/null
+++ b/http/vertx/src/main/java/io/quarkus/ts/vertx/HelloResource.java
@@ -0,0 +1,33 @@
+package io.quarkus.ts.vertx;
+
+import javax.inject.Inject;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import io.smallrye.mutiny.Uni;
+import io.vertx.mutiny.core.Vertx;
+import io.vertx.mutiny.core.buffer.Buffer;
+
+@Path("/hello")
+public class HelloResource {
+ private final Vertx vertx;
+
+ @Inject
+ public HelloResource(Vertx vertx) {
+ this.vertx = vertx;
+ }
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public Uni getMessage(@QueryParam("name") @DefaultValue("World") String name) {
+ return vertx.fileSystem()
+ .readFile("message.template")
+ .map(Buffer::toString)
+ .map(string -> String.format(string, name).trim())
+ .map(Hello::new);
+ }
+}
diff --git a/http/vertx/src/main/resources/application.properties b/http/vertx/src/main/resources/application.properties
new file mode 100644
index 000000000..d8b77f2fe
--- /dev/null
+++ b/http/vertx/src/main/resources/application.properties
@@ -0,0 +1,6 @@
+quarkus.application.name=test-http
+quarkus.native.resources.includes=message.template
+%ServerlessExtensionOpenShiftHttpMinimumIT.quarkus.kubernetes.deployment-target=knative
+%ServerlessExtensionOpenShiftHttpMinimumIT.quarkus.container-image.registry=image-registry.openshift-image-registry.svc:5000
+%ServerlessExtensionDockerBuildStrategyOpenShiftHttpMinimumIT.quarkus.kubernetes.deployment-target=knative
+%ServerlessExtensionDockerBuildStrategyOpenShiftHttpMinimumIT.quarkus.container-image.registry=image-registry.openshift-image-registry.svc:5000
diff --git a/http/vertx/src/main/resources/message.template b/http/vertx/src/main/resources/message.template
new file mode 100644
index 000000000..6807eef81
--- /dev/null
+++ b/http/vertx/src/main/resources/message.template
@@ -0,0 +1 @@
+Hello, %s!
diff --git a/http/vertx/src/test/java/io/quarkus/ts/vertx/AbstractVertxIT.java b/http/vertx/src/test/java/io/quarkus/ts/vertx/AbstractVertxIT.java
new file mode 100644
index 000000000..196b1e15e
--- /dev/null
+++ b/http/vertx/src/test/java/io/quarkus/ts/vertx/AbstractVertxIT.java
@@ -0,0 +1,121 @@
+package io.quarkus.ts.vertx;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.testcontainers.shaded.org.hamcrest.MatcherAssert.assertThat;
+import static org.testcontainers.shaded.org.hamcrest.Matchers.containsString;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+
+import io.restassured.response.Response;
+import io.restassured.specification.RequestSpecification;
+
+public abstract class AbstractVertxIT {
+
+ @Test
+ public void httpServerAndMetrics() {
+ requests().get("/hello").then()
+ .statusCode(HttpStatus.SC_OK)
+ .body("content", is("Hello, World!"));
+
+ Response response = requests().get("/q/metrics");
+ assertEquals(HttpStatus.SC_OK, response.statusCode());
+ assertThat(response.getContentType(), containsString("text/plain"));
+ String body = response.body().asString();
+ Map metrics = parseMetrics(body);
+ assertTrue(metrics.containsKey("worker_pool_active"));
+ assertTrue(metrics.containsKey("worker_pool_completed_total"));
+ assertTrue(metrics.containsKey("worker_pool_queue_size"));
+ }
+
+ @Test
+ public void httpServerParsing() {
+ requests().get("/hello?name=you").then().statusCode(HttpStatus.SC_OK).body("content", is("Hello, you!"));
+ }
+
+ public abstract RequestSpecification requests();
+
+ private Map parseMetrics(String body) {
+ Map metrics = new HashMap<>(128);
+ Arrays.stream(body.split("\n"))
+ .filter(line -> !line.startsWith("#"))
+ .map(Metric::new)
+ .forEach(metric -> metrics.put(metric.name, metric));
+ return metrics;
+ }
+
+ private class Metric {
+ private final String value;
+ private final String name;
+
+ /**
+ *
+ * @param source metric from the file, eg:
+ * worker_pool_queue_size{pool_name="vert.x-internal-blocking",pool_type="worker"} 0.0
+ * content in curly brackets is ignored (for now)
+ * since we do not care about values, we store them as strings, and ignore duplicated keys.
+ */
+ public Metric(String source) {
+ final int DEFAULT = -1;
+ int space = DEFAULT;
+ int closing = DEFAULT;
+ int opening = DEFAULT;
+ byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
+ for (int i = bytes.length - 1; i >= 0; i--) {
+ byte current = bytes[i];
+ if (current == ' ' && space == DEFAULT) {
+ space = i;
+ }
+ if (current == '}' && closing == DEFAULT) {
+ closing = i;
+ }
+ if (current == '{' && opening == DEFAULT) {
+ opening = i;
+ }
+ }
+ String key;
+ if (space > 0) {
+ value = source.substring(space);
+ key = source.substring(0, space);
+ } else {
+ throw new IllegalArgumentException("Metric " + source + " doesn't contain a value");
+ }
+ if (closing < space && opening < closing && opening > 0) {
+ name = source.substring(0, opening);
+ } else {
+ name = key;
+ }
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Metric metric = (Metric) o;
+ return value.equals(metric.value) && name.equals(metric.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value, name);
+ }
+ }
+}
diff --git a/http/vertx/src/test/java/io/quarkus/ts/vertx/LocalVertxIT.java b/http/vertx/src/test/java/io/quarkus/ts/vertx/LocalVertxIT.java
new file mode 100644
index 000000000..f69279815
--- /dev/null
+++ b/http/vertx/src/test/java/io/quarkus/ts/vertx/LocalVertxIT.java
@@ -0,0 +1,18 @@
+package io.quarkus.ts.vertx;
+
+import io.quarkus.test.bootstrap.RestService;
+import io.quarkus.test.scenarios.QuarkusScenario;
+import io.quarkus.test.services.QuarkusApplication;
+import io.restassured.specification.RequestSpecification;
+
+@QuarkusScenario
+public class LocalVertxIT extends AbstractVertxIT {
+
+ @QuarkusApplication
+ static RestService app = new RestService();
+
+ @Override
+ public RequestSpecification requests() {
+ return app.given();
+ }
+}
diff --git a/http/vertx/src/test/java/io/quarkus/ts/vertx/OpenShiftUsingExtensionDockerBuildStrategyVertxIT.java b/http/vertx/src/test/java/io/quarkus/ts/vertx/OpenShiftUsingExtensionDockerBuildStrategyVertxIT.java
new file mode 100644
index 000000000..798fa7291
--- /dev/null
+++ b/http/vertx/src/test/java/io/quarkus/ts/vertx/OpenShiftUsingExtensionDockerBuildStrategyVertxIT.java
@@ -0,0 +1,21 @@
+package io.quarkus.ts.vertx;
+
+import org.junit.jupiter.api.Tag;
+
+import io.quarkus.test.bootstrap.RestService;
+import io.quarkus.test.scenarios.OpenShiftDeploymentStrategy;
+import io.quarkus.test.scenarios.OpenShiftScenario;
+import io.quarkus.test.services.QuarkusApplication;
+import io.restassured.specification.RequestSpecification;
+
+@Tag("use-quarkus-openshift-extension")
+@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingOpenShiftExtensionAndDockerBuildStrategy)
+public class OpenShiftUsingExtensionDockerBuildStrategyVertxIT extends AbstractVertxIT {
+ @QuarkusApplication
+ static RestService app = new RestService();
+
+ @Override
+ public RequestSpecification requests() {
+ return app.given();
+ }
+}
diff --git a/http/vertx/src/test/java/io/quarkus/ts/vertx/OpenShiftUsingExtensionVertxIT.java b/http/vertx/src/test/java/io/quarkus/ts/vertx/OpenShiftUsingExtensionVertxIT.java
new file mode 100644
index 000000000..627a99211
--- /dev/null
+++ b/http/vertx/src/test/java/io/quarkus/ts/vertx/OpenShiftUsingExtensionVertxIT.java
@@ -0,0 +1,21 @@
+package io.quarkus.ts.vertx;
+
+import org.junit.jupiter.api.Tag;
+
+import io.quarkus.test.bootstrap.RestService;
+import io.quarkus.test.scenarios.OpenShiftDeploymentStrategy;
+import io.quarkus.test.scenarios.OpenShiftScenario;
+import io.quarkus.test.services.QuarkusApplication;
+import io.restassured.specification.RequestSpecification;
+
+@Tag("use-quarkus-openshift-extension")
+@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingOpenShiftExtension)
+public class OpenShiftUsingExtensionVertxIT extends AbstractVertxIT {
+ @QuarkusApplication
+ static RestService app = new RestService();
+
+ @Override
+ public RequestSpecification requests() {
+ return app.given();
+ }
+}
diff --git a/http/vertx/src/test/java/io/quarkus/ts/vertx/OpenShiftVertxIT.java b/http/vertx/src/test/java/io/quarkus/ts/vertx/OpenShiftVertxIT.java
new file mode 100644
index 000000000..5206d96a2
--- /dev/null
+++ b/http/vertx/src/test/java/io/quarkus/ts/vertx/OpenShiftVertxIT.java
@@ -0,0 +1,17 @@
+package io.quarkus.ts.vertx;
+
+import io.quarkus.test.bootstrap.RestService;
+import io.quarkus.test.scenarios.OpenShiftScenario;
+import io.quarkus.test.services.QuarkusApplication;
+import io.restassured.specification.RequestSpecification;
+
+@OpenShiftScenario
+public class OpenShiftVertxIT extends AbstractVertxIT {
+ @QuarkusApplication
+ static RestService app = new RestService();
+
+ @Override
+ public RequestSpecification requests() {
+ return app.given();
+ }
+}
diff --git a/http/vertx/src/test/java/io/quarkus/ts/vertx/ServerlessExtensionDockerBuildStrategyOpenShiftVertxIT.java b/http/vertx/src/test/java/io/quarkus/ts/vertx/ServerlessExtensionDockerBuildStrategyOpenShiftVertxIT.java
new file mode 100644
index 000000000..6da0b51c2
--- /dev/null
+++ b/http/vertx/src/test/java/io/quarkus/ts/vertx/ServerlessExtensionDockerBuildStrategyOpenShiftVertxIT.java
@@ -0,0 +1,22 @@
+package io.quarkus.ts.vertx;
+
+import org.junit.jupiter.api.Tag;
+
+import io.quarkus.test.bootstrap.RestService;
+import io.quarkus.test.scenarios.OpenShiftDeploymentStrategy;
+import io.quarkus.test.scenarios.OpenShiftScenario;
+import io.quarkus.test.services.QuarkusApplication;
+import io.restassured.specification.RequestSpecification;
+
+@Tag("use-quarkus-openshift-extension")
+@Tag("serverless")
+@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingOpenShiftExtensionAndDockerBuildStrategy)
+public class ServerlessExtensionDockerBuildStrategyOpenShiftVertxIT extends AbstractVertxIT {
+ @QuarkusApplication
+ static RestService app = new RestService();
+
+ @Override
+ public RequestSpecification requests() {
+ return app.given().relaxedHTTPSValidation();
+ }
+}
diff --git a/http/vertx/src/test/java/io/quarkus/ts/vertx/ServerlessExtensionOpenShiftVertxIT.java b/http/vertx/src/test/java/io/quarkus/ts/vertx/ServerlessExtensionOpenShiftVertxIT.java
new file mode 100644
index 000000000..fdb7acd7b
--- /dev/null
+++ b/http/vertx/src/test/java/io/quarkus/ts/vertx/ServerlessExtensionOpenShiftVertxIT.java
@@ -0,0 +1,22 @@
+package io.quarkus.ts.vertx;
+
+import org.junit.jupiter.api.Tag;
+
+import io.quarkus.test.bootstrap.RestService;
+import io.quarkus.test.scenarios.OpenShiftDeploymentStrategy;
+import io.quarkus.test.scenarios.OpenShiftScenario;
+import io.quarkus.test.services.QuarkusApplication;
+import io.restassured.specification.RequestSpecification;
+
+@Tag("use-quarkus-openshift-extension")
+@Tag("serverless")
+@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingOpenShiftExtension)
+public class ServerlessExtensionOpenShiftVertxIT extends AbstractVertxIT {
+ @QuarkusApplication
+ static RestService app = new RestService();
+
+ @Override
+ public RequestSpecification requests() {
+ return app.given().relaxedHTTPSValidation();
+ }
+}
diff --git a/pom.xml b/pom.xml
index cd670a504..e43d26089 100644
--- a/pom.xml
+++ b/pom.xml
@@ -443,6 +443,7 @@
http/hibernate-validator
http/graphql
http/graphql-telemetry
+ http/vertx