validations = new ArrayList<>();
+
+ if (System.getProperty(SSH_PRIVATE_KEY_FILEPATH, "").isEmpty()) {
+ // not using a static ssh key file
+ if (value == null || value.isEmpty()) {
+ return FormValidation.error("No ssh credentials selected and no private key file defined");
+ }
+
+ SSHUserPrivateKey sshCredential = getSshCredential(value, context);
+ if (sshCredential != null) {
+ privateKey = sshCredential.getPrivateKey();
+ } else {
+ return FormValidation.error("Failed to find credential \"" + value + "\" in store.");
+ }
} else {
- return FormValidation.error("Failed to find credential \"" + value + "\" in store.");
+ EC2PrivateKey k = EC2PrivateKey.fetchFromDisk();
+ if (k == null) {
+ validations.add(FormValidation.error(
+ "Failed to find private key file " + System.getProperty(SSH_PRIVATE_KEY_FILEPATH)));
+ if (!StringUtils.isEmpty(value)) {
+ validations.add(FormValidation.warning(
+ "Private key file path defined, selected credential will be ignored"));
+ }
+ return FormValidation.aggregate(validations);
+ }
+ privateKey = k.getPrivateKey();
}
boolean hasStart = false, hasEnd = false;
BufferedReader br = new BufferedReader(new StringReader(privateKey));
String line;
while ((line = br.readLine()) != null) {
- if (line.equals("-----BEGIN RSA PRIVATE KEY-----") ||
- line.equals("-----BEGIN OPENSSH PRIVATE KEY-----"))
+ if (line.equals("-----BEGIN RSA PRIVATE KEY-----")
+ || line.equals("-----BEGIN OPENSSH PRIVATE KEY-----")) {
hasStart = true;
- if (line.equals("-----END RSA PRIVATE KEY-----") ||
- line.equals("-----END OPENSSH PRIVATE KEY-----"))
+ }
+ if (line.equals("-----END RSA PRIVATE KEY-----") || line.equals("-----END OPENSSH PRIVATE KEY-----")) {
hasEnd = true;
+ }
}
- if (!hasStart)
- return FormValidation.error("This doesn't look like a private key at all");
- if (!hasEnd)
- return FormValidation
- .error("The private key is missing the trailing 'END RSA PRIVATE KEY' marker. Copy&paste error?");
- return FormValidation.ok();
+ if (!hasStart) {
+ validations.add(FormValidation.error("This doesn't look like a private key at all"));
+ }
+ if (!hasEnd) {
+ validations.add(FormValidation.error(
+ "The private key is missing the trailing 'END RSA PRIVATE KEY' marker. Copy&paste error?"));
+ }
+
+ if (!System.getProperty(SSH_PRIVATE_KEY_FILEPATH, "").isEmpty()) {
+ if (!StringUtils.isEmpty(value)) {
+ validations.add(FormValidation.warning("Using private key file instead of selected credential"));
+ } else {
+ validations.add(FormValidation.ok("Using private key file"));
+ }
+ }
+
+ try {
+ FIPS140Utils.ensurePrivateKeyInFipsMode(privateKey);
+ } catch (IllegalArgumentException ex) {
+ validations.add(FormValidation.error(ex, ex.getLocalizedMessage()));
+ }
+
+ validations.add(FormValidation.ok("SSH key validation successful"));
+ return FormValidation.aggregate(validations);
}
/**
* Tests the connection settings.
- *
+ *
* Overriding needs to {@code @RequirePOST}
- * @param ec2endpoint
+ * @param region
* @param useInstanceProfileForCredentials
* @param credentialsId
* @param sshKeysCredentialsId
* @param roleArn
* @param roleSessionName
- * @param region
* @return the validation result
* @throws IOException
* @throws ServletException
*/
- @POST
- protected FormValidation doTestConnection(@AncestorInPath ItemGroup context, URL ec2endpoint, boolean useInstanceProfileForCredentials, String credentialsId, String sshKeysCredentialsId, String roleArn, String roleSessionName, String region)
+ @RequirePOST
+ public FormValidation doTestConnection(
+ @AncestorInPath ItemGroup context,
+ @QueryParameter String region,
+ @QueryParameter String altEC2Endpoint,
+ @QueryParameter boolean useInstanceProfileForCredentials,
+ @QueryParameter String credentialsId,
+ @QueryParameter String sshKeysCredentialsId,
+ @QueryParameter String roleArn,
+ @QueryParameter String roleSessionName)
throws IOException, ServletException {
if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
return FormValidation.ok();
}
try {
- SSHUserPrivateKey sshCredential = getSshCredential(sshKeysCredentialsId, context);
+ List validations = new ArrayList<>();
+
+ LOGGER.fine(() -> "begin doTestConnection()");
String privateKey = "";
- if (sshCredential != null) {
- privateKey = sshCredential.getPrivateKey();
+ if (System.getProperty(SSH_PRIVATE_KEY_FILEPATH, "").isEmpty()) {
+ LOGGER.fine(() -> "static credential is in use");
+ SSHUserPrivateKey sshCredential = getSshCredential(sshKeysCredentialsId, context);
+ if (sshCredential != null) {
+ privateKey = sshCredential.getPrivateKey();
+ } else {
+ return FormValidation.error(
+ "Failed to find credential \"" + sshKeysCredentialsId + "\" in store.");
+ }
} else {
- return FormValidation.error("Failed to find credential \"" + sshKeysCredentialsId + "\" in store.");
+ EC2PrivateKey k = EC2PrivateKey.fetchFromDisk();
+ if (k == null) {
+ validations.add(FormValidation.error(
+ "Failed to find private key file " + System.getProperty(SSH_PRIVATE_KEY_FILEPATH)));
+ if (!StringUtils.isEmpty(sshKeysCredentialsId)) {
+ validations.add(FormValidation.warning(
+ "Private key file path defined, selected credential will be ignored"));
+ }
+ return FormValidation.aggregate(validations);
+ }
+ privateKey = k.getPrivateKey();
+ }
+ LOGGER.fine(() -> "private key found ok");
+
+ if (Util.fixEmpty(region) == null) {
+ region = DEFAULT_EC2_HOST;
}
- AWSCredentialsProvider credentialsProvider = createCredentialsProvider(useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region);
- AmazonEC2 ec2 = AmazonEC2Factory.getInstance().connect(credentialsProvider, ec2endpoint);
+ AwsCredentialsProvider credentialsProvider = createCredentialsProvider(
+ useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region);
+ Ec2Client ec2 = AmazonEC2Factory.getInstance()
+ .connect(credentialsProvider, parseRegion(region), parseEndpoint(altEC2Endpoint));
ec2.describeInstances();
- if (privateKey.trim().length() > 0) {
+ if (!privateKey.trim().isEmpty()) {
// check if this key exists
EC2PrivateKey pk = new EC2PrivateKey(privateKey);
- if (pk.find(ec2) == null)
- return FormValidation
- .error("The EC2 key pair private key isn't registered to this EC2 region (fingerprint is "
- + pk.getFingerprint() + ")");
+ if (pk.find(ec2) == null) {
+ validations.add(FormValidation.error(
+ "The EC2 key pair private key isn't registered to this EC2 region (fingerprint is "
+ + pk.getFingerprint() + ")"));
+ }
+ }
+
+ if (!System.getProperty(SSH_PRIVATE_KEY_FILEPATH, "").isEmpty()) {
+ if (!StringUtils.isEmpty(sshKeysCredentialsId)) {
+ validations.add(
+ FormValidation.warning("Using private key file instead of selected credential"));
+ } else {
+ validations.add(FormValidation.ok("Using private key file"));
+ }
}
- return FormValidation.ok(Messages.EC2Cloud_Success());
- } catch (AmazonClientException e) {
+ try {
+ FIPS140Utils.ensurePrivateKeyInFipsMode(privateKey);
+ } catch (IllegalArgumentException ex) {
+ validations.add(FormValidation.error(ex, ex.getLocalizedMessage()));
+ }
+
+ validations.add(FormValidation.ok(Messages.EC2Cloud_Success()));
+ return FormValidation.aggregate(validations);
+ } catch (SdkException e) {
LOGGER.log(Level.WARNING, "Failed to check EC2 credential", e);
return FormValidation.error(e.getMessage());
}
@@ -1223,7 +1737,66 @@ public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context)
}
return new StandardListBoxModel()
.includeEmptyValue()
- .includeMatchingAs(ACL.SYSTEM, context, AmazonWebServicesCredentials.class, Collections.emptyList(), CredentialsMatchers.always());
+ .includeMatchingAs(
+ ACL.SYSTEM2,
+ context,
+ AmazonWebServicesCredentials.class,
+ Collections.emptyList(),
+ CredentialsMatchers.always());
+ }
+
+ @POST
+ public FormValidation doCheckCloudName(@QueryParameter String value) {
+ if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
+ return FormValidation.ok();
+ }
+ try {
+ Jenkins.checkGoodName(value);
+ } catch (Failure e) {
+ return FormValidation.error(e.getMessage());
+ }
+ return FormValidation.ok();
+ }
+
+ @POST
+ public FormValidation doCheckAltEC2Endpoint(@QueryParameter String value) {
+ if (Util.fixEmpty(value) != null) {
+ try {
+ new URL(value);
+ } catch (MalformedURLException ignored) {
+ return FormValidation.error(Messages.EC2Cloud_MalformedUrl());
+ }
+ }
+ return FormValidation.ok();
+ }
+
+ @RequirePOST
+ public ListBoxModel doFillRegionItems(
+ @QueryParameter String altEC2Endpoint,
+ @QueryParameter boolean useInstanceProfileForCredentials,
+ @QueryParameter String credentialsId)
+ throws IOException, ServletException {
+ ListBoxModel model = new ListBoxModel();
+ if (Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
+ try {
+ AwsCredentialsProvider credentialsProvider =
+ createCredentialsProvider(useInstanceProfileForCredentials, credentialsId);
+ URI endpoint = parseEndpoint(altEC2Endpoint);
+ Ec2Client client = AmazonEC2Factory.getInstance()
+ .connect(credentialsProvider, getBootstrapRegion(endpoint), endpoint);
+ DescribeRegionsResponse regions = client.describeRegions();
+ List regionList = regions.regions();
+ for (software.amazon.awssdk.services.ec2.model.Region r : regionList) {
+ String name = r.regionName();
+ Region rr = Region.of(name);
+ RegionMetadata regionMetadata = rr != null ? RegionMetadata.of(rr) : null;
+ model.add(regionMetadata != null ? regionMetadata.description() : name, name);
+ }
+ } catch (SdkClientException ex) {
+ // Ignore, as this may happen before the credentials are specified
+ }
+ }
+ return model;
}
}
@@ -1234,8 +1807,9 @@ public static void log(Logger logger, Level level, TaskListener listener, String
public static void log(Logger logger, Level level, TaskListener listener, String message, Throwable exception) {
logger.log(level, message, exception);
if (listener != null) {
- if (exception != null)
+ if (exception != null) {
message += " Exception: " + exception;
+ }
LogRecord lr = new LogRecord(level, message);
lr.setLoggerName(LOGGER.getName());
PrintStream printStream = listener.getLogger();
@@ -1255,17 +1829,21 @@ protected void doRun() throws IOException {
Jenkins instance = Jenkins.get();
if (instance.clouds != null) {
for (Cloud cloud : instance.clouds) {
- if (cloud instanceof EC2Cloud) {
- EC2Cloud ec2_cloud = (EC2Cloud) cloud;
+ if (cloud instanceof EC2Cloud ec2_cloud) {
LOGGER.finer(() -> "Checking EC2 Connection on: " + ec2_cloud.getDisplayName());
try {
- if(ec2_cloud.connection != null) {
+ if (ec2_cloud.connection != null) {
List filters = new ArrayList<>();
- filters.add(new Filter("tag-key").withValues("bogus-EC2ConnectionKeepalive"));
- DescribeInstancesRequest dir = new DescribeInstancesRequest().withFilters(filters);
+ filters.add(Filter.builder()
+ .name("tag-key")
+ .values("bogus-EC2ConnectionKeepalive")
+ .build());
+ DescribeInstancesRequest dir = DescribeInstancesRequest.builder()
+ .filters(filters)
+ .build();
ec2_cloud.connection.describeInstances(dir);
}
- } catch (AmazonClientException e) {
+ } catch (SdkException e) {
LOGGER.finer(() -> "Reconnecting to EC2 on: " + ec2_cloud.getDisplayName());
ec2_cloud.reconnectToEc2();
}
diff --git a/src/main/java/hudson/plugins/ec2/EC2Computer.java b/src/main/java/hudson/plugins/ec2/EC2Computer.java
index 489b2f938..2bc7abe9c 100644
--- a/src/main/java/hudson/plugins/ec2/EC2Computer.java
+++ b/src/main/java/hudson/plugins/ec2/EC2Computer.java
@@ -23,22 +23,29 @@
*/
package hudson.plugins.ec2;
-import com.amazonaws.services.ec2.model.*;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import hudson.Util;
import hudson.model.Node;
import hudson.slaves.SlaveComputer;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Base64;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;
-
-
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
-import com.amazonaws.AmazonClientException;
-import com.amazonaws.services.ec2.AmazonEC2;
import org.kohsuke.stapler.verb.POST;
+import software.amazon.awssdk.core.exception.SdkException;
+import software.amazon.awssdk.services.ec2.Ec2Client;
+import software.amazon.awssdk.services.ec2.model.DescribeInstanceTypesRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeInstanceTypesResponse;
+import software.amazon.awssdk.services.ec2.model.GetConsoleOutputRequest;
+import software.amazon.awssdk.services.ec2.model.GetConsoleOutputResponse;
+import software.amazon.awssdk.services.ec2.model.Instance;
+import software.amazon.awssdk.services.ec2.model.InstanceTypeHypervisor;
/**
* @author Kohsuke Kawaguchi
@@ -52,6 +59,16 @@ public class EC2Computer extends SlaveComputer {
*/
private volatile Instance ec2InstanceDescription;
+ /**
+ * Timestamp when {@link #ec2InstanceDescription} was last fetched.
+ * Used for TTL cache to avoid repeated EC2 API calls during SSH verification retries.
+ */
+ private volatile long instanceCacheTimestamp;
+
+ /** TTL in ms for instance/state cache. Configurable via system property. */
+ private static final long INSTANCE_CACHE_TTL_MS =
+ Long.getLong(EC2Computer.class.getName() + ".instanceCacheTTLMs", 30_000);
+
private volatile Boolean isNitro;
public EC2Computer(EC2AbstractSlave slave) {
@@ -99,9 +116,9 @@ public SlaveTemplate getSlaveTemplate() {
/**
* Gets the EC2 console output.
*/
- public String getConsoleOutput() throws AmazonClientException {
+ public String getConsoleOutput() throws SdkException {
try {
- return getDecodedConsoleOutputResponse().getOutput();
+ return getDecodedConsoleOutputResponse().output();
} catch (InterruptedException e) {
return null;
}
@@ -111,44 +128,49 @@ public String getConsoleOutput() throws AmazonClientException {
* Gets the EC2 decoded console output.
* @since TODO
*/
- public String getDecodedConsoleOutput() throws AmazonClientException {
+ public String getDecodedConsoleOutput() throws SdkException {
try {
- return getDecodedConsoleOutputResponse().getDecodedOutput();
+ String encodedOutput = getDecodedConsoleOutputResponse().output();
+ byte[] decoded = Base64.getDecoder().decode(encodedOutput);
+ return new String(decoded, StandardCharsets.UTF_8);
} catch (InterruptedException e) {
return null;
}
}
- private GetConsoleOutputResult getDecodedConsoleOutputResponse() throws AmazonClientException, InterruptedException {
- AmazonEC2 ec2 = getCloud().connect();
- GetConsoleOutputRequest request = new GetConsoleOutputRequest(getInstanceId());
+ private GetConsoleOutputResponse getDecodedConsoleOutputResponse() throws SdkException, InterruptedException {
+ Ec2Client ec2 = getCloud().connect();
+ GetConsoleOutputRequest.Builder requestBuilder =
+ GetConsoleOutputRequest.builder().instanceId(getInstanceId());
if (checkIfNitro()) {
- //Can only be used if instance has hypervisor Nitro
- request.setLatest(true);
+ // Can only be used if instance has hypervisor Nitro
+ requestBuilder.latest(true);
}
- return ec2.getConsoleOutput(request);
+ return ec2.getConsoleOutput(requestBuilder.build());
}
/**
* Check if instance has hypervisor Nitro
*/
- private boolean checkIfNitro() throws AmazonClientException, InterruptedException {
+ private boolean checkIfNitro() throws SdkException, InterruptedException {
try {
if (isNitro == null) {
- DescribeInstanceTypesRequest request = new DescribeInstanceTypesRequest();
- request.setInstanceTypes(Collections.singletonList(describeInstance().getInstanceType()));
- AmazonEC2 ec2 = getCloud().connect();
- DescribeInstanceTypesResult result = ec2.describeInstanceTypes(request);
- if (result.getInstanceTypes().size() == 1) {
- String hypervisor = result.getInstanceTypes().get(0).getHypervisor();
- isNitro = hypervisor.equals("nitro");
+ DescribeInstanceTypesRequest request = DescribeInstanceTypesRequest.builder()
+ .instanceTypes(
+ Collections.singletonList(describeInstance().instanceType()))
+ .build();
+ Ec2Client ec2 = getCloud().connect();
+ DescribeInstanceTypesResponse result = ec2.describeInstanceTypes(request);
+ if (result.instanceTypes().size() == 1) {
+ InstanceTypeHypervisor hypervisor =
+ result.instanceTypes().get(0).hypervisor();
+ isNitro = hypervisor == InstanceTypeHypervisor.NITRO;
} else {
isNitro = false;
}
-
}
return isNitro;
- } catch (AmazonClientException e) {
+ } catch (SdkException e) {
LOGGER.log(Level.WARNING, "Could not describe-instance-types to check if instance is nitro based", e);
isNitro = false;
return isNitro;
@@ -159,56 +181,61 @@ private boolean checkIfNitro() throws AmazonClientException, InterruptedExceptio
* Obtains the instance state description in EC2.
*
*
- * This method returns a cached state, so it's not suitable to check {@link Instance#getState()} from the returned
- * instance (but all the other fields are valid as it won't change.)
- *
+ * This method returns a cached state (with TTL), so it's not suitable to check {@link Instance#state()} from the
+ * returned instance (but all the other fields are valid as it won't change.)
+ *
* The cache can be flushed using {@link #updateInstanceDescription()}
*/
- public Instance describeInstance() throws AmazonClientException, InterruptedException {
- if (ec2InstanceDescription == null)
- ec2InstanceDescription = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud());
+ public Instance describeInstance() throws SdkException, InterruptedException {
+ long now = System.currentTimeMillis();
+ if (ec2InstanceDescription != null && (now - instanceCacheTimestamp) < INSTANCE_CACHE_TTL_MS) {
+ return ec2InstanceDescription;
+ }
+ ec2InstanceDescription = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud());
+ instanceCacheTimestamp = now;
return ec2InstanceDescription;
}
/**
* This will flush any cached description held by {@link #describeInstance()}.
*/
- public Instance updateInstanceDescription() throws AmazonClientException, InterruptedException {
- return ec2InstanceDescription = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud());
+ public Instance updateInstanceDescription() throws SdkException, InterruptedException {
+ ec2InstanceDescription = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud());
+ instanceCacheTimestamp = System.currentTimeMillis();
+ return ec2InstanceDescription;
}
/**
* Gets the current state of the instance.
*
*
- * Unlike {@link #describeInstance()}, this method always return the current status by calling EC2.
+ * Uses a short TTL cache to avoid repeated EC2 API calls during SSH verification retries.
*/
- public InstanceState getState() throws AmazonClientException, InterruptedException {
- ec2InstanceDescription = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud());
- return InstanceState.find(ec2InstanceDescription.getState().getName());
+ public InstanceState getState() throws SdkException, InterruptedException {
+ return InstanceState.find(describeInstance().state().name().toString());
}
/**
* Number of milli-secs since the instance was started.
*/
- public long getUptime() throws AmazonClientException, InterruptedException {
- return System.currentTimeMillis() - describeInstance().getLaunchTime().getTime();
+ public long getUptime() throws SdkException, InterruptedException {
+ return describeInstance().launchTime().until(Instant.now(), ChronoUnit.MILLIS);
}
/**
* Returns uptime in the human readable form.
*/
- public String getUptimeString() throws AmazonClientException, InterruptedException {
+ public String getUptimeString() throws SdkException, InterruptedException {
return Util.getTimeSpanString(getUptime());
}
/**
- * Return the time this instance was launched in ms since the epoch.
+ * Return the Instant this instance was launched
*
- * @return Time this instance was launched, in ms since the epoch.
+ * @return Instant this instance was launched
*/
- public long getLaunchTime() throws InterruptedException {
- return this.describeInstance().getLaunchTime().getTime();
+ public Instant getLaunchTime() throws InterruptedException {
+ return this.describeInstance().launchTime();
}
/**
@@ -219,8 +246,9 @@ public long getLaunchTime() throws InterruptedException {
public HttpResponse doDoDelete() throws IOException {
checkPermission(DELETE);
EC2AbstractSlave node = getNode();
- if (node != null)
+ if (node != null) {
node.terminate();
+ }
return new HttpRedirect("..");
}
@@ -261,5 +289,4 @@ public void onConnected() {
node.onConnected();
}
}
-
}
diff --git a/src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java b/src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java
index 4d95a7b01..23c3b43d8 100644
--- a/src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java
+++ b/src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java
@@ -23,15 +23,22 @@
*/
package hudson.plugins.ec2;
+import static org.apache.sshd.client.session.ClientSession.REMOTE_COMMAND_WAIT_EVENTS;
+
import hudson.model.TaskListener;
import hudson.slaves.ComputerLauncher;
import hudson.slaves.SlaveComputer;
-
import java.io.IOException;
+import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
-
-import com.amazonaws.AmazonClientException;
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.channel.ClientChannelEvent;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.scp.client.CloseableScpClient;
+import org.apache.sshd.scp.client.ScpClient;
+import org.apache.sshd.scp.client.ScpClientCreator;
+import software.amazon.awssdk.core.exception.SdkException;
/**
* {@link ComputerLauncher} for EC2 that wraps the real user-specified {@link ComputerLauncher}.
@@ -46,33 +53,50 @@ public void launch(SlaveComputer slaveComputer, TaskListener listener) {
try {
EC2Computer computer = (EC2Computer) slaveComputer;
launchScript(computer, listener);
- } catch (AmazonClientException | IOException e) {
+ } catch (SdkException | IOException e) {
e.printStackTrace(listener.error(e.getMessage()));
- if (slaveComputer.getNode() instanceof EC2AbstractSlave) {
- LOGGER.log(Level.FINE, String.format("Terminating the ec2 agent %s due a problem launching or connecting to it", slaveComputer.getName()), e);
- EC2AbstractSlave ec2AbstractSlave = (EC2AbstractSlave) slaveComputer.getNode();
- if (ec2AbstractSlave != null) {
- ec2AbstractSlave.terminate();
- }
+ if (slaveComputer.getNode() instanceof EC2AbstractSlave ec2AbstractSlave) {
+ LOGGER.log(
+ Level.FINE,
+ String.format(
+ "Terminating the ec2 agent %s due a problem launching or connecting to it",
+ slaveComputer.getName()),
+ e);
+ ec2AbstractSlave.terminate();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace(listener.error(e.getMessage()));
- if (slaveComputer.getNode() instanceof EC2AbstractSlave) {
- LOGGER.log(Level.FINE, String.format("Terminating the ec2 agent %s due a problem launching or connecting to it", slaveComputer.getName()), e);
- EC2AbstractSlave ec2AbstractSlave = (EC2AbstractSlave) slaveComputer.getNode();
- if (ec2AbstractSlave != null) {
- ec2AbstractSlave.terminate();
- }
+ if (slaveComputer.getNode() instanceof EC2AbstractSlave ec2AbstractSlave) {
+ LOGGER.log(
+ Level.FINE,
+ String.format(
+ "Terminating the ec2 agent %s due a problem launching or connecting to it",
+ slaveComputer.getName()),
+ e);
+ ec2AbstractSlave.terminate();
}
}
-
}
/**
* Stage 2 of the launch. Called after the EC2 instance comes up.
*/
protected abstract void launchScript(EC2Computer computer, TaskListener listener)
- throws AmazonClientException, IOException, InterruptedException;
+ throws SdkException, IOException, InterruptedException;
+ protected int waitCompletion(ClientChannel clientChannel, long timeout) {
+ Set clientChannelEvents = clientChannel.waitFor(REMOTE_COMMAND_WAIT_EVENTS, timeout);
+ if (clientChannelEvents.contains(ClientChannelEvent.TIMEOUT)) {
+ return -1;
+ } else {
+ return clientChannel.getExitStatus();
+ }
+ }
+
+ protected CloseableScpClient createScpClient(ClientSession session) {
+ ScpClientCreator creator = ScpClientCreator.instance();
+ ScpClient client = creator.createScpClient(session);
+ return CloseableScpClient.singleSessionInstance(client);
+ }
}
diff --git a/src/main/java/hudson/plugins/ec2/EC2ComputerListener.java b/src/main/java/hudson/plugins/ec2/EC2ComputerListener.java
index db79e2380..9cd62873a 100644
--- a/src/main/java/hudson/plugins/ec2/EC2ComputerListener.java
+++ b/src/main/java/hudson/plugins/ec2/EC2ComputerListener.java
@@ -1,9 +1,10 @@
package hudson.plugins.ec2;
import hudson.Extension;
-import hudson.model.TaskListener;
import hudson.model.Computer;
+import hudson.model.TaskListener;
import hudson.slaves.ComputerListener;
+import jenkins.model.Jenkins;
@Extension
public class EC2ComputerListener extends ComputerListener {
@@ -13,5 +14,9 @@ public void onOnline(Computer c, TaskListener listener) {
if (c instanceof EC2Computer) {
((EC2Computer) c).onConnected();
}
+ Jenkins j = Jenkins.getInstanceOrNull();
+ if (j != null) {
+ j.getQueue().scheduleMaintenance();
+ }
}
}
diff --git a/src/main/java/hudson/plugins/ec2/EC2Filter.java b/src/main/java/hudson/plugins/ec2/EC2Filter.java
index b432d27b0..c7f82229a 100644
--- a/src/main/java/hudson/plugins/ec2/EC2Filter.java
+++ b/src/main/java/hudson/plugins/ec2/EC2Filter.java
@@ -23,21 +23,18 @@
*/
package hudson.plugins.ec2;
-import hudson.model.Descriptor;
-import hudson.model.AbstractDescribableImpl;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.Util;
-import org.kohsuke.stapler.DataBoundConstructor;
-
+import hudson.model.AbstractDescribableImpl;
+import hudson.model.Descriptor;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-import edu.umd.cs.findbugs.annotations.NonNull;
-
-import com.amazonaws.services.ec2.model.Filter;
+import org.kohsuke.stapler.DataBoundConstructor;
+import software.amazon.awssdk.services.ec2.model.Filter;
public class EC2Filter extends AbstractDescribableImpl {
@NonNull
@@ -65,10 +62,12 @@ public String toString() {
@Override
public boolean equals(Object o) {
- if (o == null)
+ if (o == null) {
return false;
- if (this.getClass() != o.getClass())
+ }
+ if (this.getClass() != o.getClass()) {
return false;
+ }
EC2Filter other = (EC2Filter) o;
return name.equals(other.name) && getValuesList().equals(other.getValuesList());
@@ -91,22 +90,19 @@ public String getValues() {
@NonNull
private List getValuesList() {
- return Stream.of(Util.tokenize(values))
- .collect(Collectors.toList());
+ return Stream.of(Util.tokenize(values)).collect(Collectors.toList());
}
/* Helper method to convert EC2Filter to Filter */
@NonNull
public Filter toFilter() {
- return new Filter(name, getValuesList());
+ return Filter.builder().name(name).values(getValuesList()).build();
}
/* Helper method to convert list of EC2Filter to list of Filter */
@NonNull
public static List toFilterList(@CheckForNull List filters) {
- return Util.fixNull(filters).stream()
- .map(EC2Filter::toFilter)
- .collect(Collectors.toList());
+ return Util.fixNull(filters).stream().map(EC2Filter::toFilter).collect(Collectors.toList());
}
@Extension
diff --git a/src/main/java/hudson/plugins/ec2/EC2HostAddressProvider.java b/src/main/java/hudson/plugins/ec2/EC2HostAddressProvider.java
index 0be9d394c..7c0c4786d 100644
--- a/src/main/java/hudson/plugins/ec2/EC2HostAddressProvider.java
+++ b/src/main/java/hudson/plugins/ec2/EC2HostAddressProvider.java
@@ -1,11 +1,8 @@
package hudson.plugins.ec2;
-import com.amazonaws.services.ec2.model.Instance;
-import org.apache.commons.lang.StringUtils;
-
import java.util.Optional;
-
-import static hudson.plugins.ec2.ConnectionStrategy.*;
+import org.apache.commons.lang.StringUtils;
+import software.amazon.awssdk.services.ec2.model.Instance;
public class EC2HostAddressProvider {
public static String unix(Instance instance, ConnectionStrategy strategy) {
@@ -34,34 +31,34 @@ public static String mac(Instance instance, ConnectionStrategy strategy) {
case PRIVATE_IP:
return getPrivateIpAddress(instance);
default:
- throw new IllegalArgumentException("Could not mac host address for strategy = " + strategy.toString());
+ throw new IllegalArgumentException("Could not mac host address for strategy = " + strategy);
}
}
public static String windows(Instance instance, ConnectionStrategy strategy) {
- if (strategy.equals(PRIVATE_DNS) || strategy.equals(PRIVATE_IP)) {
+ if (strategy.equals(ConnectionStrategy.PRIVATE_DNS) || strategy.equals(ConnectionStrategy.PRIVATE_IP)) {
return getPrivateIpAddress(instance);
- } else if (strategy.equals(PUBLIC_DNS) || strategy.equals(PUBLIC_IP)) {
+ } else if (strategy.equals(ConnectionStrategy.PUBLIC_DNS) || strategy.equals(ConnectionStrategy.PUBLIC_IP)) {
return getPublicIpAddress(instance);
} else {
- throw new IllegalArgumentException("Could not windows host address for strategy = " + strategy.toString());
+ throw new IllegalArgumentException("Could not windows host address for strategy = " + strategy);
}
}
private static String getPublicDnsName(Instance instance) {
- return instance.getPublicDnsName();
+ return instance.publicDnsName();
}
private static String getPublicIpAddress(Instance instance) {
- return instance.getPublicIpAddress();
+ return instance.publicIpAddress();
}
private static String getPrivateDnsName(Instance instance) {
- return instance.getPrivateDnsName();
+ return instance.privateDnsName();
}
private static String getPrivateIpAddress(Instance instance) {
- return instance.getPrivateIpAddress();
+ return instance.privateIpAddress();
}
private static Optional filterNonEmpty(String value) {
diff --git a/src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java b/src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java
index d648d8d10..1d8b51b57 100644
--- a/src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java
+++ b/src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java
@@ -1,30 +1,28 @@
package hudson.plugins.ec2;
import hudson.Extension;
-import hudson.model.Descriptor.FormException;
import hudson.model.Computer;
+import hudson.model.Descriptor.FormException;
import hudson.model.Node;
+import hudson.plugins.ec2.ssh.EC2MacLauncher;
import hudson.plugins.ec2.ssh.EC2UnixLauncher;
+import hudson.plugins.ec2.ssh.EC2WindowsSSHLauncher;
import hudson.plugins.ec2.win.EC2WindowsLauncher;
-import hudson.plugins.ec2.ssh.EC2MacLauncher;
import hudson.slaves.NodeProperty;
-
import java.io.IOException;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
-
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
-
import org.kohsuke.stapler.DataBoundConstructor;
-import org.kohsuke.stapler.StaplerRequest;
-
-import com.amazonaws.AmazonClientException;
-import com.amazonaws.services.ec2.AmazonEC2;
-import com.amazonaws.services.ec2.model.*;
+import org.kohsuke.stapler.StaplerRequest2;
+import software.amazon.awssdk.core.exception.SdkException;
+import software.amazon.awssdk.services.ec2.Ec2Client;
+import software.amazon.awssdk.services.ec2.model.TerminateInstancesRequest;
/**
* Agent running on EC2.
@@ -35,46 +33,440 @@ public class EC2OndemandSlave extends EC2AbstractSlave {
private static final Logger LOGGER = Logger.getLogger(EC2OndemandSlave.class.getName());
@Deprecated
- public EC2OndemandSlave(String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, int launchTimeout, AMITypeData amiType)
+ public EC2OndemandSlave(
+ String instanceId,
+ String templateDescription,
+ String remoteFS,
+ int numExecutors,
+ String labelString,
+ Mode mode,
+ String initScript,
+ String tmpDir,
+ String remoteAdmin,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String idleTerminationMinutes,
+ String publicDNS,
+ String privateDNS,
+ List tags,
+ String cloudName,
+ int launchTimeout,
+ AMITypeData amiType)
+ throws FormException, IOException {
+ this(
+ instanceId,
+ templateDescription,
+ remoteFS,
+ numExecutors,
+ labelString,
+ mode,
+ initScript,
+ tmpDir,
+ remoteAdmin,
+ jvmopts,
+ stopOnTerminate,
+ idleTerminationMinutes,
+ publicDNS,
+ privateDNS,
+ tags,
+ cloudName,
+ false,
+ launchTimeout,
+ amiType);
+ }
+
+ @Deprecated
+ public EC2OndemandSlave(
+ String instanceId,
+ String templateDescription,
+ String remoteFS,
+ int numExecutors,
+ String labelString,
+ Mode mode,
+ String initScript,
+ String tmpDir,
+ String remoteAdmin,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String idleTerminationMinutes,
+ String publicDNS,
+ String privateDNS,
+ List tags,
+ String cloudName,
+ boolean useDedicatedTenancy,
+ int launchTimeout,
+ AMITypeData amiType)
throws FormException, IOException {
- this(instanceId, templateDescription, remoteFS, numExecutors, labelString, mode, initScript, tmpDir, remoteAdmin, jvmopts, stopOnTerminate, idleTerminationMinutes, publicDNS, privateDNS, tags, cloudName, false, launchTimeout, amiType);
+ this(
+ instanceId,
+ templateDescription,
+ remoteFS,
+ numExecutors,
+ labelString,
+ mode,
+ initScript,
+ tmpDir,
+ remoteAdmin,
+ jvmopts,
+ stopOnTerminate,
+ idleTerminationMinutes,
+ publicDNS,
+ privateDNS,
+ tags,
+ cloudName,
+ false,
+ useDedicatedTenancy,
+ launchTimeout,
+ amiType);
}
@Deprecated
- public EC2OndemandSlave(String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, boolean useDedicatedTenancy, int launchTimeout, AMITypeData amiType)
+ public EC2OndemandSlave(
+ String instanceId,
+ String templateDescription,
+ String remoteFS,
+ int numExecutors,
+ String labelString,
+ Mode mode,
+ String initScript,
+ String tmpDir,
+ String remoteAdmin,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String idleTerminationMinutes,
+ String publicDNS,
+ String privateDNS,
+ List tags,
+ String cloudName,
+ boolean usePrivateDnsName,
+ boolean useDedicatedTenancy,
+ int launchTimeout,
+ AMITypeData amiType)
throws FormException, IOException {
- this(instanceId, templateDescription, remoteFS, numExecutors, labelString, mode, initScript, tmpDir, remoteAdmin, jvmopts, stopOnTerminate, idleTerminationMinutes, publicDNS, privateDNS, tags, cloudName, false, useDedicatedTenancy, launchTimeout, amiType);
+ this(
+ templateDescription + " (" + instanceId + ")",
+ instanceId,
+ templateDescription,
+ remoteFS,
+ numExecutors,
+ labelString,
+ mode,
+ initScript,
+ tmpDir,
+ Collections.emptyList(),
+ remoteAdmin,
+ jvmopts,
+ stopOnTerminate,
+ idleTerminationMinutes,
+ publicDNS,
+ privateDNS,
+ tags,
+ cloudName,
+ useDedicatedTenancy,
+ launchTimeout,
+ amiType,
+ ConnectionStrategy.backwardsCompatible(usePrivateDnsName, false, false),
+ -1);
}
@Deprecated
- public EC2OndemandSlave(String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, boolean usePrivateDnsName, boolean useDedicatedTenancy, int launchTimeout, AMITypeData amiType)
+ public EC2OndemandSlave(
+ String name,
+ String instanceId,
+ String templateDescription,
+ String remoteFS,
+ int numExecutors,
+ String labelString,
+ Mode mode,
+ String initScript,
+ String tmpDir,
+ List extends NodeProperty>> nodeProperties,
+ String remoteAdmin,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String idleTerminationMinutes,
+ String publicDNS,
+ String privateDNS,
+ List tags,
+ String cloudName,
+ boolean useDedicatedTenancy,
+ int launchTimeout,
+ AMITypeData amiType,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses)
throws FormException, IOException {
- this(templateDescription + " (" + instanceId + ")", instanceId, templateDescription, remoteFS, numExecutors, labelString, mode, initScript, tmpDir, Collections.emptyList(), remoteAdmin, jvmopts, stopOnTerminate, idleTerminationMinutes, publicDNS, privateDNS, tags, cloudName, useDedicatedTenancy, launchTimeout, amiType, ConnectionStrategy.backwardsCompatible(usePrivateDnsName, false, false), -1);
+ this(
+ name,
+ instanceId,
+ templateDescription,
+ remoteFS,
+ numExecutors,
+ labelString,
+ mode,
+ initScript,
+ tmpDir,
+ nodeProperties,
+ remoteAdmin,
+ jvmopts,
+ stopOnTerminate,
+ idleTerminationMinutes,
+ publicDNS,
+ privateDNS,
+ tags,
+ cloudName,
+ launchTimeout,
+ amiType,
+ connectionStrategy,
+ maxTotalUses,
+ Tenancy.backwardsCompatible(useDedicatedTenancy));
}
@Deprecated
- public EC2OndemandSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, List extends NodeProperty>> nodeProperties, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, boolean useDedicatedTenancy, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses)
+ public EC2OndemandSlave(
+ String name,
+ String instanceId,
+ String templateDescription,
+ String remoteFS,
+ int numExecutors,
+ String labelString,
+ Mode mode,
+ String initScript,
+ String tmpDir,
+ List extends NodeProperty>> nodeProperties,
+ String remoteAdmin,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String idleTerminationMinutes,
+ String publicDNS,
+ String privateDNS,
+ List tags,
+ String cloudName,
+ int launchTimeout,
+ AMITypeData amiType,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
+ Tenancy tenancy)
throws FormException, IOException {
- this(name, instanceId, templateDescription, remoteFS, numExecutors, labelString, mode, initScript, tmpDir, nodeProperties, remoteAdmin, jvmopts, stopOnTerminate, idleTerminationMinutes, publicDNS, privateDNS, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, Tenancy.backwardsCompatible(useDedicatedTenancy));
+ this(
+ name,
+ instanceId,
+ templateDescription,
+ remoteFS,
+ numExecutors,
+ labelString,
+ mode,
+ initScript,
+ tmpDir,
+ nodeProperties,
+ remoteAdmin,
+ DEFAULT_JAVA_PATH,
+ jvmopts,
+ stopOnTerminate,
+ idleTerminationMinutes,
+ publicDNS,
+ privateDNS,
+ tags,
+ cloudName,
+ launchTimeout,
+ amiType,
+ connectionStrategy,
+ maxTotalUses,
+ tenancy,
+ DEFAULT_METADATA_ENDPOINT_ENABLED,
+ DEFAULT_METADATA_TOKENS_REQUIRED,
+ DEFAULT_METADATA_HOPS_LIMIT);
}
@Deprecated
- public EC2OndemandSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, List extends NodeProperty>> nodeProperties, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy)
+ public EC2OndemandSlave(
+ String name,
+ String instanceId,
+ String templateDescription,
+ String remoteFS,
+ int numExecutors,
+ String labelString,
+ Mode mode,
+ String initScript,
+ String tmpDir,
+ List extends NodeProperty>> nodeProperties,
+ String remoteAdmin,
+ String javaPath,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String idleTerminationMinutes,
+ String publicDNS,
+ String privateDNS,
+ List tags,
+ String cloudName,
+ int launchTimeout,
+ AMITypeData amiType,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
+ Tenancy tenancy,
+ Boolean metadataEndpointEnabled,
+ Boolean metadataTokensRequired,
+ Integer metadataHopsLimit)
throws FormException, IOException {
- this(name, instanceId, templateDescription, remoteFS, numExecutors, labelString, mode, initScript, tmpDir, nodeProperties, remoteAdmin, DEFAULT_JAVA_PATH, jvmopts, stopOnTerminate, idleTerminationMinutes, publicDNS, privateDNS, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, tenancy, DEFAULT_METADATA_ENDPOINT_ENABLED, DEFAULT_METADATA_TOKENS_REQUIRED, DEFAULT_METADATA_HOPS_LIMIT);
+ this(
+ name,
+ instanceId,
+ templateDescription,
+ remoteFS,
+ numExecutors,
+ labelString,
+ mode,
+ initScript,
+ tmpDir,
+ nodeProperties,
+ remoteAdmin,
+ DEFAULT_JAVA_PATH,
+ jvmopts,
+ stopOnTerminate,
+ idleTerminationMinutes,
+ publicDNS,
+ privateDNS,
+ tags,
+ cloudName,
+ launchTimeout,
+ amiType,
+ connectionStrategy,
+ maxTotalUses,
+ tenancy,
+ metadataEndpointEnabled,
+ metadataTokensRequired,
+ metadataHopsLimit,
+ DEFAULT_METADATA_SUPPORTED);
}
@Deprecated
- public EC2OndemandSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, List extends NodeProperty>> nodeProperties, String remoteAdmin, String javaPath, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy, Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit)
+ public EC2OndemandSlave(
+ String name,
+ String instanceId,
+ String templateDescription,
+ String remoteFS,
+ int numExecutors,
+ String labelString,
+ Mode mode,
+ String initScript,
+ String tmpDir,
+ List extends NodeProperty>> nodeProperties,
+ String remoteAdmin,
+ String javaPath,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String idleTerminationMinutes,
+ String publicDNS,
+ String privateDNS,
+ List tags,
+ String cloudName,
+ int launchTimeout,
+ AMITypeData amiType,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
+ Tenancy tenancy,
+ Boolean metadataEndpointEnabled,
+ Boolean metadataTokensRequired,
+ Integer metadataHopsLimit,
+ Boolean metadataSupported)
throws FormException, IOException {
- this(name, instanceId, templateDescription, remoteFS, numExecutors, labelString, mode, initScript, tmpDir, nodeProperties, remoteAdmin, DEFAULT_JAVA_PATH, jvmopts, stopOnTerminate, idleTerminationMinutes, publicDNS, privateDNS, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, tenancy, metadataEndpointEnabled, metadataTokensRequired, metadataHopsLimit, DEFAULT_METADATA_SUPPORTED);
+ this(
+ name,
+ instanceId,
+ templateDescription,
+ remoteFS,
+ numExecutors,
+ labelString,
+ mode,
+ initScript,
+ tmpDir,
+ nodeProperties,
+ remoteAdmin,
+ DEFAULT_JAVA_PATH,
+ jvmopts,
+ stopOnTerminate,
+ idleTerminationMinutes,
+ publicDNS,
+ privateDNS,
+ tags,
+ cloudName,
+ launchTimeout,
+ amiType,
+ connectionStrategy,
+ maxTotalUses,
+ tenancy,
+ metadataEndpointEnabled,
+ metadataTokensRequired,
+ metadataHopsLimit,
+ metadataSupported,
+ DEFAULT_ENCLAVE_ENABLED);
}
@DataBoundConstructor
- public EC2OndemandSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, List extends NodeProperty>> nodeProperties, String remoteAdmin, String javaPath, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy, Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit, Boolean metadataSupported)
+ public EC2OndemandSlave(
+ String name,
+ String instanceId,
+ String templateDescription,
+ String remoteFS,
+ int numExecutors,
+ String labelString,
+ Mode mode,
+ String initScript,
+ String tmpDir,
+ List extends NodeProperty>> nodeProperties,
+ String remoteAdmin,
+ String javaPath,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String idleTerminationMinutes,
+ String publicDNS,
+ String privateDNS,
+ List tags,
+ String cloudName,
+ int launchTimeout,
+ AMITypeData amiType,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
+ Tenancy tenancy,
+ Boolean metadataEndpointEnabled,
+ Boolean metadataTokensRequired,
+ Integer metadataHopsLimit,
+ Boolean metadataSupported,
+ Boolean enclaveEnabled)
throws FormException, IOException {
- super(name, instanceId, templateDescription, remoteFS, numExecutors, mode, labelString, (amiType.isWindows() ? new EC2WindowsLauncher() : (amiType.isMac() ? new EC2MacLauncher():
- new EC2UnixLauncher())), new EC2RetentionStrategy(idleTerminationMinutes), initScript, tmpDir, nodeProperties, remoteAdmin, javaPath, jvmopts, stopOnTerminate, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, tenancy, metadataEndpointEnabled, metadataTokensRequired, metadataHopsLimit, metadataSupported);
+ super(
+ name,
+ instanceId,
+ templateDescription,
+ remoteFS,
+ numExecutors,
+ mode,
+ labelString,
+ (amiType.isWinRMAgent()
+ ? new EC2WindowsLauncher()
+ : (amiType.isWindows()
+ ? new EC2WindowsSSHLauncher()
+ : (amiType.isMac() ? new EC2MacLauncher() : new EC2UnixLauncher()))),
+ new EC2RetentionStrategy(idleTerminationMinutes),
+ initScript,
+ tmpDir,
+ nodeProperties,
+ remoteAdmin,
+ javaPath,
+ jvmopts,
+ stopOnTerminate,
+ idleTerminationMinutes,
+ tags,
+ cloudName,
+ launchTimeout,
+ amiType,
+ connectionStrategy,
+ maxTotalUses,
+ tenancy,
+ metadataEndpointEnabled,
+ metadataTokensRequired,
+ metadataHopsLimit,
+ metadataSupported,
+ enclaveEnabled);
this.publicDNS = publicDNS;
this.privateDNS = privateDNS;
}
@@ -83,47 +475,75 @@ public EC2OndemandSlave(String name, String instanceId, String templateDescripti
* Constructor for debugging.
*/
public EC2OndemandSlave(String instanceId) throws FormException, IOException {
- this(instanceId, instanceId, "debug", "/tmp/hudson", 1, "debug", Mode.NORMAL, "", "/tmp", Collections.emptyList(), null, null, false, null, "Fake public", "Fake private", null, null, false, 0, new UnixData(null, null, null, null, null), ConnectionStrategy.PRIVATE_IP, -1);
+ this(
+ instanceId,
+ instanceId,
+ "debug",
+ "/tmp/hudson",
+ 1,
+ "debug",
+ Mode.NORMAL,
+ "",
+ "/tmp",
+ Collections.emptyList(),
+ null,
+ null,
+ false,
+ null,
+ "Fake public",
+ "Fake private",
+ null,
+ null,
+ false,
+ 0,
+ new UnixData(null, null, null, null, null),
+ ConnectionStrategy.PRIVATE_IP,
+ -1);
}
/**
* Terminates the instance in EC2.
*/
- public void terminate() {
+ @Override
+ public Future> terminate() {
if (terminateScheduled.getCount() == 0) {
- synchronized(terminateScheduled) {
+ synchronized (terminateScheduled) {
if (terminateScheduled.getCount() == 0) {
- Computer.threadPoolForRemoting.submit(() -> {
+ Future> f = Computer.threadPoolForRemoting.submit(() -> {
try {
if (!isAlive(true)) {
/*
- * The node has been killed externally, so we've nothing to do here
- */
+ * The node has been killed externally, so we've nothing to do here
+ */
LOGGER.info("EC2 instance already terminated: " + getInstanceId());
} else {
- AmazonEC2 ec2 = getCloud().connect();
- TerminateInstancesRequest request = new TerminateInstancesRequest(Collections.singletonList(getInstanceId()));
+ Ec2Client ec2 = getCloud().connect();
+ TerminateInstancesRequest request = TerminateInstancesRequest.builder()
+ .instanceIds(Collections.singletonList(getInstanceId()))
+ .build();
ec2.terminateInstances(request);
LOGGER.info("Terminated EC2 instance (terminated): " + getInstanceId());
}
Jenkins.get().removeNode(this);
LOGGER.info("Removed EC2 instance from jenkins controller: " + getInstanceId());
- } catch (AmazonClientException | IOException e) {
+ } catch (SdkException | IOException e) {
LOGGER.log(Level.WARNING, "Failed to terminate EC2 instance: " + getInstanceId(), e);
} finally {
- synchronized(terminateScheduled) {
+ synchronized (terminateScheduled) {
terminateScheduled.countDown();
}
}
});
terminateScheduled.reset();
+ return f;
}
}
}
+ return CompletableFuture.completedFuture(null);
}
@Override
- public Node reconfigure(final StaplerRequest req, JSONObject form) throws FormException {
+ public Node reconfigure(final StaplerRequest2 req, JSONObject form) throws FormException {
if (form == null) {
return null;
}
@@ -133,8 +553,10 @@ public Node reconfigure(final StaplerRequest req, JSONObject form) throws FormEx
try {
Jenkins.get().removeNode(this);
} catch (IOException ioe) {
- LOGGER.log(Level.WARNING, "Attempt to reconfigure EC2 instance which has been externally terminated: "
- + getInstanceId(), ioe);
+ LOGGER.log(
+ Level.WARNING,
+ "Attempt to reconfigure EC2 instance which has been externally terminated: " + getInstanceId(),
+ ioe);
}
return null;
diff --git a/src/main/java/hudson/plugins/ec2/EC2PrivateKey.java b/src/main/java/hudson/plugins/ec2/EC2PrivateKey.java
index a15728cac..5d52b7ee4 100644
--- a/src/main/java/hudson/plugins/ec2/EC2PrivateKey.java
+++ b/src/main/java/hudson/plugins/ec2/EC2PrivateKey.java
@@ -23,34 +23,39 @@
*/
package hudson.plugins.ec2;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import hudson.plugins.ec2.util.KeyPair;
+import hudson.util.Secret;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.security.UnrecoverableKeyException;
import java.util.Base64;
-
-import com.amazonaws.AmazonClientException;
-import com.amazonaws.services.ec2.AmazonEC2;
-import com.amazonaws.services.ec2.model.KeyPairInfo;
-
-import hudson.util.Secret;
-import jenkins.bouncycastle.api.PEMEncodable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import javax.crypto.Cipher;
-import java.nio.charset.Charset;
-
+import jenkins.bouncycastle.api.PEMEncodable;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
+import software.amazon.awssdk.core.exception.SdkException;
+import software.amazon.awssdk.services.ec2.Ec2Client;
+import software.amazon.awssdk.services.ec2.model.KeyPairInfo;
/**
* RSA private key (the one that you generate with ec2-add-keypair.)
- *
+ *
* Starts with "----- BEGIN RSA PRIVATE KEY------\n".
*
* @author Kohsuke Kawaguchi
*/
public class EC2PrivateKey {
+ private static final Logger LOGGER = Logger.getLogger(EC2PrivateKey.class.getName());
+
private final Secret privateKey;
EC2PrivateKey(String privateKey) {
@@ -100,8 +105,9 @@ public boolean isPrivateKey() throws IOException {
BufferedReader br = new BufferedReader(new StringReader(privateKey.getPlainText()));
String line;
while ((line = br.readLine()) != null) {
- if (line.equals("-----BEGIN RSA PRIVATE KEY-----"))
+ if (line.equals("-----BEGIN RSA PRIVATE KEY-----")) {
return true;
+ }
}
return false;
}
@@ -109,40 +115,63 @@ public boolean isPrivateKey() throws IOException {
/**
* Finds the {@link KeyPairInfo} that corresponds to this key in EC2.
*/
- public com.amazonaws.services.ec2.model.KeyPair find(AmazonEC2 ec2) throws IOException, AmazonClientException {
+ public KeyPair find(Ec2Client ec2) throws IOException, SdkException {
String fp = getFingerprint();
String pfp = getPublicFingerprint();
- for (KeyPairInfo kp : ec2.describeKeyPairs().getKeyPairs()) {
- if (kp.getKeyFingerprint().equalsIgnoreCase(fp)) {
- com.amazonaws.services.ec2.model.KeyPair keyPair = new com.amazonaws.services.ec2.model.KeyPair();
- keyPair.setKeyName(kp.getKeyName());
- keyPair.setKeyFingerprint(fp);
- keyPair.setKeyMaterial(Secret.toString(privateKey));
- return keyPair;
+ for (KeyPairInfo kp : ec2.describeKeyPairs().keyPairs()) {
+ if (kp.keyFingerprint().equalsIgnoreCase(fp)) {
+ return new KeyPair(
+ KeyPairInfo.builder()
+ .keyName(kp.keyName())
+ .keyFingerprint(fp)
+ .build(),
+ Secret.toString(privateKey));
}
- if (kp.getKeyFingerprint().equalsIgnoreCase(pfp)) {
- com.amazonaws.services.ec2.model.KeyPair keyPair = new com.amazonaws.services.ec2.model.KeyPair();
- keyPair.setKeyName(kp.getKeyName());
- keyPair.setKeyFingerprint(pfp);
- keyPair.setKeyMaterial(Secret.toString(privateKey));
- return keyPair;
+ if (kp.keyFingerprint().equalsIgnoreCase(pfp)) {
+ return new KeyPair(
+ KeyPairInfo.builder()
+ .keyName(kp.keyName())
+ .keyFingerprint(pfp)
+ .build(),
+ Secret.toString(privateKey));
}
}
return null;
}
- public String decryptWindowsPassword(String encodedPassword) throws AmazonClientException {
+ public String decryptWindowsPassword(String encodedPassword) throws SdkException {
try {
Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
- cipher.init(Cipher.DECRYPT_MODE, PEMEncodable.decode(privateKey.getPlainText()).toPrivateKey());
+ cipher.init(
+ Cipher.DECRYPT_MODE,
+ PEMEncodable.decode(privateKey.getPlainText()).toPrivateKey());
byte[] cipherText = Base64.getDecoder().decode(StringUtils.deleteWhitespace(encodedPassword));
byte[] plainText = cipher.doFinal(cipherText);
- return new String(plainText, Charset.forName("ASCII"));
+ return new String(plainText, StandardCharsets.US_ASCII);
} catch (Exception e) {
- throw new AmazonClientException("Unable to decode password:\n" + e.toString());
+ throw SdkException.create("Unable to decode password", e);
}
}
+ /* visible for testing */
+ @CheckForNull
+ public static EC2PrivateKey fetchFromDisk() {
+ return fetchFromDisk(System.getProperty(EC2Cloud.SSH_PRIVATE_KEY_FILEPATH, ""));
+ }
+
+ @CheckForNull
+ public static EC2PrivateKey fetchFromDisk(String filepath) {
+ if (StringUtils.isNotEmpty(filepath)) {
+ try {
+ return new EC2PrivateKey(Files.readString(Paths.get(filepath), StandardCharsets.UTF_8));
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "unable to read private key from file " + filepath, e);
+ return null;
+ }
+ }
+ return null;
+ }
+
@Override
public int hashCode() {
return privateKey.hashCode();
@@ -150,8 +179,9 @@ public int hashCode() {
@Override
public boolean equals(Object that) {
- if (that != null && this.getClass() == that.getClass())
+ if (that != null && this.getClass() == that.getClass()) {
return this.privateKey.equals(((EC2PrivateKey) that).privateKey);
+ }
return false;
}
diff --git a/src/main/java/hudson/plugins/ec2/EC2Readiness.java b/src/main/java/hudson/plugins/ec2/EC2Readiness.java
index 3f3f112e3..c65dd7afe 100644
--- a/src/main/java/hudson/plugins/ec2/EC2Readiness.java
+++ b/src/main/java/hudson/plugins/ec2/EC2Readiness.java
@@ -1,8 +1,9 @@
package hudson.plugins.ec2;
-import com.amazonaws.AmazonClientException;
+import software.amazon.awssdk.core.exception.SdkException;
public interface EC2Readiness {
- public boolean isReady();
- public String getEc2ReadinessStatus() throws AmazonClientException;
+ boolean isReady();
+
+ String getEc2ReadinessStatus() throws SdkException;
}
diff --git a/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java b/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java
index 356e7bb47..a03956fbd 100644
--- a/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java
+++ b/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java
@@ -23,27 +23,26 @@
*/
package hudson.plugins.ec2;
-import com.amazonaws.AmazonClientException;
-
-
import hudson.init.InitMilestone;
import hudson.model.Descriptor;
import hudson.model.Executor;
import hudson.model.ExecutorListener;
+import hudson.model.Label;
import hudson.model.Queue;
import hudson.plugins.ec2.util.MinimumInstanceChecker;
-import hudson.model.Label;
import hudson.slaves.RetentionStrategy;
-import jenkins.model.Jenkins;
-
import java.time.Clock;
+import java.time.Instant;
import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
-
+import jenkins.model.Jenkins;
import org.kohsuke.stapler.DataBoundConstructor;
+import software.amazon.awssdk.core.exception.SdkException;
/**
* {@link RetentionStrategy} for EC2.
@@ -53,9 +52,20 @@
public class EC2RetentionStrategy extends RetentionStrategy implements ExecutorListener {
private static final Logger LOGGER = Logger.getLogger(EC2RetentionStrategy.class.getName());
+ /**
+ * Executor for heavy retention work (EC2 API calls, idle timeout, reconnect).
+ * Runs outside the Queue lock so the Queue can complete its periodic routine in under a second.
+ * Package-private and non-final so tests can replace with a direct (same-thread) executor.
+ */
+ static ExecutorService HEAVY_WORK_EXECUTOR = Executors.newCachedThreadPool(r -> {
+ Thread t = new Thread(r, "EC2RetentionStrategy-heavy");
+ t.setDaemon(true);
+ return t;
+ });
+
public static final boolean DISABLED = Boolean.getBoolean(EC2RetentionStrategy.class.getName() + ".disabled");
- private long nextCheckAfter = -1;
+ private volatile long nextCheckAfter = -1;
private transient Clock clock;
/**
@@ -87,7 +97,6 @@ public EC2RetentionStrategy(String idleTerminationMinutes) {
}
}
-
EC2RetentionStrategy(String idleTerminationMinutes, Clock clock, long nextCheckAfter) {
this(idleTerminationMinutes);
this.clock = clock;
@@ -98,44 +107,66 @@ long getNextCheckAfter() {
return this.nextCheckAfter;
}
+ /**
+ * Lightweight path: returns immediately so the Queue lock is not held during EC2 API calls.
+ * Heavy work (getState, getUptime, idle timeout, reconnect) is scheduled to run asynchronously
+ * outside the Queue lock. See docs/EC2_QUEUE_AUDIT.md.
+ */
@Override
public long check(EC2Computer c) {
if (!checkLock.tryLock()) {
return CHECK_INTERVAL_MINUTES;
- } else {
- try {
- long currentTime = this.clock.millis();
-
- if (currentTime > nextCheckAfter) {
- long intervalMins = internalCheck(c);
- nextCheckAfter = currentTime + TimeUnit.MINUTES.toMillis(intervalMins);
- return intervalMins;
- } else {
- return CHECK_INTERVAL_MINUTES;
- }
- } finally {
- checkLock.unlock();
+ }
+ try {
+ long currentTime = this.clock.millis();
+ if (currentTime <= nextCheckAfter) {
+ return CHECK_INTERVAL_MINUTES;
}
+ // Schedule heavy work to run outside the Queue lock; return immediately.
+ nextCheckAfter = currentTime + TimeUnit.MINUTES.toMillis(CHECK_INTERVAL_MINUTES);
+ final EC2Computer computer = c;
+ HEAVY_WORK_EXECUTOR.execute(() -> runHeavyCheck(computer));
+ return CHECK_INTERVAL_MINUTES;
+ } finally {
+ checkLock.unlock();
+ }
+ }
+
+ /**
+ * Runs outside the Queue lock. Performs EC2 API calls and retention logic.
+ * State-changing operations (disconnect, terminate) use Queue.withLock for brief sections.
+ */
+ private void runHeavyCheck(EC2Computer computer) {
+ if (!checkLock.tryLock()) {
+ return;
+ }
+ try {
+ attemptReconnectIfOffline(computer);
+ internalCheck(computer);
+ } finally {
+ checkLock.unlock();
}
}
private long internalCheck(EC2Computer computer) {
/*
- * If we've been told never to terminate, or node is null(deleted), no checks to perform
- */
+ * If we've been told never to terminate, or node is null(deleted), no checks to perform
+ */
if (idleTerminationMinutes == 0 || computer.getNode() == null) {
return CHECK_INTERVAL_MINUTES;
}
/*
- * If we have equal or less number of agents than the template's minimum instance count, don't perform check.
- */
+ * If we have equal or less number of agents than the template's minimum instance count, don't perform check.
+ */
SlaveTemplate slaveTemplate = computer.getSlaveTemplate();
if (slaveTemplate != null) {
long numberOfCurrentInstancesForTemplate = MinimumInstanceChecker.countCurrentNumberOfAgents(slaveTemplate);
- if (numberOfCurrentInstancesForTemplate > 0 && numberOfCurrentInstancesForTemplate <= slaveTemplate.getMinimumNumberOfInstances()) {
- //Check if we're in an active time-range for keeping minimum number of instances
- if (MinimumInstanceChecker.minimumInstancesActive(slaveTemplate.getMinimumNumberOfInstancesTimeRangeConfig())) {
+ if (numberOfCurrentInstancesForTemplate > 0
+ && numberOfCurrentInstancesForTemplate <= slaveTemplate.getMinimumNumberOfInstances()) {
+ // Check if we're in an active time-range for keeping minimum number of instances
+ if (MinimumInstanceChecker.minimumInstancesActive(
+ slaveTemplate.getMinimumNumberOfInstancesTimeRangeConfig())) {
return CHECK_INTERVAL_MINUTES;
}
}
@@ -143,39 +174,50 @@ private long internalCheck(EC2Computer computer) {
if (computer.isIdle() && !DISABLED) {
final long uptime;
- final long launchedAtMs;
+ final Instant launchedAt;
InstanceState state;
try {
- state = computer.getState(); //Get State before Uptime because getState will refresh the cached EC2 info
+ state = computer.getState(); // Get State before Uptime because getState will refresh the cached EC2
+ // info
uptime = computer.getUptime();
- launchedAtMs = computer.getLaunchTime();
- } catch (AmazonClientException | InterruptedException e) {
+ launchedAt = computer.getLaunchTime();
+ } catch (SdkException | InterruptedException e) {
// We'll just retry next time we test for idleness.
LOGGER.fine("Exception while checking host uptime for " + computer.getName()
+ ", will retry next check. Exception: " + e);
return CHECK_INTERVAL_MINUTES;
}
- //Don't bother checking anything else if the instance is already in the desired state:
+ // Don't bother checking anything else if the instance is already in the desired state:
// * Already Terminated
// * We use stop-on-terminate and the instance is currently stopped or stopping
if (InstanceState.TERMINATED.equals(state)
- || (slaveTemplate != null && slaveTemplate.stopOnTerminate) && (InstanceState.STOPPED.equals(state) || InstanceState.STOPPING.equals(state))) {
+ || (slaveTemplate != null && slaveTemplate.stopOnTerminate)
+ && (InstanceState.STOPPED.equals(state) || InstanceState.STOPPING.equals(state))) {
if (computer.isOnline()) {
- LOGGER.info("External Stop of " + computer.getName() + " detected - disconnecting. instance status" + state.toString());
- computer.disconnect(null);
+ LOGGER.info("External Stop of " + computer.getName() + " detected - disconnecting. instance status"
+ + state);
+ try {
+ Queue.withLock(() -> computer.disconnect(null));
+ } catch (Exception e) {
+ LOGGER.log(Level.FINE, "Error disconnecting " + computer.getName(), e);
+ }
}
return CHECK_INTERVAL_MINUTES;
}
- //on rare occasions, AWS may return fault instance which shows running in AWS console but can not be connected.
- //need terminate such fault instance.
+ // on rare occasions, AWS may return fault instance which shows running in AWS console but can not be
+ // connected.
+ // need terminate such fault instance.
// An instance may also fail running user data scripts and
// need to be cleaned up.
- if (computer.isOffline()){
+ if (computer.isOffline()) {
if (computer.isConnecting()) {
- LOGGER.log(Level.FINE, "Computer {0} connecting and still offline, will check if the launch timeout has expired", computer.getInstanceId());
+ LOGGER.log(
+ Level.FINE,
+ "Computer {0} connecting and still offline, will check if the launch timeout has expired",
+ computer.getInstanceId());
EC2AbstractSlave node = computer.getNode();
if (Objects.isNull(node)) {
@@ -185,51 +227,82 @@ private long internalCheck(EC2Computer computer) {
if (launchTimeout > 0 && uptime > launchTimeout) {
// Computer is offline and startup time has expired
LOGGER.info("Startup timeout of " + computer.getName() + " after "
- + uptime +
- " milliseconds (timeout: " + launchTimeout + " milliseconds), instance status: " + state.toString());
- node.launchTimeout();
+ + uptime + " milliseconds (timeout: "
+ + launchTimeout + " milliseconds), instance status: " + state.toString());
+ try {
+ Queue.withLock(node::launchTimeout);
+ } catch (Exception e) {
+ LOGGER.log(Level.FINE, "Error launching timeout for " + computer.getName(), e);
+ }
}
return CHECK_INTERVAL_MINUTES;
} else {
- LOGGER.log(Level.FINE, "Computer {0} offline but not connecting, will check if it should be terminated because of the idle time configured", computer.getInstanceId());
+ LOGGER.log(
+ Level.FINE,
+ "Computer {0} offline but not connecting, will check if it should be terminated because of the idle time configured",
+ computer.getInstanceId());
}
}
- final long idleMilliseconds = this.clock.millis() - Math.max(computer.getIdleStartMilliseconds(), launchedAtMs);
-
+ final long idleMilliseconds =
+ this.clock.millis() - Math.max(computer.getIdleStartMilliseconds(), launchedAt.toEpochMilli());
if (idleTerminationMinutes > 0) {
// TODO: really think about the right strategy here, see
// JENKINS-23792
- if (idleMilliseconds > TimeUnit.MINUTES.toMillis(idleTerminationMinutes) &&
- !itemsInQueueForThisSlave(computer)){
+ boolean queueHasItemsForSlave;
+ try {
+ queueHasItemsForSlave = Queue.withLock(() -> itemsInQueueForThisSlave(computer));
+ } catch (Exception e) {
+ LOGGER.log(Level.FINE, "Error checking queue for " + computer.getName(), e);
+ queueHasItemsForSlave = true; // safe default: do not terminate
+ }
+ if (idleMilliseconds > TimeUnit.MINUTES.toMillis(idleTerminationMinutes) && !queueHasItemsForSlave) {
LOGGER.info("Idle timeout of " + computer.getName() + " after "
- + TimeUnit.MILLISECONDS.toMinutes(idleMilliseconds) +
- " idle minutes, instance status"+state.toString());
+ + TimeUnit.MILLISECONDS.toMinutes(idleMilliseconds) + " idle minutes, instance status"
+ + state.toString());
EC2AbstractSlave slaveNode = computer.getNode();
if (slaveNode != null) {
- slaveNode.idleTimeout();
+ try {
+ Queue.withLock(slaveNode::idleTimeout);
+ } catch (Exception e) {
+ LOGGER.log(Level.FINE, "Error idle timeout for " + computer.getName(), e);
+ }
}
}
} else {
final int oneHourSeconds = (int) TimeUnit.SECONDS.convert(1, TimeUnit.HOURS);
- // AWS bills by the hour for EC2 Instances, so calculate the remaining seconds left in the "billing hour"
- // Note: Since October 2017, this isn't true for Linux instances, but the logic hasn't yet been updated for this
+ // AWS bills by the hour for EC2 Instances, so calculate the remaining seconds left in the "billing
+ // hour"
+ // Note: Since October 2017, this isn't true for Linux instances, but the logic hasn't yet been updated
+ // for this
final int freeSecondsLeft = oneHourSeconds
- (int) (TimeUnit.SECONDS.convert(uptime, TimeUnit.MILLISECONDS) % oneHourSeconds);
// if we have less "free" (aka already paid for) time left than
// our idle time, stop/terminate the instance
// See JENKINS-23821
- if (freeSecondsLeft <= TimeUnit.MINUTES.toSeconds(Math.abs(idleTerminationMinutes)) && !itemsInQueueForThisSlave(computer)) {
+ boolean queueHasItemsForSlaveBilling;
+ try {
+ queueHasItemsForSlaveBilling = Queue.withLock(() -> itemsInQueueForThisSlave(computer));
+ } catch (Exception e) {
+ LOGGER.log(Level.FINE, "Error checking queue for " + computer.getName(), e);
+ queueHasItemsForSlaveBilling = true;
+ }
+ if (freeSecondsLeft <= TimeUnit.MINUTES.toSeconds(Math.abs(idleTerminationMinutes))
+ && !queueHasItemsForSlaveBilling) {
LOGGER.info("Idle timeout of " + computer.getName() + " after "
+ TimeUnit.MILLISECONDS.toMinutes(idleMilliseconds) + " idle minutes, with "
+ TimeUnit.SECONDS.toMinutes(freeSecondsLeft)
+ " minutes remaining in billing period");
EC2AbstractSlave slaveNode = computer.getNode();
if (slaveNode != null) {
- slaveNode.idleTimeout();
+ try {
+ Queue.withLock(slaveNode::idleTimeout);
+ } catch (Exception e) {
+ LOGGER.log(Level.FINE, "Error idle timeout for " + computer.getName(), e);
+ }
}
}
}
@@ -237,6 +310,30 @@ private long internalCheck(EC2Computer computer) {
return CHECK_INTERVAL_MINUTES;
}
+ /**
+ * Try to reconnect the EC2 Instance if it's offline but the status is running.
+ * This could mean unstable ssh connection, so instead of failing the build,
+ * we try to reconnect as soon as the EC2 Instance is running again.
+ */
+ private void attemptReconnectIfOffline(EC2Computer computer) {
+ try {
+ if (computer.getState() == InstanceState.RUNNING && computer.isOffline()) {
+ LOGGER.warning("EC2Computer " + computer.getName() + " is offline");
+ if (!computer.isConnecting()) {
+ // Keep retrying connection to agent until the job times out
+ LOGGER.warning("Attempting to reconnect EC2Computer " + computer.getName());
+ try {
+ computer.connect(false);
+ } catch (Exception e) {
+ LOGGER.log(Level.FINE, "Error reconnecting " + computer.getName(), e);
+ }
+ }
+ }
+ } catch (SdkException | InterruptedException e) {
+ LOGGER.log(Level.FINE, "Error getting EC2 instance state for " + computer.getName(), e);
+ }
+ }
+
/*
* Checks if there are any items in the queue that are waiting for this node explicitly.
* This prevents a node from being taken offline while there are Ivy/Maven Modules waiting to build.
@@ -250,11 +347,12 @@ private boolean itemsInQueueForThisSlave(EC2Computer c) {
* doesn't have a node it will return null. In this case we want to
* return false because there's no slave to prevent a timeout of.
*/
- if (selfNode == null) return false;
+ if (selfNode == null) {
+ return false;
+ }
final Label selfLabel = selfNode.getSelfLabel();
- Queue.Item[] items = Jenkins.getInstance().getQueue().getItems();
- for (int i = 0; i < items.length; i++) {
- Queue.Item item = items[i];
+ Queue.Item[] items = Jenkins.get().getQueue().getItems();
+ for (Queue.Item item : items) {
final Label assignedLabel = item.getAssignedLabel();
if (assignedLabel == selfLabel) {
LOGGER.fine("Preventing idle timeout of " + c.getName()
@@ -266,32 +364,33 @@ private boolean itemsInQueueForThisSlave(EC2Computer c) {
}
/**
- * Called when a new {@link EC2Computer} object is introduced (such as when Hudson started, or when
- * a new agent is added.)
- *
- * When Jenkins has just started, we don't want to spin up all the instances, so we only start if
- * the EC2 instance is already running
+ * Lightweight path: returns immediately. Heavy work (getState, connect) runs asynchronously
+ * so the caller is not blocked by EC2 API calls.
*/
@Override
public void start(EC2Computer c) {
- //Jenkins is in the process of starting up
- if (Jenkins.get().getInitLevel() != InitMilestone.COMPLETED) {
- InstanceState state = null;
+ final EC2Computer computer = c;
+ HEAVY_WORK_EXECUTOR.execute(() -> {
+ if (Jenkins.get().getInitLevel() != InitMilestone.COMPLETED) {
+ InstanceState state = null;
+ try {
+ state = computer.getState();
+ } catch (SdkException | InterruptedException e) {
+ LOGGER.log(Level.FINE, "Error getting EC2 instance state for " + computer.getName(), e);
+ }
+ if (!(InstanceState.PENDING.equals(state) || InstanceState.RUNNING.equals(state))) {
+ LOGGER.info("Ignoring start request for " + computer.getName()
+ + " during Jenkins startup due to EC2 instance state of " + state);
+ return;
+ }
+ }
+ LOGGER.info("Start requested for " + computer.getName());
try {
- state = c.getState();
- } catch (AmazonClientException | InterruptedException e) {
- LOGGER.log(Level.FINE, "Error getting EC2 instance state for " + c.getName(), e);
+ computer.connect(false);
+ } catch (Exception e) {
+ LOGGER.log(Level.FINE, "Error connecting " + computer.getName(), e);
}
- if (!(InstanceState.PENDING.equals(state) || InstanceState.RUNNING.equals(state))) {
- LOGGER.info("Ignoring start request for " + c.getName()
- + " during Jenkins startup due to EC2 instance state of " + state);
- return;
- }
- }
-
-
- LOGGER.info("Start requested for " + c.getName());
- c.connect(false);
+ });
}
// no registration since this retention strategy is used only for EC2 nodes
@@ -310,6 +409,7 @@ protected Object readResolve() {
return this;
}
+ @Override
public void taskAccepted(Executor executor, Queue.Task task) {
EC2Computer computer = (EC2Computer) executor.getOwner();
if (computer != null) {
@@ -317,11 +417,12 @@ public void taskAccepted(Executor executor, Queue.Task task) {
if (slaveNode != null) {
int maxTotalUses = slaveNode.maxTotalUses;
if (maxTotalUses <= -1) {
- LOGGER.fine("maxTotalUses set to unlimited (" + slaveNode.maxTotalUses + ") for agent " + slaveNode.instanceId);
- return;
+ LOGGER.fine("maxTotalUses set to unlimited (" + slaveNode.maxTotalUses + ") for agent "
+ + slaveNode.instanceId);
} else if (maxTotalUses <= 1) {
LOGGER.info("maxTotalUses drained - suspending agent " + slaveNode.instanceId);
computer.setAcceptingTasks(false);
+ MinimumInstanceChecker.scheduleCheck();
} else {
slaveNode.maxTotalUses = slaveNode.maxTotalUses - 1;
LOGGER.info("Agent " + slaveNode.instanceId + " has " + slaveNode.maxTotalUses + " builds left");
@@ -330,10 +431,12 @@ public void taskAccepted(Executor executor, Queue.Task task) {
}
}
+ @Override
public void taskCompleted(Executor executor, Queue.Task task, long durationMS) {
postJobAction(executor);
}
+ @Override
public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) {
postJobAction(executor);
}
@@ -343,13 +446,16 @@ private void postJobAction(Executor executor) {
if (computer != null) {
EC2AbstractSlave slaveNode = computer.getNode();
if (slaveNode != null) {
- // At this point, if agent is in suspended state and has 1 last executer running, it is safe to terminate.
+ // At this point, if agent is in suspended state and has 1 last executer running, it is safe to
+ // terminate.
if (computer.countBusy() <= 1 && !computer.isAcceptingTasks()) {
- LOGGER.info("Agent " + slaveNode.instanceId + " is terminated due to maxTotalUses (" + slaveNode.maxTotalUses + ")");
+ LOGGER.info("Agent " + slaveNode.instanceId + " is terminated due to maxTotalUses ("
+ + slaveNode.maxTotalUses + ")");
slaveNode.terminate();
} else {
if (slaveNode.maxTotalUses == 1) {
- LOGGER.info("Agent " + slaveNode.instanceId + " is still in use by more than one (" + computer.countBusy() + ") executers.");
+ LOGGER.info("Agent " + slaveNode.instanceId + " is still in use by more than one ("
+ + computer.countBusy() + ") executers.");
}
}
}
diff --git a/src/main/java/hudson/plugins/ec2/EC2SlaveMonitor.java b/src/main/java/hudson/plugins/ec2/EC2SlaveMonitor.java
index daa622a39..3c8ba83a3 100644
--- a/src/main/java/hudson/plugins/ec2/EC2SlaveMonitor.java
+++ b/src/main/java/hudson/plugins/ec2/EC2SlaveMonitor.java
@@ -2,21 +2,23 @@
import hudson.Extension;
import hudson.model.AsyncPeriodicWork;
-import hudson.model.TaskListener;
import hudson.model.Node;
-
+import hudson.model.TaskListener;
+import hudson.plugins.ec2.util.MinimumInstanceChecker;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
-
-import hudson.plugins.ec2.util.MinimumInstanceChecker;
import jenkins.model.Jenkins;
-
-import com.amazonaws.AmazonClientException;
-import com.amazonaws.services.ec2.model.AmazonEC2Exception;
-
-import static hudson.plugins.ec2.EC2Cloud.EC2_REQUEST_EXPIRED_ERROR_CODE;
+import org.apache.commons.lang.StringUtils;
+import software.amazon.awssdk.core.exception.SdkException;
+import software.amazon.awssdk.services.ec2.model.Ec2Exception;
+import software.amazon.awssdk.services.ec2.model.Instance;
+import software.amazon.awssdk.services.ec2.model.InstanceStateName;
/**
* @author Bruno Meneguello
@@ -41,25 +43,82 @@ public long getRecurrencePeriod() {
@Override
protected void execute(TaskListener listener) throws IOException, InterruptedException {
removeDeadNodes();
- MinimumInstanceChecker.checkForMinimumInstances();
+ MinimumInstanceChecker.scheduleCheck();
}
private void removeDeadNodes() {
+ Map> byCloud = new HashMap<>();
for (Node node : Jenkins.get().getNodes()) {
- if (node instanceof EC2AbstractSlave) {
- final EC2AbstractSlave ec2Slave = (EC2AbstractSlave) node;
- try {
- if (!ec2Slave.isAlive(true)) {
- LOGGER.info("EC2 instance is dead: " + ec2Slave.getInstanceId());
- ec2Slave.terminate();
+ if (node instanceof EC2AbstractSlave ec2Slave) {
+ String instanceId = ec2Slave.getInstanceId();
+ if (StringUtils.isEmpty(instanceId)) {
+ continue;
+ }
+ EC2Cloud cloud = ec2Slave.getCloud();
+ if (cloud == null) {
+ continue;
+ }
+ byCloud.computeIfAbsent(cloud, k -> new ArrayList<>()).add(ec2Slave);
+ }
+ }
+
+ for (Map.Entry> entry : byCloud.entrySet()) {
+ EC2Cloud cloud = entry.getKey();
+ List slaves = entry.getValue();
+ List instanceIds = new ArrayList<>(slaves.size());
+ for (EC2AbstractSlave s : slaves) {
+ instanceIds.add(s.getInstanceId());
+ }
+
+ try {
+ Map instances = CloudHelper.getInstancesBatch(instanceIds, cloud);
+ for (EC2AbstractSlave ec2Slave : slaves) {
+ try {
+ Instance inst = instances.get(ec2Slave.getInstanceId());
+ if (inst == null) {
+ LOGGER.info("EC2 instance not found (likely terminated): " + ec2Slave.getInstanceId());
+ ec2Slave.terminate();
+ } else if (InstanceStateName.TERMINATED.equals(
+ inst.state().name())) {
+ LOGGER.info("EC2 instance is dead: " + ec2Slave.getInstanceId());
+ ec2Slave.terminate();
+ } else {
+ ec2Slave.updateFromFetchedInstance(inst);
+ }
+ } catch (SdkException e) {
+ if (e instanceof Ec2Exception
+ && EC2Cloud.EC2_REQUEST_EXPIRED_ERROR_CODE.equals(
+ ((Ec2Exception) e).awsErrorDetails().errorCode())) {
+ LOGGER.info("EC2 request expired, skipping consideration of " + ec2Slave.getInstanceId()
+ + " due to unknown state.");
+ } else {
+ LOGGER.info("EC2 instance is dead and failed to terminate: " + ec2Slave.getInstanceId());
+ removeNode(ec2Slave);
+ }
}
- } catch (AmazonClientException e) {
- if (e instanceof AmazonEC2Exception &&
- EC2_REQUEST_EXPIRED_ERROR_CODE.equals(((AmazonEC2Exception) e).getErrorCode())) {
- LOGGER.info("EC2 request expired, skipping consideration of " + ec2Slave.getInstanceId() + " due to unknown state.");
- } else {
- LOGGER.info("EC2 instance is dead and failed to terminate: " + ec2Slave.getInstanceId());
- removeNode(ec2Slave);
+ }
+ } catch (SdkException e) {
+ LOGGER.log(
+ Level.WARNING,
+ "Batch describeInstances failed for cloud " + cloud.getName()
+ + ", falling back to per-node check",
+ e);
+ for (EC2AbstractSlave ec2Slave : slaves) {
+ try {
+ if (!ec2Slave.isAlive(true)) {
+ LOGGER.info("EC2 instance is dead: " + ec2Slave.getInstanceId());
+ ec2Slave.terminate();
+ }
+ } catch (SdkException ex) {
+ if (ex instanceof Ec2Exception
+ && EC2Cloud.EC2_REQUEST_EXPIRED_ERROR_CODE.equals(
+ ((Ec2Exception) ex).awsErrorDetails().errorCode())) {
+ LOGGER.info("EC2 request expired, skipping consideration of " + ec2Slave.getInstanceId()
+ + " due to unknown state.");
+ } else {
+ LOGGER.info("EC2 instance is dead and failed to terminate: " + ec2Slave.getInstanceId());
+ removeNode(ec2Slave);
+ }
}
}
}
@@ -73,5 +132,4 @@ private void removeNode(EC2AbstractSlave ec2Slave) {
LOGGER.log(Level.WARNING, "Failed to remove node: " + ec2Slave.getInstanceId());
}
}
-
}
diff --git a/src/main/java/hudson/plugins/ec2/EC2SpotSlave.java b/src/main/java/hudson/plugins/ec2/EC2SpotSlave.java
index 04c4b861b..7256dfe57 100644
--- a/src/main/java/hudson/plugins/ec2/EC2SpotSlave.java
+++ b/src/main/java/hudson/plugins/ec2/EC2SpotSlave.java
@@ -1,33 +1,31 @@
package hudson.plugins.ec2;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import hudson.Extension;
+import hudson.model.Computer;
+import hudson.model.Descriptor.FormException;
+import hudson.plugins.ec2.ssh.EC2UnixLauncher;
+import hudson.plugins.ec2.ssh.EC2WindowsSSHLauncher;
+import hudson.plugins.ec2.win.EC2WindowsLauncher;
+import hudson.slaves.NodeProperty;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
-
import jenkins.model.Jenkins;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
-
-import com.amazonaws.AmazonClientException;
-import com.amazonaws.services.ec2.AmazonEC2;
-import com.amazonaws.services.ec2.model.CancelSpotInstanceRequestsRequest;
-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.Computer;
-import hudson.model.Descriptor.FormException;
-import hudson.plugins.ec2.ssh.EC2UnixLauncher;
-import hudson.plugins.ec2.win.EC2WindowsLauncher;
-import hudson.slaves.NodeProperty;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
+import software.amazon.awssdk.core.exception.SdkException;
+import software.amazon.awssdk.services.ec2.Ec2Client;
+import software.amazon.awssdk.services.ec2.model.CancelSpotInstanceRequestsRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeSpotInstanceRequestsRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeSpotInstanceRequestsResponse;
+import software.amazon.awssdk.services.ec2.model.SpotInstanceRequest;
+import software.amazon.awssdk.services.ec2.model.SpotInstanceState;
+import software.amazon.awssdk.services.ec2.model.TerminateInstancesRequest;
public class EC2SpotSlave extends EC2AbstractSlave implements EC2Readiness {
private static final Logger LOGGER = Logger.getLogger(EC2SpotSlave.class.getName());
@@ -35,23 +33,143 @@ public class EC2SpotSlave extends EC2AbstractSlave implements EC2Readiness {
private final String spotInstanceRequestId;
@Deprecated
- public EC2SpotSlave(String name, String spotInstanceRequestId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String initScript, String tmpDir, String labelString, String remoteAdmin, String jvmopts, String idleTerminationMinutes, List tags, String cloudName, int launchTimeout, AMITypeData amiType)
+ public EC2SpotSlave(
+ String name,
+ String spotInstanceRequestId,
+ String templateDescription,
+ String remoteFS,
+ int numExecutors,
+ Mode mode,
+ String initScript,
+ String tmpDir,
+ String labelString,
+ String remoteAdmin,
+ String jvmopts,
+ String idleTerminationMinutes,
+ List tags,
+ String cloudName,
+ int launchTimeout,
+ AMITypeData amiType)
throws FormException, IOException {
- this(name, spotInstanceRequestId, templateDescription, remoteFS, numExecutors, mode, initScript, tmpDir, labelString, remoteAdmin, jvmopts, idleTerminationMinutes, tags, cloudName, false, launchTimeout, amiType);
+ this(
+ name,
+ spotInstanceRequestId,
+ templateDescription,
+ remoteFS,
+ numExecutors,
+ mode,
+ initScript,
+ tmpDir,
+ labelString,
+ remoteAdmin,
+ jvmopts,
+ idleTerminationMinutes,
+ tags,
+ cloudName,
+ false,
+ launchTimeout,
+ amiType);
}
@Deprecated
- public EC2SpotSlave(String name, String spotInstanceRequestId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String initScript, String tmpDir, String labelString, String remoteAdmin, String jvmopts, String idleTerminationMinutes, List tags, String cloudName, boolean usePrivateDnsName, int launchTimeout, AMITypeData amiType)
+ public EC2SpotSlave(
+ String name,
+ String spotInstanceRequestId,
+ String templateDescription,
+ String remoteFS,
+ int numExecutors,
+ Mode mode,
+ String initScript,
+ String tmpDir,
+ String labelString,
+ String remoteAdmin,
+ String jvmopts,
+ String idleTerminationMinutes,
+ List tags,
+ String cloudName,
+ boolean usePrivateDnsName,
+ int launchTimeout,
+ AMITypeData amiType)
throws FormException, IOException {
- this(templateDescription + " (" + name + ")", spotInstanceRequestId, templateDescription, remoteFS, numExecutors, mode, initScript, tmpDir, labelString, Collections.emptyList(), remoteAdmin, DEFAULT_JAVA_PATH, jvmopts, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, ConnectionStrategy.backwardsCompatible(usePrivateDnsName, false, false), -1);
+ this(
+ templateDescription + " (" + name + ")",
+ spotInstanceRequestId,
+ templateDescription,
+ remoteFS,
+ numExecutors,
+ mode,
+ initScript,
+ tmpDir,
+ labelString,
+ Collections.emptyList(),
+ remoteAdmin,
+ DEFAULT_JAVA_PATH,
+ jvmopts,
+ idleTerminationMinutes,
+ tags,
+ cloudName,
+ launchTimeout,
+ amiType,
+ ConnectionStrategy.backwardsCompatible(usePrivateDnsName, false, false),
+ -1);
}
@DataBoundConstructor
- public EC2SpotSlave(String name, String spotInstanceRequestId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String initScript, String tmpDir, String labelString, List extends NodeProperty>> nodeProperties, String remoteAdmin, String javaPath, String jvmopts, String idleTerminationMinutes, List tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses)
+ public EC2SpotSlave(
+ String name,
+ String spotInstanceRequestId,
+ String templateDescription,
+ String remoteFS,
+ int numExecutors,
+ Mode mode,
+ String initScript,
+ String tmpDir,
+ String labelString,
+ List extends NodeProperty>> nodeProperties,
+ String remoteAdmin,
+ String javaPath,
+ String jvmopts,
+ String idleTerminationMinutes,
+ List tags,
+ String cloudName,
+ int launchTimeout,
+ AMITypeData amiType,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses)
throws FormException, IOException {
- super(name, "", templateDescription, remoteFS, numExecutors, mode, labelString, amiType.isWindows() ? new EC2WindowsLauncher() :
- new EC2UnixLauncher(), new EC2RetentionStrategy(idleTerminationMinutes), initScript, tmpDir, nodeProperties, remoteAdmin, javaPath, jvmopts, false, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses,null, DEFAULT_METADATA_ENDPOINT_ENABLED, DEFAULT_METADATA_TOKENS_REQUIRED, DEFAULT_METADATA_HOPS_LIMIT, DEFAULT_METADATA_SUPPORTED);
+ super(
+ name,
+ "",
+ templateDescription,
+ remoteFS,
+ numExecutors,
+ mode,
+ labelString,
+ (amiType.isWinRMAgent()
+ ? new EC2WindowsLauncher()
+ : (amiType.isWindows() ? new EC2WindowsSSHLauncher() : new EC2UnixLauncher())),
+ new EC2RetentionStrategy(idleTerminationMinutes),
+ initScript,
+ tmpDir,
+ nodeProperties,
+ remoteAdmin,
+ javaPath,
+ jvmopts,
+ false,
+ idleTerminationMinutes,
+ tags,
+ cloudName,
+ launchTimeout,
+ amiType,
+ connectionStrategy,
+ maxTotalUses,
+ null,
+ DEFAULT_METADATA_ENDPOINT_ENABLED,
+ DEFAULT_METADATA_TOKENS_REQUIRED,
+ DEFAULT_METADATA_HOPS_LIMIT,
+ DEFAULT_METADATA_SUPPORTED,
+ DEFAULT_ENCLAVE_ENABLED);
this.name = name;
this.spotInstanceRequestId = spotInstanceRequestId;
@@ -66,46 +184,54 @@ protected boolean isAlive(boolean force) {
* Cancel the spot request for the instance. Terminate the instance if it is up. Remove the agent from Jenkins.
*/
@Override
- public void terminate() {
+ public Future> terminate() {
if (terminateScheduled.getCount() == 0) {
- synchronized(terminateScheduled) {
+ synchronized (terminateScheduled) {
if (terminateScheduled.getCount() == 0) {
- Computer.threadPoolForRemoting.submit(() -> {
+ Future> f = Computer.threadPoolForRemoting.submit(() -> {
try {
// Cancel the spot request
- AmazonEC2 ec2 = getCloud().connect();
+ Ec2Client ec2 = getCloud().connect();
String instanceId = getInstanceId();
List requestIds = Collections.singletonList(spotInstanceRequestId);
- CancelSpotInstanceRequestsRequest cancelRequest = new CancelSpotInstanceRequestsRequest(requestIds);
+ CancelSpotInstanceRequestsRequest cancelRequest =
+ CancelSpotInstanceRequestsRequest.builder()
+ .spotInstanceRequestIds(requestIds)
+ .build();
try {
ec2.cancelSpotInstanceRequests(cancelRequest);
LOGGER.info("Cancelled Spot request: " + spotInstanceRequestId);
- } catch (AmazonClientException e) {
+ } catch (SdkException e) {
// Spot request is no longer valid
LOGGER.log(Level.WARNING, "Failed to cancel Spot request: " + spotInstanceRequestId, e);
}
// Terminate the agent if it is running
- if (instanceId != null && !instanceId.equals("")) {
+ if (instanceId != null && !instanceId.isEmpty()) {
if (!super.isAlive(true)) {
/*
- * The node has been killed externally, so we've nothing to do here
- */
+ * The node has been killed externally, so we've nothing to do here
+ */
LOGGER.info("EC2 instance already terminated: " + instanceId);
} else {
- TerminateInstancesRequest request = new TerminateInstancesRequest(Collections.singletonList(instanceId));
+ TerminateInstancesRequest request = TerminateInstancesRequest.builder()
+ .instanceIds(Collections.singletonList(instanceId))
+ .build();
try {
ec2.terminateInstances(request);
LOGGER.info("Terminated EC2 instance (terminated): " + instanceId);
- } catch (AmazonClientException e) {
+ } catch (SdkException e) {
// Spot request is no longer valid
- LOGGER.log(Level.WARNING, "Failed to terminate the Spot instance: " + instanceId, e);
+ LOGGER.log(
+ Level.WARNING,
+ "Failed to terminate the Spot instance: " + instanceId,
+ e);
}
}
}
} catch (Exception e) {
- LOGGER.log(Level.WARNING,"Failed to remove agent: ", e);
+ LOGGER.log(Level.WARNING, "Failed to remove agent: ", e);
} finally {
// Remove the instance even if deletion failed, otherwise it will hang around forever in
// the nodes page. One way for this to occur is that an instance was terminated
@@ -116,15 +242,17 @@ public void terminate() {
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to remove agent: " + name, e);
}
- synchronized(terminateScheduled) {
+ synchronized (terminateScheduled) {
terminateScheduled.countDown();
}
}
});
terminateScheduled.reset();
+ return f;
}
}
}
+ return CompletableFuture.completedFuture(null);
}
/**
@@ -134,21 +262,25 @@ public void terminate() {
*/
@CheckForNull
SpotInstanceRequest getSpotRequest() {
- AmazonEC2 ec2 = getCloud().connect();
+ Ec2Client ec2 = getCloud().connect();
if (this.spotInstanceRequestId == null) {
return null;
}
- DescribeSpotInstanceRequestsRequest dsirRequest = new DescribeSpotInstanceRequestsRequest().withSpotInstanceRequestIds(this.spotInstanceRequestId);
+ DescribeSpotInstanceRequestsRequest dsirRequest = DescribeSpotInstanceRequestsRequest.builder()
+ .spotInstanceRequestIds(this.spotInstanceRequestId)
+ .build();
try {
- DescribeSpotInstanceRequestsResult dsirResult = ec2.describeSpotInstanceRequests(dsirRequest);
- List siRequests = dsirResult.getSpotInstanceRequests();
+ DescribeSpotInstanceRequestsResponse dsirResult = ec2.describeSpotInstanceRequests(dsirRequest);
+ List siRequests = dsirResult.spotInstanceRequests();
return siRequests.get(0);
- } catch (AmazonClientException e) {
+ } catch (SdkException e) {
// Spot request is no longer valid
- LOGGER.log(Level.WARNING, "Failed to fetch spot instance request for requestId: " + this.spotInstanceRequestId);
+ LOGGER.log(
+ Level.WARNING,
+ "Failed to fetch spot instance request for requestId: " + this.spotInstanceRequestId);
}
return null;
@@ -160,10 +292,11 @@ public boolean isSpotRequestDead() {
return true;
}
- SpotInstanceState requestState = SpotInstanceState.fromValue(spotRequest.getState());
- return requestState == SpotInstanceState.Cancelled
- || requestState == SpotInstanceState.Closed
- || requestState == SpotInstanceState.Failed;
+ SpotInstanceState requestState =
+ SpotInstanceState.fromValue(spotRequest.state().toString());
+ return requestState == SpotInstanceState.CANCELLED
+ || requestState == SpotInstanceState.CLOSED
+ || requestState == SpotInstanceState.FAILED;
}
/**
@@ -177,8 +310,9 @@ public String getSpotInstanceRequestId() {
public String getInstanceId() {
if (StringUtils.isEmpty(instanceId)) {
SpotInstanceRequest sr = getSpotRequest();
- if (sr != null)
- instanceId = sr.getInstanceId();
+ if (sr != null) {
+ instanceId = sr.instanceId();
+ }
}
return instanceId;
}
@@ -203,8 +337,9 @@ public String getDisplayName() {
public String getEc2Type() {
SpotInstanceRequest spotRequest = getSpotRequest();
if (spotRequest != null) {
- String spotMaxBidPrice = spotRequest.getSpotPrice();
- return Messages.EC2SpotSlave_Spot1() + spotMaxBidPrice.substring(0, spotMaxBidPrice.length() - 3)
+ String spotMaxBidPrice = spotRequest.spotPrice();
+ return Messages.EC2SpotSlave_Spot1()
+ + spotMaxBidPrice.substring(0, spotMaxBidPrice.length() - 3)
+ Messages.EC2SpotSlave_Spot2();
}
return null;
@@ -219,8 +354,8 @@ public boolean isReady() {
public String getEc2ReadinessStatus() {
SpotInstanceRequest sr = getSpotRequest();
if (sr != null) {
- return sr.getStatus().getMessage();
+ return sr.status().message();
}
- throw new AmazonClientException("No spot instance request");
+ throw SdkException.builder().message("No spot instance request").build();
}
}
diff --git a/src/main/java/hudson/plugins/ec2/EC2Step.java b/src/main/java/hudson/plugins/ec2/EC2Step.java
index bd8967760..25099ed85 100644
--- a/src/main/java/hudson/plugins/ec2/EC2Step.java
+++ b/src/main/java/hudson/plugins/ec2/EC2Step.java
@@ -23,23 +23,30 @@
*/
package hudson.plugins.ec2;
-import com.amazonaws.services.ec2.model.Instance;
import hudson.Extension;
import hudson.Util;
import hudson.model.TaskListener;
import hudson.slaves.Cloud;
import hudson.util.ListBoxModel;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
import jenkins.model.Jenkins;
-import org.jenkinsci.plugins.workflow.steps.*;
+import org.jenkinsci.plugins.workflow.steps.Step;
+import org.jenkinsci.plugins.workflow.steps.StepContext;
+import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
+import org.jenkinsci.plugins.workflow.steps.StepExecution;
+import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.verb.POST;
-
-import java.util.*;
+import software.amazon.awssdk.services.ec2.model.Instance;
/**
* Returns the instance provisioned.
- *
+ *
* Used like:
*
*
@@ -60,6 +67,7 @@ public EC2Step(String cloud, String template) {
this.cloud = cloud;
this.template = template;
}
+
public String getCloud() {
return cloud;
}
@@ -70,7 +78,7 @@ public String getTemplate() {
@Override
public StepExecution start(StepContext context) throws Exception {
- return new EC2Step.Execution( this, context);
+ return new EC2Step.Execution(this, context);
}
@Extension
@@ -91,9 +99,7 @@ public ListBoxModel doFillCloudItems() {
Jenkins.get().checkPermission(Jenkins.SYSTEM_READ);
ListBoxModel r = new ListBoxModel();
r.add("", "");
- Jenkins.get().clouds
- .getAll(AmazonEC2Cloud.class)
- .forEach(c -> r.add(c.getDisplayName(), c.getDisplayName()));
+ Jenkins.get().clouds.getAll(EC2Cloud.class).forEach(c -> r.add(c.getDisplayName(), c.getDisplayName()));
return r;
}
@@ -102,30 +108,29 @@ public ListBoxModel doFillTemplateItems(@QueryParameter String cloudName) {
Jenkins.get().checkPermission(Jenkins.SYSTEM_READ);
ListBoxModel r = new ListBoxModel();
Cloud cloud = Jenkins.get().getCloud(Util.fixEmpty(cloudName));
- if (cloud instanceof AmazonEC2Cloud) {
- AmazonEC2Cloud ec2Cloud = (AmazonEC2Cloud) cloud;
+ if (cloud instanceof EC2Cloud ec2Cloud) {
for (SlaveTemplate template : ec2Cloud.getTemplates()) {
for (String labelList : template.labels.split(" ")) {
- r.add(labelList + " (AMI: " + template.getAmi() + ", REGION: " + ec2Cloud.getRegion() + ", TYPE: " + template.type.name() + ")", labelList);
+ r.add(
+ labelList + " (AMI: " + template.getAmi() + ", REGION: " + ec2Cloud.getRegion()
+ + ", TYPE: " + template.type + ")",
+ labelList);
}
}
}
return r;
}
-
@Override
public Set extends Class>> getRequiredContext() {
return Collections.singleton(TaskListener.class);
}
-
}
public static class Execution extends SynchronousNonBlockingStepExecution {
private final String cloud;
private final String template;
-
Execution(EC2Step step, StepContext context) {
super(context);
this.cloud = step.cloud;
@@ -135,9 +140,9 @@ public static class Execution extends SynchronousNonBlockingStepExecution opt = EnumSet.noneOf(SlaveTemplate.ProvisionOptions.class);
@@ -145,16 +150,19 @@ protected Instance run() throws Exception {
List instances = t.provision(1, opt);
if (instances == null) {
- throw new IllegalArgumentException("Error in AWS Cloud. Please review AWS template defined in Jenkins configuration.");
+ throw new IllegalArgumentException(
+ "Error in AWS Cloud. Please review AWS template defined in Jenkins configuration.");
}
EC2AbstractSlave slave = instances.get(0);
- return CloudHelper.getInstanceWithRetry(slave.getInstanceId(), (AmazonEC2Cloud) cl);
+ return CloudHelper.getInstanceWithRetry(slave.getInstanceId(), (EC2Cloud) cl);
} else {
- throw new IllegalArgumentException("Error in AWS Cloud. Please review AWS template defined in Jenkins configuration.");
+ throw new IllegalArgumentException(
+ "Error in AWS Cloud. Please review AWS template defined in Jenkins configuration.");
}
} else {
- throw new IllegalArgumentException("Error in AWS Cloud. Please review EC2 settings in Jenkins configuration.");
+ throw new IllegalArgumentException(
+ "Error in AWS Cloud. Please review EC2 settings in Jenkins configuration.");
}
}
@@ -172,5 +180,4 @@ public Cloud getByDisplayName(Jenkins.CloudList clouds, String name) {
return c;
}
}
-
}
diff --git a/src/main/java/hudson/plugins/ec2/EC2Tag.java b/src/main/java/hudson/plugins/ec2/EC2Tag.java
index ccdaef430..302b0d6b7 100644
--- a/src/main/java/hudson/plugins/ec2/EC2Tag.java
+++ b/src/main/java/hudson/plugins/ec2/EC2Tag.java
@@ -23,17 +23,14 @@
*/
package hudson.plugins.ec2;
-import hudson.model.Descriptor;
-import hudson.model.AbstractDescribableImpl;
import hudson.Extension;
-import org.kohsuke.stapler.DataBoundConstructor;
-
-import java.util.List;
+import hudson.model.AbstractDescribableImpl;
+import hudson.model.Descriptor;
import java.util.LinkedList;
+import java.util.List;
import java.util.Objects;
-
-
-import com.amazonaws.services.ec2.model.Tag;
+import org.kohsuke.stapler.DataBoundConstructor;
+import software.amazon.awssdk.services.ec2.model.Tag;
public class EC2Tag extends AbstractDescribableImpl {
private final String name;
@@ -43,8 +40,11 @@ public class EC2Tag extends AbstractDescribableImpl {
* Tag name for the specific jenkins agent type tag, used to identify the EC2 instances provisioned by this plugin.
*/
public static final String TAG_NAME_JENKINS_SLAVE_TYPE = "jenkins_slave_type";
+
public static final String TAG_NAME_JENKINS_SERVER_URL = "jenkins_server_url";
+ public static final String TAG_NAME_JENKINS_CLOUD_NAME = "jenkins_cloud_name";
+
@DataBoundConstructor
public EC2Tag(String name, String value) {
this.name = name;
@@ -53,8 +53,8 @@ public EC2Tag(String name, String value) {
/* Constructor from Amazon Tag */
public EC2Tag(Tag t) {
- name = t.getKey();
- value = t.getValue();
+ name = t.key();
+ value = t.value();
}
public String getName() {
@@ -72,16 +72,20 @@ public String toString() {
@Override
public boolean equals(Object o) {
- if (o == null)
+ if (o == null) {
return false;
- if (this.getClass() != o.getClass())
+ }
+ if (this.getClass() != o.getClass()) {
return false;
+ }
EC2Tag other = (EC2Tag) o;
- if ((name == null && other.name != null) || (name != null && !name.equals(other.name)))
+ if ((name == null && other.name != null) || (name != null && !name.equals(other.name))) {
return false;
- if ((value == null && other.value != null) || (value != null && !value.equals(other.value)))
+ }
+ if ((value == null && other.value != null) || (value != null && !value.equals(other.value))) {
return false;
+ }
return true;
}
@@ -105,7 +109,7 @@ public static List fromAmazonTags(List amazonTags) {
return null;
}
- LinkedList result = new LinkedList();
+ LinkedList result = new LinkedList<>();
for (Tag t : amazonTags) {
result.add(new EC2Tag(t));
}
diff --git a/src/main/java/hudson/plugins/ec2/EbsEncryptRootVolume.java b/src/main/java/hudson/plugins/ec2/EbsEncryptRootVolume.java
index 47ca5d15a..79d169b49 100644
--- a/src/main/java/hudson/plugins/ec2/EbsEncryptRootVolume.java
+++ b/src/main/java/hudson/plugins/ec2/EbsEncryptRootVolume.java
@@ -23,5 +23,4 @@ public String getDisplayText() {
public Boolean getValue() {
return value;
}
-
}
diff --git a/src/main/java/hudson/plugins/ec2/Eucalyptus.java b/src/main/java/hudson/plugins/ec2/Eucalyptus.java
deleted file mode 100644
index 68c6fa612..000000000
--- a/src/main/java/hudson/plugins/ec2/Eucalyptus.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package hudson.plugins.ec2;
-
-import hudson.Extension;
-import hudson.model.ItemGroup;
-import hudson.util.FormValidation;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.List;
-
-import javax.servlet.ServletException;
-
-import org.kohsuke.stapler.AncestorInPath;
-import org.kohsuke.stapler.DataBoundConstructor;
-import org.kohsuke.stapler.QueryParameter;
-import org.kohsuke.stapler.interceptor.RequirePOST;
-
-/**
- * Eucalyptus.
- *
- * @author Kohsuke Kawaguchi
- */
-public class Eucalyptus extends EC2Cloud {
- private final URL ec2endpoint;
- private final URL s3endpoint;
-
- @DataBoundConstructor
- public Eucalyptus(String name, URL ec2EndpointUrl, URL s3EndpointUrl, boolean useInstanceProfileForCredentials, String credentialsId, String privateKey, String sshKeysCredentialsId, String instanceCapStr, List templates, String roleArn, String roleSessionName) {
- super(name, useInstanceProfileForCredentials, credentialsId, privateKey, sshKeysCredentialsId, instanceCapStr, templates, roleArn, roleSessionName);
- this.ec2endpoint = ec2EndpointUrl;
- this.s3endpoint = s3EndpointUrl;
- }
-
- @Deprecated
- public Eucalyptus(URL ec2EndpointUrl, URL s3EndpointUrl, boolean useInstanceProfileForCredentials, String credentialsId, String privateKey, String sshKeysCredentialsId, String instanceCapStr, List templates, String roleArn, String roleSessionName)
- throws IOException {
- this("eucalyptus", ec2EndpointUrl, s3EndpointUrl, useInstanceProfileForCredentials, credentialsId, privateKey, sshKeysCredentialsId, instanceCapStr, templates, roleArn, roleSessionName);
- }
-
- @Deprecated
- public Eucalyptus(URL ec2EndpointUrl, URL s3EndpointUrl, boolean useInstanceProfileForCredentials, String credentialsId, String privateKey, String instanceCapStr, List templates, String roleArn, String roleSessionName)
- throws IOException {
- this("eucalyptus", ec2EndpointUrl, s3EndpointUrl, useInstanceProfileForCredentials, credentialsId, privateKey, null, instanceCapStr, templates, roleArn, roleSessionName);
- }
-
- @Override
- public URL getEc2EndpointUrl() throws IOException {
- return this.ec2endpoint;
- }
-
- @Override
- public URL getS3EndpointUrl() throws IOException {
- return this.s3endpoint;
- }
-
- @Extension
- public static class DescriptorImpl extends EC2Cloud.DescriptorImpl {
- @Override
- public String getDisplayName() {
- return "Eucalyptus";
- }
-
- @Override
- @RequirePOST
- public FormValidation doTestConnection(@AncestorInPath ItemGroup context, @QueryParameter URL ec2endpoint, @QueryParameter boolean useInstanceProfileForCredentials, @QueryParameter String credentialsId, @QueryParameter String sshKeysCredentialsId, @QueryParameter String roleArn, @QueryParameter String roleSessionName, @QueryParameter String region)
- throws IOException, ServletException {
- return super.doTestConnection(context, ec2endpoint, useInstanceProfileForCredentials, credentialsId, sshKeysCredentialsId, roleArn, roleSessionName, region);
- }
- }
-}
diff --git a/src/main/java/hudson/plugins/ec2/HostKeyVerificationStrategyEnum.java b/src/main/java/hudson/plugins/ec2/HostKeyVerificationStrategyEnum.java
index 69b8af71a..df4df25dd 100644
--- a/src/main/java/hudson/plugins/ec2/HostKeyVerificationStrategyEnum.java
+++ b/src/main/java/hudson/plugins/ec2/HostKeyVerificationStrategyEnum.java
@@ -35,12 +35,15 @@ public enum HostKeyVerificationStrategyEnum {
CHECK_NEW_SOFT("check-new-soft", "accept-new", new CheckNewSoftStrategy()),
ACCEPT_NEW("accept-new", "accept-new", new AcceptNewStrategy()),
OFF("off", "no", new NonVerifyingKeyVerificationStrategy());
-
+
private final String displayText;
private final SshHostKeyVerificationStrategy strategy;
private final String sshCommandEquivalentFlag;
-
- HostKeyVerificationStrategyEnum(@NonNull String displayText, @NonNull String sshCommandEquivalentFlag, @NonNull SshHostKeyVerificationStrategy strategy) {
+
+ HostKeyVerificationStrategyEnum(
+ @NonNull String displayText,
+ @NonNull String sshCommandEquivalentFlag,
+ @NonNull SshHostKeyVerificationStrategy strategy) {
this.displayText = displayText;
this.sshCommandEquivalentFlag = sshCommandEquivalentFlag;
this.strategy = strategy;
@@ -50,7 +53,7 @@ public enum HostKeyVerificationStrategyEnum {
public SshHostKeyVerificationStrategy getStrategy() {
return strategy;
}
-
+
public boolean equalsDisplayText(String other) {
return this.displayText.equals(other);
}
diff --git a/src/main/java/hudson/plugins/ec2/InstanceState.java b/src/main/java/hudson/plugins/ec2/InstanceState.java
index 4384bdca2..afaf150e4 100644
--- a/src/main/java/hudson/plugins/ec2/InstanceState.java
+++ b/src/main/java/hudson/plugins/ec2/InstanceState.java
@@ -29,7 +29,12 @@
* @author Kohsuke Kawaguchi
*/
public enum InstanceState {
- PENDING, RUNNING, SHUTTING_DOWN, TERMINATED, STOPPING, STOPPED;
+ PENDING,
+ RUNNING,
+ SHUTTING_DOWN,
+ TERMINATED,
+ STOPPING,
+ STOPPED;
public String getCode() {
return name().toLowerCase().replace('_', '-');
diff --git a/src/main/java/hudson/plugins/ec2/InstanceTypeConverter.java b/src/main/java/hudson/plugins/ec2/InstanceTypeConverter.java
index c659aa263..220d18169 100644
--- a/src/main/java/hudson/plugins/ec2/InstanceTypeConverter.java
+++ b/src/main/java/hudson/plugins/ec2/InstanceTypeConverter.java
@@ -23,15 +23,14 @@
*/
package hudson.plugins.ec2;
-import java.util.HashMap;
-import java.util.Map;
-
-import com.amazonaws.services.ec2.model.InstanceType;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import java.util.HashMap;
+import java.util.Map;
+import software.amazon.awssdk.services.ec2.model.InstanceType;
/*
* Note this is used only to handle the metadata for older versions of the ec2-plugin. The current
@@ -39,31 +38,34 @@
*/
public class InstanceTypeConverter implements Converter {
- private static final Map TYPICAL_INSTANCE_TYPES = new HashMap();
+ private static final Map TYPICAL_INSTANCE_TYPES = new HashMap<>();
static {
- TYPICAL_INSTANCE_TYPES.put("DEFAULT", InstanceType.M1Small);
- TYPICAL_INSTANCE_TYPES.put("LARGE", InstanceType.M1Large);
- TYPICAL_INSTANCE_TYPES.put("XLARGE", InstanceType.M1Xlarge);
- TYPICAL_INSTANCE_TYPES.put("MEDIUM_HCPU", InstanceType.C1Medium);
- TYPICAL_INSTANCE_TYPES.put("XLARGE_HCPU", InstanceType.C1Xlarge);
- TYPICAL_INSTANCE_TYPES.put("XLARGE_HMEM", InstanceType.M2Xlarge);
- TYPICAL_INSTANCE_TYPES.put("XLARGE_HMEM_M3", InstanceType.M3Xlarge);
- TYPICAL_INSTANCE_TYPES.put("XLARGE_DOUBLE_HMEM", InstanceType.M22xlarge);
- TYPICAL_INSTANCE_TYPES.put("XLARGE_QUAD_HMEM", InstanceType.M24xlarge);
- TYPICAL_INSTANCE_TYPES.put("XLARGE_QUAD_HMEM_M3", InstanceType.M32xlarge);
- TYPICAL_INSTANCE_TYPES.put("XLARGE_CLUSTER_COMPUTE", InstanceType.Cc14xlarge);
+ TYPICAL_INSTANCE_TYPES.put("DEFAULT", InstanceType.M1_SMALL);
+ TYPICAL_INSTANCE_TYPES.put("LARGE", InstanceType.M1_LARGE);
+ TYPICAL_INSTANCE_TYPES.put("XLARGE", InstanceType.M1_XLARGE);
+ TYPICAL_INSTANCE_TYPES.put("MEDIUM_HCPU", InstanceType.C1_MEDIUM);
+ TYPICAL_INSTANCE_TYPES.put("XLARGE_HCPU", InstanceType.C1_XLARGE);
+ TYPICAL_INSTANCE_TYPES.put("XLARGE_HMEM", InstanceType.M2_XLARGE);
+ TYPICAL_INSTANCE_TYPES.put("XLARGE_HMEM_M3", InstanceType.M3_XLARGE);
+ TYPICAL_INSTANCE_TYPES.put("XLARGE_DOUBLE_HMEM", InstanceType.M2_2_XLARGE);
+ TYPICAL_INSTANCE_TYPES.put("XLARGE_QUAD_HMEM", InstanceType.M2_4_XLARGE);
+ TYPICAL_INSTANCE_TYPES.put("XLARGE_QUAD_HMEM_M3", InstanceType.M3_2_XLARGE);
+ TYPICAL_INSTANCE_TYPES.put("XLARGE_CLUSTER_COMPUTE", InstanceType.CC1_4_XLARGE);
}
+ @Override
public boolean canConvert(Class type) {
return InstanceType.class == type;
}
+ @Override
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
InstanceType instanceType = (InstanceType) source;
writer.setValue(instanceType.name());
}
+ @Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
InstanceType instanceType = null;
@@ -77,5 +79,4 @@ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext co
return instanceType;
}
-
}
diff --git a/src/main/java/hudson/plugins/ec2/MacData.java b/src/main/java/hudson/plugins/ec2/MacData.java
index 7fc77a5d9..155895457 100644
--- a/src/main/java/hudson/plugins/ec2/MacData.java
+++ b/src/main/java/hudson/plugins/ec2/MacData.java
@@ -6,48 +6,28 @@
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
-public class MacData extends AMITypeData {
- private final String rootCommandPrefix;
- private final String slaveCommandPrefix;
- private final String slaveCommandSuffix;
- private final String sshPort;
- private final String bootDelay;
-
+public class MacData extends SSHData {
@DataBoundConstructor
- public MacData(String rootCommandPrefix, String slaveCommandPrefix, String slaveCommandSuffix, String sshPort, String bootDelay) {
- this.rootCommandPrefix = rootCommandPrefix;
- this.slaveCommandPrefix = slaveCommandPrefix;
- this.slaveCommandSuffix = slaveCommandSuffix;
- this.sshPort = sshPort;
- this.bootDelay = bootDelay;
-
- this.readResolve();
+ public MacData(
+ String rootCommandPrefix,
+ String slaveCommandPrefix,
+ String slaveCommandSuffix,
+ String sshPort,
+ String bootDelay) {
+ super(rootCommandPrefix, slaveCommandPrefix, slaveCommandSuffix, sshPort, bootDelay);
}
+ @Override
protected Object readResolve() {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
return this;
}
- @Override
- public boolean isWindows() {
- return false;
- }
-
- @Override
- public boolean isUnix() {
- return false;
- }
-
@Override
public boolean isMac() {
return true;
}
- public String getBootDelay() {
- return bootDelay;
- }
-
@Extension
public static class DescriptorImpl extends Descriptor {
@Override
@@ -56,22 +36,6 @@ public String getDisplayName() {
}
}
- public String getRootCommandPrefix() {
- return rootCommandPrefix;
- }
-
- public String getSlaveCommandPrefix() {
- return slaveCommandPrefix;
- }
-
- public String getSlaveCommandSuffix() {
- return slaveCommandSuffix;
- }
-
- public String getSshPort() {
- return sshPort == null || sshPort.isEmpty() ? "22" : sshPort;
- }
-
@Override
public int hashCode() {
final int prime = 31;
@@ -85,33 +49,44 @@ public int hashCode() {
@Override
public boolean equals(Object obj) {
- if (this == obj)
+ if (this == obj) {
return true;
- if (obj == null)
+ }
+ if (obj == null) {
return false;
- if (this.getClass() != obj.getClass())
+ }
+ if (this.getClass() != obj.getClass()) {
return false;
+ }
final MacData other = (MacData) obj;
if (StringUtils.isEmpty(rootCommandPrefix)) {
- if (!StringUtils.isEmpty(other.rootCommandPrefix))
+ if (!StringUtils.isEmpty(other.rootCommandPrefix)) {
return false;
- } else if (!rootCommandPrefix.equals(other.rootCommandPrefix))
+ }
+ } else if (!rootCommandPrefix.equals(other.rootCommandPrefix)) {
return false;
+ }
if (StringUtils.isEmpty(slaveCommandPrefix)) {
- if (!StringUtils.isEmpty(other.slaveCommandPrefix))
+ if (!StringUtils.isEmpty(other.slaveCommandPrefix)) {
return false;
- } else if (!slaveCommandPrefix.equals(other.slaveCommandPrefix))
+ }
+ } else if (!slaveCommandPrefix.equals(other.slaveCommandPrefix)) {
return false;
+ }
if (StringUtils.isEmpty(slaveCommandSuffix)) {
- if (!StringUtils.isEmpty(other.slaveCommandSuffix))
+ if (!StringUtils.isEmpty(other.slaveCommandSuffix)) {
return false;
- } else if (!slaveCommandSuffix.equals(other.slaveCommandSuffix))
+ }
+ } else if (!slaveCommandSuffix.equals(other.slaveCommandSuffix)) {
return false;
+ }
if (StringUtils.isEmpty(sshPort)) {
- if (!StringUtils.isEmpty(other.sshPort))
+ if (!StringUtils.isEmpty(other.sshPort)) {
return false;
- } else if (!sshPort.equals(other.sshPort))
+ }
+ } else if (!sshPort.equals(other.sshPort)) {
return false;
+ }
return true;
}
}
diff --git a/src/main/java/hudson/plugins/ec2/NoDelayProvisionerStrategy.java b/src/main/java/hudson/plugins/ec2/NoDelayProvisionerStrategy.java
index a4af7a43a..d9993eaa3 100644
--- a/src/main/java/hudson/plugins/ec2/NoDelayProvisionerStrategy.java
+++ b/src/main/java/hudson/plugins/ec2/NoDelayProvisionerStrategy.java
@@ -5,11 +5,10 @@
import hudson.model.LoadStatistics;
import hudson.slaves.Cloud;
import hudson.slaves.NodeProvisioner;
-import jenkins.model.Jenkins;
-
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
+import jenkins.model.Jenkins;
/**
* Implementation of {@link NodeProvisioner.Strategy} which will provision a new node immediately as
@@ -28,27 +27,42 @@ public NodeProvisioner.StrategyDecision apply(NodeProvisioner.StrategyState stra
final Label label = strategyState.getLabel();
LoadStatistics.LoadStatisticsSnapshot snapshot = strategyState.getSnapshot();
- int availableCapacity =
- snapshot.getAvailableExecutors() // live executors
- + snapshot.getConnectingExecutors() // executors present but not yet connected
- + strategyState.getPlannedCapacitySnapshot() // capacity added by previous strategies from previous rounds
- + strategyState.getAdditionalPlannedCapacity(); // capacity added by previous strategies _this round_
+
+ int availableCapacity = snapshot.getAvailableExecutors() // live executors (idle)
+ + snapshot.getConnectingExecutors() // executors present but not yet connected
+ + strategyState
+ .getPlannedCapacitySnapshot() // capacity added by previous strategies from previous rounds
+ + strategyState.getAdditionalPlannedCapacity(); // capacity added by previous strategies _this round_
int currentDemand = snapshot.getQueueLength();
- LOGGER.log(Level.FINE, "Available capacity={0}, currentDemand={1}",
- new Object[]{availableCapacity, currentDemand});
+
+ LOGGER.log(
+ Level.FINE, "Available capacity={0}, currentDemand={1}", new Object[] {availableCapacity, currentDemand
+ });
if (availableCapacity < currentDemand) {
Jenkins jenkinsInstance = Jenkins.get();
for (Cloud cloud : jenkinsInstance.clouds) {
- if (!(cloud instanceof AmazonEC2Cloud)) continue;
- if (!cloud.canProvision(label)) continue;
- AmazonEC2Cloud ec2 = (AmazonEC2Cloud) cloud;
- if (!ec2.isNoDelayProvisioning()) continue;
+ if (!(cloud instanceof EC2Cloud ec2)) {
+ continue;
+ }
+ if (!cloud.canProvision(new Cloud.CloudState(label, 0))) {
+ continue;
+ }
+ if (!ec2.isNoDelayProvisioning()) {
+ continue;
+ }
+
+ int numToProvision = currentDemand - availableCapacity;
+ LOGGER.log(Level.FINE, "Planned {0} new nodes", numToProvision);
+
+ Collection plannedNodes =
+ cloud.provision(new Cloud.CloudState(label, 0), numToProvision);
- Collection plannedNodes = cloud.provision(label, currentDemand - availableCapacity);
LOGGER.log(Level.FINE, "Planned {0} new nodes", plannedNodes.size());
strategyState.recordPendingLaunches(plannedNodes);
availableCapacity += plannedNodes.size();
- LOGGER.log(Level.FINE, "After provisioning, available capacity={0}, currentDemand={1}", new Object[]{availableCapacity, currentDemand});
+ LOGGER.log(Level.FINE, "After provisioning, available capacity={0}, currentDemand={1}", new Object[] {
+ availableCapacity, currentDemand
+ });
break;
}
}
@@ -60,5 +74,4 @@ public NodeProvisioner.StrategyDecision apply(NodeProvisioner.StrategyState stra
return NodeProvisioner.StrategyDecision.CONSULT_REMAINING_STRATEGIES;
}
}
-
-}
\ No newline at end of file
+}
diff --git a/src/main/java/hudson/plugins/ec2/PluginImpl.java b/src/main/java/hudson/plugins/ec2/PluginImpl.java
index 656cfff58..666046144 100644
--- a/src/main/java/hudson/plugins/ec2/PluginImpl.java
+++ b/src/main/java/hudson/plugins/ec2/PluginImpl.java
@@ -28,12 +28,9 @@
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.plugins.ec2.util.MinimumInstanceChecker;
-import jenkins.model.Jenkins;
-
import java.io.IOException;
import java.util.logging.Logger;
-
-import java.io.IOException;
+import jenkins.model.Jenkins;
/**
* Added to handle backwards compatibility of xstream class name mapping.
@@ -41,17 +38,18 @@
@Extension
public class PluginImpl extends Plugin implements Describable {
private static final Logger LOGGER = Logger.getLogger(PluginImpl.class.getName());
-
+
// Whether the SshHostKeyVerificationAdministrativeMonitor should show messages when we have templates using
// accept-new or check-new-soft strategies
- private long dismissInsecureMessages;
+ private long dismissInsecureMessages;
public void saveDismissInsecureMessages(long dismissInsecureMessages) {
this.dismissInsecureMessages = dismissInsecureMessages;
try {
save();
- } catch(IOException io) {
- LOGGER.warning("There was a problem saving that you want to dismiss all messages related to insecure EC2 templates");
+ } catch (IOException io) {
+ LOGGER.warning(
+ "There was a problem saving that you want to dismiss all messages related to insecure EC2 templates");
}
}
@@ -59,6 +57,7 @@ public long getDismissInsecureMessages() {
return dismissInsecureMessages;
}
+ @Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) Jenkins.get().getDescriptorOrDie(getClass());
}
@@ -78,13 +77,13 @@ public String getDisplayName() {
@Override
public void postInitialize() throws IOException {
// backward compatibility with the legacy class name
- Jenkins.XSTREAM.alias("hudson.plugins.ec2.EC2Cloud", AmazonEC2Cloud.class);
+ Jenkins.XSTREAM.alias("hudson.plugins.ec2.EC2Cloud", AmazonEC2Cloud.class, EC2Cloud.class);
Jenkins.XSTREAM.alias("hudson.plugins.ec2.EC2Slave", EC2OndemandSlave.class);
// backward compatibility with the legacy instance type
Jenkins.XSTREAM.registerConverter(new InstanceTypeConverter());
load();
-
+
MinimumInstanceChecker.checkForMinimumInstances();
}
}
diff --git a/src/main/java/hudson/plugins/ec2/SSHData.java b/src/main/java/hudson/plugins/ec2/SSHData.java
new file mode 100644
index 000000000..5d7c5a00a
--- /dev/null
+++ b/src/main/java/hudson/plugins/ec2/SSHData.java
@@ -0,0 +1,143 @@
+package hudson.plugins.ec2;
+
+import jenkins.model.Jenkins;
+import org.apache.commons.lang.StringUtils;
+
+public abstract class SSHData extends AMITypeData {
+ protected final String rootCommandPrefix;
+ protected final String slaveCommandPrefix;
+ protected final String slaveCommandSuffix;
+ protected final String sshPort;
+ protected final String bootDelay;
+
+ protected SSHData(
+ String rootCommandPrefix,
+ String slaveCommandPrefix,
+ String slaveCommandSuffix,
+ String sshPort,
+ String bootDelay) {
+ this.rootCommandPrefix = rootCommandPrefix;
+ this.slaveCommandPrefix = slaveCommandPrefix;
+ this.slaveCommandSuffix = slaveCommandSuffix;
+ this.sshPort = sshPort;
+ this.bootDelay = bootDelay;
+
+ this.readResolve();
+ }
+
+ protected Object readResolve() {
+ Jenkins j = Jenkins.getInstanceOrNull();
+ if (j != null) {
+ j.checkPermission(Jenkins.ADMINISTER);
+ }
+ return this;
+ }
+
+ @Override
+ public boolean isWindows() {
+ return false;
+ }
+
+ @Override
+ public boolean isUnix() {
+ return false;
+ }
+
+ @Override
+ public boolean isMac() {
+ return false;
+ }
+
+ @Override
+ public boolean isSSHAgent() {
+ return true;
+ }
+
+ @Override
+ public boolean isWinRMAgent() {
+ return false;
+ }
+
+ public String getRootCommandPrefix() {
+ return rootCommandPrefix;
+ }
+
+ public String getSlaveCommandPrefix() {
+ return slaveCommandPrefix;
+ }
+
+ public String getSlaveCommandSuffix() {
+ return slaveCommandSuffix;
+ }
+
+ public String getSshPort() {
+ return sshPort == null || sshPort.isEmpty() ? "22" : sshPort;
+ }
+
+ @Override
+ public String getBootDelay() {
+ return bootDelay;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((rootCommandPrefix == null) ? 0 : rootCommandPrefix.hashCode());
+ result = prime * result + ((slaveCommandPrefix == null) ? 0 : slaveCommandPrefix.hashCode());
+ result = prime * result + ((slaveCommandSuffix == null) ? 0 : slaveCommandSuffix.hashCode());
+ result = prime * result + ((sshPort == null) ? 0 : sshPort.hashCode());
+ result = prime * result + ((bootDelay == null) ? 0 : bootDelay.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (this.getClass() != obj.getClass()) {
+ return false;
+ }
+ final SSHData other = (SSHData) obj;
+ if (StringUtils.isEmpty(rootCommandPrefix)) {
+ if (!StringUtils.isEmpty(other.rootCommandPrefix)) {
+ return false;
+ }
+ } else if (!rootCommandPrefix.equals(other.rootCommandPrefix)) {
+ return false;
+ }
+ if (StringUtils.isEmpty(slaveCommandPrefix)) {
+ if (!StringUtils.isEmpty(other.slaveCommandPrefix)) {
+ return false;
+ }
+ } else if (!slaveCommandPrefix.equals(other.slaveCommandPrefix)) {
+ return false;
+ }
+ if (StringUtils.isEmpty(slaveCommandSuffix)) {
+ if (!StringUtils.isEmpty(other.slaveCommandSuffix)) {
+ return false;
+ }
+ } else if (!slaveCommandSuffix.equals(other.slaveCommandSuffix)) {
+ return false;
+ }
+ if (StringUtils.isEmpty(sshPort)) {
+ if (!StringUtils.isEmpty(other.sshPort)) {
+ return false;
+ }
+ } else if (!sshPort.equals(other.sshPort)) {
+ return false;
+ }
+ if (bootDelay == null) {
+ if (other.bootDelay != null) {
+ return false;
+ }
+ } else if (!bootDelay.equals(other.bootDelay)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java
index 337b2b78d..aeb8e5c4f 100644
--- a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java
+++ b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java
@@ -17,59 +17,8 @@
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package hudson.plugins.ec2;
-import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED;
-import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_METADATA_ENDPOINT_ENABLED;
-import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED;
-import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT;
-import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_JAVA_PATH;
-
-import com.amazonaws.AmazonClientException;
-import com.amazonaws.AmazonServiceException;
-import com.amazonaws.auth.AWSCredentialsProvider;
-import com.amazonaws.services.ec2.AmazonEC2;
-import com.amazonaws.services.ec2.model.AmazonEC2Exception;
-import com.amazonaws.services.ec2.model.BlockDeviceMapping;
-import com.amazonaws.services.ec2.model.CancelSpotInstanceRequestsRequest;
-import com.amazonaws.services.ec2.model.CreateTagsRequest;
-import com.amazonaws.services.ec2.model.CreditSpecificationRequest;
-import com.amazonaws.services.ec2.model.DescribeImagesRequest;
-import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
-import com.amazonaws.services.ec2.model.DescribeInstancesResult;
-import com.amazonaws.services.ec2.model.DescribeSecurityGroupsRequest;
-import com.amazonaws.services.ec2.model.DescribeSecurityGroupsResult;
-import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsRequest;
-import com.amazonaws.services.ec2.model.DescribeSubnetsRequest;
-import com.amazonaws.services.ec2.model.DescribeSubnetsResult;
-import com.amazonaws.services.ec2.model.Filter;
-import com.amazonaws.services.ec2.model.HttpTokensState;
-import com.amazonaws.services.ec2.model.IamInstanceProfileSpecification;
-import com.amazonaws.services.ec2.model.Image;
-import com.amazonaws.services.ec2.model.Instance;
-import com.amazonaws.services.ec2.model.InstanceMarketOptionsRequest;
-import com.amazonaws.services.ec2.model.InstanceMetadataEndpointState;
-import com.amazonaws.services.ec2.model.InstanceMetadataOptionsRequest;
-import com.amazonaws.services.ec2.model.InstanceNetworkInterfaceSpecification;
-import com.amazonaws.services.ec2.model.InstanceStateName;
-import com.amazonaws.services.ec2.model.InstanceType;
-import com.amazonaws.services.ec2.model.KeyPair;
-import com.amazonaws.services.ec2.model.LaunchSpecification;
-import com.amazonaws.services.ec2.model.MarketType;
-import com.amazonaws.services.ec2.model.Placement;
-import com.amazonaws.services.ec2.model.RequestSpotInstancesRequest;
-import com.amazonaws.services.ec2.model.RequestSpotInstancesResult;
-import com.amazonaws.services.ec2.model.Reservation;
-import com.amazonaws.services.ec2.model.ResourceType;
-import com.amazonaws.services.ec2.model.RunInstancesRequest;
-import com.amazonaws.services.ec2.model.SecurityGroup;
-import com.amazonaws.services.ec2.model.ShutdownBehavior;
-import com.amazonaws.services.ec2.model.SpotInstanceRequest;
-import com.amazonaws.services.ec2.model.SpotMarketOptions;
-import com.amazonaws.services.ec2.model.SpotPlacement;
-import com.amazonaws.services.ec2.model.StartInstancesRequest;
-import com.amazonaws.services.ec2.model.StartInstancesResult;
-import com.amazonaws.services.ec2.model.Subnet;
-import com.amazonaws.services.ec2.model.Tag;
-import com.amazonaws.services.ec2.model.TagSpecification;
+
+import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.Util;
@@ -89,6 +38,8 @@
import hudson.plugins.ec2.util.DeviceMappingParser;
import hudson.plugins.ec2.util.EC2AgentConfig;
import hudson.plugins.ec2.util.EC2AgentFactory;
+import hudson.plugins.ec2.util.InstanceTypeCompat;
+import hudson.plugins.ec2.util.KeyPair;
import hudson.plugins.ec2.util.MinimumInstanceChecker;
import hudson.plugins.ec2.util.MinimumNumberOfInstancesTimeRangeConfig;
import hudson.security.Permission;
@@ -98,25 +49,9 @@
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import hudson.util.Secret;
-import jenkins.model.Jenkins;
-import jenkins.model.JenkinsLocationConfiguration;
-import jenkins.slaves.iterators.api.NodeIterator;
-import org.apache.commons.lang.StringUtils;
-import org.kohsuke.accmod.Restricted;
-import org.kohsuke.accmod.restrictions.NoExternalUse;
-import org.kohsuke.stapler.DataBoundConstructor;
-import org.kohsuke.stapler.DataBoundSetter;
-import org.kohsuke.stapler.QueryParameter;
-import org.kohsuke.stapler.Stapler;
-import org.kohsuke.stapler.interceptor.RequirePOST;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-import org.kohsuke.stapler.verb.POST;
-
-import javax.servlet.ServletException;
+import jakarta.servlet.ServletException;
import java.io.IOException;
import java.io.PrintStream;
-import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -136,6 +71,73 @@
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import jenkins.model.Jenkins;
+import jenkins.model.JenkinsLocationConfiguration;
+import jenkins.slaves.iterators.api.NodeIterator;
+import org.apache.commons.lang.StringUtils;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.DataBoundConstructor;
+import org.kohsuke.stapler.DataBoundSetter;
+import org.kohsuke.stapler.QueryParameter;
+import org.kohsuke.stapler.Stapler;
+import org.kohsuke.stapler.interceptor.RequirePOST;
+import org.kohsuke.stapler.verb.POST;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.awscore.exception.AwsServiceException;
+import software.amazon.awssdk.core.exception.SdkException;
+import software.amazon.awssdk.services.ec2.Ec2Client;
+import software.amazon.awssdk.services.ec2.model.BlockDeviceMapping;
+import software.amazon.awssdk.services.ec2.model.CancelSpotInstanceRequestsRequest;
+import software.amazon.awssdk.services.ec2.model.CreateTagsRequest;
+import software.amazon.awssdk.services.ec2.model.CreditSpecificationRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeImagesRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeInstanceTypesRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeInstanceTypesResponse;
+import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse;
+import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsResponse;
+import software.amazon.awssdk.services.ec2.model.DescribeSpotInstanceRequestsRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeSubnetsRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeSubnetsResponse;
+import software.amazon.awssdk.services.ec2.model.DeviceType;
+import software.amazon.awssdk.services.ec2.model.EbsBlockDevice;
+import software.amazon.awssdk.services.ec2.model.Ec2Exception;
+import software.amazon.awssdk.services.ec2.model.EnclaveOptionsRequest;
+import software.amazon.awssdk.services.ec2.model.Filter;
+import software.amazon.awssdk.services.ec2.model.HttpTokensState;
+import software.amazon.awssdk.services.ec2.model.IamInstanceProfileSpecification;
+import software.amazon.awssdk.services.ec2.model.Image;
+import software.amazon.awssdk.services.ec2.model.Instance;
+import software.amazon.awssdk.services.ec2.model.InstanceMarketOptionsRequest;
+import software.amazon.awssdk.services.ec2.model.InstanceMetadataEndpointState;
+import software.amazon.awssdk.services.ec2.model.InstanceMetadataOptionsRequest;
+import software.amazon.awssdk.services.ec2.model.InstanceNetworkInterfaceSpecification;
+import software.amazon.awssdk.services.ec2.model.InstanceStateName;
+import software.amazon.awssdk.services.ec2.model.InstanceType;
+import software.amazon.awssdk.services.ec2.model.InstanceTypeHypervisor;
+import software.amazon.awssdk.services.ec2.model.InstanceTypeInfo;
+import software.amazon.awssdk.services.ec2.model.MarketType;
+import software.amazon.awssdk.services.ec2.model.NitroEnclavesSupport;
+import software.amazon.awssdk.services.ec2.model.Placement;
+import software.amazon.awssdk.services.ec2.model.RequestSpotInstancesRequest;
+import software.amazon.awssdk.services.ec2.model.RequestSpotInstancesResponse;
+import software.amazon.awssdk.services.ec2.model.RequestSpotLaunchSpecification;
+import software.amazon.awssdk.services.ec2.model.Reservation;
+import software.amazon.awssdk.services.ec2.model.ResourceType;
+import software.amazon.awssdk.services.ec2.model.RunInstancesMonitoringEnabled;
+import software.amazon.awssdk.services.ec2.model.RunInstancesRequest;
+import software.amazon.awssdk.services.ec2.model.SecurityGroup;
+import software.amazon.awssdk.services.ec2.model.ShutdownBehavior;
+import software.amazon.awssdk.services.ec2.model.SpotInstanceRequest;
+import software.amazon.awssdk.services.ec2.model.SpotMarketOptions;
+import software.amazon.awssdk.services.ec2.model.SpotPlacement;
+import software.amazon.awssdk.services.ec2.model.StartInstancesRequest;
+import software.amazon.awssdk.services.ec2.model.StartInstancesResponse;
+import software.amazon.awssdk.services.ec2.model.Subnet;
+import software.amazon.awssdk.services.ec2.model.Tag;
+import software.amazon.awssdk.services.ec2.model.TagSpecification;
/**
* Template of {@link EC2AbstractSlave} to launch.
@@ -159,7 +161,7 @@ public class SlaveTemplate implements Describable {
public final String remoteFS;
- public final InstanceType type;
+ public String type;
public final boolean ebsOptimized;
@@ -189,6 +191,8 @@ public class SlaveTemplate implements Describable {
public final String idleTerminationMinutes;
+ private boolean terminateIdleDuringShutdown;
+
public final String iamInstanceProfile;
public final boolean deleteRootOnTermination;
@@ -213,7 +217,10 @@ public class SlaveTemplate implements Describable {
public HostKeyVerificationStrategyEnum hostKeyVerificationStrategy;
- public final boolean associatePublicIp;
+ public AssociateIPStrategy associateIPStrategy;
+
+ @Deprecated
+ public transient boolean associatePublicIp;
protected transient EC2Cloud parent;
@@ -225,6 +232,8 @@ public class SlaveTemplate implements Describable {
public int maxTotalUses;
+ private boolean avoidUsingOrphanedNodes;
+
private /* lazily initialized */ DescribableList, NodePropertyDescriptor> nodeProperties;
public int nextSubnet;
@@ -243,9 +252,11 @@ public class SlaveTemplate implements Describable {
private Integer metadataHopsLimit;
- private transient/* almost final */ Set labelSet;
+ private Boolean enclaveEnabled;
+
+ private transient /* almost final */ Set labelSet;
- private transient/* almost final */Set securityGroupSet;
+ private transient /* almost final */ Set securityGroupSet;
/* FIXME: Ideally these would be List, but Jenkins currently
* doesn't offer a usable way to represent those in forms. Instead
@@ -287,23 +298,62 @@ public class SlaveTemplate implements Describable {
public transient boolean useDedicatedTenancy;
@DataBoundConstructor
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String javaPath, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances,
- int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination,
- boolean useEphemeralDevices, String launchTimeoutStr, boolean associatePublicIp,
- String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring,
- boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses,
- List extends NodeProperty>> nodeProperties, HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, Tenancy tenancy, EbsEncryptRootVolume ebsEncryptRootVolume,
- Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit, Boolean metadataSupported) {
-
- if(StringUtils.isNotBlank(remoteAdmin) || StringUtils.isNotBlank(jvmopts) || StringUtils.isNotBlank(tmpDir)){
- LOGGER.log(Level.FINE, "As remoteAdmin, jvmopts or tmpDir is not blank, we must ensure the user has ADMINISTER rights.");
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ String type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String javaPath,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ int minimumNumberOfInstances,
+ int minimumNumberOfSpareInstances,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ String launchTimeoutStr,
+ AssociateIPStrategy associateIPStrategy,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean monitoring,
+ boolean t2Unlimited,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
+ List extends NodeProperty>> nodeProperties,
+ HostKeyVerificationStrategyEnum hostKeyVerificationStrategy,
+ Tenancy tenancy,
+ EbsEncryptRootVolume ebsEncryptRootVolume,
+ Boolean metadataEndpointEnabled,
+ Boolean metadataTokensRequired,
+ Integer metadataHopsLimit,
+ Boolean metadataSupported,
+ Boolean enclaveEnabled) {
+
+ if (StringUtils.isNotBlank(remoteAdmin) || StringUtils.isNotBlank(jvmopts) || StringUtils.isNotBlank(tmpDir)) {
+ LOGGER.log(
+ Level.FINE,
+ "As remoteAdmin, jvmopts or tmpDir is not blank, we must ensure the user has ADMINISTER rights.");
// Can be null during tests
Jenkins j = Jenkins.getInstanceOrNull();
- if (j != null)
+ if (j != null) {
j.checkPermission(Jenkins.ADMINISTER);
+ }
}
this.ami = ami;
@@ -312,7 +362,8 @@ public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, Stri
this.securityGroups = securityGroups;
this.remoteFS = remoteFS;
this.amiType = amiType;
- this.type = type;
+ this.type =
+ type != null && !type.isEmpty() ? InstanceTypeCompat.of(type).toString() : null;
this.ebsOptimized = ebsOptimized;
this.labels = Util.fixNull(labelString);
this.mode = mode != null ? mode : Node.Mode.NORMAL;
@@ -326,7 +377,7 @@ public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, Stri
if (StringUtils.isNotBlank(javaPath)) {
this.javaPath = javaPath;
} else {
- this.javaPath = DEFAULT_JAVA_PATH;
+ this.javaPath = EC2AbstractSlave.DEFAULT_JAVA_PATH;
}
this.jvmopts = jvmopts;
@@ -334,7 +385,6 @@ public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, Stri
this.subnetId = subnetId;
this.tags = tags;
this.idleTerminationMinutes = idleTerminationMinutes;
- this.associatePublicIp = associatePublicIp;
this.connectionStrategy = connectionStrategy == null ? ConnectionStrategy.PRIVATE_IP : connectionStrategy;
this.useDedicatedTenancy = tenancy == Tenancy.Dedicated;
this.connectBySSHProcess = connectBySSHProcess;
@@ -367,243 +417,1208 @@ public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, Stri
this.customDeviceMapping = customDeviceMapping;
this.t2Unlimited = t2Unlimited;
- this.hostKeyVerificationStrategy = hostKeyVerificationStrategy != null ? hostKeyVerificationStrategy : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT;
+ this.hostKeyVerificationStrategy = hostKeyVerificationStrategy != null
+ ? hostKeyVerificationStrategy
+ : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT;
this.tenancy = tenancy != null ? tenancy : Tenancy.Default;
this.ebsEncryptRootVolume = ebsEncryptRootVolume != null ? ebsEncryptRootVolume : EbsEncryptRootVolume.DEFAULT;
- this.metadataSupported = metadataSupported != null ? metadataSupported : DEFAULT_METADATA_SUPPORTED;
- this.metadataEndpointEnabled = metadataEndpointEnabled != null ? metadataEndpointEnabled : DEFAULT_METADATA_ENDPOINT_ENABLED;
- this.metadataTokensRequired = metadataTokensRequired != null ? metadataTokensRequired : DEFAULT_METADATA_TOKENS_REQUIRED;
- this.metadataHopsLimit = metadataHopsLimit != null ? metadataHopsLimit : DEFAULT_METADATA_HOPS_LIMIT;
+ this.metadataSupported =
+ metadataSupported != null ? metadataSupported : EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED;
+ this.metadataEndpointEnabled = metadataEndpointEnabled != null
+ ? metadataEndpointEnabled
+ : EC2AbstractSlave.DEFAULT_METADATA_ENDPOINT_ENABLED;
+ this.metadataTokensRequired = metadataTokensRequired != null
+ ? metadataTokensRequired
+ : EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED;
+ this.metadataHopsLimit =
+ metadataHopsLimit != null ? metadataHopsLimit : EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT;
+ this.enclaveEnabled = enclaveEnabled != null ? enclaveEnabled : EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED;
+ this.associateIPStrategy = associateIPStrategy != null ? associateIPStrategy : AssociateIPStrategy.DEFAULT;
+
readResolve(); // initialize
}
@Deprecated
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String javaPath, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances,
- int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination,
- boolean useEphemeralDevices, String launchTimeoutStr, boolean associatePublicIp,
- String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring,
- boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses,
- List extends NodeProperty>> nodeProperties, HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, Tenancy tenancy, EbsEncryptRootVolume ebsEncryptRootVolume,
- Boolean metadataSupported, Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit) {
- this(ami, zone, spotConfig, securityGroups, remoteFS,
- type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, amiType, DEFAULT_JAVA_PATH, jvmopts,
- stopOnTerminate, subnetId, tags, idleTerminationMinutes, minimumNumberOfInstances,
- minimumNumberOfSpareInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination,
- useEphemeralDevices, launchTimeoutStr, associatePublicIp,
- customDeviceMapping, connectBySSHProcess, monitoring,
- t2Unlimited, connectionStrategy, maxTotalUses,
- nodeProperties, hostKeyVerificationStrategy, tenancy, null, metadataEndpointEnabled,
- metadataTokensRequired, metadataHopsLimit, DEFAULT_METADATA_SUPPORTED);
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ String type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String javaPath,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ int minimumNumberOfInstances,
+ int minimumNumberOfSpareInstances,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean monitoring,
+ boolean t2Unlimited,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
+ List extends NodeProperty>> nodeProperties,
+ HostKeyVerificationStrategyEnum hostKeyVerificationStrategy,
+ Tenancy tenancy,
+ EbsEncryptRootVolume ebsEncryptRootVolume,
+ Boolean metadataEndpointEnabled,
+ Boolean metadataTokensRequired,
+ Integer metadataHopsLimit,
+ Boolean metadataSupported,
+ Boolean enclaveEnabled) {
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ InstanceType.fromValue(type.toString()).toString(),
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ javaPath,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ minimumNumberOfInstances,
+ minimumNumberOfSpareInstances,
+ instanceCapStr,
+ iamInstanceProfile,
+ deleteRootOnTermination,
+ useEphemeralDevices,
+ launchTimeoutStr,
+ AssociateIPStrategy.backwardsCompatible(associatePublicIp),
+ customDeviceMapping,
+ connectBySSHProcess,
+ monitoring,
+ t2Unlimited,
+ connectionStrategy,
+ maxTotalUses,
+ nodeProperties,
+ hostKeyVerificationStrategy,
+ tenancy,
+ ebsEncryptRootVolume,
+ metadataEndpointEnabled,
+ metadataTokensRequired,
+ metadataHopsLimit,
+ metadataSupported,
+ enclaveEnabled);
+ }
+
+ @Deprecated
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String javaPath,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ int minimumNumberOfInstances,
+ int minimumNumberOfSpareInstances,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean monitoring,
+ boolean t2Unlimited,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
+ List extends NodeProperty>> nodeProperties,
+ HostKeyVerificationStrategyEnum hostKeyVerificationStrategy,
+ Tenancy tenancy,
+ EbsEncryptRootVolume ebsEncryptRootVolume,
+ Boolean metadataEndpointEnabled,
+ Boolean metadataTokensRequired,
+ Integer metadataHopsLimit,
+ Boolean metadataSupported) {
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ InstanceType.fromValue(type.toString()).toString(),
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ javaPath,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ minimumNumberOfInstances,
+ minimumNumberOfSpareInstances,
+ instanceCapStr,
+ iamInstanceProfile,
+ deleteRootOnTermination,
+ useEphemeralDevices,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ connectBySSHProcess,
+ monitoring,
+ t2Unlimited,
+ connectionStrategy,
+ maxTotalUses,
+ nodeProperties,
+ hostKeyVerificationStrategy,
+ tenancy,
+ ebsEncryptRootVolume,
+ metadataEndpointEnabled,
+ metadataTokensRequired,
+ metadataHopsLimit,
+ metadataSupported,
+ EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED);
}
@Deprecated
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances,
- int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination,
- boolean useEphemeralDevices, String launchTimeoutStr, boolean associatePublicIp,
- String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring,
- boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses,
- List extends NodeProperty>> nodeProperties, HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, Tenancy tenancy, EbsEncryptRootVolume ebsEncryptRootVolume) {
- this(ami, zone, spotConfig, securityGroups, remoteFS,
- type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, amiType, DEFAULT_JAVA_PATH, jvmopts,
- stopOnTerminate, subnetId, tags, idleTerminationMinutes, minimumNumberOfInstances,
- minimumNumberOfSpareInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination,
- useEphemeralDevices, launchTimeoutStr, associatePublicIp,
- customDeviceMapping, connectBySSHProcess, monitoring,
- t2Unlimited, connectionStrategy, maxTotalUses,
- nodeProperties, hostKeyVerificationStrategy, tenancy, null, DEFAULT_METADATA_ENDPOINT_ENABLED,
- DEFAULT_METADATA_TOKENS_REQUIRED, DEFAULT_METADATA_HOPS_LIMIT, DEFAULT_METADATA_SUPPORTED);
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String javaPath,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ int minimumNumberOfInstances,
+ int minimumNumberOfSpareInstances,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean monitoring,
+ boolean t2Unlimited,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
+ List extends NodeProperty>> nodeProperties,
+ HostKeyVerificationStrategyEnum hostKeyVerificationStrategy,
+ Tenancy tenancy,
+ EbsEncryptRootVolume ebsEncryptRootVolume,
+ Boolean metadataSupported,
+ Boolean metadataEndpointEnabled,
+ Boolean metadataTokensRequired,
+ Integer metadataHopsLimit) {
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ InstanceType.fromValue(type.toString()).toString(),
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ EC2AbstractSlave.DEFAULT_JAVA_PATH,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ minimumNumberOfInstances,
+ minimumNumberOfSpareInstances,
+ instanceCapStr,
+ iamInstanceProfile,
+ deleteRootOnTermination,
+ useEphemeralDevices,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ connectBySSHProcess,
+ monitoring,
+ t2Unlimited,
+ connectionStrategy,
+ maxTotalUses,
+ nodeProperties,
+ hostKeyVerificationStrategy,
+ tenancy,
+ null,
+ metadataEndpointEnabled,
+ metadataTokensRequired,
+ metadataHopsLimit,
+ EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED,
+ EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED);
}
@Deprecated
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances,
- int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination,
- boolean useEphemeralDevices, String launchTimeoutStr, boolean associatePublicIp,
- String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring,
- boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses,
- List extends NodeProperty>> nodeProperties, HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, Tenancy tenancy) {
- this(ami, zone, spotConfig, securityGroups, remoteFS,
- type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts,
- stopOnTerminate, subnetId, tags, idleTerminationMinutes, minimumNumberOfInstances,
- minimumNumberOfSpareInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination,
- useEphemeralDevices, launchTimeoutStr, associatePublicIp,
- customDeviceMapping, connectBySSHProcess, monitoring,
- t2Unlimited, connectionStrategy, maxTotalUses,
- nodeProperties, hostKeyVerificationStrategy, tenancy, null);
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ int minimumNumberOfInstances,
+ int minimumNumberOfSpareInstances,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean monitoring,
+ boolean t2Unlimited,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
+ List extends NodeProperty>> nodeProperties,
+ HostKeyVerificationStrategyEnum hostKeyVerificationStrategy,
+ Tenancy tenancy,
+ EbsEncryptRootVolume ebsEncryptRootVolume) {
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ InstanceType.fromValue(type.toString()).toString(),
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ EC2AbstractSlave.DEFAULT_JAVA_PATH,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ minimumNumberOfInstances,
+ minimumNumberOfSpareInstances,
+ instanceCapStr,
+ iamInstanceProfile,
+ deleteRootOnTermination,
+ useEphemeralDevices,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ connectBySSHProcess,
+ monitoring,
+ t2Unlimited,
+ connectionStrategy,
+ maxTotalUses,
+ nodeProperties,
+ hostKeyVerificationStrategy,
+ tenancy,
+ null,
+ EC2AbstractSlave.DEFAULT_METADATA_ENDPOINT_ENABLED,
+ EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED,
+ EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT,
+ EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED,
+ EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED);
}
@Deprecated
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances,
- int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination,
- boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp,
- String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring,
- boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses,
- List extends NodeProperty>> nodeProperties, HostKeyVerificationStrategyEnum hostKeyVerificationStrategy) {
- this(ami, zone, spotConfig, securityGroups, remoteFS,
- type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts,
- stopOnTerminate, subnetId, tags, idleTerminationMinutes, minimumNumberOfInstances,
- minimumNumberOfSpareInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination,
- useEphemeralDevices, launchTimeoutStr, associatePublicIp,
- customDeviceMapping, connectBySSHProcess, monitoring,
- t2Unlimited, connectionStrategy, maxTotalUses,
- nodeProperties, hostKeyVerificationStrategy, Tenancy.backwardsCompatible(useDedicatedTenancy));
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ int minimumNumberOfInstances,
+ int minimumNumberOfSpareInstances,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean monitoring,
+ boolean t2Unlimited,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
+ List extends NodeProperty>> nodeProperties,
+ HostKeyVerificationStrategyEnum hostKeyVerificationStrategy,
+ Tenancy tenancy) {
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ type,
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ minimumNumberOfInstances,
+ minimumNumberOfSpareInstances,
+ instanceCapStr,
+ iamInstanceProfile,
+ deleteRootOnTermination,
+ useEphemeralDevices,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ connectBySSHProcess,
+ monitoring,
+ t2Unlimited,
+ connectionStrategy,
+ maxTotalUses,
+ nodeProperties,
+ hostKeyVerificationStrategy,
+ tenancy,
+ null);
}
@Deprecated
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances,
- int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination,
- boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp,
- String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring,
- boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses,
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ int minimumNumberOfInstances,
+ int minimumNumberOfSpareInstances,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ boolean useDedicatedTenancy,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean monitoring,
+ boolean t2Unlimited,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
+ List extends NodeProperty>> nodeProperties,
+ HostKeyVerificationStrategyEnum hostKeyVerificationStrategy) {
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ type,
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ minimumNumberOfInstances,
+ minimumNumberOfSpareInstances,
+ instanceCapStr,
+ iamInstanceProfile,
+ deleteRootOnTermination,
+ useEphemeralDevices,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ connectBySSHProcess,
+ monitoring,
+ t2Unlimited,
+ connectionStrategy,
+ maxTotalUses,
+ nodeProperties,
+ hostKeyVerificationStrategy,
+ Tenancy.backwardsCompatible(useDedicatedTenancy));
+ }
+
+ @Deprecated
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ int minimumNumberOfInstances,
+ int minimumNumberOfSpareInstances,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ boolean useDedicatedTenancy,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean monitoring,
+ boolean t2Unlimited,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
List extends NodeProperty>> nodeProperties) {
- this(ami, zone, spotConfig, securityGroups, remoteFS,
- type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts,
- stopOnTerminate, subnetId, tags, idleTerminationMinutes, minimumNumberOfInstances,
- minimumNumberOfSpareInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination,
- useEphemeralDevices, useDedicatedTenancy, launchTimeoutStr, associatePublicIp,
- customDeviceMapping, connectBySSHProcess, monitoring,
- t2Unlimited, connectionStrategy, maxTotalUses,
- nodeProperties, null);
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ type,
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ minimumNumberOfInstances,
+ minimumNumberOfSpareInstances,
+ instanceCapStr,
+ iamInstanceProfile,
+ deleteRootOnTermination,
+ useEphemeralDevices,
+ useDedicatedTenancy,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ connectBySSHProcess,
+ monitoring,
+ t2Unlimited,
+ connectionStrategy,
+ maxTotalUses,
+ nodeProperties,
+ null);
}
@Deprecated
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances,
- String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination,
- boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp,
- String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring,
- boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses,List extends NodeProperty>> nodeProperties ) {
- this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags,
- idleTerminationMinutes, minimumNumberOfInstances, 0, instanceCapStr, iamInstanceProfile, deleteRootOnTermination,
- useEphemeralDevices, useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping,
- connectBySSHProcess, monitoring, t2Unlimited, connectionStrategy, maxTotalUses, nodeProperties);
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ int minimumNumberOfInstances,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ boolean useDedicatedTenancy,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean monitoring,
+ boolean t2Unlimited,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses,
+ List extends NodeProperty>> nodeProperties) {
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ type,
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ minimumNumberOfInstances,
+ 0,
+ instanceCapStr,
+ iamInstanceProfile,
+ deleteRootOnTermination,
+ useEphemeralDevices,
+ useDedicatedTenancy,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ connectBySSHProcess,
+ monitoring,
+ t2Unlimited,
+ connectionStrategy,
+ maxTotalUses,
+ nodeProperties);
}
@Deprecated
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances,
- String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination,
- boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp,
- String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring,
- boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses) {
- this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags,
- idleTerminationMinutes, minimumNumberOfInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination,
- useEphemeralDevices, useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping,
- connectBySSHProcess, monitoring, t2Unlimited, connectionStrategy, maxTotalUses, Collections.emptyList());
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ int minimumNumberOfInstances,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ boolean useDedicatedTenancy,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean monitoring,
+ boolean t2Unlimited,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses) {
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ type,
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ minimumNumberOfInstances,
+ instanceCapStr,
+ iamInstanceProfile,
+ deleteRootOnTermination,
+ useEphemeralDevices,
+ useDedicatedTenancy,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ connectBySSHProcess,
+ monitoring,
+ t2Unlimited,
+ connectionStrategy,
+ maxTotalUses,
+ Collections.emptyList());
}
@Deprecated
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes,
- String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination,
- boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp,
- String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring,
- boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses) {
- this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags,
- idleTerminationMinutes, 0, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, useEphemeralDevices,
- useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess,
- monitoring, t2Unlimited, connectionStrategy, maxTotalUses);
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ boolean useDedicatedTenancy,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean monitoring,
+ boolean t2Unlimited,
+ ConnectionStrategy connectionStrategy,
+ int maxTotalUses) {
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ type,
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ 0,
+ instanceCapStr,
+ iamInstanceProfile,
+ deleteRootOnTermination,
+ useEphemeralDevices,
+ useDedicatedTenancy,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ connectBySSHProcess,
+ monitoring,
+ t2Unlimited,
+ connectionStrategy,
+ maxTotalUses);
}
@Deprecated
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes,
- boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination,
- boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp,
- String customDeviceMapping, boolean connectBySSHProcess, boolean connectUsingPublicIp, boolean monitoring,
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ boolean usePrivateDnsName,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ boolean useDedicatedTenancy,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean connectUsingPublicIp,
+ boolean monitoring,
boolean t2Unlimited) {
- this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags,
- idleTerminationMinutes, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, useEphemeralDevices,
- useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess,
- monitoring, t2Unlimited, ConnectionStrategy.backwardsCompatible(usePrivateDnsName, connectUsingPublicIp, associatePublicIp), -1);
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ type,
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ instanceCapStr,
+ iamInstanceProfile,
+ deleteRootOnTermination,
+ useEphemeralDevices,
+ useDedicatedTenancy,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ connectBySSHProcess,
+ monitoring,
+ t2Unlimited,
+ ConnectionStrategy.backwardsCompatible(usePrivateDnsName, connectUsingPublicIp, associatePublicIp),
+ -1);
}
@Deprecated
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes,
- boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination,
- boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp,
- String customDeviceMapping, boolean connectBySSHProcess, boolean connectUsingPublicIp) {
- this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags,
- idleTerminationMinutes, usePrivateDnsName, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, useEphemeralDevices,
- useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess,
- connectUsingPublicIp, false, false);
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ boolean usePrivateDnsName,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean deleteRootOnTermination,
+ boolean useEphemeralDevices,
+ boolean useDedicatedTenancy,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
+ boolean connectBySSHProcess,
+ boolean connectUsingPublicIp) {
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ type,
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ usePrivateDnsName,
+ instanceCapStr,
+ iamInstanceProfile,
+ deleteRootOnTermination,
+ useEphemeralDevices,
+ useDedicatedTenancy,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ connectBySSHProcess,
+ connectUsingPublicIp,
+ false,
+ false);
}
@Deprecated
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes,
- boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean useEphemeralDevices,
- boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, String customDeviceMapping,
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ boolean usePrivateDnsName,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean useEphemeralDevices,
+ boolean useDedicatedTenancy,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping,
boolean connectBySSHProcess) {
- this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags,
- idleTerminationMinutes, usePrivateDnsName, instanceCapStr, iamInstanceProfile, false, useEphemeralDevices,
- useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess, false);
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ type,
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ usePrivateDnsName,
+ instanceCapStr,
+ iamInstanceProfile,
+ false,
+ useEphemeralDevices,
+ useDedicatedTenancy,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ connectBySSHProcess,
+ false);
}
@Deprecated
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript,
- String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts,
- boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes,
- boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean useEphemeralDevices,
- boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, String customDeviceMapping) {
- this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags,
- idleTerminationMinutes, usePrivateDnsName, instanceCapStr, iamInstanceProfile, useEphemeralDevices,
- useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, false);
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ AMITypeData amiType,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ boolean usePrivateDnsName,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean useEphemeralDevices,
+ boolean useDedicatedTenancy,
+ String launchTimeoutStr,
+ boolean associatePublicIp,
+ String customDeviceMapping) {
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ type,
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ amiType,
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ usePrivateDnsName,
+ instanceCapStr,
+ iamInstanceProfile,
+ useEphemeralDevices,
+ useDedicatedTenancy,
+ launchTimeoutStr,
+ associatePublicIp,
+ customDeviceMapping,
+ false);
}
/**
* Backward compatible constructor for reloading previous version data
*/
- public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS,
- String sshPort, InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description,
- String initScript, String tmpDir, String userData, String numExecutors, String remoteAdmin, String rootCommandPrefix,
- String slaveCommandPrefix, String slaveCommandSuffix, String jvmopts, boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes,
- boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean useEphemeralDevices,
+ public SlaveTemplate(
+ String ami,
+ String zone,
+ SpotConfiguration spotConfig,
+ String securityGroups,
+ String remoteFS,
+ String sshPort,
+ com.amazonaws.services.ec2.model.InstanceType type,
+ boolean ebsOptimized,
+ String labelString,
+ Node.Mode mode,
+ String description,
+ String initScript,
+ String tmpDir,
+ String userData,
+ String numExecutors,
+ String remoteAdmin,
+ String rootCommandPrefix,
+ String slaveCommandPrefix,
+ String slaveCommandSuffix,
+ String jvmopts,
+ boolean stopOnTerminate,
+ String subnetId,
+ List tags,
+ String idleTerminationMinutes,
+ boolean usePrivateDnsName,
+ String instanceCapStr,
+ String iamInstanceProfile,
+ boolean useEphemeralDevices,
String launchTimeoutStr) {
- this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript,
- tmpDir, userData, numExecutors, remoteAdmin, new UnixData(rootCommandPrefix, slaveCommandPrefix, slaveCommandSuffix, sshPort, null),
- jvmopts, stopOnTerminate, subnetId, tags, idleTerminationMinutes, usePrivateDnsName, instanceCapStr, iamInstanceProfile,
- useEphemeralDevices, false, launchTimeoutStr, false, null);
+ this(
+ ami,
+ zone,
+ spotConfig,
+ securityGroups,
+ remoteFS,
+ type,
+ ebsOptimized,
+ labelString,
+ mode,
+ description,
+ initScript,
+ tmpDir,
+ userData,
+ numExecutors,
+ remoteAdmin,
+ new UnixData(rootCommandPrefix, slaveCommandPrefix, slaveCommandSuffix, sshPort, null),
+ jvmopts,
+ stopOnTerminate,
+ subnetId,
+ tags,
+ idleTerminationMinutes,
+ usePrivateDnsName,
+ instanceCapStr,
+ iamInstanceProfile,
+ useEphemeralDevices,
+ false,
+ launchTimeoutStr,
+ false,
+ null);
}
public boolean isConnectBySSHProcess() {
@@ -638,7 +1653,7 @@ public String getSlaveName(String instanceId) {
}
}
- String getZone() {
+ public String getZone() {
return zone;
}
@@ -651,7 +1666,7 @@ public Set getSecurityGroupSet() {
}
public Set parseSecurityGroups() {
- if (securityGroups == null || "".equals(securityGroups.trim())) {
+ if (securityGroups == null || securityGroups.trim().isEmpty()) {
return Collections.emptySet();
} else {
return new HashSet<>(Arrays.asList(securityGroups.split("\\s*,\\s*")));
@@ -662,18 +1677,15 @@ public int getNumExecutors() {
try {
return Integer.parseInt(numExecutors);
} catch (NumberFormatException e) {
- return EC2AbstractSlave.toNumExecutors(type);
+ return EC2AbstractSlave.toNumExecutors(InstanceType.fromValue(type));
}
}
public int getSshPort() {
try {
String sshPort = "";
- if (amiType.isUnix()) {
- sshPort = ((UnixData) amiType).getSshPort();
- }
- if (amiType.isMac()) {
- sshPort = ((MacData) amiType).getSshPort();
+ if (amiType.isSSHAgent()) {
+ sshPort = ((SSHData) amiType).getSshPort();
}
return Integer.parseInt(sshPort);
} catch (NumberFormatException e) {
@@ -686,22 +1698,22 @@ public String getRemoteAdmin() {
}
public String getRootCommandPrefix() {
- return (amiType.isUnix() ? ((UnixData) amiType).getRootCommandPrefix() : (amiType.isMac() ? ((MacData) amiType).getRootCommandPrefix():""));
+ return amiType.isSSHAgent() ? ((SSHData) amiType).getRootCommandPrefix() : "";
}
public String getSlaveCommandPrefix() {
- return (amiType.isUnix() ? ((UnixData) amiType).getSlaveCommandPrefix() : (amiType.isMac() ? ((MacData) amiType).getSlaveCommandPrefix() : ""));
+ return amiType.isSSHAgent() ? ((SSHData) amiType).getSlaveCommandPrefix() : "";
}
public String getSlaveCommandSuffix() {
- return (amiType.isUnix() ? ((UnixData) amiType).getSlaveCommandSuffix() : (amiType.isMac() ? ((MacData) amiType).getSlaveCommandSuffix() : ""));
+ return amiType.isSSHAgent() ? ((SSHData) amiType).getSlaveCommandSuffix() : "";
}
public String chooseSubnetId() {
if (StringUtils.isBlank(subnetId)) {
return null;
} else {
- String[] subnetIdList= getSubnetId().split(EC2_RESOURCE_ID_DELIMETERS);
+ String[] subnetIdList = getSubnetId().split(EC2_RESOURCE_ID_DELIMETERS);
// Round-robin subnet selection.
currentSubnetId = subnetIdList[nextSubnet];
@@ -727,22 +1739,36 @@ public String getCurrentSubnetId() {
return currentSubnetId;
}
+ @Deprecated
public boolean getAssociatePublicIp() {
- return associatePublicIp;
+ return AssociateIPStrategy.PUBLIC_IP == associateIPStrategy;
+ }
+
+ @Deprecated
+ @DataBoundSetter
+ public void setAssociatePublicIp(boolean associatePublicIp) {
+ this.associatePublicIp = associatePublicIp;
+ this.associateIPStrategy = AssociateIPStrategy.backwardsCompatible(associatePublicIp);
+ }
+
+ public AssociateIPStrategy getAssociateIPStrategy() {
+ return associateIPStrategy;
}
@Deprecated
@DataBoundSetter
public void setConnectUsingPublicIp(boolean connectUsingPublicIp) {
this.connectUsingPublicIp = connectUsingPublicIp;
- this.connectionStrategy = ConnectionStrategy.backwardsCompatible(this.usePrivateDnsName, this.connectUsingPublicIp, this.associatePublicIp);
+ this.connectionStrategy = ConnectionStrategy.backwardsCompatible(
+ this.usePrivateDnsName, this.connectUsingPublicIp, getAssociatePublicIp());
}
@Deprecated
@DataBoundSetter
public void setUsePrivateDnsName(boolean usePrivateDnsName) {
this.usePrivateDnsName = usePrivateDnsName;
- this.connectionStrategy = ConnectionStrategy.backwardsCompatible(this.usePrivateDnsName, this.connectUsingPublicIp, this.associatePublicIp);
+ this.connectionStrategy = ConnectionStrategy.backwardsCompatible(
+ this.usePrivateDnsName, this.connectUsingPublicIp, getAssociatePublicIp());
}
@Deprecated
@@ -756,8 +1782,9 @@ public boolean isConnectUsingPublicIp() {
}
public List getTags() {
- if (null == tags)
+ if (null == tags) {
return null;
+ }
return Collections.unmodifiableList(tags);
}
@@ -765,6 +1792,15 @@ public String getidleTerminationMinutes() {
return idleTerminationMinutes;
}
+ public boolean getTerminateIdleDuringShutdown() {
+ return terminateIdleDuringShutdown;
+ }
+
+ @DataBoundSetter
+ public void setTerminateIdleDuringShutdown(boolean terminateIdleDuringShutdown) {
+ this.terminateIdleDuringShutdown = terminateIdleDuringShutdown;
+ }
+
public Set getLabelSet() {
if (labelSet == null) {
labelSet = Label.parse(labels);
@@ -801,7 +1837,8 @@ public MinimumNumberOfInstancesTimeRangeConfig getMinimumNumberOfInstancesTimeRa
}
@DataBoundSetter
- public void setMinimumNumberOfInstancesTimeRangeConfig(MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig) {
+ public void setMinimumNumberOfInstancesTimeRangeConfig(
+ MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig) {
this.minimumNumberOfInstancesTimeRangeConfig = minimumNumberOfInstancesTimeRangeConfig;
}
@@ -810,8 +1847,9 @@ public int getInstanceCap() {
}
public int getSpotBlockReservationDuration() {
- if (spotConfig == null)
+ if (spotConfig == null) {
return 0;
+ }
return spotConfig.getSpotBlockReservationDuration();
}
@@ -820,8 +1858,9 @@ public String getSpotBlockReservationDurationStr() {
return "";
} else {
int dur = getSpotBlockReservationDuration();
- if (dur == 0)
+ if (dur == 0) {
return "";
+ }
return String.valueOf(getSpotBlockReservationDuration());
}
}
@@ -835,8 +1874,9 @@ public String getInstanceCapStr() {
}
public String getSpotMaxBidPrice() {
- if (spotConfig == null)
+ if (spotConfig == null) {
return null;
+ }
return SpotConfiguration.normalizeBid(spotConfig.getSpotMaxBidPrice());
}
@@ -846,12 +1886,16 @@ public String getIamInstanceProfile() {
@DataBoundSetter
public void setHostKeyVerificationStrategy(HostKeyVerificationStrategyEnum hostKeyVerificationStrategy) {
- this.hostKeyVerificationStrategy = (hostKeyVerificationStrategy != null) ? hostKeyVerificationStrategy : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT;
+ this.hostKeyVerificationStrategy = (hostKeyVerificationStrategy != null)
+ ? hostKeyVerificationStrategy
+ : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT;
}
@NonNull
public HostKeyVerificationStrategyEnum getHostKeyVerificationStrategy() {
- return hostKeyVerificationStrategy != null ? hostKeyVerificationStrategy : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT;
+ return hostKeyVerificationStrategy != null
+ ? hostKeyVerificationStrategy
+ : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT;
}
@CheckForNull
@@ -884,20 +1928,130 @@ public void setAmiFilters(List amiFilters) {
this.amiFilters = amiFilters;
}
+ @DataBoundSetter
+ public void setAvoidUsingOrphanedNodes(Boolean avoidUsingOrphanedNodes) {
+ this.avoidUsingOrphanedNodes = avoidUsingOrphanedNodes;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getRemoteFS() {
+ return remoteFS;
+ }
+
+ public SpotConfiguration getSpotConfig() {
+ return spotConfig;
+ }
+
+ public String getSecurityGroups() {
+ return securityGroups;
+ }
+
+ public String getJavaPath() {
+ return javaPath;
+ }
+
+ public String getJvmopts() {
+ return jvmopts;
+ }
+
+ public boolean getStopOnTerminate() {
+ return stopOnTerminate;
+ }
+
+ public String getIdleTerminationMinutes() {
+ return idleTerminationMinutes;
+ }
+
+ public String getInitScript() {
+ return initScript;
+ }
+
+ public String getTmpDir() {
+ return tmpDir;
+ }
+
+ public String getUserData() {
+ return userData;
+ }
+
+ public boolean getConnectBySSHProcess() {
+ return connectBySSHProcess;
+ }
+
+ public boolean getConnectUsingPublicIp() {
+ return connectUsingPublicIp;
+ }
+
+ public boolean getDeleteRootOnTermination() {
+ return deleteRootOnTermination;
+ }
+
+ public boolean getUseEphemeralDevices() {
+ return useEphemeralDevices;
+ }
+
+ public boolean getEbsOptimized() {
+ return ebsOptimized;
+ }
+
+ public boolean getMonitoring() {
+ return monitoring;
+ }
+
+ public boolean getT2Unlimited() {
+ return t2Unlimited;
+ }
+
+ public EbsEncryptRootVolume getEbsEncryptRootVolume() {
+ return ebsEncryptRootVolume;
+ }
+
+ public String getCustomDeviceMapping() {
+ return customDeviceMapping;
+ }
+
+ public Tenancy getTenancy() {
+ return tenancy;
+ }
+
+ public ConnectionStrategy getConnectionStrategy() {
+ return connectionStrategy;
+ }
+
+ public boolean getUseDedicatedTenancy() {
+ return useDedicatedTenancy;
+ }
+
+ public int getNextSubnet() {
+ return nextSubnet;
+ }
+
@Override
public String toString() {
- return "SlaveTemplate{" +
- "description='" + description + '\'' +
- ", labels='" + labels + '\'' +
- '}';
+ return "SlaveTemplate{" + "description='" + description + '\'' + ", labels='" + labels + '\'' + '}';
}
public int getMaxTotalUses() {
return maxTotalUses;
}
+ public boolean isAvoidUsingOrphanedNodes() {
+ return avoidUsingOrphanedNodes;
+ }
+
+ public boolean getAvoidUsingOrphanedNodes() {
+ return avoidUsingOrphanedNodes;
+ }
+
public Boolean getMetadataSupported() {
- return metadataSupported;
+ return metadataSupported;
}
public Boolean getMetadataEndpointEnabled() {
@@ -916,11 +2070,18 @@ public Tenancy getTenancyAttribute() {
return tenancy;
}
+ public Boolean getEnclaveEnabled() {
+ return enclaveEnabled;
+ }
+
public DescribableList, NodePropertyDescriptor> getNodeProperties() {
return Objects.requireNonNull(nodeProperties);
}
- public enum ProvisionOptions { ALLOW_CREATE, FORCE_CREATE }
+ public enum ProvisionOptions {
+ ALLOW_CREATE,
+ FORCE_CREATE
+ }
/**
* Provisions a new EC2 agent or starts a previously stopped on-demand instance.
@@ -928,11 +2089,14 @@ public enum ProvisionOptions { ALLOW_CREATE, FORCE_CREATE }
* @return always non-null. This needs to be then added to {@link Hudson#addNode(Node)}.
*/
@NonNull
- public List provision(int number, EnumSet provisionOptions) throws AmazonClientException, IOException {
+ public List provision(int number, EnumSet provisionOptions)
+ throws SdkException, IOException {
final Image image = getImage();
if (this.spotConfig != null) {
- if (provisionOptions.contains(ProvisionOptions.ALLOW_CREATE) || provisionOptions.contains(ProvisionOptions.FORCE_CREATE))
+ if (provisionOptions.contains(ProvisionOptions.ALLOW_CREATE)
+ || provisionOptions.contains(ProvisionOptions.FORCE_CREATE)) {
return provisionSpot(image, number, provisionOptions);
+ }
return Collections.emptyList();
}
return provisionOndemand(image, number, provisionOptions);
@@ -943,11 +2107,10 @@ public List provision(int number, EnumSet pr
*/
private boolean checkInstance(Instance instance) {
for (EC2AbstractSlave node : NodeIterator.nodes(EC2AbstractSlave.class)) {
- if ( (node.getInstanceId().equals(instance.getInstanceId())) &&
- (! (instance.getState().getName().equalsIgnoreCase(InstanceStateName.Stopped.toString())
- ))
- ){
- logInstanceCheck(instance, ". false - found existing corresponding Jenkins agent: " + node.getInstanceId());
+ if ((node.getInstanceId().equals(instance.instanceId()))
+ && (!(instance.state().name().equals(InstanceStateName.STOPPED)))) {
+ logInstanceCheck(
+ instance, ". false - found existing corresponding Jenkins agent: " + node.getInstanceId());
return false;
}
}
@@ -956,102 +2119,116 @@ private boolean checkInstance(Instance instance) {
}
private void logInstanceCheck(Instance instance, String message) {
- logProvisionInfo("checkInstance: " + instance.getInstanceId() + "." + message);
+ logProvisionInfo("checkInstance: " + instance.instanceId() + "." + message);
}
private boolean isSameIamInstanceProfile(Instance instance) {
- return StringUtils.isBlank(getIamInstanceProfile()) ||
- (instance.getIamInstanceProfile() != null &&
- instance.getIamInstanceProfile().getArn().equals(getIamInstanceProfile()));
-
+ return StringUtils.isBlank(getIamInstanceProfile())
+ || (instance.iamInstanceProfile() != null
+ && instance.iamInstanceProfile().arn().equals(getIamInstanceProfile()));
}
- private boolean isTerminatingOrShuttindDown(String instanceStateName) {
- return instanceStateName.equalsIgnoreCase(InstanceStateName.Terminated.toString())
- || instanceStateName.equalsIgnoreCase(InstanceStateName.ShuttingDown.toString());
+ private boolean isTerminatingOrShuttindDown(InstanceStateName instanceStateName) {
+ return instanceStateName.equals(InstanceStateName.TERMINATED)
+ || instanceStateName.equals(InstanceStateName.SHUTTING_DOWN);
}
private void logProvisionInfo(String message) {
LOGGER.info(this + ". " + message);
}
- HashMap> makeRunInstancesRequestAndFilters(Image image, int number, AmazonEC2 ec2) throws IOException {
+ HashMap> makeRunInstancesRequestAndFilters(Image image, int number, Ec2Client ec2)
+ throws IOException {
return makeRunInstancesRequestAndFilters(image, number, ec2, true);
}
@Deprecated
- HashMap> makeRunInstancesRequestAndFilters(int number, AmazonEC2 ec2) throws IOException {
+ HashMap> makeRunInstancesRequestAndFilters(int number, Ec2Client ec2)
+ throws IOException {
return makeRunInstancesRequestAndFilters(getImage(), number, ec2);
}
- HashMap> makeRunInstancesRequestAndFilters(Image image, int number, AmazonEC2 ec2, boolean rotateSubnet) throws IOException {
- String imageId = image.getImageId();
- RunInstancesRequest riRequest = new RunInstancesRequest(imageId, 1, number).withInstanceType(type);
- riRequest.setEbsOptimized(ebsOptimized);
- riRequest.setMonitoring(monitoring);
+ HashMap> makeRunInstancesRequestAndFilters(
+ Image image, int number, Ec2Client ec2, boolean rotateSubnet) throws IOException {
+ String imageId = image.imageId();
+ RunInstancesRequest.Builder riRequestBuilder = RunInstancesRequest.builder()
+ .imageId(image.imageId())
+ .minCount(1)
+ .maxCount(number)
+ .instanceType(type)
+ .ebsOptimized(ebsOptimized)
+ .monitoring(RunInstancesMonitoringEnabled.builder()
+ .enabled(monitoring)
+ .build());
- if (t2Unlimited){
- CreditSpecificationRequest creditRequest = new CreditSpecificationRequest();
- creditRequest.setCpuCredits("unlimited");
- riRequest.setCreditSpecification(creditRequest);
+ if (t2Unlimited) {
+ CreditSpecificationRequest creditRequest =
+ CreditSpecificationRequest.builder().cpuCredits("unlimited").build();
+ riRequestBuilder.creditSpecification(creditRequest);
}
- setupBlockDeviceMappings(image, riRequest.getBlockDeviceMappings());
+ riRequestBuilder.blockDeviceMappings(getBlockDeviceMappings(image));
- if(stopOnTerminate){
- riRequest.setInstanceInitiatedShutdownBehavior(ShutdownBehavior.Stop);
+ if (stopOnTerminate) {
+ riRequestBuilder.instanceInitiatedShutdownBehavior(ShutdownBehavior.STOP);
logProvisionInfo("Setting Instance Initiated Shutdown Behavior : ShutdownBehavior.Stop");
- }else{
- riRequest.setInstanceInitiatedShutdownBehavior(ShutdownBehavior.Terminate);
+ } else {
+ riRequestBuilder.instanceInitiatedShutdownBehavior(ShutdownBehavior.TERMINATE);
logProvisionInfo("Setting Instance Initiated Shutdown Behavior : ShutdownBehavior.Terminate");
}
List diFilters = new ArrayList<>();
- diFilters.add(new Filter("image-id").withValues(imageId));
- diFilters.add(new Filter("instance-type").withValues(type.toString()));
+ diFilters.add(Filter.builder().name("image-id").values(imageId).build());
+ diFilters.add(Filter.builder().name("instance-type").values(type).build());
KeyPair keyPair = getKeyPair(ec2);
- if (keyPair == null){
+ if (keyPair == null) {
logProvisionInfo("Could not retrieve a valid key pair.");
return null;
}
- riRequest.setUserData(Base64.getEncoder().encodeToString(userData.getBytes(StandardCharsets.UTF_8)));
- riRequest.setKeyName(keyPair.getKeyName());
- diFilters.add(new Filter("key-name").withValues(keyPair.getKeyName()));
-
+ riRequestBuilder.userData(Base64.getEncoder().encodeToString(userData.getBytes(StandardCharsets.UTF_8)));
+ riRequestBuilder.keyName(keyPair.getKeyPairInfo().keyName());
+ diFilters.add(Filter.builder()
+ .name("key-name")
+ .values(keyPair.getKeyPairInfo().keyName())
+ .build());
+ Placement.Builder placementBuilder = Placement.builder();
if (StringUtils.isNotBlank(getZone())) {
- Placement placement = new Placement(getZone());
if (getTenancyAttribute().equals(Tenancy.Dedicated)) {
- placement.setTenancy("dedicated");
+ placementBuilder.tenancy("dedicated");
}
- riRequest.setPlacement(placement);
- diFilters.add(new Filter("availability-zone").withValues(getZone()));
- }
-
- if(getTenancyAttribute().equals(Tenancy.Host)){
- Placement placement = new Placement();
- placement.setTenancy("host");
- riRequest.setPlacement(placement);
- diFilters.add(new Filter("tenancy").withValues(placement.getTenancy()));
- }else if(getTenancyAttribute().equals(Tenancy.Default)){
- Placement placement = new Placement();
- placement.setTenancy("default");
- riRequest.setPlacement(placement);
- diFilters.add(new Filter("tenancy").withValues(placement.getTenancy()));
+ riRequestBuilder.placement(placementBuilder.build());
+ diFilters.add(
+ Filter.builder().name("availability-zone").values(getZone()).build());
+ }
+
+ if (getTenancyAttribute().equals(Tenancy.Host)) {
+ placementBuilder.tenancy("host");
+ Placement placement = placementBuilder.build();
+ riRequestBuilder.placement(placement);
+ diFilters.add(Filter.builder()
+ .name("tenancy")
+ .values(placement.tenancyAsString())
+ .build());
+ } else if (getTenancyAttribute().equals(Tenancy.Default)) {
+ placementBuilder.tenancy("default");
+ Placement placement = placementBuilder.build();
+ riRequestBuilder.placement(placement);
+ diFilters.add(Filter.builder()
+ .name("tenancy")
+ .values(placement.tenancyAsString())
+ .build());
}
String subnetId = chooseSubnetId(rotateSubnet);
+ LOGGER.log(Level.FINE, () -> String.format("Chose subnetId %s", subnetId));
- InstanceNetworkInterfaceSpecification net = new InstanceNetworkInterfaceSpecification();
+ InstanceNetworkInterfaceSpecification.Builder netBuilder = InstanceNetworkInterfaceSpecification.builder();
if (StringUtils.isNotBlank(subnetId)) {
- if (getAssociatePublicIp()) {
- net.setSubnetId(subnetId);
- } else {
- riRequest.setSubnetId(subnetId);
- }
+ netBuilder.subnetId(subnetId);
- diFilters.add(new Filter("subnet-id").withValues(subnetId));
+ diFilters.add(Filter.builder().name("subnet-id").values(subnetId).build());
/*
* If we have a subnet ID then we can only use VPC security groups
@@ -1060,136 +2237,195 @@ HashMap> makeRunInstancesRequestAndFilters(Ima
List groupIds = getEc2SecurityGroups(ec2);
if (!groupIds.isEmpty()) {
- if (getAssociatePublicIp()) {
- net.setGroups(groupIds);
- } else {
- riRequest.setSecurityGroupIds(groupIds);
- }
+ netBuilder.groups(groupIds);
- diFilters.add(new Filter("instance.group-id").withValues(groupIds));
+ diFilters.add(Filter.builder()
+ .name("instance.group-id")
+ .values(groupIds)
+ .build());
}
}
} else {
- List groupIds = getSecurityGroupsBy("group-name", securityGroupSet, ec2)
- .getSecurityGroups()
- .stream().map(SecurityGroup::getGroupId)
- .collect(Collectors.toList());
- if (getAssociatePublicIp()) {
- net.setGroups(groupIds);
- } else {
- riRequest.setSecurityGroups(securityGroupSet);
- }
+ List groupIds = getSecurityGroupsBy("group-name", securityGroupSet, ec2).securityGroups().stream()
+ .map(SecurityGroup::groupId)
+ .collect(Collectors.toList());
+ netBuilder.groups(groupIds);
+
if (!groupIds.isEmpty()) {
- diFilters.add(new Filter("instance.group-id").withValues(groupIds));
+ diFilters.add(Filter.builder()
+ .name("instance.group-id")
+ .values(groupIds)
+ .build());
}
}
- net.setAssociatePublicIpAddress(getAssociatePublicIp());
- net.setDeviceIndex(0);
-
- if (getAssociatePublicIp()) {
- riRequest.withNetworkInterfaces(net);
+ switch (getAssociateIPStrategy()) {
+ case PUBLIC_IP:
+ netBuilder.associatePublicIpAddress(true);
+ break;
+ case PRIVATE_IP:
+ netBuilder.associatePublicIpAddress(false);
+ break;
+ case SUBNET:
+ case DEFAULT:
+ break;
}
+ netBuilder.deviceIndex(0);
+ riRequestBuilder.networkInterfaces(netBuilder.build());
+
HashSet instTags = buildTags(EC2Cloud.EC2_SLAVE_TYPE_DEMAND);
for (Tag tag : instTags) {
- diFilters.add(new Filter("tag:" + tag.getKey()).withValues(tag.getValue()));
+ diFilters.add(Filter.builder()
+ .name("tag:" + tag.key())
+ .values(tag.value())
+ .build());
}
if (StringUtils.isNotBlank(getIamInstanceProfile())) {
- riRequest.setIamInstanceProfile(new IamInstanceProfileSpecification().withArn(getIamInstanceProfile()));
+ riRequestBuilder.iamInstanceProfile(IamInstanceProfileSpecification.builder()
+ .arn(getIamInstanceProfile())
+ .build());
}
List