From 742724e8c6a7f449e28a38c2ba246f5a710cf9e3 Mon Sep 17 00:00:00 2001 From: guruprasad Date: Tue, 10 Dec 2024 18:06:05 +0530 Subject: [PATCH 01/19] Add maxRunDuration feature to VM provisioning --- .../computeengine/InstanceConfiguration.java | 26 ++++++++++++++++++- .../InstanceConfiguration/config.jelly | 25 +++++++++++++++++- .../help-maxRunDurationSeconds.html | 4 +++ .../help-preemptible.html | 7 ++--- 4 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-maxRunDurationSeconds.html diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java index c476926e..a746fee4 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java @@ -19,6 +19,7 @@ import static com.google.cloud.graphite.platforms.plugin.client.util.ClientUtil.nameFromSelfLink; import static com.google.jenkins.plugins.computeengine.ComputeEngineCloud.checkPermissions; +import com.google.api.client.json.GenericJson; import com.google.api.services.compute.model.AcceleratorConfig; import com.google.api.services.compute.model.AttachedDisk; import com.google.api.services.compute.model.AttachedDiskInitializeParams; @@ -121,6 +122,7 @@ public class InstanceConfiguration implements Describable private String numExecutorsStr; private String startupScript; private boolean preemptible; + private long maxRunDurationSeconds; private String minCpuPlatform; private String labels; private String runAsUser; @@ -494,7 +496,16 @@ private Tags tags() { private Scheduling scheduling() { Scheduling scheduling = new Scheduling(); - scheduling.setPreemptible(preemptible); + if (preemptible) { + scheduling.setProvisioningModel("SPOT"); + scheduling.setInstanceTerminationAction("DELETE"); + } + if (maxRunDurationSeconds > 0) { + GenericJson j = new GenericJson(); + j.set("seconds", maxRunDurationSeconds); + scheduling.set("maxRunDuration", j); + scheduling.setInstanceTerminationAction("DELETE"); + } return scheduling; } @@ -783,6 +794,18 @@ public FormValidation doCheckMachineType(@QueryParameter String value) { return FormValidation.ok(); } + public FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) { + try { + long maxRunDurationSeconds = Long.parseLong(value); + if (maxRunDurationSeconds < 0) { + return FormValidation.error("Max run duration must be greater than or equal to 0"); + } + return FormValidation.ok(); + } catch (NumberFormatException e) { + return FormValidation.error("Max run duration must be non-negative number"); + } + } + public ListBoxModel doFillMinCpuPlatformItems( @AncestorInPath Jenkins context, @QueryParameter("projectId") @RelativePath("..") final String projectId, @@ -959,6 +982,7 @@ public InstanceConfiguration build() { instanceConfiguration.setNumExecutorsStr(this.numExecutorsStr); instanceConfiguration.setStartupScript(this.startupScript); instanceConfiguration.setPreemptible(this.preemptible); + instanceConfiguration.setMaxRunDurationSeconds(this.maxRunDurationSeconds); instanceConfiguration.setMinCpuPlatform(this.minCpuPlatform); instanceConfiguration.setLabelString(this.labels); instanceConfiguration.setRunAsUser(this.runAsUser); diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly index bf4420fa..1c72eeb1 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly @@ -89,9 +89,32 @@ - + + + + + + + diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-maxRunDurationSeconds.html b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-maxRunDurationSeconds.html new file mode 100644 index 00000000..7c05682c --- /dev/null +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-maxRunDurationSeconds.html @@ -0,0 +1,4 @@ +
+ The maximum duration (in seconds) after which the VM will be automatically deleted by GCP. + See Limit the run time of a VM for more details. +
\ No newline at end of file diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-preemptible.html b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-preemptible.html index 974de830..103f23cd 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-preemptible.html +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-preemptible.html @@ -12,11 +12,8 @@ License. -->
- A preemptible VM is an instance that you can create and run at a much lower price than normal instances. However, - Compute Engine might terminate (preempt) these instances if it requires access to those resources for other tasks. - Preemptible instances are excess Compute Engine capacity so their availability varies with usage. + A Spot VM (previously called preemptible) is a low-cost instance that may be terminated by Compute Engine if resources are needed elsewhere. Availability varies.

- See the Preemptible VM Instances - documentation for more information. + See Spot VMs for more details. For historical reference, see Preemptible VMs.

\ No newline at end of file From 0f38202a4dd5539426cb038eca517b0c42b1522c Mon Sep 17 00:00:00 2001 From: guruprasad Date: Wed, 11 Dec 2024 17:13:10 +0530 Subject: [PATCH 02/19] spot vm and max run duration settings put --- .../computeengine/InstanceConfiguration.java | 60 +++++++++++++++++-- .../ui/helpers/PreemptibleVm.java | 27 +++++++++ .../ui/helpers/ProvisioningType.java | 30 ++++++++++ .../ui/helpers/ProvisioningTypeValue.java | 7 +++ .../computeengine/ui/helpers/SpotVm.java | 27 +++++++++ .../computeengine/ui/helpers/Standard.java | 27 +++++++++ .../InstanceConfiguration/config.jelly | 27 ++------- .../help-maxRunDurationSeconds.html | 15 +++++ .../help-preemptible.html | 19 ------ .../help-provisioningType.html | 41 +++++++++++++ 10 files changed, 232 insertions(+), 48 deletions(-) create mode 100644 src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java create mode 100644 src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java create mode 100644 src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningTypeValue.java create mode 100644 src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java create mode 100644 src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java delete mode 100644 src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-preemptible.html create mode 100644 src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-provisioningType.html diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java index a746fee4..dffbc3bd 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java @@ -18,6 +18,8 @@ import static com.google.cloud.graphite.platforms.plugin.client.util.ClientUtil.nameFromSelfLink; import static com.google.jenkins.plugins.computeengine.ComputeEngineCloud.checkPermissions; +import static com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningTypeValue.PREEMPTIBLE; +import static com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningTypeValue.SPOT; import com.google.api.client.json.GenericJson; import com.google.api.services.compute.model.AcceleratorConfig; @@ -43,6 +45,8 @@ import com.google.jenkins.plugins.computeengine.ssh.GoogleKeyCredential; import com.google.jenkins.plugins.computeengine.ssh.GoogleKeyPair; import com.google.jenkins.plugins.computeengine.ssh.GooglePrivateKey; +import com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningType; +import com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningTypeValue; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Extension; @@ -64,6 +68,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.logging.Level; @@ -121,7 +126,18 @@ public class InstanceConfiguration implements Describable private String machineType; private String numExecutorsStr; private String startupScript; + + /** + * Use the {@link #provisioningType} field instead. + */ + @Deprecated private boolean preemptible; + + /** + * Succeeds {@link #preemptible}. + */ + private ProvisioningType provisioningType; + private long maxRunDurationSeconds; private String minCpuPlatform; private String labels; @@ -496,14 +512,24 @@ private Tags tags() { private Scheduling scheduling() { Scheduling scheduling = new Scheduling(); - if (preemptible) { - scheduling.setProvisioningModel("SPOT"); - scheduling.setInstanceTerminationAction("DELETE"); - } - if (maxRunDurationSeconds > 0) { + + if (provisioningType != null) { + if (provisioningType.getValue() == PREEMPTIBLE) { + scheduling.setPreemptible(true); + } else if (provisioningType.getValue() == SPOT) { + scheduling.setProvisioningModel("SPOT"); + // only the instance is deleted, the disk deletion is based on bootDiskAutoDelete config value + scheduling.setInstanceTerminationAction("DELETE"); + } + } else if (preemptible) { // keeping the check for `preemptible` for backward compatibility + scheduling.setPreemptible(true); + } // else: standard provisioning + + if (maxRunDurationSeconds > 0 && provisioningType.getValue() != PREEMPTIBLE) { GenericJson j = new GenericJson(); j.set("seconds", maxRunDurationSeconds); scheduling.set("maxRunDuration", j); + // only the instance is deleted, the disk deletion is based on bootDiskAutoDelete config value scheduling.setInstanceTerminationAction("DELETE"); } return scheduling; @@ -565,6 +591,7 @@ private List serviceAccounts() { @Extension public static final class DescriptorImpl extends Descriptor { private static ComputeClient computeClient; + private static List provisioningTypeDescriptors; public static void setComputeClient(ComputeClient client) { computeClient = client; @@ -794,12 +821,23 @@ public FormValidation doCheckMachineType(@QueryParameter String value) { return FormValidation.ok(); } - public FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) { + public FormValidation doCheckMaxRunDurationSeconds( + @QueryParameter String value, @QueryParameter(value="provisioningType") String provisioningTypeIdx) { try { long maxRunDurationSeconds = Long.parseLong(value); if (maxRunDurationSeconds < 0) { return FormValidation.error("Max run duration must be greater than or equal to 0"); } + + if (StringUtils.isNotBlank(provisioningTypeIdx)) { + ProvisioningTypeValue provisioningTypeValue = provisioningTypeDescriptors + .get(Integer.parseInt(provisioningTypeIdx)) + .getProvisioningTypeValue(); + if (provisioningTypeValue == PREEMPTIBLE) { + return FormValidation.warning("Max run duration is not supported for preemptible VMs and will be ignored."); + } + } + return FormValidation.ok(); } catch (NumberFormatException e) { return FormValidation.error("Max run duration must be non-negative number"); @@ -966,6 +1004,15 @@ public FormValidation doCheckNumExecutorsStr( return FormValidation.ok(); } + @SuppressWarnings("unused") + public List getProvisioningTypes() { + if (provisioningTypeDescriptors == null) { + provisioningTypeDescriptors = + Objects.requireNonNull(Jenkins.getInstanceOrNull()).getDescriptorList(ProvisioningType.class); + } + return provisioningTypeDescriptors; + } + public List getNetworkInterfaceIpStackModeDescriptors() { return ExtensionList.lookup(NetworkInterfaceIpStackMode.Descriptor.class); } @@ -982,6 +1029,7 @@ public InstanceConfiguration build() { instanceConfiguration.setNumExecutorsStr(this.numExecutorsStr); instanceConfiguration.setStartupScript(this.startupScript); instanceConfiguration.setPreemptible(this.preemptible); + instanceConfiguration.setProvisioningType(this.provisioningType); instanceConfiguration.setMaxRunDurationSeconds(this.maxRunDurationSeconds); instanceConfiguration.setMinCpuPlatform(this.minCpuPlatform); instanceConfiguration.setLabelString(this.labels); diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java new file mode 100644 index 00000000..11c1ca5c --- /dev/null +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java @@ -0,0 +1,27 @@ +package com.google.jenkins.plugins.computeengine.ui.helpers; + +import hudson.Extension; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; + +@SuppressWarnings("unused") +public class PreemptibleVm extends ProvisioningType { + + @DataBoundConstructor + public PreemptibleVm() { + super(ProvisioningTypeValue.PREEMPTIBLE); + } + + @Extension + public static class DescriptorImpl extends ProvisioningTypeDescriptor { + @Override + public String getDisplayName() { + return "Preemptible VM"; + } + + @Override + public ProvisioningTypeValue getProvisioningTypeValue() { + return ProvisioningTypeValue.PREEMPTIBLE; + } + } +} diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java new file mode 100644 index 00000000..b96c516f --- /dev/null +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java @@ -0,0 +1,30 @@ +package com.google.jenkins.plugins.computeengine.ui.helpers; + +import org.kohsuke.stapler.DataBoundSetter; + +import hudson.model.AbstractDescribableImpl; +import hudson.model.Descriptor; + +/** + * ProvisioningType represents the type of VM to be provisioned. + */ +public abstract class ProvisioningType extends AbstractDescribableImpl { + public ProvisioningTypeValue value; + + public ProvisioningType(ProvisioningTypeValue value) { + this.value = value; + } + + public ProvisioningTypeValue getValue() { + return value; + } + + @DataBoundSetter + public void setProvisioningTypeValue(ProvisioningTypeValue value) { + this.value = value; + } + + public abstract static class ProvisioningTypeDescriptor extends Descriptor { + public abstract ProvisioningTypeValue getProvisioningTypeValue(); + } +} diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningTypeValue.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningTypeValue.java new file mode 100644 index 00000000..1727e7b6 --- /dev/null +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningTypeValue.java @@ -0,0 +1,7 @@ +package com.google.jenkins.plugins.computeengine.ui.helpers; + +public enum ProvisioningTypeValue { + STANDARD, + SPOT, + PREEMPTIBLE; +} diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java new file mode 100644 index 00000000..e54c756f --- /dev/null +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java @@ -0,0 +1,27 @@ +package com.google.jenkins.plugins.computeengine.ui.helpers; + +import hudson.Extension; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; + +@SuppressWarnings("unused") +public class SpotVm extends ProvisioningType { + + @DataBoundConstructor + public SpotVm() { + super(ProvisioningTypeValue.SPOT); + } + + @Extension + public static class DescriptorImpl extends ProvisioningTypeDescriptor { + @Override + public String getDisplayName() { + return "Spot VM"; + } + + @Override + public ProvisioningTypeValue getProvisioningTypeValue() { + return ProvisioningTypeValue.SPOT; + } + } +} diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java new file mode 100644 index 00000000..443d87f5 --- /dev/null +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java @@ -0,0 +1,27 @@ +package com.google.jenkins.plugins.computeengine.ui.helpers; + +import hudson.Extension; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; + +@SuppressWarnings("unused") +public class Standard extends ProvisioningType { + + @DataBoundConstructor + public Standard() { + super(ProvisioningTypeValue.STANDARD); + } + + @Extension + public static class DescriptorImpl extends ProvisioningTypeDescriptor { + @Override + public String getDisplayName() { + return "Standard"; + } + + @Override + public ProvisioningTypeValue getProvisioningTypeValue() { + return ProvisioningTypeValue.STANDARD; + } + } +} diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly index 1c72eeb1..4df7656e 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly @@ -89,32 +89,13 @@ - - - + - - - diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-maxRunDurationSeconds.html b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-maxRunDurationSeconds.html index 7c05682c..c26d22d2 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-maxRunDurationSeconds.html +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-maxRunDurationSeconds.html @@ -1,3 +1,18 @@ +
The maximum duration (in seconds) after which the VM will be automatically deleted by GCP. See Limit the run time of a VM for more details. diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-preemptible.html b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-preemptible.html deleted file mode 100644 index 103f23cd..00000000 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-preemptible.html +++ /dev/null @@ -1,19 +0,0 @@ - -
- A Spot VM (previously called preemptible) is a low-cost instance that may be terminated by Compute Engine if resources are needed elsewhere. Availability varies. -

- See Spot VMs for more details. For historical reference, see Preemptible VMs. -

-
\ No newline at end of file diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-provisioningType.html b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-provisioningType.html new file mode 100644 index 00000000..48215c46 --- /dev/null +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-provisioningType.html @@ -0,0 +1,41 @@ + + +
+ GCP offers three VM provisioning options, each with different availability, pricing, and termination policies. + The "Max Run Duration Seconds" field below applies to all three types. +
    +
  • + Standard: Stable provisioning model with guaranteed availability by GCP SLAs. + Higher pricing compared to Spot and Preemptible VMs. +
  • +
  • + Spot: Low-cost instances that may be terminated if resources are needed elsewhere. Availability varies. + Unlike Preemptible VMs, they are not always terminated after 24 hours. Spot and Preemptible VMs are priced the same. + Set the "Max Run Duration Seconds" field below to ensure termination after a specific duration. +

    + See Spot VMs for more details. +

    +
  • +
  • + Preemptible: Low-cost instances that may be terminated if resources are needed for other tasks. + Availability varies with usage. Spot and Preemptible VMs are priced the same. +

    + See the Preemptible VM Instances documentation for more information. +

    +
  • +
+
\ No newline at end of file From d8e0bca7b316756f1d1e54873debfd95ff131350 Mon Sep 17 00:00:00 2001 From: guruprasad Date: Wed, 11 Dec 2024 18:21:24 +0530 Subject: [PATCH 03/19] update comments --- .../computeengine/InstanceConfiguration.java | 5 +++-- .../ui/helpers/PreemptibleVm.java | 17 ++++++++++++++++- .../ui/helpers/ProvisioningType.java | 19 +++++++++++++++++-- .../ui/helpers/ProvisioningTypeValue.java | 16 ++++++++++++++++ .../computeengine/ui/helpers/SpotVm.java | 17 ++++++++++++++++- .../computeengine/ui/helpers/Standard.java | 17 ++++++++++++++++- 6 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java index dffbc3bd..06063bc3 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java @@ -822,7 +822,7 @@ public FormValidation doCheckMachineType(@QueryParameter String value) { } public FormValidation doCheckMaxRunDurationSeconds( - @QueryParameter String value, @QueryParameter(value="provisioningType") String provisioningTypeIdx) { + @QueryParameter String value, @QueryParameter(value = "provisioningType") String provisioningTypeIdx) { try { long maxRunDurationSeconds = Long.parseLong(value); if (maxRunDurationSeconds < 0) { @@ -834,7 +834,8 @@ public FormValidation doCheckMaxRunDurationSeconds( .get(Integer.parseInt(provisioningTypeIdx)) .getProvisioningTypeValue(); if (provisioningTypeValue == PREEMPTIBLE) { - return FormValidation.warning("Max run duration is not supported for preemptible VMs and will be ignored."); + return FormValidation.warning( + "Max run duration is not supported for preemptible VMs and will be ignored."); } } diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java index 11c1ca5c..7b490a86 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java @@ -1,8 +1,23 @@ +/* + * 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.ui.helpers; import hudson.Extension; import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.DataBoundSetter; @SuppressWarnings("unused") public class PreemptibleVm extends ProvisioningType { diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java index b96c516f..d76deb76 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java @@ -1,9 +1,24 @@ -package com.google.jenkins.plugins.computeengine.ui.helpers; +/* + * 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. + */ -import org.kohsuke.stapler.DataBoundSetter; +package com.google.jenkins.plugins.computeengine.ui.helpers; import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; +import org.kohsuke.stapler.DataBoundSetter; /** * ProvisioningType represents the type of VM to be provisioned. diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningTypeValue.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningTypeValue.java index 1727e7b6..ac342003 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningTypeValue.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningTypeValue.java @@ -1,3 +1,19 @@ +/* + * 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.ui.helpers; public enum ProvisioningTypeValue { diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java index e54c756f..4cc69bb0 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java @@ -1,8 +1,23 @@ +/* + * 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.ui.helpers; import hudson.Extension; import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.DataBoundSetter; @SuppressWarnings("unused") public class SpotVm extends ProvisioningType { diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java index 443d87f5..93b2d1ae 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java @@ -1,8 +1,23 @@ +/* + * 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.ui.helpers; import hudson.Extension; import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.DataBoundSetter; @SuppressWarnings("unused") public class Standard extends ProvisioningType { From 615e60500d04d67cb2c03736db6ac9d90cffe5cc Mon Sep 17 00:00:00 2001 From: guruprasad Date: Thu, 12 Dec 2024 14:04:25 +0530 Subject: [PATCH 04/19] change from radio to dropdown descriptor selector --- .../computeengine/InstanceConfiguration.java | 71 ++++++------------- .../ui/helpers/PreemptibleVm.java | 5 -- .../ui/helpers/ProvisioningType.java | 5 +- .../computeengine/ui/helpers/SpotVm.java | 19 ++++- .../computeengine/ui/helpers/Standard.java | 19 ++++- .../computeengine/ui/helpers/Utils.java | 35 +++++++++ .../InstanceConfiguration/config.jelly | 12 ++-- .../help-provisioningType.html | 10 +-- .../helpers/PreemptibleVm/config-detail.jelly | 5 ++ .../ui/helpers/ProvisioningType/config.jelly | 4 ++ .../ui/helpers/SpotVm/config-detail.jelly | 7 ++ .../SpotVm}/help-maxRunDurationSeconds.html | 0 .../ui/helpers/Standard/config-detail.jelly | 7 ++ .../Standard/help-maxRunDurationSeconds.html | 19 +++++ 14 files changed, 145 insertions(+), 73 deletions(-) create mode 100644 src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Utils.java create mode 100644 src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm/config-detail.jelly create mode 100644 src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/config.jelly create mode 100644 src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/config-detail.jelly rename src/main/resources/com/google/jenkins/plugins/computeengine/{InstanceConfiguration => ui/helpers/SpotVm}/help-maxRunDurationSeconds.html (100%) create mode 100644 src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/config-detail.jelly create mode 100644 src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/help-maxRunDurationSeconds.html diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java index 06063bc3..ac22ab50 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java @@ -47,6 +47,8 @@ import com.google.jenkins.plugins.computeengine.ssh.GooglePrivateKey; import com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningType; import com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningTypeValue; +import com.google.jenkins.plugins.computeengine.ui.helpers.SpotVm; +import com.google.jenkins.plugins.computeengine.ui.helpers.Standard; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Extension; @@ -68,7 +70,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.logging.Level; @@ -126,19 +127,15 @@ public class InstanceConfiguration implements Describable private String machineType; private String numExecutorsStr; private String startupScript; - /** - * Use the {@link #provisioningType} field instead. + * `preemptible` is no more exposed in the UI, however we are still keeping it in here, just to provide + * compatibility with old configurations. Use the {@link #provisioningType} field instead where + * {@link com.google.jenkins.plugins.computeengine.ui.helpers.PreemptibleVm} is used for preemptible instances. */ @Deprecated private boolean preemptible; - /** - * Succeeds {@link #preemptible}. - */ private ProvisioningType provisioningType; - - private long maxRunDurationSeconds; private String minCpuPlatform; private String labels; private String runAsUser; @@ -512,26 +509,29 @@ private Tags tags() { private Scheduling scheduling() { Scheduling scheduling = new Scheduling(); - + long maxRunDurationSeconds = 0; if (provisioningType != null) { - if (provisioningType.getValue() == PREEMPTIBLE) { + ProvisioningTypeValue ptValue = provisioningType.getValue(); + if (ptValue == PREEMPTIBLE) { scheduling.setPreemptible(true); } else if (provisioningType.getValue() == SPOT) { + maxRunDurationSeconds = ((SpotVm) provisioningType).getMaxRunDurationSeconds(); scheduling.setProvisioningModel("SPOT"); // only the instance is deleted, the disk deletion is based on bootDiskAutoDelete config value scheduling.setInstanceTerminationAction("DELETE"); + } else { + maxRunDurationSeconds = ((Standard) provisioningType).getMaxRunDurationSeconds(); + } + if (maxRunDurationSeconds > 0) { + GenericJson j = new GenericJson(); + j.set("seconds", maxRunDurationSeconds); + scheduling.set("maxRunDuration", j); + // only the instance is deleted, the disk deletion is based on bootDiskAutoDelete config value + scheduling.setInstanceTerminationAction("DELETE"); } } else if (preemptible) { // keeping the check for `preemptible` for backward compatibility scheduling.setPreemptible(true); } // else: standard provisioning - - if (maxRunDurationSeconds > 0 && provisioningType.getValue() != PREEMPTIBLE) { - GenericJson j = new GenericJson(); - j.set("seconds", maxRunDurationSeconds); - scheduling.set("maxRunDuration", j); - // only the instance is deleted, the disk deletion is based on bootDiskAutoDelete config value - scheduling.setInstanceTerminationAction("DELETE"); - } return scheduling; } @@ -821,28 +821,9 @@ public FormValidation doCheckMachineType(@QueryParameter String value) { return FormValidation.ok(); } - public FormValidation doCheckMaxRunDurationSeconds( - @QueryParameter String value, @QueryParameter(value = "provisioningType") String provisioningTypeIdx) { - try { - long maxRunDurationSeconds = Long.parseLong(value); - if (maxRunDurationSeconds < 0) { - return FormValidation.error("Max run duration must be greater than or equal to 0"); - } - - if (StringUtils.isNotBlank(provisioningTypeIdx)) { - ProvisioningTypeValue provisioningTypeValue = provisioningTypeDescriptors - .get(Integer.parseInt(provisioningTypeIdx)) - .getProvisioningTypeValue(); - if (provisioningTypeValue == PREEMPTIBLE) { - return FormValidation.warning( - "Max run duration is not supported for preemptible VMs and will be ignored."); - } - } - - return FormValidation.ok(); - } catch (NumberFormatException e) { - return FormValidation.error("Max run duration must be non-negative number"); - } + @SuppressWarnings("unused") + public ProvisioningType defualtProvisioningType() { + return new Standard(); } public ListBoxModel doFillMinCpuPlatformItems( @@ -1007,11 +988,7 @@ public FormValidation doCheckNumExecutorsStr( @SuppressWarnings("unused") public List getProvisioningTypes() { - if (provisioningTypeDescriptors == null) { - provisioningTypeDescriptors = - Objects.requireNonNull(Jenkins.getInstanceOrNull()).getDescriptorList(ProvisioningType.class); - } - return provisioningTypeDescriptors; + return ExtensionList.lookup(ProvisioningType.ProvisioningTypeDescriptor.class); } public List getNetworkInterfaceIpStackModeDescriptors() { @@ -1029,10 +1006,8 @@ public InstanceConfiguration build() { instanceConfiguration.setMachineType(this.machineType); instanceConfiguration.setNumExecutorsStr(this.numExecutorsStr); instanceConfiguration.setStartupScript(this.startupScript); - instanceConfiguration.setPreemptible(this.preemptible); - instanceConfiguration.setProvisioningType(this.provisioningType); - instanceConfiguration.setMaxRunDurationSeconds(this.maxRunDurationSeconds); instanceConfiguration.setMinCpuPlatform(this.minCpuPlatform); + instanceConfiguration.setProvisioningType(this.provisioningType); instanceConfiguration.setLabelString(this.labels); instanceConfiguration.setRunAsUser(this.runAsUser); instanceConfiguration.setWindowsConfiguration(this.windowsConfiguration); diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java index 7b490a86..45fa3473 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java @@ -33,10 +33,5 @@ public static class DescriptorImpl extends ProvisioningTypeDescriptor { public String getDisplayName() { return "Preemptible VM"; } - - @Override - public ProvisioningTypeValue getProvisioningTypeValue() { - return ProvisioningTypeValue.PREEMPTIBLE; - } } } diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java index d76deb76..60d09222 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java @@ -34,12 +34,11 @@ public ProvisioningTypeValue getValue() { return value; } + @SuppressWarnings("unused") @DataBoundSetter public void setProvisioningTypeValue(ProvisioningTypeValue value) { this.value = value; } - public abstract static class ProvisioningTypeDescriptor extends Descriptor { - public abstract ProvisioningTypeValue getProvisioningTypeValue(); - } + public abstract static class ProvisioningTypeDescriptor extends Descriptor {} } diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java index 4cc69bb0..9c9b9916 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java @@ -17,16 +17,30 @@ package com.google.jenkins.plugins.computeengine.ui.helpers; import hudson.Extension; +import hudson.util.FormValidation; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; @SuppressWarnings("unused") public class SpotVm extends ProvisioningType { + @DataBoundSetter + private long maxRunDurationSeconds; + @DataBoundConstructor public SpotVm() { super(ProvisioningTypeValue.SPOT); } + public void setMaxRunDurationSeconds(long maxRunDurationSeconds) { + this.maxRunDurationSeconds = maxRunDurationSeconds; + } + + public long getMaxRunDurationSeconds() { + return maxRunDurationSeconds; + } + @Extension public static class DescriptorImpl extends ProvisioningTypeDescriptor { @Override @@ -34,9 +48,8 @@ public String getDisplayName() { return "Spot VM"; } - @Override - public ProvisioningTypeValue getProvisioningTypeValue() { - return ProvisioningTypeValue.SPOT; + public FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) { + return Utils.doCheckMaxRunDurationSeconds(value); } } } diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java index 93b2d1ae..bf52754b 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java @@ -17,16 +17,30 @@ package com.google.jenkins.plugins.computeengine.ui.helpers; import hudson.Extension; +import hudson.util.FormValidation; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; @SuppressWarnings("unused") public class Standard extends ProvisioningType { + @DataBoundSetter + private long maxRunDurationSeconds; + @DataBoundConstructor public Standard() { super(ProvisioningTypeValue.STANDARD); } + public void setMaxRunDurationSeconds(long maxRunDurationSeconds) { + this.maxRunDurationSeconds = maxRunDurationSeconds; + } + + public long getMaxRunDurationSeconds() { + return maxRunDurationSeconds; + } + @Extension public static class DescriptorImpl extends ProvisioningTypeDescriptor { @Override @@ -34,9 +48,8 @@ public String getDisplayName() { return "Standard"; } - @Override - public ProvisioningTypeValue getProvisioningTypeValue() { - return ProvisioningTypeValue.STANDARD; + public FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) { + return Utils.doCheckMaxRunDurationSeconds(value); } } } diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Utils.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Utils.java new file mode 100644 index 00000000..fc7930a5 --- /dev/null +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Utils.java @@ -0,0 +1,35 @@ +/* + * 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.ui.helpers; + +import hudson.util.FormValidation; +import org.kohsuke.stapler.QueryParameter; + +public class Utils { + + public static FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) { + try { + long maxRunDurationSeconds = Long.parseLong(value); + if (maxRunDurationSeconds < 0) { + return FormValidation.error("Max run duration must be greater than or equal to 0"); + } + return FormValidation.ok(); + } catch (NumberFormatException e) { + return FormValidation.error("Max run duration must be non-negative number"); + } + } +} diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly index 4df7656e..2c414d8f 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly @@ -86,16 +86,14 @@ + - - - - diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-provisioningType.html b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-provisioningType.html index 48215c46..4e1242b2 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-provisioningType.html +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-provisioningType.html @@ -16,23 +16,25 @@
GCP offers three VM provisioning options, each with different availability, pricing, and termination policies. - The "Max Run Duration Seconds" field below applies to all three types.
  • Standard: Stable provisioning model with guaranteed availability by GCP SLAs. Higher pricing compared to Spot and Preemptible VMs. + Use the "Max Run Duration Seconds" field to limit the instance duration and enable automatic deletion.
  • Spot: Low-cost instances that may be terminated if resources are needed elsewhere. Availability varies. - Unlike Preemptible VMs, they are not always terminated after 24 hours. Spot and Preemptible VMs are priced the same. - Set the "Max Run Duration Seconds" field below to ensure termination after a specific duration. + Unlike Preemptible VMs, they are not always terminated after 24 hours. + Spot and Preemptible VMs are priced the same. + Use the "Max Run Duration Seconds" field to ensure termination after a specific duration.

    See Spot VMs for more details.

  • Preemptible: Low-cost instances that may be terminated if resources are needed for other tasks. - Availability varies with usage. Spot and Preemptible VMs are priced the same. + Availability varies. Spot and Preemptible VMs are priced the same. + The "Max Run Duration Seconds" field does not apply to Preemptible instances.

    See the Preemptible VM Instances documentation for more information.

    diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm/config-detail.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm/config-detail.jelly new file mode 100644 index 00000000..48881af1 --- /dev/null +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm/config-detail.jelly @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/config.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/config.jelly new file mode 100644 index 00000000..74e4e986 --- /dev/null +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/config.jelly @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/config-detail.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/config-detail.jelly new file mode 100644 index 00000000..36340ade --- /dev/null +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/config-detail.jelly @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-maxRunDurationSeconds.html b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/help-maxRunDurationSeconds.html similarity index 100% rename from src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/help-maxRunDurationSeconds.html rename to src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/help-maxRunDurationSeconds.html diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/config-detail.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/config-detail.jelly new file mode 100644 index 00000000..36340ade --- /dev/null +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/config-detail.jelly @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/help-maxRunDurationSeconds.html b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/help-maxRunDurationSeconds.html new file mode 100644 index 00000000..c26d22d2 --- /dev/null +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/help-maxRunDurationSeconds.html @@ -0,0 +1,19 @@ + +
    + The maximum duration (in seconds) after which the VM will be automatically deleted by GCP. + See Limit the run time of a VM for more details. +
    \ No newline at end of file From d57ea0cb598ac9c2b962cb04cc26d6a10b7a8554 Mon Sep 17 00:00:00 2001 From: guruprasad Date: Thu, 12 Dec 2024 14:17:07 +0530 Subject: [PATCH 05/19] update comments --- .../plugins/computeengine/ui/helpers/Utils.java | 4 ++-- .../ui/helpers/PreemptibleVm/config-detail.jelly | 16 +++++++++++++++- .../ui/helpers/ProvisioningType/config.jelly | 15 +++++++++++++++ .../ui/helpers/SpotVm/config-detail.jelly | 16 +++++++++++++++- .../ui/helpers/Standard/config-detail.jelly | 16 +++++++++++++++- 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Utils.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Utils.java index fc7930a5..5e286eec 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Utils.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Utils.java @@ -19,9 +19,9 @@ import hudson.util.FormValidation; import org.kohsuke.stapler.QueryParameter; -public class Utils { +class Utils { - public static FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) { + static FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) { try { long maxRunDurationSeconds = Long.parseLong(value); if (maxRunDurationSeconds < 0) { diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm/config-detail.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm/config-detail.jelly index 48881af1..adc39644 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm/config-detail.jelly +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm/config-detail.jelly @@ -1,4 +1,18 @@ - + diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/config.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/config.jelly index 74e4e986..b7da155a 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/config.jelly +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/config.jelly @@ -1,3 +1,18 @@ + diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/config-detail.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/config-detail.jelly index 36340ade..aa657710 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/config-detail.jelly +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/config-detail.jelly @@ -1,4 +1,18 @@ - + diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/config-detail.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/config-detail.jelly index 36340ade..aa657710 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/config-detail.jelly +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/config-detail.jelly @@ -1,4 +1,18 @@ - + From 579410e0f3bd12d72b4fb78ed2915c429ead477e Mon Sep 17 00:00:00 2001 From: guruprasad Date: Fri, 13 Dec 2024 18:24:25 +0530 Subject: [PATCH 06/19] add an integration test for spot vms with maximum run durations --- docs/integration-tests.md | 67 ++++++++ .../computeengine/ComputeEngineComputer.java | 2 +- .../computeengine/InstanceConfiguration.java | 14 +- .../computeengine/client/ClientUtil.java | 15 ++ .../integration/ComputeClientIT.java | 2 +- .../computeengine/integration/ITUtil.java | 9 +- .../integration/MaxRunDurationCasCIT.java | 144 ++++++++++++++++++ .../casc-max-run-duration-agent-it.yml | 42 +++++ 8 files changed, 285 insertions(+), 10 deletions(-) create mode 100644 docs/integration-tests.md create mode 100644 src/test/java/com/google/jenkins/plugins/computeengine/integration/MaxRunDurationCasCIT.java create mode 100644 src/test/resources/com/google/jenkins/plugins/computeengine/integration/casc-max-run-duration-agent-it.yml diff --git a/docs/integration-tests.md b/docs/integration-tests.md new file mode 100644 index 00000000..855eec89 --- /dev/null +++ b/docs/integration-tests.md @@ -0,0 +1,67 @@ +# Integration Tests + +* GCP Project +* Create a service account with relevant access - See [Refer to IAM Credentials](Home.md#iam-credentials) +* Most of the tests require a VM with Java pre-installed at `/usr/bin/java`. + There are one or two which don't require java pre-installed, as they supply `startup-script` to gcloud apis, and install `java` on a plain linux Debian image. + * You can create an image with `java` preinstalled as, + ```bash + project= + zone= + + # Create a debian based VM + gcloud compute instances create java-install-instance \ + --project=$project \ + --zone=$zone \ + --machine-type=e2-medium \ + --image-project=debian-cloud \ + --image-family=debian-12 + + # Wait for the machine to start and access ssh connections. Install java via ssh + gcloud compute ssh java-install-instance \ + --project=$project \ + --zone=$zone \ + --command="sudo apt-get update && sudo apt-get install -y openjdk-17-jdk" + + # Ensure java is installed and print the java path + gcloud compute ssh java-install-instance \ + --project=$project \ + --zone=$zone \ + --command="java -version" + + gcloud compute ssh java-install-instance \ + --project=$project \ + --zone=$zone \ + --command="which java" + + # For creating image, you need to first stop the VM + gcloud compute instances stop java-install-instance \ + --project=$project \ + --zone=$zone \ + + # Create an image from the VM + gcloud compute images create java-debian-12-image \ + --source-disk=java-install-instance \ + --source-disk-zone=$zone \ + --project=$project \ + --family=custom-java-debian-family + + # Delete the VM + gcloud compute instances delete java-install-instance \ + --project=$project \ + --zone=$zone + ``` +* Export these environment variables + ```bash + export GOOGLE_PROJECT_ID= + export GOOGLE_SA_NAME= + export GOOGLE_CREDENTIALS_FILE= + export GOOGLE_ZONE= + export GOOGLE_REGION= + export GOOGLE_BOOT_DISK_PROJECT_ID= + export GOOGLE_BOOT_DISK_IMAGE_NAME=java-debian-12-image # this is created in previous step + ``` +* Execute an integration test (example) + ```bash + mvn clean test -Dtest=ComputeEngineCloudRestartPreemptedIT#testIfNodeWasPreempted + ``` diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ComputeEngineComputer.java b/src/main/java/com/google/jenkins/plugins/computeengine/ComputeEngineComputer.java index 473dcb60..acdbbb0a 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ComputeEngineComputer.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ComputeEngineComputer.java @@ -44,7 +44,7 @@ void onConnected(TaskListener listener) { ComputeEngineInstance node = getNode(); if (node != null) { node.onConnected(); - if (getPreemptible()) { + if (getPreemptible()) { // TODO: maybe need to handle similarly when `maxRunDuration` is set. String nodeName = node.getNodeName(); final String msg = "Instance " + nodeName + " is preemptive, setting up preemption listener"; log.log(Level.INFO, msg); diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java index ac22ab50..00ddcb61 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java @@ -510,15 +510,13 @@ private Tags tags() { private Scheduling scheduling() { Scheduling scheduling = new Scheduling(); long maxRunDurationSeconds = 0; - if (provisioningType != null) { + if (provisioningType != null) { // check `null` for backward compatibility ProvisioningTypeValue ptValue = provisioningType.getValue(); if (ptValue == PREEMPTIBLE) { scheduling.setPreemptible(true); } else if (provisioningType.getValue() == SPOT) { maxRunDurationSeconds = ((SpotVm) provisioningType).getMaxRunDurationSeconds(); scheduling.setProvisioningModel("SPOT"); - // only the instance is deleted, the disk deletion is based on bootDiskAutoDelete config value - scheduling.setInstanceTerminationAction("DELETE"); } else { maxRunDurationSeconds = ((Standard) provisioningType).getMaxRunDurationSeconds(); } @@ -526,7 +524,13 @@ private Scheduling scheduling() { GenericJson j = new GenericJson(); j.set("seconds", maxRunDurationSeconds); scheduling.set("maxRunDuration", j); - // only the instance is deleted, the disk deletion is based on bootDiskAutoDelete config value + /* 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"); } } else if (preemptible) { // keeping the check for `preemptible` for backward compatibility @@ -1007,6 +1011,8 @@ public InstanceConfiguration build() { instanceConfiguration.setNumExecutorsStr(this.numExecutorsStr); instanceConfiguration.setStartupScript(this.startupScript); instanceConfiguration.setMinCpuPlatform(this.minCpuPlatform); + // even though `preemptible` is deprecated, we still set it here for backward compatibility + instanceConfiguration.setPreemptible(this.preemptible); instanceConfiguration.setProvisioningType(this.provisioningType); instanceConfiguration.setLabelString(this.labels); instanceConfiguration.setRunAsUser(this.runAsUser); diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/client/ClientUtil.java b/src/main/java/com/google/jenkins/plugins/computeengine/client/ClientUtil.java index cfcd5381..2413d259 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/client/ClientUtil.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/client/ClientUtil.java @@ -13,6 +13,7 @@ import com.google.jenkins.plugins.credentials.oauth.GoogleOAuth2Credentials; import com.google.jenkins.plugins.credentials.oauth.GoogleRobotCredentials; import hudson.AbortException; +import hudson.Main; import hudson.model.ItemGroup; import hudson.security.ACL; import java.io.IOException; @@ -73,6 +74,20 @@ private static GoogleRobotCredentials getRobotCredentials( ItemGroup itemGroup, List domainRequirements, String credentialsId) throws AbortException { + /* During the integration tests, the parameter `credentialId`= that we have set during + integration test. But the actual credential created within Jenkins is having `id` as a random UUID. + + So the `CredentialsMatchers.firstOrNull` was returning `null` due to `CredentialsMatchers.withId(credentialsId)` + */ + if (Main.isUnitTest) { + var credentialList = CredentialsProvider.lookupCredentials( + GoogleOAuth2Credentials.class, itemGroup, ACL.SYSTEM, domainRequirements); + if (!credentialList.isEmpty()) { + return (GoogleRobotCredentials) credentialList.get(0); + } + return null; + } + GoogleOAuth2Credentials credentials = CredentialsMatchers.firstOrNull( CredentialsProvider.lookupCredentials( GoogleOAuth2Credentials.class, itemGroup, ACL.SYSTEM, domainRequirements), diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/integration/ComputeClientIT.java b/src/test/java/com/google/jenkins/plugins/computeengine/integration/ComputeClientIT.java index 19bc4b34..da1d08c9 100644 --- a/src/test/java/com/google/jenkins/plugins/computeengine/integration/ComputeClientIT.java +++ b/src/test/java/com/google/jenkins/plugins/computeengine/integration/ComputeClientIT.java @@ -58,7 +58,7 @@ public static void teardown() throws IOException { @Test public void testGetImage() throws Exception { - Image image = client.getImage("debian-cloud", "debian-9-stretch-v20180820"); + Image image = client.getImage("debian-cloud", "debian-12-bookworm-v20241210"); assertNotNull(image); assertEquals("READY", image.getStatus()); } diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/integration/ITUtil.java b/src/test/java/com/google/jenkins/plugins/computeengine/integration/ITUtil.java index 1810ac00..68b98fab 100644 --- a/src/test/java/com/google/jenkins/plugins/computeengine/integration/ITUtil.java +++ b/src/test/java/com/google/jenkins/plugins/computeengine/integration/ITUtil.java @@ -111,12 +111,13 @@ class ITUtil { private static final String CONFIG_DESC = "integration"; private static final String BOOT_DISK_TYPE = ZONE_BASE + "/diskTypes/pd-ssd"; private static final boolean BOOT_DISK_AUTODELETE = true; - private static final String BOOT_DISK_PROJECT_ID = - windows ? System.getenv("GOOGLE_BOOT_DISK_PROJECT_ID") : "debian-cloud"; - private static final String BOOT_DISK_IMAGE_NAME = windows + private static final String BOOT_DISK_PROJECT_ID = System.getenv("GOOGLE_BOOT_DISK_PROJECT_ID") != null + ? System.getenv("GOOGLE_BOOT_DISK_PROJECT_ID") + : "debian" + "-cloud"; + private static final String BOOT_DISK_IMAGE_NAME = System.getenv("GOOGLE_BOOT_DISK_IMAGE_NAME") != null ? String.format( "projects/%s/global/images/%s", BOOT_DISK_PROJECT_ID, System.getenv("GOOGLE_BOOT_DISK_IMAGE_NAME")) - : "projects/debian-cloud/global/images/family/debian-9"; + : "projects/debian-cloud/global/images/family/debian-12"; private static final String BOOT_DISK_SIZE_GB_STR = windows ? "50" : "10"; private static final Node.Mode NODE_MODE = Node.Mode.EXCLUSIVE; private static final String ACCELERATOR_NAME = ""; diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/integration/MaxRunDurationCasCIT.java b/src/test/java/com/google/jenkins/plugins/computeengine/integration/MaxRunDurationCasCIT.java new file mode 100644 index 00000000..1d109d40 --- /dev/null +++ b/src/test/java/com/google/jenkins/plugins/computeengine/integration/MaxRunDurationCasCIT.java @@ -0,0 +1,144 @@ +/* + * 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.integration; + +import static com.google.jenkins.plugins.computeengine.integration.ITUtil.LABEL; +import static com.google.jenkins.plugins.computeengine.integration.ITUtil.PROJECT_ID; +import static com.google.jenkins.plugins.computeengine.integration.ITUtil.ZONE; +import static com.google.jenkins.plugins.computeengine.integration.ITUtil.execute; +import static com.google.jenkins.plugins.computeengine.integration.ITUtil.getLabel; +import static com.google.jenkins.plugins.computeengine.integration.ITUtil.initClient; +import static com.google.jenkins.plugins.computeengine.integration.ITUtil.initCloud; +import static com.google.jenkins.plugins.computeengine.integration.ITUtil.initCredentials; +import static com.google.jenkins.plugins.computeengine.integration.ITUtil.windows; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; + +import com.google.api.client.util.ArrayMap; +import com.google.api.services.compute.model.Instance; +import com.google.api.services.compute.model.Scheduling; +import com.google.cloud.graphite.platforms.plugin.client.ComputeClient; +import com.google.jenkins.plugins.computeengine.ComputeEngineCloud; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.Result; +import hudson.model.labels.LabelAtom; +import hudson.slaves.NodeProvisioner.PlannedNode; +import hudson.tasks.Builder; +import io.jenkins.plugins.casc.ConfigurationAsCode; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import lombok.extern.java.Log; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.Timeout; +import org.jvnet.hudson.test.JenkinsRule; + +@Log +public class MaxRunDurationCasCIT { + + @ClassRule + public static Timeout timeout = new Timeout(20, TimeUnit.MINUTES); + + @ClassRule + public static JenkinsRule j = new JenkinsRule(); + + private static ComputeClient client; + private static ComputeEngineCloud cloud; + private static Map label = getLabel(MaxRunDurationCasCIT.class); + + @BeforeClass + public static void init() throws Exception { + assumeFalse(windows); + log.info("init"); + initCredentials(j); + cloud = initCloud(j); + client = initClient(j, label, log); + } + + @Test + public void testMaxRunDurationDeletesAndNoNewBuilds() throws Exception { + assumeFalse(windows); + ConfigurationAsCode.get() + .configure(Objects.requireNonNull(this.getClass().getResource("casc-max-run-duration-agent-it.yml")) + .toString()); + ComputeEngineCloud cloud = (ComputeEngineCloud) j.jenkins.clouds.getByName("gce-integration"); + cloud.getConfigurations().get(0).setGoogleLabels(label); + Collection planned = cloud.provision(new LabelAtom(LABEL), 1); + planned.iterator().next().future.get(); // wait for the node creation to finish + Instance instance = + client.getInstance(PROJECT_ID, ZONE, planned.iterator().next().displayName); + String instanceName = instance.getName(); + log.info("Instance: " + instance.getName()); + + // assert the scheduling configurations. + Scheduling sch = instance.getScheduling(); + assertEquals("SPOT", sch.get("provisioningModel")); + assertEquals("DELETE", sch.get("instanceTerminationAction")); + assertEquals(180, Integer.parseInt((String) ((ArrayMap) sch.get("maxRunDuration")).get("seconds"))); + log.info("instance scheduling configs are correct"); + + // try to execute a build on the agent + FreeStyleProject fp = j.createFreeStyleProject(); + Builder step = execute(Commands.ECHO, "hello world"); + fp.getBuildersList().add(step); + fp.setAssignedLabel(new LabelAtom(LABEL)); + Future buildFuture = fp.scheduleBuild2(0); + FreeStyleBuild build = buildFuture.get(); + assertEquals(Result.SUCCESS, build.getResult()); + String agent1 = printLogsAndReturnAgentName(build); + log.info("first build completed"); + assertEquals(agent1, instanceName); + + // wait for 3 minutes to make sure the instance is fully deleted due to `maxRunDuration` + log.info("sleeping 180s to make sure the instance is deleted"); + TimeUnit.SECONDS.sleep(180); + log.info("sleeping completed"); + + // assert there are no nodes remaining; + assertTrue(client.listInstancesWithLabel(PROJECT_ID, label).isEmpty()); + + // trigger another build, notice a new instance is being created + log.info("proceeding to 2nd build, after no remaining instances"); + buildFuture = fp.scheduleBuild2(0); + build = buildFuture.get(); + String agent2 = printLogsAndReturnAgentName(build); + log.info("second build completed"); + + assertNotEquals(agent1, agent2); + } + + private static String printLogsAndReturnAgentName(FreeStyleBuild build) throws IOException { + List logs = build.getLog(50); + String agentName = null; + for (String line : logs) { + if (line.contains("Building remotely on")) { + agentName = line.split(" ")[3]; + } + log.info(line); + } + return agentName; + } +} diff --git a/src/test/resources/com/google/jenkins/plugins/computeengine/integration/casc-max-run-duration-agent-it.yml b/src/test/resources/com/google/jenkins/plugins/computeengine/integration/casc-max-run-duration-agent-it.yml new file mode 100644 index 00000000..f918f28a --- /dev/null +++ b/src/test/resources/com/google/jenkins/plugins/computeengine/integration/casc-max-run-duration-agent-it.yml @@ -0,0 +1,42 @@ +jenkins: + clouds: + - computeEngine: + cloudName: integration + projectId: ${env.GOOGLE_PROJECT_ID} + instanceCapStr: 10 + credentialsId: ${env.GOOGLE_PROJECT_ID} + configurations: + - namePrefix: max-run-duration + description: max-run-duration + launchTimeoutSecondsStr: '' + retentionTimeMinutesStr: '' + mode: EXCLUSIVE + labelString: integration + numExecutorsStr: 1 + runAsUser: jenkins + remoteFs: '' + oneShot: true + createSnapshot: false + region: "https://www.googleapis.com/compute/v1/projects/${env.GOOGLE_PROJECT_ID}/regions/${env.GOOGLE_REGION}" + zone: "https://www.googleapis.com/compute/v1/projects/${env.GOOGLE_PROJECT_ID}/zones/${env.GOOGLE_ZONE}" + template: '' # tried not setting, added when 'saved' in UI + machineType: "https://www.googleapis.com/compute/v1/projects/${env.GOOGLE_PROJECT_ID}/zones/${env.GOOGLE_ZONE}/machineTypes/n1-standard-1" + javaExecPath: '/usr/bin/java' + provisioningType: + SpotVm: + maxRunDurationSeconds: 180 + networkConfiguration: + autofilled: + network: default + subnetwork: default + networkTags: "jenkins-agent ssh" + networkInterfaceIpStackMode: + singleStack: + externalIPV4Address: true + useInternalAddress: false + bootDiskSourceImageProject: ${env.GOOGLE_BOOT_DISK_PROJECT_ID} + bootDiskSourceImageName: "projects/${env.GOOGLE_BOOT_DISK_PROJECT_ID}/global/images/${env.GOOGLE_BOOT_DISK_IMAGE_NAME}" + bootDiskType: "https://www.googleapis.com/compute/v1/projects/${env.GOOGLE_PROJECT_ID}/zones/${env.GOOGLE_ZONE}/diskTypes/pd-standard" + bootDiskSizeGbStr: 10 + bootDiskAutoDelete: true + serviceAccountEmail: "${env.GOOGLE_SA_NAME}@${env.GOOGLE_PROJECT_ID}.iam.gserviceaccount.com" From 17a011391ea2ba8e17fd3e4c43453790250c04fb Mon Sep 17 00:00:00 2001 From: guruprasad Date: Mon, 16 Dec 2024 14:09:12 +0530 Subject: [PATCH 07/19] Fix trailing slash --- docs/integration-tests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integration-tests.md b/docs/integration-tests.md index 855eec89..81f2b81a 100644 --- a/docs/integration-tests.md +++ b/docs/integration-tests.md @@ -37,7 +37,7 @@ # For creating image, you need to first stop the VM gcloud compute instances stop java-install-instance \ --project=$project \ - --zone=$zone \ + --zone=$zone # Create an image from the VM gcloud compute images create java-debian-12-image \ From 3ddd4d3604b6fc51252d2d18a53465f897b2a754 Mon Sep 17 00:00:00 2001 From: guruprasad Date: Mon, 16 Dec 2024 22:17:06 +0530 Subject: [PATCH 08/19] update tests and new tests for scheduling code --- pom.xml | 16 ++- .../computeengine/InstanceConfiguration.java | 4 +- .../computeengine/SchedulingTests.java | 111 ++++++++++++++++++ ...ProvisioningWithMaxRunDurationCasCIT.java} | 77 +++++++----- .../testMaxRunDurationSpot/config.xml | 23 ++++ .../testMaxRunDurationStandard/config.xml | 23 ++++ .../config.xml | 22 ++++ .../config.xml | 19 +++ .../testPreemptibleCompatibility/config.xml | 20 ++++ .../config.xml | 23 ++++ ...ovisioning-with-max-run-duration-casc.yml} | 2 +- 11 files changed, 308 insertions(+), 32 deletions(-) create mode 100644 src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java rename src/test/java/com/google/jenkins/plugins/computeengine/integration/{MaxRunDurationCasCIT.java => SpotVmProvisioningWithMaxRunDurationCasCIT.java} (62%) create mode 100644 src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testMaxRunDurationSpot/config.xml create mode 100644 src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testMaxRunDurationStandard/config.xml create mode 100644 src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testNoMaxRunDurationPreemptible/config.xml create mode 100644 src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testNoProvisioningTypeCompatibility/config.xml create mode 100644 src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testPreemptibleCompatibility/config.xml create mode 100644 src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testProvisioningTypeOverridesPreemptible/config.xml rename src/test/resources/com/google/jenkins/plugins/computeengine/integration/{casc-max-run-duration-agent-it.yml => spot-vm-provisioning-with-max-run-duration-casc.yml} (98%) diff --git a/pom.xml b/pom.xml index f3d480a9..64c91e22 100644 --- a/pom.xml +++ b/pom.xml @@ -269,7 +269,21 @@ ${powershell.version} test - + + org.jenkins-ci.plugins.workflow + workflow-cps + test + + + org.jenkins-ci.plugins.workflow + workflow-durable-task-step + test + + + org.jenkins-ci.plugins.workflow + workflow-job + test + org.mockito mockito-core diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java index 00ddcb61..27f1ab87 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java @@ -40,6 +40,7 @@ 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.ssh.GoogleKeyCredential; @@ -507,7 +508,8 @@ private Tags tags() { return null; } - private Scheduling scheduling() { + @VisibleForTesting + Scheduling scheduling() { Scheduling scheduling = new Scheduling(); long maxRunDurationSeconds = 0; if (provisioningType != null) { // check `null` for backward compatibility diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java b/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java new file mode 100644 index 00000000..92d45db2 --- /dev/null +++ b/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java @@ -0,0 +1,111 @@ +package com.google.jenkins.plugins.computeengine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.google.api.client.json.GenericJson; +import com.google.api.services.compute.model.Scheduling; +import com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningTypeValue; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.recipes.LocalData; + +public class SchedulingTests { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + private Scheduling getScheduling() { + ComputeEngineCloud cloud = (ComputeEngineCloud) j.jenkins.clouds.getByName("gce-unit-tests"); + assertEquals(1, cloud.getConfigurations().size()); + return cloud.getConfigurations().get(0).scheduling(); + } + + /** + * When the previously used {@code preemptible} configuration is loaded, there should be no error in initializing + * of the new field {@code provisioningType} as well as with building effective `scheduling` configurations. No + * other provisioning model should be set. + */ + @Test + @LocalData + public void testPreemptibleCompatibility() { + Scheduling sch = getScheduling(); + assertTrue(sch.getPreemptible()); + assertNull(sch.getProvisioningModel()); + assertNull(sch.get("maxRunDuration")); + assertNull(sch.getInstanceTerminationAction()); + } + + /** + * Without any scheduling configuration as well the `scheduling` should still build correctly. + * Effectively results in Standard VM on GCP side. + */ + @Test + @LocalData + public void testNoProvisioningTypeCompatibility() { + Scheduling sch = getScheduling(); + assertNull(sch.getPreemptible()); + assertNull(sch.getProvisioningModel()); + assertNull(sch.get("maxRunDuration")); + assertNull(sch.getInstanceTerminationAction()); + } + + /** + * When the new {@code provisioningType} is updated by the user, the old {@code preemptible} configuration should + * be ignored. Even for a given {@code provisioningType} the {@code maxRunDurationSeconds} is still optional. + */ + @Test + @LocalData + public void testProvisioningTypeOverridesPreemptible() { + Scheduling sch = getScheduling(); + assertNull(sch.getPreemptible()); + assertEquals("SPOT", sch.getProvisioningModel()); + assertNull(sch.get("maxRunDuration")); + assertNull(sch.getInstanceTerminationAction()); + } + + /** + * When the standard type vm is being provisioned, the {@code maxRunDurationSeconds} should be applicable if + * existing. No value should be set for the provisioning model, and GCP will choose the Standard as the + * default model. + */ + @Test + @LocalData + public void testMaxRunDurationStandard() { + Scheduling sch = getScheduling(); + GenericJson maxRunDuration = (GenericJson) sch.get("maxRunDuration"); + assertEquals(100L, maxRunDuration.get("seconds")); + assertNull(sch.getProvisioningModel()); + assertNull(sch.getPreemptible()); + assertEquals("DELETE", sch.getInstanceTerminationAction()); + } + + @Test + @LocalData + public void testMaxRunDurationSpot() { + Scheduling sch = getScheduling(); + GenericJson maxRunDuration = (GenericJson) sch.get("maxRunDuration"); + assertEquals(120L, maxRunDuration.get("seconds")); + assertEquals(ProvisioningTypeValue.SPOT, ProvisioningTypeValue.valueOf(sch.getProvisioningModel())); + assertNull(sch.getPreemptible()); + assertEquals("DELETE", sch.getInstanceTerminationAction()); + } + + /** + * When the {@code preemptible} provisioning is configured, the getter and setters of the SDK should be used, and + * not the generic {@code provisioningModel} field. + * Also {@code maxRunDuration} should not be set as that will result in error (i.e. preemptible vm doesn't support) + * even if setting to 0. + */ + @Test + @LocalData + public void testNoMaxRunDurationPreemptible() { + Scheduling sch = getScheduling(); + assertNull(sch.get("maxRunDuration")); + assertNull(sch.getProvisioningModel()); + assertTrue(sch.getPreemptible()); + assertNull(sch.getInstanceTerminationAction()); + } +} diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/integration/MaxRunDurationCasCIT.java b/src/test/java/com/google/jenkins/plugins/computeengine/integration/SpotVmProvisioningWithMaxRunDurationCasCIT.java similarity index 62% rename from src/test/java/com/google/jenkins/plugins/computeengine/integration/MaxRunDurationCasCIT.java rename to src/test/java/com/google/jenkins/plugins/computeengine/integration/SpotVmProvisioningWithMaxRunDurationCasCIT.java index 1d109d40..112200cb 100644 --- a/src/test/java/com/google/jenkins/plugins/computeengine/integration/MaxRunDurationCasCIT.java +++ b/src/test/java/com/google/jenkins/plugins/computeengine/integration/SpotVmProvisioningWithMaxRunDurationCasCIT.java @@ -25,9 +25,9 @@ import static com.google.jenkins.plugins.computeengine.integration.ITUtil.initCloud; import static com.google.jenkins.plugins.computeengine.integration.ITUtil.initCredentials; import static com.google.jenkins.plugins.computeengine.integration.ITUtil.windows; +import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; import com.google.api.client.util.ArrayMap; @@ -50,48 +50,65 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import lombok.extern.java.Log; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.Timeout; import org.jvnet.hudson.test.JenkinsRule; @Log -public class MaxRunDurationCasCIT { +public class SpotVmProvisioningWithMaxRunDurationCasCIT { - @ClassRule - public static Timeout timeout = new Timeout(20, TimeUnit.MINUTES); + // Increase the timeout from default 180s to 10minutes. + static { + System.setProperty("jenkins.test.timeout", "600"); + } @ClassRule public static JenkinsRule j = new JenkinsRule(); private static ComputeClient client; - private static ComputeEngineCloud cloud; - private static Map label = getLabel(MaxRunDurationCasCIT.class); + private static final Map vmLabels = getLabel(SpotVmProvisioningWithMaxRunDurationCasCIT.class); @BeforeClass public static void init() throws Exception { assumeFalse(windows); log.info("init"); initCredentials(j); - cloud = initCloud(j); - client = initClient(j, label, log); + initCloud(j); + client = initClient(j, vmLabels, log); } + /** + * Ensures that an agent VM is provisioned for the first build (freestyle), and waits for the VM to be removed due to `maxRunDuration`. + * Then schedules another build (pipeline), resulting in a new agent VM creation and success. + *

    + * This test verifies that VM deletion on GCP does occur for `maxRunDuration` setting, and Jenkins agent is not + * going to be in a wrong state, but gets deleted as well. + *

    + * It covers both freestyle and pipeline builds. + */ @Test public void testMaxRunDurationDeletesAndNoNewBuilds() throws Exception { assumeFalse(windows); ConfigurationAsCode.get() - .configure(Objects.requireNonNull(this.getClass().getResource("casc-max-run-duration-agent-it.yml")) + .configure(Objects.requireNonNull( + this.getClass().getResource("spot-vm-provisioning-with-max-run-duration-casc.yml")) .toString()); + + // verify no nodes to start with. + assertEquals(0, j.jenkins.getNodes().size()); + ComputeEngineCloud cloud = (ComputeEngineCloud) j.jenkins.clouds.getByName("gce-integration"); - cloud.getConfigurations().get(0).setGoogleLabels(label); + cloud.getConfigurations().get(0).setGoogleLabels(vmLabels); Collection planned = cloud.provision(new LabelAtom(LABEL), 1); planned.iterator().next().future.get(); // wait for the node creation to finish Instance instance = client.getInstance(PROJECT_ID, ZONE, planned.iterator().next().displayName); String instanceName = instance.getName(); log.info("Instance: " + instance.getName()); + assertEquals(1, j.jenkins.getNodes().size()); // assert the scheduling configurations. Scheduling sch = instance.getScheduling(); @@ -100,7 +117,7 @@ public void testMaxRunDurationDeletesAndNoNewBuilds() throws Exception { assertEquals(180, Integer.parseInt((String) ((ArrayMap) sch.get("maxRunDuration")).get("seconds"))); log.info("instance scheduling configs are correct"); - // try to execute a build on the agent + // try to execute a build on the agent (freestyle type) FreeStyleProject fp = j.createFreeStyleProject(); Builder step = execute(Commands.ECHO, "hello world"); fp.getBuildersList().add(step); @@ -108,34 +125,36 @@ public void testMaxRunDurationDeletesAndNoNewBuilds() throws Exception { Future buildFuture = fp.scheduleBuild2(0); FreeStyleBuild build = buildFuture.get(); assertEquals(Result.SUCCESS, build.getResult()); - String agent1 = printLogsAndReturnAgentName(build); - log.info("first build completed"); + String agent1 = printLogsAndReturnAgentName(build.getLog(50)); + log.info("first build completed on" + agent1); assertEquals(agent1, instanceName); - // wait for 3 minutes to make sure the instance is fully deleted due to `maxRunDuration` - log.info("sleeping 180s to make sure the instance is deleted"); - TimeUnit.SECONDS.sleep(180); - log.info("sleeping completed"); - - // assert there are no nodes remaining; - assertTrue(client.listInstancesWithLabel(PROJECT_ID, label).isEmpty()); + // wait for the agent to be removed due to maxRunDuration; retention time is far more 30 minutes - so the + // deletion should be due to maxRunDuration. + await("Jenkins agent to be removed due to maxRunDuration well before the retention time") + .atMost(180, TimeUnit.SECONDS) + .until(() -> j.jenkins.getNodes().isEmpty()); + await("GCP VM deletion to complete due to maxRunDuration") + .atMost(180, TimeUnit.SECONDS) + .until(() -> client.listInstancesWithLabel(PROJECT_ID, vmLabels).isEmpty()); - // trigger another build, notice a new instance is being created + // trigger another build, notice a new instance is being created (pipeline type) log.info("proceeding to 2nd build, after no remaining instances"); - buildFuture = fp.scheduleBuild2(0); - build = buildFuture.get(); - String agent2 = printLogsAndReturnAgentName(build); - log.info("second build completed"); - + var p = j.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition("node('" + LABEL + "') { sh 'date' }", true)); + var run = j.assertBuildStatus(Result.SUCCESS, p.scheduleBuild2(0).get()); + String agent2 = printLogsAndReturnAgentName(run.getLog(50)); + log.info("second build completed on " + agent2); assertNotEquals(agent1, agent2); } - private static String printLogsAndReturnAgentName(FreeStyleBuild build) throws IOException { - List logs = build.getLog(50); + private static String printLogsAndReturnAgentName(List logs) throws IOException { String agentName = null; for (String line : logs) { if (line.contains("Building remotely on")) { agentName = line.split(" ")[3]; + } else if (line.contains("Running on")) { + agentName = line.split(" ")[2]; } log.info(line); } diff --git a/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testMaxRunDurationSpot/config.xml b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testMaxRunDurationSpot/config.xml new file mode 100644 index 00000000..64977e34 --- /dev/null +++ b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testMaxRunDurationSpot/config.xml @@ -0,0 +1,23 @@ + + + 2.452.3 + + + + ${JENKINS_HOME}/workspace/${ITEM_FULL_NAME} + ${ITEM_ROOTDIR}/builds + + + gce-unit-tests + my-project + + + + SPOT + 120 + + + + + + \ No newline at end of file diff --git a/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testMaxRunDurationStandard/config.xml b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testMaxRunDurationStandard/config.xml new file mode 100644 index 00000000..5c232df5 --- /dev/null +++ b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testMaxRunDurationStandard/config.xml @@ -0,0 +1,23 @@ + + + 2.452.3 + + + + ${JENKINS_HOME}/workspace/${ITEM_FULL_NAME} + ${ITEM_ROOTDIR}/builds + + + gce-unit-tests + my-project + + + + STANDARD + 100 + + + + + + \ No newline at end of file diff --git a/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testNoMaxRunDurationPreemptible/config.xml b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testNoMaxRunDurationPreemptible/config.xml new file mode 100644 index 00000000..aa69f70f --- /dev/null +++ b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testNoMaxRunDurationPreemptible/config.xml @@ -0,0 +1,22 @@ + + + 2.452.3 + + + + ${JENKINS_HOME}/workspace/${ITEM_FULL_NAME} + ${ITEM_ROOTDIR}/builds + + + gce-unit-tests + + + false + + PREEMPTIBLE + + + + + + \ No newline at end of file diff --git a/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testNoProvisioningTypeCompatibility/config.xml b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testNoProvisioningTypeCompatibility/config.xml new file mode 100644 index 00000000..61e27bfc --- /dev/null +++ b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testNoProvisioningTypeCompatibility/config.xml @@ -0,0 +1,19 @@ + + + 2.452.3 + + + + ${JENKINS_HOME}/workspace/${ITEM_FULL_NAME} + ${ITEM_ROOTDIR}/builds + + + gce-unit-tests + my-project + + + + + + + \ No newline at end of file diff --git a/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testPreemptibleCompatibility/config.xml b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testPreemptibleCompatibility/config.xml new file mode 100644 index 00000000..358ea5d0 --- /dev/null +++ b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testPreemptibleCompatibility/config.xml @@ -0,0 +1,20 @@ + + + 2.452.3 + + + + ${JENKINS_HOME}/workspace/${ITEM_FULL_NAME} + ${ITEM_ROOTDIR}/builds + + + gce-unit-tests + my-project + + + true + + + + + \ No newline at end of file diff --git a/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testProvisioningTypeOverridesPreemptible/config.xml b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testProvisioningTypeOverridesPreemptible/config.xml new file mode 100644 index 00000000..3728888d --- /dev/null +++ b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testProvisioningTypeOverridesPreemptible/config.xml @@ -0,0 +1,23 @@ + + + 2.452.3 + + + + ${JENKINS_HOME}/workspace/${ITEM_FULL_NAME} + ${ITEM_ROOTDIR}/builds + + + gce-unit-tests + my-project + + + true + + SPOT + + + + + + \ No newline at end of file diff --git a/src/test/resources/com/google/jenkins/plugins/computeengine/integration/casc-max-run-duration-agent-it.yml b/src/test/resources/com/google/jenkins/plugins/computeengine/integration/spot-vm-provisioning-with-max-run-duration-casc.yml similarity index 98% rename from src/test/resources/com/google/jenkins/plugins/computeengine/integration/casc-max-run-duration-agent-it.yml rename to src/test/resources/com/google/jenkins/plugins/computeengine/integration/spot-vm-provisioning-with-max-run-duration-casc.yml index f918f28a..4d7d9662 100644 --- a/src/test/resources/com/google/jenkins/plugins/computeengine/integration/casc-max-run-duration-agent-it.yml +++ b/src/test/resources/com/google/jenkins/plugins/computeengine/integration/spot-vm-provisioning-with-max-run-duration-casc.yml @@ -9,7 +9,7 @@ jenkins: - namePrefix: max-run-duration description: max-run-duration launchTimeoutSecondsStr: '' - retentionTimeMinutesStr: '' + retentionTimeMinutesStr: "30" mode: EXCLUSIVE labelString: integration numExecutorsStr: 1 From 363925afc8ca807a8f674ec6009b45daa3b973ae Mon Sep 17 00:00:00 2001 From: guruprasad Date: Mon, 16 Dec 2024 22:56:56 +0530 Subject: [PATCH 09/19] remove the @deprecated annotation that would otherwise break casc compatibility --- .../jenkins/plugins/computeengine/InstanceConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java index 27f1ab87..8617e761 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java @@ -132,8 +132,8 @@ public class InstanceConfiguration implements Describable * `preemptible` is no more exposed in the UI, however we are still keeping it in here, just to provide * compatibility with old configurations. Use the {@link #provisioningType} field instead where * {@link com.google.jenkins.plugins.computeengine.ui.helpers.PreemptibleVm} is used for preemptible instances. + * We are not marking it as {@code @Deprecated} because then CasC will result in ConfiguratorException. */ - @Deprecated private boolean preemptible; private ProvisioningType provisioningType; From f4c7154bfd347f5ded58f0331de7acb9cd4424cf Mon Sep 17 00:00:00 2001 From: guruprasad Date: Mon, 16 Dec 2024 23:06:56 +0530 Subject: [PATCH 10/19] remove the todo added during code analysis --- .../jenkins/plugins/computeengine/ComputeEngineComputer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ComputeEngineComputer.java b/src/main/java/com/google/jenkins/plugins/computeengine/ComputeEngineComputer.java index acdbbb0a..473dcb60 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ComputeEngineComputer.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ComputeEngineComputer.java @@ -44,7 +44,7 @@ void onConnected(TaskListener listener) { ComputeEngineInstance node = getNode(); if (node != null) { node.onConnected(); - if (getPreemptible()) { // TODO: maybe need to handle similarly when `maxRunDuration` is set. + if (getPreemptible()) { String nodeName = node.getNodeName(); final String msg = "Instance " + nodeName + " is preemptive, setting up preemption listener"; log.log(Level.INFO, msg); From 74fe8c3adbe55ea8fc43267bccf8190e7a36bafa Mon Sep 17 00:00:00 2001 From: guruprasad Date: Mon, 16 Dec 2024 23:22:39 +0530 Subject: [PATCH 11/19] fix the access specifier in descriptor --- .../plugins/computeengine/ui/helpers/ProvisioningType.java | 3 ++- .../jenkins/plugins/computeengine/ui/helpers/SpotVm.java | 2 +- .../jenkins/plugins/computeengine/ui/helpers/Standard.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java index 60d09222..fbdfee66 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java @@ -24,7 +24,8 @@ * ProvisioningType represents the type of VM to be provisioned. */ public abstract class ProvisioningType extends AbstractDescribableImpl { - public ProvisioningTypeValue value; + + private ProvisioningTypeValue value; public ProvisioningType(ProvisioningTypeValue value) { this.value = value; diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java index 9c9b9916..e6531c44 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java @@ -25,7 +25,6 @@ @SuppressWarnings("unused") public class SpotVm extends ProvisioningType { - @DataBoundSetter private long maxRunDurationSeconds; @DataBoundConstructor @@ -33,6 +32,7 @@ public SpotVm() { super(ProvisioningTypeValue.SPOT); } + @DataBoundSetter public void setMaxRunDurationSeconds(long maxRunDurationSeconds) { this.maxRunDurationSeconds = maxRunDurationSeconds; } diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java index bf52754b..a8467d10 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java @@ -25,7 +25,6 @@ @SuppressWarnings("unused") public class Standard extends ProvisioningType { - @DataBoundSetter private long maxRunDurationSeconds; @DataBoundConstructor @@ -33,6 +32,7 @@ public Standard() { super(ProvisioningTypeValue.STANDARD); } + @DataBoundSetter public void setMaxRunDurationSeconds(long maxRunDurationSeconds) { this.maxRunDurationSeconds = maxRunDurationSeconds; } From cfae90639024171decb6cf4d60aef5a0ac1d5e73 Mon Sep 17 00:00:00 2001 From: guruprasad Date: Mon, 16 Dec 2024 23:29:09 +0530 Subject: [PATCH 12/19] comment add license --- .../plugins/computeengine/SchedulingTests.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java b/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java index 92d45db2..f46ae9c3 100644 --- a/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java +++ b/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java @@ -1,3 +1,19 @@ +/* + * 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; import static org.junit.Assert.assertEquals; From 937abdfc8911ce9a48e43478357a9f4ee89301c0 Mon Sep 17 00:00:00 2001 From: guruprasad Date: Mon, 16 Dec 2024 23:38:22 +0530 Subject: [PATCH 13/19] remove unused field leftout during refactor --- .../jenkins/plugins/computeengine/InstanceConfiguration.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java index 8617e761..04b36e40 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java @@ -597,7 +597,6 @@ private List serviceAccounts() { @Extension public static final class DescriptorImpl extends Descriptor { private static ComputeClient computeClient; - private static List provisioningTypeDescriptors; public static void setComputeClient(ComputeClient client) { computeClient = client; From 8fa2222fcc097093e3f092cd3c0cca83cfe3f26a Mon Sep 17 00:00:00 2001 From: guruprasad Date: Tue, 17 Dec 2024 13:06:17 +0530 Subject: [PATCH 14/19] fix spelling mistake in default --- .../jenkins/plugins/computeengine/InstanceConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java index 04b36e40..0a006f2b 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java @@ -827,7 +827,7 @@ public FormValidation doCheckMachineType(@QueryParameter String value) { } @SuppressWarnings("unused") - public ProvisioningType defualtProvisioningType() { + public ProvisioningType defaultProvisioningType() { return new Standard(); } From 170813535c22f8b41010e28eec7a0364b713966e Mon Sep 17 00:00:00 2001 From: guruprasad Date: Tue, 17 Dec 2024 13:08:52 +0530 Subject: [PATCH 15/19] re-order setters to reduce diff --- .../jenkins/plugins/computeengine/InstanceConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java index 0a006f2b..95680599 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java @@ -1011,10 +1011,10 @@ public InstanceConfiguration build() { instanceConfiguration.setMachineType(this.machineType); instanceConfiguration.setNumExecutorsStr(this.numExecutorsStr); instanceConfiguration.setStartupScript(this.startupScript); - instanceConfiguration.setMinCpuPlatform(this.minCpuPlatform); // even though `preemptible` is deprecated, we still set it here for backward compatibility instanceConfiguration.setPreemptible(this.preemptible); instanceConfiguration.setProvisioningType(this.provisioningType); + instanceConfiguration.setMinCpuPlatform(this.minCpuPlatform); instanceConfiguration.setLabelString(this.labels); instanceConfiguration.setRunAsUser(this.runAsUser); instanceConfiguration.setWindowsConfiguration(this.windowsConfiguration); From e806d2550e178cc3b4525e544957d93fb5dbaf9a Mon Sep 17 00:00:00 2001 From: guruprasad Date: Tue, 17 Dec 2024 18:30:57 +0530 Subject: [PATCH 16/19] fix the default provisioning type considering preemptible could be already set --- .../plugins/computeengine/InstanceConfiguration.java | 11 ++++++----- .../computeengine/InstanceConfiguration/config.jelly | 2 +- .../plugins/computeengine/SchedulingTests.java | 10 ++++++++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java index 95680599..84df5fd3 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java @@ -46,6 +46,7 @@ import com.google.jenkins.plugins.computeengine.ssh.GoogleKeyCredential; import com.google.jenkins.plugins.computeengine.ssh.GoogleKeyPair; import com.google.jenkins.plugins.computeengine.ssh.GooglePrivateKey; +import com.google.jenkins.plugins.computeengine.ui.helpers.PreemptibleVm; import com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningType; import com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningTypeValue; import com.google.jenkins.plugins.computeengine.ui.helpers.SpotVm; @@ -594,6 +595,11 @@ private List serviceAccounts() { } } + @SuppressWarnings("unused") + public ProvisioningType defaultProvisioningType() { + return this.preemptible ? new PreemptibleVm() : new Standard(); + } + @Extension public static final class DescriptorImpl extends Descriptor { private static ComputeClient computeClient; @@ -826,11 +832,6 @@ public FormValidation doCheckMachineType(@QueryParameter String value) { return FormValidation.ok(); } - @SuppressWarnings("unused") - public ProvisioningType defaultProvisioningType() { - return new Standard(); - } - public ListBoxModel doFillMinCpuPlatformItems( @AncestorInPath Jenkins context, @QueryParameter("projectId") @RelativePath("..") final String projectId, diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly index 2c414d8f..8866c27e 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly @@ -90,7 +90,7 @@ title="Provisioning Type" field="provisioningType" descriptors="${descriptor.provisioningTypes}" - default="${descriptor.defaultProvisioningType()}"/> + default="${instance.defaultProvisioningType()}"/> diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java b/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java index f46ae9c3..b28f129f 100644 --- a/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java +++ b/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java @@ -22,7 +22,9 @@ import com.google.api.client.json.GenericJson; import com.google.api.services.compute.model.Scheduling; +import com.google.jenkins.plugins.computeengine.ui.helpers.PreemptibleVm; import com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningTypeValue; +import com.google.jenkins.plugins.computeengine.ui.helpers.Standard; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; @@ -39,6 +41,12 @@ private Scheduling getScheduling() { return cloud.getConfigurations().get(0).scheduling(); } + private InstanceConfiguration getInstanceConfiguration() { + ComputeEngineCloud cloud = (ComputeEngineCloud) j.jenkins.clouds.getByName("gce-unit-tests"); + assertEquals(1, cloud.getConfigurations().size()); + return cloud.getConfigurations().get(0); + } + /** * When the previously used {@code preemptible} configuration is loaded, there should be no error in initializing * of the new field {@code provisioningType} as well as with building effective `scheduling` configurations. No @@ -49,6 +57,7 @@ private Scheduling getScheduling() { public void testPreemptibleCompatibility() { Scheduling sch = getScheduling(); assertTrue(sch.getPreemptible()); + assertEquals(PreemptibleVm.class, getInstanceConfiguration().defaultProvisioningType().getClass()); assertNull(sch.getProvisioningModel()); assertNull(sch.get("maxRunDuration")); assertNull(sch.getInstanceTerminationAction()); @@ -66,6 +75,7 @@ public void testNoProvisioningTypeCompatibility() { assertNull(sch.getProvisioningModel()); assertNull(sch.get("maxRunDuration")); assertNull(sch.getInstanceTerminationAction()); + assertEquals(Standard.class, getInstanceConfiguration().defaultProvisioningType().getClass()); } /** From 50a74b80d1c688feac9e04dc4ea166f825f41e5c Mon Sep 17 00:00:00 2001 From: guruprasad Date: Tue, 17 Dec 2024 18:40:11 +0530 Subject: [PATCH 17/19] spotless --- .../jenkins/plugins/computeengine/SchedulingTests.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java b/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java index b28f129f..d9ea9797 100644 --- a/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java +++ b/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java @@ -57,7 +57,9 @@ private InstanceConfiguration getInstanceConfiguration() { public void testPreemptibleCompatibility() { Scheduling sch = getScheduling(); assertTrue(sch.getPreemptible()); - assertEquals(PreemptibleVm.class, getInstanceConfiguration().defaultProvisioningType().getClass()); + assertEquals( + PreemptibleVm.class, + getInstanceConfiguration().defaultProvisioningType().getClass()); assertNull(sch.getProvisioningModel()); assertNull(sch.get("maxRunDuration")); assertNull(sch.getInstanceTerminationAction()); @@ -75,7 +77,9 @@ public void testNoProvisioningTypeCompatibility() { assertNull(sch.getProvisioningModel()); assertNull(sch.get("maxRunDuration")); assertNull(sch.getInstanceTerminationAction()); - assertEquals(Standard.class, getInstanceConfiguration().defaultProvisioningType().getClass()); + assertEquals( + Standard.class, + getInstanceConfiguration().defaultProvisioningType().getClass()); } /** From 3e7e7872cb2989de02b2201ab92c8e9d27931281 Mon Sep 17 00:00:00 2001 From: guruprasad Date: Wed, 18 Dec 2024 17:56:34 +0530 Subject: [PATCH 18/19] fix duplication, handle field deprecation and migration --- .../computeengine/InstanceConfiguration.java | 81 +++++++++---------- .../ui/helpers/PreemptibleVm.java | 11 +++ .../ui/helpers/ProvisioningType.java | 26 +++++- .../computeengine/ui/helpers/SpotVm.java | 12 +++ .../computeengine/ui/helpers/Standard.java | 11 +++ .../InstanceConfiguration/config.jelly | 2 +- .../helpers/PreemptibleVm/config-detail.jelly | 19 ----- .../ui/helpers/ProvisioningType/config.jelly | 8 +- .../help-maxRunDurationSeconds.html | 0 .../ui/helpers/SpotVm/config-detail.jelly | 21 ----- .../ui/helpers/Standard/config-detail.jelly | 21 ----- .../Standard/help-maxRunDurationSeconds.html | 19 ----- .../computeengine/ConfigAsCodeTest.java | 13 +++ .../computeengine/SchedulingTests.java | 14 ---- .../config.xml | 2 +- .../casc-preemptible-compatibility.yml | 44 ++++++++++ 16 files changed, 161 insertions(+), 143 deletions(-) delete mode 100644 src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm/config-detail.jelly rename src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/{SpotVm => ProvisioningType}/help-maxRunDurationSeconds.html (100%) delete mode 100644 src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/config-detail.jelly delete mode 100644 src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/config-detail.jelly delete mode 100644 src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/help-maxRunDurationSeconds.html create mode 100644 src/test/resources/com/google/jenkins/plugins/computeengine/casc-preemptible-compatibility.yml diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java index 84df5fd3..d89b30d1 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/InstanceConfiguration.java @@ -19,9 +19,7 @@ import static com.google.cloud.graphite.platforms.plugin.client.util.ClientUtil.nameFromSelfLink; import static com.google.jenkins.plugins.computeengine.ComputeEngineCloud.checkPermissions; import static com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningTypeValue.PREEMPTIBLE; -import static com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningTypeValue.SPOT; -import com.google.api.client.json.GenericJson; import com.google.api.services.compute.model.AcceleratorConfig; import com.google.api.services.compute.model.AttachedDisk; import com.google.api.services.compute.model.AttachedDiskInitializeParams; @@ -48,8 +46,6 @@ import com.google.jenkins.plugins.computeengine.ssh.GooglePrivateKey; import com.google.jenkins.plugins.computeengine.ui.helpers.PreemptibleVm; import com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningType; -import com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningTypeValue; -import com.google.jenkins.plugins.computeengine.ui.helpers.SpotVm; import com.google.jenkins.plugins.computeengine.ui.helpers.Standard; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -129,14 +125,6 @@ public class InstanceConfiguration implements Describable private String machineType; private String numExecutorsStr; private String startupScript; - /** - * `preemptible` is no more exposed in the UI, however we are still keeping it in here, just to provide - * compatibility with old configurations. Use the {@link #provisioningType} field instead where - * {@link com.google.jenkins.plugins.computeengine.ui.helpers.PreemptibleVm} is used for preemptible instances. - * We are not marking it as {@code @Deprecated} because then CasC will result in ConfiguratorException. - */ - private boolean preemptible; - private ProvisioningType provisioningType; private String minCpuPlatform; private String labels; @@ -184,6 +172,10 @@ public class InstanceConfiguration implements Describable @Setter(AccessLevel.PROTECTED) protected transient ComputeEngineCloud cloud; + /** @deprecated Use {@link #provisioningType} instead. */ + @SuppressWarnings("DeprecatedIsStillUsed") + private transient boolean preemptible; + private static List mergeMetadataItems(List winner, List loser) { if (loser == null) { loser = new ArrayList(); @@ -253,6 +245,19 @@ public void setCreateSnapshot(boolean createSnapshot) { this.createSnapshot = createSnapshot && this.oneShot; } + /** + * Required for JCasC Compatibility, + * Without this setter explicitly defined, the {@code ConfigAsCodeTest} test fails as `preemptible` is deprecated. + * Also see, + * + */ + @DataBoundSetter + public void setPreemptible(boolean preemptible) { + if (preemptible) { + this.provisioningType = new PreemptibleVm(); + } + } + public static Integer intOrDefault(String toParse, Integer defaultTo) { Integer toReturn; try { @@ -305,6 +310,13 @@ public int getLaunchTimeoutMillis() { return launchTimeoutSeconds * 1000; } + /** + * This getter is only for backward compatibility for `preemptible` field. + */ + public boolean getPreemptible() { + return provisioningType != null && provisioningType.getValue() == PREEMPTIBLE; + } + public void appendLabels(Map labels) { if (googleLabels == null) { googleLabels = new HashMap<>(); @@ -374,6 +386,10 @@ protected Object readResolve() { this.networkInterfaceIpStackMode = new NetworkInterfaceSingleStack(externalAddress); this.externalAddress = null; } + /* deprecating `preemptible` in favor of extensible `provisioningType` */ + if (preemptible && provisioningType == null) { + provisioningType = new PreemptibleVm(); + } return this; } @@ -512,33 +528,10 @@ private Tags tags() { @VisibleForTesting Scheduling scheduling() { Scheduling scheduling = new Scheduling(); - long maxRunDurationSeconds = 0; - if (provisioningType != null) { // check `null` for backward compatibility - ProvisioningTypeValue ptValue = provisioningType.getValue(); - if (ptValue == PREEMPTIBLE) { - scheduling.setPreemptible(true); - } else if (provisioningType.getValue() == SPOT) { - maxRunDurationSeconds = ((SpotVm) provisioningType).getMaxRunDurationSeconds(); - scheduling.setProvisioningModel("SPOT"); - } else { - maxRunDurationSeconds = ((Standard) provisioningType).getMaxRunDurationSeconds(); - } - 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"); - } - } else if (preemptible) { // keeping the check for `preemptible` for backward compatibility - scheduling.setPreemptible(true); - } // else: standard provisioning + if (provisioningType == null) { + return scheduling; + } + provisioningType.configure(scheduling); return scheduling; } @@ -595,11 +588,6 @@ private List serviceAccounts() { } } - @SuppressWarnings("unused") - public ProvisioningType defaultProvisioningType() { - return this.preemptible ? new PreemptibleVm() : new Standard(); - } - @Extension public static final class DescriptorImpl extends Descriptor { private static ComputeClient computeClient; @@ -639,6 +627,11 @@ public static SshConfiguration defaultSshConfiguration() { return SshConfiguration.builder().customPrivateKeyCredentialsId("").build(); } + @SuppressWarnings("unused") + public ProvisioningType defaultProvisioningType() { + return new Standard(); + } + public static NetworkConfiguration defaultNetworkConfiguration() { return new AutofilledNetworkConfiguration(); } diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java index 45fa3473..aeeff0a9 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm.java @@ -16,6 +16,7 @@ package com.google.jenkins.plugins.computeengine.ui.helpers; +import com.google.api.services.compute.model.Scheduling; import hudson.Extension; import org.kohsuke.stapler.DataBoundConstructor; @@ -27,11 +28,21 @@ public PreemptibleVm() { super(ProvisioningTypeValue.PREEMPTIBLE); } + @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; + } } } diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java index fbdfee66..f3762cd6 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType.java @@ -16,6 +16,8 @@ package com.google.jenkins.plugins.computeengine.ui.helpers; +import com.google.api.client.json.GenericJson; +import com.google.api.services.compute.model.Scheduling; import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; import org.kohsuke.stapler.DataBoundSetter; @@ -41,5 +43,27 @@ public void setProvisioningTypeValue(ProvisioningTypeValue value) { this.value = value; } - public abstract static class ProvisioningTypeDescriptor extends Descriptor {} + 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 { + + @SuppressWarnings("unused") + public abstract boolean isMaxRunDurationSupported(); + } } diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java index e6531c44..5dc593bd 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm.java @@ -16,6 +16,7 @@ package com.google.jenkins.plugins.computeengine.ui.helpers; +import com.google.api.services.compute.model.Scheduling; import hudson.Extension; import hudson.util.FormValidation; import org.kohsuke.stapler.DataBoundConstructor; @@ -41,6 +42,12 @@ 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 @@ -51,5 +58,10 @@ public String getDisplayName() { public FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) { return Utils.doCheckMaxRunDurationSeconds(value); } + + @Override + public boolean isMaxRunDurationSupported() { + return true; + } } } diff --git a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java index a8467d10..98be5189 100644 --- a/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java +++ b/src/main/java/com/google/jenkins/plugins/computeengine/ui/helpers/Standard.java @@ -16,6 +16,7 @@ package com.google.jenkins.plugins.computeengine.ui.helpers; +import com.google.api.services.compute.model.Scheduling; import hudson.Extension; import hudson.util.FormValidation; import org.kohsuke.stapler.DataBoundConstructor; @@ -41,6 +42,11 @@ public long getMaxRunDurationSeconds() { return maxRunDurationSeconds; } + @Override + public void configure(Scheduling scheduling) { + super.configureMaxRunDuration(scheduling, maxRunDurationSeconds); + } + @Extension public static class DescriptorImpl extends ProvisioningTypeDescriptor { @Override @@ -51,5 +57,10 @@ public String getDisplayName() { public FormValidation doCheckMaxRunDurationSeconds(@QueryParameter String value) { return Utils.doCheckMaxRunDurationSeconds(value); } + + @Override + public boolean isMaxRunDurationSupported() { + return true; + } } } diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly index 8866c27e..2c414d8f 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/InstanceConfiguration/config.jelly @@ -90,7 +90,7 @@ title="Provisioning Type" field="provisioningType" descriptors="${descriptor.provisioningTypes}" - default="${instance.defaultProvisioningType()}"/> + default="${descriptor.defaultProvisioningType()}"/> diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm/config-detail.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm/config-detail.jelly deleted file mode 100644 index adc39644..00000000 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/PreemptibleVm/config-detail.jelly +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/config.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/config.jelly index b7da155a..3ef796fb 100644 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/config.jelly +++ b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/config.jelly @@ -14,6 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + + + + + + \ No newline at end of file diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/help-maxRunDurationSeconds.html b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/help-maxRunDurationSeconds.html similarity index 100% rename from src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/help-maxRunDurationSeconds.html rename to src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/ProvisioningType/help-maxRunDurationSeconds.html diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/config-detail.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/config-detail.jelly deleted file mode 100644 index aa657710..00000000 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/SpotVm/config-detail.jelly +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/config-detail.jelly b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/config-detail.jelly deleted file mode 100644 index aa657710..00000000 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/config-detail.jelly +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/help-maxRunDurationSeconds.html b/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/help-maxRunDurationSeconds.html deleted file mode 100644 index c26d22d2..00000000 --- a/src/main/resources/com/google/jenkins/plugins/computeengine/ui/helpers/Standard/help-maxRunDurationSeconds.html +++ /dev/null @@ -1,19 +0,0 @@ - -

    - The maximum duration (in seconds) after which the VM will be automatically deleted by GCP. - See Limit the run time of a VM for more details. -
    \ No newline at end of file diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/ConfigAsCodeTest.java b/src/test/java/com/google/jenkins/plugins/computeengine/ConfigAsCodeTest.java index 2d9ea2c4..86aa2fbf 100644 --- a/src/test/java/com/google/jenkins/plugins/computeengine/ConfigAsCodeTest.java +++ b/src/test/java/com/google/jenkins/plugins/computeengine/ConfigAsCodeTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; +import com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningTypeValue; import com.google.jenkins.plugins.credentials.oauth.GoogleRobotPrivateKeyCredentials; import hudson.model.Node; import io.jenkins.plugins.casc.misc.ConfiguredWithCode; @@ -51,4 +52,16 @@ public void shouldCreateGCEClientFromCode() throws Exception { // Ensure correct exception is thrown assertThrows(GoogleRobotPrivateKeyCredentials.PrivateKeyNotSetException.class, cloud::getClient); } + + @Test + @ConfiguredWithCode("casc-preemptible-compatibility.yml") + public void provisioningTypeShouldBePreemptible() { + ComputeEngineCloud cloud = (ComputeEngineCloud) jenkinsRule.jenkins.clouds.getByName("gce-jenkins-build"); + assertEquals("Zero configurations found", 1, cloud.getConfigurations().size()); + InstanceConfiguration configuration = cloud.getConfigurations().get(0); + assertEquals( + "Provisioning type is wrong", + ProvisioningTypeValue.PREEMPTIBLE, + configuration.getProvisioningType().getValue()); + } } diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java b/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java index d9ea9797..f46ae9c3 100644 --- a/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java +++ b/src/test/java/com/google/jenkins/plugins/computeengine/SchedulingTests.java @@ -22,9 +22,7 @@ import com.google.api.client.json.GenericJson; import com.google.api.services.compute.model.Scheduling; -import com.google.jenkins.plugins.computeengine.ui.helpers.PreemptibleVm; import com.google.jenkins.plugins.computeengine.ui.helpers.ProvisioningTypeValue; -import com.google.jenkins.plugins.computeengine.ui.helpers.Standard; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; @@ -41,12 +39,6 @@ private Scheduling getScheduling() { return cloud.getConfigurations().get(0).scheduling(); } - private InstanceConfiguration getInstanceConfiguration() { - ComputeEngineCloud cloud = (ComputeEngineCloud) j.jenkins.clouds.getByName("gce-unit-tests"); - assertEquals(1, cloud.getConfigurations().size()); - return cloud.getConfigurations().get(0); - } - /** * When the previously used {@code preemptible} configuration is loaded, there should be no error in initializing * of the new field {@code provisioningType} as well as with building effective `scheduling` configurations. No @@ -57,9 +49,6 @@ private InstanceConfiguration getInstanceConfiguration() { public void testPreemptibleCompatibility() { Scheduling sch = getScheduling(); assertTrue(sch.getPreemptible()); - assertEquals( - PreemptibleVm.class, - getInstanceConfiguration().defaultProvisioningType().getClass()); assertNull(sch.getProvisioningModel()); assertNull(sch.get("maxRunDuration")); assertNull(sch.getInstanceTerminationAction()); @@ -77,9 +66,6 @@ public void testNoProvisioningTypeCompatibility() { assertNull(sch.getProvisioningModel()); assertNull(sch.get("maxRunDuration")); assertNull(sch.getInstanceTerminationAction()); - assertEquals( - Standard.class, - getInstanceConfiguration().defaultProvisioningType().getClass()); } /** diff --git a/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testNoMaxRunDurationPreemptible/config.xml b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testNoMaxRunDurationPreemptible/config.xml index aa69f70f..3270aefc 100644 --- a/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testNoMaxRunDurationPreemptible/config.xml +++ b/src/test/resources/com/google/jenkins/plugins/computeengine/SchedulingTests/testNoMaxRunDurationPreemptible/config.xml @@ -12,7 +12,7 @@ false - + PREEMPTIBLE diff --git a/src/test/resources/com/google/jenkins/plugins/computeengine/casc-preemptible-compatibility.yml b/src/test/resources/com/google/jenkins/plugins/computeengine/casc-preemptible-compatibility.yml new file mode 100644 index 00000000..dc690976 --- /dev/null +++ b/src/test/resources/com/google/jenkins/plugins/computeengine/casc-preemptible-compatibility.yml @@ -0,0 +1,44 @@ +jenkins: + clouds: + - computeEngine: + cloudName: jenkins-build + projectId: gce-jenkins + instanceCapStr: 2 + credentialsId: gce-jenkins + noDelayProvisioning: false + configurations: + - namePrefix: jenkins-agent-image + description: Jenkins agent + launchTimeoutSecondsStr: 6 + retentionTimeMinutesStr: 300 + mode: EXCLUSIVE + labelString: jenkins-agent + numExecutorsStr: 1 + runAsUser: jenkins + remoteFs: agent + javaExecPath: "java" + oneShot: true + createSnapshot: false + region: "https://www.googleapis.com/compute/v1/projects/gce-jenkins/regions/europe-west1" + zone: "https://www.googleapis.com/compute/v1/projects/gce-jenkins/zones/europe-west1-a" + template: '' + machineType: "https://www.googleapis.com/compute/v1/projects/gce-jenkins/zones/europe-west1-a/machineTypes/n1-standard-2" + preemptible: true + minCpuPlatform: '' + startupScript: '' + networkConfiguration: + sharedVpc: + projectId: gce-jenkins-cloud-123456 + region: europe-west1 + subnetworkShortName: gce-jenkins-cloud + networkTags: jenkins-agent + networkInterfaceIpStackMode: + singleStack: + externalIPV4Address: true + useInternalAddress: false + bootDiskSourceImageProject: gce-jenkins + bootDiskSourceImageName: "https://www.googleapis.com/compute/v1/projects/gce-jenkins/global/images/gce-jenkins-build-image" + bootDiskType: "https://www.googleapis.com/compute/v1/projects/gce-jenkins/zones/europe-west1-a/diskTypes/pd-standard" + bootDiskSizeGbStr: 50 + bootDiskAutoDelete: true + serviceAccountEmail: 'jenkins-test@gce-jenkins-ops.iam.gserviceaccount.com' From 244da5979e8dc2270681d3c7f9980302bf5177b1 Mon Sep 17 00:00:00 2001 From: guruprasad Date: Wed, 18 Dec 2024 19:53:25 +0530 Subject: [PATCH 19/19] fix the preemptive task rescheduling integration tests --- .../computeengine/InstanceConfigurationTest.java | 4 +++- .../ComputeEngineCloudRestartPreemptedIT.java | 13 ++++++++----- .../plugins/computeengine/integration/ITUtil.java | 4 +++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/InstanceConfigurationTest.java b/src/test/java/com/google/jenkins/plugins/computeengine/InstanceConfigurationTest.java index 62a126b5..b9e17423 100644 --- a/src/test/java/com/google/jenkins/plugins/computeengine/InstanceConfigurationTest.java +++ b/src/test/java/com/google/jenkins/plugins/computeengine/InstanceConfigurationTest.java @@ -36,6 +36,8 @@ import com.google.api.services.compute.model.Zone; import com.google.cloud.graphite.platforms.plugin.client.ComputeClient; import com.google.common.collect.ImmutableList; +import com.google.jenkins.plugins.computeengine.ui.helpers.PreemptibleVm; +import com.google.jenkins.plugins.computeengine.ui.helpers.Standard; import hudson.model.Node; import hudson.util.FormValidation; import java.util.ArrayList; @@ -308,7 +310,7 @@ public static InstanceConfiguration.Builder instanceConfigurationBuilder() { .machineType(MACHINE_TYPE) .numExecutorsStr(NUM_EXECUTORS) .startupScript(STARTUP_SCRIPT) - .preemptible(PREEMPTIBLE) + .provisioningType(PREEMPTIBLE ? new PreemptibleVm() : new Standard()) .labels(LABEL) .description(CONFIG_DESC) .bootDiskType(BOOT_DISK_TYPE) diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/integration/ComputeEngineCloudRestartPreemptedIT.java b/src/test/java/com/google/jenkins/plugins/computeengine/integration/ComputeEngineCloudRestartPreemptedIT.java index 6115ddaa..276555e5 100644 --- a/src/test/java/com/google/jenkins/plugins/computeengine/integration/ComputeEngineCloudRestartPreemptedIT.java +++ b/src/test/java/com/google/jenkins/plugins/computeengine/integration/ComputeEngineCloudRestartPreemptedIT.java @@ -20,7 +20,6 @@ import static com.google.jenkins.plugins.computeengine.integration.ITUtil.NULL_TEMPLATE; import static com.google.jenkins.plugins.computeengine.integration.ITUtil.NUM_EXECUTORS; import static com.google.jenkins.plugins.computeengine.integration.ITUtil.PROJECT_ID; -import static com.google.jenkins.plugins.computeengine.integration.ITUtil.TEST_TIMEOUT_MULTIPLIER; import static com.google.jenkins.plugins.computeengine.integration.ITUtil.ZONE; import static com.google.jenkins.plugins.computeengine.integration.ITUtil.execute; import static com.google.jenkins.plugins.computeengine.integration.ITUtil.getLabel; @@ -39,6 +38,7 @@ import com.google.jenkins.plugins.computeengine.ComputeEngineCloud; import com.google.jenkins.plugins.computeengine.ComputeEngineComputer; import com.google.jenkins.plugins.computeengine.InstanceConfiguration; +import com.google.jenkins.plugins.computeengine.ui.helpers.PreemptibleVm; import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; import hudson.model.Node; @@ -57,7 +57,6 @@ import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.Timeout; import org.jvnet.hudson.test.JenkinsRule; /** @@ -66,8 +65,11 @@ */ @Log public class ComputeEngineCloudRestartPreemptedIT { - @ClassRule - public static Timeout timeout = new Timeout(20 * TEST_TIMEOUT_MULTIPLIER, TimeUnit.MINUTES); + + // Increase the timeout from default 180s to 20minutes. + static { + System.setProperty("jenkins.test.timeout", "1200"); + } @ClassRule public static JenkinsRule jenkinsRule = new JenkinsRule(); @@ -87,7 +89,7 @@ public static void init() throws Exception { .numExecutorsStr(NUM_EXECUTORS) .labels(LABEL) .template(NULL_TEMPLATE) - .preemptible(true) + .provisioningType(new PreemptibleVm()) .googleLabels(label) .oneShot(false) .build(); @@ -131,5 +133,6 @@ public void testIfNodeWasPreempted() throws Exception { FreeStyleBuild nextBuild = freeStyleBuild.getNextBuild(); Awaitility.await().timeout(5, TimeUnit.MINUTES).until(() -> nextBuild.getResult() != null); assertEquals(SUCCESS, nextBuild.getResult()); + log.info("Tests passed"); // sometimes there are IO connection errors logged afterwards so. } } diff --git a/src/test/java/com/google/jenkins/plugins/computeengine/integration/ITUtil.java b/src/test/java/com/google/jenkins/plugins/computeengine/integration/ITUtil.java index 68b98fab..44f345c6 100644 --- a/src/test/java/com/google/jenkins/plugins/computeengine/integration/ITUtil.java +++ b/src/test/java/com/google/jenkins/plugins/computeengine/integration/ITUtil.java @@ -61,6 +61,8 @@ import com.google.jenkins.plugins.computeengine.client.ClientUtil; import com.google.jenkins.plugins.computeengine.ssh.GoogleKeyCredential; import com.google.jenkins.plugins.computeengine.ssh.GoogleKeyPair; +import com.google.jenkins.plugins.computeengine.ui.helpers.PreemptibleVm; +import com.google.jenkins.plugins.computeengine.ui.helpers.Standard; import com.google.jenkins.plugins.credentials.oauth.GoogleRobotPrivateKeyCredentials; import com.google.jenkins.plugins.credentials.oauth.JsonServiceAccountConfig; import hudson.model.Node; @@ -297,7 +299,7 @@ static InstanceConfiguration.Builder instanceConfigurationBuilder() { .region(REGION) .zone(ZONE) .machineType(MACHINE_TYPE) - .preemptible(PREEMPTIBLE) + .provisioningType(PREEMPTIBLE ? new PreemptibleVm() : new Standard()) .minCpuPlatform(MIN_CPU_PLATFORM) .description(CONFIG_DESC) .bootDiskType(BOOT_DISK_TYPE)