forked from jenkinsci/jclouds-plugin
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial work on BuildWrapper for spinning up non-slave instances duri…
…ng a build. TODO: Add config help. Add a lot better logging. Still really want to clear out that net.schmizz noise, and would really like to capture some of the logging from jclouds.compute, filter it, and dump it to the job log. And I'm fairly sure my code could be really cleaned up.
- Loading branch information
Showing
7 changed files
with
353 additions
and
7 deletions.
There are no files selected for viewing
63 changes: 63 additions & 0 deletions
63
src/main/java/jenkins/plugins/jclouds/compute/InstancesToRun.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package jenkins.plugins.jclouds.compute; | ||
|
||
import java.io.Serializable; | ||
|
||
import hudson.Extension; | ||
import hudson.Util; | ||
import hudson.model.AbstractDescribableImpl; | ||
import hudson.model.Descriptor; | ||
import hudson.util.FormValidation; | ||
import hudson.util.ListBoxModel; | ||
|
||
import org.apache.commons.lang.StringUtils; | ||
import org.kohsuke.stapler.DataBoundConstructor; | ||
import org.kohsuke.stapler.QueryParameter; | ||
import org.kohsuke.stapler.export.Exported; | ||
import org.kohsuke.stapler.export.ExportedBean; | ||
|
||
public final class InstancesToRun extends AbstractDescribableImpl<InstancesToRun> { | ||
public final String cloudName; | ||
public final String templateName; | ||
public final int count; | ||
public final boolean suspendOrTerminate; | ||
|
||
@DataBoundConstructor | ||
public InstancesToRun(String cloudName, String templateName, int count, boolean suspendOrTerminate) { | ||
this.cloudName = Util.fixEmptyAndTrim(cloudName); | ||
this.templateName = Util.fixEmptyAndTrim(templateName); | ||
this.count = count; | ||
this.suspendOrTerminate = suspendOrTerminate; | ||
} | ||
|
||
@Extension | ||
public static class DescriptorImpl extends Descriptor<InstancesToRun> { | ||
public ListBoxModel doFillCloudNameItems() { | ||
ListBoxModel m = new ListBoxModel(); | ||
for (String cloudName : JCloudsCloud.getCloudNames()) { | ||
m.add(cloudName, cloudName); | ||
} | ||
|
||
return m; | ||
} | ||
|
||
public ListBoxModel doFillTemplateNameItems(@QueryParameter String cloudName) { | ||
ListBoxModel m = new ListBoxModel(); | ||
JCloudsCloud c = JCloudsCloud.getByName(cloudName); | ||
if (c != null) { | ||
for (JCloudsSlaveTemplate t : c.getTemplates()) { | ||
m.add(String.format("%s in cloud %s", t.name, cloudName), t.name); | ||
} | ||
} | ||
return m; | ||
} | ||
|
||
public FormValidation doCheckCount(@QueryParameter String value) { | ||
return FormValidation.validatePositiveInteger(value); | ||
} | ||
|
||
@Override | ||
public String getDisplayName() { | ||
return ""; | ||
} | ||
} | ||
} |
241 changes: 241 additions & 0 deletions
241
src/main/java/jenkins/plugins/jclouds/compute/JCloudsBuildWrapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
package jenkins.plugins.jclouds.compute; | ||
|
||
import hudson.Extension; | ||
import hudson.Launcher; | ||
import hudson.Util; | ||
import hudson.model.AbstractBuild; | ||
import hudson.model.AbstractDescribableImpl; | ||
import hudson.model.AbstractProject; | ||
import hudson.model.BuildListener; | ||
import hudson.model.Computer; | ||
import hudson.tasks.BuildWrapper; | ||
import hudson.tasks.BuildWrapperDescriptor; | ||
import hudson.util.FormValidation; | ||
import hudson.util.ListBoxModel; | ||
|
||
import org.kohsuke.stapler.DataBoundConstructor; | ||
import org.kohsuke.stapler.QueryParameter; | ||
import org.kohsuke.stapler.StaplerRequest; | ||
import org.kohsuke.stapler.export.Exported; | ||
import org.kohsuke.stapler.export.ExportedBean; | ||
|
||
import org.jclouds.compute.ComputeService; | ||
import org.jclouds.compute.domain.NodeMetadata; | ||
import org.jclouds.compute.domain.NodeState; | ||
|
||
import java.io.IOException; | ||
import java.io.PrintStream; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.Future; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.Callable; | ||
|
||
public class JCloudsBuildWrapper extends BuildWrapper { | ||
public List<InstancesToRun> instancesToRun; | ||
public final int MAX_ATTEMPTS = 5; | ||
|
||
@DataBoundConstructor | ||
public JCloudsBuildWrapper(List<InstancesToRun> instancesToRun) { | ||
this.instancesToRun = instancesToRun; | ||
} | ||
|
||
public InstancesToRun getMatchingInstanceToRun(String cloudName, String templateName) { | ||
for (InstancesToRun i : instancesToRun) { | ||
if (i.cloudName.equals(cloudName) && i.templateName.equals(templateName)) { | ||
return i; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
@Override | ||
public Environment setUp(AbstractBuild build, final Launcher launcher, final BuildListener listener) throws IOException, InterruptedException { | ||
|
||
final Map<String,Map<String,List<NodeMetadata>>> instances; | ||
Map<String,Map<String,List<NodeMetadata>>> spawnedInstances = new HashMap<String,Map<String,List<NodeMetadata>>>(); | ||
List<PlannedInstance> plannedInstances = new ArrayList<PlannedInstance>(); | ||
|
||
for (InstancesToRun instance : instancesToRun) { | ||
final JCloudsCloud cloud = JCloudsCloud.getByName(instance.cloudName); | ||
final JCloudsSlaveTemplate template = cloud.getTemplate(instance.templateName); | ||
|
||
for (int i=0; i < instance.count; i++) { | ||
|
||
plannedInstances.add(new PlannedInstance(instance.cloudName, | ||
instance.templateName, | ||
i, | ||
Computer.threadPoolForRemoting.submit(new Callable<NodeMetadata>() { | ||
public NodeMetadata call() throws Exception { | ||
int attempts = 0; | ||
|
||
while (attempts < MAX_ATTEMPTS) { | ||
attempts++; | ||
try { | ||
NodeMetadata n = template.provision(); | ||
if (n != null) { | ||
return n; | ||
} | ||
} catch (RuntimeException e) { | ||
// Something to log the e.getCause() which should be a RunNodesException | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
}))); | ||
listener.getLogger().println("Queuing cloud instance: #" + i + " of " + instance.count + ", " + instance.cloudName + " " + instance.templateName); | ||
} | ||
} | ||
|
||
int failedLaunches = 0; | ||
|
||
while (plannedInstances.size() > 0) { | ||
for (Iterator<PlannedInstance> itr = plannedInstances.iterator(); itr.hasNext();) { | ||
PlannedInstance f = itr.next(); | ||
if (f.future.isDone()) { | ||
try { | ||
Map<String,List<NodeMetadata>> cloudMap = spawnedInstances.get(f.cloudName); | ||
if (cloudMap==null) { | ||
spawnedInstances.put(f.cloudName, cloudMap = new HashMap<String,List<NodeMetadata>>()); | ||
} | ||
List<NodeMetadata> templateList = cloudMap.get(f.templateName); | ||
if (templateList==null) { | ||
cloudMap.put(f.templateName, templateList = new ArrayList<NodeMetadata>()); | ||
} | ||
|
||
NodeMetadata n = f.future.get(); | ||
|
||
if (n != null) { | ||
templateList.add(n); | ||
} else { | ||
failedLaunches++; | ||
} | ||
} catch (InterruptedException e) { | ||
failedLaunches++; | ||
listener.error("Interruption while launching instance " + f.index + " of " + f.cloudName + "/" + f.templateName + ": " + e); | ||
} catch (ExecutionException e) { | ||
failedLaunches++; | ||
listener.error("Error while launching instance " + f.index + " of " + f.cloudName + "/" + f.templateName + ": " + e.getCause()); | ||
} | ||
|
||
itr.remove(); | ||
} | ||
} | ||
} | ||
|
||
instances = Collections.unmodifiableMap(spawnedInstances); | ||
|
||
if (failedLaunches > 0) { | ||
terminateNodes(instances, listener.getLogger()); | ||
throw new IOException("One or more instances failed to launch."); | ||
} | ||
|
||
|
||
return new Environment() { | ||
@Override | ||
public void buildEnvVars(Map<String,String> env) { | ||
List<String> ips = getInstanceIPs(instances, listener.getLogger()); | ||
env.put("CLOUD_IPS", Util.join(ips, ",")); | ||
} | ||
|
||
@Override | ||
public boolean tearDown(AbstractBuild build, final BuildListener listener) throws IOException, InterruptedException { | ||
terminateNodes(instances, listener.getLogger()); | ||
|
||
return true; | ||
|
||
} | ||
|
||
}; | ||
|
||
} | ||
|
||
public List<String> getInstanceIPs(Map<String,Map<String,List<NodeMetadata>>> instances, PrintStream logger) { | ||
List<String> ips = new ArrayList<String>(); | ||
|
||
for (String cloudName : instances.keySet()) { | ||
for (String templateName : instances.get(cloudName).keySet()) { | ||
for (NodeMetadata n : instances.get(cloudName).get(templateName)) { | ||
String[] possibleIPs = JCloudsLauncher.getConnectionAddresses(n, logger); | ||
if (possibleIPs[0] != null) { | ||
ips.add(possibleIPs[0]); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return ips; | ||
} | ||
|
||
|
||
|
||
public void terminateNodes(Map<String,Map<String,List<NodeMetadata>>> instances, PrintStream logger) { | ||
for (String cloudName : instances.keySet()) { | ||
for (String templateName : instances.get(cloudName).keySet()) { | ||
InstancesToRun i = getMatchingInstanceToRun(cloudName, templateName); | ||
for (NodeMetadata n : instances.get(cloudName).get(templateName)) { | ||
terminateNode(cloudName, n.getId(), i.suspendOrTerminate, logger); | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
/** | ||
* Destroy the node calls {@link ComputeService#destroyNode} | ||
* | ||
*/ | ||
public void terminateNode(String cloudName, String nodeId, boolean suspendOrTerminate, PrintStream logger) { | ||
final ComputeService compute = JCloudsCloud.getByName(cloudName).getCompute(); | ||
if (compute.getNodeMetadata(nodeId) != null && | ||
compute.getNodeMetadata(nodeId).getState().equals(NodeState.RUNNING)) { | ||
if (suspendOrTerminate) { | ||
logger.println("Suspending the Node : " + nodeId); | ||
compute.suspendNode(nodeId); | ||
} else { | ||
logger.println("Terminating the Node : " + nodeId); | ||
compute.destroyNode(nodeId); | ||
} | ||
} else { | ||
logger.println("Node " + nodeId + " is already not running."); | ||
} | ||
} | ||
|
||
|
||
|
||
@Extension | ||
public static final class DescriptorImpl extends BuildWrapperDescriptor { | ||
@Override | ||
public String getDisplayName() { | ||
return "JClouds Instance Creation"; | ||
} | ||
|
||
@Override | ||
public boolean isApplicable(AbstractProject item) { | ||
return true; | ||
} | ||
|
||
} | ||
|
||
|
||
public static final class PlannedInstance { | ||
public final String cloudName; | ||
public final String templateName; | ||
public final Future<NodeMetadata> future; | ||
public final int index; | ||
|
||
public PlannedInstance(String cloudName, String templateName, int index, Future<NodeMetadata> future) { | ||
this.cloudName = cloudName; | ||
this.templateName = templateName; | ||
this.index = index; | ||
this.future = future; | ||
} | ||
} | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
src/main/resources/jenkins/plugins/jclouds/compute/InstancesToRun/config.jelly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<j:jelly xmlns:j="jelly:core" | ||
xmlns:f="/lib/form"> | ||
<f:entry title="Cloud Name" field="cloudName"> | ||
<f:select /> | ||
</f:entry> | ||
|
||
<f:entry title="Template" field="templateName"> | ||
<f:select /> | ||
</f:entry> | ||
|
||
<f:entry title="Number of Instances" field="count"> | ||
<f:textbox /> | ||
</f:entry> | ||
|
||
<f:entry title="${%Stop on Terminate}" field="suspendOrTerminate"> | ||
<f:checkbox /> | ||
</f:entry> | ||
|
||
<f:entry> | ||
<div align="right"> | ||
<f:repeatableDeleteButton /> | ||
</div> | ||
</f:entry> | ||
|
||
</j:jelly> |
6 changes: 6 additions & 0 deletions
6
src/main/resources/jenkins/plugins/jclouds/compute/JCloudsBuildWrapper/config.jelly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<j:jelly xmlns:j="jelly:core" | ||
xmlns:f="/lib/form"> | ||
<f:entry field="instancesToRun"> | ||
<f:repeatableProperty field="instancesToRun" /> | ||
</f:entry> | ||
</j:jelly> |