Skip to content

Commit 4439803

Browse files
Merge pull request #492 from gbhat618/add-max-run-duration-to-vm-provisioning
Add spotVM and maxRunDuration feature to VM provisioning
2 parents 7ebe780 + ea9c1df commit 4439803

File tree

29 files changed

+948
-36
lines changed

29 files changed

+948
-36
lines changed

src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,12 @@
3737
import com.google.api.services.compute.model.Zone;
3838
import com.google.cloud.graphite.platforms.plugin.client.ClientFactory;
3939
import com.google.cloud.graphite.platforms.plugin.client.ComputeClient;
40+
import com.google.common.annotations.VisibleForTesting;
4041
import com.google.common.base.Strings;
4142
import com.google.jenkins.plugins.computeengine.client.ClientUtil;
43+
import com.google.jenkins.plugins.computeengine.config.PreemptibleVm;
44+
import com.google.jenkins.plugins.computeengine.config.ProvisioningType;
45+
import com.google.jenkins.plugins.computeengine.config.Standard;
4246
import com.google.jenkins.plugins.computeengine.ssh.GoogleKeyCredential;
4347
import com.google.jenkins.plugins.computeengine.ssh.GoogleKeyPair;
4448
import com.google.jenkins.plugins.computeengine.ssh.GooglePrivateKey;
@@ -117,7 +121,7 @@ public class InstanceConfiguration implements Describable<InstanceConfiguration>
117121
private String machineType;
118122
private String numExecutorsStr;
119123
private String startupScript;
120-
private boolean preemptible;
124+
private ProvisioningType provisioningType;
121125
private String minCpuPlatform;
122126
private String labels;
123127
private String runAsUser;
@@ -164,6 +168,11 @@ public class InstanceConfiguration implements Describable<InstanceConfiguration>
164168
@Setter(AccessLevel.PROTECTED)
165169
protected transient ComputeEngineCloud cloud;
166170

171+
/** @deprecated Use {@link #provisioningType} instead. */
172+
@SuppressWarnings("DeprecatedIsStillUsed")
173+
@Deprecated
174+
private transient boolean preemptible;
175+
167176
private static List<Metadata.Items> mergeMetadataItems(List<Metadata.Items> winner, List<Metadata.Items> loser) {
168177
if (loser == null) {
169178
loser = new ArrayList<Metadata.Items>();
@@ -233,6 +242,24 @@ public void setCreateSnapshot(boolean createSnapshot) {
233242
this.createSnapshot = createSnapshot && this.oneShot;
234243
}
235244

245+
/**
246+
* This setter is kept only to provide JCasC compatibility, don't use for any other.
247+
* Although JCasC is not "required" to keep compatibility, but in this case,
248+
* as it is very low effort to keep the compatibility, we have decided to keep it.
249+
* <p>
250+
* Previously, JCasC syntax would be {@code preemptible: true}, going forward instead should be done as,
251+
* {@code provisioningType: preemptibleVm}
252+
* <p>
253+
* Currently only caller is, JCasC configurators if the bundle is having `preemptible` field defined in it.
254+
* Consider deleting it in future (perhaps after a year or so)
255+
*/
256+
@DataBoundSetter
257+
public void setPreemptible(boolean preemptible) {
258+
if (preemptible) {
259+
this.provisioningType = new PreemptibleVm();
260+
}
261+
}
262+
236263
public static Integer intOrDefault(String toParse, Integer defaultTo) {
237264
Integer toReturn;
238265
try {
@@ -354,6 +381,10 @@ protected Object readResolve() {
354381
this.networkInterfaceIpStackMode = new NetworkInterfaceSingleStack(externalAddress);
355382
this.externalAddress = null;
356383
}
384+
/* deprecating `preemptible` in favor of extensible `provisioningType` */
385+
if (preemptible && provisioningType == null) {
386+
provisioningType = new PreemptibleVm();
387+
}
357388
return this;
358389
}
359390

@@ -489,9 +520,13 @@ private Tags tags() {
489520
return null;
490521
}
491522

492-
private Scheduling scheduling() {
523+
@VisibleForTesting
524+
Scheduling scheduling() {
493525
Scheduling scheduling = new Scheduling();
494-
scheduling.setPreemptible(preemptible);
526+
if (provisioningType == null) {
527+
return scheduling;
528+
}
529+
provisioningType.configure(scheduling);
495530
return scheduling;
496531
}
497532

@@ -587,6 +622,11 @@ public static SshConfiguration defaultSshConfiguration() {
587622
return SshConfiguration.builder().customPrivateKeyCredentialsId("").build();
588623
}
589624

625+
@SuppressWarnings("unused") // jelly
626+
public ProvisioningType defaultProvisioningType() {
627+
return new Standard(0);
628+
}
629+
590630
public static NetworkConfiguration defaultNetworkConfiguration() {
591631
return new AutofilledNetworkConfiguration();
592632
}
@@ -936,6 +976,11 @@ public FormValidation doCheckNumExecutorsStr(
936976
return FormValidation.ok();
937977
}
938978

979+
@SuppressWarnings("unused") // jelly
980+
public List<ProvisioningType.ProvisioningTypeDescriptor> getProvisioningTypes() {
981+
return ExtensionList.lookup(ProvisioningType.ProvisioningTypeDescriptor.class);
982+
}
983+
939984
public List<NetworkInterfaceIpStackMode.Descriptor> getNetworkInterfaceIpStackModeDescriptors() {
940985
return ExtensionList.lookup(NetworkInterfaceIpStackMode.Descriptor.class);
941986
}
@@ -951,7 +996,7 @@ public InstanceConfiguration build() {
951996
instanceConfiguration.setMachineType(this.machineType);
952997
instanceConfiguration.setNumExecutorsStr(this.numExecutorsStr);
953998
instanceConfiguration.setStartupScript(this.startupScript);
954-
instanceConfiguration.setPreemptible(this.preemptible);
999+
instanceConfiguration.setProvisioningType(this.provisioningType);
9551000
instanceConfiguration.setMinCpuPlatform(this.minCpuPlatform);
9561001
instanceConfiguration.setLabelString(this.labels);
9571002
instanceConfiguration.setRunAsUser(this.runAsUser);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2024 CloudBees, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.jenkins.plugins.computeengine.config;
18+
19+
import com.google.api.services.compute.model.Scheduling;
20+
import hudson.Extension;
21+
import org.kohsuke.stapler.DataBoundConstructor;
22+
23+
public class PreemptibleVm extends ProvisioningType {
24+
25+
@DataBoundConstructor
26+
public PreemptibleVm() {}
27+
28+
@Override
29+
public void configure(Scheduling scheduling) {
30+
scheduling.setPreemptible(true);
31+
}
32+
33+
@Extension
34+
public static class DescriptorImpl extends ProvisioningTypeDescriptor {
35+
@Override
36+
public String getDisplayName() {
37+
return "Preemptible VM";
38+
}
39+
40+
@Override
41+
public boolean isMaxRunDurationSupported() {
42+
return false;
43+
}
44+
}
45+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2024 CloudBees, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.jenkins.plugins.computeengine.config;
18+
19+
import com.google.api.client.json.GenericJson;
20+
import com.google.api.services.compute.model.Scheduling;
21+
import hudson.model.AbstractDescribableImpl;
22+
import hudson.model.Descriptor;
23+
24+
/**
25+
* ProvisioningType represents the type of VM to be provisioned.
26+
*/
27+
public abstract class ProvisioningType extends AbstractDescribableImpl<ProvisioningType> {
28+
29+
protected void configureMaxRunDuration(Scheduling scheduling, long maxRunDurationSeconds) {
30+
if (maxRunDurationSeconds > 0) {
31+
GenericJson j = new GenericJson();
32+
j.set("seconds", maxRunDurationSeconds);
33+
scheduling.set("maxRunDuration", j);
34+
/* Note: Only the instance is set to delete here, not the disk. Disk deletion is based on the
35+
`bootDiskAutoDelete` config value. For instance termination at `maxRunDuration`, GCP supports two
36+
termination actions: DELETE and STOP.
37+
For Jenkins agents, DELETE is more appropriate. If the agent instance is needed again, it can be
38+
recreated using the disk, which should have been anticipated and disk should be set to not delete in
39+
`bootDiskAutoDelete`.
40+
*/
41+
scheduling.setInstanceTerminationAction("DELETE");
42+
}
43+
}
44+
45+
public abstract void configure(Scheduling scheduling);
46+
47+
public abstract static class ProvisioningTypeDescriptor extends Descriptor<ProvisioningType> {
48+
49+
@SuppressWarnings("unused") // jelly
50+
public abstract boolean isMaxRunDurationSupported();
51+
}
52+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2024 CloudBees, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.jenkins.plugins.computeengine.config;
18+
19+
import com.google.api.services.compute.model.Scheduling;
20+
import hudson.Extension;
21+
import hudson.util.FormValidation;
22+
import org.kohsuke.stapler.DataBoundConstructor;
23+
import org.kohsuke.stapler.DataBoundSetter;
24+
import org.kohsuke.stapler.QueryParameter;
25+
26+
public class SpotVm extends ProvisioningType {
27+
28+
private long maxRunDurationSeconds;
29+
30+
// required for casc
31+
@DataBoundConstructor
32+
public SpotVm(long maxRunDurationSeconds) {
33+
this.maxRunDurationSeconds = maxRunDurationSeconds;
34+
}
35+
36+
@SuppressWarnings("unused") // jelly
37+
@DataBoundSetter
38+
public void setMaxRunDurationSeconds(long maxRunDurationSeconds) {
39+
this.maxRunDurationSeconds = maxRunDurationSeconds;
40+
}
41+
42+
@SuppressWarnings("unused") // jelly
43+
public long getMaxRunDurationSeconds() {
44+
return maxRunDurationSeconds;
45+
}
46+
47+
@Override
48+
public void configure(Scheduling scheduling) {
49+
scheduling.setProvisioningModel("SPOT");
50+
super.configureMaxRunDuration(scheduling, maxRunDurationSeconds);
51+
}
52+
53+
@Extension
54+
public static class DescriptorImpl extends ProvisioningTypeDescriptor {
55+
@Override
56+
public String getDisplayName() {
57+
return "Spot VM";
58+
}
59+
60+
@SuppressWarnings("unused") // jelly
61+
public FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) {
62+
return Utils.doCheckMaxRunDurationSeconds(value);
63+
}
64+
65+
@Override
66+
public boolean isMaxRunDurationSupported() {
67+
return true;
68+
}
69+
}
70+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2024 CloudBees, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.jenkins.plugins.computeengine.config;
18+
19+
import com.google.api.services.compute.model.Scheduling;
20+
import hudson.Extension;
21+
import hudson.util.FormValidation;
22+
import org.kohsuke.stapler.DataBoundConstructor;
23+
import org.kohsuke.stapler.DataBoundSetter;
24+
import org.kohsuke.stapler.QueryParameter;
25+
26+
public class Standard extends ProvisioningType {
27+
28+
private long maxRunDurationSeconds;
29+
30+
// required for casc
31+
@DataBoundConstructor
32+
public Standard(long maxRunDurationSeconds) {
33+
this.maxRunDurationSeconds = maxRunDurationSeconds;
34+
}
35+
36+
@SuppressWarnings("unused") // jelly
37+
@DataBoundSetter
38+
public void setMaxRunDurationSeconds(long maxRunDurationSeconds) {
39+
this.maxRunDurationSeconds = maxRunDurationSeconds;
40+
}
41+
42+
@SuppressWarnings("unused") // jelly
43+
public long getMaxRunDurationSeconds() {
44+
return maxRunDurationSeconds;
45+
}
46+
47+
@Override
48+
public void configure(Scheduling scheduling) {
49+
super.configureMaxRunDuration(scheduling, maxRunDurationSeconds);
50+
}
51+
52+
@Extension
53+
public static class DescriptorImpl extends ProvisioningTypeDescriptor {
54+
@Override
55+
public String getDisplayName() {
56+
return "Standard";
57+
}
58+
59+
@SuppressWarnings("unused") // jelly
60+
public FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) {
61+
return Utils.doCheckMaxRunDurationSeconds(value);
62+
}
63+
64+
@Override
65+
public boolean isMaxRunDurationSupported() {
66+
return true;
67+
}
68+
}
69+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2024 CloudBees, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.jenkins.plugins.computeengine.config;
18+
19+
import hudson.util.FormValidation;
20+
import org.kohsuke.stapler.QueryParameter;
21+
22+
class Utils {
23+
24+
static FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) {
25+
try {
26+
long maxRunDurationSeconds = Long.parseLong(value);
27+
if (maxRunDurationSeconds < 0) {
28+
return FormValidation.error("Max run duration must be greater than or equal to 0");
29+
}
30+
return FormValidation.ok();
31+
} catch (NumberFormatException e) {
32+
return FormValidation.error("Max run duration must be non-negative number");
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)