Skip to content

Commit

Permalink
Merge pull request #25 from amuniz/JENKINS-30269
Browse files Browse the repository at this point in the history
[JENKINS-30269] Add pipeline support for resource locking
  • Loading branch information
amuniz committed Apr 14, 2016
2 parents 76c93d5 + e3ae1d2 commit 466adef
Show file tree
Hide file tree
Showing 15 changed files with 807 additions and 53 deletions.
66 changes: 62 additions & 4 deletions pom.xml
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>1.509.4</version>
<version>1.609.1</version>
</parent>

<groupId>org.6wind.jenkins</groupId>
Expand All @@ -28,6 +28,12 @@
</description>
<url>https://wiki.jenkins-ci.org/display/JENKINS/Lockable+Resources+Plugin</url>

<properties>
<!-- First version including TailCall is 1.9, and the same baseline is maintained
until 1.14, so let's pick up the greatest -->
<workflow.version>1.14</workflow.version>
</properties>

<licenses>
<license>
<name>MIT</name>
Expand All @@ -46,6 +52,16 @@
</roles>
<timezone>CET</timezone>
</developer>
<developer>
<id>amuniz</id>
<name>Antonio Muñiz</name>
<email>amuniz@cloudbees.com</email>
<roles>
<role>developer</role>
<role>maintainer</role>
</roles>
<timezone>CET</timezone>
</developer>
</developers>

<dependencies>
Expand All @@ -54,17 +70,59 @@
<artifactId>mailer</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
<version>${workflow.version}</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>matrix-project</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>annotations</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-annotation</artifactId>
<version>1.14</version>
<optional>true</optional>
</dependency>

<!-- Testing scope -->
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-aggregator</artifactId>
<version>${workflow.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
<version>${workflow.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency> <!-- Required by workflow-cps-global-lib (transitive of git-server) -->
<groupId>org.jenkins-ci.modules</groupId>
<artifactId>sshd</artifactId>
<version>1.6</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.jenkins-ci.tools</groupId>
<artifactId>maven-hpi-plugin</artifactId>
<version>1.96</version>
<extensions>true</extensions>
<configuration>
<contextPath>/</contextPath>
<loggers>
<org.jenkins.plugins.lockableresources>FINE</org.jenkins.plugins.lockableresources>
</loggers>
</configuration>
</plugin>
<plugin>
Expand Down
62 changes: 62 additions & 0 deletions src/main/java/org/jenkins/plugins/lockableresources/LockStep.java
@@ -0,0 +1,62 @@
package org.jenkins.plugins.lockableresources;

import java.io.Serializable;

import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;

import hudson.Extension;
import hudson.model.AutoCompletionCandidates;

public class LockStep extends AbstractStepImpl implements Serializable {

public final String resource;

public boolean inversePrecedence = false;

@DataBoundConstructor
public LockStep(String resource) {
if (resource == null || resource.isEmpty()) {
throw new IllegalArgumentException("must specify resource");
}
this.resource = resource;
}

@DataBoundSetter
public void setInversePrecedence(boolean inversePrecedence) {
this.inversePrecedence = inversePrecedence;
}

@Extension
public static final class DescriptorImpl extends AbstractStepDescriptorImpl {

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

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

@Override
public String getDisplayName() {
return "Lock shared resource";
}

@Override
public boolean takesImplicitBlockArgument() {
return true;
}

public AutoCompletionCandidates doAutoCompleteResource(@QueryParameter String value) {
return RequiredResourcesProperty.DescriptorImpl.doAutoCompleteResourceNames(value);
}
}

private static final long serialVersionUID = 1L;

}
@@ -0,0 +1,95 @@
package org.jenkins.plugins.lockableresources;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jenkins.plugins.lockableresources.queue.LockableResourcesStruct;
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;

import com.google.inject.Inject;

import hudson.model.Run;
import hudson.model.TaskListener;

public class LockStepExecution extends AbstractStepExecutionImpl {

@Inject(optional = true)
private LockStep step;

@StepContextParameter
private transient Run<?, ?> run;

@StepContextParameter
private transient TaskListener listener;

private static final Logger LOGGER = Logger.getLogger(LockStepExecution.class.getName());

@Override
public boolean start() throws Exception {
if (LockableResourcesManager.get().createResource(step.resource)) {
listener.getLogger().println("Resource [" + step.resource + "] did not exist. Created.");
}
listener.getLogger().println("Trying to acquire lock on [" + step.resource + "]");
LockableResourcesStruct resourceHolder = new LockableResourcesStruct(step.resource);
if(!LockableResourcesManager.get().lock(resourceHolder.required, run, getContext(), step.inversePrecedence)) {
// we have to wait
listener.getLogger().println("[" + step.resource + "] is locked, waiting...");
} // proceed is called inside lock otherwise
return false;
}

public static void proceed(StepContext context, String resource, boolean inversePrecedence) {
LockableResourcesStruct resourceHolder = new LockableResourcesStruct(resource);
Run<?, ?> r = null;
try {
r = context.get(Run.class);
context.get(TaskListener.class).getLogger().println("Lock acquired on [" + resource + "]");
} catch (Exception e) {
context.onFailure(e);
return;
}

LOGGER.finest("Lock acquired on [" + resource + "] by " + r.getExternalizableId());
context.newBodyInvoker().
withCallback(new Callback(resourceHolder, inversePrecedence)).
withDisplayName(null).
start();
}

private static final class Callback extends BodyExecutionCallback.TailCall {

private final LockableResourcesStruct resourceHolder;
private final boolean inversePrecedence;

Callback(LockableResourcesStruct resourceHolder, boolean inversePrecedence) {
// It's granted to contain one item (and only one for now)
this.resourceHolder = resourceHolder;
this.inversePrecedence = inversePrecedence;
}

protected void finished(StepContext context) throws Exception {
LockableResourcesManager.get().unlock(resourceHolder.required, context.get(Run.class), context, inversePrecedence);
context.get(TaskListener.class).getLogger().println("Lock released on resouce [" + resourceHolder.required.get(0) + "]");
LOGGER.finest("Lock released on [" + resourceHolder.required.get(0) + "]");
}

private static final long serialVersionUID = 1L;

}

@Override
public void stop(Throwable cause) throws Exception {
boolean cleaned = LockableResourcesManager.get().cleanWaitingContext(LockableResourcesManager.get().fromName(step.resource), getContext());
if (!cleaned) {
LOGGER.log(Level.WARNING, "Cannot remove context from lockable resource witing list. The context is not in the waiting list.");
}
getContext().onFailure(cause);
}

private static final long serialVersionUID = 1L;

}

0 comments on commit 466adef

Please sign in to comment.