-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add nodes pre-provisioning #127
base: master
Are you sure you want to change the base?
Changes from all commits
3116d76
b9bab52
1656dfc
2a5abc1
256eaf4
335181d
1cbedb6
3fe45d5
d27517a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package org.jenkinsci.plugins.vsphere; | ||
|
||
import hudson.Extension; | ||
import hudson.Functions; | ||
import hudson.model.TaskListener; | ||
import hudson.model.AsyncPeriodicWork; | ||
import hudson.slaves.Cloud; | ||
import jenkins.model.Jenkins; | ||
import org.jenkinsci.plugins.vSphereCloud; | ||
import org.jenkinsci.plugins.vSphereCloudSlaveTemplate; | ||
|
||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
|
||
import org.kohsuke.accmod.Restricted; | ||
import org.kohsuke.accmod.restrictions.NoExternalUse; | ||
|
||
/** | ||
* A {@link AsyncPeriodicWork} that pre-provisions nodes to meet insntanceMin template value. | ||
* <p> | ||
* The async work will check the number of active nodes | ||
* and provision additional ones to meet template values. | ||
* | ||
* The check is happening every 2 minutes. | ||
*/ | ||
@Extension | ||
@Restricted(NoExternalUse.class) | ||
public final class VSpherePreProvisonWork extends AsyncPeriodicWork { | ||
private static final Logger LOGGER = Logger.getLogger(VSpherePreProvisonWork.class.getName()); | ||
|
||
public VSpherePreProvisonWork() { | ||
super("Vsphere pre-provision check"); | ||
} | ||
|
||
@Override | ||
public long getRecurrencePeriod() { | ||
return Functions.getIsUnitTest() ? Long.MAX_VALUE : MIN * 2; | ||
} | ||
|
||
@Override | ||
public void execute(TaskListener listener) { | ||
for (Cloud cloud : Jenkins.getActiveInstance().clouds) { | ||
if (!(cloud instanceof vSphereCloud)) continue; | ||
vSphereCloud vsCloud = (vSphereCloud) cloud; | ||
for (vSphereCloudSlaveTemplate template : vsCloud.getTemplates()) { | ||
if (template.getInstancesMin() > 0) { | ||
LOGGER.log(Level.INFO, "Check if template (label=" + template.getLabelString() + ") has enough active nodes to meet instances Min value"); | ||
vsCloud.preProvisionNodes(template); | ||
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,6 +81,23 @@ public static String findUnusedName(CloudProvisioningRecord record) { | |
+ ", even after " + maxAttempts + " attempts."); | ||
} | ||
|
||
/** | ||
* Compares sum of provisioned and planned nodes for the template. | ||
* | ||
* If the sum is less than instanceMin template value we should provision more nodes, | ||
* otherwise the value is satisfied and we should not add any more nodes yet. | ||
* | ||
* @param record | ||
* Our record regarding the template the agent will be created | ||
* from. | ||
* @return A number of nodes to be provisioned. | ||
*/ | ||
public static int shouldPreProvisionNodes(CloudProvisioningRecord record) { | ||
int provisionedNodes = record.getCurrentlyProvisioned().size() + record.getCurrentlyPlanned().size(); | ||
int requiredPreProvisionedNodes = record.getTemplate().getInstancesMin(); | ||
return requiredPreProvisionedNodes - provisionedNodes; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're going to need a unit test for this new method. |
||
|
||
private static String calcSequentialSuffix(final int attempt) { | ||
final int slaveNumber = attempt + 1; | ||
final String suffix = Integer.toString(slaveNumber); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<div> | ||
The number of VMs to be provisioned beforehand.<br> | ||
This allows to speed up CI runs by starting them immediately without waiting for a VM to get booted.<br> | ||
<dt>If the number is set to 0:</dt> | ||
<dd>No VMs provisioned in advance.</dd> | ||
<dt>If the number is bigger than 0:</dt> | ||
<dd>The plugin provisions new VMs to meet the value.</dd> | ||
<dt>If instances Min is bigger than instance Cap:</dt> | ||
<dd>The plugin provisions max number of VMs specified in instance Cap (the smallest of cloud and template options).</dd> | ||
The plugin checks the number of running VMs once in 2 minutes. | ||
<dt>Pre-provisoned VMs will be deleted based on the retention policy.</dt> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package org.jenkinsci.plugins; | ||
|
||
import static org.junit.Assert.*; | ||
import static org.hamcrest.CoreMatchers.*; | ||
|
||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.logging.Handler; | ||
import java.util.logging.Level; | ||
import java.util.logging.LogRecord; | ||
import java.util.logging.Logger; | ||
|
||
import org.jenkinsci.plugins.vSphereCloud; | ||
import org.jenkinsci.plugins.vSphereCloudSlaveTemplate; | ||
import org.jenkinsci.plugins.vSphereCloud.VSpherePlannedNode; | ||
import org.jenkinsci.plugins.vsphere.VSphereConnectionConfig; | ||
import org.jenkinsci.plugins.vsphere.tools.CloudProvisioningAlgorithm; | ||
import org.jenkinsci.plugins.vsphere.tools.CloudProvisioningRecord; | ||
import org.jenkinsci.plugins.vsphere.tools.CloudProvisioningState; | ||
|
||
import hudson.model.Label; | ||
import hudson.slaves.JNLPLauncher; | ||
import hudson.slaves.RetentionStrategy; | ||
import hudson.slaves.NodeProvisioner.PlannedNode; | ||
|
||
import org.junit.Before; | ||
import org.junit.BeforeClass; | ||
import org.junit.Test; | ||
|
||
public class NodePreProvisionTest { | ||
/** Used when faking up test data */ | ||
private static List<vSphereCloudSlaveTemplate> stubVSphereCloudTemplates; | ||
private static VSphereConnectionConfig vsConnectionConfig; | ||
private static vSphereCloud stubVSphereCloud; | ||
private static CloudProvisioningState stubVSphereTemplateState; | ||
private Logger testLogger; | ||
private List<LogRecord> loggedMessages; | ||
|
||
@BeforeClass | ||
public static void setupClass() { | ||
stubVSphereCloudTemplates = new ArrayList<vSphereCloudSlaveTemplate>(); | ||
vsConnectionConfig = new VSphereConnectionConfig("vsHost", false, "credentialsId"); | ||
stubVSphereCloud = new vSphereCloud(vsConnectionConfig, "vsDescription", 100, 100, stubVSphereCloudTemplates); | ||
stubVSphereTemplateState = new CloudProvisioningState(stubVSphereCloud); | ||
} | ||
|
||
@Before | ||
public void setup() { | ||
stubVSphereCloudTemplates.clear(); | ||
loggedMessages = new ArrayList<LogRecord>(); | ||
// Get vSphereCloud logger | ||
Logger logger = Logger.getLogger("vsphere-cloud"); | ||
logger.setLevel(Level.ALL); | ||
// final Handler[] handlers = logger.getHandlers(); | ||
// for (final Handler handler : handlers) { | ||
// logger.removeHandler(handler); | ||
// } | ||
final Handler testHandler = new Handler() { | ||
@Override | ||
public void publish(LogRecord record) { | ||
loggedMessages.add(record); | ||
} | ||
|
||
@Override | ||
public void flush() { | ||
} | ||
|
||
@Override | ||
public void close() { | ||
} | ||
}; | ||
logger.addHandler(testHandler); | ||
testLogger = logger; | ||
} | ||
|
||
@Test | ||
public void shouldPreProvisionNodesWhenNotEnough() { | ||
vSphereCloudSlaveTemplate template = createTemplate(10, 2); | ||
provisionNode(template); | ||
assertThat(stubVSphereTemplateState.countNodes(), equalTo(1)); | ||
|
||
// Here it says that there still should be provisioned 2 nodes, despite the fact there is 1 active already | ||
stubVSphereCloud.preProvisionNodes(template); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logs:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note, that when |
||
|
||
// Below is a draft line | ||
//assertThat(loggedMessages.get(1).getMessage(), equalTo("should pre-provision 1 node")); | ||
|
||
} | ||
|
||
private vSphereCloudSlaveTemplate createTemplate(int templateCapacity, int instanceMin){ | ||
return stubTemplate("templateCapacity" + templateCapacity + "instanceMin" + instanceMin, templateCapacity, instanceMin); | ||
} | ||
|
||
private void provisionNode(vSphereCloudSlaveTemplate template) { | ||
CloudProvisioningRecord provisionable = stubVSphereTemplateState.getOrCreateRecord(template); | ||
final String nodeName = CloudProvisioningAlgorithm.findUnusedName(provisionable); | ||
stubVSphereTemplateState.provisionedSlaveNowActive(provisionable, nodeName); | ||
// Below doesn't work either | ||
//VSpherePlannedNode.createInstance(stubVSphereTemplateState, nodeName, provisionable); | ||
} | ||
|
||
private static vSphereCloudSlaveTemplate stubTemplate(String prefix, int templateInstanceCap, int instanceMin) { | ||
return new vSphereCloudSlaveTemplate(prefix, "", null, null, false, null, null, null, null, null, null, templateInstanceCap, 1, | ||
null, null, null, false, false, 0, 0, false, null, null, instanceMin, null, new JNLPLauncher(), | ||
RetentionStrategy.NOOP, null, null); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not subtracting 1 here since
maxSlavesToProvisionBeforeCloudCapHit
is re-calculated on every call tocloudHasCapacity()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood - done like this (re-calculating every time) is not as CPU-efficient as it was, but I guess the additional workload is insigificant compared to everything else.