From c03c43be7c5555e49ce8f2a1622f48bc12113cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Wed, 26 Jun 2024 22:36:51 +0200 Subject: [PATCH] Make Full MP app more real & address eng feedback --- app-full-microprofile/pom.xml | 12 +- .../quarkus/metric/MetricController.java | 33 ++-- .../example/quarkus/secure/MPJWTToken.java | 162 ------------------ .../quarkus/secure/ProtectedController.java | 27 --- .../quarkus/secure/TestSecureController.java | 82 --------- .../quarkus/serialization/JsonResource.java | 26 +++ .../quarkus/serialization/dto/ComplexDto.java | 4 + .../quarkus/serialization/dto/MyEnum.java | 7 + .../serialization/dto/NestedClass.java | 15 ++ .../serialization/dto/NestedInterface.java | 35 ++++ .../serialization/dto/NestedRecord.java | 9 + .../META-INF/resources/publicKey.pem | 9 - .../src/main/resources/application.properties | 12 +- .../src/main/resources/privateKey.pem | 28 --- pom.xml | 2 + testsuite/pom.xml | 7 + .../quarkus/ts/startstop/StartStopTest.java | 15 +- .../utils/OpenTelemetryCollector.java | 99 +++++++++++ .../ts/startstop/utils/URLContent.java | 17 +- .../ts/startstop/utils/UnitTestResource.java | 27 +++ .../ts/startstop/utils/WhitelistLogLines.java | 5 +- 21 files changed, 280 insertions(+), 353 deletions(-) delete mode 100644 app-full-microprofile/src/main/java/com/example/quarkus/secure/MPJWTToken.java delete mode 100644 app-full-microprofile/src/main/java/com/example/quarkus/secure/ProtectedController.java delete mode 100644 app-full-microprofile/src/main/java/com/example/quarkus/secure/TestSecureController.java create mode 100644 app-full-microprofile/src/main/java/com/example/quarkus/serialization/JsonResource.java create mode 100644 app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/ComplexDto.java create mode 100644 app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/MyEnum.java create mode 100644 app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/NestedClass.java create mode 100644 app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/NestedInterface.java create mode 100644 app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/NestedRecord.java delete mode 100644 app-full-microprofile/src/main/resources/META-INF/resources/publicKey.pem delete mode 100644 app-full-microprofile/src/main/resources/privateKey.pem create mode 100644 testsuite/src/it/java/io/quarkus/ts/startstop/utils/OpenTelemetryCollector.java create mode 100644 testsuite/src/it/java/io/quarkus/ts/startstop/utils/UnitTestResource.java diff --git a/app-full-microprofile/pom.xml b/app-full-microprofile/pom.xml index 0eea8e28..1d090c91 100644 --- a/app-full-microprofile/pom.xml +++ b/app-full-microprofile/pom.xml @@ -20,13 +20,9 @@ - - io.vertx - vertx-auth-jwt - io.quarkus - quarkus-rest + quarkus-rest-jackson io.quarkus @@ -34,11 +30,7 @@ io.quarkus - quarkus-smallrye-jwt - - - io.quarkus - quarkus-smallrye-metrics + quarkus-micrometer-registry-prometheus io.quarkus diff --git a/app-full-microprofile/src/main/java/com/example/quarkus/metric/MetricController.java b/app-full-microprofile/src/main/java/com/example/quarkus/metric/MetricController.java index 8ebfdfd4..eb35fb2c 100644 --- a/app-full-microprofile/src/main/java/com/example/quarkus/metric/MetricController.java +++ b/app-full-microprofile/src/main/java/com/example/quarkus/metric/MetricController.java @@ -2,14 +2,10 @@ import java.util.Random; -import org.eclipse.microprofile.metrics.Counter; -import org.eclipse.microprofile.metrics.MetricUnits; -import org.eclipse.microprofile.metrics.annotation.Gauge; -import org.eclipse.microprofile.metrics.annotation.Metric; -import org.eclipse.microprofile.metrics.annotation.Timed; - +import io.micrometer.core.annotation.Timed; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -17,15 +13,15 @@ @ApplicationScoped //Required for @Gauge public class MetricController { - @Inject - @Metric(name = "endpoint_counter") + private final MeterRegistry registry; - // https://quarkus.io/guides/cdi-reference#private-members - // - private Counter counter; - Counter counter; + public MetricController(MeterRegistry registry) { + this.registry = registry; + registry.gauge("counter_gauge", this, MetricController::getCustomerCount); + } @Path("timed") - @Timed(name = "timed-request") + @Timed(value = "timed-request") @GET public String timedRequest() { // Demo, not production style @@ -43,12 +39,15 @@ public String timedRequest() { @Path("increment") @GET public long doIncrement() { - counter.inc(); - return counter.getCount(); + getEndpointCounter().increment(); + return (long) getEndpointCounter().count(); } - @Gauge(name = "counter_gauge", unit = MetricUnits.NONE) long getCustomerCount() { - return counter.getCount(); + return (long) getEndpointCounter().count(); + } + + private Counter getEndpointCounter() { + return registry.counter("endpoint_counter"); } } diff --git a/app-full-microprofile/src/main/java/com/example/quarkus/secure/MPJWTToken.java b/app-full-microprofile/src/main/java/com/example/quarkus/secure/MPJWTToken.java deleted file mode 100644 index 92252df9..00000000 --- a/app-full-microprofile/src/main/java/com/example/quarkus/secure/MPJWTToken.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.example.quarkus.secure; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import io.vertx.core.json.JsonArray; -import io.vertx.core.json.JsonObject; - -/** - * Represent the MP Auth token - */ - -public class MPJWTToken { - - private String iss; // issuer - private String aud; // audience - private String jti; // Unique identifier - private Long exp; // expiration time - private Long iat; // issued at - private String sub; // subject - private String upn; // value for name in Principal - private String preferredUsername; // value for name in Principal - private List groups = new ArrayList<>(); - /* - * "iss": "https://server.example.com", - * "aud": "s6BhdRkqt3", - * "jti": "a-123", - * "exp": 1311281970, - * "iat": 1311280970, - * "sub": "24400320", - * "upn": "jdoe@server.example.com", - * "groups": ["red-group", "green-group", "admin-group", "admin"], - */ - - private List roles; - private Map additionalClaims; - - public String getIss() { - return iss; - } - - public void setIss(String iss) { - this.iss = iss; - } - - public String getAud() { - return aud; - } - - public void setAud(String aud) { - this.aud = aud; - } - - public String getJti() { - return jti; - } - - public void setJti(String jti) { - this.jti = jti; - } - - public Long getExp() { - return exp; - } - - public void setExp(Long exp) { - this.exp = exp; - } - - public Long getIat() { - return iat; - } - - public void setIat(Long iat) { - this.iat = iat; - } - - public String getSub() { - return sub; - } - - public void setSub(String sub) { - this.sub = sub; - } - - public String getUpn() { - return upn; - } - - public void setUpn(String upn) { - this.upn = upn; - } - - public String getPreferredUsername() { - return preferredUsername; - } - - public void setPreferredUsername(String preferredUsername) { - this.preferredUsername = preferredUsername; - } - - public List getGroups() { - return groups; - } - - public void setGroups(List groups) { - this.groups = groups; - } - - public List getRoles() { - return roles; - } - - public void setRoles(List roles) { - this.roles = roles; - } - - public Map getAdditionalClaims() { - return additionalClaims; - } - - public void setAdditionalClaims(Map additionalClaims) { - this.additionalClaims = additionalClaims; - } - - public void addAdditionalClaims(String key, String value) { - if (additionalClaims == null) { - additionalClaims = new HashMap<>(); - } - additionalClaims.put(key, value); - } - - public JsonObject toJSONString() { - JsonObject jsonObject = new JsonObject(); - - jsonObject.put("iss", iss); - jsonObject.put("aud", aud); - jsonObject.put("jti", jti); - jsonObject.put("exp", exp / 1000); - jsonObject.put("iat", iat / 1000); - jsonObject.put("sub", sub); - jsonObject.put("upn", upn); - jsonObject.put("preferred_username", preferredUsername); - - if (additionalClaims != null) { - for (Map.Entry entry : additionalClaims.entrySet()) { - jsonObject.put(entry.getKey(), entry.getValue()); - } - } - - JsonArray jsonArray = new JsonArray(); - for (String group : groups) { - jsonArray.add(group); - } - jsonObject.put("groups", jsonArray); - - return jsonObject; - } - -} diff --git a/app-full-microprofile/src/main/java/com/example/quarkus/secure/ProtectedController.java b/app-full-microprofile/src/main/java/com/example/quarkus/secure/ProtectedController.java deleted file mode 100644 index 1d546376..00000000 --- a/app-full-microprofile/src/main/java/com/example/quarkus/secure/ProtectedController.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.example.quarkus.secure; - -import org.eclipse.microprofile.jwt.Claim; -import org.eclipse.microprofile.jwt.ClaimValue; - -import jakarta.annotation.security.RolesAllowed; -import jakarta.enterprise.context.RequestScoped; -import jakarta.inject.Inject; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; - -@Path("/protected") -@RequestScoped -public class ProtectedController { - - @Inject - @Claim("custom-value") - // https://quarkus.io/guides/cdi-reference#private-members - // - private ClaimValue custom; - ClaimValue custom; - - @GET - @RolesAllowed("protected") - public String getJWTBasedValue() { - return "Protected Resource; Custom value : " + custom.getValue(); - } -} diff --git a/app-full-microprofile/src/main/java/com/example/quarkus/secure/TestSecureController.java b/app-full-microprofile/src/main/java/com/example/quarkus/secure/TestSecureController.java deleted file mode 100644 index 160c439c..00000000 --- a/app-full-microprofile/src/main/java/com/example/quarkus/secure/TestSecureController.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.example.quarkus.secure; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.UUID; - -import io.vertx.core.json.JsonObject; -import io.vertx.ext.auth.JWTOptions; -import io.vertx.ext.auth.PubSecKeyOptions; -import io.vertx.ext.auth.jwt.JWTAuth; -import io.vertx.ext.auth.jwt.JWTAuthOptions; -import jakarta.annotation.PostConstruct; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.client.ClientBuilder; -import jakarta.ws.rs.client.WebTarget; -import jakarta.ws.rs.core.Response; - -@Path("/secured") -@ApplicationScoped -public class TestSecureController { - - private String key; - - @PostConstruct - public void init() { - key = readPemFile(); - } - - @GET - @Path("/test") - public String testSecureCall() { - if (key == null) { - throw new WebApplicationException("Unable to read privateKey.pem", 500); - } - String jwt = generateJWT(key); - WebTarget target = ClientBuilder.newClient().target("http://localhost:8080/data/protected"); - Response response = target.request().header("authorization", "Bearer " + jwt).buildGet().invoke(); - return String.format("Claim value within JWT of 'custom-value' : %s", response.readEntity(String.class)); - } - - private static String generateJWT(String key) { - JWTAuth provider = JWTAuth.create(null, new JWTAuthOptions() - .addPubSecKey(new PubSecKeyOptions() - .setAlgorithm("RS256") - .setBuffer(key))); // Set up private key - - MPJWTToken token = new MPJWTToken(); - token.setAud("targetService"); - token.setIss("https://server.example.com"); - token.setJti(UUID.randomUUID().toString()); - token.setSub("Jessie"); - token.setUpn("Jessie"); - token.setIat(System.currentTimeMillis()); - token.setExp(System.currentTimeMillis() + 30000); - token.addAdditionalClaims("custom-value", "Jessie specific value"); - token.setGroups(Arrays.asList("user", "protected")); - - return provider.generateToken(new JsonObject().mergeIn(token.toJSONString()), new JWTOptions().setAlgorithm("RS256")); - } - - private static String readPemFile() { - StringBuilder sb = new StringBuilder(8192); - try (BufferedReader is = new BufferedReader( - new InputStreamReader( - TestSecureController.class.getResourceAsStream("/privateKey.pem"), StandardCharsets.US_ASCII))) { - String line; - while ((line = is.readLine()) != null) { - sb.append(line); - sb.append('\n'); - } - } catch (IOException e) { - e.printStackTrace(); - } - return sb.toString(); - } -} diff --git a/app-full-microprofile/src/main/java/com/example/quarkus/serialization/JsonResource.java b/app-full-microprofile/src/main/java/com/example/quarkus/serialization/JsonResource.java new file mode 100644 index 00000000..890fe7c2 --- /dev/null +++ b/app-full-microprofile/src/main/java/com/example/quarkus/serialization/JsonResource.java @@ -0,0 +1,26 @@ +package com.example.quarkus.serialization; + +import com.example.quarkus.serialization.dto.ComplexDto; +import com.example.quarkus.serialization.dto.MyEnum; +import com.example.quarkus.serialization.dto.NestedClass; +import com.example.quarkus.serialization.dto.NestedInterface; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +/** + * Tests DTO serialization as this is most likely done by majority of apps these days. + */ +@Produces(MediaType.APPLICATION_JSON) +@Path("serialization/json") +public class JsonResource { + + @GET + @Path("complex-dto") + public ComplexDto complexDto() { + return new ComplexDto(MyEnum.ONE, new NestedClass(), NestedInterface.INSTANCE); + } + +} diff --git a/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/ComplexDto.java b/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/ComplexDto.java new file mode 100644 index 00000000..3bbe21d3 --- /dev/null +++ b/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/ComplexDto.java @@ -0,0 +1,4 @@ +package com.example.quarkus.serialization.dto; + +public record ComplexDto(MyEnum myEnum, NestedClass nestedClass, NestedInterface nestedInterface) { +} diff --git a/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/MyEnum.java b/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/MyEnum.java new file mode 100644 index 00000000..a18b42bc --- /dev/null +++ b/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/MyEnum.java @@ -0,0 +1,7 @@ +package com.example.quarkus.serialization.dto; + +public enum MyEnum { + ONE, + TWO, + THREE +} diff --git a/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/NestedClass.java b/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/NestedClass.java new file mode 100644 index 00000000..1f7d7801 --- /dev/null +++ b/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/NestedClass.java @@ -0,0 +1,15 @@ +package com.example.quarkus.serialization.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public class NestedClass { + + public String getProperty() { + return "property-value"; + } + + @JsonIgnore + public String getIgnoredProperty() { + return "ignored-value"; + } +} diff --git a/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/NestedInterface.java b/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/NestedInterface.java new file mode 100644 index 00000000..b4c8aa4a --- /dev/null +++ b/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/NestedInterface.java @@ -0,0 +1,35 @@ +package com.example.quarkus.serialization.dto; + +public interface NestedInterface { + + NestedInterface INSTANCE = new NestedInterface() { + @Override + public String getString() { + return "Vive la résistance!"; + } + + @Override + public int getInt() { + return 42; + } + + @Override + public NestedRecord getRecord() { + return new NestedRecord(); + } + + @Override + public char getCharacter() { + return Character.MAX_VALUE; + } + }; + + String getString(); + + int getInt(); + + Object getRecord(); + + char getCharacter(); + +} diff --git a/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/NestedRecord.java b/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/NestedRecord.java new file mode 100644 index 00000000..1ee08629 --- /dev/null +++ b/app-full-microprofile/src/main/java/com/example/quarkus/serialization/dto/NestedRecord.java @@ -0,0 +1,9 @@ +package com.example.quarkus.serialization.dto; + +public record NestedRecord(String recordProperty) { + + public NestedRecord() { + this("record-property-value"); + } + +} diff --git a/app-full-microprofile/src/main/resources/META-INF/resources/publicKey.pem b/app-full-microprofile/src/main/resources/META-INF/resources/publicKey.pem deleted file mode 100644 index 42a30224..00000000 --- a/app-full-microprofile/src/main/resources/META-INF/resources/publicKey.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzwDtyf6ePOjf/oELJcX7 -AuFXLe3AFTIu79yOpk1w4s8Pia6TE0TVVHJ1X9MpiiMRh5LW56rRSQX5H5SHSyMT -OT8DwdtydHoZJTjTavMxqoxWUCPU1NR93tdibewFJ+EykGj17QUGpcm2+msk+0YY -jTa1Bhjcx/sC9WpPI1QBBaS7hXH6IycLIRd5sjX1yJwKkSBghHkARcma4fESvj8a -BjomJlySYMQ8bX69HDFWu8tIHT7kqhA0DQ/r7fJFUJOO3CvHhh9cqaD35HpnjTqY -4w0WiSLqA4FxDgkMR1U9ajeDo1jnwGEWV7X78usaz/q0uaNIKoP9bWWxe11Jq5uO -rwIDAQAB ------END PUBLIC KEY----- diff --git a/app-full-microprofile/src/main/resources/application.properties b/app-full-microprofile/src/main/resources/application.properties index 0481da9f..ac337791 100644 --- a/app-full-microprofile/src/main/resources/application.properties +++ b/app-full-microprofile/src/main/resources/application.properties @@ -6,10 +6,8 @@ com.example.quarkus.client.Service/mp-rest/url=http://localhost:8080/data/client quarkus.ssl.native=true quarkus.console.color=false -mp.jwt.verify.publickey.location=META-INF/resources/publicKey.pem -mp.jwt.verify.issuer=https://server.example.com -quarkus.smallrye-jwt.enabled=true - -quarkus.otel.traces.exporter=none - -quarkus.native.additional-build-args=-H:Log=registerResource:, -H:IncludeResources=privateKey.pem +# address of simplistic Vert.x collector started before 'app-full-microprofile' project is tested +quarkus.otel.exporter.otlp.traces.endpoint=http://localhost:4317/api/traces +# shorten waiting period for traces +quarkus.otel.bsp.schedule.delay=700ms +quarkus.otel.bsp.export.timeout=700ms diff --git a/app-full-microprofile/src/main/resources/privateKey.pem b/app-full-microprofile/src/main/resources/privateKey.pem deleted file mode 100644 index 326b510f..00000000 --- a/app-full-microprofile/src/main/resources/privateKey.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPAO3J/p486N/+ -gQslxfsC4Vct7cAVMi7v3I6mTXDizw+JrpMTRNVUcnVf0ymKIxGHktbnqtFJBfkf -lIdLIxM5PwPB23J0ehklONNq8zGqjFZQI9TU1H3e12Jt7AUn4TKQaPXtBQalybb6 -ayT7RhiNNrUGGNzH+wL1ak8jVAEFpLuFcfojJwshF3myNfXInAqRIGCEeQBFyZrh -8RK+PxoGOiYmXJJgxDxtfr0cMVa7y0gdPuSqEDQND+vt8kVQk47cK8eGH1ypoPfk -emeNOpjjDRaJIuoDgXEOCQxHVT1qN4OjWOfAYRZXtfvy6xrP+rS5o0gqg/1tZbF7 -XUmrm46vAgMBAAECggEBAMehqrFKF4rAPvzvsDN+ikPN08icZ8lJO1DhUMT7HCnv -7JkoPfiwQlgNhjqip4XrqgUoTI7hArK8yvN0x0FkEy77IYF8RBYmhkeKVQHohXZn -nvnshF24i6cz6l395z79hEkWoE0zsqSCMy+v0tttT1Iod03o/kryPXk2TBnS8qVf -6nv/ohuu2S5KI0b4skxGSM60kA96h6ngCcq4pkwHEtnwJmF2xz4EO+zb558yOF5A -OznjKxTbNH3RsCGG1bMWSvy4qIihdJRDUAeKKdtAZOeLgUrSFHUDxKwCLFIESde0 -JP9uXuUlAb8WOnsL4z0jbgx1+ctI0UOIKTJc8sErXrECgYEA+S8cSpgED416ydrt -WWmETeW2sPUJsmDfQhQxUoxhSypDTDBGHGBZzvLSoWCxJBnuY8teiDzDAXh7nHMs -SZA1vT9hZu+qWzgdU+0SK6r0FyY5RFBIHB468fO6S5mJm0wqmy1J94Voaeofij1q -1pE8/tniLKciP2BnkX7lu2F1E4cCgYEA1Kp0Kz0WwUcu2UnmKOOzqIsA9mwucKAF -zmGA1c8q+Cx2dIgeHQjWtl3SWs0qVps74YEZUYbxmG/M9MHHGbRq7w8hwaSpeFt+ -DswiscELag4PuEHn6yGdJyPnXBoERnOvVPlkdDts6NPvLtfcBBkgPqhpvhObf9vf -Cv+trWWExZkCgYAlmY86Tj/mnOGXTdqcsEhPfMcZYpApA2cM0IE0xIv1zJXFDE+3 -/m3uxUM1KKLyIJuRIWHNSuXd9fEpBVP8ca86NDMdVjKtewUp4c7pGe2lBJaFkVug -KouYcL9+otdZwJ95NNdBazb7LGG/+U6Cu/2pMvVm6X1IdOKL2MsPgEArRwKBgQDA -midfyZHENg2t6Qmz2pUpfcq/YrakdakMgq3F9jw6Szp0y5pKPWkH/Oy4I7vGeAzB -bMRbW9WOcyKyQJVrKET4gUHXOKPrRyFhkWuShP0rbdS60aWTA/xqKFAuz7kzfS47 -zSo3QmKecuLaD9FJPOBBHxG1fdiE8cKNGYZX1etrcQKBgHhnkwmCTxXdimQTswE1 -nytAbdXEUSqLU2sZT3sQq497j6uOjcBlZekPtibOuyQLFmeKIMZMI1j0AARbu+ZJ -T7SwLL4cjsws838VujJzXPGxvbp3twvWpZHsUvp2eX/8LiWexL5TPj4jkHkkholH -H675T5VXIbt4N8LR1fHThOJB ------END PRIVATE KEY----- diff --git a/pom.xml b/pom.xml index 7c9bf309..711609ec 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,7 @@ 3.11.0 17 17 + 17 3.10.1 2.22.2 2.22.2 @@ -23,6 +24,7 @@ 3.5.0.Final 2.19.0 1.30.0 + 4.5.7 2.22.0 1.8.0 3.2.2 diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 1801e540..5111028e 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -27,6 +27,13 @@ ${junit.jupiter.version} test + + + io.vertx + vertx-grpc-server + ${vertx-grpc-server.version} + test + diff --git a/testsuite/src/it/java/io/quarkus/ts/startstop/StartStopTest.java b/testsuite/src/it/java/io/quarkus/ts/startstop/StartStopTest.java index 82fd2245..af96bde2 100644 --- a/testsuite/src/it/java/io/quarkus/ts/startstop/StartStopTest.java +++ b/testsuite/src/it/java/io/quarkus/ts/startstop/StartStopTest.java @@ -6,6 +6,8 @@ import io.quarkus.ts.startstop.utils.LogBuilder; import io.quarkus.ts.startstop.utils.Logs; import io.quarkus.ts.startstop.utils.MvnCmds; +import io.quarkus.ts.startstop.utils.OpenTelemetryCollector; +import io.quarkus.ts.startstop.utils.UnitTestResource; import io.quarkus.ts.startstop.utils.WebpageTester; import org.apache.commons.io.FileUtils; import org.jboss.logging.Logger; @@ -27,6 +29,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import static io.quarkus.ts.startstop.utils.Commands.cleanTarget; import static io.quarkus.ts.startstop.utils.Commands.dropCaches; @@ -66,6 +69,10 @@ public class StartStopTest { public static final String BASE_DIR = getBaseDir(); public void testRuntime(TestInfo testInfo, Apps app, MvnCmds mvnCmds) throws IOException, InterruptedException { + testRuntime(testInfo, app, mvnCmds, UnitTestResource.NOOP_SUPPLIER); + } + + public void testRuntime(TestInfo testInfo, Apps app, MvnCmds mvnCmds, Supplier testResourceSupplier) throws IOException, InterruptedException { LOGGER.info("Testing app: " + app.toString() + ", mode: " + mvnCmds.toString()); LOGGER.info("Cleanup Enabled: " + !disableCleanup()); @@ -77,7 +84,7 @@ public void testRuntime(TestInfo testInfo, Apps app, MvnCmds mvnCmds) throws IOE Optional asyncProfiler = mvnCmds == MvnCmds.JVM ? AsyncProfiler.create() : Optional.empty(); String cn = testInfo.getTestClass().get().getCanonicalName(); String mn = testInfo.getTestMethod().get().getName(); - try { + try(var testResource = testResourceSupplier.get()) { // Cleanup asyncProfiler.ifPresent(ignore -> AsyncProfiler.cleanProfilingResults(app)); cleanTarget(app); @@ -181,6 +188,8 @@ public void testRuntime(TestInfo testInfo, Apps app, MvnCmds mvnCmds) throws IOE rssKbList.add(rssKb); timeToFirstOKRequestList.add(timeToFirstOKRequest); + + testResource.reset(); } long rssKbAvgWithoutMinMax = getAvgWithoutMinMax(rssKbList); @@ -222,12 +231,12 @@ public void jakartaRESTMinimalNative(TestInfo testInfo) throws IOException, Inte @Test public void fullMicroProfileJVM(TestInfo testInfo) throws IOException, InterruptedException { - testRuntime(testInfo, Apps.FULL_MICROPROFILE, MvnCmds.JVM); + testRuntime(testInfo, Apps.FULL_MICROPROFILE, MvnCmds.JVM, OpenTelemetryCollector::new); } @Test @Tag("native") public void fullMicroProfileNative(TestInfo testInfo) throws IOException, InterruptedException { - testRuntime(testInfo, Apps.FULL_MICROPROFILE, MvnCmds.NATIVE); + testRuntime(testInfo, Apps.FULL_MICROPROFILE, MvnCmds.NATIVE, OpenTelemetryCollector::new); } } diff --git a/testsuite/src/it/java/io/quarkus/ts/startstop/utils/OpenTelemetryCollector.java b/testsuite/src/it/java/io/quarkus/ts/startstop/utils/OpenTelemetryCollector.java new file mode 100644 index 00000000..e5f456dc --- /dev/null +++ b/testsuite/src/it/java/io/quarkus/ts/startstop/utils/OpenTelemetryCollector.java @@ -0,0 +1,99 @@ +package io.quarkus.ts.startstop.utils; + +import io.vertx.core.Vertx; +import io.vertx.core.http.HttpServer; +import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.grpc.common.GrpcMessage; +import io.vertx.grpc.server.GrpcServer; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This simplistic collector allows us to test Vert.x-based traces exporter in Quarkus without starting a container. + */ +public class OpenTelemetryCollector implements UnitTestResource { + + private static final String HELLO_ENDPOINT_OPERATION_NAME = "GET /hello"; + private static final String GET_HELLO_INVOCATION_NOT_TRACED = HELLO_ENDPOINT_OPERATION_NAME + " invocation not traced"; + /** + * If you change this port, you must also change respective 'quarkus.otel.exporter.otlp.traces.endpoint' value. + */ + private static final int OTEL_COLLECTOR_PORT = 4317; + private static final String GET_HELLO_TRACES_PATH = "/recorded-traces/get-hello"; + static final String GET_HELLO_TRACES_URL = "http://localhost:" + OTEL_COLLECTOR_PORT + GET_HELLO_TRACES_PATH; + static final String GET_HELLO_INVOCATION_TRACED = HELLO_ENDPOINT_OPERATION_NAME + " invocation traced"; + + private final Closeable closeable; + private final RequestHandler requestHandler; + + + public OpenTelemetryCollector() { + this.closeable = createGrpcServer(); + this.requestHandler = new RequestHandler(); + } + + private Closeable createGrpcServer() { + Vertx vertx = Vertx.vertx(); + GrpcServer grpcProxy = GrpcServer.server(vertx); + + // record incoming traces + grpcProxy.callHandler(reqFromQuarkus -> reqFromQuarkus.messageHandler(requestHandler::onReceivedTraces)); + + HttpServer httpServer = vertx.createHttpServer(new HttpServerOptions().setPort(OTEL_COLLECTOR_PORT)); + httpServer.requestHandler(httpServerRequest -> { + if (httpServerRequest.path().contains(GET_HELLO_TRACES_PATH)) { + requestHandler.handleTracesRequest(httpServerRequest); + } else { + grpcProxy.handle(httpServerRequest); + } + }).listen(); + + // close resources + return () -> { + httpServer.close().toCompletionStage().toCompletableFuture().join(); + vertx.close().toCompletionStage().toCompletableFuture().join(); + }; + } + + @Override + public void close() throws IOException { + closeable.close(); + } + + @Override + public void reset() { + requestHandler.resetTraces(); + } + + private static class RequestHandler { + + private final AtomicBoolean helloEndpointCallTraced = new AtomicBoolean(false); + + private void handleTracesRequest(HttpServerRequest request) { + final String response; + if (helloEndpointCallTraced.get()) { + response = GET_HELLO_INVOCATION_TRACED; + } else { + response = GET_HELLO_INVOCATION_NOT_TRACED; + } + request.response().end(response); + } + + private void onReceivedTraces(GrpcMessage exportedTraces) { + if (!helloEndpointCallTraced.get() && helloEndpointCallTraced(exportedTraces)) { + helloEndpointCallTraced.set(true); + } + } + + private void resetTraces() { + helloEndpointCallTraced.set(false); + } + + private static boolean helloEndpointCallTraced(GrpcMessage msgFromQuarkus) { + return msgFromQuarkus.payload().toString().contains(HELLO_ENDPOINT_OPERATION_NAME); + } + } +} diff --git a/testsuite/src/it/java/io/quarkus/ts/startstop/utils/URLContent.java b/testsuite/src/it/java/io/quarkus/ts/startstop/utils/URLContent.java index 619bebb9..6ff66146 100755 --- a/testsuite/src/it/java/io/quarkus/ts/startstop/utils/URLContent.java +++ b/testsuite/src/it/java/io/quarkus/ts/startstop/utils/URLContent.java @@ -1,5 +1,8 @@ package io.quarkus.ts.startstop.utils; +import static io.quarkus.ts.startstop.utils.OpenTelemetryCollector.GET_HELLO_INVOCATION_TRACED; +import static io.quarkus.ts.startstop.utils.OpenTelemetryCollector.GET_HELLO_TRACES_URL; + /** * Available endpoitns and expected content. */ @@ -9,17 +12,21 @@ public enum URLContent { new String[]{"http://localhost:8080", "Hello from a simple Jakarta REST app."} }), FULL_MICROPROFILE(new String[][]{ - new String[]{"http://localhost:8080/data/hello", "Hello World"}, + new String[]{"http://localhost:8080/q/health/ready", "\"UP\""}, new String[]{"http://localhost:8080", "Hello from a full MicroProfile suite"}, + new String[]{"http://localhost:8080/data/hello", "Hello World"}, new String[]{"http://localhost:8080/data/config/injected", "Config value as Injected by CDI Injected value"}, new String[]{"http://localhost:8080/data/config/lookup", "Config value from ConfigProvider lookup value"}, new String[]{"http://localhost:8080/data/resilience", "Fallback answer due to timeout"}, - new String[]{"http://localhost:8080/q/health", "\"UP\""}, new String[]{"http://localhost:8080/data/metric/timed", "Request is used in statistics, check with the Metrics call."}, - new String[]{"http://localhost:8080/q/metrics", "Controller_timed_request_seconds_count"}, - new String[]{"http://localhost:8080/data/secured/test", "Jessie specific value"}, + new String[]{"http://localhost:8080/q/metrics", "timed_request_seconds_count{class=\"com.example.quarkus.metric.MetricController\",exception=\"none\",method=\"timedRequest\"}"}, + new String[]{"http://localhost:8080/data/metric/increment", "1"}, // check counter incremented exactly once + new String[]{"http://localhost:8080/q/metrics", "endpoint_counter_total 1.0"}, // counter metric must match + new String[]{"http://localhost:8080/q/metrics", "counter_gauge"}, // check gauge for the counter exists + new String[]{"http://localhost:8080/data/serialization/json/complex-dto", "Vive la résistance!"}, new String[]{"http://localhost:8080/q/openapi", "/resilience"}, - new String[]{"http://localhost:8080/data/client/test/parameterValue=xxx", "Processed parameter value 'parameterValue=xxx'"} + new String[]{"http://localhost:8080/data/client/test/parameterValue=xxx", "Processed parameter value 'parameterValue=xxx'"}, + new String[]{GET_HELLO_TRACES_URL, GET_HELLO_INVOCATION_TRACED} }), GENERATED_SKELETON(new String[][]{ new String[]{"http://localhost:8080/", "Congratulation! You are on landing page."}, diff --git a/testsuite/src/it/java/io/quarkus/ts/startstop/utils/UnitTestResource.java b/testsuite/src/it/java/io/quarkus/ts/startstop/utils/UnitTestResource.java new file mode 100644 index 00000000..363f86e5 --- /dev/null +++ b/testsuite/src/it/java/io/quarkus/ts/startstop/utils/UnitTestResource.java @@ -0,0 +1,27 @@ +package io.quarkus.ts.startstop.utils; + +import java.io.Closeable; +import java.util.function.Supplier; + +public interface UnitTestResource extends Closeable { + + Supplier NOOP_SUPPLIER = () -> new UnitTestResource() { + + @Override + public void close() { + + } + + @Override + public void reset() { + + } + + }; + + /** + * Resets resources so that next test can start with a clean sheet. + */ + void reset(); + +} diff --git a/testsuite/src/it/java/io/quarkus/ts/startstop/utils/WhitelistLogLines.java b/testsuite/src/it/java/io/quarkus/ts/startstop/utils/WhitelistLogLines.java index 06b38304..d1f6ab27 100755 --- a/testsuite/src/it/java/io/quarkus/ts/startstop/utils/WhitelistLogLines.java +++ b/testsuite/src/it/java/io/quarkus/ts/startstop/utils/WhitelistLogLines.java @@ -40,9 +40,8 @@ public enum WhitelistLogLines { // netty 4 which doesn't have the relevant native config in the lib. See https://github.com/netty/netty/pull/13596 Pattern.compile(".*Warning: Please re-evaluate whether any experimental option is required, and either remove or unlock it\\..*"), Pattern.compile(".*Warning: The option '-H:ReflectionConfigurationResources=META-INF/native-image/io\\.netty/netty-transport/reflection-config\\.json' is experimental and must be enabled via.*"), - // native options added explicitly by FULL_MICROPROFILE application.properies - Pattern.compile(".*Warning: The option '-H:Log=registerResource:' is experimental and must be enabled via.*"), - Pattern.compile(".*Warning: The option '-H:IncludeResources=privateKey\\.pem' is experimental and must be enabled via.*"), + // TODO: remove next line when https://github.com/quarkusio/quarkus/issues/41351 gets fixed + Pattern.compile(".*Error Occurred After Shutdown.*java.lang.NullPointerException.*Cannot invoke.*io.smallrye.context.SmallRyeContextManager.defaultThreadContext.*"), }), GENERATED_SKELETON(new Pattern[]{ // Harmless warning