From 4de4d8e52459094e1a0c589d463a390066f30810 Mon Sep 17 00:00:00 2001 From: xstefank Date: Mon, 25 Jul 2022 15:22:06 +0200 Subject: [PATCH] Add MicroProfile LRA TCK --- .../lra/deployment/NarayanaLRAProcessor.java | 2 +- tcks/microprofile-lra/pom.xml | 139 ++++++++++++++++++ .../quarkus/tck/lra/ArquillianExtension.java | 16 ++ .../io/quarkus/tck/lra/BaseURLProvider.java | 29 ++++ .../quarkus/tck/lra/DeploymentProcessor.java | 28 ++++ .../tck/lra/LRACoordinatorManager.java | 62 ++++++++ .../quarkus/tck/lra/NarayanaLRARecovery.java | 86 +++++++++++ .../test/java/io/quarkus/tck/lra/Dummy.java | 6 + ...ile.lra.tck.service.spi.LRARecoveryService | 1 + ...boss.arquillian.core.spi.LoadableExtension | 1 + tcks/pom.xml | 1 + .../QuarkusBeforeAfterLifecycle.java | 25 ++-- 12 files changed, 385 insertions(+), 11 deletions(-) create mode 100644 tcks/microprofile-lra/pom.xml create mode 100644 tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/ArquillianExtension.java create mode 100644 tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/BaseURLProvider.java create mode 100644 tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/DeploymentProcessor.java create mode 100644 tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/LRACoordinatorManager.java create mode 100644 tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/NarayanaLRARecovery.java create mode 100644 tcks/microprofile-lra/src/test/java/io/quarkus/tck/lra/Dummy.java create mode 100644 tcks/microprofile-lra/src/test/resources/META-INF/services/org.eclipse.microprofile.lra.tck.service.spi.LRARecoveryService create mode 100644 tcks/microprofile-lra/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension diff --git a/extensions/narayana-lra/deployment/src/main/java/io/quarkus/narayana/lra/deployment/NarayanaLRAProcessor.java b/extensions/narayana-lra/deployment/src/main/java/io/quarkus/narayana/lra/deployment/NarayanaLRAProcessor.java index 1016389f5ea34..3f77956e3179e 100644 --- a/extensions/narayana-lra/deployment/src/main/java/io/quarkus/narayana/lra/deployment/NarayanaLRAProcessor.java +++ b/extensions/narayana-lra/deployment/src/main/java/io/quarkus/narayana/lra/deployment/NarayanaLRAProcessor.java @@ -83,7 +83,7 @@ void createLRAParticipantRegistry(NarayanaLRARecorder recorder, continue; } - int modifiers = classInfo.getClass().getModifiers(); + int modifiers = classInfo.flags(); if (Modifier.isInterface(modifiers) || Modifier.isAbstract(modifiers) || !isLRAParticipant(index, classInfo)) { continue; diff --git a/tcks/microprofile-lra/pom.xml b/tcks/microprofile-lra/pom.xml new file mode 100644 index 0000000000000..ff89ed367c6fd --- /dev/null +++ b/tcks/microprofile-lra/pom.xml @@ -0,0 +1,139 @@ + + + quarkus-tck-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-tck-microprofile-lra + Quarkus - TCK - MicroProfile LRA + + + 1.0 + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + false + + + + + org.eclipse.microprofile.lra:microprofile-lra-tck + + false + + + + + + + io.quarkus + quarkus-arquillian + + + io.quarkus + quarkus-narayana-lra + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-resteasy-jackson + + + io.quarkus + quarkus-rest-client + + + + org.eclipse.microprofile.lra + microprofile-lra-tck + ${microprofile-lra-tck.version} + + + org.jboss.logging + commons-logging-jboss-logging + + + org.testcontainers + testcontainers + + + junit + junit + + + + + + + + io.quarkus + quarkus-narayana-lra-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-jackson-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-rest-client-deployment + ${project.version} + pom + test + + + * + * + + + + + + diff --git a/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/ArquillianExtension.java b/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/ArquillianExtension.java new file mode 100644 index 0000000000000..b46393feb5717 --- /dev/null +++ b/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/ArquillianExtension.java @@ -0,0 +1,16 @@ +package io.quarkus.tck.lra; + +import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor; +import org.jboss.arquillian.core.spi.LoadableExtension; +import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider; + +public class ArquillianExtension implements LoadableExtension { + + @Override + public void register(ExtensionBuilder extensionBuilder) { + extensionBuilder.service(ApplicationArchiveProcessor.class, DeploymentProcessor.class); + extensionBuilder.service(ResourceProvider.class, BaseURLProvider.class); + // works but restarts coordinator with every test class + extensionBuilder.observer(LRACoordinatorManager.class); + } +} diff --git a/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/BaseURLProvider.java b/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/BaseURLProvider.java new file mode 100644 index 0000000000000..0b688728036f7 --- /dev/null +++ b/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/BaseURLProvider.java @@ -0,0 +1,29 @@ +package io.quarkus.tck.lra; + +import java.lang.annotation.Annotation; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider; + +public class BaseURLProvider implements ResourceProvider { + @Override + public boolean canProvide(Class aClass) { + return aClass.isAssignableFrom(URL.class); + } + + @Override + public Object lookup(ArquillianResource arquillianResource, Annotation... annotations) { + String testHost = ConfigProvider.getConfig().getOptionalValue("quarkus.http.host", String.class).orElse("localhost"); + Integer testPort = ConfigProvider.getConfig().getOptionalValue("quarkus.http.test-port", Integer.class).orElse(8081); + + try { + return URI.create(String.format("http://%s:%d", testHost, testPort)).toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/DeploymentProcessor.java b/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/DeploymentProcessor.java new file mode 100644 index 0000000000000..e94ff148de7aa --- /dev/null +++ b/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/DeploymentProcessor.java @@ -0,0 +1,28 @@ +package io.quarkus.tck.lra; + +import org.eclipse.microprofile.lra.tck.TckTestBase; +import org.eclipse.microprofile.lra.tck.participant.api.NonParticipatingTckResource; +import org.eclipse.microprofile.lra.tck.participant.api.ResourceParent; +import org.eclipse.microprofile.lra.tck.service.spi.LRARecoveryService; +import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor; +import org.jboss.arquillian.test.spi.TestClass; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.spec.WebArchive; + +public class DeploymentProcessor implements ApplicationArchiveProcessor { + + @Override + public void process(Archive archive, TestClass testClass) { + if (archive instanceof WebArchive) { + WebArchive war = (WebArchive) archive; + + war.addAsServiceProvider(LRARecoveryService.class, + NarayanaLRARecovery.class); + war.addPackages(false, + "org.eclipse.microprofile.lra"); + war.addClasses(TckTestBase.class, NarayanaLRARecovery.class, NonParticipatingTckResource.class, + ResourceParent.class); + } + + } +} diff --git a/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/LRACoordinatorManager.java b/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/LRACoordinatorManager.java new file mode 100644 index 0000000000000..8721d08e41755 --- /dev/null +++ b/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/LRACoordinatorManager.java @@ -0,0 +1,62 @@ +package io.quarkus.tck.lra; + +import java.io.IOException; +import java.net.ServerSocket; + +import org.jboss.arquillian.core.api.annotation.Observes; +import org.jboss.logging.Logger; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +public class LRACoordinatorManager { + + private static final int DEFAULT_PRECEDENCE = -100; + private static final Logger LOGGER = Logger.getLogger(LRACoordinatorManager.class); + private final int coordinatorPort = getFreePort(50000, 60000); + + private GenericContainer coordinatorContainer; + + public void beforeClass( + @Observes(precedence = DEFAULT_PRECEDENCE) org.jboss.arquillian.test.spi.event.suite.BeforeSuite event) { + LOGGER.debug("Starting LRA coordinator on port " + coordinatorPort); + coordinatorContainer = new GenericContainer<>(DockerImageName.parse("jbosstm/lra-coordinator:latest")) + // lra-coordinator is a Quarkus service + .withEnv("QUARKUS_HTTP_PORT", String.valueOf(coordinatorPort)) + // need to run with host network because coordinator calls the TCK services from the container + .withNetworkMode("host"); + + coordinatorContainer.start(); + + System.setProperty("lra.coordinator.url", String.format("http://localhost:%d/lra-coordinator", coordinatorPort)); + } + + public void afterClass( + @Observes(precedence = DEFAULT_PRECEDENCE) org.jboss.arquillian.test.spi.event.suite.AfterSuite event) { + if (coordinatorContainer != null && coordinatorContainer.isRunning()) { + coordinatorContainer.stop(); + } + } + + public int getFreePort(int from, int to) { + int currentPort = from; + while (currentPort <= to) { + if (isLocalPortFree(currentPort)) { + return currentPort; + } else { + currentPort++; + } + } + + throw new RuntimeException( + String.format("Unable to find a free port for the LRA coordinator in range [%d, %d]", from, to)); + } + + private boolean isLocalPortFree(int port) { + try { + new ServerSocket(port).close(); + return true; + } catch (IOException e) { + return false; + } + } +} diff --git a/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/NarayanaLRARecovery.java b/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/NarayanaLRARecovery.java new file mode 100644 index 0000000000000..7c3cd165d8c33 --- /dev/null +++ b/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/NarayanaLRARecovery.java @@ -0,0 +1,86 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2022, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +package io.quarkus.tck.lra; + +import static io.narayana.lra.LRAConstants.RECOVERY_COORDINATOR_PATH_NAME; + +import java.net.URI; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.microprofile.lra.tck.service.spi.LRARecoveryService; +import org.jboss.logging.Logger; + +import io.narayana.lra.LRAConstants; + +public class NarayanaLRARecovery implements LRARecoveryService { + private static final Logger log = Logger.getLogger(NarayanaLRARecovery.class); + + @Override + public void waitForCallbacks(URI lraId) { + // no action needed + } + + @Override + public boolean waitForEndPhaseReplay(URI lraId) { + log.info("waitForEndPhaseReplay for: " + lraId.toASCIIString()); + if (!recoverLRAs(lraId)) { + // first recovery scan probably collided with periodic recovery which started + // before the test execution so try once more + return recoverLRAs(lraId); + } + return true; + } + + /** + * Invokes LRA coordinator recovery REST endpoint and returns whether the recovery of intended LRAs happened + * + * @param lraId the LRA id of the LRA that is intended to be recovered + * @return true the intended LRA recovered, false otherwise + */ + private boolean recoverLRAs(URI lraId) { + // trigger a recovery scan + Client recoveryCoordinatorClient = ClientBuilder.newClient(); + + try { + URI lraCoordinatorUri = LRAConstants.getLRACoordinatorUrl(lraId); + URI recoveryCoordinatorUri = UriBuilder.fromUri(lraCoordinatorUri) + .path(RECOVERY_COORDINATOR_PATH_NAME).build(); + WebTarget recoveryTarget = recoveryCoordinatorClient.target(recoveryCoordinatorUri); + + // send the request to the recovery coordinator + Response response = recoveryTarget.request().get(); + String json = response.readEntity(String.class); + response.close(); + + // intended LRA didn't recover + return !json.contains(lraId.toASCIIString()); + } finally { + recoveryCoordinatorClient.close(); + } + } +} diff --git a/tcks/microprofile-lra/src/test/java/io/quarkus/tck/lra/Dummy.java b/tcks/microprofile-lra/src/test/java/io/quarkus/tck/lra/Dummy.java new file mode 100644 index 0000000000000..329312d042045 --- /dev/null +++ b/tcks/microprofile-lra/src/test/java/io/quarkus/tck/lra/Dummy.java @@ -0,0 +1,6 @@ +package io.quarkus.tck.lra; + +// We need this class so that target/test-classes is added to the class path? +public class Dummy { + +} diff --git a/tcks/microprofile-lra/src/test/resources/META-INF/services/org.eclipse.microprofile.lra.tck.service.spi.LRARecoveryService b/tcks/microprofile-lra/src/test/resources/META-INF/services/org.eclipse.microprofile.lra.tck.service.spi.LRARecoveryService new file mode 100644 index 0000000000000..d36807d1a6832 --- /dev/null +++ b/tcks/microprofile-lra/src/test/resources/META-INF/services/org.eclipse.microprofile.lra.tck.service.spi.LRARecoveryService @@ -0,0 +1 @@ +io.quarkus.tck.lra.NarayanaLRARecovery diff --git a/tcks/microprofile-lra/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/tcks/microprofile-lra/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension new file mode 100644 index 0000000000000..b1eb2d2935db7 --- /dev/null +++ b/tcks/microprofile-lra/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension @@ -0,0 +1 @@ +io.quarkus.tck.lra.ArquillianExtension \ No newline at end of file diff --git a/tcks/pom.xml b/tcks/pom.xml index 41f75a774414e..ce61052ab1b5f 100644 --- a/tcks/pom.xml +++ b/tcks/pom.xml @@ -103,6 +103,7 @@ microprofile-rest-client-reactive microprofile-openapi microprofile-opentracing + microprofile-lra resteasy-reactive diff --git a/test-framework/arquillian/src/main/java/io/quarkus/arquillian/QuarkusBeforeAfterLifecycle.java b/test-framework/arquillian/src/main/java/io/quarkus/arquillian/QuarkusBeforeAfterLifecycle.java index 8d935c9454c32..b5d51af78dbb6 100644 --- a/test-framework/arquillian/src/main/java/io/quarkus/arquillian/QuarkusBeforeAfterLifecycle.java +++ b/test-framework/arquillian/src/main/java/io/quarkus/arquillian/QuarkusBeforeAfterLifecycle.java @@ -4,6 +4,7 @@ import java.lang.reflect.Method; import org.jboss.arquillian.container.spi.context.annotation.DeploymentScoped; +import org.jboss.arquillian.container.test.api.RunAsClient; import org.jboss.arquillian.core.api.InstanceProducer; import org.jboss.arquillian.core.api.annotation.Inject; import org.jboss.arquillian.core.api.annotation.Observes; @@ -27,21 +28,25 @@ public class QuarkusBeforeAfterLifecycle { public void on(@Observes(precedence = DEFAULT_PRECEDENCE) org.jboss.arquillian.test.spi.event.suite.Before event) throws Throwable { - if (isJunitAvailable()) { - invokeCallbacks(JUNIT_INVOKE_BEFORES, JUNIT_CALLBACKS); - } - if (isTestNGAvailable()) { - invokeCallbacks(TESTNG_INVOKE_BEFORE_METHOD, TESTNG_CALLBACKS); + if (!event.getTestClass().isAnnotationPresent(RunAsClient.class)) { + if (isJunitAvailable()) { + invokeCallbacks(JUNIT_INVOKE_BEFORES, JUNIT_CALLBACKS); + } + if (isTestNGAvailable()) { + invokeCallbacks(TESTNG_INVOKE_BEFORE_METHOD, TESTNG_CALLBACKS); + } } } public void on(@Observes(precedence = DEFAULT_PRECEDENCE) org.jboss.arquillian.test.spi.event.suite.After event) throws Throwable { - if (isJunitAvailable()) { - invokeCallbacks(JUNIT_INVOKE_AFTERS, JUNIT_CALLBACKS); - } - if (isTestNGAvailable()) { - invokeCallbacks(TESTNG_INVOKE_AFTER_METHOD, TESTNG_CALLBACKS); + if (!event.getTestClass().isAnnotationPresent(RunAsClient.class)) { + if (isJunitAvailable()) { + invokeCallbacks(JUNIT_INVOKE_AFTERS, JUNIT_CALLBACKS); + } + if (isTestNGAvailable()) { + invokeCallbacks(TESTNG_INVOKE_AFTER_METHOD, TESTNG_CALLBACKS); + } } }