-
Notifications
You must be signed in to change notification settings - Fork 89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add spotVM and maxRunDuration feature to VM provisioning #492
base: develop
Are you sure you want to change the base?
Changes from all commits
742724e
0f38202
d8e0bca
615e605
d57ea0c
579410e
17a0113
411e510
3ddd4d3
363925a
f4c7154
74fe8c3
cfae906
937abdf
8fa2222
1708135
e806d25
50a74b8
3e7e787
244da59
c076896
f656ffc
361bdd0
11c7068
8d7f256
e602674
ba2d713
a2dc12c
3a2cea4
7442934
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -37,8 +37,12 @@ | |||||
import com.google.api.services.compute.model.Zone; | ||||||
import com.google.cloud.graphite.platforms.plugin.client.ClientFactory; | ||||||
import com.google.cloud.graphite.platforms.plugin.client.ComputeClient; | ||||||
import com.google.common.annotations.VisibleForTesting; | ||||||
import com.google.common.base.Strings; | ||||||
import com.google.jenkins.plugins.computeengine.client.ClientUtil; | ||||||
import com.google.jenkins.plugins.computeengine.config.PreemptibleVm; | ||||||
import com.google.jenkins.plugins.computeengine.config.ProvisioningType; | ||||||
import com.google.jenkins.plugins.computeengine.config.Standard; | ||||||
import com.google.jenkins.plugins.computeengine.ssh.GoogleKeyCredential; | ||||||
import com.google.jenkins.plugins.computeengine.ssh.GoogleKeyPair; | ||||||
import com.google.jenkins.plugins.computeengine.ssh.GooglePrivateKey; | ||||||
|
@@ -120,7 +124,7 @@ | |||||
private String machineType; | ||||||
private String numExecutorsStr; | ||||||
private String startupScript; | ||||||
private boolean preemptible; | ||||||
private ProvisioningType provisioningType; | ||||||
gbhat618 marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note for reviewers: this class is using Lombok (not a good idea!): Lines 88 to 89 in 8d7f256
|
||||||
private String minCpuPlatform; | ||||||
private String labels; | ||||||
private String runAsUser; | ||||||
|
@@ -167,6 +171,11 @@ | |||||
@Setter(AccessLevel.PROTECTED) | ||||||
protected transient ComputeEngineCloud cloud; | ||||||
|
||||||
/** @deprecated Use {@link #provisioningType} instead. */ | ||||||
@SuppressWarnings("DeprecatedIsStillUsed") | ||||||
@Deprecated | ||||||
private transient boolean preemptible; | ||||||
|
||||||
private static List<Metadata.Items> mergeMetadataItems(List<Metadata.Items> winner, List<Metadata.Items> loser) { | ||||||
if (loser == null) { | ||||||
loser = new ArrayList<Metadata.Items>(); | ||||||
|
@@ -236,6 +245,24 @@ | |||||
this.createSnapshot = createSnapshot && this.oneShot; | ||||||
} | ||||||
|
||||||
/** | ||||||
* This setter is kept only to provide JCasC compatibility, don't use for any other. | ||||||
* Although JCasC is not "required" to keep compatibility, but in this case, | ||||||
* as it is very low effort to keep the compatibility, we have decided to keep it. | ||||||
* <p> | ||||||
* Previously, JCasC syntax would be {@code preemptible: true}, going forward instead should be done as, | ||||||
* {@code provisioningType: preemptibleVm} | ||||||
* <p> | ||||||
* Currently only caller is, JCasC configurators if the bundle is having `preemptible` field defined in it. | ||||||
* Consider deleting it in future (perhaps after a year or so) | ||||||
*/ | ||||||
@DataBoundSetter | ||||||
public void setPreemptible(boolean preemptible) { | ||||||
if (preemptible) { | ||||||
this.provisioningType = new PreemptibleVm(); | ||||||
} | ||||||
} | ||||||
|
||||||
public static Integer intOrDefault(String toParse, Integer defaultTo) { | ||||||
Integer toReturn; | ||||||
try { | ||||||
|
@@ -357,6 +384,10 @@ | |||||
this.networkInterfaceIpStackMode = new NetworkInterfaceSingleStack(externalAddress); | ||||||
this.externalAddress = null; | ||||||
} | ||||||
/* deprecating `preemptible` in favor of extensible `provisioningType` */ | ||||||
if (preemptible && provisioningType == null) { | ||||||
provisioningType = new PreemptibleVm(); | ||||||
} | ||||||
return this; | ||||||
} | ||||||
|
||||||
|
@@ -492,9 +523,13 @@ | |||||
return null; | ||||||
} | ||||||
|
||||||
private Scheduling scheduling() { | ||||||
@VisibleForTesting | ||||||
Scheduling scheduling() { | ||||||
gbhat618 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
Scheduling scheduling = new Scheduling(); | ||||||
scheduling.setPreemptible(preemptible); | ||||||
if (provisioningType == null) { | ||||||
return scheduling; | ||||||
} | ||||||
provisioningType.configure(scheduling); | ||||||
return scheduling; | ||||||
} | ||||||
|
||||||
|
@@ -590,359 +625,369 @@ | |||||
return SshConfiguration.builder().customPrivateKeyCredentialsId("").build(); | ||||||
} | ||||||
|
||||||
@SuppressWarnings("unused") // jelly | ||||||
public ProvisioningType defaultProvisioningType() { | ||||||
return new Standard(0); | ||||||
} | ||||||
|
||||||
public static NetworkConfiguration defaultNetworkConfiguration() { | ||||||
return new AutofilledNetworkConfiguration(); | ||||||
} | ||||||
|
||||||
private static ComputeClient computeClient(Jenkins context, String credentialsId) throws IOException { | ||||||
if (computeClient != null) { | ||||||
return computeClient; | ||||||
} | ||||||
ClientFactory clientFactory = ClientUtil.getClientFactory(context, credentialsId); | ||||||
return clientFactory.computeClient(); | ||||||
} | ||||||
|
||||||
@Override | ||||||
public String getHelpFile(String fieldName) { | ||||||
String p = super.getHelpFile(fieldName); | ||||||
if (p == null) { | ||||||
Descriptor d = Jenkins.get().getDescriptor(ComputeEngineInstance.class); | ||||||
if (d != null) p = d.getHelpFile(fieldName); | ||||||
} | ||||||
return p; | ||||||
} | ||||||
|
||||||
public List<NetworkConfiguration.NetworkConfigurationDescriptor> getNetworkConfigurationDescriptors() { | ||||||
List<NetworkConfiguration.NetworkConfigurationDescriptor> d = | ||||||
Jenkins.get().getDescriptorList(NetworkConfiguration.class); | ||||||
// No deprecated regions | ||||||
Iterator it = d.iterator(); | ||||||
while (it.hasNext()) { | ||||||
NetworkConfiguration.NetworkConfigurationDescriptor o = | ||||||
(NetworkConfiguration.NetworkConfigurationDescriptor) it.next(); | ||||||
if (o.clazz.getName().equals("NetworkConfiguration")) { | ||||||
it.remove(); | ||||||
} | ||||||
} | ||||||
return d; | ||||||
} | ||||||
|
||||||
public FormValidation doCheckNetworkTags(@QueryParameter String value) { | ||||||
if (value == null || value.isEmpty()) { | ||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
String re = "[a-z]([-a-z0-9]*[a-z0-9])?"; | ||||||
for (String tag : value.split(" ")) { | ||||||
if (!tag.matches(re)) { | ||||||
return FormValidation.error("Tags must be space-delimited and each tag must match regex" + re); | ||||||
} | ||||||
} | ||||||
|
||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
public FormValidation doCheckNamePrefix(@QueryParameter String value) { | ||||||
if (value == null || value.isEmpty()) { | ||||||
return FormValidation.error("A prefix is required"); | ||||||
} | ||||||
|
||||||
String re = "[a-z]([-a-z0-9]*[a-z0-9])?"; | ||||||
if (!value.matches(re)) { | ||||||
return FormValidation.error("Prefix must match regex " + re); | ||||||
} | ||||||
|
||||||
Integer maxLen = 50; | ||||||
if (value.length() > maxLen) { | ||||||
return FormValidation.error("Maximum length is " + maxLen); | ||||||
} | ||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
public FormValidation doCheckDescription(@QueryParameter String value) { | ||||||
if (value == null || value.isEmpty()) { | ||||||
return FormValidation.error("A description is required"); | ||||||
} | ||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
public ListBoxModel doFillRegionItems( | ||||||
@AncestorInPath Jenkins context, | ||||||
@QueryParameter("projectId") @RelativePath("..") final String projectId, | ||||||
@QueryParameter("credentialsId") @RelativePath("..") final String credentialsId) { | ||||||
checkPermissions(Jenkins.get(), Jenkins.ADMINISTER); | ||||||
ListBoxModel items = new ListBoxModel(); | ||||||
items.add(""); | ||||||
try { | ||||||
ComputeClient compute = computeClient(context, credentialsId); | ||||||
List<Region> regions = compute.listRegions(projectId); | ||||||
|
||||||
for (Region r : regions) { | ||||||
items.add(r.getName(), r.getSelfLink()); | ||||||
} | ||||||
return items; | ||||||
} catch (IOException ioe) { | ||||||
items.clear(); | ||||||
items.add("Error retrieving regions"); | ||||||
return items; | ||||||
} | ||||||
} | ||||||
|
||||||
public ListBoxModel doFillTemplateItems( | ||||||
@AncestorInPath Jenkins context, | ||||||
@QueryParameter("projectId") @RelativePath("..") final String projectId, | ||||||
@QueryParameter("credentialsId") @RelativePath("..") final String credentialsId) { | ||||||
checkPermissions(Jenkins.get(), Jenkins.ADMINISTER); | ||||||
ListBoxModel items = new ListBoxModel(); | ||||||
items.add(""); | ||||||
try { | ||||||
ComputeClient compute = computeClient(context, credentialsId); | ||||||
List<InstanceTemplate> instanceTemplates = compute.listTemplates(projectId); | ||||||
|
||||||
for (InstanceTemplate instanceTemplate : instanceTemplates) { | ||||||
items.add(instanceTemplate.getName(), instanceTemplate.getSelfLink()); | ||||||
} | ||||||
return items; | ||||||
} catch (IOException ioe) { | ||||||
items.clear(); | ||||||
items.add("Error retrieving instanceTemplates"); | ||||||
return items; | ||||||
} | ||||||
} | ||||||
|
||||||
public FormValidation doCheckRegion(@QueryParameter String value) { | ||||||
if (StringUtils.isEmpty(value)) { | ||||||
return FormValidation.error("Please select a region..."); | ||||||
} | ||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
public ListBoxModel doFillZoneItems( | ||||||
@AncestorInPath Jenkins context, | ||||||
@QueryParameter("projectId") @RelativePath("..") final String projectId, | ||||||
@QueryParameter("region") final String region, | ||||||
@QueryParameter("credentialsId") @RelativePath("..") final String credentialsId) { | ||||||
checkPermissions(Jenkins.get(), Jenkins.ADMINISTER); | ||||||
ListBoxModel items = new ListBoxModel(); | ||||||
items.add(""); | ||||||
try { | ||||||
ComputeClient compute = computeClient(context, credentialsId); | ||||||
List<Zone> zones = compute.listZones(projectId, region); | ||||||
|
||||||
for (Zone z : zones) { | ||||||
items.add(z.getName(), z.getSelfLink()); | ||||||
} | ||||||
return items; | ||||||
} catch (IOException ioe) { | ||||||
items.clear(); | ||||||
items.add("Error retrieving zones"); | ||||||
return items; | ||||||
} catch (IllegalArgumentException iae) { | ||||||
// TODO log | ||||||
return null; | ||||||
} | ||||||
} | ||||||
|
||||||
public FormValidation doCheckZone(@QueryParameter String value) { | ||||||
if (StringUtils.isEmpty(value)) { | ||||||
return FormValidation.error("Please select a zone..."); | ||||||
} | ||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
public ListBoxModel doFillMachineTypeItems( | ||||||
@AncestorInPath Jenkins context, | ||||||
@QueryParameter("projectId") @RelativePath("..") final String projectId, | ||||||
@QueryParameter("zone") final String zone, | ||||||
@QueryParameter("credentialsId") @RelativePath("..") final String credentialsId) { | ||||||
checkPermissions(Jenkins.get(), Jenkins.ADMINISTER); | ||||||
ListBoxModel items = new ListBoxModel(); | ||||||
items.add(""); | ||||||
try { | ||||||
ComputeClient compute = computeClient(context, credentialsId); | ||||||
List<MachineType> machineTypes = compute.listMachineTypes(projectId, zone); | ||||||
|
||||||
for (MachineType m : machineTypes) { | ||||||
items.add(m.getName(), m.getSelfLink()); | ||||||
} | ||||||
return items; | ||||||
} catch (IOException ioe) { | ||||||
items.clear(); | ||||||
items.add("Error retrieving machine types"); | ||||||
return items; | ||||||
} catch (IllegalArgumentException iae) { | ||||||
// TODO log | ||||||
return null; | ||||||
} | ||||||
} | ||||||
|
||||||
public FormValidation doCheckMachineType(@QueryParameter String value) { | ||||||
if (StringUtils.isEmpty(value)) { | ||||||
return FormValidation.error("Please select a machine type..."); | ||||||
} | ||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
public ListBoxModel doFillMinCpuPlatformItems( | ||||||
@AncestorInPath Jenkins context, | ||||||
@QueryParameter("projectId") @RelativePath("..") final String projectId, | ||||||
@QueryParameter("zone") final String zone, | ||||||
@QueryParameter("credentialsId") @RelativePath("..") final String credentialsId) { | ||||||
checkPermissions(Jenkins.get(), Jenkins.ADMINISTER); | ||||||
ListBoxModel items = new ListBoxModel(); | ||||||
items.add(""); | ||||||
try { | ||||||
ComputeClient compute = computeClient(context, credentialsId); | ||||||
List<String> cpuPlatforms = compute.listCpuPlatforms(projectId, zone); | ||||||
|
||||||
for (String cpuPlatform : cpuPlatforms) { | ||||||
items.add(cpuPlatform); | ||||||
} | ||||||
return items; | ||||||
} catch (IOException ioe) { | ||||||
items.clear(); | ||||||
items.add("Error retrieving cpu Platforms"); | ||||||
return items; | ||||||
} catch (IllegalArgumentException iae) { | ||||||
// TODO log | ||||||
return null; | ||||||
} | ||||||
} | ||||||
|
||||||
public ListBoxModel doFillBootDiskTypeItems( | ||||||
@AncestorInPath Jenkins context, | ||||||
@QueryParameter("projectId") @RelativePath("..") final String projectId, | ||||||
@QueryParameter("zone") String zone, | ||||||
@QueryParameter("credentialsId") @RelativePath("..") final String credentialsId) { | ||||||
checkPermissions(Jenkins.get(), Jenkins.ADMINISTER); | ||||||
ListBoxModel items = new ListBoxModel(); | ||||||
try { | ||||||
ComputeClient compute = computeClient(context, credentialsId); | ||||||
List<DiskType> diskTypes = compute.listBootDiskTypes(projectId, zone); | ||||||
|
||||||
for (DiskType dt : diskTypes) { | ||||||
items.add(dt.getName(), dt.getSelfLink()); | ||||||
} | ||||||
return items; | ||||||
} catch (IOException ioe) { | ||||||
items.clear(); | ||||||
items.add("Error retrieving disk types"); | ||||||
return items; | ||||||
} catch (IllegalArgumentException iae) { | ||||||
// TODO: log | ||||||
return null; | ||||||
} | ||||||
} | ||||||
|
||||||
public ListBoxModel doFillBootDiskSourceImageProjectItems( | ||||||
@AncestorInPath Jenkins context, | ||||||
@QueryParameter("projectId") @RelativePath("..") final String projectId) { | ||||||
checkPermissions(Jenkins.get(), Jenkins.ADMINISTER); | ||||||
ListBoxModel items = new ListBoxModel(); | ||||||
items.add(""); | ||||||
items.add(projectId); | ||||||
for (String v : KNOWN_IMAGE_PROJECTS) { | ||||||
items.add(v); | ||||||
} | ||||||
return items; | ||||||
} | ||||||
|
||||||
public FormValidation doCheckBootDiskSourceImageProject(@QueryParameter String value) { | ||||||
if (StringUtils.isEmpty(value)) { | ||||||
return FormValidation.warning("Please select source image project..."); | ||||||
} | ||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
public ListBoxModel doFillBootDiskSourceImageNameItems( | ||||||
@AncestorInPath Jenkins context, | ||||||
@QueryParameter("bootDiskSourceImageProject") final String projectId, | ||||||
@QueryParameter("credentialsId") @RelativePath("..") final String credentialsId) { | ||||||
checkPermissions(Jenkins.get(), Jenkins.ADMINISTER); | ||||||
ListBoxModel items = new ListBoxModel(); | ||||||
items.add(""); | ||||||
try { | ||||||
ComputeClient compute = computeClient(context, credentialsId); | ||||||
List<Image> images = compute.listImages(projectId); | ||||||
|
||||||
for (Image i : images) { | ||||||
items.add(i.getName(), i.getSelfLink()); | ||||||
} | ||||||
} catch (IOException ioe) { | ||||||
items.clear(); | ||||||
items.add("Error retrieving images for project"); | ||||||
} catch (IllegalArgumentException iae) { | ||||||
// TODO: log | ||||||
return null; | ||||||
} | ||||||
return items; | ||||||
} | ||||||
|
||||||
public FormValidation doCheckBootDiskSourceImageName(@QueryParameter String value) { | ||||||
if (StringUtils.isEmpty(value)) { | ||||||
return FormValidation.warning("Please select source image..."); | ||||||
} | ||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
public FormValidation doCheckBootDiskSizeGbStr( | ||||||
@AncestorInPath Jenkins context, | ||||||
@QueryParameter String value, | ||||||
@QueryParameter("bootDiskSourceImageProject") final String projectId, | ||||||
@QueryParameter("bootDiskSourceImageName") final String imageName, | ||||||
@QueryParameter("credentialsId") @RelativePath("..") final String credentialsId) { | ||||||
checkPermissions(Jenkins.get(), Jenkins.ADMINISTER); | ||||||
if (Strings.isNullOrEmpty(credentialsId) | ||||||
|| Strings.isNullOrEmpty(projectId) | ||||||
|| Strings.isNullOrEmpty(imageName)) return FormValidation.ok(); | ||||||
|
||||||
try { | ||||||
ComputeClient compute = computeClient(context, credentialsId); | ||||||
Image i = compute.getImage(nameFromSelfLink(projectId), nameFromSelfLink(imageName)); | ||||||
if (i == null) return FormValidation.error("Could not find image " + imageName); | ||||||
Long bootDiskSizeGb = Long.parseLong(value); | ||||||
if (bootDiskSizeGb < i.getDiskSizeGb()) { | ||||||
return FormValidation.error(String.format( | ||||||
"The disk image you have chosen requires a minimum of %dGB. Please increase boot disk size to accommodate.", | ||||||
i.getDiskSizeGb())); | ||||||
} | ||||||
} catch (IOException ioe) { | ||||||
return FormValidation.error(ioe, "Error validating boot disk size"); | ||||||
} | ||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
public FormValidation doCheckLabelString(@QueryParameter String value, @QueryParameter Node.Mode mode) { | ||||||
if (mode == Node.Mode.EXCLUSIVE && (value == null || value.trim().isEmpty())) { | ||||||
return FormValidation.warning("You may want to assign labels to this node;" | ||||||
+ " it's marked to only run jobs that are exclusively tied to itself or a label."); | ||||||
} | ||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
public FormValidation doCheckCreateSnapshot( | ||||||
@AncestorInPath Jenkins context, | ||||||
@QueryParameter boolean value, | ||||||
@QueryParameter("oneShot") boolean oneShot) { | ||||||
if (!oneShot && value) { | ||||||
return FormValidation.error(Messages.InstanceConfiguration_SnapshotConfigError()); | ||||||
} | ||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
public FormValidation doCheckNumExecutorsStr( | ||||||
@AncestorInPath Jenkins context, | ||||||
@QueryParameter String value, | ||||||
@QueryParameter("oneShot") boolean oneShot) { | ||||||
int numExecutors = intOrDefault(value, DEFAULT_NUM_EXECUTORS); | ||||||
if (numExecutors < 1) { | ||||||
return FormValidation.error(Messages.InstanceConfiguration_NumExecutorsLessThanOneConfigError()); | ||||||
} else if (numExecutors > 1 && oneShot) { | ||||||
return FormValidation.error(Messages.InstanceConfiguration_NumExecutorsOneShotError()); | ||||||
} | ||||||
return FormValidation.ok(); | ||||||
} | ||||||
|
||||||
@SuppressWarnings("unused") // jelly | ||||||
public List<ProvisioningType.ProvisioningTypeDescriptor> getProvisioningTypes() { | ||||||
return ExtensionList.lookup(ProvisioningType.ProvisioningTypeDescriptor.class); | ||||||
} | ||||||
|
||||||
public List<NetworkInterfaceIpStackMode.Descriptor> getNetworkInterfaceIpStackModeDescriptors() { | ||||||
return ExtensionList.lookup(NetworkInterfaceIpStackMode.Descriptor.class); | ||||||
} | ||||||
|
@@ -958,7 +1003,7 @@ | |||||
instanceConfiguration.setMachineType(this.machineType); | ||||||
instanceConfiguration.setNumExecutorsStr(this.numExecutorsStr); | ||||||
instanceConfiguration.setStartupScript(this.startupScript); | ||||||
instanceConfiguration.setPreemptible(this.preemptible); | ||||||
instanceConfiguration.setProvisioningType(this.provisioningType); | ||||||
instanceConfiguration.setMinCpuPlatform(this.minCpuPlatform); | ||||||
instanceConfiguration.setLabelString(this.labels); | ||||||
instanceConfiguration.setRunAsUser(this.runAsUser); | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* Copyright 2024 CloudBees, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.jenkins.plugins.computeengine.config; | ||
|
||
import com.google.api.services.compute.model.Scheduling; | ||
import hudson.Extension; | ||
import org.kohsuke.stapler.DataBoundConstructor; | ||
|
||
public class PreemptibleVm extends ProvisioningType { | ||
|
||
@DataBoundConstructor | ||
public PreemptibleVm() {} | ||
|
||
@Override | ||
public void configure(Scheduling scheduling) { | ||
scheduling.setPreemptible(true); | ||
} | ||
|
||
@Extension | ||
public static class DescriptorImpl extends ProvisioningTypeDescriptor { | ||
@Override | ||
public String getDisplayName() { | ||
return "Preemptible VM"; | ||
} | ||
|
||
@Override | ||
public boolean isMaxRunDurationSupported() { | ||
return false; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* Copyright 2024 CloudBees, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.jenkins.plugins.computeengine.config; | ||
|
||
import com.google.api.client.json.GenericJson; | ||
import com.google.api.services.compute.model.Scheduling; | ||
import hudson.model.AbstractDescribableImpl; | ||
import hudson.model.Descriptor; | ||
|
||
/** | ||
* ProvisioningType represents the type of VM to be provisioned. | ||
*/ | ||
public abstract class ProvisioningType extends AbstractDescribableImpl<ProvisioningType> { | ||
|
||
protected void configureMaxRunDuration(Scheduling scheduling, long maxRunDurationSeconds) { | ||
if (maxRunDurationSeconds > 0) { | ||
GenericJson j = new GenericJson(); | ||
j.set("seconds", maxRunDurationSeconds); | ||
scheduling.set("maxRunDuration", j); | ||
/* Note: Only the instance is set to delete here, not the disk. Disk deletion is based on the | ||
`bootDiskAutoDelete` config value. For instance termination at `maxRunDuration`, GCP supports two | ||
termination actions: DELETE and STOP. | ||
For Jenkins agents, DELETE is more appropriate. If the agent instance is needed again, it can be | ||
recreated using the disk, which should have been anticipated and disk should be set to not delete in | ||
`bootDiskAutoDelete`. | ||
*/ | ||
scheduling.setInstanceTerminationAction("DELETE"); | ||
} | ||
} | ||
|
||
public abstract void configure(Scheduling scheduling); | ||
|
||
public abstract static class ProvisioningTypeDescriptor extends Descriptor<ProvisioningType> { | ||
|
||
@SuppressWarnings("unused") // jelly | ||
public abstract boolean isMaxRunDurationSupported(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
* Copyright 2024 CloudBees, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.jenkins.plugins.computeengine.config; | ||
|
||
import com.google.api.services.compute.model.Scheduling; | ||
import hudson.Extension; | ||
import hudson.util.FormValidation; | ||
import org.kohsuke.stapler.DataBoundConstructor; | ||
import org.kohsuke.stapler.DataBoundSetter; | ||
import org.kohsuke.stapler.QueryParameter; | ||
|
||
public class SpotVm extends ProvisioningType { | ||
|
||
private long maxRunDurationSeconds; | ||
|
||
// required for casc | ||
@DataBoundConstructor | ||
public SpotVm(long maxRunDurationSeconds) { | ||
this.maxRunDurationSeconds = maxRunDurationSeconds; | ||
} | ||
|
||
@SuppressWarnings("unused") // jelly | ||
@DataBoundSetter | ||
public void setMaxRunDurationSeconds(long maxRunDurationSeconds) { | ||
this.maxRunDurationSeconds = maxRunDurationSeconds; | ||
} | ||
|
||
@SuppressWarnings("unused") // jelly | ||
public long getMaxRunDurationSeconds() { | ||
return maxRunDurationSeconds; | ||
} | ||
|
||
@Override | ||
public void configure(Scheduling scheduling) { | ||
scheduling.setProvisioningModel("SPOT"); | ||
super.configureMaxRunDuration(scheduling, maxRunDurationSeconds); | ||
} | ||
|
||
@Extension | ||
public static class DescriptorImpl extends ProvisioningTypeDescriptor { | ||
@Override | ||
public String getDisplayName() { | ||
return "Spot VM"; | ||
} | ||
|
||
@SuppressWarnings("unused") // jelly | ||
public FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) { | ||
Check warning Code scanning / Jenkins Security Scan Stapler: Missing POST/RequirePOST annotation Warning
Potential CSRF vulnerability: If DescriptorImpl#doCheckMaxRunDurationSeconds connects to user-specified URLs, modifies state, or is expensive to run, it should be annotated with @POST or @RequirePOST
Check warning Code scanning / Jenkins Security Scan Stapler: Missing permission check Warning
Potential missing permission check in DescriptorImpl#doCheckMaxRunDurationSeconds
|
||
return Utils.doCheckMaxRunDurationSeconds(value); | ||
} | ||
|
||
@Override | ||
public boolean isMaxRunDurationSupported() { | ||
return true; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* | ||
* Copyright 2024 CloudBees, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.jenkins.plugins.computeengine.config; | ||
|
||
import com.google.api.services.compute.model.Scheduling; | ||
import hudson.Extension; | ||
import hudson.util.FormValidation; | ||
import org.kohsuke.stapler.DataBoundConstructor; | ||
import org.kohsuke.stapler.DataBoundSetter; | ||
import org.kohsuke.stapler.QueryParameter; | ||
|
||
public class Standard extends ProvisioningType { | ||
|
||
private long maxRunDurationSeconds; | ||
|
||
// required for casc | ||
@DataBoundConstructor | ||
public Standard(long maxRunDurationSeconds) { | ||
this.maxRunDurationSeconds = maxRunDurationSeconds; | ||
} | ||
|
||
@SuppressWarnings("unused") // jelly | ||
@DataBoundSetter | ||
public void setMaxRunDurationSeconds(long maxRunDurationSeconds) { | ||
this.maxRunDurationSeconds = maxRunDurationSeconds; | ||
} | ||
|
||
@SuppressWarnings("unused") // jelly | ||
public long getMaxRunDurationSeconds() { | ||
return maxRunDurationSeconds; | ||
} | ||
|
||
@Override | ||
public void configure(Scheduling scheduling) { | ||
super.configureMaxRunDuration(scheduling, maxRunDurationSeconds); | ||
} | ||
|
||
@Extension | ||
public static class DescriptorImpl extends ProvisioningTypeDescriptor { | ||
@Override | ||
public String getDisplayName() { | ||
return "Standard"; | ||
} | ||
|
||
@SuppressWarnings("unused") // jelly | ||
public FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) { | ||
Check warning Code scanning / Jenkins Security Scan Stapler: Missing POST/RequirePOST annotation Warning
Potential CSRF vulnerability: If DescriptorImpl#doCheckMaxRunDurationSeconds connects to user-specified URLs, modifies state, or is expensive to run, it should be annotated with @POST or @RequirePOST
Check warning Code scanning / Jenkins Security Scan Stapler: Missing permission check Warning
Potential missing permission check in DescriptorImpl#doCheckMaxRunDurationSeconds
|
||
return Utils.doCheckMaxRunDurationSeconds(value); | ||
} | ||
|
||
@Override | ||
public boolean isMaxRunDurationSupported() { | ||
return true; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These pom dependencies will be removed after merging #495
currently keeping here, otherwise compilation will fail in CI