Skip to content

Commit

Permalink
Poll for spot instances instead of JNLP launcher JENKINS-27529 JENKIN…
Browse files Browse the repository at this point in the history
…S-19059

* No more need for special spot startup script.
* Changes spot slave name to use spot request ID (partially fixes JENKINS-32399)
  • Loading branch information
mbarrien committed Jan 26, 2016
1 parent 882379a commit ae7e53a
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 87 deletions.
2 changes: 0 additions & 2 deletions src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java
Expand Up @@ -46,7 +46,6 @@
import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

Expand Down Expand Up @@ -121,7 +120,6 @@ public abstract class EC2AbstractSlave extends Slave {

public static final String TEST_ZONE = "testZone";

@DataBoundConstructor
public EC2AbstractSlave(String name, String instanceId, String description, String remoteFS, int numExecutors, Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy<EC2Computer> retentionStrategy, String initScript, String tmpDir, List<? extends NodeProperty<?>> nodeProperties, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, List<EC2Tag> tags, String cloudName, boolean usePrivateDnsName, boolean useDedicatedTenancy, int launchTimeout, AMITypeData amiType)
throws FormException, IOException {

Expand Down
24 changes: 22 additions & 2 deletions src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java
Expand Up @@ -51,6 +51,26 @@ public abstract class EC2ComputerLauncher extends ComputerLauncher {
public void launch(SlaveComputer _computer, TaskListener listener) {
try {
EC2Computer computer = (EC2Computer) _computer;

while (true) {
String instanceId = computer.getInstanceId();
if (instanceId != null && !instanceId.equals("")) {
break;
}
// Only spot slaves can have no instance id.
EC2SpotSlave ec2Slave = (EC2SpotSlave) computer.getNode();
if (ec2Slave.isSpotRequestDead()) {
// Terminate launch
return;
}
final String msg = "Node " + computer.getName() + "(SpotRequest " + computer.getSpotInstanceRequestId() +
") still requesting the instance, waiting 10s";
// report to system log and console
((EC2Computer) _computer).getCloud().log(LOGGER, Level.FINEST, listener, msg);
// check every 10 seconds if in spot request phase
Thread.sleep(10000);
}

final String baseMsg = "Node " + computer.getName() + "(" + computer.getInstanceId() + ")";
String msg;

Expand Down Expand Up @@ -91,10 +111,10 @@ public void launch(SlaveComputer _computer, TaskListener listener) {
break;
}

// report to system log and console
((EC2Computer) _computer).getCloud().log(LOGGER, Level.FINEST, listener, msg);
// check every 5 secs
Thread.sleep(5000);
// and report to system log and console
((EC2Computer) _computer).getCloud().log(LOGGER, Level.FINEST, listener, msg);
}

launch(computer, listener, computer.describeInstance());
Expand Down
19 changes: 0 additions & 19 deletions src/main/java/hudson/plugins/ec2/EC2SpotComputerLauncher.java

This file was deleted.

17 changes: 0 additions & 17 deletions src/main/java/hudson/plugins/ec2/EC2SpotRetentionStrategy.java

This file was deleted.

23 changes: 20 additions & 3 deletions src/main/java/hudson/plugins/ec2/EC2SpotSlave.java
Expand Up @@ -15,11 +15,14 @@
import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsRequest;
import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsResult;
import com.amazonaws.services.ec2.model.SpotInstanceRequest;
import com.amazonaws.services.ec2.model.SpotInstanceState;
import com.amazonaws.services.ec2.model.TerminateInstancesRequest;

import hudson.Extension;
import hudson.model.Hudson;
import hudson.model.Descriptor.FormException;
import hudson.plugins.ec2.ssh.EC2UnixLauncher;
import hudson.plugins.ec2.win.EC2WindowsLauncher;
import hudson.slaves.NodeProperty;

public final class EC2SpotSlave extends EC2AbstractSlave {
Expand All @@ -28,18 +31,25 @@ public final class EC2SpotSlave extends EC2AbstractSlave {

public EC2SpotSlave(String name, String spotInstanceRequestId, String description, String remoteFS, int numExecutors, Mode mode, String initScript, String tmpDir, String labelString, String remoteAdmin, String jvmopts, String idleTerminationMinutes, List<EC2Tag> tags, String cloudName, boolean usePrivateDnsName, int launchTimeout, AMITypeData amiType)
throws FormException, IOException {
this(name, spotInstanceRequestId, description, remoteFS, numExecutors, mode, initScript, tmpDir, labelString, Collections.<NodeProperty<?>> emptyList(), remoteAdmin, jvmopts, idleTerminationMinutes, tags, cloudName, usePrivateDnsName, launchTimeout, amiType);
this(description + " (" + name + ")", spotInstanceRequestId, description, remoteFS, numExecutors, mode, initScript, tmpDir, labelString, Collections.<NodeProperty<?>> emptyList(), remoteAdmin, jvmopts, idleTerminationMinutes, tags, cloudName, usePrivateDnsName, launchTimeout, amiType);
}

@DataBoundConstructor
public EC2SpotSlave(String name, String spotInstanceRequestId, String description, String remoteFS, int numExecutors, Mode mode, String initScript, String tmpDir, String labelString, List<? extends NodeProperty<?>> nodeProperties, String remoteAdmin, String jvmopts, String idleTerminationMinutes, List<EC2Tag> tags, String cloudName, boolean usePrivateDnsName, int launchTimeout, AMITypeData amiType)
throws FormException, IOException {

super(name, "", description, remoteFS, numExecutors, mode, labelString, new EC2SpotComputerLauncher(), new EC2SpotRetentionStrategy(idleTerminationMinutes), initScript, tmpDir, nodeProperties, remoteAdmin, jvmopts, false, idleTerminationMinutes, tags, cloudName, usePrivateDnsName, false, launchTimeout, amiType);
super(name, "", description, remoteFS, numExecutors, mode, labelString, amiType.isWindows() ? new EC2WindowsLauncher() :
new EC2UnixLauncher(), new EC2RetentionStrategy(idleTerminationMinutes), initScript, tmpDir, nodeProperties, remoteAdmin, jvmopts, false, idleTerminationMinutes, tags, cloudName, usePrivateDnsName, false, launchTimeout, amiType);

this.name = name;
this.spotInstanceRequestId = spotInstanceRequestId;
}

@Override
protected boolean isAlive(boolean force) {
return super.isAlive(force) || !this.isSpotRequestDead();
}

/**
* Cancel the spot request for the instance. Terminate the instance if it is up. Remove the slave from Jenkins.
*/
Expand All @@ -58,7 +68,7 @@ public void terminate() {

// Terminate the slave if it is running
if (instanceId != null && !instanceId.equals("")) {
if (!isAlive(true)) {
if (!super.isAlive(true)) {
/*
* The node has been killed externally, so we've nothing to do here
*/
Expand Down Expand Up @@ -123,6 +133,13 @@ SpotInstanceRequest getSpotRequest() {
return siRequests.get(0);
}

public boolean isSpotRequestDead() {
SpotInstanceState requestState = SpotInstanceState.fromValue(this.getSpotRequest().getState());
return requestState == SpotInstanceState.Cancelled
|| requestState == SpotInstanceState.Closed
|| requestState == SpotInstanceState.Failed;
}

/**
* Accessor for the spotInstanceRequestId
*/
Expand Down
39 changes: 2 additions & 37 deletions src/main/java/hudson/plugins/ec2/SlaveTemplate.java
Expand Up @@ -725,43 +725,7 @@ private EC2AbstractSlave provisionSpot(TaskListener listener) throws AmazonClien
}
}

// The slave must know the Jenkins server to register with as well
// as the name of the node in Jenkins it should register as. The
// only
// way to give information to the Spot slaves is through the ec2
// user data
String jenkinsUrl = Hudson.getInstance().getRootUrl();
// We must provide a unique node name for the slave to connect to
// Jenkins.
// We don't have the EC2 generated instance ID, or the Spot request
// ID
// until after the instance is requested, which is then too late to
// set the
// user-data for the request. Instead we generate a unique name from
// UUID
// so that the slave has a unique name within Jenkins to register
// to.
String slaveName = UUID.randomUUID().toString();
String newUserData = "";

// We want to allow node configuration with cloud-init and
// user-data,
// while maintaining backward compatibility with old ami's
// The 'new' way is triggered by the presence of '${SLAVE_NAME}'' in
// the user data
// (which is not too much to ask)
if (userData.contains("${SLAVE_NAME}")) {
// The cloud-init compatible way
newUserData = new String(userData);
newUserData = newUserData.replace("${SLAVE_NAME}", slaveName);
newUserData = newUserData.replace("${JENKINS_URL}", jenkinsUrl);
} else {
// The 'old' way - maitain full backward compatibility
newUserData = "JENKINS_URL=" + jenkinsUrl + "&SLAVE_NAME=" + slaveName + "&USER_DATA="
+ Base64.encodeBase64String(userData.getBytes());
}

String userDataString = Base64.encodeBase64String(newUserData.getBytes());
String userDataString = Base64.encodeBase64String(userData.getBytes());

launchSpecification.setUserData(userDataString);
launchSpecification.setKeyName(keyPair.getKeyName());
Expand Down Expand Up @@ -808,6 +772,7 @@ private EC2AbstractSlave provisionSpot(TaskListener listener) throws AmazonClien
if (spotInstReq == null) {
throw new AmazonClientException("Spot instance request is null");
}
String slaveName = spotInstReq.getSpotInstanceRequestId();

/* Now that we have our Spot request, we can set tags on it */
if (inst_tags != null) {
Expand Down
Expand Up @@ -50,13 +50,6 @@ THE SOFTWARE.
</f:entry>

<f:optionalBlock name="spotConfig" title="Use Spot Instance" checked="${instance.spotConfig != null}">
<f:description>Be aware, AMIs used for Spot slaves must be configured to callback to Jenkins when
a Spot request has been fulfilled and the instance has become available. The call back script can
be found by clicking the following link. <a href="${resURL}/plugin/ec2/AMI-Scripts/ubuntu-ami-setup.sh">Ubuntu-ami-setup</a>
</f:description>
<f:description>Slaves designated as Spot slaves will initially show up as disconnected. The state
will change to connecting when a Spot request has been fulfilled. </f:description>

<f:validateButton title="${%Check Current Spot Price}" progress="${%Checking...}" method="currentSpotPrice" with="useInstanceProfileForCredentials,accessId,secretKey,region,type,zone" />

<f:entry title="${%Spot Max Bid Price}" field="spotMaxBidPrice">
Expand Down

0 comments on commit ae7e53a

Please sign in to comment.