-
Notifications
You must be signed in to change notification settings - Fork 99
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
Jenkins 31084 graceful shutdown #115
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -145,10 +145,17 @@ public void run(SwarmClient swarmClient, String... args) throws InterruptedExcep | |
// The Jenkins that we are trying to connect to. | ||
Candidate target; | ||
|
||
ShutdownHook shutdownHook = null; | ||
|
||
// wait until we get the ACK back | ||
int retry = 0; | ||
while (true) { | ||
try { | ||
if (shutdownHook != null) { | ||
Runtime.getRuntime().removeShutdownHook(shutdownHook); | ||
shutdownHook = null; | ||
} | ||
|
||
if (options.master == null) { | ||
logger.info("No Jenkins master supplied on command line, performing auto-discovery"); | ||
target = swarmClient.discoverFromBroadcast(); | ||
|
@@ -190,7 +197,13 @@ public void run(SwarmClient swarmClient, String... args) throws InterruptedExcep | |
labelFileWatcherThread.start(); | ||
} | ||
|
||
/* | ||
* Set up a shutdown hook so we disconnect gracefully | ||
*/ | ||
shutdownHook = new ShutdownHook(swarmClient, target); | ||
Runtime.getRuntime().addShutdownHook(shutdownHook); | ||
swarmClient.connect(target); | ||
|
||
if (options.noRetryAfterConnected) { | ||
logger.warning("Connection closed, exiting..."); | ||
swarmClient.exitWithStatus(0); | ||
|
@@ -285,4 +298,26 @@ private static boolean isDefaultOption(String key, Object value, CmdLineParser d | |
} | ||
return false; | ||
} | ||
|
||
|
||
public static class ShutdownHook extends Thread { | ||
private SwarmClient client; | ||
private Candidate target; | ||
|
||
public ShutdownHook(SwarmClient client, Candidate target) { | ||
this.client = client; | ||
this.target = target; | ||
} | ||
|
||
@Override | ||
public void run() { | ||
logger.warning("Client shutdown initiated"); | ||
try { | ||
client.takeSlaveOfflineAndWait(target, "JVM is shutting down"); | ||
} catch (Exception e) { | ||
logger.log(Level.SEVERE, "Unable to perform graceful slave shutdown: ", e); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would happen if you started a shutdown operation (e.g., by sending a |
||
} | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,8 +9,10 @@ | |
import hudson.model.Computer; | ||
import hudson.model.Descriptor.FormException; | ||
import hudson.model.Node; | ||
import hudson.model.User; | ||
import hudson.slaves.EnvironmentVariablesNodeProperty; | ||
import hudson.slaves.NodeProperty; | ||
import hudson.slaves.OfflineCause; | ||
import hudson.slaves.SlaveComputer; | ||
import hudson.tools.ToolDescriptor; | ||
import hudson.tools.ToolInstallation; | ||
|
@@ -73,14 +75,18 @@ public void doGetSlaveLabels(StaplerRequest req, StaplerResponse rsp, @QueryPara | |
return; | ||
} | ||
|
||
normalResponse(req, rsp, nn.getLabelString()); | ||
normalLabelResponse(req, rsp, nn.getLabelString()); | ||
} | ||
|
||
private void normalResponse(StaplerRequest req, StaplerResponse rsp, String sLabelList) throws IOException { | ||
private static void normalLabelResponse(StaplerRequest req, StaplerResponse rsp, String sLabelList) throws IOException { | ||
normalResponse(req, rsp, "<labelResponse><labels>" + sLabelList + "</labels></labelResponse>"); | ||
} | ||
|
||
private static void normalResponse(StaplerRequest req, StaplerResponse rsp, String rawXml) throws IOException { | ||
rsp.setContentType("text/xml"); | ||
|
||
Writer w = rsp.getCompressedWriter(req); | ||
w.write("<labelResponse><labels>" + sLabelList + "</labels></labelResponse>"); | ||
w.write(rawXml); | ||
w.close(); | ||
} | ||
|
||
|
@@ -106,7 +112,7 @@ public void doAddSlaveLabels(StaplerRequest req, StaplerResponse rsp, @QueryPara | |
nn.setLabelString(setToString(hs)); | ||
nn.getAssignedLabels(); | ||
|
||
normalResponse(req, rsp, nn.getLabelString()); | ||
normalLabelResponse(req, rsp, nn.getLabelString()); | ||
} | ||
|
||
private static String setToString(Set<String> labels) { | ||
|
@@ -134,7 +140,7 @@ public void doRemoveSlaveLabels(StaplerRequest req, StaplerResponse rsp, @QueryP | |
hs.removeAll(lBadLabels); | ||
nn.setLabelString(setToString(hs)); | ||
nn.getAssignedLabels(); | ||
normalResponse(req, rsp, nn.getLabelString()); | ||
normalLabelResponse(req, rsp, nn.getLabelString()); | ||
} | ||
|
||
/** | ||
|
@@ -221,6 +227,38 @@ public void doCreateSlave(StaplerRequest req, StaplerResponse rsp, @QueryParamet | |
} | ||
} | ||
|
||
/** | ||
* Allows the client to mark a slave offline | ||
*/ | ||
public void doMarkSlaveOffline(StaplerRequest req, StaplerResponse rsp, @QueryParameter String name, | ||
@QueryParameter String reason, @QueryParameter String secret) throws IOException { | ||
if (!getSwarmSecret().equals(secret)) { | ||
rsp.setStatus(SC_FORBIDDEN); | ||
return; | ||
} | ||
|
||
Jenkins jenkins = Jenkins.getInstance(); | ||
|
||
jenkins.checkPermission(SlaveComputer.DISCONNECT); | ||
|
||
Node node = getNodeByName(name, rsp); | ||
|
||
node.toComputer().setTemporarilyOffline(true, new OfflineCause.UserCause(User.current(), reason)); | ||
|
||
normalResponse(req, rsp, "<response/>"); //TODO: not sure what to send here | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure either offhand. Assuming that the default response status is |
||
} | ||
|
||
public void doGetSlaveReadyForShutdown(StaplerRequest req, StaplerResponse rsp, @QueryParameter String name) throws IOException { | ||
Jenkins jenkins = Jenkins.getInstance(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regarding checking for secrets/permissions: I can't see any disadvantages to checking, and I can't see any advantages to not checking. In contrast, if we don't check, we increase the attack surface for potential security issues. Based on the above reasoning, I think I'd prefer to check secrets/permissions. |
||
|
||
Node node = getNodeByName(name, rsp); | ||
|
||
boolean ready = node.toComputer().isOffline() && node.toComputer().isIdle(); | ||
|
||
normalResponse(req, rsp, "<slaveInfo><readyStatus>" + ready + "</readyStatus></slaveInfo>"); | ||
} | ||
|
||
|
||
private static List<ToolLocation> parseToolLocations(String[] toolLocations) { | ||
List<ToolLocationNodeProperty.ToolLocation> result = new ArrayList<>(); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] These can be
private final
.