third_party/jenkins_plugin/kubernetes: fix a bug about how pods are counted and capped.

Keep track of "queued" pods and take them into account when checking
caps.

Also added more logs.

Change-Id: I9a6e45493c270142b80cc1d5f1a1fb0edf122a77
diff --git a/java/jenkins_plugins/kubernetes/README.google b/java/jenkins_plugins/kubernetes/README.google
index 8b55f53..e635dec 100644
--- a/java/jenkins_plugins/kubernetes/README.google
+++ b/java/jenkins_plugins/kubernetes/README.google
@@ -7,4 +7,5 @@
 Run dynamic slaves in a Kubernetes/Docker environment.
 
 Local Modifications:
-None.
+- Fixed a bug about how pods are counted and capped.
+- Minor UI tweaks.
diff --git a/java/jenkins_plugins/kubernetes/pom.xml b/java/jenkins_plugins/kubernetes/pom.xml
index 59256a8..57ed7e9 100644
--- a/java/jenkins_plugins/kubernetes/pom.xml
+++ b/java/jenkins_plugins/kubernetes/pom.xml
@@ -7,9 +7,9 @@
   </parent>
 
   <groupId>org.csanchez.jenkins.plugins</groupId>
-  <artifactId>kubernetes</artifactId>
-  <version>0.6-SNAPSHOT</version>
-  <name>Kubernetes plugin</name>
+  <artifactId>vanadium-kubernetes</artifactId>
+  <version>0.7</version>
+  <name>Vanadium Kubernetes plugin</name>
   <description>Jenkins plugin to run dynamic slaves in a Kubernetes/Docker environment</description>
   <packaging>hpi</packaging>
   <url>https://wiki.jenkins-ci.org/display/JENKINS/Kubernetes+Plugin</url>
diff --git a/java/jenkins_plugins/kubernetes/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java b/java/jenkins_plugins/kubernetes/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java
index e27bc10..e0be826 100644
--- a/java/jenkins_plugins/kubernetes/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java
+++ b/java/jenkins_plugins/kubernetes/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java
@@ -1,15 +1,23 @@
 package org.csanchez.jenkins.plugins.kubernetes;
 
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.AtomicLongMap;
+
 import com.cloudbees.plugins.credentials.CredentialsMatchers;
 import com.cloudbees.plugins.credentials.CredentialsProvider;
 import com.cloudbees.plugins.credentials.common.StandardCredentials;
 import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
 import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
 import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
+
+import org.apache.commons.lang.StringUtils;
+import org.kohsuke.stapler.DataBoundConstructor;
+import org.kohsuke.stapler.DataBoundSetter;
+import org.kohsuke.stapler.QueryParameter;
+
 import hudson.Extension;
 import hudson.Util;
 import hudson.model.Computer;
@@ -21,16 +29,17 @@
 import hudson.slaves.NodeProvisioner;
 import hudson.util.FormValidation;
 import hudson.util.ListBoxModel;
-import io.fabric8.kubernetes.api.model.*;
+import io.fabric8.kubernetes.api.model.ContainerStatus;
+import io.fabric8.kubernetes.api.model.EnvVar;
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodBuilder;
+import io.fabric8.kubernetes.api.model.PodList;
+import io.fabric8.kubernetes.api.model.Volume;
+import io.fabric8.kubernetes.api.model.VolumeMount;
 import io.fabric8.kubernetes.client.KubernetesClient;
 import jenkins.model.Jenkins;
 import jenkins.model.JenkinsLocationConfiguration;
-import org.apache.commons.lang.StringUtils;
-import org.kohsuke.stapler.DataBoundConstructor;
-import org.kohsuke.stapler.DataBoundSetter;
-import org.kohsuke.stapler.QueryParameter;
 
-import javax.annotation.CheckForNull;
 import java.io.IOException;
 import java.net.URL;
 import java.security.KeyStoreException;
@@ -47,11 +56,13 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import javax.annotation.CheckForNull;
+
 /**
  * Kubernetes cloud provider.
- * 
+ *
  * Starts slaves in a Kubernetes cluster using defined Docker templates for each label.
- * 
+ *
  * @author Carlos Sanchez carlos@apache.org
  */
 public class KubernetesCloud extends Cloud {
@@ -87,6 +98,13 @@
 
     private transient KubernetesClient client;
 
+    /**
+     * Keeps track of number of "queued" pods, indexed by templates.
+     *
+     * Queued pods are the ones that are being created that Jenkins doesn't know about.
+     */
+    private AtomicLongMap<PodTemplate> queuedPodsCounts;
+
     @DataBoundConstructor
     public KubernetesCloud(String name, List<? extends PodTemplate> templates, String serverUrl, String namespace,
             String jenkinsUrl, String containerCapStr, int connectTimeout, int readTimeout, int retentionTimeout) {
@@ -287,7 +305,19 @@
     public synchronized Collection<NodeProvisioner.PlannedNode> provision(final Label label, final int excessWorkload) {
         try {
 
-            LOGGER.log(Level.INFO, "Excess workload after pending Spot instances: " + excessWorkload);
+            LOGGER.log(Level.INFO,
+                "Excess workload for label '" + label.getName()
+                    + "' after pending Spot instances: "
+                    + excessWorkload);
+
+            // The constructor of this class will only be invoked when the kubernetes cloud
+            // configuration is created in Jenkins' "Configure System" page. It won't be invoked
+            // when Jenkins is restarted.
+            //
+            // To make sure queuedPodsCounts is initialized properly we do it here.
+            if (queuedPodsCounts == null) {
+              queuedPodsCounts = AtomicLongMap.create();
+            }
 
             List<NodeProvisioner.PlannedNode> r = new ArrayList<NodeProvisioner.PlannedNode>();
 
@@ -317,8 +347,12 @@
             this.cloud = cloud;
             this.t = t;
             this.label = label;
+
+            // Increase queued slave count for this pod template.
+            queuedPodsCounts.incrementAndGet(t);
         }
 
+        @Override
         public Node call() throws Exception {
             KubernetesSlave slave = null;
             try {
@@ -391,6 +425,9 @@
                 LOGGER.log(Level.SEVERE, "Error in provisioning; slave={0}, template={1}", new Object[] { slave, t });
                 ex.printStackTrace();
                 throw Throwables.propagate(ex);
+            } finally {
+              // Decrease the count of queued pods.
+              queuedPodsCounts.decrementAndGet(t);
             }
         }
     }
@@ -412,17 +449,30 @@
             return true;
         }
 
+        LOGGER.log(Level.INFO,
+            "Checking caps for label '" + label.getName()
+                + "' in template '" + template.getName() + "'");
+        long totalQueuedPods = queuedPodsCounts.sum();
+        long queuedPodsForThisTemplate = queuedPodsCounts.get(template);
+
         KubernetesClient client = connect();
         PodList slaveList = client.pods().inNamespace(namespace).withLabels(POD_LABEL).list();
         PodList namedList = client.pods().inNamespace(namespace).withLabel("name", getIdForLabel(label)).list();
 
+        LOGGER.log(Level.INFO,
+          "global container cap: " + containerCap + "\n"
+              + "template container cap: " + template.getInstanceCap() + "\n"
+              + "total running pods: " + slaveList.getItems().size() + "\n"
+              + "total queued pods: " + totalQueuedPods + "\n"
+              + "running pods for '" + label.getName() + "': " + namedList.getItems().size() + "\n"
+              + "queued pods for '" + label.getName() + "': " + queuedPodsForThisTemplate + "\n");
 
-        if (containerCap < slaveList.getItems().size()) {
+        if (containerCap <= slaveList.getItems().size() + totalQueuedPods) {
             LOGGER.log(Level.INFO, "Total container cap of " + containerCap + " reached, not provisioning.");
             return false;
         }
 
-        if (template.getInstanceCap() < namedList.getItems().size()) {
+        if (template.getInstanceCap() <= namedList.getItems().size() + queuedPodsForThisTemplate) {
             LOGGER.log(Level.INFO, "Template instance cap of " + template.getInstanceCap() + " reached for template "
                     + template.getImage() + ", not provisioning.");
             return false; // maxed out
@@ -480,7 +530,7 @@
     public static class DescriptorImpl extends Descriptor<Cloud> {
         @Override
         public String getDisplayName() {
-            return "Kubernetes";
+            return "Vanadium Kubernetes";
         }
 
         public FormValidation doTestConnection(@QueryParameter URL serverUrl, @QueryParameter String credentialsId,
diff --git a/java/jenkins_plugins/kubernetes/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/Messages.properties b/java/jenkins_plugins/kubernetes/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/Messages.properties
index 3675a2d..f06c427 100644
--- a/java/jenkins_plugins/kubernetes/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/Messages.properties
+++ b/java/jenkins_plugins/kubernetes/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/Messages.properties
@@ -3,6 +3,6 @@
 PluginDescription=\
   Plugin for launching Slaves on Kubernetes
 DisplayName=\
-  Kubernetes
+  Vanadium Kubernetes
 
 offline=Kubernetes slave is going offline