diff --git a/src/main/java/org/jenkinsci/plugins/nomad/NomadProvisioningStrategy.java b/src/main/java/org/jenkinsci/plugins/nomad/NomadProvisioningStrategy.java index c2f4fa54..81207042 100644 --- a/src/main/java/org/jenkinsci/plugins/nomad/NomadProvisioningStrategy.java +++ b/src/main/java/org/jenkinsci/plugins/nomad/NomadProvisioningStrategy.java @@ -2,14 +2,19 @@ import hudson.Extension; import hudson.model.Label; +import hudson.model.Queue; import hudson.model.LoadStatistics.LoadStatisticsSnapshot; import hudson.slaves.Cloud; import hudson.slaves.NodeProvisioner; import hudson.slaves.NodeProvisioner.PlannedNode; import jenkins.model.Jenkins; +import jenkins.util.Timer; +import hudson.model.queue.QueueListener; +import hudson.slaves.CloudProvisioningListener; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collection; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -39,26 +44,33 @@ public NodeProvisioner.StrategyDecision apply(@NonNull NodeProvisioner.StrategyS for (Cloud nomadCloud : Jenkins.get().clouds) { if (nomadCloud instanceof NomadCloud) { - LOGGER.log(Level.FINE, "Available executors={0} connecting executors={1} AdditionalPlannedCapacity={2} pending ={3}", - new Object[]{snapshot.getAvailableExecutors(), snapshot.getConnectingExecutors(), strategyState.getAdditionalPlannedCapacity(), ((NomadCloud) nomadCloud).getPending()}); + LOGGER.log(Level.FINE, + "Available executors={0} connecting executors={1} AdditionalPlannedCapacity={2} pending ={3}", + new Object[] { snapshot.getAvailableExecutors(), snapshot.getConnectingExecutors(), + strategyState.getAdditionalPlannedCapacity(), ((NomadCloud) nomadCloud).getPending() }); int availableCapacity = snapshot.getAvailableExecutors() + snapshot.getConnectingExecutors() + strategyState.getAdditionalPlannedCapacity() + - ((NomadCloud) nomadCloud).getPending(); - + strategyState.getPlannedCapacitySnapshot(); + int previousCapacity = availableCapacity; int currentDemand = snapshot.getQueueLength(); LOGGER.log(Level.FINE, "Available capacity=" + availableCapacity + " currentDemand=" + currentDemand); if (availableCapacity < currentDemand) { - Collection plannedNodes = nomadCloud.provision(label, currentDemand - availableCapacity); + Collection plannedNodes = nomadCloud.provision(label, + currentDemand - availableCapacity); LOGGER.log(Level.FINE, "Planned " + plannedNodes.size() + " new nodes"); - + fireOnStarted(nomadCloud, strategyState.getLabel(), plannedNodes); strategyState.recordPendingLaunches(plannedNodes); availableCapacity += plannedNodes.size(); - LOGGER.log(Level.FINE, "After provisioning, available capacity=" + availableCapacity + " currentDemand=" + currentDemand); + LOGGER.log(Level.FINE, "After provisioning, available capacity=" + availableCapacity + + " currentDemand=" + currentDemand); + } + if (availableCapacity > previousCapacity && label != null) { + LOGGER.log(Level.FINE, "Suggesting NodeProvisioner review"); + Timer.get().schedule(label.nodeProvisioner::suggestReviewNow, 1L, TimeUnit.SECONDS); } - if (availableCapacity >= currentDemand) { LOGGER.log(Level.FINE, "Provisioning completed"); return NodeProvisioner.StrategyDecision.PROVISIONING_COMPLETED; @@ -71,5 +83,44 @@ public NodeProvisioner.StrategyDecision apply(@NonNull NodeProvisioner.StrategyS LOGGER.log(Level.FINE, "Provisioning not complete, consulting remaining strategies"); return NodeProvisioner.StrategyDecision.CONSULT_REMAINING_STRATEGIES; } -} + /** + * Force the onStarted event on the CloudProvisioningListener even if the nodes are not ready + * to notify the state as early as possible + */ + private static void fireOnStarted(final Cloud cloud, final Label label, + final Collection plannedNodes) { + for (CloudProvisioningListener cl : CloudProvisioningListener.all()) { + try { + cl.onStarted(cloud, label, plannedNodes); + } catch (Error e) { + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, "Unexpected uncaught exception encountered while " + + "processing onStarted() listener call in " + cl + " for label " + + label.toString(), e); + } + } + } + + /** + * Ping the nodeProvisioner as a new task enters the queue. + */ + @Extension + public static class FastProvisioning extends QueueListener { + + @Override + public void onEnterBuildable(Queue.BuildableItem item) { + final Jenkins jenkins = Jenkins.get(); + final Label label = item.getAssignedLabel(); + for (Cloud cloud : jenkins.clouds) { + if (cloud instanceof NomadCloud && cloud.canProvision(new Cloud.CloudState(label, 0))) { + final NodeProvisioner provisioner = (label == null + ? jenkins.unlabeledNodeProvisioner + : label.nodeProvisioner); + provisioner.suggestReviewNow(); + } + } + } + } +}