Skip to content

Commit 9617537

Browse files
Add backwards compatibility for executor scaling feature (#424)
* Reverted small name changes in comments and documentation * Reverted remaming of EC2Fleet to Fleet * Reverted EC2Fleet to Fleet name changes. Changed EC2Fleet to EC2EC2Fleet * Reverted additional EC2Fleet to Fleet changes * Reverted EC2Fleet name change in EC2FleetLabelCloudConfigurationAsCodeTest * Fixed setting of ExcessCapacityTerminationPolicy in modify fleet request * Added backwards compatability for Executor Scaling feature for EC2FleetCloud * Revised tests with backwards compatability changes * Updated CasC documentation * Updated CloudNanny cloud reaplcement for better testability. Added new test for cloud replacement when no Scaler is present
1 parent cd67586 commit 9617537

13 files changed

+216
-136
lines changed

docs/CONFIGURATION-AS-CODE.md

+23-4
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,32 @@ jenkins:
8585
numExecutors: 12
8686
addNodeOnlyIfRunning: true
8787
restrictUsage: true
88-
executorScaler:
89-
nodeHardwareScaler:
90-
memoryGiBPerExecutor: 2
91-
vCpuPerExecutor: 3
88+
executorScaler: "noScaler"
9289
initOnlineTimeoutSec: 181
9390
initOnlineCheckIntervalSec: 13
9491
cloudStatusIntervalSec: 11
9592
disableTaskResubmit: true
9693
noDelayProvision: true
9794
```
95+
96+
### EC2FleetCloud (Node Hardware Scaling)
97+
98+
```yaml
99+
jenkins:
100+
clouds:
101+
- eC2Fleet:
102+
name: ec2-fleet
103+
computerConnector:
104+
sshConnector:
105+
credentialsId: cred
106+
sshHostKeyVerificationStrategy:
107+
NonVerifyingKeyVerificationStrategy
108+
region: us-east-2
109+
fleet: my-fleet
110+
minSize: 1
111+
maxSize: 10
112+
executorScaler:
113+
nodeHardwareScaler:
114+
memoryGiBPerExecutor: 2
115+
vCpuPerExecutor: 3
116+
```

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

+27-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import hudson.slaves.Cloud;
66
import jenkins.model.Jenkins;
77

8+
import java.io.IOException;
89
import java.util.Collections;
910
import java.util.List;
1011
import java.util.Map;
@@ -51,6 +52,7 @@ protected void doRun() {
5152
recurrenceCounter.set(fleetCloud.getCloudStatusIntervalSec());
5253

5354
try {
55+
updateCloudWithScaler(getClouds(), fleetCloud);
5456
// Update the cluster states
5557
fleetCloud.update();
5658
} catch (Exception e) {
@@ -66,10 +68,34 @@ protected void doRun() {
6668
*
6769
* @return basic java list
6870
*/
69-
private static List<Cloud> getClouds() {
71+
private static Jenkins.CloudList getClouds() {
7072
return Jenkins.get().clouds;
7173
}
7274

75+
private void updateCloudWithScaler(Jenkins.CloudList clouds, EC2FleetCloud oldCloud) throws IOException {
76+
if(oldCloud.getExecutorScaler() != null) return;
77+
78+
EC2FleetCloud.ExecutorScaler scaler = oldCloud.isScaleExecutorsByWeight() ? new EC2FleetCloud.WeightedScaler() :
79+
new EC2FleetCloud.NoScaler();
80+
scaler.withNumExecutors(oldCloud.getNumExecutors());
81+
EC2FleetCloud fleetCloudWithScaler = createCloudWithScaler(oldCloud, scaler);
82+
clouds.replace(oldCloud, fleetCloudWithScaler);
83+
Jenkins.get().save();
84+
}
85+
86+
private EC2FleetCloud createCloudWithScaler(EC2FleetCloud oldCloud, EC2FleetCloud.ExecutorScaler scaler) {
87+
return new EC2FleetCloud(oldCloud.getDisplayName(), oldCloud.getAwsCredentialsId(),
88+
oldCloud.getAwsCredentialsId(), oldCloud.getRegion(), oldCloud.getEndpoint(), oldCloud.getFleet(),
89+
oldCloud.getLabelString(), oldCloud.getFsRoot(), oldCloud.getComputerConnector(),
90+
oldCloud.isPrivateIpUsed(), oldCloud.isAlwaysReconnect(), oldCloud.getIdleMinutes(),
91+
oldCloud.getMinSize(), oldCloud.getMaxSize(), oldCloud.getMinSpareSize(), oldCloud.getNumExecutors(),
92+
oldCloud.isAddNodeOnlyIfRunning(), oldCloud.isRestrictUsage(),
93+
String.valueOf(oldCloud.getMaxTotalUses()), oldCloud.isDisableTaskResubmit(),
94+
oldCloud.getInitOnlineTimeoutSec(), oldCloud.getInitOnlineCheckIntervalSec(),
95+
oldCloud.getCloudStatusIntervalSec(), oldCloud.isNoDelayProvision(),
96+
oldCloud.isScaleExecutorsByWeight(), scaler);
97+
}
98+
7399
private AtomicInteger getRecurrenceCounter(EC2FleetCloud fleetCloud) {
74100
AtomicInteger counter = new AtomicInteger(fleetCloud.getCloudStatusIntervalSec());
75101
// If a counter already exists, return the value, otherwise set the new counter value and return it.

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

+7
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ public class EC2FleetCloud extends AbstractEC2FleetCloud {
119119
private final int numExecutors;
120120
private final boolean addNodeOnlyIfRunning;
121121
private final boolean restrictUsage;
122+
private final boolean scaleExecutorsByWeight;
122123
private final ExecutorScaler executorScaler;
123124
private final Integer initOnlineTimeoutSec;
124125
private final Integer initOnlineCheckIntervalSec;
@@ -183,6 +184,7 @@ public EC2FleetCloud(@Nonnull final String name,
183184
final Integer initOnlineCheckIntervalSec,
184185
final Integer cloudStatusIntervalSec,
185186
final boolean noDelayProvision,
187+
final boolean scaleExecutorsByWeight,
186188
final ExecutorScaler executorScaler) {
187189
super(StringUtils.isNotBlank(name) ? name : CloudNames.generateUnique(BASE_DEFAULT_FLEET_CLOUD_ID));
188190
init();
@@ -212,6 +214,7 @@ public EC2FleetCloud(@Nonnull final String name,
212214
this.initOnlineCheckIntervalSec = initOnlineCheckIntervalSec;
213215
this.cloudStatusIntervalSec = cloudStatusIntervalSec;
214216
this.noDelayProvision = noDelayProvision;
217+
this.scaleExecutorsByWeight = scaleExecutorsByWeight;
215218
this.executorScaler = executorScaler == null ? new NoScaler().withNumExecutors(this.numExecutors) :
216219
executorScaler.withNumExecutors(this.numExecutors);
217220
if (fleet != null) {
@@ -317,6 +320,10 @@ public int getNumExecutors() {
317320
return numExecutors;
318321
}
319322

323+
public boolean isScaleExecutorsByWeight() {
324+
return scaleExecutorsByWeight;
325+
}
326+
320327
public ExecutorScaler getExecutorScaler() {
321328
return this.executorScaler;
322329
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public void should_successfully_resubmit_freestyle_task() throws Exception {
8181
null, "fId", "momo", null, new LocalComputerConnector(j), false, false,
8282
0, 0, 10, 0, 1, false, true,
8383
"-1", false, 0, 0,
84-
10, false, noScaling);
84+
10, false, false, noScaling);
8585
j.jenkins.clouds.add(cloud);
8686

8787
List<QueueTaskFuture> rs = enqueTask(1);
@@ -117,7 +117,7 @@ public void should_successfully_resubmit_parametrized_task() throws Exception {
117117
null, "fId", "momo", null, new LocalComputerConnector(j), false, false,
118118
0, 0, 10, 0, 1, false, true,
119119
"-1", false, 0, 0,
120-
10, false, noScaling);
120+
10, false, false, noScaling);
121121
j.jenkins.clouds.add(cloud);
122122

123123
List<QueueTaskFuture> rs = new ArrayList<>();
@@ -172,7 +172,7 @@ public void should_not_resubmit_if_disabled() throws Exception {
172172
EC2FleetCloud cloud = new EC2FleetCloud("TestCloud", "credId", null, "region",
173173
null, "fId", "momo", null, new LocalComputerConnector(j), false, false,
174174
0, 0, 10, 0, 1, false, true,
175-
"-1", true, 0, 0, 10, false, noScaling);
175+
"-1", true, 0, 0, 10, false, false, noScaling);
176176
j.jenkins.clouds.add(cloud);
177177

178178
List<QueueTaskFuture> rs = enqueTask(1);

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

+11-11
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public void isUnique_true() {
1717
"test-label", null, null, false, false,
1818
0, 0, 0, 0, 0, true, false,
1919
"-1", false, 0, 0,
20-
10, false, noScaling));
20+
10, false, false, noScaling));
2121

2222
Assert.assertTrue(CloudNames.isUnique("TestCloud"));
2323
}
@@ -28,7 +28,7 @@ public void isUnique_false() {
2828
"test-label", null, null, false, false,
2929
0, 0, 0, 0, 0, true, false,
3030
"-1", false, 0, 0,
31-
10, false, noScaling));
31+
10, false, false, noScaling));
3232

3333
Assert.assertFalse(CloudNames.isUnique("SomeDefaultName"));
3434
}
@@ -39,13 +39,13 @@ public void isDuplicated_false() {
3939
"test-label", null, null, false, false,
4040
0, 0, 0, 0, 0, true, false,
4141
"-1", false, 0, 0,
42-
10, false, noScaling));
42+
10, false, false, noScaling));
4343

4444
j.jenkins.clouds.add(new EC2FleetCloud("TestCloud2", null, null, null, null, null,
4545
"test-label", null, null, false, false,
4646
0, 0, 0, 0, 0, true, false,
4747
"-1", false, 0, 0,
48-
10, false, noScaling));
48+
10, false, false, noScaling));
4949

5050
Assert.assertFalse(CloudNames.isDuplicated("TestCloud"));
5151
}
@@ -56,13 +56,13 @@ public void isDuplicated_true() {
5656
"test-label", null, null, false, false,
5757
0, 0, 0, 0, 0, true, false,
5858
"-1", false, 0, 0,
59-
10, false, noScaling));
59+
10, false, false, noScaling));
6060

6161
j.jenkins.clouds.add(new EC2FleetCloud("TestCloud", null, null, null, null, null,
6262
"test-label", null, null, false, false,
6363
0, 0, 0, 0, 0, true, false,
6464
"-1", false, 0, 0,
65-
10, false, noScaling));
65+
10, false, false, noScaling));
6666

6767
Assert.assertTrue(CloudNames.isDuplicated("TestCloud"));
6868
}
@@ -78,7 +78,7 @@ public void generateUnique_addsSuffixOnlyWhenNeeded() {
7878
"test-label", null, null, false, false,
7979
0, 0, 0, 0, 0, true, false,
8080
"-1", false, 0, 0,
81-
10, false, noScaling));
81+
10, false, false, noScaling));
8282

8383
Assert.assertEquals("UniqueCloud", CloudNames.generateUnique("UniqueCloud"));
8484
}
@@ -89,13 +89,13 @@ public void generateUnique_addsSuffixCorrectly() {
8989
"test-label", null, null, false, false,
9090
0, 0, 0, 0, 0, true, false,
9191
"-1", false, 0, 0,
92-
10, false, noScaling));
92+
10, false, false, noScaling));
9393

9494
j.jenkins.clouds.add(new EC2FleetCloud("UniqueCloud-1", null, null, null, null, null,
9595
"test-label", null, null, false, false,
9696
0, 0, 0, 0, 0, true, false,
9797
"-1", false, 0, 0,
98-
10, false, noScaling));
98+
10, false, false, noScaling));
9999

100100
String actual = CloudNames.generateUnique("UniqueCloud");
101101
Assert.assertTrue(actual.length() == ("UniqueCloud".length() + CloudNames.SUFFIX_LENGTH + 1));
@@ -108,7 +108,7 @@ public void generateUnique_emptyStringInConstructor() {
108108
"test-label", null, null, false, false,
109109
0, 0, 0, 0, 0, true, false,
110110
"-1", false, 0, 0,
111-
10, false, noScaling);
111+
10, false, false, noScaling);
112112

113113
EC2FleetLabelCloud fleetLabelCloud = new EC2FleetLabelCloud("", null, null,
114114
null, null, new LocalComputerConnector(j), false, false,
@@ -128,7 +128,7 @@ public void generateUnique_nonEmptyStringInConstructor() {
128128
"test-label", null, null, false, false,
129129
0, 0, 0, 0, 0, true, false,
130130
"-1", false, 0, 0,
131-
10, false, noScaling);
131+
10, false, false, noScaling);
132132

133133
EC2FleetLabelCloud fleetLabelCloud = new EC2FleetLabelCloud("UniqueLabelCloud", null, null,
134134
null, null, new LocalComputerConnector(j), false, false,

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

+38-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.amazon.jenkins.ec2fleet;
22

3+
import com.amazon.jenkins.ec2fleet.fleet.EC2Fleet;
4+
import com.amazon.jenkins.ec2fleet.fleet.EC2Fleets;
35
import hudson.slaves.Cloud;
6+
import jenkins.model.Jenkins;
47
import org.junit.Before;
58
import org.junit.Test;
69
import org.junit.runner.RunWith;
@@ -10,32 +13,30 @@
1013
import org.powermock.modules.junit4.PowerMockRunner;
1114
import org.powermock.reflect.Whitebox;
1215

13-
import java.util.ArrayList;
1416
import java.util.Collections;
15-
import java.util.List;
1617
import java.util.Map;
1718
import java.util.WeakHashMap;
1819
import java.util.concurrent.atomic.AtomicInteger;
1920

2021
import static org.junit.Assert.assertEquals;
21-
import static org.mockito.Mockito.atLeastOnce;
22-
import static org.mockito.Mockito.mock;
23-
import static org.mockito.Mockito.verify;
24-
import static org.mockito.Mockito.verifyNoMoreInteractions;
25-
import static org.mockito.Mockito.verifyZeroInteractions;
26-
import static org.mockito.Mockito.when;
22+
import static org.mockito.Mockito.*;
2723

2824
@RunWith(PowerMockRunner.class)
29-
@PrepareForTest(CloudNanny.class)
25+
@PrepareForTest({CloudNanny.class, Jenkins.class, EC2Fleets.class})
3026
public class CloudNannyTest {
27+
@Mock
28+
private Jenkins jenkins;
29+
30+
@Mock
31+
private EC2Fleet ec2Fleet;
3132

3233
@Mock
3334
private EC2FleetCloud cloud1;
3435

3536
@Mock
3637
private EC2FleetCloud cloud2;
3738

38-
private List<Cloud> clouds = new ArrayList<>();
39+
private Jenkins.CloudList clouds = new Jenkins.CloudList();
3940

4041
private FleetStateStats stats1 = new FleetStateStats(
4142
"f1", 1, new FleetStateStats.State(true, false, "a"), Collections.emptySet(), Collections.<String, Double>emptyMap());
@@ -55,6 +56,15 @@ public void before() throws Exception {
5556
PowerMockito.mockStatic(CloudNanny.class);
5657
PowerMockito.when(CloudNanny.class, "getClouds").thenReturn(clouds);
5758

59+
PowerMockito.mockStatic(Jenkins.class);
60+
PowerMockito.when(Jenkins.get()).thenReturn(jenkins);
61+
62+
PowerMockito.mockStatic(EC2Fleets.class);
63+
when(EC2Fleets.get(anyString())).thenReturn(ec2Fleet);
64+
PowerMockito.when(ec2Fleet.getState(anyString(), anyString(), anyString(), anyString()))
65+
.thenReturn(new FleetStateStats("", 0, FleetStateStats.State.active(),
66+
Collections.<String>emptySet(), Collections.<String, Double>emptyMap()));
67+
5868
when(cloud1.getLabelString()).thenReturn("a");
5969
when(cloud2.getLabelString()).thenReturn("");
6070
when(cloud1.getFleet()).thenReturn("f1");
@@ -187,4 +197,22 @@ public void updateOnlyOneCloud() {
187197
assertEquals(1, recurrenceCounter1.get());
188198
assertEquals(cloud2.getCloudStatusIntervalSec(), recurrenceCounter2.get());
189199
}
200+
201+
@Test
202+
public void doRun_updatesCloudsWithScaler_whenScalerIsNull() {
203+
when(cloud1.isScaleExecutorsByWeight()).thenReturn(true);
204+
when(cloud2.isScaleExecutorsByWeight()).thenReturn(false);
205+
206+
clouds.add(cloud1);
207+
clouds.add(cloud2);
208+
CloudNanny cloudNanny = getMockCloudNannyInstance();
209+
210+
cloudNanny.doRun();
211+
212+
cloud1 = (EC2FleetCloud) clouds.get(0);
213+
cloud2 = (EC2FleetCloud) clouds.get(1);
214+
215+
assertEquals(EC2FleetCloud.WeightedScaler.class, cloud1.getExecutorScaler().getClass());
216+
assertEquals(EC2FleetCloud.NoScaler.class, cloud2.getExecutorScaler().getClass());
217+
}
190218
}

0 commit comments

Comments
 (0)