Skip to content

Commit 6bce254

Browse files
pdk27cjerad
andauthored
[Fix] Fix computation of excess workload and available capacity (#371)
* [Fix] Fix computation of excess workload and available capacity #322 #359 * Update src/main/java/com/amazon/jenkins/ec2fleet/NoDelayProvisionStrategy.java Co-authored-by: Jerad C <[email protected]> --------- Co-authored-by: Jerad C <[email protected]>
1 parent 36ed713 commit 6bce254

File tree

2 files changed

+63
-34
lines changed

2 files changed

+63
-34
lines changed

src/main/java/com/amazon/jenkins/ec2fleet/NoDelayProvisionStrategy.java

+35-34
Original file line numberDiff line numberDiff line change
@@ -30,63 +30,64 @@ public NodeProvisioner.StrategyDecision apply(final NodeProvisioner.StrategyStat
3030
final Label label = strategyState.getLabel();
3131

3232
final LoadStatistics.LoadStatisticsSnapshot snapshot = strategyState.getSnapshot();
33-
final int availableCapacity =
34-
snapshot.getAvailableExecutors() // live executors
35-
+ snapshot.getConnectingExecutors() // executors present but not yet connected
36-
+ strategyState.getPlannedCapacitySnapshot() // capacity added by previous strategies from previous rounds
37-
+ strategyState.getAdditionalPlannedCapacity(); // capacity added by previous strategies _this round_
38-
39-
int currentDemand = snapshot.getQueueLength() - availableCapacity;
40-
LOGGER.log(currentDemand < 1 ? Level.FINE : Level.INFO,
41-
"label [{0}]: currentDemand {1} availableCapacity {2} (availableExecutors {3} connectingExecutors {4} plannedCapacitySnapshot {5} additionalPlannedCapacity {6})",
42-
new Object[]{label, currentDemand, availableCapacity, snapshot.getAvailableExecutors(),
43-
snapshot.getConnectingExecutors(), strategyState.getPlannedCapacitySnapshot(),
44-
strategyState.getAdditionalPlannedCapacity()});
45-
46-
for (final Cloud cloud : getClouds()) {
47-
if (currentDemand < 1) {
48-
LOGGER.log(Level.FINE, "label [{0}]: currentDemand is less than 1, not provisioning", label);
33+
final int availableCapacity = snapshot.getAvailableExecutors() // available executors
34+
+ strategyState.getPlannedCapacitySnapshot() // capacity added by previous strategies from previous rounds
35+
+ strategyState.getAdditionalPlannedCapacity(); // capacity added by previous strategies _this round_
36+
37+
int qLen = snapshot.getQueueLength();
38+
int excessWorkload = qLen - availableCapacity;
39+
LOGGER.log(Level.FINE, "label [{0}]: queueLength {1} availableCapacity {2} (availableExecutors {3} plannedCapacitySnapshot {4} additionalPlannedCapacity {5})",
40+
new Object[]{label, qLen, availableCapacity, snapshot.getAvailableExecutors(),
41+
strategyState.getPlannedCapacitySnapshot(), strategyState.getAdditionalPlannedCapacity()});
42+
43+
if (excessWorkload <= 0) {
44+
LOGGER.log(Level.INFO, "label [{0}]: No excess workload, provisioning not needed.", label);
45+
return NodeProvisioner.StrategyDecision.PROVISIONING_COMPLETED;
46+
}
47+
48+
for (final Cloud c : getClouds()) {
49+
if (excessWorkload < 1) {
4950
break;
5051
}
5152

52-
if (!(cloud instanceof EC2FleetCloud)) {
53+
if (!(c instanceof EC2FleetCloud)) {
5354
LOGGER.log(Level.FINE, "label [{0}]: cloud {1} is not an EC2FleetCloud, continuing...",
54-
new Object[]{label, cloud.getDisplayName()});
55+
new Object[]{label, c.getDisplayName()});
5556
continue;
5657
}
5758

5859
Cloud.CloudState cloudState = new Cloud.CloudState(label, strategyState.getAdditionalPlannedCapacity());
59-
if (!cloud.canProvision(cloudState)) {
60+
if (!c.canProvision(cloudState)) {
6061
LOGGER.log(Level.INFO, "label [{0}]: cloud {1} can not provision for this label, continuing...",
61-
new Object[]{label, cloud.getDisplayName()});
62+
new Object[]{label, c.getDisplayName()});
6263
continue;
6364
}
6465

65-
final EC2FleetCloud ec2 = (EC2FleetCloud) cloud;
66-
if (!ec2.isNoDelayProvision()) {
66+
if (!((EC2FleetCloud) c).isNoDelayProvision()) {
6767
LOGGER.log(Level.FINE, "label [{0}]: cloud {1} does not use No Delay Provision Strategy, continuing...",
68-
new Object[]{label, cloud.getDisplayName()});
68+
new Object[]{label, c.getDisplayName()});
6969
continue;
7070
}
7171

72-
LOGGER.log(Level.INFO, "label [{0}]: cloud {1} can provision for this label",
73-
new Object[]{label, cloud.getDisplayName()});
74-
final Collection<NodeProvisioner.PlannedNode> plannedNodes = cloud.provision(cloudState, currentDemand);
75-
for (NodeProvisioner.PlannedNode plannedNode : plannedNodes) {
76-
currentDemand -= plannedNode.numExecutors;
72+
LOGGER.log(Level.FINE, "label [{0}]: cloud {1} can provision for this label",
73+
new Object[]{label, c.getDisplayName()});
74+
final Collection<NodeProvisioner.PlannedNode> plannedNodes = c.provision(cloudState, excessWorkload);
75+
for (NodeProvisioner.PlannedNode pn : plannedNodes) {
76+
excessWorkload -= pn.numExecutors;
77+
LOGGER.log(Level.INFO, "Started provisioning {0} from {1} with {2,number,integer} "
78+
+ "executors. Remaining excess workload: {3,number,#.###}",
79+
new Object[]{pn.displayName, c.name, pn.numExecutors, excessWorkload});
7780
}
78-
LOGGER.log(Level.FINE, "Planned {0} new nodes", plannedNodes.size());
7981
strategyState.recordPendingLaunches(plannedNodes);
80-
LOGGER.log(Level.FINE, "After provisioning currentDemand={0}", new Object[]{currentDemand});
8182
}
8283

83-
if (currentDemand < 1) {
84-
LOGGER.log(Level.FINE, "Provisioning completed");
85-
return NodeProvisioner.StrategyDecision.PROVISIONING_COMPLETED;
86-
} else {
84+
if (excessWorkload > 0) {
8785
LOGGER.log(Level.FINE, "Provisioning not complete, consulting remaining strategies");
8886
return NodeProvisioner.StrategyDecision.CONSULT_REMAINING_STRATEGIES;
8987
}
88+
89+
LOGGER.log(Level.FINE, "Provisioning completed");
90+
return NodeProvisioner.StrategyDecision.PROVISIONING_COMPLETED;
9091
}
9192

9293
// Visible for testing

src/test/java/com/amazon/jenkins/ec2fleet/NoDelayProvisionStrategyTest.java

+28
Original file line numberDiff line numberDiff line change
@@ -221,4 +221,32 @@ public void givenEC2Clouds_shouldReduceAsAmountOfExecutors() {
221221
verify(ec2FleetCloud1, times(1)).provision(any(Cloud.CloudState.class), eq(2));
222222
}
223223

224+
@Test
225+
public void givenPlannedCapacity_shouldComputeExcessWorkloadCorrectly() {
226+
when(snapshot.getQueueLength()).thenReturn(6);
227+
when(snapshot.getAvailableExecutors()).thenReturn(0);
228+
when(state.getPlannedCapacitySnapshot()).thenReturn(3);
229+
when(state.getLabel()).thenReturn(label);
230+
231+
final EC2FleetCloud ec2FleetCloud1 = mock(EC2FleetCloud.class);
232+
clouds.add(ec2FleetCloud1);
233+
when(ec2FleetCloud1.canProvision(any(Cloud.CloudState.class))).thenReturn(true);
234+
when(ec2FleetCloud1.isNoDelayProvision()).thenReturn(true);
235+
final NodeProvisioner.PlannedNode plannedNode1 = new NodeProvisioner.PlannedNode("fc1-0", new CompletableFuture<>(), 2);
236+
when(ec2FleetCloud1.provision(any(Cloud.CloudState.class), anyInt())).thenReturn(Arrays.asList(plannedNode1));
237+
238+
final EC2FleetCloud ec2FleetCloud2 = mock(EC2FleetCloud.class);
239+
clouds.add(ec2FleetCloud2);
240+
when(ec2FleetCloud2.canProvision(any(Cloud.CloudState.class))).thenReturn(true);
241+
when(ec2FleetCloud2.isNoDelayProvision()).thenReturn(true);
242+
final NodeProvisioner.PlannedNode plannedNode2 = new NodeProvisioner.PlannedNode("fc2-0", new CompletableFuture<>(), 1);
243+
when(ec2FleetCloud2.provision(any(Cloud.CloudState.class), anyInt())).thenReturn(Arrays.asList(plannedNode2));
244+
245+
final NodeProvisioner.StrategyDecision decision = strategy.apply(state);
246+
247+
Assert.assertEquals(NodeProvisioner.StrategyDecision.PROVISIONING_COMPLETED, decision);
248+
verify(ec2FleetCloud1, times(1)).provision(any(Cloud.CloudState.class), eq(3));
249+
verify(ec2FleetCloud2, times(1)).provision(any(Cloud.CloudState.class), eq(1));
250+
}
251+
224252
}

0 commit comments

Comments
 (0)