diff --git a/SingularityBase/src/main/java/com/hubspot/singularity/api/SingularityBounceRequest.java b/SingularityBase/src/main/java/com/hubspot/singularity/api/SingularityBounceRequest.java index 6604201ff9..d5f62fca11 100644 --- a/SingularityBase/src/main/java/com/hubspot/singularity/api/SingularityBounceRequest.java +++ b/SingularityBase/src/main/java/com/hubspot/singularity/api/SingularityBounceRequest.java @@ -1,5 +1,7 @@ package com.hubspot.singularity.api; +import java.util.UUID; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Optional; @@ -18,6 +20,10 @@ public SingularityBounceRequest(@JsonProperty("incremental") Optional i this.skipHealthchecks = skipHealthchecks; } + public static SingularityBounceRequest defaultRequest() { + return new SingularityBounceRequest(Optional.absent(), Optional.absent(), Optional.absent(), Optional.of(UUID.randomUUID().toString()), Optional.absent()); + } + @ApiModelProperty(required=false, value="If present and set to true, old tasks will be killed as soon as replacement tasks are available, instead of waiting for all replacement tasks to be healthy") public Optional getIncremental() { return incremental; diff --git a/SingularityService/src/main/java/com/hubspot/singularity/config/SingularityConfiguration.java b/SingularityService/src/main/java/com/hubspot/singularity/config/SingularityConfiguration.java index 0e9b08611c..deb3edb8e3 100644 --- a/SingularityService/src/main/java/com/hubspot/singularity/config/SingularityConfiguration.java +++ b/SingularityService/src/main/java/com/hubspot/singularity/config/SingularityConfiguration.java @@ -73,6 +73,9 @@ public class SingularityConfiguration extends Configuration { @JsonProperty("database") private DataSourceFactory databaseConfiguration; + @Min(value = 1, message = "Must be positive and non-zero") + private int defaultBounceExpirationMinutes = 60; + @NotNull private SlavePlacement defaultSlavePlacement = SlavePlacement.GREEDY; @@ -339,6 +342,14 @@ public Optional getDatabaseConfiguration() { return Optional.fromNullable(databaseConfiguration); } + public int getDefaultBounceExpirationMinutes() { + return defaultBounceExpirationMinutes; + } + + public void setDefaultBounceExpirationMinutes(int defaultBounceExpirationMinutes) { + this.defaultBounceExpirationMinutes = defaultBounceExpirationMinutes; + } + public SlavePlacement getDefaultSlavePlacement() { return defaultSlavePlacement; } diff --git a/SingularityService/src/main/java/com/hubspot/singularity/resources/RequestResource.java b/SingularityService/src/main/java/com/hubspot/singularity/resources/RequestResource.java index 712733e26b..6ae365a0e1 100644 --- a/SingularityService/src/main/java/com/hubspot/singularity/resources/RequestResource.java +++ b/SingularityService/src/main/java/com/hubspot/singularity/resources/RequestResource.java @@ -179,10 +179,10 @@ public SingularityRequestParent bounce(@ApiParam("The request ID to bounce") @Pa if (bounceRequest.isPresent()) { actionId = bounceRequest.get().getActionId(); message = bounceRequest.get().getMessage(); + } - if (bounceRequest.get().getDurationMillis().isPresent() && !actionId.isPresent()) { - actionId = Optional.of(UUID.randomUUID().toString()); - } + if (!actionId.isPresent()) { + actionId = Optional.of(UUID.randomUUID().toString()); } final String deployId = getAndCheckDeployId(requestId); @@ -195,10 +195,8 @@ public SingularityRequestParent bounce(@ApiParam("The request ID to bounce") @Pa requestManager.bounce(requestWithState.getRequest(), System.currentTimeMillis(), JavaUtils.getUserEmail(user), message); - if (bounceRequest.isPresent() && bounceRequest.get().getDurationMillis().isPresent()) { - requestManager.saveExpiringObject(new SingularityExpiringBounce(requestId, deployId, JavaUtils.getUserEmail(user), - System.currentTimeMillis(), bounceRequest.get(), actionId.get())); - } + requestManager.saveExpiringObject(new SingularityExpiringBounce(requestId, deployId, JavaUtils.getUserEmail(user), + System.currentTimeMillis(), bounceRequest.or(SingularityBounceRequest.defaultRequest()), actionId.get())); return fillEntireRequest(requestWithState); } diff --git a/SingularityService/src/main/java/com/hubspot/singularity/scheduler/SingularityExpiringUserActionPoller.java b/SingularityService/src/main/java/com/hubspot/singularity/scheduler/SingularityExpiringUserActionPoller.java index cd4a0ea544..f7754483cd 100644 --- a/SingularityService/src/main/java/com/hubspot/singularity/scheduler/SingularityExpiringUserActionPoller.java +++ b/SingularityService/src/main/java/com/hubspot/singularity/scheduler/SingularityExpiringUserActionPoller.java @@ -46,6 +46,7 @@ public class SingularityExpiringUserActionPoller extends SingularityLeaderOnlyPo private final SingularityMailer mailer; private final RequestHelper requestHelper; private final List> handlers; + private final SingularityConfiguration configuration; @Inject SingularityExpiringUserActionPoller(SingularityConfiguration configuration, RequestManager requestManager, TaskManager taskManager, @@ -56,6 +57,7 @@ public class SingularityExpiringUserActionPoller extends SingularityLeaderOnlyPo this.requestHelper = requestHelper; this.mailer = mailer; this.taskManager = taskManager; + this.configuration = configuration; List> tempHandlers = Lists.newArrayList(); tempHandlers.add(new SingularityExpiringBounceHandler()); @@ -85,18 +87,22 @@ private boolean isExpiringDue(T expiringObject) { final long now = System.currentTimeMillis(); final long duration = now - expiringObject.getStartMillis(); - return duration > expiringObject.getExpiringAPIRequestObject().getDurationMillis().get(); + return duration > getDurationMillis(expiringObject); } protected String getMessage(T expiringObject) { String msg = String.format("%s expired after %s", getActionName(), - JavaUtils.durationFromMillis(expiringObject.getExpiringAPIRequestObject().getDurationMillis().get())); + JavaUtils.durationFromMillis(getDurationMillis(expiringObject))); if (expiringObject.getExpiringAPIRequestObject().getMessage().isPresent() && expiringObject.getExpiringAPIRequestObject().getMessage().get().length() > 0) { msg = String.format("%s (%s)", msg, expiringObject.getExpiringAPIRequestObject().getMessage().get()); } return msg; } + protected long getDurationMillis(T expiringObject) { + return expiringObject.getExpiringAPIRequestObject().getDurationMillis().get(); + } + protected void checkExpiringObjects() { for (T expiringObject : requestManager.getExpiringObjects(clazz)) { if (isExpiringDue(expiringObject)) { @@ -130,6 +136,11 @@ protected String getActionName() { return "Bounce"; } + @Override + protected long getDurationMillis(SingularityExpiringBounce expiringBounce) { + return expiringBounce.getExpiringAPIRequestObject().getDurationMillis().or(TimeUnit.MINUTES.toMillis(configuration.getDefaultBounceExpirationMinutes())); + } + @Override protected void handleExpiringObject(SingularityExpiringBounce expiringObject, SingularityRequestWithState requestWithState, String message) { for (SingularityTaskCleanup taskCleanup : taskManager.getCleanupTasks()) { diff --git a/SingularityService/src/main/java/com/hubspot/singularity/views/IndexView.java b/SingularityService/src/main/java/com/hubspot/singularity/views/IndexView.java index 9a4cef1c1c..cbf93dedcb 100644 --- a/SingularityService/src/main/java/com/hubspot/singularity/views/IndexView.java +++ b/SingularityService/src/main/java/com/hubspot/singularity/views/IndexView.java @@ -32,6 +32,7 @@ public class IndexView extends View { private final Integer slaveHttpPort; private final Integer slaveHttpsPort; + private final int defaultBounceExpirationMinutes; private final long defaultHealthcheckIntervalSeconds; private final long defaultHealthcheckTimeoutSeconds; private final long defaultDeployHealthTimeoutSeconds; @@ -77,6 +78,7 @@ public IndexView(String singularityUriBase, String appRoot, SingularityConfigura this.navColor = configuration.getUiConfiguration().getNavColor(); + this.defaultBounceExpirationMinutes = configuration.getDefaultBounceExpirationMinutes(); this.defaultHealthcheckIntervalSeconds = configuration.getHealthcheckIntervalSeconds(); this.defaultHealthcheckTimeoutSeconds = configuration.getHealthcheckTimeoutSeconds(); this.defaultDeployHealthTimeoutSeconds = configuration.getDeployHealthyBySeconds(); @@ -154,6 +156,10 @@ public Boolean getLoadBalancingEnabled() { return loadBalancingEnabled; } + public int getDefaultBounceExpirationMinutes() { + return defaultBounceExpirationMinutes; + } + public long getDefaultHealthcheckIntervalSeconds() { return defaultHealthcheckIntervalSeconds; } @@ -213,6 +219,7 @@ public String toString() { ", title='" + title + '\'' + ", slaveHttpPort=" + slaveHttpPort + ", slaveHttpsPort=" + slaveHttpsPort + + ", defaultBounceExpirationMinutes=" + defaultBounceExpirationMinutes + ", defaultHealthcheckIntervalSeconds=" + defaultHealthcheckIntervalSeconds + ", defaultHealthcheckTimeoutSeconds=" + defaultHealthcheckTimeoutSeconds + ", defaultDeployHealthTimeoutSeconds=" + defaultDeployHealthTimeoutSeconds + diff --git a/SingularityUI/app/assets/_index.mustache b/SingularityUI/app/assets/_index.mustache index 87a6d4c580..6aedd119f4 100644 --- a/SingularityUI/app/assets/_index.mustache +++ b/SingularityUI/app/assets/_index.mustache @@ -23,6 +23,7 @@ loadBalancingEnabled: {{{loadBalancingEnabled}}}, defaultCpus: {{{defaultCpus}}}, defaultMemory: {{{defaultMemory}}}, + defaultBounceExpirationMinutes: {{{defaultBounceExpirationMinutes}}}, defaultHealthcheckIntervalSeconds: {{{defaultHealthcheckIntervalSeconds}}}, defaultHealthcheckTimeoutSeconds: {{{defaultHealthcheckTimeoutSeconds}}}, defaultDeployHealthTimeoutSeconds: {{{defaultDeployHealthTimeoutSeconds}}}, diff --git a/SingularityUI/app/models/Request.coffee b/SingularityUI/app/models/Request.coffee index ca3bc81363..3a1dec6efa 100644 --- a/SingularityUI/app/models/Request.coffee +++ b/SingularityUI/app/models/Request.coffee @@ -453,7 +453,9 @@ class Request extends Model promptBounce: (callback) => vex.dialog.confirm - message: bounceTemplate id: @get "id" + message: bounceTemplate + id: @get "id" + config: config input: """ """ diff --git a/SingularityUI/app/templates/vex/requestBounce.hbs b/SingularityUI/app/templates/vex/requestBounce.hbs index d6cff89c91..8b2ff1f17d 100644 --- a/SingularityUI/app/templates/vex/requestBounce.hbs +++ b/SingularityUI/app/templates/vex/requestBounce.hbs @@ -12,6 +12,6 @@ Skip healthchecks during bounce
- +
If an expiration duration is specified, this bounce will be aborted if not finished. Accepts any english time duration. (Days, Hr, Min...) diff --git a/SingularityUI/app/views/requestActionExpirations.coffee b/SingularityUI/app/views/requestActionExpirations.coffee index 3ee49dc7bc..b5734a48f8 100644 --- a/SingularityUI/app/views/requestActionExpirations.coffee +++ b/SingularityUI/app/views/requestActionExpirations.coffee @@ -29,15 +29,20 @@ class requestActionExpirations extends View revertParam: request.expiringScale.revertToInstances message: request.expiringScale.expiringAPIRequestObject.message - if request.expiringBounce and (request.expiringBounce.startMillis + request.expiringBounce.expiringAPIRequestObject.durationMillis) > new Date().getTime() - expirations.push - action: 'Bounce' - user: if request.expiringBounce.user then request.expiringBounce.user.split('@')[0] else "" - endMillis: request.expiringBounce.startMillis + request.expiringBounce.expiringAPIRequestObject.durationMillis - canRevert: false - cancelText: 'Cancel' - cancelAction: 'cancelBounce' - message: request.expiringBounce.expiringAPIRequestObject.message + if request.expiringBounce + if request.expiringBounce.expiringAPIRequestObject.durationMillis + endMillis = request.expiringBounce.startMillis + request.expiringBounce.expiringAPIRequestObject.durationMillis + else + endMillis = request.expiringBounce.startMillis + (config.defaultBounceExpirationMinutes * 60 * 1000) + if endMillis > new Date().getTime() + expirations.push + action: 'Bounce' + user: if request.expiringBounce.user then request.expiringBounce.user.split('@')[0] else "" + endMillis: endMillis + canRevert: false + cancelText: 'Cancel' + cancelAction: 'cancelBounce' + message: request.expiringBounce.expiringAPIRequestObject.message if request.expiringPause and (request.expiringPause.startMillis + request.expiringPause.expiringAPIRequestObject.durationMillis) > new Date().getTime() expirations.push diff --git a/SingularityUI/config.coffee b/SingularityUI/config.coffee index 61642f5ef1..c6ab48885b 100644 --- a/SingularityUI/config.coffee +++ b/SingularityUI/config.coffee @@ -45,6 +45,7 @@ exports.config = navColor: process.env.SINGULARITY_NAV_COLOR defaultCpus: process.env.SINGUALRITY_DEFAULT_CPUS ? 1 defaultMemory: process.env.SINGULARITY_DEFAULT_MEMORY ? 128 + defaultBounceExpirationMinutes: process.env.SINGULARITY_DEFAULT_BOUNCE_EXPIRATION_MINUTES ? 60 defaultHealthcheckIntervalSeconds: process.env.SINGULARITY_DEFAULT_HEALTHCHECK_INTERVAL_SECONDS ? 5 defaultHealthcheckTimeoutSeconds: process.env.SINGULARITY_HEALTHCHECK_TIMEOUT_SECONDS ? 5 defaultDeployHealthTimeoutSeconds: process.env.SINGULARITY_DEPLOY_HEALTH_TIMEOUT_SECONDS ? 120