Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Even number across racks #906

Merged
merged 10 commits into from Mar 22, 2016
Expand Up @@ -47,6 +47,8 @@ public class SingularityRequest {

private final Optional<Map<SingularityEmailType, List<SingularityEmailDestination>>> emailConfigurationOverrides;

private final Optional<Boolean> hideEvenNumberAcrossRacksHint;

@JsonCreator
public SingularityRequest(@JsonProperty("id") String id, @JsonProperty("requestType") RequestType requestType, @JsonProperty("owners") Optional<List<String>> owners,
@JsonProperty("numRetriesOnFailure") Optional<Integer> numRetriesOnFailure, @JsonProperty("schedule") Optional<String> schedule, @JsonProperty("instances") Optional<Integer> instances,
Expand All @@ -58,7 +60,8 @@ public SingularityRequest(@JsonProperty("id") String id, @JsonProperty("requestT
@JsonProperty("waitAtLeastMillisAfterTaskFinishesForReschedule") Optional<Long> waitAtLeastMillisAfterTaskFinishesForReschedule, @JsonProperty("group") Optional<String> group,
@JsonProperty("readOnlyGroups") Optional<Set<String>> readOnlyGroups, @JsonProperty("bounceAfterScale") Optional<Boolean> bounceAfterScale,
@JsonProperty("skipHealthchecks") Optional<Boolean> skipHealthchecks,
@JsonProperty("emailConfigurationOverrides") Optional<Map<SingularityEmailType, List<SingularityEmailDestination>>> emailConfigurationOverrides, @JsonProperty("daemon") @Deprecated Optional<Boolean> daemon) {
@JsonProperty("emailConfigurationOverrides") Optional<Map<SingularityEmailType, List<SingularityEmailDestination>>> emailConfigurationOverrides,
@JsonProperty("daemon") @Deprecated Optional<Boolean> daemon, @JsonProperty("hideEvenNumberAcrossRacks") Optional<Boolean> hideEvenNumberAcrossRacksHint) {
this.id = checkNotNull(id, "id cannot be null");
this.owners = owners;
this.numRetriesOnFailure = numRetriesOnFailure;
Expand All @@ -80,6 +83,7 @@ public SingularityRequest(@JsonProperty("id") String id, @JsonProperty("requestT
this.bounceAfterScale = bounceAfterScale;
this.emailConfigurationOverrides = emailConfigurationOverrides;
this.skipHealthchecks = skipHealthchecks;
this.hideEvenNumberAcrossRacksHint = hideEvenNumberAcrossRacksHint;
if (requestType == null) {
this.requestType = RequestType.fromDaemonAndScheduleAndLoadBalanced(schedule, daemon, loadBalanced);
} else {
Expand Down Expand Up @@ -108,7 +112,8 @@ public SingularityRequestBuilder toBuilder() {
.setReadOnlyGroups(readOnlyGroups)
.setBounceAfterScale(bounceAfterScale)
.setEmailConfigurationOverrides(emailConfigurationOverrides)
.setSkipHealthchecks(skipHealthchecks);
.setSkipHealthchecks(skipHealthchecks)
.setHideEvenNumberAcrossRacksHint(hideEvenNumberAcrossRacksHint);
}

public String getId() {
Expand Down Expand Up @@ -252,6 +257,8 @@ public Optional<Boolean> getSkipHealthchecks() {
return skipHealthchecks;
}

public Optional<Boolean> getHideEvenNumberAcrossRacksHint() { return hideEvenNumberAcrossRacksHint; }

@Override
public String toString() {
return "SingularityRequest[" +
Expand All @@ -276,6 +283,7 @@ public String toString() {
", readOnlyGroups=" + readOnlyGroups +
", bounceAfterScale=" + bounceAfterScale +
", emailConfigurationOverrides=" + emailConfigurationOverrides +
", hideEvenNumberAcrossRacksHint=" + hideEvenNumberAcrossRacksHint +
']';
}

Expand Down Expand Up @@ -308,11 +316,12 @@ public boolean equals(Object o) {
Objects.equals(group, request.group) &&
Objects.equals(readOnlyGroups, request.readOnlyGroups) &&
Objects.equals(bounceAfterScale, request.bounceAfterScale) &&
Objects.equals(emailConfigurationOverrides, request.emailConfigurationOverrides);
Objects.equals(emailConfigurationOverrides, request.emailConfigurationOverrides) &&
Objects.equals(hideEvenNumberAcrossRacksHint, request.hideEvenNumberAcrossRacksHint);
}

@Override
public int hashCode() {
return Objects.hash(id, requestType, owners, numRetriesOnFailure, schedule, quartzSchedule, scheduleType, killOldNonLongRunningTasksAfterMillis, scheduledExpectedRuntimeMillis, waitAtLeastMillisAfterTaskFinishesForReschedule, instances, rackSensitive, rackAffinity, slavePlacement, requiredSlaveAttributes, allowedSlaveAttributes, loadBalanced, group, readOnlyGroups, bounceAfterScale, emailConfigurationOverrides);
return Objects.hash(id, requestType, owners, numRetriesOnFailure, schedule, quartzSchedule, scheduleType, killOldNonLongRunningTasksAfterMillis, scheduledExpectedRuntimeMillis, waitAtLeastMillisAfterTaskFinishesForReschedule, instances, rackSensitive, rackAffinity, slavePlacement, requiredSlaveAttributes, allowedSlaveAttributes, loadBalanced, group, readOnlyGroups, bounceAfterScale, emailConfigurationOverrides, hideEvenNumberAcrossRacksHint);
}
}
Expand Up @@ -41,6 +41,7 @@ public class SingularityRequestBuilder {
private Optional<Set<String>> readOnlyGroups;
private Optional<Boolean> bounceAfterScale;
private Optional<Map<SingularityEmailType, List<SingularityEmailDestination>>> emailConfigurationOverrides;
private Optional<Boolean> hideEvenNumberAcrossRacksHint;

public SingularityRequestBuilder(String id, RequestType requestType) {
this.id = checkNotNull(id, "id cannot be null");
Expand All @@ -65,12 +66,13 @@ public SingularityRequestBuilder(String id, RequestType requestType) {
this.bounceAfterScale = Optional.absent();
this.emailConfigurationOverrides = Optional.absent();
this.skipHealthchecks = Optional.absent();
this.hideEvenNumberAcrossRacksHint = Optional.absent();
}

public SingularityRequest build() {
return new SingularityRequest(id, requestType, owners, numRetriesOnFailure, schedule, instances, rackSensitive, loadBalanced, killOldNonLongRunningTasksAfterMillis, scheduleType, quartzSchedule,
rackAffinity, slavePlacement, requiredSlaveAttributes, allowedSlaveAttributes, scheduledExpectedRuntimeMillis, waitAtLeastMillisAfterTaskFinishesForReschedule, group, readOnlyGroups,
bounceAfterScale, skipHealthchecks, emailConfigurationOverrides, Optional.<Boolean>absent());
bounceAfterScale, skipHealthchecks, emailConfigurationOverrides, Optional.<Boolean>absent(), hideEvenNumberAcrossRacksHint);
}

public Optional<Boolean> getSkipHealthchecks() {
Expand Down Expand Up @@ -253,6 +255,13 @@ public SingularityRequestBuilder setEmailConfigurationOverrides(Optional<Map<Sin
return this;
}

public Optional<Boolean> getHideEvenNumberAcrossRacksHint() { return hideEvenNumberAcrossRacksHint; }

public SingularityRequestBuilder setHideEvenNumberAcrossRacksHint(Optional<Boolean> hideEvenNumberAcrossRacksHint) {
this.hideEvenNumberAcrossRacksHint = hideEvenNumberAcrossRacksHint;
return this;
}

@Override
public String toString() {
return "SingularityRequestBuilder[" +
Expand All @@ -278,6 +287,7 @@ public String toString() {
", bounceAfterScale=" + bounceAfterScale +
", emailConfigurationOverrides=" + emailConfigurationOverrides +
", skipHealthchecks=" + skipHealthchecks +
", hideEvenNumberAcrossRacksHint=" + hideEvenNumberAcrossRacksHint +
']';
}

Expand Down Expand Up @@ -311,14 +321,15 @@ public boolean equals(Object o) {
Objects.equals(readOnlyGroups, that.readOnlyGroups) &&
Objects.equals(bounceAfterScale, that.bounceAfterScale) &&
Objects.equals(skipHealthchecks, that.skipHealthchecks) &&
Objects.equals(emailConfigurationOverrides, that.emailConfigurationOverrides);
Objects.equals(emailConfigurationOverrides, that.emailConfigurationOverrides) &&
Objects.equals(hideEvenNumberAcrossRacksHint, that.hideEvenNumberAcrossRacksHint);
}

@Override
public int hashCode() {
return Objects.hash(id, requestType, owners, numRetriesOnFailure, schedule, quartzSchedule, scheduleType, killOldNonLongRunningTasksAfterMillis,
scheduledExpectedRuntimeMillis, waitAtLeastMillisAfterTaskFinishesForReschedule, instances, rackSensitive, rackAffinity, slavePlacement,
requiredSlaveAttributes, allowedSlaveAttributes, loadBalanced, group, readOnlyGroups, bounceAfterScale, skipHealthchecks, emailConfigurationOverrides);
requiredSlaveAttributes, allowedSlaveAttributes, loadBalanced, group, readOnlyGroups, bounceAfterScale, skipHealthchecks, emailConfigurationOverrides, hideEvenNumberAcrossRacksHint);
}

}
83 changes: 79 additions & 4 deletions SingularityUI/app/models/Request.coffee
@@ -1,7 +1,10 @@
Model = require './model'

Racks = require '../collections/Racks'

pauseTemplate = require '../templates/vex/requestPause'
scaleTemplate = require '../templates/vex/requestScale'
scaleEvenNumbersTemplate = require '../templates/vex/requestScaleConfirmRacks'
unpauseTemplate = require '../templates/vex/requestUnpause'
runTemplate = require '../templates/vex/requestRun'
removeTemplate = require '../templates/vex/requestRemove'
Expand Down Expand Up @@ -58,6 +61,16 @@ class Request extends Model
message: data.message
)

hideEvenNumberAcrossRacksHint: (callback) ->
@attributes.request.hideEvenNumberAcrossRacksHint = true
ajaxPromise = $.ajax(
type: 'POST'
url: "#{ config.apiRoot }/requests"
contentType: 'application/json'
data: JSON.stringify @attributes.request
)
ajaxPromise.then callback

pause: (killTasks, duration, message) =>
data =
user: app.getUsername()
Expand Down Expand Up @@ -235,6 +248,60 @@ class Request extends Model
if !duration or (duration and @_validateDuration(duration, @promptPause, callback))
@pause(killTasks, duration, message).done callback

callScale: (data, bounce, incremental, message, duration, callback, setHideEvenNumberAcrossRacksHintTrue) =>
@scale(data).done =>
if setHideEvenNumberAcrossRacksHintTrue
@attributes.request.instances = data.instances
@hideEvenNumberAcrossRacksHint () =>
if bounce
@bounce({incremental}).done callback
else
callback()
else if bounce
@bounce({incremental}).done callback
else
callback()

promptScaleEvenNumberRacks: (scaleData) =>
vex.dialog.open
message: scaleEvenNumbersTemplate
instances: parseInt(scaleData.data.instances)
notOneInstance: parseInt(scaleData.data.instances) != 1
racks: @racks.length
notOneRack: @racks.length != 1
mod: scaleData.mod
modNotOne: scaleData.mod != 1
lower: parseInt(scaleData.data.instances) - scaleData.mod
higher: parseInt(scaleData.data.instances) + @racks.length - scaleData.mod
config: config
input: """

"""
buttons: [
$.extend _.clone(vex.dialog.buttons.YES), text: "Scale"
vex.dialog.buttons.NO
]
scaleData: scaleData # Not sure why this is necessary, callback for whatever reason doesn't have access to the function's variables
callback: (data) =>
return unless data
scaleData.data.instances = data.instances
@callScale scaleData.data, scaleData.bounce, scaleData.incremental, scaleData.message, scaleData.duration, scaleData.callback, data.optOut


checkScaleEvenNumberRacks: (data, bounce, incremental, message, duration, callback) =>
mod = data.instances %% @racks.length
if mod
@promptScaleEvenNumberRacks
callback: callback
data: data
mod: mod
bounce: bounce
incremental: incremental
message: message
duration: duration
else
@callScale data, bounce, incremental, message, duration, callback, false

promptScale: (callback) =>
vex.dialog.open
message: "Enter the desired number of instances to run for request:"
Expand All @@ -261,11 +328,19 @@ class Request extends Model
message = $('.vex #scale-message').val()
duration = $('.vex #scale-expiration').val()
if !duration or (duration and @_validateDuration(duration, @promptScale, callback))
@scale(data).done =>
if bounce
@bounce({incremental}).done callback
if @attributes.request.rackSensitive and not @attributes.request.hideEvenNumberAcrossRacksHint
if @racks
@checkScaleEvenNumberRacks data, bounce, incremental, message, duration, callback
else
callback()
@racks = new Racks []
@racks.fetch
success: () => @checkScaleEvenNumberRacks data, bounce, incremental, message, duration, callback
error: () =>
app.caughtError() # Since we scale anyway, don't show the error
@callScale data, bounce, incremental, message, duration, callback, false
else
@callScale data, bounce, incremental, message, duration, callback, false


promptDisableHealthchecksDuration: (message, duration, callback) =>
durationMillis = @_parseDuration(duration)
Expand Down
16 changes: 16 additions & 0 deletions SingularityUI/app/templates/requestForm.hbs
Expand Up @@ -74,6 +74,14 @@
</label>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="hide-distribute-across-racks-hint-SERVICE" data-tooltip='hide-distribute-across-racks-hint'>
<input type="checkbox" id="hide-distribute-across-racks-hint-SERVICE">
Hide Distribute Evenly Across Racks Hint
</label>
</div>
</div>
{{#if config.loadBalancingEnabled }}
<div class="col-sm-4">
<div class="form-group">
Expand Down Expand Up @@ -109,6 +117,14 @@
</label>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="hide-distribute-across-racks-hint-WORKER" data-tooltip='hide-distribute-across-racks-hint'>
<input type="checkbox" id="hide-distribute-across-racks-hint-WORKER">
Hide Distribute Evenly Across Racks Hint
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="waitAtLeast-WORKER">Task rescheduling delay</label>
Expand Down
48 changes: 48 additions & 0 deletions SingularityUI/app/templates/vex/requestScaleConfirmRacks.hbs
@@ -0,0 +1,48 @@
<p>
We cannot distribute {{ instances }}
{{#if notOneInstance}}
instances
{{else}}
instance
{{/if}}
evenly across our {{ racks }}
{{#if notOneRack}}
racks.
{{else}}
rack.
{{/if}}
This will result in increased load on {{mod}}
{{#if modNotOne}}
racks.
{{else}}
rack.
{{/if}}
</p>
<p>
We suggest scaling to
{{#if lower}}
either <strong>{{ lower }}</strong> or
{{/if}}
<strong>{{ higher }}</strong> instances.
How many instances would you like to scale to?
</p>
<div id="instances">
{{#if lower}}
<input type="radio" name="instances" id="lower" value="{{ lower }}"> <label for="lower" id="lower-label">{{ lower }} instances</label><br/>
{{/if}}
<input type="radio" name="instances" id="default" value="{{ instances }}" checked> <label for="default" id="default-label">{{ instances }}
{{#if notOneInstance}}
instances
{{else}}
instance
{{/if}}
</label><br/>
<input type="radio" name="instances" id="higher" value="{{ higher }}"> <label for="higher" id="higher-label">{{ higher }} instances</label><br/>
</div>
<br />
<div id="opt-out">
<label for='opt-out-box' id='opt-out-box-label'>
<input type='checkbox' name='optOut' id='opt-out-box' value='opt-out'>
Don't ask again for this request, for any user
</label>
</div>
2 changes: 2 additions & 0 deletions SingularityUI/app/views/requestFormBase.coffee
Expand Up @@ -92,6 +92,8 @@ class RequestFormBase extends FormBaseView
requestObject.waitAtLeastMillisAfterTaskFinishesForReschedule = waitAtLeast if waitAtLeast

requestObject.rackSensitive = @$("#rack-sensitive-#{ type }").is ':checked'

requestObject.hideEvenNumberAcrossRacksHint = @$("#hide-distribute-across-racks-hint-#{ type }").is ':checked'

requestObject.rackAffinity = @getSelect2Val "#rackAffinity-#{ type }"

Expand Down
1 change: 1 addition & 0 deletions SingularityUI/app/views/requestFormEdit.coffee
Expand Up @@ -28,6 +28,7 @@ class RequestFormEdit extends RequestFormBaseView
if @requestType is 'SERVICE' or 'WORKER'
@$("#instances-#{@requestType}").val request.request.instances
@$("#rack-sensitive-#{@requestType}").prop 'checked', request.request.rackSensitive
@$("#hide-distribute-across-racks-hint-#{@requestType}").prop 'checked', request.request.hideEvenNumberAcrossRacksHint
@$("#load-balanced").prop 'checked', request.request.loadBalanced

if @requestType in ['SCHEDULED','ON_DEMAND','RUN_ONCE']
Expand Down