Skip to content

Commit 1bf98e5

Browse files
committed
feat: handling for failed deployments during boot
Previously any failure during verticle deployment during boot could lead to the bootstrap of NeonBee failing / undeployment of all verticles. This commit introduces a new "bootDeploymentHandling" option in the NeonBeeConfig, which lets the user specify how to deal with failures during verticles / module deployments during boot. The change is compatible with how it is handled currently (FAIL_ON_ERROR), however also provides options to undeploy any failing modules (UNDEPLOY_FAILING) or even keeping modules that could only be partially deployed (KEEP_PARTIAL).
1 parent 1656dfb commit 1bf98e5

File tree

4 files changed

+111
-18
lines changed

4 files changed

+111
-18
lines changed

src/generated/java/io/neonbee/config/NeonBeeConfigConverter.java

+9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ public class NeonBeeConfigConverter {
1919
static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, NeonBeeConfig obj) {
2020
for (java.util.Map.Entry<String, Object> member : json) {
2121
switch (member.getKey()) {
22+
case "bootDeploymentHandling":
23+
if (member.getValue() instanceof String) {
24+
obj.setBootDeploymentHandling(
25+
io.neonbee.config.NeonBeeConfig.BootDeploymentHandling.valueOf((String) member.getValue()));
26+
}
27+
break;
2228
case "defaultThreadingModel":
2329
if (member.getValue() instanceof String) {
2430
obj.setDefaultThreadingModel(io.vertx.core.ThreadingModel.valueOf((String) member.getValue()));
@@ -116,6 +122,9 @@ static void toJson(NeonBeeConfig obj, JsonObject json) {
116122
}
117123

118124
static void toJson(NeonBeeConfig obj, java.util.Map<String, Object> json) {
125+
if (obj.getBootDeploymentHandling() != null) {
126+
json.put("bootDeploymentHandling", obj.getBootDeploymentHandling().name());
127+
}
119128
if (obj.getDefaultThreadingModel() != null) {
120129
json.put("defaultThreadingModel", obj.getDefaultThreadingModel().name());
121130
}

src/main/java/io/neonbee/NeonBee.java

+51-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.neonbee;
22

3+
import static io.neonbee.config.NeonBeeConfig.BootDeploymentHandling.FAIL_ON_ERROR;
4+
import static io.neonbee.config.NeonBeeConfig.BootDeploymentHandling.KEEP_PARTIAL;
35
import static io.neonbee.internal.deploy.DeployableModule.fromJar;
46
import static io.neonbee.internal.deploy.DeployableVerticle.fromClass;
57
import static io.neonbee.internal.deploy.DeployableVerticle.fromVerticle;
@@ -44,6 +46,7 @@
4446
import io.neonbee.cluster.ClusterManagerFactory;
4547
import io.neonbee.config.HealthConfig;
4648
import io.neonbee.config.NeonBeeConfig;
49+
import io.neonbee.config.NeonBeeConfig.BootDeploymentHandling;
4750
import io.neonbee.config.ServerConfig;
4851
import io.neonbee.data.DataException;
4952
import io.neonbee.data.DataQuery;
@@ -566,21 +569,25 @@ private Future<Void> deploySystemVerticles() {
566569
List<Future<? extends Deployable>> requiredVerticles = new ArrayList<>();
567570
requiredVerticles.add(fromClass(vertx, ConsolidationVerticle.class, new JsonObject().put("instances", 1)));
568571
requiredVerticles.add(fromClass(vertx, LoggerManagerVerticle.class));
569-
570-
List<Future<Optional<? extends Deployable>>> optionalVerticles = new ArrayList<>();
571572
if (Optional.ofNullable(config.getHealthConfig()).map(HealthConfig::isEnabled).orElse(true)) {
572573
requiredVerticles.add(fromClass(vertx, HealthCheckVerticle.class));
573574
}
575+
576+
List<Future<Optional<? extends Deployable>>> optionalVerticles = new ArrayList<>();
574577
optionalVerticles.add(deployableWatchVerticle(options.getModelsDirectory(), ModelRefreshVerticle::new));
575578
optionalVerticles.add(deployableWatchVerticle(options.getModulesDirectory(), DeployerVerticle::new));
576579
optionalVerticles.add(deployableRedeployEntitiesJobVerticle(options));
577580

578581
LOGGER.info("Deploying system verticles ...");
579-
return all(List.of(fromDeployables(requiredVerticles).compose(allTo(this)),
580-
all(optionalVerticles).map(CompositeFuture::list).map(optionals -> {
581-
return optionals.stream().map(Optional.class::cast).filter(Optional::isPresent).map(Optional::get)
582-
.map(Deployable.class::cast).toList();
583-
}).map(Deployables::new).compose(anyTo(this)))).mapEmpty();
582+
return all(fromDeployables(requiredVerticles).compose(allTo(this)).onFailure(throwable -> {
583+
LOGGER.error("Failed to deploy (some / all) required system verticle(s)", throwable);
584+
}), all(optionalVerticles).map(CompositeFuture::list).map(optionals -> {
585+
return optionals.stream().map(Optional.class::cast).filter(Optional::isPresent).map(Optional::get)
586+
.map(Deployable.class::cast).toList();
587+
}).map(Deployables::new).compose(anyTo(this)).onFailure(throwable -> {
588+
LOGGER.error("Failed to deploy (some / all) optional system verticle(s), bootstrap will continue",
589+
throwable);
590+
}).otherwiseEmpty()).mapEmpty();
584591
}
585592

586593
private Future<Optional<? extends Deployable>> deployableWatchVerticle(
@@ -621,7 +628,9 @@ private Future<Optional<? extends Deployable>> deployableRedeployEntitiesJobVert
621628
private Future<Void> deployServerVerticle() {
622629
LOGGER.info("Deploying server verticle ...");
623630
return fromClass(vertx, ServerVerticle.class, new JsonObject().put("instances", NUMBER_DEFAULT_INSTANCES))
624-
.compose(deployable -> deployable.deploy(this)).mapEmpty();
631+
.compose(deployable -> deployable.deploy(this)).onFailure(throwable -> {
632+
LOGGER.error("Failed to deploy server verticle", throwable);
633+
}).mapEmpty();
625634
}
626635

627636
/**
@@ -638,11 +647,7 @@ private Future<Void> deployClassPathVerticles() {
638647
return scanForDeployableClasses(vertx).compose(deployableClasses -> fromDeployables(deployableClasses.stream()
639648
.filter(verticleClass -> filterByAutoDeployAndProfiles(verticleClass, options.getActiveProfiles()))
640649
.map(verticleClass -> fromClass(vertx, verticleClass)).collect(Collectors.toList())))
641-
.onSuccess(deployables -> {
642-
if (LOGGER.isInfoEnabled()) {
643-
LOGGER.info("Deploy class path verticle(s) {}.", deployables.getIdentifier());
644-
}
645-
}).compose(allTo(this)).mapEmpty();
650+
.compose(handleBootDeployment("class path verticle(s)"));
646651
}
647652

648653
@VisibleForTesting
@@ -665,7 +670,39 @@ private Future<Void> deployModules() {
665670

666671
LOGGER.info("Deploying module(s) ...");
667672
return fromDeployables(moduleJarPaths.stream().map(moduleJarPath -> fromJar(vertx, moduleJarPath))
668-
.collect(Collectors.toList())).compose(allTo(this)).mapEmpty();
673+
.collect(Collectors.toList())).compose(handleBootDeployment("module(s)"));
674+
}
675+
676+
private Function<Deployables, Future<Void>> handleBootDeployment(String deploymentType) {
677+
BootDeploymentHandling handling = config.getBootDeploymentHandling();
678+
return deployables -> {
679+
// in case we should keep partial deployments, for every deployable that we are about to deploy
680+
// set the keep partial deployment flag, so that in case there is an error we don't undeploy
681+
if (handling == KEEP_PARTIAL) {
682+
for (Deployable deployable : deployables.getDeployables()) {
683+
if (deployable instanceof Deployables) {
684+
((Deployables) deployable).keepPartialDeployment();
685+
}
686+
}
687+
}
688+
689+
return (handling == FAIL_ON_ERROR ? allTo(this) : anyTo(this)).apply(deployables)
690+
.onSuccess(deployments -> {
691+
if (LOGGER.isInfoEnabled()) {
692+
LOGGER.info("Successfully deployed all {} {}",
693+
deploymentType, deployments.getDeploymentId());
694+
}
695+
}).recover(throwable -> {
696+
if (LOGGER.isErrorEnabled()) {
697+
LOGGER.error("Failed to deploy (some / all) {}{}",
698+
deploymentType, handling == FAIL_ON_ERROR ? "" : ", bootstrap will continue",
699+
throwable);
700+
}
701+
702+
// abort the boot process if any class path verticle failed to deploy
703+
return handling == FAIL_ON_ERROR ? failedFuture(throwable) : succeededFuture();
704+
}).mapEmpty();
705+
};
669706
}
670707

671708
@VisibleForTesting

src/main/java/io/neonbee/config/NeonBeeConfig.java

+48
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.neonbee.config;
22

3+
import static io.neonbee.config.NeonBeeConfig.BootDeploymentHandling.FAIL_ON_ERROR;
34
import static io.neonbee.internal.helper.ConfigHelper.notFound;
45
import static io.neonbee.internal.helper.ConfigHelper.readConfig;
56
import static io.neonbee.internal.helper.ConfigHelper.rephraseConfigNames;
@@ -44,6 +45,31 @@
4445
@DataObject
4546
@JsonGen(publicConverter = false)
4647
public class NeonBeeConfig {
48+
/**
49+
* How (non-system related) deployments like verticles and modules are handled during booting up {@link NeonBee}.
50+
*/
51+
public enum BootDeploymentHandling {
52+
/**
53+
* Abort the {@link NeonBee} boot, if any (non-system related) verticle / module deployment fails to be
54+
* deployed.
55+
*/
56+
FAIL_ON_ERROR,
57+
58+
/**
59+
* Log any failure of a (non-system related) verticle / module deployments, but continue booting up
60+
* {@link NeonBee} otherwise, while discarding / undeploying any partial deployments (i.e. if a module fails to
61+
* deploy, all deployables related to that module will be undeployed again).
62+
*/
63+
UNDEPLOY_FAILING,
64+
65+
/**
66+
* Log any failure of a (non-system related) verticle / module deployments, but continue booting up
67+
* {@link NeonBee} otherwise, while keeping partial deployments (i.e. if a module fails to deploy, some
68+
* verticles might still stay deployed).
69+
*/
70+
KEEP_PARTIAL
71+
}
72+
4773
/**
4874
* The default timeout for an event bus request.
4975
*/
@@ -74,6 +100,8 @@ public class NeonBeeConfig {
74100

75101
private int eventBusTimeout = DEFAULT_EVENT_BUS_TIMEOUT;
76102

103+
private BootDeploymentHandling bootDeploymentHandling = FAIL_ON_ERROR;
104+
77105
private int deploymentTimeout = DEFAULT_DEPLOYMENT_TIMEOUT;
78106

79107
private Integer modelsDeploymentTimeout;
@@ -258,6 +286,26 @@ public NeonBeeConfig setEventBusTimeout(int eventBusTimeout) {
258286
return this;
259287
}
260288

289+
/**
290+
* Gets how {@link NeonBee} handles failures deploying verticles / modules during boot.
291+
*
292+
* @return the selected boot deployment handling method
293+
*/
294+
public BootDeploymentHandling getBootDeploymentHandling() {
295+
return bootDeploymentHandling;
296+
}
297+
298+
/**
299+
* Sets how {@link NeonBee} should handle failures deploying verticles / modules during boot.
300+
*
301+
* @param bootDeploymentHandling the selected boot deployment handling method
302+
* @return the {@linkplain NeonBeeConfig} for fluent use
303+
*/
304+
public NeonBeeConfig setBootDeploymentHandling(BootDeploymentHandling bootDeploymentHandling) {
305+
this.bootDeploymentHandling = bootDeploymentHandling;
306+
return this;
307+
}
308+
261309
/**
262310
* Returns the general deployment timeout for an individual deployment of any type in seconds. If unset / equal or
263311
* smaller than 0, no timeout applies to the deployment.

src/main/java/io/neonbee/internal/deploy/Deployables.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,9 @@ protected Future<Void> undeploy(String deploymentId) {
167167

168168
getDeployables().stream().map(deployable -> deployable.deploy(neonBee)).forEach(pendingDeployments::add);
169169
// when we should keep partial deployments use a joinComposite, so we wait for all deployments to finish
170-
// independent if a single one fails or not. in case we should not keep partial deployments (default) use
171-
// allComposite here, which will fail, when one deployment fails, and thus we can start undeploying all
172-
// succeeded
173-
// (or to be succeeded pending deployments) as unfortunately there is no way to cancel active deployments
170+
// independent if a single one fails. in case we should not keep partial deployments (default) use allComposite
171+
// here, which will fail, when one deployment fails, and thus we can start undeploying all succeeded (or to be
172+
// succeeded pending deployments) as unfortunately there is no way to cancel active deployments
174173
(keepPartialDeployment ? Future.join(pendingDeployments) : Future.all(pendingDeployments))
175174
.onComplete(deployPromise);
176175

0 commit comments

Comments
 (0)