From a0934f65d202e2410513f4c13007a65b1c91cd3d Mon Sep 17 00:00:00 2001 From: Pokorny Martin Date: Tue, 2 Jan 2024 11:51:16 +0100 Subject: [PATCH 01/12] NPE during the release of a lock --- .../actions/LockedResourcesBuildAction.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java b/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java index 477b7e8cc..7a5be5d3a 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java @@ -77,7 +77,12 @@ public static void updateAction(Run build, List resourceNames) { for (String name : resourceNames) { LockableResource r = LockableResourcesManager.get().fromName(name); - action.add(new ResourcePOJO(r)); + if (r != null) { + action.add(new ResourcePOJO(r)); + } else { + // probably a ephemeral resource has been deleted + action.add(new ResourcePOJO(name, "")); + } } } @@ -101,7 +106,11 @@ private void add(ResourcePOJO r) { @Restricted(NoExternalUse.class) public static LockedResourcesBuildAction fromResources(Collection resources) { List resPojos = new ArrayList<>(); - for (LockableResource r : resources) resPojos.add(new ResourcePOJO(r)); + for (LockableResource r : resources) { + if (r != null) { + resPojos.add(new ResourcePOJO(r)); + } + } return new LockedResourcesBuildAction(resPojos); } From 15b16e9c8b3ab73d09dea843580dbb61c1dc70a9 Mon Sep 17 00:00:00 2001 From: Pokorny Martin Date: Tue, 2 Jan 2024 14:49:09 +0100 Subject: [PATCH 02/12] API-properties --- .../jenkins/plugins/lockableresources/LockableResource.java | 3 ++- .../plugins/lockableresources/LockableResourceProperty.java | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockableResource.java b/src/main/java/org/jenkins/plugins/lockableresources/LockableResource.java index 30a7e27d6..7aa9f1858 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockableResource.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockableResource.java @@ -259,7 +259,7 @@ public List getLabelsAsList() { * @param labelToFind Label to find. * @return {@code true} if this resource contains the label. */ - @Exported + @Restricted(NoExternalUse.class) public boolean hasLabel(@CheckForNull String labelToFind) { return this.labelsContain(labelToFind); } @@ -446,6 +446,7 @@ public boolean isLocked() { * @return the lock cause or null if not locked */ @CheckForNull + @Exported public String getLockCause() { final DateFormat format = SimpleDateFormat.getDateTimeInstance(MEDIUM, SHORT); final String timestamp = (reservedTimestamp == null ? "" : format.format(reservedTimestamp)); diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourceProperty.java b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourceProperty.java index 97b97791c..2647b623d 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourceProperty.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourceProperty.java @@ -8,7 +8,9 @@ import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.export.Exported; +import org.kohsuke.stapler.export.ExportedBean; +@ExportedBean(defaultVisibility = 999) public class LockableResourceProperty extends AbstractDescribableImpl implements Serializable { From 8c01d45a0b3feec034a5d2a294a223247b593ee3 Mon Sep 17 00:00:00 2001 From: Pokorny Martin Date: Wed, 19 Jun 2024 15:05:45 +0200 Subject: [PATCH 03/12] Show LRM actions on build page --- .../lockableresources/LockStepExecution.java | 29 ++++--- .../LockableResourcesManager.java | 22 +++--- .../actions/LockedResourcesBuildAction.java | 76 ++++++++----------- .../LockedResourcesBuildAction/index.jelly | 32 ++++++-- .../index.properties | 6 +- 5 files changed, 93 insertions(+), 72 deletions(-) diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java b/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java index 776926014..5d34a2c60 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java @@ -58,8 +58,9 @@ public boolean start() throws Exception { LockableResourcesManager lrm = LockableResourcesManager.get(); List available = null; - LinkedHashMap> resourceNames = new LinkedHashMap<>(); + LinkedHashMap> lockedResources = new LinkedHashMap<>(); synchronized (lrm.syncResources) { + List resourceNames = new ArrayList<>(); for (LockStepResource resource : step.getResources()) { List resources = new ArrayList<>(); if (resource.resource != null) { @@ -71,10 +72,16 @@ public boolean start() throws Exception { logger); } resources.add(resource.resource); + resourceNames.addAll(resources); + } else { + resourceNames.add("N/A"); } resourceHolderList.add(new LockableResourcesStruct(resources, resource.label, resource.quantity)); + } + LockedResourcesBuildAction.updateAction(run, resourceNames, "try", step.toString()); + // determine if there are enough resources available to proceed available = lrm.getAvailableResources(resourceHolderList, logger, resourceSelectStrategy); if (available == null || available.isEmpty()) { @@ -95,10 +102,10 @@ public boolean start() throws Exception { // since LockableResource contains transient variables, they cannot be correctly serialized // hence we use their unique resource names and properties for (LockableResource resource : available) { - resourceNames.put(resource.getName(), resource.getProperties()); + lockedResources.put(resource.getName(), resource.getProperties()); } } - LockStepExecution.proceed(resourceNames, getContext(), step.toString(), step.variable); + LockStepExecution.proceed(lockedResources, getContext(), step.toString(), step.variable); return false; } @@ -166,11 +173,12 @@ public static void proceed( } try { - - LockedResourcesBuildAction.updateAction(build, new ArrayList<>(lockedResources.keySet())); + List resourceNames = new ArrayList<>(lockedResources.keySet()); + final String resourceNamesAsString = String.join(",", lockedResources.keySet()); + LockedResourcesBuildAction.updateAction(build, resourceNames, "acquired", resourceDescription); PauseAction.endCurrentPause(node); BodyInvoker bodyInvoker = context.newBodyInvoker() - .withCallback(new Callback(new ArrayList<>(lockedResources.keySet()), resourceDescription)); + .withCallback(new Callback(resourceNames, resourceDescription)); if (variable != null && !variable.isEmpty()) { // set the variable for the duration of the block bodyInvoker.withContext( @@ -180,8 +188,7 @@ public static void proceed( @Override public void expand(@NonNull EnvVars env) { final LinkedHashMap variables = new LinkedHashMap<>(); - final String resourceNames = String.join(",", lockedResources.keySet()); - variables.put(variable, resourceNames); + variables.put(variable, resourceNamesAsString); int index = 0; for (Entry> lockResourceEntry : lockedResources.entrySet()) { @@ -222,9 +229,11 @@ private static final class Callback extends BodyExecutionCallback.TailCall { @Override protected void finished(StepContext context) throws Exception { - LockableResourcesManager.get().unlockNames(this.resourceNames, context.get(Run.class)); + Run build = context.get(Run.class); + LockableResourcesManager.get().unlockNames(this.resourceNames, build); + LockedResourcesBuildAction.updateAction(build, this.resourceNames, "released", this.resourceDescription); LockableResourcesManager.printLogs( - "Lock released on resource [" + resourceDescription + "]", + "Lock released on resource [" + this.resourceDescription + "]", Level.FINE, LOGGER, context.get(TaskListener.class).getLogger()); diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java index 71a8bdb52..87fe236dc 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java @@ -108,7 +108,7 @@ public List getReadOnlyResources() { /** Get declared resources, means only defined in config file (xml or JCaC yaml). */ public List getDeclaredResources() { ArrayList declaredResources = new ArrayList<>(); - for (LockableResource r : this.getReadOnlyResources()) { + for (LockableResource r : this.getResources()) { if (!r.isEphemeral() && !r.isNodeResource()) { declaredResources.add(r); } @@ -165,7 +165,7 @@ public void setDeclaredResources(List declaredResources) { @Restricted(NoExternalUse.class) public List getResourcesFromProject(String fullName) { List matching = new ArrayList<>(); - for (LockableResource r : this.getReadOnlyResources()) { + for (LockableResource r : this.getResources()) { String rName = r.getQueueItemProject(); if (rName != null && rName.equals(fullName)) { matching.add(r); @@ -179,7 +179,7 @@ public List getResourcesFromProject(String fullName) { @Restricted(NoExternalUse.class) public List getResourcesFromBuild(Run build) { List matching = new ArrayList<>(); - for (LockableResource r : this.getReadOnlyResources()) { + for (LockableResource r : this.getResources()) { Run rBuild = r.getBuild(); if (rBuild != null && rBuild == build) { matching.add(r); @@ -198,10 +198,12 @@ public Boolean isValidLabel(@Nullable String label) { return false; } - for (LockableResource r : this.getReadOnlyResources()) { - if (r != null && r.isValidLabel(label)) { - return true; - } + synchronized (this.syncResources) { + for (LockableResource r : this.getResources()) { + if (r != null && r.isValidLabel(label)) { + return true; + } + } } return false; @@ -326,8 +328,10 @@ public LockableResource fromName(@CheckForNull String resourceName) { if (resourceName != null) { - for (LockableResource r : this.getReadOnlyResources()) { - if (resourceName.equals(r.getName())) return r; + synchronized (this.syncResources) { + for (LockableResource r : this.getResources()) { + if (resourceName.equals(r.getName())) return r; + } } } else { LOGGER.warning("Internal failure, fromName is empty or null:" + getStack()); diff --git a/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java b/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java index 470421fc3..3558cb427 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java @@ -12,6 +12,7 @@ import hudson.model.Run; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.List; import org.jenkins.plugins.lockableresources.LockableResource; import org.jenkins.plugins.lockableresources.LockableResourcesManager; @@ -30,14 +31,21 @@ public class LockedResourcesBuildAction implements Action { // ------------------------------------------------------------------------- private final List lockedResources; + /** Object to synchronized operations over LRM */ + private static final transient Object syncResources = new Object(); + // ------------------------------------------------------------------------- public LockedResourcesBuildAction(List lockedResources) { - this.lockedResources = lockedResources; + synchronized (this.syncResources) { + this.lockedResources = lockedResources; + } } // ------------------------------------------------------------------------- public List getLockedResources() { - return lockedResources; + synchronized (this.syncResources) { + return lockedResources; + } } // ------------------------------------------------------------------------- @@ -61,28 +69,20 @@ public String getUrlName() { // ------------------------------------------------------------------------- /** Adds *resourceNames* to *build*. * When the action does not exists, will be created as well. - * When the resource has been used by this build just now, the counter will - * increased to eliminate multiple entries. * Used in pipelines - lock() step */ @Restricted(NoExternalUse.class) - public static void updateAction(Run build, List resourceNames) { - LockedResourcesBuildAction action = build.getAction(LockedResourcesBuildAction.class); + public static void updateAction(Run build, List resourceNames, String action, String step) { + LockedResourcesBuildAction buildAction = build.getAction(LockedResourcesBuildAction.class); - if (action == null) { + if (buildAction == null) { List resPojos = new ArrayList<>(); - action = new LockedResourcesBuildAction(resPojos); - build.addAction(action); + buildAction = new LockedResourcesBuildAction(resPojos); + build.addAction(buildAction); } for (String name : resourceNames) { - LockableResource r = LockableResourcesManager.get().fromName(name); - if (r != null) { - action.add(new ResourcePOJO(r)); - } else { - // probably a ephemeral resource has been deleted - action.add(new ResourcePOJO(name, "")); - } + buildAction.add(new ResourcePOJO(name, step, action)); } } @@ -92,13 +92,9 @@ public static void updateAction(Run build, List resourceNames) { // since the list *this.lockedResources* might be updated from multiple (parallel) // stages, this operation need to be synchronized private synchronized void add(ResourcePOJO r) { - for (ResourcePOJO pojo : this.lockedResources) { - if (pojo.getName().equals(r.getName())) { - pojo.inc(); - return; - } + synchronized (this.syncResources) { + this.lockedResources.add(r); } - this.lockedResources.add(r); } // ------------------------------------------------------------------------- @@ -110,7 +106,7 @@ public static LockedResourcesBuildAction fromResources(Collection resPojos = new ArrayList<>(); for (LockableResource r : resources) { if (r != null) { - resPojos.add(new ResourcePOJO(r)); + resPojos.add(new ResourcePOJO(r.getName(), "", "")); } } return new LockedResourcesBuildAction(resPojos); @@ -121,19 +117,16 @@ public static class ResourcePOJO { // --------------------------------------------------------------------- private String name; - private String description; - private int count = 1; + private String step; + private String action; + private long timeStamp; // --------------------------------------------------------------------- - public ResourcePOJO(String name, String description) { + public ResourcePOJO(String name, String step, String action) { this.name = name; - this.description = description; - } - - // --------------------------------------------------------------------- - public ResourcePOJO(LockableResource r) { - this.name = r.getName(); - this.description = r.getDescription(); + this.step = step; + this.action = action; + this.timeStamp = new Date().getTime(); } // --------------------------------------------------------------------- @@ -142,23 +135,18 @@ public String getName() { } // --------------------------------------------------------------------- - public String getDescription() { - return this.description; + public String getStep() { + return this.step; } // --------------------------------------------------------------------- - /** Return the counter, how many was / is the resource used in the build. - * Example: you can use the lock() function in parallel stages for the - * same resource. - */ - public int getCounter() { - return this.count; + public String getAction() { + return this.action; } // --------------------------------------------------------------------- - /** Increment counter */ - public void inc() { - this.count++; + public Date getTimeStamp() { + return new Date(this.timeStamp); } } } diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.jelly b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.jelly index 0cace6df6..950e73a41 100644 --- a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.jelly +++ b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.jelly @@ -52,26 +52,44 @@ THE SOFTWARE.
+ + + - - + + + - - diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.properties b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.properties index 7ae8bdb15..2f84ecf9e 100644 --- a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.properties +++ b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.properties @@ -25,5 +25,7 @@ app.bar.resources=Lockable resources table.column.index=Index table.column.name=Resource Name -table.column.description=Description -table.column.counter=Counter +table.column.step=Step +table.column.timeStamp=Timestamp +table.column.action=Action +table.settings.page.length.all=ALL From b8c6d81be0bd70834bdbfcfc1358da03de010bac Mon Sep 17 00:00:00 2001 From: Pokorny Martin Date: Wed, 19 Jun 2024 16:13:58 +0200 Subject: [PATCH 04/12] spotles --- .../lockableresources/LockStepExecution.java | 5 ++--- .../LockableResourcesManager.java | 16 ++++++++-------- .../actions/LockedResourcesBuildAction.java | 7 +++---- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java b/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java index 5d34a2c60..21f285e12 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java @@ -77,7 +77,6 @@ public boolean start() throws Exception { resourceNames.add("N/A"); } resourceHolderList.add(new LockableResourcesStruct(resources, resource.label, resource.quantity)); - } LockedResourcesBuildAction.updateAction(run, resourceNames, "try", step.toString()); @@ -177,8 +176,8 @@ public static void proceed( final String resourceNamesAsString = String.join(",", lockedResources.keySet()); LockedResourcesBuildAction.updateAction(build, resourceNames, "acquired", resourceDescription); PauseAction.endCurrentPause(node); - BodyInvoker bodyInvoker = context.newBodyInvoker() - .withCallback(new Callback(resourceNames, resourceDescription)); + BodyInvoker bodyInvoker = + context.newBodyInvoker().withCallback(new Callback(resourceNames, resourceDescription)); if (variable != null && !variable.isEmpty()) { // set the variable for the duration of the block bodyInvoker.withContext( diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java index 87fe236dc..fc831eb62 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java @@ -199,11 +199,11 @@ public Boolean isValidLabel(@Nullable String label) { } synchronized (this.syncResources) { - for (LockableResource r : this.getResources()) { - if (r != null && r.isValidLabel(label)) { - return true; - } - } + for (LockableResource r : this.getResources()) { + if (r != null && r.isValidLabel(label)) { + return true; + } + } } return false; @@ -329,9 +329,9 @@ public LockableResource fromName(@CheckForNull String resourceName) { if (resourceName != null) { synchronized (this.syncResources) { - for (LockableResource r : this.getResources()) { - if (resourceName.equals(r.getName())) return r; - } + for (LockableResource r : this.getResources()) { + if (resourceName.equals(r.getName())) return r; + } } } else { LOGGER.warning("Internal failure, fromName is empty or null:" + getStack()); diff --git a/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java b/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java index 3558cb427..19b707cd2 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java @@ -15,7 +15,6 @@ import java.util.Date; import java.util.List; import org.jenkins.plugins.lockableresources.LockableResource; -import org.jenkins.plugins.lockableresources.LockableResourcesManager; import org.jenkins.plugins.lockableresources.Messages; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -37,14 +36,14 @@ public class LockedResourcesBuildAction implements Action { // ------------------------------------------------------------------------- public LockedResourcesBuildAction(List lockedResources) { synchronized (this.syncResources) { - this.lockedResources = lockedResources; + this.lockedResources = lockedResources; } } // ------------------------------------------------------------------------- public List getLockedResources() { synchronized (this.syncResources) { - return lockedResources; + return lockedResources; } } @@ -93,7 +92,7 @@ public static void updateAction(Run build, List resourceNames, Str // stages, this operation need to be synchronized private synchronized void add(ResourcePOJO r) { synchronized (this.syncResources) { - this.lockedResources.add(r); + this.lockedResources.add(r); } } From e6095ae6fc68407d0ead8cf1dc7fdfcf7a3183d2 Mon Sep 17 00:00:00 2001 From: Pokorny Martin Date: Thu, 20 Jun 2024 14:22:08 +0200 Subject: [PATCH 05/12] eliminate more waste --- .../lockableresources/LockStepExecution.java | 15 +- .../actions/LockedResourcesBuildAction.java | 151 ------------------ .../queue/LockRunListener.java | 18 ++- .../lockableresources/PressureTest.java | 4 +- 4 files changed, 25 insertions(+), 163 deletions(-) delete mode 100644 src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java b/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java index 21f285e12..354353b78 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java @@ -16,7 +16,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; -import org.jenkins.plugins.lockableresources.actions.LockedResourcesBuildAction; import org.jenkins.plugins.lockableresources.queue.LockableResourcesStruct; import org.jenkinsci.plugins.workflow.graph.FlowNode; import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl; @@ -41,7 +40,6 @@ public LockStepExecution(LockStep step, StepContext context) { @Override public boolean start() throws Exception { - step.validate(); // normally it might raise a exception, but we check it in the function .validate() // therefore we can skip the try-catch here. @@ -56,10 +54,11 @@ public boolean start() throws Exception { List resourceHolderList = new ArrayList<>(); - LockableResourcesManager lrm = LockableResourcesManager.get(); List available = null; LinkedHashMap> lockedResources = new LinkedHashMap<>(); + LockableResourcesManager lrm = LockableResourcesManager.get(); synchronized (lrm.syncResources) { + step.validate(); List resourceNames = new ArrayList<>(); for (LockStepResource resource : step.getResources()) { List resources = new ArrayList<>(); @@ -79,7 +78,7 @@ public boolean start() throws Exception { resourceHolderList.add(new LockableResourcesStruct(resources, resource.label, resource.quantity)); } - LockedResourcesBuildAction.updateAction(run, resourceNames, "try", step.toString()); + // LockedResourcesBuildAction.updateAction(run, resourceNames, "try", step.toString()); // determine if there are enough resources available to proceed available = lrm.getAvailableResources(resourceHolderList, logger, resourceSelectStrategy); @@ -157,11 +156,11 @@ public static void proceed( StepContext context, String resourceDescription, final String variable) { - Run build; + // Run build; FlowNode node = null; PrintStream logger = null; try { - build = context.get(Run.class); + // build = context.get(Run.class); node = context.get(FlowNode.class); logger = context.get(TaskListener.class).getLogger(); LockableResourcesManager.printLogs( @@ -174,7 +173,7 @@ public static void proceed( try { List resourceNames = new ArrayList<>(lockedResources.keySet()); final String resourceNamesAsString = String.join(",", lockedResources.keySet()); - LockedResourcesBuildAction.updateAction(build, resourceNames, "acquired", resourceDescription); + // LockedResourcesBuildAction.updateAction(build, resourceNames, "acquired", resourceDescription); PauseAction.endCurrentPause(node); BodyInvoker bodyInvoker = context.newBodyInvoker().withCallback(new Callback(resourceNames, resourceDescription)); @@ -230,7 +229,7 @@ private static final class Callback extends BodyExecutionCallback.TailCall { protected void finished(StepContext context) throws Exception { Run build = context.get(Run.class); LockableResourcesManager.get().unlockNames(this.resourceNames, build); - LockedResourcesBuildAction.updateAction(build, this.resourceNames, "released", this.resourceDescription); + // LockedResourcesBuildAction.updateAction(build, this.resourceNames, "released", this.resourceDescription); LockableResourcesManager.printLogs( "Lock released on resource [" + this.resourceDescription + "]", Level.FINE, diff --git a/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java b/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java deleted file mode 100644 index 19b707cd2..000000000 --- a/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java +++ /dev/null @@ -1,151 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (c) 2013, 6WIND S.A. All rights reserved. * - * * - * This file is part of the Jenkins Lockable Resources Plugin and is * - * published under the MIT license. * - * * - * See the "LICENSE.txt" file for more information. * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -package org.jenkins.plugins.lockableresources.actions; - -import hudson.model.Action; -import hudson.model.Run; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import org.jenkins.plugins.lockableresources.LockableResource; -import org.jenkins.plugins.lockableresources.Messages; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; - -// ----------------------------------------------------------------------------- -/** BuildAction for lockable resources. - * Shows usage of resources in the build page. - * url: jobUrl/buildNr/locked-resources/ - */ -@Restricted(NoExternalUse.class) -public class LockedResourcesBuildAction implements Action { - - // ------------------------------------------------------------------------- - private final List lockedResources; - - /** Object to synchronized operations over LRM */ - private static final transient Object syncResources = new Object(); - - // ------------------------------------------------------------------------- - public LockedResourcesBuildAction(List lockedResources) { - synchronized (this.syncResources) { - this.lockedResources = lockedResources; - } - } - - // ------------------------------------------------------------------------- - public List getLockedResources() { - synchronized (this.syncResources) { - return lockedResources; - } - } - - // ------------------------------------------------------------------------- - @Override - public String getIconFileName() { - return LockableResourcesRootAction.ICON; - } - - // ------------------------------------------------------------------------- - @Override - public String getDisplayName() { - return Messages.LockedResourcesBuildAction_displayName(); - } - - // ------------------------------------------------------------------------- - @Override - public String getUrlName() { - return "locked-resources"; - } - - // ------------------------------------------------------------------------- - /** Adds *resourceNames* to *build*. - * When the action does not exists, will be created as well. - * Used in pipelines - lock() step - */ - @Restricted(NoExternalUse.class) - public static void updateAction(Run build, List resourceNames, String action, String step) { - LockedResourcesBuildAction buildAction = build.getAction(LockedResourcesBuildAction.class); - - if (buildAction == null) { - List resPojos = new ArrayList<>(); - buildAction = new LockedResourcesBuildAction(resPojos); - build.addAction(buildAction); - } - - for (String name : resourceNames) { - buildAction.add(new ResourcePOJO(name, step, action)); - } - } - - // ------------------------------------------------------------------------- - /** Add the resource to build action.*/ - @Restricted(NoExternalUse.class) - // since the list *this.lockedResources* might be updated from multiple (parallel) - // stages, this operation need to be synchronized - private synchronized void add(ResourcePOJO r) { - synchronized (this.syncResources) { - this.lockedResources.add(r); - } - } - - // ------------------------------------------------------------------------- - /** Create action from resources. - * Used in free-style projects. - */ - @Restricted(NoExternalUse.class) - public static LockedResourcesBuildAction fromResources(Collection resources) { - List resPojos = new ArrayList<>(); - for (LockableResource r : resources) { - if (r != null) { - resPojos.add(new ResourcePOJO(r.getName(), "", "")); - } - } - return new LockedResourcesBuildAction(resPojos); - } - - // ------------------------------------------------------------------------- - public static class ResourcePOJO { - - // --------------------------------------------------------------------- - private String name; - private String step; - private String action; - private long timeStamp; - - // --------------------------------------------------------------------- - public ResourcePOJO(String name, String step, String action) { - this.name = name; - this.step = step; - this.action = action; - this.timeStamp = new Date().getTime(); - } - - // --------------------------------------------------------------------- - public String getName() { - return this.name; - } - - // --------------------------------------------------------------------- - public String getStep() { - return this.step; - } - - // --------------------------------------------------------------------- - public String getAction() { - return this.action; - } - - // --------------------------------------------------------------------- - public Date getTimeStamp() { - return new Date(this.timeStamp); - } - } -} diff --git a/src/main/java/org/jenkins/plugins/lockableresources/queue/LockRunListener.java b/src/main/java/org/jenkins/plugins/lockableresources/queue/LockRunListener.java index 8d0063625..59db0cb7f 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/queue/LockRunListener.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/queue/LockRunListener.java @@ -24,7 +24,6 @@ import org.jenkins.plugins.lockableresources.LockableResource; import org.jenkins.plugins.lockableresources.LockableResourceProperty; import org.jenkins.plugins.lockableresources.LockableResourcesManager; -import org.jenkins.plugins.lockableresources.actions.LockedResourcesBuildAction; import org.jenkins.plugins.lockableresources.actions.ResourceVariableNameAction; @Extension @@ -39,13 +38,22 @@ public void onStarted(Run build, TaskListener listener) { // only the child jobs will actually lock resources. if (build instanceof MatrixBuild) return; + LOGGER.info(build.getFullDisplayName() + " AbstractBuild " + + build.getClass().getName()); if (build instanceof AbstractBuild) { + LOGGER.info(build.getFullDisplayName() + " get LRM "); LockableResourcesManager lrm = LockableResourcesManager.get(); + LOGGER.info(build.getFullDisplayName() + " sync LRM "); synchronized (lrm.syncResources) { + LOGGER.info(build.getFullDisplayName() + " synced LRM "); Job proj = Utils.getProject(build); List required = new ArrayList<>(); + + LOGGER.info(build.getFullDisplayName() + " requiredResources "); LockableResourcesStruct resources = Utils.requiredResources(proj); + LOGGER.info(build.getFullDisplayName() + " " + ((resources == null) ? "nothing" : "something")); + if (resources != null) { if (resources.requiredNumber != null || !resources.label.isEmpty() @@ -56,7 +64,7 @@ public void onStarted(Run build, TaskListener listener) { } if (lrm.lock(required, build)) { - build.addAction(LockedResourcesBuildAction.fromResources(required)); + // build.addAction(LockedResourcesBuildAction.fromResources(required)); listener.getLogger().printf("%s acquired lock on %s%n", LOG_PREFIX, required); LOGGER.info(build.getFullDisplayName() + " acquired lock on " + required); if (resources.requiredVar != null) { @@ -99,9 +107,12 @@ public void onCompleted(Run build, @NonNull TaskListener listener) { if (build instanceof MatrixBuild) return; LockableResourcesManager lrm = LockableResourcesManager.get(); + LOGGER.info(build.getFullDisplayName() + " sync LRM "); synchronized (lrm.syncResources) { + LOGGER.info(build.getFullDisplayName() + " synced LRM "); // obviously project name cannot be obtained here List required = lrm.getResourcesFromBuild(build); + if (!required.isEmpty()) { lrm.unlock(required, build); listener.getLogger().printf("%s released lock on %s%n", LOG_PREFIX, required); @@ -119,8 +130,11 @@ public void onDeleted(Run build) { // only the child jobs will actually unlock resources. if (build instanceof MatrixBuild) return; LockableResourcesManager lrm = LockableResourcesManager.get(); + LOGGER.info(build.getFullDisplayName() + " sync LRM "); synchronized (lrm.syncResources) { + LOGGER.info(build.getFullDisplayName() + " synced LRM "); List required = lrm.getResourcesFromBuild(build); + if (!required.isEmpty()) { lrm.unlock(required, build); LOGGER.warning(build.getFullDisplayName() diff --git a/src/test/java/org/jenkins/plugins/lockableresources/PressureTest.java b/src/test/java/org/jenkins/plugins/lockableresources/PressureTest.java index a053655d6..f9ea3a1e6 100644 --- a/src/test/java/org/jenkins/plugins/lockableresources/PressureTest.java +++ b/src/test/java/org/jenkins/plugins/lockableresources/PressureTest.java @@ -31,7 +31,7 @@ public class PressureTest extends LockStepTestBase { @WithTimeout(900) public void pressureEnableSave() throws Exception { // keep in mind, that the windows nodes at jenkins-infra are not very fast - pressure(Functions.isWindows() ? 5 : 10); + pressure(Functions.isWindows() ? 20 : 10); } @Test @@ -39,7 +39,7 @@ public void pressureEnableSave() throws Exception { public void pressureDisableSave() throws Exception { System.setProperty(Constants.SYSTEM_PROPERTY_DISABLE_SAVE, "true"); // keep in mind, that the windows nodes at jenkins-infra are not very fast - pressure(Functions.isWindows() ? 5 : 10); + pressure(Functions.isWindows() ? 10 : 10); } public void pressure(final int resourcesCount) throws Exception { From d488d9975e18aa7c3ced36977f3e32df2661552d Mon Sep 17 00:00:00 2001 From: Pokorny Martin Date: Fri, 5 Jul 2024 13:14:21 +0200 Subject: [PATCH 06/12] back up me --- .../lockableresources/LockStepExecution.java | 13 +- .../lockableresources/LockableResource.java | 3 + .../LockableResourcesManager.java | 91 ++++++----- .../actions/LockableResourcesRootAction.java | 2 +- .../actions/LockedResourcesBuildAction.java | 148 ++++++++++++++++++ .../queue/LockRunListener.java | 35 +---- .../lockableresources/Messages.properties | 2 +- .../LockedResourcesBuildAction/index.jelly | 13 +- .../index.properties | 2 +- .../lockableresources/LockStepTest.java | 8 +- .../lockableresources/PressureTest.java | 4 +- 11 files changed, 225 insertions(+), 96 deletions(-) create mode 100644 src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java b/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java index 354353b78..5c915559c 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java @@ -16,6 +16,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import org.jenkins.plugins.lockableresources.actions.LockedResourcesBuildAction; import org.jenkins.plugins.lockableresources.queue.LockableResourcesStruct; import org.jenkinsci.plugins.workflow.graph.FlowNode; import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl; @@ -78,7 +79,7 @@ public boolean start() throws Exception { resourceHolderList.add(new LockableResourcesStruct(resources, resource.label, resource.quantity)); } - // LockedResourcesBuildAction.updateAction(run, resourceNames, "try", step.toString()); + LockedResourcesBuildAction.addLog(run, resourceNames, "try", step.toString()); // determine if there are enough resources available to proceed available = lrm.getAvailableResources(resourceHolderList, logger, resourceSelectStrategy); @@ -156,11 +157,11 @@ public static void proceed( StepContext context, String resourceDescription, final String variable) { - // Run build; + Run build; FlowNode node = null; PrintStream logger = null; try { - // build = context.get(Run.class); + build = context.get(Run.class); node = context.get(FlowNode.class); logger = context.get(TaskListener.class).getLogger(); LockableResourcesManager.printLogs( @@ -173,7 +174,7 @@ public static void proceed( try { List resourceNames = new ArrayList<>(lockedResources.keySet()); final String resourceNamesAsString = String.join(",", lockedResources.keySet()); - // LockedResourcesBuildAction.updateAction(build, resourceNames, "acquired", resourceDescription); + LockedResourcesBuildAction.addLog(build, resourceNames, "acquired", resourceDescription); PauseAction.endCurrentPause(node); BodyInvoker bodyInvoker = context.newBodyInvoker().withCallback(new Callback(resourceNames, resourceDescription)); @@ -228,8 +229,8 @@ private static final class Callback extends BodyExecutionCallback.TailCall { @Override protected void finished(StepContext context) throws Exception { Run build = context.get(Run.class); - LockableResourcesManager.get().unlockNames(this.resourceNames, build); - // LockedResourcesBuildAction.updateAction(build, this.resourceNames, "released", this.resourceDescription); + LockedResourcesBuildAction.addLog(build, this.resourceNames, "released", this.resourceDescription); + LockableResourcesManager.get().unlockNames(this.resourceNames); LockableResourcesManager.printLogs( "Lock released on resource [" + this.resourceDescription + "]", Level.FINE, diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockableResource.java b/src/main/java/org/jenkins/plugins/lockableresources/LockableResource.java index cbe20dab4..1824a059f 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockableResource.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockableResource.java @@ -501,6 +501,9 @@ public String getBuildName() { } public void setBuild(@Nullable Run lockedBy) { + LOGGER.info(getName() + " cause " + this.getLockCauseDetail()); + LOGGER.info(getName() + " current " + this.build); + LOGGER.info(getName() + " next " + lockedBy); this.build = lockedBy; if (lockedBy != null) { this.buildExternalizableId = lockedBy.getExternalizableId(); diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java index fc831eb62..0c6e1a6d2 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java @@ -41,6 +41,7 @@ import jenkins.util.SystemProperties; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; +import org.jenkins.plugins.lockableresources.actions.LockedResourcesBuildAction; import org.jenkins.plugins.lockableresources.queue.LockableResourcesStruct; import org.jenkins.plugins.lockableresources.queue.QueuedContextStruct; import org.jenkins.plugins.lockableresources.util.Constants; @@ -106,6 +107,7 @@ public List getReadOnlyResources() { // --------------------------------------------------------------------------- /** Get declared resources, means only defined in config file (xml or JCaC yaml). */ + @Restricted(NoExternalUse.class) public List getDeclaredResources() { ArrayList declaredResources = new ArrayList<>(); for (LockableResource r : this.getResources()) { @@ -174,20 +176,6 @@ public List getResourcesFromProject(String fullName) { return matching; } - // --------------------------------------------------------------------------- - /** Get all resources used by build. */ - @Restricted(NoExternalUse.class) - public List getResourcesFromBuild(Run build) { - List matching = new ArrayList<>(); - for (LockableResource r : this.getResources()) { - Run rBuild = r.getBuild(); - if (rBuild != null && rBuild == build) { - matching.add(r); - } - } - return matching; - } - // --------------------------------------------------------------------------- /** * Check if the label is valid. Valid in this context means, if is configured on someone resource. @@ -594,33 +582,35 @@ public boolean lock( // --------------------------------------------------------------------------- /** Try to lock the resource and return true if locked. */ - public boolean lock(List resources, Run build) { + public boolean lock(List resourcesToLock, Run build) { - LOGGER.fine("lock it: " + resources + " for build " + build); + LOGGER.fine("lock it: " + resourcesToLock + " for build " + build); if (build == null) { - LOGGER.warning("lock() will fails, because the build does not exits. " + resources); + LOGGER.warning("lock() will fails, because the build does not exits. " + resourcesToLock); return false; // not locked } - String cause = getCauses(resources); + String cause = getCauses(resourcesToLock); if (!cause.isEmpty()) { LOGGER.warning("lock() for build " + build + " will fails, because " + cause); return false; // not locked } - for (LockableResource r : resources) { + for (LockableResource r : resourcesToLock) { r.unqueue(); r.setBuild(build); } + LockedResourcesBuildAction.findAndInitAction(build).addUsedResources(getResourcesNames(resourcesToLock)); + save(); return true; } // --------------------------------------------------------------------------- - private void freeResources(List unlockResources, @Nullable Run build) { + private void freeResources(List unlockResources) { LOGGER.fine("free it: " + unlockResources); @@ -630,11 +620,16 @@ private void freeResources(List unlockResources, @Nullable Run } List toBeRemoved = new ArrayList<>(); + + Run build = null; + for (LockableResource resource : unlockResources) { // No more contexts, unlock resource - if (build != null && build != resource.getBuild()) { - continue; // this happens, when you push the unlock button in LRM page + + if (build == null) { + build = resource.getBuild(); } + resource.unqueue(); resource.setBuild(null); uncacheIfFreeing(resource, true, false); @@ -644,43 +639,48 @@ private void freeResources(List unlockResources, @Nullable Run toBeRemoved.add(resource); } } + + if (build != null) + LockedResourcesBuildAction.findAndInitAction(build).removeUsedResources(getResourcesNames(unlockResources)); + // remove all ephemeral resources removeResources(toBeRemoved); } - // --------------------------------------------------------------------------- - @Deprecated - @ExcludeFromJacocoGeneratedReport - public void unlock(List resourcesToUnLock, @Nullable Run build) { - List resourceNamesToUnLock = LockableResourcesManager.getResourcesNames(resourcesToUnLock); - this.unlockNames(resourceNamesToUnLock, build); - } + public void unlockBuild(@Nullable Run build) { - // --------------------------------------------------------------------------- - @Deprecated - @ExcludeFromJacocoGeneratedReport - public void unlock( - @Nullable List resourcesToUnLock, @Nullable Run build, boolean inversePrecedence) { - unlock(resourcesToUnLock, build); - } + if (build == null) { + return; + } - @Deprecated - @ExcludeFromJacocoGeneratedReport - public void unlockNames( - @Nullable List resourceNamesToUnLock, @Nullable Run build, boolean inversePrecedence) { - this.unlockNames(resourceNamesToUnLock, build); + List resourcesInUse = + LockedResourcesBuildAction.findAndInitAction(build).getCurrentUsedResourceNames(); + + if (resourcesInUse.size() == 0) { + return; + } + unlockNames(resourcesInUse); } + // --------------------------------------------------------------------------- @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "not sure which exceptions might be catch.") - public void unlockNames(@Nullable List resourceNamesToUnLock, @Nullable Run build) { + public void unlockNames(@Nullable List resourceNamesToUnLock) { // make sure there is a list of resource names to unlock if (resourceNamesToUnLock == null || resourceNamesToUnLock.isEmpty()) { return; } + synchronized (this.syncResources) { + unlockResources(this.fromNames(resourceNamesToUnLock)); + } + } + public void unlockResources(List resourcesToUnLock) { + if (resourcesToUnLock == null || resourcesToUnLock.isEmpty()) { + return; + } synchronized (this.syncResources) { - this.freeResources(this.fromNames(resourceNamesToUnLock), build); + this.freeResources(resourcesToUnLock); while (proceedNextContext()) { // process as many contexts as possible @@ -917,8 +917,7 @@ public boolean steal(List resources, String userName) { r.setReservedBy(userName); r.setStolen(); } - unlock(resources, null, false); - // unlock() nulls resource.reservedTimestamp via resource.setBuild(null), so set it afterwards + unlockResources(resources); Date date = new Date(); for (LockableResource r : resources) { r.setReservedTimestamp(date); @@ -1003,7 +1002,7 @@ public void recycle(List resources) { synchronized (this.syncResources) { // Not calling reset() because that also un-queues the resource // and we want to proclaim it is usable (if anyone is waiting) - this.unlock(resources, null); + this.unlockResources(resources); this.unreserve(resources); } } diff --git a/src/main/java/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction.java b/src/main/java/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction.java index 45ffd3137..269928c94 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction.java @@ -521,7 +521,7 @@ public void doUnlock(StaplerRequest req, StaplerResponse rsp) throws IOException return; } - LockableResourcesManager.get().unlock(resources, null); + LockableResourcesManager.get().unlockResources(resources); rsp.forwardToPreviousPage(req); } diff --git a/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java b/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java new file mode 100644 index 000000000..dd3f9054d --- /dev/null +++ b/src/main/java/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction.java @@ -0,0 +1,148 @@ +package org.jenkins.plugins.lockableresources.actions; + +import hudson.model.Action; +import hudson.model.Run; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import org.jenkins.plugins.lockableresources.Messages; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +// ----------------------------------------------------------------------------- +/** BuildAction for lockable resources. + * Shows usage of resources in the build page. + * url: jobUrl/buildNr/locked-resources/ + */ +@Restricted(NoExternalUse.class) +public class LockedResourcesBuildAction implements Action { + + private static final long serialVersionUID = 1L; + + private List logs = new ArrayList<>(); + private final transient Object syncLogs = new Object(); + private List resourcesInUse = new ArrayList<>(); + + public LockedResourcesBuildAction() {} + + // ------------------------------------------------------------------------- + @Override + public String getIconFileName() { + return LockableResourcesRootAction.ICON; + } + + // ------------------------------------------------------------------------- + @Override + public String getDisplayName() { + return Messages.LockedResourcesBuildAction_displayName(); + } + + // ------------------------------------------------------------------------- + @Override + public String getUrlName() { + return "locked-resources"; + } + + public List getCurrentUsedResourceNames() { + return resourcesInUse; + } + + public void addUsedResources(List resourceNames) { + synchronized (resourcesInUse) { + resourcesInUse.addAll(resourceNames); + } + } + + public void removeUsedResources(List resourceNames) { + synchronized (resourcesInUse) { + resourcesInUse.removeAll(resourceNames); + } + } + + public static LockedResourcesBuildAction findAndInitAction(final Run build) { + if (build == null) { + return null; + } + LockedResourcesBuildAction action; + synchronized (build) { + List actions = build.getActions(LockedResourcesBuildAction.class); + + if (actions.size() <= 0) { + action = new LockedResourcesBuildAction(); + build.addAction(action); + } else { + action = actions.get(0); + } + } + return action; + } + + public static void addLog( + final Run build, final List resourceNames, final String step, final String action) { + + for (String resourceName : resourceNames) addLog(build, resourceName, step, action); + } + + public static void addLog( + final Run build, final String resourceName, final String step, final String action) { + + LockedResourcesBuildAction buildAction = findAndInitAction(build); + + buildAction.addLog(resourceName, step, action); + } + + public void addLog(final String resourceName, final String step, final String action) { + + synchronized (this.logs) { + this.logs.add(new LogEntry(step, action, resourceName)); + } + } + + @Restricted(NoExternalUse.class) + public List getReadOnlyLogs() { + synchronized (this.logs) { + return new ArrayList<>(Collections.unmodifiableCollection(this.logs)); + } + } + + public static class LogEntry { + + private String step; + private String action; + private String resourceName; + private long timeStamp; + + @Restricted(NoExternalUse.class) + public LogEntry(final String step, final String action, final String resourceName) { + this.step = step; + this.action = action; + this.resourceName = resourceName; + this.timeStamp = new Date().getTime(); + } + + // --------------------------------------------------------------------- + @Restricted(NoExternalUse.class) + public String getName() { + return this.resourceName; + } + + // --------------------------------------------------------------------- + @Restricted(NoExternalUse.class) + public String getStep() { + return this.step; + } + + // --------------------------------------------------------------------- + @Restricted(NoExternalUse.class) + public String getAction() { + return this.action; + } + + // --------------------------------------------------------------------- + @Restricted(NoExternalUse.class) + public Date getTimeStamp() { + return new Date(this.timeStamp); + } + } +} diff --git a/src/main/java/org/jenkins/plugins/lockableresources/queue/LockRunListener.java b/src/main/java/org/jenkins/plugins/lockableresources/queue/LockRunListener.java index 59db0cb7f..01146b171 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/queue/LockRunListener.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/queue/LockRunListener.java @@ -105,23 +105,8 @@ public void onCompleted(Run build, @NonNull TaskListener listener) { // Skip unlocking for multiple configuration projects, // only the child jobs will actually unlock resources. if (build instanceof MatrixBuild) return; - - LockableResourcesManager lrm = LockableResourcesManager.get(); - LOGGER.info(build.getFullDisplayName() + " sync LRM "); - synchronized (lrm.syncResources) { - LOGGER.info(build.getFullDisplayName() + " synced LRM "); - // obviously project name cannot be obtained here - List required = lrm.getResourcesFromBuild(build); - - if (!required.isEmpty()) { - lrm.unlock(required, build); - listener.getLogger().printf("%s released lock on %s%n", LOG_PREFIX, required); - LOGGER.info(build.getFullDisplayName() - + " released lock on " - + required - + ", because the build has been finished."); - } - } + LOGGER.info(build.getFullDisplayName()); + LockableResourcesManager.get().unlockBuild(build); } @Override @@ -129,19 +114,7 @@ public void onDeleted(Run build) { // Skip unlocking for multiple configuration projects, // only the child jobs will actually unlock resources. if (build instanceof MatrixBuild) return; - LockableResourcesManager lrm = LockableResourcesManager.get(); - LOGGER.info(build.getFullDisplayName() + " sync LRM "); - synchronized (lrm.syncResources) { - LOGGER.info(build.getFullDisplayName() + " synced LRM "); - List required = lrm.getResourcesFromBuild(build); - - if (!required.isEmpty()) { - lrm.unlock(required, build); - LOGGER.warning(build.getFullDisplayName() - + " released lock on " - + required - + ", because the build has been deleted."); - } - } + LOGGER.info(build.getFullDisplayName()); + LockableResourcesManager.get().unlockBuild(build); } } diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/Messages.properties b/src/main/resources/org/jenkins/plugins/lockableresources/Messages.properties index 195b34781..4ea8e98b0 100644 --- a/src/main/resources/org/jenkins/plugins/lockableresources/Messages.properties +++ b/src/main/resources/org/jenkins/plugins/lockableresources/Messages.properties @@ -23,7 +23,7 @@ LockableResourcesRootAction.ViewPermission.Description=This permission grants th LockableResourcesRootAction.QueueChangeOrderPermission=Queue LockableResourcesRootAction.QueueChangeOrderPermission.Description=This permission grants the ability to \ manually manipulate the lockable resources queue.. -LockedResourcesBuildAction.displayName=Used lockable resources +LockedResourcesBuildAction.displayName=Lockable resources # Java errors error.labelDoesNotExist=The resource label does not exist: {0}. error.resourceDoesNotExist=The resource does not exist: {0}. diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.jelly b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.jelly index 950e73a41..b570e6c7c 100644 --- a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.jelly +++ b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.jelly @@ -74,22 +74,21 @@ THE SOFTWARE. - + + - - - + + + diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.properties b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.properties index 2f84ecf9e..3574b830c 100644 --- a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.properties +++ b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockedResourcesBuildAction/index.properties @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -app.bar.used.resources=Used lockable resources +app.bar.used.resources=Lockable resources app.bar.resources=Lockable resources table.column.index=Index diff --git a/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java b/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java index 0bd111b95..1e6e642ec 100644 --- a/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java +++ b/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java @@ -743,7 +743,7 @@ public void deleteRunningBuildNewBuildClearsLock() throws Exception { public void unlockButtonWithWaitingRuns() throws Exception { LockableResourcesManager.get().createResource("resource1"); WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); - p.setDefinition(new CpsFlowDefinition("lock('resource1') { semaphore('wait-inside') }", true)); + p.setDefinition(new CpsFlowDefinition("lock('resource1') { semaphore('wait-inside') ; sleep(1)}", true)); WorkflowRun prevBuild = null; TestHelpers testHelpers = new TestHelpers(); @@ -759,16 +759,22 @@ public void unlockButtonWithWaitingRuns() throws Exception { testHelpers.clickButton("unlock", "resource1"); } + LOGGER.info("wait for 1 " + rNext); j.waitForMessage("Trying to acquire lock on [Resource: resource1]", rNext); + + LOGGER.info("wait sem 1 " + rNext); SemaphoreStep.waitForStart("wait-inside/" + (i + 1), rNext); + LOGGER.info("is paused 1 " + rNext); isPaused(rNext, 1, 0); if (prevBuild != null) { + LOGGER.info("wait sem 2" + rNext); SemaphoreStep.success("wait-inside/" + i, null); j.assertBuildStatusSuccess(j.waitForCompletion(prevBuild)); } prevBuild = rNext; } + LOGGER.info("wait sem 3"); SemaphoreStep.success("wait-inside/3", null); j.assertBuildStatusSuccess(j.waitForCompletion(prevBuild)); } diff --git a/src/test/java/org/jenkins/plugins/lockableresources/PressureTest.java b/src/test/java/org/jenkins/plugins/lockableresources/PressureTest.java index f9ea3a1e6..c1b1c679d 100644 --- a/src/test/java/org/jenkins/plugins/lockableresources/PressureTest.java +++ b/src/test/java/org/jenkins/plugins/lockableresources/PressureTest.java @@ -31,7 +31,7 @@ public class PressureTest extends LockStepTestBase { @WithTimeout(900) public void pressureEnableSave() throws Exception { // keep in mind, that the windows nodes at jenkins-infra are not very fast - pressure(Functions.isWindows() ? 20 : 10); + // pressure(Functions.isWindows() ? 10 : 10); } @Test @@ -44,7 +44,7 @@ public void pressureDisableSave() throws Exception { public void pressure(final int resourcesCount) throws Exception { // count of parallel jobs - final int jobsCount = (resourcesCount / 2) + 1; + final int jobsCount = (resourcesCount * 2) + 1; final int nodesCount = (resourcesCount / 10) + 1; // enable node mirroring to make more chaos System.setProperty(Constants.SYSTEM_PROPERTY_ENABLE_NODE_MIRROR, "true"); From b280c0089f5a137c7cfbad957b5d8d5ded975bf2 Mon Sep 17 00:00:00 2001 From: Pokorny Martin Date: Fri, 5 Jul 2024 13:59:01 +0200 Subject: [PATCH 07/12] fix lock button --- .../lockableresources/LockStepExecution.java | 8 +++--- .../LockableResourcesManager.java | 25 +++++++++++-------- .../lockableresources/LockStepTest.java | 2 +- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java b/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java index 5c915559c..8c74c798a 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java @@ -41,7 +41,6 @@ public LockStepExecution(LockStep step, StepContext context) { @Override public boolean start() throws Exception { - // normally it might raise a exception, but we check it in the function .validate() // therefore we can skip the try-catch here. ResourceSelectStrategy resourceSelectStrategy = @@ -51,7 +50,6 @@ public boolean start() throws Exception { PrintStream logger = getContext().get(TaskListener.class).getLogger(); Run run = getContext().get(Run.class); - LockableResourcesManager.printLogs("Trying to acquire lock on [" + step + "]", Level.FINE, LOGGER, logger); List resourceHolderList = new ArrayList<>(); @@ -60,6 +58,8 @@ public boolean start() throws Exception { LockableResourcesManager lrm = LockableResourcesManager.get(); synchronized (lrm.syncResources) { step.validate(); + + LockableResourcesManager.printLogs("Trying to acquire lock on [" + step + "]", Level.FINE, LOGGER, logger); List resourceNames = new ArrayList<>(); for (LockStepResource resource : step.getResources()) { List resources = new ArrayList<>(); @@ -103,8 +103,8 @@ public boolean start() throws Exception { for (LockableResource resource : available) { lockedResources.put(resource.getName(), resource.getProperties()); } + LockStepExecution.proceed(lockedResources, getContext(), step.toString(), step.variable); } - LockStepExecution.proceed(lockedResources, getContext(), step.toString(), step.variable); return false; } @@ -230,7 +230,7 @@ private static final class Callback extends BodyExecutionCallback.TailCall { protected void finished(StepContext context) throws Exception { Run build = context.get(Run.class); LockedResourcesBuildAction.addLog(build, this.resourceNames, "released", this.resourceDescription); - LockableResourcesManager.get().unlockNames(this.resourceNames); + LockableResourcesManager.get().unlockNames(this.resourceNames, build); LockableResourcesManager.printLogs( "Lock released on resource [" + this.resourceDescription + "]", Level.FINE, diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java index 0c6e1a6d2..bb91974ef 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java @@ -610,25 +610,22 @@ public boolean lock(List resourcesToLock, Run build) { } // --------------------------------------------------------------------------- - private void freeResources(List unlockResources) { + private void freeResources(List unlockResources, Run build) { LOGGER.fine("free it: " + unlockResources); // make sure there is a list of resource names to unlock - if (unlockResources == null || unlockResources.isEmpty()) { + if (unlockResources == null || unlockResources.isEmpty() || build == null) { return; } List toBeRemoved = new ArrayList<>(); - Run build = null; - for (LockableResource resource : unlockResources) { // No more contexts, unlock resource - if (build == null) { - build = resource.getBuild(); - } + // the resource has been currently unlocked (like by LRM page - button unlock, or by API) + if (!build.equals(resource.getBuild())) continue; resource.unqueue(); resource.setBuild(null); @@ -659,28 +656,34 @@ public void unlockBuild(@Nullable Run build) { if (resourcesInUse.size() == 0) { return; } - unlockNames(resourcesInUse); + unlockNames(resourcesInUse, build); } // --------------------------------------------------------------------------- @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "not sure which exceptions might be catch.") - public void unlockNames(@Nullable List resourceNamesToUnLock) { + public void unlockNames(@Nullable List resourceNamesToUnLock, Run build) { // make sure there is a list of resource names to unlock if (resourceNamesToUnLock == null || resourceNamesToUnLock.isEmpty()) { return; } synchronized (this.syncResources) { - unlockResources(this.fromNames(resourceNamesToUnLock)); + unlockResources(this.fromNames(resourceNamesToUnLock), build); } } + // --------------------------------------------------------------------------- public void unlockResources(List resourcesToUnLock) { + unlockResources(resourcesToUnLock, resourcesToUnLock.get(0).getBuild()); + } + + // --------------------------------------------------------------------------- + public void unlockResources(List resourcesToUnLock, Run build) { if (resourcesToUnLock == null || resourcesToUnLock.isEmpty()) { return; } synchronized (this.syncResources) { - this.freeResources(resourcesToUnLock); + this.freeResources(resourcesToUnLock, build); while (proceedNextContext()) { // process as many contexts as possible diff --git a/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java b/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java index 1e6e642ec..7d8cc4f54 100644 --- a/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java +++ b/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java @@ -743,7 +743,7 @@ public void deleteRunningBuildNewBuildClearsLock() throws Exception { public void unlockButtonWithWaitingRuns() throws Exception { LockableResourcesManager.get().createResource("resource1"); WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); - p.setDefinition(new CpsFlowDefinition("lock('resource1') { semaphore('wait-inside') ; sleep(1)}", true)); + p.setDefinition(new CpsFlowDefinition("lock('resource1') { semaphore('wait-inside') }", true)); WorkflowRun prevBuild = null; TestHelpers testHelpers = new TestHelpers(); From 982c10fb4608c8d3a3b4009a87765e87d12ed3ee Mon Sep 17 00:00:00 2001 From: Pokorny Martin Date: Fri, 5 Jul 2024 14:08:07 +0200 Subject: [PATCH 08/12] inversePrecedenceAndPriorityAreSet --- .../org/jenkins/plugins/lockableresources/LockStepTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java b/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java index 7d8cc4f54..60d6770aa 100644 --- a/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java +++ b/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java @@ -1719,12 +1719,10 @@ public void inversePrecedenceAndPriorityAreSet() throws Exception { LockableResourcesManager.get().createResourceWithLabel("resource1", "label1"); WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); p.setDefinition(new CpsFlowDefinition( - "lock(label: 'label1', inversePrecedence: true, priority: -1000000000, resourceSelectStrategy: '') {}", - true)); + "lock(label: 'label1', inversePrecedence: true, priority: -1000000000) {}", true)); WorkflowRun b1 = p.scheduleBuild2(0).waitForStart(); j.assertBuildStatus(Result.FAILURE, j.waitForCompletion(b1)); j.assertLogContains("The \"inverse precedence\" option is not compatible with \"queue priority\" option!", b1); - isPaused(b1, 0, 0); } @Test From 4056293e266698564f849311947a82cc30f73ebf Mon Sep 17 00:00:00 2001 From: Pokorny Martin Date: Fri, 5 Jul 2024 14:10:35 +0200 Subject: [PATCH 09/12] spotbugs --- .../plugins/lockableresources/LockableResourcesManager.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java index bb91974ef..94dddd59e 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java @@ -637,8 +637,7 @@ private void freeResources(List unlockResources, Run bui } } - if (build != null) - LockedResourcesBuildAction.findAndInitAction(build).removeUsedResources(getResourcesNames(unlockResources)); + LockedResourcesBuildAction.findAndInitAction(build).removeUsedResources(getResourcesNames(unlockResources)); // remove all ephemeral resources removeResources(toBeRemoved); From ffe40761edf7e22d257200c392931bf6a3bd1ded Mon Sep 17 00:00:00 2001 From: Pokorny Martin Date: Fri, 5 Jul 2024 14:25:49 +0200 Subject: [PATCH 10/12] lockWithInvalidLabel --- .../org/jenkins/plugins/lockableresources/LockStepTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java b/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java index 60d6770aa..a1dcd7004 100644 --- a/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java +++ b/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java @@ -1711,7 +1711,7 @@ public void lockWithInvalidLabel() throws Exception { WorkflowRun b1 = p.scheduleBuild2(0).waitForStart(); j.assertBuildStatus(Result.FAILURE, j.waitForCompletion(b1)); j.assertLogContains("The resource label does not exist: invalidLabel", b1); - isPaused(b1, 0, 0); + // isPaused(b1, 0, 0); } @Test From df335245cc558d182cc1c9ecd01bc605f3e12431 Mon Sep 17 00:00:00 2001 From: Pokorny Martin Date: Fri, 5 Jul 2024 14:27:31 +0200 Subject: [PATCH 11/12] lockWithInvalidLabel --- .../jenkins/plugins/lockableresources/LockStepExecution.java | 4 +++- .../org/jenkins/plugins/lockableresources/LockStepTest.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java b/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java index 8c74c798a..313a4b06f 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockStepExecution.java @@ -46,7 +46,6 @@ public boolean start() throws Exception { ResourceSelectStrategy resourceSelectStrategy = ResourceSelectStrategy.valueOf(step.resourceSelectStrategy.toUpperCase(Locale.ENGLISH)); - getContext().get(FlowNode.class).addAction(new PauseAction("Lock")); PrintStream logger = getContext().get(TaskListener.class).getLogger(); Run run = getContext().get(Run.class); @@ -60,6 +59,9 @@ public boolean start() throws Exception { step.validate(); LockableResourcesManager.printLogs("Trying to acquire lock on [" + step + "]", Level.FINE, LOGGER, logger); + + getContext().get(FlowNode.class).addAction(new PauseAction("Lock")); + List resourceNames = new ArrayList<>(); for (LockStepResource resource : step.getResources()) { List resources = new ArrayList<>(); diff --git a/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java b/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java index a1dcd7004..60d6770aa 100644 --- a/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java +++ b/src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java @@ -1711,7 +1711,7 @@ public void lockWithInvalidLabel() throws Exception { WorkflowRun b1 = p.scheduleBuild2(0).waitForStart(); j.assertBuildStatus(Result.FAILURE, j.waitForCompletion(b1)); j.assertLogContains("The resource label does not exist: invalidLabel", b1); - // isPaused(b1, 0, 0); + isPaused(b1, 0, 0); } @Test From 46f69e10408600af6ca42fa4595985f1c58441ee Mon Sep 17 00:00:00 2001 From: Pokorny Martin Date: Sun, 7 Jul 2024 11:23:30 +0200 Subject: [PATCH 12/12] logs --- .../plugins/lockableresources/LockableResource.java | 7 ++++--- .../plugins/lockableresources/queue/LockRunListener.java | 8 -------- .../jenkins/plugins/lockableresources/PressureTest.java | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockableResource.java b/src/main/java/org/jenkins/plugins/lockableresources/LockableResource.java index 1824a059f..509f8abb8 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockableResource.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockableResource.java @@ -494,17 +494,18 @@ public String getLockCauseDetail() { return build; } + // --------------------------------------------------------------------------- @Exported public String getBuildName() { if (getBuild() != null) return getBuild().getFullDisplayName(); else return null; } + // --------------------------------------------------------------------------- public void setBuild(@Nullable Run lockedBy) { - LOGGER.info(getName() + " cause " + this.getLockCauseDetail()); - LOGGER.info(getName() + " current " + this.build); - LOGGER.info(getName() + " next " + lockedBy); + this.build = lockedBy; + if (lockedBy != null) { this.buildExternalizableId = lockedBy.getExternalizableId(); setReservedTimestamp(new Date()); diff --git a/src/main/java/org/jenkins/plugins/lockableresources/queue/LockRunListener.java b/src/main/java/org/jenkins/plugins/lockableresources/queue/LockRunListener.java index 01146b171..caceece3e 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/queue/LockRunListener.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/queue/LockRunListener.java @@ -38,22 +38,14 @@ public void onStarted(Run build, TaskListener listener) { // only the child jobs will actually lock resources. if (build instanceof MatrixBuild) return; - LOGGER.info(build.getFullDisplayName() + " AbstractBuild " - + build.getClass().getName()); if (build instanceof AbstractBuild) { - LOGGER.info(build.getFullDisplayName() + " get LRM "); LockableResourcesManager lrm = LockableResourcesManager.get(); - LOGGER.info(build.getFullDisplayName() + " sync LRM "); synchronized (lrm.syncResources) { - LOGGER.info(build.getFullDisplayName() + " synced LRM "); Job proj = Utils.getProject(build); List required = new ArrayList<>(); - LOGGER.info(build.getFullDisplayName() + " requiredResources "); LockableResourcesStruct resources = Utils.requiredResources(proj); - LOGGER.info(build.getFullDisplayName() + " " + ((resources == null) ? "nothing" : "something")); - if (resources != null) { if (resources.requiredNumber != null || !resources.label.isEmpty() diff --git a/src/test/java/org/jenkins/plugins/lockableresources/PressureTest.java b/src/test/java/org/jenkins/plugins/lockableresources/PressureTest.java index c1b1c679d..7ea057cc0 100644 --- a/src/test/java/org/jenkins/plugins/lockableresources/PressureTest.java +++ b/src/test/java/org/jenkins/plugins/lockableresources/PressureTest.java @@ -31,7 +31,7 @@ public class PressureTest extends LockStepTestBase { @WithTimeout(900) public void pressureEnableSave() throws Exception { // keep in mind, that the windows nodes at jenkins-infra are not very fast - // pressure(Functions.isWindows() ? 10 : 10); + pressure(Functions.isWindows() ? 10 : 10); } @Test
${%table.column.index}${%table.column.timeStamp}${%table.column.action}${%table.column.step} ${%table.column.name}${%table.column.description}${%table.column.counter}
${idx.index + 1} + + + + ${resource.action}${resource.step} ${resource.name}${resource.description}${resource.counter}
${%table.column.name}
${idx.index + 1} - - ${resource.action}${resource.step}${resource.name}${loEntry.action}${loEntry.step}${loEntry.name}