Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.hubspot.singularity;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.Objects;
import java.util.Optional;

@Schema(description = "Warning to users users about potential destructive actions")
public class FireAlarm {
private final String title;
private final String message;
private final Optional<String> url;

@JsonCreator
public FireAlarm(
@JsonProperty("title") String title,
@JsonProperty("message") String message,
@JsonProperty("url") Optional<String> url
) {
this.title = title;
this.message = message;
this.url = url;
}

@Schema(required = true, description = "Fire alarm title")
public String getTitle() {
return title;
}

@Schema(required = true, description = "Fire alarm message")
public String getMessage() {
return message;
}

@Schema(required = true, description = "Fire alarm url to link to for more information")
public Optional<String> getUrl() {
return url;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FireAlarm that = (FireAlarm) o;
return (
Objects.equals(title, that.title) &&
Objects.equals(message, that.message) &&
Objects.equals(url, that.url)
);
}

@Override
public int hashCode() {
return Objects.hash(title, message, url);
}

@Override
public String toString() {
return MoreObjects
.toStringHelper(this)
.add("title", title)
.add("message", message)
.add("url", url)
.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public class SingularityState {

private final long avgStatusUpdateDelayMs;
private final long lastHeartbeatAt;
private final Optional<FireAlarm> fireAlarm;

@SuppressFBWarnings("NP_NULL_PARAM_DEREF_NONVIRTUAL")
public SingularityState(
Expand Down Expand Up @@ -106,7 +107,8 @@ public SingularityState(
Optional<Boolean> authDatastoreHealthy,
Optional<Double> minimumPriorityLevel,
long avgStatusUpdateDelayMs,
long lastHeartbeatAt
long lastHeartbeatAt,
Optional<FireAlarm> fireAlarm
) {
this(
activeTasks,
Expand Down Expand Up @@ -152,7 +154,8 @@ public SingularityState(
activeAgents,
deadAgents,
decommissioningAgents,
unknownAgents
unknownAgents,
fireAlarm
);
}

Expand Down Expand Up @@ -203,7 +206,8 @@ public SingularityState(
@JsonProperty("activeAgents") Integer activeAgents,
@JsonProperty("deadAgents") Integer deadAgents,
@JsonProperty("decommissioningAgents") Integer decommissioningAgents,
@JsonProperty("unknownAgents") Integer unknownAgents
@JsonProperty("unknownAgents") Integer unknownAgents,
@JsonProperty("fireAlarm") Optional<FireAlarm> fireAlarm
) {
this.activeTasks = activeTasks;
this.launchingTasks = launchingTasks;
Expand Down Expand Up @@ -247,6 +251,7 @@ public SingularityState(
this.minimumPriorityLevel = minimumPriorityLevel;
this.avgStatusUpdateDelayMs = avgStatusUpdateDelayMs;
this.lastHeartbeatAt = lastHeartbeatAt;
this.fireAlarm = fireAlarm;
}

@Schema(description = "Count of requests in finished state")
Expand Down Expand Up @@ -522,6 +527,11 @@ public long getLastHeartbeatAt() {
return lastHeartbeatAt;
}

@Schema(description = "Fire alarm status")
public Optional<FireAlarm> getFireAlarm() {
return fireAlarm;
}

@Override
public String toString() {
return (
Expand Down Expand Up @@ -606,6 +616,8 @@ public String toString() {
avgStatusUpdateDelayMs +
", lastHeartbeatAt=" +
lastHeartbeatAt +
", fireAlarm=" +
fireAlarm +
'}'
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.codahale.metrics.MetricRegistry;
import com.google.inject.Inject;
import com.hubspot.singularity.FireAlarm;
import com.hubspot.singularity.SingularityAction;
import com.hubspot.singularity.SingularityCreateResult;
import com.hubspot.singularity.SingularityDeleteResult;
Expand Down Expand Up @@ -32,23 +33,28 @@ public class DisasterManager extends CuratorAsyncManager {
private static final String DISASTER_STATS_PATH = DISASTERS_ROOT + "/statistics";
private static final String DISABLE_AUTOMATED_PATH = DISASTERS_ROOT + "/disabled";

private static final String FIRE_ALARM_PATH = "/firealarm";

private static final String MESSAGE_FORMAT = "Cannot perform action %s: %s";
private static final String DEFAULT_MESSAGE = "Action is currently disabled";

private final Transcoder<SingularityDisabledAction> disabledActionTranscoder;
private final Transcoder<SingularityDisasterDataPoints> disasterStatsTranscoder;
private final Transcoder<FireAlarm> fireAlarmTranscoder;

@Inject
public DisasterManager(
CuratorFramework curator,
SingularityConfiguration configuration,
MetricRegistry metricRegistry,
Transcoder<SingularityDisabledAction> disabledActionTranscoder,
Transcoder<SingularityDisasterDataPoints> disasterStatsTranscoder
Transcoder<SingularityDisasterDataPoints> disasterStatsTranscoder,
Transcoder<FireAlarm> fireAlarmTranscoder
) {
super(curator, configuration, metricRegistry);
this.disabledActionTranscoder = disabledActionTranscoder;
this.disasterStatsTranscoder = disasterStatsTranscoder;
this.fireAlarmTranscoder = fireAlarmTranscoder;
}

private String getActionPath(SingularityAction action) {
Expand Down Expand Up @@ -261,4 +267,16 @@ public void enableAutomatedDisabledActions() {
public boolean isAutomatedDisabledActionsDisabled() {
return exists(DISABLE_AUTOMATED_PATH);
}

public void setFireAlarm(FireAlarm fireAlarm) {
save(FIRE_ALARM_PATH, fireAlarm, fireAlarmTranscoder);
}

public Optional<FireAlarm> getFireAlarm() {
return getData(FIRE_ALARM_PATH, fireAlarmTranscoder);
}

public void deleteFireAlarm() {
delete(FIRE_ALARM_PATH);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class StateManager extends CuratorManager {
private final DeployManager deployManager;
private final AgentManager agentManager;
private final RackManager rackManager;
private final DisasterManager disasterManager;
private final Transcoder<SingularityState> stateTranscoder;
private final Transcoder<SingularityHostState> hostStateTranscoder;
private final SingularityConfiguration singularityConfiguration;
Expand All @@ -71,6 +72,7 @@ public StateManager(
DeployManager deployManager,
AgentManager agentManager,
RackManager rackManager,
DisasterManager disasterManager,
Transcoder<SingularityState> stateTranscoder,
Transcoder<SingularityHostState> hostStateTranscoder,
SingularityConfiguration singularityConfiguration,
Expand All @@ -89,6 +91,7 @@ public StateManager(
this.hostStateTranscoder = hostStateTranscoder;
this.agentManager = agentManager;
this.rackManager = rackManager;
this.disasterManager = disasterManager;
this.deployManager = deployManager;
this.singularityConfiguration = singularityConfiguration;
this.authDatastore = authDatastore;
Expand Down Expand Up @@ -330,7 +333,8 @@ public SingularityState generateState(boolean includeRequestIds) {
authDatastoreHealthy,
minimumPriorityLevel,
(long) statusUpdateDeltas.getSnapshot().getMean(),
lastHeartbeatTime.get()
lastHeartbeatTime.get(),
disasterManager.getFireAlarm()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Inject;
import com.hubspot.singularity.FireAlarm;
import com.hubspot.singularity.Singularity;
import com.hubspot.singularity.SingularityAbort;
import com.hubspot.singularity.SingularityAbort.AbortReason;
Expand Down Expand Up @@ -218,4 +219,33 @@ private Response runFailover(SingularityUser user) {
);
return Response.ok().build();
}

@GET
@Path("/firealarm")
@Operation(summary = "Set a firealarm warning in singularity")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is the same summary as the post endpoint

public Optional<FireAlarm> getFireAlarm(
@Parameter(hidden = true) @Auth SingularityUser user,
FireAlarm fireAlarm
) {
return disasterManager.getFireAlarm();
}

@POST
@Path("/firealarm")
@Operation(summary = "Set a firealarm warning in singularity")
public void enableFireAlarm(
@Parameter(hidden = true) @Auth SingularityUser user,
FireAlarm fireAlarm
) {
authorizationHelper.checkAdminAuthorization(user);
disasterManager.setFireAlarm(fireAlarm);
}

@DELETE
@Path("/firealarm")
@Operation(summary = "Deleting ongoing fire alarm")
public void disableFireAlarm(@Parameter(hidden = true) @Auth SingularityUser user) {
authorizationHelper.checkAdminAuthorization(user);
disasterManager.deleteFireAlarm();
}
}