Skip to content

Commit

Permalink
Add support for fleets created through the CreateFleet API (#410)
Browse files Browse the repository at this point in the history
* Added support for the CreateFleet API

* Fixed EC2CreateFleet usage of launch template overrides for retreiving instance type and weight capacity

* Rename all occurances of EC2Fleet to Fleet, and rename CreateFleet to EC2Fleet

* Renaming instances of 'EC2Fleet' to 'Fleet'

* Add permision checks and add missing permissions for EC2Fleets, fleets created by the CreateFleet API

---------

Co-authored-by: Prathibha Datta Kumar <[email protected]>
  • Loading branch information
GavinBurris42 and pdk27 authored Nov 2, 2023
1 parent d79c241 commit 9aaaac1
Show file tree
Hide file tree
Showing 72 changed files with 1,324 additions and 743 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ import hudson.plugins.sshslaves.verifiers.NonVerifyingKeyVerificationStrategy
import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.domains.Domain
import hudson.model.*
import com.amazon.jenkins.ec2fleet.EC2FleetCloud
import com.amazon.jenkins.ec2fleet.FleetCloud
import jenkins.model.Jenkins
// just modify this config other code just logic
Expand Down Expand Up @@ -247,7 +247,7 @@ BasicSSHUserPrivateKey instanceCredentials = new BasicSSHUserPrivateKey(
)
// find detailed information about parameters on plugin config page or
// https://github.com/jenkinsci/ec2-fleet-plugin/blob/master/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetCloud.java
EC2FleetCloud ec2FleetCloud = new EC2FleetCloud(
FleetCloud fleetCloud = new FleetCloud(
"", // fleetCloudName
null,
awsCredentials.id,
Expand Down
16 changes: 8 additions & 8 deletions docs/CONFIGURATION-AS-CODE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

## Properties

### EC2FleetCloud
### FleetCloud

[Definition](https://github.com/jenkinsci/ec2-fleet-plugin/blob/master/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetCloud.java#L156-L179)
[Definition](https://github.com/jenkinsci/ec2-fleet-plugin/blob/master/src/main/java/com/amazon/jenkins/ec2fleet/FleetCloud.java#L156-L179)

| Property | Type | Required | Description |
|----------------------------|---------|------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
Expand Down Expand Up @@ -35,20 +35,20 @@
| cloudStatusIntervalSec | int | no, default ```10``` ||
| noDelayProvision | boolean | no, default ```false``` ||

## EC2FleetLabelCloud
## FleetLabelCloud

More about this type [here](LABEL-BASED-CONFIGURATION.md)

[Definition](https://github.com/jenkinsci/ec2-fleet-plugin/blob/master/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetLabelCloud.java#L123-L145)
[Definition](https://github.com/jenkinsci/ec2-fleet-plugin/blob/master/src/main/java/com/amazon/jenkins/ec2fleet/FleetLabelCloud.java#L123-L145)

## Examples

### EC2FleetCloud (min set of properties)
### FleetCloud (min set of properties)

```yaml
jenkins:
clouds:
- ec2Fleet:
- eC2Fleet:
name: ec2-fleet
computerConnector:
sshConnector:
Expand All @@ -61,12 +61,12 @@ jenkins:
maxSize: 10
```
### EC2FleetCloud (All properties)
### FleetCloud (All properties)
```yaml
jenkins:
clouds:
- ec2Fleet:
- eC2Fleet:
name: ec2-fleet
awsCredentialsId: xx
computerConnector:
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
<name>Steve Nay</name>
<email>[email protected]</email>
</developer>
<developer>
<id>GavinBurris42</id>
<name>Gavin Burris</name>
<email>[email protected]</email>
</developer>
</developers>

<scm>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import hudson.slaves.Cloud;

public abstract class AbstractEC2FleetCloud extends Cloud {
public abstract class AbstractFleetCloud extends Cloud {

protected AbstractEC2FleetCloud(String name) {
protected AbstractFleetCloud(String name) {
super(name);
}

Expand Down
3 changes: 1 addition & 2 deletions src/main/java/com/amazon/jenkins/ec2fleet/CloudNames.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.amazon.jenkins.ec2fleet;

import hudson.slaves.Cloud;
import jenkins.model.Jenkins;
import org.apache.commons.lang.RandomStringUtils;

Expand All @@ -26,7 +25,7 @@ public static String generateUnique(final String proposedName) {
? Jenkins.get().clouds.stream().map(c -> c.name).collect(Collectors.toSet())
: Collections.emptySet();

if (proposedName.equals(EC2FleetCloud.BASE_DEFAULT_FLEET_CLOUD_ID) || proposedName.equals(EC2FleetLabelCloud.BASE_DEFAULT_FLEET_CLOUD_ID) || usedNames.contains(proposedName)) {
if (proposedName.equals(FleetCloud.BASE_DEFAULT_FLEET_CLOUD_ID) || proposedName.equals(FleetLabelCloud.BASE_DEFAULT_FLEET_CLOUD_ID) || usedNames.contains(proposedName)) {
return proposedName + "-" + generateSuffix();
}

Expand Down
10 changes: 5 additions & 5 deletions src/main/java/com/amazon/jenkins/ec2fleet/CloudNanny.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import java.util.logging.Logger;

/**
* {@link CloudNanny} is responsible for periodically running update (i.e. sync-state-with-AWS) cycles for {@link EC2FleetCloud}s.
* {@link CloudNanny} is responsible for periodically running update (i.e. sync-state-with-AWS) cycles for {@link FleetCloud}s.
*/
@Extension
@SuppressWarnings("unused")
Expand All @@ -23,7 +23,7 @@ public class CloudNanny extends PeriodicWork {
private static final Logger LOGGER = Logger.getLogger(CloudNanny.class.getName());

// the map should not hold onto fleet instances to allow deletion of fleets.
private final Map<EC2FleetCloud, AtomicInteger> recurrenceCounters = Collections.synchronizedMap(new WeakHashMap<>());
private final Map<FleetCloud, AtomicInteger> recurrenceCounters = Collections.synchronizedMap(new WeakHashMap<>());

@Override
public long getRecurrencePeriod() {
Expand All @@ -39,8 +39,8 @@ public long getRecurrencePeriod() {
@Override
protected void doRun() {
for (final Cloud cloud : getClouds()) {
if (!(cloud instanceof EC2FleetCloud)) continue;
final EC2FleetCloud fleetCloud = (EC2FleetCloud) cloud;
if (!(cloud instanceof FleetCloud)) continue;
final FleetCloud fleetCloud = (FleetCloud) cloud;

final AtomicInteger recurrenceCounter = getRecurrenceCounter(fleetCloud);

Expand Down Expand Up @@ -70,7 +70,7 @@ private static List<Cloud> getClouds() {
return Jenkins.get().clouds;
}

private AtomicInteger getRecurrenceCounter(EC2FleetCloud fleetCloud) {
private AtomicInteger getRecurrenceCounter(FleetCloud fleetCloud) {
AtomicInteger counter = new AtomicInteger(fleetCloud.getCloudStatusIntervalSec());
// If a counter already exists, return the value, otherwise set the new counter value and return it.
AtomicInteger existing = recurrenceCounters.putIfAbsent(fleetCloud, counter);
Expand Down
56 changes: 28 additions & 28 deletions src/main/java/com/amazon/jenkins/ec2fleet/EC2RetentionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,24 @@
import java.util.logging.Logger;

/**
* The {@link EC2RetentionStrategy} controls when to take {@link EC2FleetNodeComputer} offline, bring it back online, or even to destroy it.
* The {@link EC2RetentionStrategy} controls when to take {@link FleetNodeComputer} offline, bring it back online, or even to destroy it.
*/
public class EC2RetentionStrategy extends RetentionStrategy<EC2FleetNodeComputer> implements ExecutorListener {
public class EC2RetentionStrategy extends RetentionStrategy<FleetNodeComputer> implements ExecutorListener {
private static final Logger LOGGER = Logger.getLogger(EC2RetentionStrategy.class.getName());
private static final int RE_CHECK_IN_A_MINUTE = 1;

/**
* Will be called under {@link hudson.model.Queue#withLock(Runnable)}
*
* @param fc EC2FleetNodeComputer
* @param fc FleetNodeComputer
* @return delay in min before next run
*/
@SuppressFBWarnings(
value = "BC_UNCONFIRMED_CAST",
justification = "to ignore EC2FleetNodeComputer cast")
justification = "to ignore FleetNodeComputer cast")
@Override
public long check(final EC2FleetNodeComputer fc) {
final AbstractEC2FleetCloud cloud = fc.getCloud();
public long check(final FleetNodeComputer fc) {
final AbstractFleetCloud cloud = fc.getCloud();

LOGGER.fine(String.format("Checking if node '%s' is idle ", fc.getName()));

Expand All @@ -41,7 +41,7 @@ public long check(final EC2FleetNodeComputer fc) {
return RE_CHECK_IN_A_MINUTE;
}

// Ensure that the EC2FleetCloud cannot be mutated from under us while
// Ensure that the FleetCloud cannot be mutated from under us while
// we're doing this check
// Ensure nobody provisions onto this node until we've done
// checking
Expand All @@ -58,14 +58,14 @@ public long check(final EC2FleetNodeComputer fc) {
EC2AgentTerminationReason reason;
// Determine the reason for termination from specific to generic use cases.
// Reasoning for checking all cases of termination initiated by the plugin:
// A user-initiated change to cloud configuration creates a new EC2FleetCloud object, erasing class fields containing data like instance IDs to terminate.
// A user-initiated change to cloud configuration creates a new FleetCloud object, erasing class fields containing data like instance IDs to terminate.
// Hence, determine the reasons for termination here using persisted fields for accurate handling of termination.
if (fc.isMarkedForDeletion()) {
reason = EC2AgentTerminationReason.AGENT_DELETED;
} else if (cloud.hasExcessCapacity()) {
reason = EC2AgentTerminationReason.EXCESS_CAPACITY;
} else if (cloud instanceof EC2FleetCloud && !((EC2FleetCloud) cloud).hasUnlimitedUsesForNodes()
&& ((EC2FleetNode)node).getUsesRemaining() <= 0) {
} else if (cloud instanceof FleetCloud && !((FleetCloud) cloud).hasUnlimitedUsesForNodes()

Check warning on line 67 in src/main/java/com/amazon/jenkins/ec2fleet/EC2RetentionStrategy.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 67 is only partially covered, one branch is missing
&& ((FleetNode)node).getUsesRemaining() <= 0) {
reason = EC2AgentTerminationReason.MAX_TOTAL_USES_EXHAUSTED;
} else if (isIdleForTooLong(cloud, fc)) {
reason = EC2AgentTerminationReason.IDLE_FOR_TOO_LONG;
Expand Down Expand Up @@ -97,12 +97,12 @@ public long check(final EC2FleetNodeComputer fc) {
}

@Override
public void start(EC2FleetNodeComputer c) {
public void start(FleetNodeComputer c) {
LOGGER.log(Level.INFO, "Connecting to instance: " + c.getDisplayName());
c.connect(false);
}

private boolean isIdleForTooLong(final AbstractEC2FleetCloud cloud, final Computer computer) {
private boolean isIdleForTooLong(final AbstractFleetCloud cloud, final Computer computer) {
final int idleMinutes = cloud.getIdleMinutes();
if (idleMinutes <= 0) return false;

Expand All @@ -122,25 +122,25 @@ private boolean isIdleForTooLong(final AbstractEC2FleetCloud cloud, final Comput

@Override
public void taskAccepted(Executor executor, Queue.Task task) {
final EC2FleetNodeComputer computer = (EC2FleetNodeComputer) executor.getOwner();
final FleetNodeComputer computer = (FleetNodeComputer) executor.getOwner();
if (computer != null) {
final EC2FleetNode ec2FleetNode = computer.getNode();
if (ec2FleetNode != null) {
final int maxTotalUses = ec2FleetNode.getMaxTotalUses();
final FleetNode fleetNode = computer.getNode();
if (fleetNode != null) {

Check warning on line 128 in src/main/java/com/amazon/jenkins/ec2fleet/EC2RetentionStrategy.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 128 is only partially covered, one branch is missing
final int maxTotalUses = fleetNode.getMaxTotalUses();
if (maxTotalUses <= -1) { // unlimited uses
LOGGER.fine("maxTotalUses set to unlimited (" + maxTotalUses + ") for agent " + computer.getName());
} else { // limited uses
if (ec2FleetNode.getUsesRemaining() > 1) {
ec2FleetNode.decrementUsesRemaining();
LOGGER.info("Agent " + computer.getName() + " has " + ec2FleetNode.getUsesRemaining() + " builds left");
} else if (ec2FleetNode.getUsesRemaining() == 1) { // current task should be the last task for this agent
if (fleetNode.getUsesRemaining() > 1) {
fleetNode.decrementUsesRemaining();
LOGGER.info("Agent " + computer.getName() + " has " + fleetNode.getUsesRemaining() + " builds left");
} else if (fleetNode.getUsesRemaining() == 1) { // current task should be the last task for this agent
LOGGER.info(String.format("maxTotalUses drained - suspending agent %s after current build", computer.getName()));
computer.setAcceptingTasks(false);
ec2FleetNode.decrementUsesRemaining();
fleetNode.decrementUsesRemaining();
} else {
// don't decrement when usesRemaining=0, as -1 has a special meaning.
LOGGER.warning(String.format("Agent %s accepted a task after being suspended!!! MaxTotalUses: %d, uses remaining: %d",
computer.getName(), ec2FleetNode.getMaxTotalUses(), ec2FleetNode.getUsesRemaining()));
computer.getName(), fleetNode.getMaxTotalUses(), fleetNode.getUsesRemaining()));
}
}
}
Expand Down Expand Up @@ -170,15 +170,15 @@ private void postJobAction(final Executor executor, final Throwable throwable) {
TimeUnit.MILLISECONDS.toSeconds(executor.getElapsedTime())));
}

final EC2FleetNodeComputer computer = (EC2FleetNodeComputer) executor.getOwner();
final FleetNodeComputer computer = (FleetNodeComputer) executor.getOwner();
if (computer != null) {
final EC2FleetNode ec2FleetNode = computer.getNode();
if (ec2FleetNode != null) {
final AbstractEC2FleetCloud cloud = ec2FleetNode.getCloud();
final FleetNode fleetNode = computer.getNode();
if (fleetNode != null) {

Check warning on line 176 in src/main/java/com/amazon/jenkins/ec2fleet/EC2RetentionStrategy.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 176 is only partially covered, one branch is missing
final AbstractFleetCloud cloud = fleetNode.getCloud();
if (computer.countBusy() <= 1 && !computer.isAcceptingTasks()) {
LOGGER.info("Calling scheduleToTerminate for node " + ec2FleetNode.getNodeName() + " due to exhausted maxTotalUses.");
LOGGER.info("Calling scheduleToTerminate for node " + fleetNode.getNodeName() + " due to exhausted maxTotalUses.");
// Schedule instance for termination even if it breaches minSize and minSpareSize constraints
cloud.scheduleToTerminate(ec2FleetNode.getNodeName(), true, EC2AgentTerminationReason.MAX_TOTAL_USES_EXHAUSTED);
cloud.scheduleToTerminate(fleetNode.getNodeName(), true, EC2AgentTerminationReason.MAX_TOTAL_USES_EXHAUSTED);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,31 @@
import java.util.logging.Logger;

/**
* The {@link EC2FleetAutoResubmitComputerLauncher} is responsible for controlling:
* * how {@link EC2FleetNodeComputer}s are launched
* * how {@link EC2FleetNodeComputer}s connect to agents {@link EC2FleetNode}
* The {@link FleetAutoResubmitComputerLauncher} is responsible for controlling:
* * how {@link FleetNodeComputer}s are launched
* * how {@link FleetNodeComputer}s connect to agents {@link FleetNode}
*
* This is wrapper for {@link ComputerLauncher} to get notification when agent was disconnected
* and automatically resubmit {@link hudson.model.Queue.Task} if reason is unexpected termination
* which usually means EC2 instance was interrupted.
* <p>
* This is optional feature, it's enabled by default, but could be disabled by
* {@link EC2FleetCloud#isDisableTaskResubmit()}
* {@link FleetCloud#isDisableTaskResubmit()}
*/
@SuppressWarnings("WeakerAccess")
@ThreadSafe
public class EC2FleetAutoResubmitComputerLauncher extends DelegatingComputerLauncher {
public class FleetAutoResubmitComputerLauncher extends DelegatingComputerLauncher {

private static final Level LOG_LEVEL = Level.INFO;
private static final Logger LOGGER = Logger.getLogger(EC2FleetAutoResubmitComputerLauncher.class.getName());
private static final Logger LOGGER = Logger.getLogger(FleetAutoResubmitComputerLauncher.class.getName());

/**
* Delay which will be applied when job {@link Queue#scheduleInternal(Queue.Task, int, List)}
* rescheduled after offline
*/
private static final int RESCHEDULE_QUIET_PERIOD_SEC = 10;

public EC2FleetAutoResubmitComputerLauncher(final ComputerLauncher launcher) {
public FleetAutoResubmitComputerLauncher(final ComputerLauncher launcher) {
super(launcher);
}

Expand All @@ -72,14 +72,14 @@ public EC2FleetAutoResubmitComputerLauncher(final ComputerLauncher launcher) {
*/
@SuppressFBWarnings(
value = "BC_UNCONFIRMED_CAST",
justification = "to ignore EC2FleetNodeComputer cast")
justification = "to ignore FleetNodeComputer cast")
@Override
public void afterDisconnect(final SlaveComputer computer, final TaskListener listener) {
// according to jenkins docs could be null in edge cases, check ComputerLauncher.afterDisconnect
if (computer == null) return;

// in some multi-thread edge cases cloud could be null for some time, just be ok with that
final AbstractEC2FleetCloud cloud = ((EC2FleetNodeComputer) computer).getCloud();
final AbstractFleetCloud cloud = ((FleetNodeComputer) computer).getCloud();
if (cloud == null) {
LOGGER.warning("Cloud is null for computer " + computer.getDisplayName()
+ ". This should be autofixed in a few minutes, if not please create an issue for the plugin");
Expand Down
Loading

0 comments on commit 9aaaac1

Please sign in to comment.