Skip to content
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

Open
wants to merge 30 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
742724e
Add maxRunDuration feature to VM provisioning
gbhat618 Dec 10, 2024
0f38202
spot vm and max run duration settings put
gbhat618 Dec 11, 2024
d8e0bca
update comments
gbhat618 Dec 11, 2024
615e605
change from radio to dropdown descriptor selector
gbhat618 Dec 12, 2024
d57ea0c
update comments
gbhat618 Dec 12, 2024
579410e
add an integration test for spot vms with maximum run durations
gbhat618 Dec 13, 2024
17a0113
Fix trailing slash
gbhat618 Dec 16, 2024
411e510
Merge branch 'develop' of github.com:jenkinsci/google-compute-engine-…
gbhat618 Dec 16, 2024
3ddd4d3
update tests and new tests for scheduling code
gbhat618 Dec 16, 2024
363925a
remove the @deprecated annotation that would otherwise break casc com…
gbhat618 Dec 16, 2024
f4c7154
remove the todo added during code analysis
gbhat618 Dec 16, 2024
74fe8c3
fix the access specifier in descriptor
gbhat618 Dec 16, 2024
cfae906
comment add license
gbhat618 Dec 16, 2024
937abdf
remove unused field leftout during refactor
gbhat618 Dec 16, 2024
8fa2222
fix spelling mistake in default
gbhat618 Dec 17, 2024
1708135
re-order setters to reduce diff
gbhat618 Dec 17, 2024
e806d25
fix the default provisioning type considering preemptible could be al…
gbhat618 Dec 17, 2024
50a74b8
spotless
gbhat618 Dec 17, 2024
3e7e787
fix duplication, handle field deprecation and migration
gbhat618 Dec 18, 2024
244da59
fix the preemptive task rescheduling integration tests
gbhat618 Dec 18, 2024
c076896
Update src/main/java/com/google/jenkins/plugins/computeengine/Instanc…
gbhat618 Dec 19, 2024
f656ffc
Update src/main/java/com/google/jenkins/plugins/computeengine/Instanc…
gbhat618 Dec 19, 2024
361bdd0
removing the ProvisioningTypeValue enum
gbhat618 Dec 19, 2024
11c7068
fix the test instead of casc compatibility
gbhat618 Dec 19, 2024
8d7f256
add DataBoundConstructors to classes
gbhat618 Dec 19, 2024
e602674
keep casc compatibility for preemptible field
gbhat618 Dec 19, 2024
ba2d713
fix the test throwing error in RemainingActivityListener, by waitUnti…
gbhat618 Dec 20, 2024
a2dc12c
rename the package from ui.helps to better name configs
gbhat618 Dec 20, 2024
3a2cea4
config (not confi)
gbhat618 Dec 20, 2024
7442934
revert the integration test infra chagnes as these are handled by oth…
gbhat618 Dec 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,21 @@
<version>${powershell.version}</version>
<scope>test</scope>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-durable-task-step</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-job</artifactId>
<scope>test</scope>
</dependency>
Copy link
Author

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

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for reviewers: this class is using Lombok (not a good idea!):

private String minCpuPlatform;
private String labels;
private String runAsUser;
Expand Down Expand Up @@ -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>();
Expand Down Expand Up @@ -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) {

Check warning on line 261 in src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 261 is only partially covered, one branch is missing
this.provisioningType = new PreemptibleVm();
}
}

public static Integer intOrDefault(String toParse, Integer defaultTo) {
Integer toReturn;
try {
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);

Check warning on line 988 in src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 630-988 are not covered by tests
}

public List<NetworkInterfaceIpStackMode.Descriptor> getNetworkInterfaceIpStackModeDescriptors() {
return ExtensionList.lookup(NetworkInterfaceIpStackMode.Descriptor.class);
}
Expand All @@ -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);
Expand Down
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;

Check warning on line 42 in src/main/java/com/google/jenkins/plugins/computeengine/config/PreemptibleVm.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 42 is not covered by tests
}
}
}
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;

Check warning on line 44 in src/main/java/com/google/jenkins/plugins/computeengine/config/SpotVm.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 32-44 are not covered by tests
}

@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;

Check warning on line 67 in src/main/java/com/google/jenkins/plugins/computeengine/config/SpotVm.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 62-67 are not covered by tests
}
}
}
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;

Check warning on line 44 in src/main/java/com/google/jenkins/plugins/computeengine/config/Standard.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 32-44 are not covered by tests
}

@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;

Check warning on line 66 in src/main/java/com/google/jenkins/plugins/computeengine/config/Standard.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 61-66 are not covered by tests
}
}
}
Loading
Loading