diff --git a/pom.xml b/pom.xml index d31056ac..c5b99e36 100644 --- a/pom.xml +++ b/pom.xml @@ -30,9 +30,9 @@ - olivergondza - Oliver Gondža - ogondza@gmail.com + olivergondza + Oliver Gondža + ogondza@gmail.com @@ -233,7 +233,7 @@ - + ${project.artifactId} @@ -258,3 +258,4 @@ + diff --git a/src/main/java/jenkins/plugins/openstack/compute/JCloudsPreCreationThread.java b/src/main/java/jenkins/plugins/openstack/compute/JCloudsPreCreationThread.java index 5dc60f0f..845f1f03 100644 --- a/src/main/java/jenkins/plugins/openstack/compute/JCloudsPreCreationThread.java +++ b/src/main/java/jenkins/plugins/openstack/compute/JCloudsPreCreationThread.java @@ -2,10 +2,12 @@ import java.lang.Math; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import hudson.model.Executor; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -13,6 +15,7 @@ import hudson.Functions; import hudson.model.TaskListener; import hudson.model.AsyncPeriodicWork; +import org.openstack4j.model.compute.Server; /** * Periodically ensure enough slaves are created. @@ -92,6 +95,91 @@ public void execute(TaskListener listener) { } } + /** + * Methods which return the list of VM + */ + public static List getVmList(String templateName, String cloudName){ + List vmList = null; + for (JCloudsCloud cloud : JCloudsCloud.getClouds()) { + if (cloud.getDisplayName().equals(cloudName)){ + vmList = cloud.getTemplate(templateName).getRunningNodes(); + } + } + return vmList; + } + + /** + * Methods which return the name of the oldestVM for a specific template. + */ + public static String getOldestVm(String templateName, String cloudName){ + return getOldestVM(templateName, cloudName).getName(); + } + + + /** + * Methods which return the oldestVM + * return a Server + * return a VM which is not offline/Pending delete in Jenkins + */ + private static Server getOldestVM(String templateName, String cloudName){ + Server oldestVm = null; + long oldestVmTime; + long currentVMTime; + String computerName; + String vmName; + List vmList = getVmList(templateName, cloudName); + List listComputer = JCloudsComputer.getAll(); + if (oldestVm == null){ + oldestVm = vmList.get(0); + } + for (int i = 0; i currentVMTime){ + for (int y = 0; y listComputer = JCloudsComputer.getAll(); + List listExecutors; + //For each computer.. + for (int y=0; y { private transient ReentrantLock checkLock; + private static List listOldestVm = new ArrayList<>(); @DataBoundConstructor public JCloudsRetentionStrategy() { @@ -48,33 +51,105 @@ public long check(JCloudsComputer c) { } private void doCheck(JCloudsComputer c) { - if (c.isPendingDelete()) return; // No need to do it again + if (c.isPendingDelete()) return; if (c.isConnecting()) return; // Do not discard slave while launching for the first time when "idle time" does not make much sense - if (!c.isIdle() || c.getOfflineCause() instanceof OfflineCause.UserCause) return; // Occupied by user initiated activity - final JCloudsSlave node = c.getNode(); - if (node == null) return; // Node is gone already - - final int retentionTime = node.getSlaveOptions().getRetentionTime(); - if (retentionTime <= 0) return; // 0 is handled in JCloudsComputer, negative values needs no handling - final long idleSince = c.getIdleStart(); - final long idleMilliseconds = getNow() - idleSince; - if (idleMilliseconds > TimeUnit.MINUTES.toMillis(retentionTime)) { - if (JCloudsPreCreationThread.isNeededReadyComputer(node.getComputer())) { - LOGGER.info("Keeping " + c .getName() + " to meet minimum requirements"); - return; + boolean oldestSlaveTermination = node.getSlaveOptions().getOldestSlaveTermination(); + + //case where the checkbox isn't checked + if (oldestSlaveTermination == false) { + if (!c.isIdle() || c.getOfflineCause() instanceof OfflineCause.UserCause) return; // Occupied by user initiated activity + + if (node == null) return; // Node is gone already + + final int retentionTime = node.getSlaveOptions().getRetentionTime(); + + if (retentionTime <= 0) return; // 0 is handled in JCloudsComputer, negative values needs no handling + final long idleSince = c.getIdleStart(); + final long idleMilliseconds = getNow() - idleSince; + if (idleMilliseconds > TimeUnit.MINUTES.toMillis(retentionTime)) { + if (JCloudsPreCreationThread.isNeededReadyComputer(node.getComputer())) { + LOGGER.info("Keeping " + c.getName() + " to meet minimum requirements"); + return; + } + LOGGER.info("Scheduling " + c.getName() + " for termination after " + retentionTime + " minutes as it was idle since " + new Date(idleSince)); + if (LOGGER.isLoggable(Level.FINE)) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Jenkins.XSTREAM2.toXMLUTF8(node, out); + LOGGER.fine(out.toString("UTF-8")); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Failed to dump node config", e); + } + } + c.setPendingDelete(true); } - LOGGER.info("Scheduling " + c .getName() + " for termination after " + retentionTime+ " minutes as it was idle since " + new Date(idleSince)); - if (LOGGER.isLoggable(Level.FINE)) { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Jenkins.XSTREAM2.toXMLUTF8(node, out); - LOGGER.fine(out.toString("UTF-8")); - } catch (IOException e) { - LOGGER.log(Level.WARNING, "Failed to dump node config", e); + } + + //case where the checkbox is checked + if ((oldestSlaveTermination == true)) { + + String templateName = c.getId().getTemplateName(); + String cloudName = c.getId().getCloudName(); + String oldestVmName = ""; + + //number of free executors in our VM template + int numOfFreeExec = JCloudsPreCreationThread.getNumOfFreeExec(templateName); + + //number of executor required for our template + int numOfMinExec = c.getNode().getSlaveOptions().getNumExecutors() * node.getSlaveOptions().getInstancesMin(); + + //If oldest VM list is empty + if (listOldestVm.isEmpty()){ + //Find the oldest VM for the template of the current Computer + oldestVmName = JCloudsPreCreationThread.getOldestVm(templateName, cloudName); + //New object + JCloudsPreCreationThread.OldestVM oldestVM = new JCloudsPreCreationThread.OldestVM(templateName, oldestVmName); + //Add the object to the oldest VM list + listOldestVm.add(oldestVM); + } else { + + boolean templateInTheList = false; + for (int i = 0; i numOfMinExec){ + //If the current VM is the oldest + for (int i = 0; i, Serializable { private static final long serialVersionUID = -1L; - private static final SlaveOptions EMPTY = new SlaveOptions(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + private static final SlaveOptions EMPTY = new SlaveOptions(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, false); // Provisioning attributes private /*final*/ @CheckForNull BootSource bootSource; @@ -75,6 +77,7 @@ public class SlaveOptions implements Describable, Serializable { // Slave attributes private final Integer retentionTime; + private final boolean oldestSlaveTermination; // Replaced by BootSource @Deprecated private transient @CheckForNull String imageId; @@ -143,6 +146,8 @@ public Integer getRetentionTime() { return retentionTime; } + public boolean getOldestSlaveTermination() { return oldestSlaveTermination; } + public SlaveOptions(Builder b) { this( b.bootSource, @@ -160,7 +165,8 @@ public SlaveOptions(Builder b) { b.jvmOptions, b.fsRoot, b.launcherFactory, - b.retentionTime + b.retentionTime, + b.oldestSlaveTermination ); } @@ -181,7 +187,8 @@ public SlaveOptions( String jvmOptions, String fsRoot, LauncherFactory launcherFactory, - Integer retentionTime + Integer retentionTime, + boolean oldestSlaveTermination ) { this.bootSource = bootSource; this.hardwareId = Util.fixEmpty(hardwareId); @@ -199,6 +206,7 @@ public SlaveOptions( this.fsRoot = Util.fixEmpty(fsRoot); this.launcherFactory = launcherFactory; this.retentionTime = retentionTime; + this.oldestSlaveTermination = TRUE.equals(oldestSlaveTermination); } private Object readResolve() { @@ -230,6 +238,7 @@ private Object readResolve() { .fsRoot(_override(this.fsRoot, o.fsRoot)) .launcherFactory(_override(this.launcherFactory, o.launcherFactory)) .retentionTime(_override(this.retentionTime, o.retentionTime)) + .oldestSlaveTermination(_override(this.oldestSlaveTermination, o.oldestSlaveTermination)) .build() ; } @@ -259,6 +268,7 @@ private Object readResolve() { .fsRoot(_erase(this.fsRoot, defaults.fsRoot)) .launcherFactory(_erase(this.launcherFactory, defaults.launcherFactory)) .retentionTime(_erase(this.retentionTime, defaults.retentionTime)) + .oldestSlaveTermination(this.oldestSlaveTermination) .build() ; } @@ -289,6 +299,7 @@ public String toString() { .append("fsRoot", fsRoot) .append("launcherFactory", launcherFactory) .append("retentionTime", retentionTime) + .append("oldestSlaveTermination", oldestSlaveTermination) .toString() ; } @@ -361,6 +372,7 @@ public Builder getBuilder() { .fsRoot(fsRoot) .launcherFactory(launcherFactory) .retentionTime(retentionTime) + .oldestSlaveTermination(oldestSlaveTermination) ; } @@ -394,6 +406,7 @@ public static final class Builder { private @CheckForNull LauncherFactory launcherFactory; private @CheckForNull Integer retentionTime; + private boolean oldestSlaveTermination; public Builder() {} @@ -480,6 +493,12 @@ public Builder() {} this.retentionTime = retentionTime; return this; } + + public @Nonnull Builder oldestSlaveTermination(boolean oldestSlaveTermination) { + this.oldestSlaveTermination = oldestSlaveTermination; + return this; + } + } /** diff --git a/src/main/resources/jenkins/plugins/openstack/compute/SlaveOptions/config.jelly b/src/main/resources/jenkins/plugins/openstack/compute/SlaveOptions/config.jelly index 82913f40..310b2b7d 100644 --- a/src/main/resources/jenkins/plugins/openstack/compute/SlaveOptions/config.jelly +++ b/src/main/resources/jenkins/plugins/openstack/compute/SlaveOptions/config.jelly @@ -63,6 +63,9 @@ + + + diff --git a/src/test/java/jenkins/plugins/openstack/PluginTestRule.java b/src/test/java/jenkins/plugins/openstack/PluginTestRule.java index 7ac8689e..f0ac0fab 100644 --- a/src/test/java/jenkins/plugins/openstack/PluginTestRule.java +++ b/src/test/java/jenkins/plugins/openstack/PluginTestRule.java @@ -114,7 +114,7 @@ public static SlaveOptions dummySlaveOptions() { } return new SlaveOptions( new BootSource.VolumeSnapshot("id"), "hw", "nw1,mw2", "dummyUserDataId", 1, 2, "pool", "sg", "az", 1, null, 10, - "jvmo", "fsRoot", LauncherFactory.JNLP.JNLP, 1 + "jvmo", "fsRoot", LauncherFactory.JNLP.JNLP, 1, false ); } diff --git a/src/test/java/jenkins/plugins/openstack/compute/JCloudsCloudTest.java b/src/test/java/jenkins/plugins/openstack/compute/JCloudsCloudTest.java index d3c829b7..9136320e 100644 --- a/src/test/java/jenkins/plugins/openstack/compute/JCloudsCloudTest.java +++ b/src/test/java/jenkins/plugins/openstack/compute/JCloudsCloudTest.java @@ -151,10 +151,10 @@ public void presentUIDefaults() throws Exception { JCloudsSlaveTemplate template = new JCloudsSlaveTemplate("template", "label", new SlaveOptions( new BootSource.Image("iid"), "hw", "nw", "ud", 1, 0, "public", "sg", "az", 2, "kp", 3, "jvmo", "fsRoot", LauncherFactory.JNLP.JNLP, 4 - )); + , false)); JCloudsCloud cloud = new JCloudsCloud("openstack", "endPointUrl", false,"zone", new SlaveOptions( new BootSource.VolumeSnapshot("vsid"), "HW", "NW", "UD", 6, 4, null, "SG", "AZ", 7, "KP", 8, "JVMO", "FSrOOT", new LauncherFactory.SSH("cid"), 9 - ), Collections.singletonList(template),openstackAuth); + , false), Collections.singletonList(template),openstackAuth); j.jenkins.clouds.add(cloud); JenkinsRule.WebClient wc = j.createWebClient(); diff --git a/src/test/java/jenkins/plugins/openstack/compute/JCloudsRetentionStrategyTest.java b/src/test/java/jenkins/plugins/openstack/compute/JCloudsRetentionStrategyTest.java index 35f3ec52..0dd400f9 100644 --- a/src/test/java/jenkins/plugins/openstack/compute/JCloudsRetentionStrategyTest.java +++ b/src/test/java/jenkins/plugins/openstack/compute/JCloudsRetentionStrategyTest.java @@ -175,7 +175,8 @@ public void doNotRemoveSlaveShortlyAfterConnection() throws Exception { Assume.assumeFalse(Functions.isWindows()); LauncherFactory launcherFactory = new TestCommandLauncherFactory("bash -c \"sleep 70 && java -jar '%s'\""); JCloudsCloud cloud = j.configureSlaveProvisioningWithFloatingIP(j.dummyCloud(j.dummySlaveTemplate( - j.defaultSlaveOptions().getBuilder().retentionTime(1).launcherFactory(launcherFactory).build(), + j.defaultSlaveOptions().getBuilder().retentionTime(1).launcherFactory(launcherFactory) + .oldestSlaveTermination(false).build(), "label" ))); diff --git a/src/test/java/jenkins/plugins/openstack/compute/SlaveOptionsTest.java b/src/test/java/jenkins/plugins/openstack/compute/SlaveOptionsTest.java index 310497e5..5c482375 100644 --- a/src/test/java/jenkins/plugins/openstack/compute/SlaveOptionsTest.java +++ b/src/test/java/jenkins/plugins/openstack/compute/SlaveOptionsTest.java @@ -90,7 +90,7 @@ public void emptyStrings() { SlaveOptions nulls = SlaveOptions.empty(); SlaveOptions emptyStrings = new SlaveOptions( null, "", "", "", null, null, "", "", "", null, "", null, "", "", null, null - ); + , false); SlaveOptions emptyBuilt = SlaveOptions.builder() .hardwareId("") .networkId("")