Skip to content

Commit

Permalink
[FIXED JENKINS-28632] Workflow step for awaiting for a deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
fbelzunc committed Nov 29, 2015
1 parent 98ece8b commit c572815
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 0 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -36,6 +36,11 @@
<version>2.15</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-api</artifactId>
<version>1.2</version>
</dependency>
</dependencies>

<scm>
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/org/jenkinsci/plugins/deployment/Condition.java
Expand Up @@ -16,8 +16,12 @@ public abstract class Condition extends AbstractDescribableImpl<Condition> imple
* of the given {@link Job}. If so, return the build number of the job that matches,
* otherwise return negative number to indicate no match.
*/
@Deprecated
public abstract RangeSet calcMatchingBuildNumberOf(Job upstream, DeploymentFacet<?> facet);

public abstract RangeSet calcMatchingBuildNumberOf(Job upstream, DeploymentFacet<?> facet, Job workflowJob);

@Deprecated
public RangeSet calcMatchingBuildNumberOf(Job upstream, Fingerprint f) {
for (FingerprintFacet ff : f.getFacets()) {
if (ff instanceof DeploymentFacet) {
Expand All @@ -27,6 +31,15 @@ public RangeSet calcMatchingBuildNumberOf(Job upstream, Fingerprint f) {
return new RangeSet(); // no match
}

public RangeSet calcMatchingBuildNumberOf(Job upstream, Fingerprint f, Job workflowJob) {
for (FingerprintFacet ff : f.getFacets()) {
if (ff instanceof DeploymentFacet) {
return calcMatchingBuildNumberOf(upstream,(DeploymentFacet)ff, workflowJob);
}
}
return new RangeSet(); // no match
}

@Override
public ConditionDescriptor getDescriptor() {
return (ConditionDescriptor)super.getDescriptor();
Expand Down
Expand Up @@ -36,6 +36,38 @@ public int getThreshold() {
return threshold;
}


@Override
public RangeSet calcMatchingBuildNumberOf(Job upstream, DeploymentFacet<?> facet, Job workflowJob) {
Fingerprint f = facet.getFingerprint();
RangeSet r;

if (upstream==null && workflowJob==null) return new RangeSet();

if (workflowJob==null) {
r = f.getRangeSet(upstream);
if (r.isEmpty()) return new RangeSet();
} else {
r = f.getRangeSet(workflowJob);
if (r.isEmpty()) return new RangeSet();
}

// at this point, we verified that the fingerprint touches the project we care about

// count the deployment
Set<String> hosts = new HashSet<String>();
for (HostRecord hr : facet.records) {
if (env==null || env.equals(hr.getEnv()))
hosts.add(hr.getHost());
if (hosts.size()>=threshold)
return r;
}

// not enough deployments have happened yet
return new RangeSet();
}

@Deprecated
@Override
public RangeSet calcMatchingBuildNumberOf(Job upstream, DeploymentFacet<?> facet) {
Fingerprint f = facet.getFingerprint();
Expand Down
@@ -0,0 +1,68 @@
package org.jenkinsci.plugins.deployment.workflowsteps;

import hudson.model.Run;
import jenkins.model.RunAction2;
import org.kohsuke.stapler.DataBoundConstructor;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
* AwaitDeployment action
*/
public class AwaitDeploymentAction implements RunAction2 {

private final String message;
private final List<AwaitDeploymentStepExecution> executions = new ArrayList<AwaitDeploymentStepExecution>();

@DataBoundConstructor
public AwaitDeploymentAction(String message) {
this.message = message;
}

private transient Run<?,?> run;
@Override
public void onAttached(Run<?, ?> run) {
this.run = run;
}

@Override
public void onLoad(Run<?, ?> run) {
this.run = run;
assert executions != null && !executions.contains(null) : executions;
for (AwaitDeploymentStepExecution step : executions) {
step.run = run;
}
}

@Override
public String getIconFileName() {
return null;
}

@Override
public String getDisplayName() {
return null;
}

@Override
public String getUrlName() {
return null;
}

/**
* Called when {@link AwaitDeploymentAction} is completed to remove it from the active input list.
*/
public synchronized void remove(AwaitDeploymentStepExecution exec) throws IOException {
executions.remove(exec);
run.save();
}

public synchronized void add(@Nonnull AwaitDeploymentStepExecution step) throws IOException {
this.executions.add(step);
run.save();
}

}
@@ -0,0 +1,55 @@
package org.jenkinsci.plugins.deployment.workflowsteps;

import hudson.Extension;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.Serializable;

/**
* Workflow Step for awaiting for a deployment
*/
public class AwaitDeploymentStep extends AbstractStepImpl implements Serializable {

private final int threshold;
private final String env;

@DataBoundConstructor
public AwaitDeploymentStep(int threshold, String env) {
this.threshold = threshold;
this.env = env;
}

public int getThreshold() {
return threshold;
}

public String getEnv() {
return env;
}

@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl)super.getDescriptor();
}

@Extension
public static class DescriptorImpl extends AbstractStepDescriptorImpl {

public DescriptorImpl() {
super(AwaitDeploymentStepExecution.class);
}

@Override
public String getFunctionName() {
return "awaitDeployment";
}

@Override
public String getDisplayName() {
return "Awaiting for deployment";
}
}

}
@@ -0,0 +1,66 @@
package org.jenkinsci.plugins.deployment.workflowsteps;

import com.google.inject.Inject;
import hudson.model.Fingerprint;
import hudson.model.Job;
import hudson.model.Run;
import org.jenkinsci.plugins.deployment.DeploymentFacet;
import org.jenkinsci.plugins.deployment.HostRecord;
import org.jenkinsci.plugins.deployment.conditions.ThresholdCondition;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;

import java.util.concurrent.ScheduledFuture;

/**
* The Execution for AwaitDeploymentStep
*/
public class AwaitDeploymentStepExecution extends AbstractStepExecutionImpl {

@Inject(optional=true) private transient AwaitDeploymentStep awaitDeploymentStep;
@StepContextParameter
private transient FlowNode node;
@StepContextParameter
transient Run run;
private transient volatile ScheduledFuture<?> task;
@StepContextParameter
private transient Run build;
@StepContextParameter
private transient Job job;

@Override
public boolean start() throws Exception {
node.addAction(new AwaitDeploymentAction("Await for deployment"));

return false;
}

@Override public void stop(Throwable cause) throws Exception {
if (task != null) {
task.cancel(false);
}
getContext().onFailure(cause);
}

public void proceed(DeploymentFacet<?> facet, HostRecord newRecord) {
ThresholdCondition thresholdCondition = new ThresholdCondition(awaitDeploymentStep.getEnv(), awaitDeploymentStep.getThreshold());
Fingerprint.RangeSet r = thresholdCondition.calcMatchingBuildNumberOf(null, facet.getFingerprint(), job);

if (!r.isEmpty()) {
for (Integer n : r.listNumbers()) {
Run b = job.getBuildByNumber(n);
if (b!=null) {
if (b.getId().equals(build.getId())) {
getContext().onSuccess(null);
}
}
}
}
}

@Override public void onResume() {
super.onResume();
}

}
@@ -0,0 +1,58 @@
package org.jenkinsci.plugins.deployment.workflowsteps;

import com.google.common.base.Function;
import hudson.Extension;
import org.jenkinsci.plugins.deployment.DeploymentFacet;
import org.jenkinsci.plugins.deployment.DeploymentFacetListener;
import org.jenkinsci.plugins.deployment.DeploymentTrigger;
import org.jenkinsci.plugins.deployment.HostRecord;

import javax.annotation.Nonnull;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Deployment Listener for Workflow jobs
*/
@Extension
public class WorkflowListenerImpl extends DeploymentFacetListener {

@Override
public void onChange(final DeploymentFacet facet, final HostRecord newRecord) {
LOGGER.log(Level.FINE, "Deployment triggered");
POOL.submit(new Runnable() {
public void run() {
AwaitDeploymentStepExecution.applyAll(AwaitDeploymentStepExecution.class, new Function<AwaitDeploymentStepExecution, Void>() {
@Override public Void apply(@Nonnull AwaitDeploymentStepExecution awaitDeploymentStepExecution) {
awaitDeploymentStepExecution.proceed(facet, newRecord);
return null;
}
});
}
});
}

/**
* Waits until all the pending deployment facets are processed.
*/
public void sync() throws InterruptedException {
try {
POOL.submit(new Runnable() {
public void run() {
// no-op
}
}).get();
} catch (ExecutionException e) {
throw (InterruptedException)new InterruptedException().initCause(e);
}
}

public final ExecutorService POOL = new ThreadPoolExecutor(0, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
private static final Logger LOGGER = Logger.getLogger(DeploymentTrigger.class.getName());

}

0 comments on commit c572815

Please sign in to comment.