3434import com .microsoft .azure .vmagent .util .FailureStage ;
3535import com .microsoft .azure .vmagent .util .PoolLock ;
3636import com .microsoft .jenkins .credentials .AzureResourceManagerCache ;
37+ import edu .umd .cs .findbugs .annotations .CheckForNull ;
3738import edu .umd .cs .findbugs .annotations .NonNull ;
3839import hudson .Extension ;
3940import hudson .init .Initializer ;
5657import org .jenkinsci .plugins .cloudstats .CloudStatistics ;
5758import org .jenkinsci .plugins .cloudstats .ProvisioningActivity ;
5859import org .jenkinsci .plugins .cloudstats .TrackedPlannedNode ;
60+ import org .kohsuke .accmod .Restricted ;
61+ import org .kohsuke .accmod .restrictions .NoExternalUse ;
5962import org .kohsuke .stapler .AncestorInPath ;
6063import org .kohsuke .stapler .DataBoundConstructor ;
6164import org .kohsuke .stapler .DataBoundSetter ;
7174import java .util .HashMap ;
7275import java .util .List ;
7376import java .util .Map ;
77+ import java .util .TreeMap ;
7478import java .util .concurrent .Callable ;
7579import java .util .concurrent .CopyOnWriteArrayList ;
7680import java .util .concurrent .ExecutionException ;
@@ -122,7 +126,8 @@ public class AzureVMCloud extends Cloud {
122126 private transient String configurationStatus ;
123127
124128 // Approximate virtual machine count. Updated periodically.
125- private int approximateVirtualMachineCount ;
129+ @ CheckForNull
130+ private transient Map <String , Integer > approximateVirtualMachineCountsByTemplate ;
126131
127132 private transient AzureResourceManager azureClient ;
128133
@@ -358,74 +363,94 @@ public void setConfigurationStatus(String status) {
358363 }
359364
360365 /**
361- * Retrieves the current approximate virtual machine count.
366+ * Retrieves the current approximate virtual machine count,
367+ * counting only VMs we've made.
362368 *
363369 * @return The approximate count
364370 */
365371 public int getApproximateVirtualMachineCount () {
366372 synchronized (this ) {
367- return approximateVirtualMachineCount ;
373+ int count = 0 ;
374+ if (approximateVirtualMachineCountsByTemplate != null ) {
375+ for (final Integer templateCount : approximateVirtualMachineCountsByTemplate .values ()) {
376+ count += templateCount ;
377+ }
378+ }
379+ return count ;
368380 }
369381 }
370382
371383 /**
372- * Given the number of VMs that are desired, returns the number of VMs that
373- * can be allocated and adjusts the number of VMs we believe exist.
374- * If the number desired is less than 0, this subtracts from the total number
375- * of virtual machines we currently have available.
384+ * Retrieves the current count of the number of virtual machines for a specific
385+ * template.
376386 *
377- * @param delta Number that are desired, or if less than 0, the number we are 'returning' to the pool
378- * @param templateLimit max agents for the template in use
379- * @return Number that can be allocated, up to the number desired. 0 if the number desired was < 0
387+ * @param template the template VMs are being counted for.
388+ * @return The number of VMs of that template.
380389 */
381- public int adjustVirtualMachineCount (int delta , int templateLimit ) {
390+ @ Restricted (NoExternalUse .class )
391+ int getApproximateVirtualMachineCountForTemplate (AzureVMAgentTemplate template ) {
392+ final String templateName = template .getTemplateName ();
382393 synchronized (this ) {
383- if (delta < 0 ) {
384- LOGGER .log (Level .FINE , "Current estimated VM count: {0}, reducing by {1}" ,
385- new Object []{approximateVirtualMachineCount , delta });
386- approximateVirtualMachineCount = Math .max (0 , approximateVirtualMachineCount + delta );
387- return 0 ;
388- } else {
389- LOGGER .log (Level .FINE , "Current estimated VM count: {0}, quantity desired {1}" ,
390- new Object []{approximateVirtualMachineCount , delta });
391- int limit = determineLimit (templateLimit );
392- if (approximateVirtualMachineCount + delta <= limit ) {
393- // Enough available, return the desired quantity, and update the number we think we
394- // have laying around.
395- approximateVirtualMachineCount += delta ;
396- return delta ;
397- } else {
398- // Not enough available, return what we have. Remember we could
399- // go negative (if for instance another Jenkins instance had
400- // a higher limit.
401- int quantityAvailable = Math .max (0 , limit - approximateVirtualMachineCount );
402- approximateVirtualMachineCount += quantityAvailable ;
403- return quantityAvailable ;
394+ if (approximateVirtualMachineCountsByTemplate != null ) {
395+ final Integer templateCount = approximateVirtualMachineCountsByTemplate .get (templateName );
396+ if (templateCount != null ) {
397+ return templateCount ;
404398 }
405399 }
400+ return 0 ;
406401 }
407402 }
408403
409- private int determineLimit (int templateLimit ) {
410- int cloudLimit = getMaxVirtualMachinesLimit ();
411- if (templateLimit > cloudLimit ) {
412- return cloudLimit ;
413- }
414- if (templateLimit == 0 ) {
415- return cloudLimit ;
404+ /**
405+ * Adjusts our estimate of the current number of VMs we have for a specific
406+ * template.
407+ *
408+ * @param delta The change to make. Positive means we think we've got more
409+ * VMs, negative means we think we've got fewer.
410+ * @param template the template we're talking about.
411+ */
412+ @ Restricted (NoExternalUse .class )
413+ void adjustApproximateVirtualMachineCount (int delta , AzureVMAgentTemplate template ) {
414+ final String templateName = template .getTemplateName ();
415+ synchronized (this ) {
416+ final int currentCount = getApproximateVirtualMachineCountForTemplate (template );
417+ final int newCount = currentCount + delta ;
418+ if (approximateVirtualMachineCountsByTemplate == null ) {
419+ if (newCount != 0 ) {
420+ setCurrentVirtualMachineCount (Collections .singletonMap (templateName , newCount ));
421+ }
422+ } else {
423+ if (newCount == 0 ) {
424+ approximateVirtualMachineCountsByTemplate .remove (templateName );
425+ if (approximateVirtualMachineCountsByTemplate .isEmpty ()) {
426+ approximateVirtualMachineCountsByTemplate = null ;
427+ }
428+ } else {
429+ approximateVirtualMachineCountsByTemplate .put (templateName , newCount );
430+ }
431+ }
416432 }
417- return templateLimit ;
418433 }
419434
420435 /**
421- * Sets the new approximate virtual machine count .
436+ * Sets the new approximate virtual machine counts .
422437 * This is run by the verification task to update the VM count periodically.
423438 *
424- * @param newCount New approximate count
439+ * @param countsIndexedByTemplateName New approximate counts
425440 */
426- public void setVirtualMachineCount (int newCount ) {
441+ @ Restricted (NoExternalUse .class )
442+ void setCurrentVirtualMachineCount (Map <String , Integer > countsIndexedByTemplateName ) {
427443 synchronized (this ) {
428- approximateVirtualMachineCount = newCount ;
444+ if (countsIndexedByTemplateName == null || countsIndexedByTemplateName .isEmpty ()) {
445+ approximateVirtualMachineCountsByTemplate = null ;
446+ } else {
447+ if (approximateVirtualMachineCountsByTemplate == null ) {
448+ approximateVirtualMachineCountsByTemplate = new TreeMap <>(countsIndexedByTemplateName );
449+ } else {
450+ approximateVirtualMachineCountsByTemplate .clear ();
451+ approximateVirtualMachineCountsByTemplate .putAll (countsIndexedByTemplateName );
452+ }
453+ }
429454 }
430455 }
431456
@@ -675,26 +700,22 @@ public Collection<PlannedNode> provision(CloudState cloudState, int workLoad) {
675700 if (numberOfAgents > 0 ) {
676701 if (template .getMaximumDeploymentSize () > 0 && numberOfAgents > template .getMaximumDeploymentSize ()) {
677702 LOGGER .log (Level .FINE ,
678- "Setting size of deployment from {0} to {1} nodes, according to template's maximum deployment size" ,
679- new Object []{numberOfAgents , template .getMaximumDeploymentSize ()});
703+ "Reduced template {0} deployment from {1} to {2} nodes, due to its maximumDeploymentSize" ,
704+ new Object [] {template .getTemplateName (), numberOfAgents ,
705+ template .getMaximumDeploymentSize () });
680706 numberOfAgents = template .getMaximumDeploymentSize ();
681707 }
682708
683709 try {
684710 // Determine how many agents we can actually provision from here and
685711 // adjust our count (before deployment to avoid races)
686- int adjustedNumberOfAgents = adjustVirtualMachineCount (numberOfAgents ,
687- template .getMaxVirtualMachinesLimit ());
712+ final int adjustedNumberOfAgents ;
713+ synchronized (this ) {
714+ adjustedNumberOfAgents = calculateNumberOfAgentsToRequest (template , numberOfAgents );
715+ adjustApproximateVirtualMachineCount (adjustedNumberOfAgents , template );
716+ }
688717 if (adjustedNumberOfAgents == 0 ) {
689- LOGGER .log (Level .INFO ,
690- "Not able to create {0} nodes, at or above maximum VM count of {1} and already {2} VM(s)" ,
691- new Object []{numberOfAgents , determineLimit (template .getMaxVirtualMachinesLimit ()),
692- getApproximateVirtualMachineCount ()});
693718 return plannedNodes ;
694- } else if (adjustedNumberOfAgents < numberOfAgents ) {
695- LOGGER .log (Level .INFO ,
696- "Able to create new nodes, but can only create {0} (desired {1})" ,
697- new Object []{adjustedNumberOfAgents , numberOfAgents });
698719 }
699720 doProvision (adjustedNumberOfAgents , plannedNodes , template );
700721 // wait for deployment completion and then check for created nodes
@@ -710,6 +731,49 @@ public Collection<PlannedNode> provision(CloudState cloudState, int workLoad) {
710731 return plannedNodes ;
711732 }
712733
734+ /**
735+ * Works out how many VMs our limits allow us to request.
736+ *
737+ * @param template The template in question
738+ * @param desiredNumberOfAgents The number of VMs we'd like to have if there
739+ * were no limits.
740+ * @return The number we can request before exceeding our cloud and template
741+ * limits.
742+ */
743+ @ Restricted (NoExternalUse .class ) // Package access for tests only
744+ int calculateNumberOfAgentsToRequest (final AzureVMAgentTemplate template , int desiredNumberOfAgents ) {
745+ final int currentVMsForTemplate = Math .max (0 , getApproximateVirtualMachineCountForTemplate (template ));
746+ final int maxVMsForTemplate = template .getMaxVirtualMachinesLimit () > 0
747+ ? template .getMaxVirtualMachinesLimit ()
748+ : Integer .MAX_VALUE ;
749+ final int currentVMsForCloud = Math .max (0 , getApproximateVirtualMachineCount ());
750+ final int maxVMsForCloud = getMaxVirtualMachinesLimit () > 0 ? getMaxVirtualMachinesLimit ()
751+ : Integer .MAX_VALUE ;
752+ final int maxBeforeTemplateLimit = Math .max (0 , maxVMsForTemplate - currentVMsForTemplate );
753+ final int maxBeforeCloudLimit = Math .max (0 , maxVMsForCloud - currentVMsForCloud );
754+ final int adjustedNumberOfAgents = Math .min (Math .min (maxBeforeTemplateLimit , maxBeforeCloudLimit ),
755+ desiredNumberOfAgents );
756+ final String templateMsg = desiredNumberOfAgents > maxBeforeTemplateLimit
757+ ? ", have template limit of {3} but have {4} VMs already so we can have {5} more"
758+ : ", currently have {4} VMs of this template" ;
759+ final String cloudMsg = desiredNumberOfAgents > maxBeforeCloudLimit
760+ ? ", have cloud limit of {6} but have {7} VMs already so we can only have {8} more"
761+ : ", currently have {7} VMs in cloud" ;
762+ final Object [] logParams = new Object [] {template .getTemplateName (), desiredNumberOfAgents ,
763+ adjustedNumberOfAgents , maxVMsForTemplate , currentVMsForTemplate , maxBeforeTemplateLimit ,
764+ maxVMsForCloud , currentVMsForCloud , maxBeforeCloudLimit };
765+ if (adjustedNumberOfAgents == 0 ) {
766+ LOGGER .log (Level .INFO , "Wanted to create {1} nodes from template {0} but cannot create any"
767+ + templateMsg + cloudMsg , logParams );
768+ } else if (adjustedNumberOfAgents < desiredNumberOfAgents ) {
769+ LOGGER .log (Level .INFO , "Wanted to create {1} nodes from template {0} but can only create {2}"
770+ + templateMsg + cloudMsg , logParams );
771+ } else {
772+ LOGGER .log (Level .INFO , "Creating {1} nodes from template {0}" + templateMsg + cloudMsg , logParams );
773+ }
774+ return adjustedNumberOfAgents ;
775+ }
776+
713777 public void doProvision (final int numberOfNewAgents ,
714778 List <PlannedNode > plannedNodes ,
715779 final AzureVMAgentTemplate template ) {
@@ -858,8 +922,8 @@ private void handleFailure(
858922 // Do not throw to avoid it being recorded
859923 }
860924 }
861- template .retrieveAzureCloudReference ().adjustVirtualMachineCount (-1 ,
862- template . getMaxVirtualMachinesLimit () );
925+ template .retrieveAzureCloudReference ().adjustApproximateVirtualMachineCount (-1 ,
926+ template );
863927 // Update the template status given this new issue.
864928 template .handleTemplateProvisioningFailure (e .getMessage (), stage );
865929 }
0 commit comments