Skip to content

Commit d843288

Browse files
author
nkorabli
committed
Refactor the code to address comments
* Use native logic that takes into account state of the nodes
1 parent 1cbedb6 commit d843288

File tree

7 files changed

+113
-156
lines changed

7 files changed

+113
-156
lines changed

src/main/java/org/jenkinsci/plugins/vSphereCloud.java

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
import org.jenkinsci.plugins.folder.FolderVSphereCloudProperty;
2424
import org.jenkinsci.plugins.vsphere.VSphereConnectionConfig;
2525
import org.jenkinsci.plugins.vsphere.tools.*;
26-
import org.kohsuke.accmod.Restricted;
27-
import org.kohsuke.accmod.restrictions.NoExternalUse;
2826
import org.kohsuke.stapler.DataBoundConstructor;
2927
import org.kohsuke.stapler.QueryParameter;
3028
import org.kohsuke.stapler.Stapler;
@@ -217,8 +215,7 @@ public List<? extends vSphereCloudSlaveTemplate> getTemplates() {
217215
return this.templates;
218216
}
219217

220-
@Restricted(NoExternalUse.class)
221-
vSphereCloudSlaveTemplate getTemplateForVM(final String vmName) {
218+
private vSphereCloudSlaveTemplate getTemplateForVM(final String vmName) {
222219
if (this.templates == null || vmName == null)
223220
return null;
224221
for (final vSphereCloudSlaveTemplate t : this.templates) {
@@ -355,22 +352,15 @@ public Collection<PlannedNode> provision(final Label label, int excessWorkload)
355352
final List<PlannedNode> plannedNodes = new ArrayList<PlannedNode>();
356353
synchronized (templateState) {
357354
templateState.pruneUnwantedRecords();
358-
Integer maxSlavesToProvisionBeforeCloudCapHit = calculateMaxAdditionalSlavesPermitted();
359-
if (maxSlavesToProvisionBeforeCloudCapHit != null && maxSlavesToProvisionBeforeCloudCapHit <= 0) {
355+
if (!cloudHasCapacity()) {
360356
return Collections.emptySet(); // no capacity due to cloud instance cap
361357
}
362358
final List<vSphereCloudSlaveTemplate> templates = getTemplates(label);
363359
final List<CloudProvisioningRecord> whatWeCouldUse = templateState.calculateProvisionableTemplates(templates);
364360
VSLOG.log(Level.INFO, methodCallDescription + ": " + numberOfvSphereCloudSlaves + " existing slaves (="
365361
+ numberOfvSphereCloudSlaveExecutors + " executors), templates available are " + whatWeCouldUse);
366362
while (excessWorkloadSoFar > 0) {
367-
if (maxSlavesToProvisionBeforeCloudCapHit != null) {
368-
final int intValue = maxSlavesToProvisionBeforeCloudCapHit.intValue();
369-
if (intValue <= 0) {
370-
break; // out of capacity due to cloud instance cap
371-
}
372-
maxSlavesToProvisionBeforeCloudCapHit = Integer.valueOf(intValue - 1);
373-
}
363+
if (!cloudHasCapacity()) break;
374364
final CloudProvisioningRecord whatWeShouldSpinUp = CloudProvisioningAlgorithm.findTemplateWithMostFreeCapacity(whatWeCouldUse);
375365
if (whatWeShouldSpinUp == null) {
376366
break; // out of capacity due to template instance cap
@@ -390,6 +380,46 @@ public Collection<PlannedNode> provision(final Label label, int excessWorkload)
390380
}
391381
}
392382

383+
/**
384+
* Pre-provisions nodes per template to save time on a VM boot.
385+
*
386+
* @param template
387+
*/
388+
public void preProvisionNodes(vSphereCloudSlaveTemplate template) {
389+
final String methodCallDescription = "preProvisionNodesForTemplate(" + template.getLabelString() + ")";
390+
try {
391+
synchronized (this) {
392+
ensureLists();
393+
}
394+
synchronized (templateState) {
395+
templateState.pruneUnwantedRecords();
396+
final CloudProvisioningRecord provisionable = templateState.getOrCreateRecord(template);
397+
int nodesToProvision = CloudProvisioningAlgorithm.shouldPreProvisionNodes(provisionable);
398+
VSLOG.log(Level.INFO, methodCallDescription + ": should pre-provision " + nodesToProvision + " nodes");
399+
while (nodesToProvision > 0) {
400+
if (!cloudHasCapacity()) break;
401+
final String nodeName = CloudProvisioningAlgorithm.findUnusedName(provisionable);
402+
VSpherePlannedNode.createInstance(templateState, nodeName, provisionable);
403+
nodesToProvision -= 1;
404+
}
405+
}
406+
} catch (Exception ex) {
407+
VSLOG.log(Level.WARNING, methodCallDescription + ": Failed.", ex);
408+
}
409+
}
410+
411+
/**
412+
* Check if at least one additional node can be provisioned.
413+
*/
414+
private boolean cloudHasCapacity(){
415+
Integer maxSlavesToProvisionBeforeCloudCapHit = calculateMaxAdditionalSlavesPermitted();
416+
if (maxSlavesToProvisionBeforeCloudCapHit != null && maxSlavesToProvisionBeforeCloudCapHit <= 0) {
417+
VSLOG.info("The cloud is at max capacity. Can not provison more nodes.");
418+
return false;
419+
}
420+
return true;
421+
}
422+
393423
/**
394424
* Has another go at deleting VMs we failed to delete earlier. It's possible
395425
* that we were unable to talk to vSphere (or some other failure happened)

src/main/java/org/jenkinsci/plugins/vSphereCloudSlaveComputer.java

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,8 @@
22

33
import java.io.PrintWriter;
44
import java.io.StringWriter;
5-
import java.util.ArrayList;
6-
import java.util.List;
75

8-
import javax.annotation.Nonnull;
96
import org.jenkinsci.plugins.vsphere.tools.VSphere;
10-
import org.kohsuke.accmod.Restricted;
11-
import org.kohsuke.accmod.restrictions.NoExternalUse;
127

138
import com.vmware.vim25.VirtualHardware;
149
import com.vmware.vim25.VirtualMachineConfigInfo;
@@ -19,10 +14,8 @@
1914
import com.vmware.vim25.mo.ManagedEntity;
2015
import com.vmware.vim25.mo.VirtualMachine;
2116

22-
import hudson.model.Computer;
2317
import hudson.slaves.AbstractCloudComputer;
2418
import hudson.slaves.AbstractCloudSlave;
25-
import jenkins.model.Jenkins;
2619

2720
public class vSphereCloudSlaveComputer extends AbstractCloudComputer {
2821
private final vSphereCloudSlave vSlave;
@@ -84,19 +77,6 @@ public String getVmInformationError() {
8477
return getVMInformation().errorEncounteredWhenDataWasRead;
8578
}
8679

87-
/**
88-
* Get all vsphere computers.
89-
*/
90-
@Restricted(NoExternalUse.class)
91-
static @Nonnull List<vSphereCloudSlaveComputer> getAll() {
92-
ArrayList<vSphereCloudSlaveComputer> out = new ArrayList<>();
93-
for (final Computer c : Jenkins.get().getComputers()) {
94-
if (!(c instanceof vSphereCloudSlaveComputer)) continue;
95-
out.add((vSphereCloudSlaveComputer) c);
96-
}
97-
return out;
98-
}
99-
10080
/** 10 seconds */
10181
private static final long NANOSECONDS_TO_CACHE_VMINFORMATION = 10L * 1000000000L;
10282

src/main/java/org/jenkinsci/plugins/vSphereCloudSlaveTemplate.java

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -325,37 +325,6 @@ public RetentionStrategy<?> getRetentionStrategy() {
325325
return this.retentionStrategy;
326326
}
327327

328-
/**
329-
* Return a list of running nodes provisioned using this template.
330-
*/
331-
@Restricted(NoExternalUse.class)
332-
public List<vSphereCloudSlaveComputer> getOnlineNodes() {
333-
return getNodes(false);
334-
}
335-
336-
/**
337-
* Return a list of idle nodes provisioned using this template.
338-
*/
339-
@Restricted(NoExternalUse.class)
340-
public List<vSphereCloudSlaveComputer> getIdleNodes() {
341-
return getNodes(true);
342-
}
343-
344-
private List<vSphereCloudSlaveComputer> getNodes(boolean idle) {
345-
List<vSphereCloudSlaveComputer> nodes = new ArrayList<>();
346-
for (vSphereCloudSlaveComputer node : vSphereCloudSlaveComputer.getAll()) {
347-
if (!node.isOnline()) continue;
348-
if (idle && !node.isIdle()) continue;
349-
String vmName = node.getName();
350-
vSphereCloudSlaveTemplate nodeTemplate = getParent().getTemplateForVM(vmName);
351-
// Filter out nodes from other clouds: nodeTemplate is null for these.
352-
if (nodeTemplate == null) continue;
353-
if (getLabelString() != nodeTemplate.getLabelString()) continue;
354-
nodes.add(node);
355-
}
356-
return nodes;
357-
}
358-
359328
protected Object readResolve() {
360329
this.labelSet = Label.parse(labelString);
361330
if(this.templateInstanceCap == 0) {

src/main/java/org/jenkinsci/plugins/vsphere/VSphereNodeReconcileWork.java

Lines changed: 0 additions & 91 deletions
This file was deleted.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.jenkinsci.plugins.vsphere;
2+
3+
import hudson.Extension;
4+
import hudson.Functions;
5+
import hudson.model.TaskListener;
6+
import hudson.model.AsyncPeriodicWork;
7+
import hudson.slaves.Cloud;
8+
import jenkins.model.Jenkins;
9+
import org.jenkinsci.plugins.vSphereCloud;
10+
import org.jenkinsci.plugins.vSphereCloudSlaveTemplate;
11+
12+
import java.util.logging.Level;
13+
import java.util.logging.Logger;
14+
15+
import org.kohsuke.accmod.Restricted;
16+
import org.kohsuke.accmod.restrictions.NoExternalUse;
17+
18+
/**
19+
* A {@link AsyncPeriodicWork} that pre-provisions nodes to meet insntanceMin template value.
20+
* <p>
21+
* The async work will check the number of active nodes
22+
* and provision additional ones to meet template values.
23+
*
24+
* The check is happening every 2 minutes.
25+
*/
26+
@Extension
27+
@Restricted(NoExternalUse.class)
28+
public final class VSpherePreProvisonWork extends AsyncPeriodicWork {
29+
private static final Logger LOGGER = Logger.getLogger(VSpherePreProvisonWork.class.getName());
30+
31+
public VSpherePreProvisonWork() {
32+
super("Vsphere pre-provision check");
33+
}
34+
35+
@Override
36+
public long getRecurrencePeriod() {
37+
return Functions.getIsUnitTest() ? Long.MAX_VALUE : MIN * 2;
38+
}
39+
40+
@Override
41+
public void execute(TaskListener listener) {
42+
for (Cloud cloud : Jenkins.getActiveInstance().clouds) {
43+
if (!(cloud instanceof vSphereCloud)) continue;
44+
for (vSphereCloudSlaveTemplate template : ((vSphereCloud) cloud).getTemplates()) {
45+
if (template.getInstancesMin() > 0) {
46+
LOGGER.log(Level.INFO, "Check if template (label=" + template.getLabelString() + ") has enough active nodes to meet instances Min value");
47+
((vSphereCloud) cloud).preProvisionNodes(template);
48+
}
49+
}
50+
}
51+
}
52+
}

src/main/java/org/jenkinsci/plugins/vsphere/tools/CloudProvisioningAlgorithm.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,23 @@ public static String findUnusedName(CloudProvisioningRecord record) {
8181
+ ", even after " + maxAttempts + " attempts.");
8282
}
8383

84+
/**
85+
* Compares sum of provisioned and planned nodes for the template.
86+
*
87+
* If the sum is less than instanceMin template value we should provision more nodes,
88+
* otherwise the value is satisfied and we should not add any more nodes yet.
89+
*
90+
* @param record
91+
* Our record regarding the template the agent will be created
92+
* from.
93+
* @return A number of nodes to be provisioned.
94+
*/
95+
public static int shouldPreProvisionNodes(CloudProvisioningRecord record) {
96+
int provisionedNodes = record.getCurrentlyProvisioned().size() + record.getCurrentlyPlanned().size();
97+
int requiredNodes = record.getTemplate().getInstancesMin();
98+
return requiredNodes - provisionedNodes;
99+
}
100+
84101
private static String calcSequentialSuffix(final int attempt) {
85102
final int slaveNumber = attempt + 1;
86103
final String suffix = Integer.toString(slaveNumber);

src/main/resources/org/jenkinsci/plugins/vSphereCloudSlaveTemplate/help-instancesMin.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
<dt>If instances Min is bigger than instance Cap:</dt>
99
<dd>The plugin provisions max number of VMs specified in instance Cap (the smallest of cloud and template options).</dd>
1010
The plugin checks the number of running VMs once in 2 minutes.
11-
<dt>The plugin respects retention policies.</dt>
11+
<dt>Pre-provisoned VMs will be deleted based on the retention policy.</dt>
1212
</div>

0 commit comments

Comments
 (0)