diff --git a/src/generated/java/io/neonbee/config/NeonBeeConfigConverter.java b/src/generated/java/io/neonbee/config/NeonBeeConfigConverter.java index b8760044..ac43c270 100644 --- a/src/generated/java/io/neonbee/config/NeonBeeConfigConverter.java +++ b/src/generated/java/io/neonbee/config/NeonBeeConfigConverter.java @@ -19,6 +19,11 @@ public class NeonBeeConfigConverter { static void fromJson(Iterable> json, NeonBeeConfig obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { + case "deploymentTimeout": + if (member.getValue() instanceof Number) { + obj.setDeploymentTimeout(((Number) member.getValue()).intValue()); + } + break; case "eventBusCodecs": if (member.getValue() instanceof JsonObject) { java.util.Map map = new java.util.LinkedHashMap<>(); @@ -62,6 +67,16 @@ static void fromJson(Iterable> json, NeonBee obj.setMicrometerRegistries(list); } break; + case "modelsDeploymentTimeout": + if (member.getValue() instanceof Number) { + obj.setModelsDeploymentTimeout(((Number) member.getValue()).intValue()); + } + break; + case "moduleDeploymentTimeout": + if (member.getValue() instanceof Number) { + obj.setModuleDeploymentTimeout(((Number) member.getValue()).intValue()); + } + break; case "platformClasses": if (member.getValue() instanceof JsonArray) { java.util.ArrayList list = new java.util.ArrayList<>(); @@ -82,6 +97,11 @@ static void fromJson(Iterable> json, NeonBee obj.setTrackingDataHandlingStrategy((String) member.getValue()); } break; + case "verticleDeploymentTimeout": + if (member.getValue() instanceof Number) { + obj.setVerticleDeploymentTimeout(((Number) member.getValue()).intValue()); + } + break; } } } @@ -91,6 +111,7 @@ static void toJson(NeonBeeConfig obj, JsonObject json) { } static void toJson(NeonBeeConfig obj, java.util.Map json) { + json.put("deploymentTimeout", obj.getDeploymentTimeout()); if (obj.getEventBusCodecs() != null) { JsonObject map = new JsonObject(); obj.getEventBusCodecs().forEach((key, value) -> map.put(key, value)); @@ -109,6 +130,12 @@ static void toJson(NeonBeeConfig obj, java.util.Map json) { obj.getMicrometerRegistries().forEach(item -> array.add(item.toJson())); json.put("micrometerRegistries", array); } + if (obj.getModelsDeploymentTimeout() != null) { + json.put("modelsDeploymentTimeout", obj.getModelsDeploymentTimeout()); + } + if (obj.getModuleDeploymentTimeout() != null) { + json.put("moduleDeploymentTimeout", obj.getModuleDeploymentTimeout()); + } if (obj.getPlatformClasses() != null) { JsonArray array = new JsonArray(); obj.getPlatformClasses().forEach(item -> array.add(item)); @@ -120,5 +147,8 @@ static void toJson(NeonBeeConfig obj, java.util.Map json) { if (obj.getTrackingDataHandlingStrategy() != null) { json.put("trackingDataHandlingStrategy", obj.getTrackingDataHandlingStrategy()); } + if (obj.getVerticleDeploymentTimeout() != null) { + json.put("verticleDeploymentTimeout", obj.getVerticleDeploymentTimeout()); + } } } diff --git a/src/main/java/io/neonbee/config/NeonBeeConfig.java b/src/main/java/io/neonbee/config/NeonBeeConfig.java index 68a90725..5577f69b 100644 --- a/src/main/java/io/neonbee/config/NeonBeeConfig.java +++ b/src/main/java/io/neonbee/config/NeonBeeConfig.java @@ -3,13 +3,16 @@ import static io.neonbee.internal.helper.ConfigHelper.notFound; import static io.neonbee.internal.helper.ConfigHelper.readConfig; import static io.neonbee.internal.helper.ConfigHelper.rephraseConfigNames; +import static io.neonbee.internal.helper.StringHelper.EMPTY; import static io.vertx.core.Future.future; import static io.vertx.core.Future.succeededFuture; import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.stream.Stream; import com.fasterxml.jackson.core.StreamReadConstraints; @@ -42,6 +45,11 @@ public class NeonBeeConfig { */ public static final int DEFAULT_EVENT_BUS_TIMEOUT = 30; + /** + * The default timeout for a deployment to finish. + */ + public static final int DEFAULT_DEPLOYMENT_TIMEOUT = 30; + /** * The default tracking data handling strategy. */ @@ -57,6 +65,14 @@ public class NeonBeeConfig { private int eventBusTimeout = DEFAULT_EVENT_BUS_TIMEOUT; + private int deploymentTimeout = DEFAULT_DEPLOYMENT_TIMEOUT; + + private Integer modelsDeploymentTimeout; + + private Integer moduleDeploymentTimeout; + + private Integer verticleDeploymentTimeout; + private Map eventBusCodecs = Map.of(); private String trackingDataHandlingStrategy = DEFAULT_TRACKING_DATA_HANDLING_STRATEGY; @@ -231,6 +247,119 @@ public NeonBeeConfig setEventBusTimeout(int eventBusTimeout) { return this; } + /** + * Returns the general deployment timeout for an individual deployment of any type in seconds. If unset / equal or + * smaller than 0, no timeout applies to the deployment. + * + * @return the deployment timeout in seconds + */ + public int getDeploymentTimeout() { + return deploymentTimeout; + } + + /** + * Returns the deployment timeout for a given deployment type, or the general {@link #getDeploymentTimeout()} if the + * deployment timeout for a given type is unset / equal or smaller than zero or an unrecognized type / {@code null} + * was passed. + * + * @param deploymentType the type of the deployment, e.g. modules, module, verticle or {@code null} + * @return the individual or general deployment timeout in seconds + */ + public int getDeploymentTimeout(String deploymentType) { + switch (Optional.ofNullable(deploymentType).map(type -> type.toLowerCase(Locale.ROOT)).orElse(EMPTY)) { + case "models": + return getModelsDeploymentTimeout(); + case "module": + return getModuleDeploymentTimeout(); + case "verticle": + return getVerticleDeploymentTimeout(); + default: + return getDeploymentTimeout(); + } + } + + /** + * Set the general deployment timeout for an individual deployment of any type in seconds. If equal or smaller than + * 0, no timeout applies to the deployment. + * + * @param deploymentTimeout the deployment timeout in seconds + * @return the {@linkplain NeonBeeConfig} for fluent use + */ + @Fluent + public NeonBeeConfig setDeploymentTimeout(int deploymentTimeout) { + this.deploymentTimeout = deploymentTimeout; + return this; + } + + /** + * Returns the deployment timeout of an individual models deployment in seconds. If unset the general + * {@link #getDeploymentTimeout()} is returned. + * + * @return the deployment timeout in seconds + */ + public Integer getModelsDeploymentTimeout() { + return modelsDeploymentTimeout != null ? modelsDeploymentTimeout : getDeploymentTimeout(); + } + + /** + * Set the deployment timeout of an individual models deployment in seconds. If equal or smaller than 0, no timeout + * applies to the deployment. If set to {@code null} the general {@link #getDeploymentTimeout()} applies. + * + * @param modelsDeploymentTimeout the deployment timeout in seconds or {@code null} + * @return the {@linkplain NeonBeeConfig} for fluent use + */ + @Fluent + public NeonBeeConfig setModelsDeploymentTimeout(Integer modelsDeploymentTimeout) { + this.modelsDeploymentTimeout = modelsDeploymentTimeout; + return this; + } + + /** + * Returns the deployment timeout of an individual module deployment in seconds. If unset the general + * {@link #getDeploymentTimeout()} is returned. + * + * @return the deployment timeout in seconds + */ + public Integer getModuleDeploymentTimeout() { + return moduleDeploymentTimeout != null ? moduleDeploymentTimeout : getDeploymentTimeout(); + } + + /** + * Set the deployment timeout of an individual module deployment in seconds. If equal or smaller than 0, no timeout + * applies to the deployment. If set to {@code null} the general {@link #getDeploymentTimeout()} applies. + * + * @param moduleDeploymentTimeout the deployment timeout in seconds or {@code null} + * @return the {@linkplain NeonBeeConfig} for fluent use + */ + @Fluent + public NeonBeeConfig setModuleDeploymentTimeout(Integer moduleDeploymentTimeout) { + this.moduleDeploymentTimeout = moduleDeploymentTimeout; + return this; + } + + /** + * Returns the deployment timeout of an individual verticle deployment in seconds. If unset the general + * {@link #getDeploymentTimeout()} is returned. + * + * @return the deployment timeout in seconds + */ + public Integer getVerticleDeploymentTimeout() { + return verticleDeploymentTimeout != null ? verticleDeploymentTimeout : getDeploymentTimeout(); + } + + /** + * Set the deployment timeout of an individual verticle deployment in seconds. If equal or smaller than 0, no + * timeout applies to the deployment. If set to {@code null} the general {@link #getDeploymentTimeout()} applies. + * + * @param verticleDeploymentTimeout the deployment timeout in seconds or {@code null} + * @return the {@linkplain NeonBeeConfig} for fluent use + */ + @Fluent + public NeonBeeConfig setVerticleDeploymentTimeout(Integer verticleDeploymentTimeout) { + this.verticleDeploymentTimeout = verticleDeploymentTimeout; + return this; + } + /** * Gets a list of default codecs to register on the event bus. *

diff --git a/src/main/java/io/neonbee/internal/deploy/PendingDeployment.java b/src/main/java/io/neonbee/internal/deploy/PendingDeployment.java index 344ca7b1..1d2e78a3 100644 --- a/src/main/java/io/neonbee/internal/deploy/PendingDeployment.java +++ b/src/main/java/io/neonbee/internal/deploy/PendingDeployment.java @@ -12,6 +12,8 @@ import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; +import io.vertx.core.Promise; +import io.vertx.core.Vertx; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.future.FutureInternal; import io.vertx.core.impl.future.Listener; @@ -26,7 +28,23 @@ public abstract class PendingDeployment extends Deployment implements FutureInte LOGGER.info("Started deployment of {} ...", deployable); - this.deployFuture = deployFuture.map(deploymentId -> { + Future timeoutFuture = deployFuture; + int timeout = neonBee.getConfig().getDeploymentTimeout(deployable.getType()); + if (timeout > 0) { + Vertx vertx = neonBee.getVertx(); + Promise timeoutPromise = Promise.promise(); + // fail the promise after the timeout is expired + long timerId = vertx.setTimer(TimeUnit.SECONDS.toMillis(timeout), nothing -> { + timeoutPromise.fail("Deployment timed-out after " + timeout + " seconds"); + }); + // in case the deployment finished, it completes the promise and we can cancel the timer + deployFuture.onComplete(timeoutPromise).onComplete(deploymentId -> { + vertx.cancelTimer(timerId); + }); + timeoutFuture = timeoutPromise.future(); + } + + this.deployFuture = timeoutFuture.map(deploymentId -> { // in case a deployment doesn't want to specify a own deployment ID, generate one based on the hash code of // the pending deployment (thus all deployables, might just return an empty future) return deploymentId != null ? deploymentId : super.getDeploymentId(); diff --git a/src/test/java/io/neonbee/internal/deploy/DeployableModelsTest.java b/src/test/java/io/neonbee/internal/deploy/DeployableModelsTest.java index 749165fa..11f433aa 100644 --- a/src/test/java/io/neonbee/internal/deploy/DeployableModelsTest.java +++ b/src/test/java/io/neonbee/internal/deploy/DeployableModelsTest.java @@ -1,8 +1,7 @@ package io.neonbee.internal.deploy; import static com.google.common.truth.Truth.assertThat; -import static io.neonbee.NeonBeeMockHelper.defaultVertxMock; -import static io.neonbee.NeonBeeMockHelper.registerNeonBeeMock; +import static io.neonbee.internal.deploy.DeploymentTest.newNeonBeeMockForDeployment; import static io.vertx.core.Future.failedFuture; import static io.vertx.core.Future.succeededFuture; import static java.nio.charset.StandardCharsets.UTF_8; @@ -58,13 +57,12 @@ void testDeployUndeploy() throws NoSuchFieldException, IllegalAccessException { Map.of("okay", new JsonObject().put("namespace", "test").toBuffer().getBytes()), Map.of()); DeployableModels deployable = new DeployableModels(definition); - Vertx vertxMock = defaultVertxMock(); - NeonBee neonBee = registerNeonBeeMock(vertxMock, new NeonBeeOptions.Mutable().setIgnoreClassPath(true)); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(new NeonBeeOptions.Mutable().setIgnoreClassPath(true)); - PendingDeployment deployment = deployable.deploy(neonBee); + PendingDeployment deployment = deployable.deploy(neonBeeMock); assertThat(deployment.succeeded()).isTrue(); Set definitions = - ReflectionHelper.getValueOfPrivateField(neonBee.getModelManager(), "externalModelDefinitions"); + ReflectionHelper.getValueOfPrivateField(neonBeeMock.getModelManager(), "externalModelDefinitions"); assertThat(definitions).contains(definition); assertThat(deployment.undeploy().succeeded()).isTrue(); @@ -77,11 +75,11 @@ void testDeployFailed() { EntityModelDefinition definition = new EntityModelDefinition(Map.of(), Map.of()); DeployableModels deployable = new DeployableModels(definition); - Vertx vertxMock = defaultVertxMock(); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(new NeonBeeOptions.Mutable().setIgnoreClassPath(true)); + Vertx vertxMock = neonBeeMock.getVertx(); when(vertxMock.fileSystem().readDir(any())).thenReturn(failedFuture("any failure")); - NeonBee neonBee = registerNeonBeeMock(vertxMock, new NeonBeeOptions.Mutable().setIgnoreClassPath(true)); - PendingDeployment deployment = deployable.deploy(neonBee); + PendingDeployment deployment = deployable.deploy(neonBeeMock); assertThat(deployment.failed()).isTrue(); assertThat(deployment.cause()).hasMessageThat().isEqualTo("any failure"); assertThat(deployment.undeploy().succeeded()).isTrue(); @@ -90,7 +88,8 @@ void testDeployFailed() { @Test @DisplayName("test read model payloads") void testReadModelPayloads() { - Vertx vertxMock = defaultVertxMock(); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + Vertx vertxMock = neonBeeMock.getVertx(); ClassLoader classLoaderMock = mock(ClassLoader.class); when(classLoaderMock.getResourceAsStream(any())).thenAnswer(invocation -> { @@ -106,7 +105,8 @@ void testReadModelPayloads() { @Test @DisplayName("test scan class path") void testScanClassPath() { - Vertx vertxMock = defaultVertxMock(); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + Vertx vertxMock = neonBeeMock.getVertx(); ClassPathScanner classPathScannerMock = mock(ClassPathScanner.class); when(classPathScannerMock.scanManifestFiles(any(), any())).thenReturn(succeededFuture(List.of("entry"))); @@ -129,8 +129,11 @@ void testScanClassPath() { @Test @DisplayName("test from JAR") void testFromJar() throws IOException { + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + Vertx vertxMock = neonBeeMock.getVertx(); + NeonBeeModuleJar moduleJar = NeonBeeModuleJar.create("testmodule").withModels().build(); - Future deployable = DeployableModels.fromJar(defaultVertxMock(), moduleJar.writeToTempPath()); + Future deployable = DeployableModels.fromJar(vertxMock, moduleJar.writeToTempPath()); assertThat(deployable.succeeded()).isTrue(); assertThat(deployable.result().modelDefinition.getCSNModelDefinitions()) .comparingValuesUsing(Correspondence.from(Arrays::equals, "is not equal to")) diff --git a/src/test/java/io/neonbee/internal/deploy/DeployableModuleTest.java b/src/test/java/io/neonbee/internal/deploy/DeployableModuleTest.java index caffb39e..cde6aed6 100644 --- a/src/test/java/io/neonbee/internal/deploy/DeployableModuleTest.java +++ b/src/test/java/io/neonbee/internal/deploy/DeployableModuleTest.java @@ -2,8 +2,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; -import static io.neonbee.NeonBeeMockHelper.defaultVertxMock; -import static io.neonbee.NeonBeeMockHelper.registerNeonBeeMock; +import static io.neonbee.internal.deploy.DeploymentTest.newNeonBeeMockForDeployment; import static io.neonbee.test.helper.DummyVerticleHelper.DUMMY_VERTICLE; import static io.neonbee.test.helper.FileSystemHelper.createTempDirectory; import static io.vertx.core.Future.succeededFuture; @@ -28,6 +27,7 @@ import com.google.common.truth.Correspondence; +import io.neonbee.NeonBee; import io.neonbee.internal.BasicJar; import io.neonbee.internal.NeonBeeModuleJar; import io.vertx.core.DeploymentOptions; @@ -74,9 +74,11 @@ void testGetDeployablesIsUnmodifiable() { @Test @DisplayName("test that undeploy closes the module class loader") void testUndeployClosesModuleClassLoader() throws IOException { + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + URLClassLoader classLoaderMock = mock(URLClassLoader.class); PendingDeployment deployment = new DeployableModule("module", classLoaderMock, List.of()) - .deploy(registerNeonBeeMock(defaultVertxMock())); + .deploy(neonBeeMock); assertThat(deployment.succeeded()).isTrue(); assertThat(deployment.undeploy().succeeded()).isTrue(); verify(classLoaderMock).close(); @@ -88,8 +90,8 @@ void testFromJar() throws IOException { NeonBeeModuleJar moduleJar = NeonBeeModuleJar.create("testmodule").withVerticles().withModels().build(); Path moduleJarPath = moduleJar.writeToTempPath(); - Vertx vertxMock = defaultVertxMock(); - registerNeonBeeMock(vertxMock); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + Vertx vertxMock = neonBeeMock.getVertx(); Future deployableFuture = DeployableModule.fromJar(vertxMock, moduleJarPath); assertThat(deployableFuture.cause()).isNull(); @@ -122,8 +124,8 @@ void testFromJarWithoutVerticles() throws IOException { NeonBeeModuleJar moduleJar = NeonBeeModuleJar.create("testmodule").withModels().build(); Path moduleJarPath = moduleJar.writeToTempPath(); - Vertx vertxMock = defaultVertxMock(); - registerNeonBeeMock(vertxMock); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + Vertx vertxMock = neonBeeMock.getVertx(); Future deployableFuture = DeployableModule.fromJar(vertxMock, moduleJarPath); assertThat(deployableFuture.cause()).isNull(); @@ -150,8 +152,8 @@ void testFromJarWithoutVerticles() throws IOException { @Test @DisplayName("test fromJar exceptions") void testFromJarExceptions() throws IOException { - Vertx vertxMock = defaultVertxMock(); - registerNeonBeeMock(vertxMock); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + Vertx vertxMock = neonBeeMock.getVertx(); BasicJar noModuleAttribute = new BasicJar(Map.of(), Map.of()); Throwable noModuleAttributeException = diff --git a/src/test/java/io/neonbee/internal/deploy/DeployableTest.java b/src/test/java/io/neonbee/internal/deploy/DeployableTest.java index b497b3fc..200c7e6c 100644 --- a/src/test/java/io/neonbee/internal/deploy/DeployableTest.java +++ b/src/test/java/io/neonbee/internal/deploy/DeployableTest.java @@ -1,6 +1,7 @@ package io.neonbee.internal.deploy; import static com.google.common.truth.Truth.assertThat; +import static io.neonbee.internal.deploy.DeploymentTest.newNeonBeeMockForDeployment; import static io.vertx.core.Future.failedFuture; import static io.vertx.core.Future.succeededFuture; @@ -34,15 +35,15 @@ void testToString() { @DisplayName("test deploy/undeploy of Deployable") void testDeployUndeploy() { DeployableThing deployable = new DeployableThing("foo"); - assertThat(deployable.deploy(null).succeeded()).isTrue(); - assertThat(deployable.deploy(null).undeploy().succeeded()).isTrue(); + assertThat(deployable.deploy().succeeded()).isTrue(); + assertThat(deployable.deploy().undeploy().succeeded()).isTrue(); deployable.undeployFuture = failedFuture("foo"); - assertThat(deployable.deploy(null).succeeded()).isTrue(); - assertThat(deployable.deploy(null).undeploy().failed()).isTrue(); + assertThat(deployable.deploy().succeeded()).isTrue(); + assertThat(deployable.deploy().undeploy().failed()).isTrue(); deployable.deployFuture = failedFuture("foo"); - assertThat(deployable.deploy(null).failed()).isTrue(); + assertThat(deployable.deploy().failed()).isTrue(); } static class DeployableThing extends Deployable { @@ -73,6 +74,10 @@ public String getIdentifier() { return identifier; } + public PendingDeployment deploy() { + return deploy(newNeonBeeMockForDeployment()); + } + @Override public PendingDeployment deploy(NeonBee neonBee) { deploying = true; diff --git a/src/test/java/io/neonbee/internal/deploy/DeployableVerticleTest.java b/src/test/java/io/neonbee/internal/deploy/DeployableVerticleTest.java index 1e310f2f..69b47d1f 100644 --- a/src/test/java/io/neonbee/internal/deploy/DeployableVerticleTest.java +++ b/src/test/java/io/neonbee/internal/deploy/DeployableVerticleTest.java @@ -2,8 +2,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; -import static io.neonbee.NeonBeeMockHelper.defaultVertxMock; -import static io.neonbee.NeonBeeMockHelper.registerNeonBeeMock; +import static io.neonbee.internal.deploy.DeploymentTest.newNeonBeeMockForDeployment; import static io.neonbee.test.helper.DummyVerticleHelper.DUMMY_VERTICLE; import static io.vertx.core.Future.failedFuture; import static io.vertx.core.Future.succeededFuture; @@ -76,13 +75,13 @@ void testGetIdentifier() { void testDeployUndeploy() { DeploymentOptions options = new DeploymentOptions(); - Vertx vertxMock = defaultVertxMock(); - NeonBee neonBee = registerNeonBeeMock(vertxMock, new NeonBeeOptions.Mutable().setIgnoreClassPath(true)); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(new NeonBeeOptions.Mutable().setIgnoreClassPath(true)); + Vertx vertxMock = neonBeeMock.getVertx(); DeployableVerticle deployable1 = new DeployableVerticle(DUMMY_VERTICLE, options); assertThat(deployable1.getIdentifier()).isEqualTo(DUMMY_VERTICLE.getClass().getName()); - PendingDeployment deployment1 = deployable1.deploy(neonBee); + PendingDeployment deployment1 = deployable1.deploy(neonBeeMock); assertThat(deployment1.succeeded()).isTrue(); verify(vertxMock).deployVerticle(DUMMY_VERTICLE, deployable1.options); @@ -92,7 +91,7 @@ void testDeployUndeploy() { DeployableVerticle deployable2 = new DeployableVerticle(DUMMY_VERTICLE.getClass(), options); assertThat(deployable2.getIdentifier()).isEqualTo(DUMMY_VERTICLE.getClass().getName()); - PendingDeployment deployment2 = deployable2.deploy(neonBee); + PendingDeployment deployment2 = deployable2.deploy(neonBeeMock); assertThat(deployment2.succeeded()).isTrue(); verify(vertxMock).deployVerticle(DUMMY_VERTICLE.getClass(), deployable2.options); @@ -103,14 +102,14 @@ void testDeployUndeploy() { @Test @DisplayName("test deploy failed") void testDeployFailed() { - Vertx vertxMock = defaultVertxMock(); - NeonBee neonBee = registerNeonBeeMock(vertxMock, new NeonBeeOptions.Mutable().setIgnoreClassPath(true)); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(new NeonBeeOptions.Mutable().setIgnoreClassPath(true)); + Vertx vertxMock = neonBeeMock.getVertx(); when(vertxMock.deployVerticle(any(Verticle.class), any(DeploymentOptions.class))) .thenReturn(failedFuture("any failure")); DeployableVerticle deployable = new DeployableVerticle(DUMMY_VERTICLE, new DeploymentOptions()); - PendingDeployment deployment = deployable.deploy(neonBee); + PendingDeployment deployment = deployable.deploy(neonBeeMock); assertThat(deployment.failed()).isTrue(); assertThat(deployment.cause()).hasMessageThat().isEqualTo("any failure"); assertThat(deployment.undeploy().succeeded()).isTrue(); @@ -119,9 +118,10 @@ void testDeployFailed() { @Test @DisplayName("test read not found verticle config") void testReadVerticleConfigNotFound() throws IOException { - Vertx vertxMock = defaultVertxMock(); + NeonBee neonBeeMock = newNeonBeeMockForDeployment( + new NeonBeeOptions.Mutable().setWorkingDirectory(Path.of(""))); + Vertx vertxMock = neonBeeMock.getVertx(); FileSystem fileSystemMock = vertxMock.fileSystem(); - registerNeonBeeMock(vertxMock, new NeonBeeOptions.Mutable().setWorkingDirectory(Path.of(""))); when(fileSystemMock.readFile(any())) .thenReturn(failedFuture(new FileSystemException(new NoSuchFileException("file")))); @@ -141,9 +141,10 @@ void testReadVerticleConfigNotFound() throws IOException { @Test @DisplayName("test read not found verticle config not found with default") void testReadVerticleConfigNotFoundWithDefault() throws IOException { - Vertx vertxMock = defaultVertxMock(); + NeonBee neonBeeMock = newNeonBeeMockForDeployment( + new NeonBeeOptions.Mutable().setWorkingDirectory(Path.of(""))); + Vertx vertxMock = neonBeeMock.getVertx(); FileSystem fileSystemMock = vertxMock.fileSystem(); - registerNeonBeeMock(vertxMock, new NeonBeeOptions.Mutable().setWorkingDirectory(Path.of(""))); when(fileSystemMock.readFile(any())) .thenReturn(failedFuture(new FileSystemException(new NoSuchFileException("file")))); @@ -156,9 +157,10 @@ void testReadVerticleConfigNotFoundWithDefault() throws IOException { @Test @DisplayName("test read verticle config") void testReadVerticleConfig() throws IOException { - Vertx vertxMock = defaultVertxMock(); + NeonBee neonBeeMock = newNeonBeeMockForDeployment( + new NeonBeeOptions.Mutable().setWorkingDirectory(Path.of(""))); + Vertx vertxMock = neonBeeMock.getVertx(); FileSystem fileSystemMock = vertxMock.fileSystem(); - registerNeonBeeMock(vertxMock, new NeonBeeOptions.Mutable().setWorkingDirectory(Path.of(""))); when(fileSystemMock.readFile(any())) .thenReturn(failedFuture(new FileSystemException(new NoSuchFileException("file")))); @@ -172,9 +174,10 @@ void testReadVerticleConfig() throws IOException { @Test @DisplayName("test read verticle config with default") void testReadVerticleConfigWithDefault() throws IOException { - Vertx vertxMock = defaultVertxMock(); + NeonBee neonBeeMock = newNeonBeeMockForDeployment( + new NeonBeeOptions.Mutable().setWorkingDirectory(Path.of(""))); + Vertx vertxMock = neonBeeMock.getVertx(); FileSystem fileSystemMock = vertxMock.fileSystem(); - registerNeonBeeMock(vertxMock, new NeonBeeOptions.Mutable().setWorkingDirectory(Path.of(""))); when(fileSystemMock.readFile(any())) .thenReturn(failedFuture(new FileSystemException(new NoSuchFileException("file")))); @@ -189,9 +192,10 @@ void testReadVerticleConfigWithDefault() throws IOException { @Test @DisplayName("test read verticle config failure") void testReadVerticleConfigFailure() throws IOException { - Vertx vertxMock = defaultVertxMock(); + NeonBee neonBeeMock = newNeonBeeMockForDeployment( + new NeonBeeOptions.Mutable().setWorkingDirectory(Path.of(""))); + Vertx vertxMock = neonBeeMock.getVertx(); FileSystem fileSystemMock = vertxMock.fileSystem(); - registerNeonBeeMock(vertxMock, new NeonBeeOptions.Mutable().setWorkingDirectory(Path.of(""))); when(fileSystemMock.readFile(any())) .thenReturn(failedFuture(new FileSystemException(new NoSuchFileException("file")))); @@ -206,8 +210,8 @@ void testReadVerticleConfigFailure() throws IOException { @DisplayName("test scan class path") @SuppressWarnings("rawtypes") void testScanClassPath() throws ClassNotFoundException { - Vertx vertxMock = defaultVertxMock(); - registerNeonBeeMock(vertxMock); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + Vertx vertxMock = neonBeeMock.getVertx(); ClassPathScanner classPathScannerMock = mock(ClassPathScanner.class); when(classPathScannerMock.scanManifestFiles(any(), any())) @@ -234,8 +238,8 @@ void testScanClassPath() throws ClassNotFoundException { @Test @DisplayName("test from JAR") void testFromJar() throws IOException { - Vertx vertxMock = defaultVertxMock(); - registerNeonBeeMock(vertxMock); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + Vertx vertxMock = neonBeeMock.getVertx(); NeonBeeModuleJar moduleJar = NeonBeeModuleJar.create("testmodule").withVerticles().build(); Path moduleJarPath = moduleJar.writeToTempPath(); @@ -255,8 +259,8 @@ void testFromJar() throws IOException { @DisplayName("test from class name") @SuppressWarnings("rawtypes") void testFromClassName() throws ClassNotFoundException { - Vertx vertxMock = defaultVertxMock(); - registerNeonBeeMock(vertxMock); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + Vertx vertxMock = neonBeeMock.getVertx(); JsonObject config = new JsonObject().put("config", new JsonObject().put("foo", "bar")); @@ -288,8 +292,8 @@ void testFromClassName() throws ClassNotFoundException { @Test @DisplayName("test from wrong class name") void testFromWrongClassName() throws ClassNotFoundException { - Vertx vertxMock = defaultVertxMock(); - registerNeonBeeMock(vertxMock); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + Vertx vertxMock = neonBeeMock.getVertx(); ClassLoader classLoaderMock = mock(ClassLoader.class); when(classLoaderMock.loadClass(any())).thenThrow(ClassNotFoundException.class); @@ -303,8 +307,8 @@ void testFromWrongClassName() throws ClassNotFoundException { @Test @DisplayName("test from class") void testFromClass() { - Vertx vertxMock = defaultVertxMock(); - registerNeonBeeMock(vertxMock); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + Vertx vertxMock = neonBeeMock.getVertx(); JsonObject config = new JsonObject().put("config", new JsonObject().put("foo", "bar")); @@ -322,8 +326,8 @@ void testFromClass() { @Test @DisplayName("test from verticle") void testFromVerticle() { - Vertx vertxMock = defaultVertxMock(); - registerNeonBeeMock(vertxMock); + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + Vertx vertxMock = neonBeeMock.getVertx(); JsonObject config = new JsonObject().put("config", new JsonObject().put("foo", "bar")); diff --git a/src/test/java/io/neonbee/internal/deploy/DeployablesTest.java b/src/test/java/io/neonbee/internal/deploy/DeployablesTest.java index 0b121b1c..5508a7eb 100644 --- a/src/test/java/io/neonbee/internal/deploy/DeployablesTest.java +++ b/src/test/java/io/neonbee/internal/deploy/DeployablesTest.java @@ -1,6 +1,7 @@ package io.neonbee.internal.deploy; import static com.google.common.truth.Truth.assertThat; +import static io.neonbee.internal.deploy.DeploymentTest.newNeonBeeMockForDeployment; import static io.vertx.core.Future.failedFuture; import static io.vertx.core.Future.succeededFuture; import static org.junit.Assert.assertThrows; @@ -12,6 +13,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import io.neonbee.NeonBee; import io.neonbee.internal.deploy.DeployableTest.DeployableThing; import io.vertx.core.Future; @@ -72,11 +74,13 @@ void testKeepPartialDeployments() { @Test @DisplayName("test deploy/undeploy") void testDeployUndeploy() { + NeonBee neonBeeMock = newNeonBeeMockForDeployment(); + DeployableThing deployable1 = new DeployableThing("foo"); DeployableThing deployable2 = new DeployableThing("bar"); List deployableThings = List.of(deployable1, deployable2); Deployables deployables = new Deployables(deployableThings); - PendingDeployment deployment = deployables.deploy(null); + PendingDeployment deployment = deployables.deploy(neonBeeMock); // regular deploying and undeploying works assertThat(deployment.succeeded()).isTrue(); @@ -91,7 +95,7 @@ void testDeployUndeploy() { // if any deployment fails, the stuff that is already deployed gets undeployed deployableThings.forEach(DeployableThing::reset); deployable1.deployFuture = failedFuture("fail"); - deployment = deployables.deploy(null); + deployment = deployables.deploy(neonBeeMock); assertThat(deployment.succeeded()).isFalse(); assertThat(deployable1.deploying).isTrue(); assertThat(deployable2.deploying).isTrue(); @@ -101,7 +105,7 @@ void testDeployUndeploy() { // if any deployment fails, the stuff that is already deployed gets undeployed (other way around) deployableThings.forEach(DeployableThing::reset); deployable2.deployFuture = failedFuture("fail"); - deployment = deployables.deploy(null); + deployment = deployables.deploy(neonBeeMock); assertThat(deployment.succeeded()).isFalse(); assertThat(deployable1.deploying).isTrue(); assertThat(deployable2.deploying).isTrue(); @@ -111,7 +115,7 @@ void testDeployUndeploy() { // undeployments can fail too, but at least we have to attempt them deployableThings.forEach(DeployableThing::reset); deployable1.undeployFuture = failedFuture("fail"); - deployment = deployables.deploy(null); + deployment = deployables.deploy(neonBeeMock); assertThat(deployment.succeeded()).isTrue(); assertThat(deployable1.deploying).isTrue(); assertThat(deployable2.deploying).isTrue(); @@ -125,7 +129,7 @@ void testDeployUndeploy() { deployableThings.forEach(DeployableThing::reset); deployable1.deployFuture = failedFuture("deployfail"); deployable2.undeployFuture = failedFuture("undeployfail"); - deployment = deployables.deploy(null); + deployment = deployables.deploy(neonBeeMock); assertThat(deployment.succeeded()).isFalse(); assertThat(deployable1.deploying).isTrue(); assertThat(deployable2.deploying).isTrue(); @@ -135,7 +139,7 @@ void testDeployUndeploy() { // undeploy hook should be called and be able to influence the result on success deployableThings.forEach(DeployableThing::reset); - deployment = deployables.deploy(null, undeploymentResult -> { + deployment = deployables.deploy(neonBeeMock, undeploymentResult -> { return failedFuture("testfailed"); }); assertThat(deployment.succeeded()).isTrue(); @@ -144,12 +148,12 @@ void testDeployUndeploy() { // undeploy hook should be called in any case, but not influence the original result on failure deployableThings.forEach(DeployableThing::reset); deployable1.undeployFuture = failedFuture("undeployfailed"); - deployment = deployables.deploy(null, undeploymentResult -> { + deployment = deployables.deploy(neonBeeMock, undeploymentResult -> { return succeededFuture(); }); assertThat(deployment.succeeded()).isTrue(); assertThat(deployment.undeploy().cause().getMessage()).isEqualTo("undeployfailed"); - deployment = deployables.deploy(null, undeploymentResult -> { + deployment = deployables.deploy(neonBeeMock, undeploymentResult -> { return failedFuture("testfailed"); }); assertThat(deployment.succeeded()).isTrue(); @@ -159,7 +163,7 @@ void testDeployUndeploy() { deployableThings.forEach(DeployableThing::reset); deployable1.deployFuture = failedFuture("fail"); deployables.keepPartialDeployment(); - deployment = deployables.deploy(null); + deployment = deployables.deploy(neonBeeMock); assertThat(deployment.succeeded()).isFalse(); assertThat(deployable1.deploying).isTrue(); assertThat(deployable2.deploying).isTrue(); diff --git a/src/test/java/io/neonbee/internal/deploy/DeploymentTest.java b/src/test/java/io/neonbee/internal/deploy/DeploymentTest.java index 9a4668bf..4bd6667b 100644 --- a/src/test/java/io/neonbee/internal/deploy/DeploymentTest.java +++ b/src/test/java/io/neonbee/internal/deploy/DeploymentTest.java @@ -1,15 +1,23 @@ package io.neonbee.internal.deploy; import static com.google.common.truth.Truth.assertThat; +import static io.neonbee.NeonBeeMockHelper.defaultVertxMock; +import static io.neonbee.NeonBeeMockHelper.registerNeonBeeMock; import static io.vertx.core.Future.succeededFuture; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doAnswer; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import io.neonbee.NeonBee; +import io.neonbee.NeonBeeOptions; +import io.neonbee.config.NeonBeeConfig; import io.neonbee.internal.deploy.DeployableTest.DeployableThing; import io.vertx.core.Future; +import io.vertx.core.Vertx; import io.vertx.junit5.VertxExtension; @ExtendWith(VertxExtension.class) @@ -18,7 +26,7 @@ class DeploymentTest { @DisplayName("should return the deployable") void getDeployableTest() { DeployableThing deployable = new DeployableThing("test"); - Deployment deployment = new TestDeployment(null, deployable); + Deployment deployment = new TestDeployment(deployable); assertThat(deployment.getDeployable()).isSameInstanceAs(deployable); } @@ -26,13 +34,13 @@ void getDeployableTest() { @DisplayName("test deployment ID") void getDeploymentIdTest() { DeployableThing deployable = new DeployableThing("test"); - Deployment deployment = new TestDeployment(null, deployable); + Deployment deployment = new TestDeployment(deployable); assertThat(deployment.getDeploymentId()).isEqualTo("test@" + Integer.toHexString(deployment.hashCode())); } private static class TestDeployment extends Deployment { - protected TestDeployment(NeonBee neonBee, Deployable deployable) { - super(neonBee, deployable); + protected TestDeployment(Deployable deployable) { + super(newNeonBeeMockForDeployment(), deployable); } @Override @@ -40,4 +48,16 @@ public Future undeploy() { return succeededFuture(); } } + + public static NeonBee newNeonBeeMockForDeployment() { + return newNeonBeeMockForDeployment(new NeonBeeOptions.Mutable()); + } + + public static NeonBee newNeonBeeMockForDeployment(NeonBeeOptions options) { + Vertx vertxMock = defaultVertxMock(); + // we have to disable / ignore timers, as otherwise the time-out would apply every time with the mock + doAnswer(invocation -> -1L).when(vertxMock).setTimer(anyLong(), any()); + + return registerNeonBeeMock(vertxMock, options, new NeonBeeConfig().setDeploymentTimeout(-1)); + } } diff --git a/src/test/java/io/neonbee/internal/deploy/PendingDeploymentTest.java b/src/test/java/io/neonbee/internal/deploy/PendingDeploymentTest.java index 88d9783d..dd91c7c9 100644 --- a/src/test/java/io/neonbee/internal/deploy/PendingDeploymentTest.java +++ b/src/test/java/io/neonbee/internal/deploy/PendingDeploymentTest.java @@ -1,13 +1,21 @@ package io.neonbee.internal.deploy; import static com.google.common.truth.Truth.assertThat; +import static io.neonbee.NeonBeeMockHelper.defaultVertxMock; +import static io.neonbee.NeonBeeMockHelper.registerNeonBeeMock; +import static io.neonbee.internal.deploy.DeploymentTest.newNeonBeeMockForDeployment; import static io.vertx.core.Future.failedFuture; import static io.vertx.core.Future.succeededFuture; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.util.concurrent.TimeUnit; @@ -16,9 +24,12 @@ import org.junit.jupiter.api.Test; +import io.neonbee.NeonBee; +import io.neonbee.config.NeonBeeConfig; import io.neonbee.internal.deploy.DeployableTest.DeployableThing; import io.vertx.core.Future; import io.vertx.core.Promise; +import io.vertx.core.Vertx; import io.vertx.core.impl.future.FutureInternal; class PendingDeploymentTest { @@ -123,11 +134,64 @@ void testFutureInterface() { verify(futureMock).timeout(10, TimeUnit.SECONDS); } + @Test + void deploymentTimeoutTest() { + Vertx vertxMock = defaultVertxMock(); + // we have to disable / ignore timers, as otherwise the time-out would apply every time with the mock + doAnswer(invocation -> -1L).when(vertxMock).setTimer(anyLong(), any()); + + NeonBee neonBeeMock = registerNeonBeeMock(vertxMock, new NeonBeeConfig().setDeploymentTimeout(-1)); + new TestPendingDeployment(neonBeeMock, Promise.promise().future()); + verify(vertxMock, times(0)).setTimer(anyLong(), any()); + + reset(vertxMock); + neonBeeMock = registerNeonBeeMock(vertxMock, new NeonBeeConfig().setDeploymentTimeout(42)); + new TestPendingDeployment(neonBeeMock, Promise.promise().future()); + verify(vertxMock).setTimer(eq(42000L), any()); + + reset(vertxMock); + neonBeeMock = registerNeonBeeMock(vertxMock, new NeonBeeConfig().setDeploymentTimeout(1337)); + new TestPendingDeployment(neonBeeMock, "models", Promise.promise().future()); + verify(vertxMock).setTimer(eq(1337000L), any()); + + reset(vertxMock); + neonBeeMock = registerNeonBeeMock(vertxMock, new NeonBeeConfig().setModelsDeploymentTimeout(48)); + new TestPendingDeployment(neonBeeMock, "models", Promise.promise().future()); + verify(vertxMock).setTimer(eq(48000L), any()); + new TestPendingDeployment(neonBeeMock, "verticle", Promise.promise().future()); + verify(vertxMock).setTimer(eq(TimeUnit.SECONDS.toMillis(NeonBeeConfig.DEFAULT_DEPLOYMENT_TIMEOUT)), any()); + + reset(vertxMock); + neonBeeMock = registerNeonBeeMock(vertxMock, new NeonBeeConfig().setModuleDeploymentTimeout(-1)); + new TestPendingDeployment(neonBeeMock, "module", Promise.promise().future()); + verify(vertxMock, times(0)).setTimer(anyLong(), any()); + + reset(vertxMock); + neonBeeMock = registerNeonBeeMock(vertxMock, new NeonBeeConfig().setVerticleDeploymentTimeout(10) + .setVerticleDeploymentTimeout(null)); + new TestPendingDeployment(neonBeeMock, "verticle", Promise.promise().future()); + verify(vertxMock).setTimer(eq(TimeUnit.SECONDS.toMillis(NeonBeeConfig.DEFAULT_DEPLOYMENT_TIMEOUT)), any()); + + } + private static class TestPendingDeployment extends PendingDeployment { public String undeployDeploymentId; protected TestPendingDeployment(Future deployFuture) { - super(null, new DeployableThing("foo"), deployFuture); + this(newNeonBeeMockForDeployment(), deployFuture); + } + + protected TestPendingDeployment(NeonBee neonBee, Future deployFuture) { + this(neonBee, (String) null, deployFuture); + } + + protected TestPendingDeployment(NeonBee neonBee, String deployableType, Future deployFuture) { + super(neonBee, new DeployableThing("foo") { + @Override + public String getType() { + return deployableType == null ? super.getType() : deployableType; + } + }, deployFuture); } @Override