diff --git a/README.md b/README.md index 3959acdba..cf99d4039 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ This plugin allows defining lockable resources (such as printers, phones, computers, etc.) that can be used by builds. If a build requires a resource which is already locked, it will wait for the resource to be free. +---- + ## Usage ### Adding lockable resources @@ -89,7 +91,6 @@ pipeline { #### Take first position in queue - ```groovy lock(resource: 'staging-server', inversePrecedence: true) { node { @@ -109,7 +110,6 @@ lock(label: 'some_resource', variable: 'LOCKED_RESOURCE') { When multiple locks are acquired, each will be assigned to a numbered variable: - ```groovy lock(label: 'some_resource', variable: 'LOCKED_RESOURCE', quantity: 2) { // comma separated names of all acquired locks @@ -161,6 +161,7 @@ echo 'Finish' More examples are [here](src/doc/examples/readme.md). +---- ## Configuration as Code This plugin can be configured via @@ -182,9 +183,15 @@ unclassified: Properties *description*, *labels* and *reservedBy* are optional. +---- + ## lockable-resources overview -The page `/lockable-resources/` provides an overview over all resources and actions to change resource status. +The page `/lockable-resources/` provides an overview over all lockable-resources. + +### Resources + +Provides an status overview over all resources and actions to change resource status. Name | Permission | Description -----|------------|------------ @@ -196,40 +203,70 @@ Reassign | STEAL | Reserves a resource that may be or not be reserved by some pe Reset | UNLOCK | Reset a resource that may be reserved, locked or queued. Note | RESERVE | Add or edit resource note. +### Labels + +Provides an overview over all lockable-resources labels. + +> *Note:* Please keep in mind, that lockable-resource-label is not the same as node-label! + +### Queue + +Provides an overview over currently queued requests. +A request is queued by the pipeline step `lock()`. When the requested resource(s) is currently in use (not free), then any new request for this resource will be added into the queue. + +A resource may be requested by: + +- name, such as in `lock('ABC') { ... }` +- label, such as in `lock(label : 'COFFEE_MACHINE')` + +> *Note:* Please keep in mind that groovy expressions are currently supported only in free-style jobs. Free-style jobs do not update this queue and therefore can not be shown in this view. + +> *Note:* An empty value in the column 'Requested at' means that this build has been started in an older plugin version - [1117.v157231b_03882](https://github.com/jenkinsci/lockable-resources-plugin/releases/tag/1117.v157231b_03882) and early. In this case we cannot recognize the timestamp. + +---- + ## Upgrading from 1102.vde5663d777cf Due an [issue](https://github.com/jenkinsci/lockable-resources-plugin/issues/434) **is not possible anymore to read resource-labels** from the config file org.jenkins.plugins.lockableresources.LockableResourcesManager.xml, **which is generated in the release** [1102.vde5663d777cf](https://github.com/jenkinsci/lockable-resources-plugin/releases/tag/1102.vde5663d777cf) This issue does not **effect** instances configured by [Configuration-as-Code](https://github.com/jenkinsci/configuration-as-code-plugin) plugin. -A possible solution is, to remove the `` tags from your `org.jenkins.plugins.lockableresources.LockableResourcesManager.xml`config file manually, before you upgrade to new version (Keep in mind, that a backup is still good idea). - +A possible solution is to remove the `` tags from your `org.jenkins.plugins.lockableresources.LockableResourcesManager.xml` config file manually, before you upgrade to new version (Keep in mind that a backup is still good idea). Example: change this one -``` + +```xml tests-integration-installation ``` + to -``` + +```xml tests-integration-installation ``` +---- + ## Changelog - See [GitHub Releases](https://github.com/jenkinsci/lockable-resources-plugin/releases) for recent versions. - See the [old changelog](CHANGELOG.old.md) for versions 2.5 and older. +---- + ## Report an Issue Please report issues and enhancements through the [Jenkins issue tracker in GitHub](https://github.com/jenkinsci/lockable-resources-plugin/issues/new/choose) +---- + ## Contributing Contributions are welcome, please @@ -237,6 +274,9 @@ refer to the separate [CONTRIBUTING](CONTRIBUTING.md) document for details on how to proceed! Join [Gitter channel](https://gitter.im/jenkinsci/lockable-resources) to discuss your ideas with the community. +---- + ## License + All source code is licensed under the MIT license. See [LICENSE](LICENSE.txt) diff --git a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java index d6e9bce7c..262f1115a 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java @@ -692,6 +692,12 @@ private QueuedContextStruct getNextQueuedContext( return newestEntry; } + /** Returns current queue */ + @Restricted(NoExternalUse.class) // used by jelly + public List getCurrentQueuedContext() { + return Collections.unmodifiableList(this.queuedContexts); + } + /** Creates the resource if it does not exist. */ public synchronized boolean createResource(String name) { name = Util.fixEmptyAndTrim(name); 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 4510d480f..dd4b1c45d 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction.java @@ -22,6 +22,8 @@ import java.util.Set; import javax.servlet.ServletException; import jenkins.model.Jenkins; +import org.jenkins.plugins.lockableresources.queue.LockableResourcesStruct; +import org.jenkins.plugins.lockableresources.queue.QueuedContextStruct; import org.jenkins.plugins.lockableresources.LockableResource; import org.jenkins.plugins.lockableresources.LockableResourcesManager; import org.jenkins.plugins.lockableresources.Messages; @@ -158,6 +160,32 @@ public int getAssignedResourceAmount(String label) { return LockableResourcesManager.get().getResourcesWithLabel(label, null).size(); } + /** Returns current queue */ + @Restricted(NoExternalUse.class) // used by jelly + public List getCurrentQueuedContext() { + return LockableResourcesManager.get().getCurrentQueuedContext(); + } + + /** Returns current queue */ + @Restricted(NoExternalUse.class) // used by jelly + @CheckForNull + public LockableResourcesStruct getOldestQueue() { + LockableResourcesStruct oldest = null; + for (QueuedContextStruct context : this.getCurrentQueuedContext()) { + for(LockableResourcesStruct resourceStruct : context.getResources()) { + if (resourceStruct.queuedAt == 0) { + // Older versions of this plugin might miss this information. + // Therefore skip it here. + continue; + } + if (oldest == null || oldest.queuedAt > resourceStruct.queuedAt) { + oldest = resourceStruct; + } + } + } + return oldest; + } + @RequirePOST public void doUnlock(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException diff --git a/src/main/java/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct.java b/src/main/java/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct.java index c67986a74..fe089dcbc 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct.java @@ -14,12 +14,15 @@ import hudson.EnvVars; import java.io.Serializable; import java.util.ArrayList; +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.RequiredResourcesProperty; import org.jenkins.plugins.lockableresources.util.SerializableSecureGroovyScript; import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; public class LockableResourcesStruct implements Serializable { @@ -30,6 +33,7 @@ public class LockableResourcesStruct implements Serializable { public String label; public String requiredVar; public String requiredNumber; + public long queuedAt = 0; @CheckForNull private final SerializableSecureGroovyScript serializableResourceMatchScript; @@ -39,6 +43,7 @@ public class LockableResourcesStruct implements Serializable { private static final long serialVersionUID = 1L; public LockableResourcesStruct(RequiredResourcesProperty property, EnvVars env) { + queuedAt = new Date().getTime(); required = new ArrayList<>(); LockableResourcesManager resourcesManager = LockableResourcesManager.get(); @@ -81,6 +86,7 @@ public LockableResourcesStruct( public LockableResourcesStruct( @Nullable List resources, @Nullable String label, int quantity) { + queuedAt = new Date().getTime(); required = new ArrayList<>(); if (resources != null) { for (String resource : resources) { @@ -138,4 +144,18 @@ public String toString() { + ", Number of resources: " + this.requiredNumber; } + + /** Returns timestamp when the resource has been added into queue.*/ + @Restricted(NoExternalUse.class) // used by jelly + public Date getQueuedTimestamp() { + return new Date(this.queuedAt); + } + + /** Check if the queue takes too long. + At the moment "too long" means over 1 hour. + */ + @Restricted(NoExternalUse.class) // used by jelly + public boolean takeTooLong() { + return (new Date().getTime() - this.queuedAt) > 3600000L; + } } diff --git a/src/main/java/org/jenkins/plugins/lockableresources/queue/QueuedContextStruct.java b/src/main/java/org/jenkins/plugins/lockableresources/queue/QueuedContextStruct.java index 4753d1931..c54e98170 100644 --- a/src/main/java/org/jenkins/plugins/lockableresources/queue/QueuedContextStruct.java +++ b/src/main/java/org/jenkins/plugins/lockableresources/queue/QueuedContextStruct.java @@ -8,9 +8,14 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ package org.jenkins.plugins.lockableresources.queue; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import hudson.model.Run; +import java.io.IOException; import java.io.Serializable; import java.util.List; import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; /* * This class is used to queue pipeline contexts @@ -56,6 +61,18 @@ public StepContext getContext() { return this.context; } + /** Return build, where is the resource used.*/ + @CheckForNull + @Restricted(NoExternalUse.class) // used by jelly + public Run getBuild() { + try { + return this.getContext().get(Run.class); + } catch (IOException | InterruptedException e) { + // for some reason there is no Run object for this context + return null; + } + } + /* * Gets the required resources. */ diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction/index.jelly b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction/index.jelly index befd3e99a..206c7ecd9 100644 --- a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction/index.jelly +++ b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction/index.jelly @@ -14,10 +14,14 @@ xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:st="jelly:stapler" + xmlns:t="/lib/hudson" > + + + + -

${%header.resources}

@@ -39,7 +43,11 @@ - +

+
@@ -52,6 +60,11 @@
+
+
+ +
+
+

${%queue.warning.count(queue.size(), h.getTimeSpanString(oldestQueue.getQueuedTimestamp()))}

+
${%queue.warning.count.detail}
+ + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
${%queue.table.column.request.type}${%queue.table.column.request.info}${%queue.table.column.requested.by}${%queue.table.column.requested.at}${%queue.table.column.reason}
+ + + ${build.fullDisplayName} + + + ${%N/A} + + + + + ${%ago(h.getTimeSpanString(queuedResource.getQueuedTimestamp()))} + +
+
+ diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction/tableQueue/table.properties b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction/tableQueue/table.properties new file mode 100644 index 000000000..6ceec1861 --- /dev/null +++ b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction/tableQueue/table.properties @@ -0,0 +1,46 @@ +# The MIT License +# +# Copyright 2023 Martin Pokorny. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +queue.isEmpty=The queue is currently empty. +queue.warning.count=The queue has {0} item(s). The oldest one was inserted {1} ago! +queue.warning.count.detail=\ +In many cases, users forget to un-reserve the resource (or an external API has died). \ +Sometimes the build does not release the resource. \ +This can happen when Jenkins crashes and the builds are no longer executable after restart.
\ +Please check the status and manually release the resource if necessary. +If you do not have permission to release the resource, contact your administrator. + +queue.table.column.request.type=Request type +queue.table.column.request.info=Request +queue.table.column.requested.by=Requested by +queue.table.column.requested.at=Requested at +queue.table.column.reason=Reason + +#status +resource.status.free=FREE +resource.status.locked=LOCKED by {1} +resource.status.reservedBy=RESERVED by {0} +resource.status.queuedBy=QUEUED by {0} {1} +ago={0} ago +label.requiredNumber={0} label(s) are required. +label.requiredAll=All possible labels are required. +label.status=Found {1} free resource(s) from {0} possible. \ No newline at end of file diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction/tableResources/table.jelly b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction/tableResources/table.jelly index 7bfc71e8e..9f67f9cbb 100644 --- a/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction/tableResources/table.jelly +++ b/src/main/resources/org/jenkins/plugins/lockableresources/actions/LockableResourcesRootAction/tableResources/table.jelly @@ -71,7 +71,7 @@ THE SOFTWARE.
- ${%ephemeral} + ${%resources.ephemeral}
diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/reason/cell.jelly b/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/reason/cell.jelly new file mode 100644 index 000000000..1bee56b36 --- /dev/null +++ b/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/reason/cell.jelly @@ -0,0 +1,79 @@ + + + + + + + + + + + ${%resource.status.reservedBy(resource.reservedBy)} + + + ${%resource.status.locked(rootURL + '/' + resource.build.url, resource.build.fullDisplayName)} + + + ${%resource.status.queuedBy(resource.queueItemProject, resource.queueItemId)} + + + + ${%resource.status.free} + + + + ${%ago(h.getTimeSpanString(resource.reservedTimestamp))} + + + + + + + + + ${%label.requiredNumber(queuedResource.requiredNumber)} + + + ${%label.requiredAll} + + + ${%label.status(it.getFreeResourceAmount(queuedResource.label), it.getAssignedResourceAmount(queuedResource.label))} + + + + + + ${%groovy.status} + + + + + ${%N/A} + + + + diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/reason/cell.properties b/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/reason/cell.properties new file mode 100644 index 000000000..75d4211e2 --- /dev/null +++ b/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/reason/cell.properties @@ -0,0 +1,34 @@ +# The MIT License +# +# Copyright 2023 Martin Pokorny. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +resource.status.locked=Locked by {1} +resource.status.reservedBy=Reserved by {0} +resource.status.queuedBy=Queued by {0} {1} +resource.status.free=Free + +label.requiredNumber=Requested {0} label(s). +label.requiredAll=Requested ALL possible labels. +label.status=
There are {1} resource(s) matched, but only {0} free. + +groovy.status=!!!Groovy expression is currently not supported!!! + +ago={0} ago \ No newline at end of file diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/request/cell.jelly b/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/request/cell.jelly new file mode 100644 index 000000000..17f4b1d93 --- /dev/null +++ b/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/request/cell.jelly @@ -0,0 +1,75 @@ + + + + + + + + +
+
+
+ ${resource.name} +
+
+ + ${%resources.ephemeral} + +
+
+ +
+
+ ${resource.description} +
+
+
+
+
+
+ + + + + ${queuedResource.label} + + + + + + + + ${%groovy.code} + + + + + ${%N/A} + + +
+
diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/request/cell.properties b/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/request/cell.properties new file mode 100644 index 000000000..f4de898e7 --- /dev/null +++ b/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/request/cell.properties @@ -0,0 +1,25 @@ +# The MIT License +# +# Copyright 2023 Martin Pokorny. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +resources.ephemeral=Ephemeral + +groovy.code=!!!Groovy expression is currently not supported!!! \ No newline at end of file diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/request_type/cell.jelly b/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/request_type/cell.jelly new file mode 100644 index 000000000..022ae5151 --- /dev/null +++ b/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/request_type/cell.jelly @@ -0,0 +1,47 @@ + + + + + + + ${%type.resources} + + + + ${%type.label} + + + + + ${%type.groovy} + + + + ${%N/A} + + + diff --git a/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/request_type/cell.properties b/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/request_type/cell.properties new file mode 100644 index 000000000..79cc4f277 --- /dev/null +++ b/src/main/resources/org/jenkins/plugins/lockableresources/queue/LockableResourcesStruct/request_type/cell.properties @@ -0,0 +1,25 @@ +# The MIT License +# +# Copyright 2023 Martin Pokorny. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +type.resources=Resources +type.label=Label +type.groovy=Groovy expression \ No newline at end of file