Skip to content

Commit

Permalink
[JENKINS-17904] allow to configure labels per node
Browse files Browse the repository at this point in the history
in certain cases one doesn't want to have all the generated labels one
certain nodes.
Additionally add a global configuration for the labels

This might also fix JENKINS-17615.
The problem might be that the Node object gets detroyed and recreated
when a node config changes while the associated Computer is untouched
when the connection itself is unchanged (e.g. just a label change). I
noticed that when chaning the nodeproperty my changes didn't get
reflected. Only after caching the computer it started working.
  • Loading branch information
mawinter69 committed Oct 7, 2019
1 parent 3445016 commit 738cd2b
Show file tree
Hide file tree
Showing 24 changed files with 531 additions and 114 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ For example:
| Platform | Operating System | Version | Platform |
| ---------------------------- | ------------------ | -------------- | -------- |
| CentOS 7 | `CentOS` | `7` | `amd64` |

It can be configured globally and per agent which labels should be generated.
To define for an agent, just check 'Automatic Platform Labels' and select the labels you want.
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package org.jvnet.hudson.plugins.platformlabeler;

import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

/** Stores configuration about labels to generate */
public class LabelConfig extends AbstractDescribableImpl<LabelConfig> {

private boolean architecture = true;
private boolean name = true;
private boolean version = true;
private boolean architectureName = true;
private boolean nameVersion = true;
private boolean architectureNameVersion = true;

@DataBoundConstructor
public LabelConfig() {}

public boolean isArchitecture() {
return architecture;
}

@DataBoundSetter
public void setArchitecture(boolean arch) {
this.architecture = arch;
}

public boolean isName() {
return name;
}

@DataBoundSetter
public void setName(boolean name) {
this.name = name;
}

public boolean isVersion() {
return version;
}

@DataBoundSetter
public void setVersion(boolean version) {
this.version = version;
}

public boolean isArchitectureName() {
return architectureName;
}

@DataBoundSetter
public void setArchitectureName(boolean archName) {
this.architectureName = archName;
}

public boolean isNameVersion() {
return nameVersion;
}

@DataBoundSetter
public void setNameVersion(boolean nameVersion) {
this.nameVersion = nameVersion;
}

public boolean isArchitectureNameVersion() {
return architectureNameVersion;
}

@DataBoundSetter
public void setArchitectureNameVersion(boolean archNameVersion) {
this.architectureNameVersion = archNameVersion;
}

@Extension
@Symbol("platformlabelerconfig")
public static class DescriptorImpl extends Descriptor<LabelConfig> {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,25 @@
import hudson.slaves.ComputerListener;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.GlobalConfiguration;
import jenkins.model.Jenkins;

/** A cache of Node labels for the LabelFinder in our package. */
@Extension
public class NodeLabelCache extends ComputerListener {

/** The OS properties for nodes * */
private static transient Map<Computer, PlatformDetails> nodePlatformProperties =
Collections.synchronizedMap(new WeakHashMap<>());
/** The labels computed for nodes - accessible package wide. */
static transient WeakHashMap<Node, Collection<LabelAtom>> nodeLabels =
new WeakHashMap<Node, Collection<LabelAtom>>();
static transient Map<Node, Collection<LabelAtom>> nodeLabels = new WeakHashMap<>();
/** Logging of issues. */
private static final transient Logger LOGGER =
Logger.getLogger("org.jvnet.hudson.plugins.platformlabeler");
Expand All @@ -67,6 +72,17 @@ public final void onOnline(final Computer computer, final TaskListener ignored)
refreshModel(computer);
}

/** When any computer has changed, update the platform labels according to the configuration */
@Override
public final void onConfigurationChange() {
synchronized (nodePlatformProperties) {
nodePlatformProperties.forEach(
(node, labels) -> {
refreshModel(node);
});
}
}

/**
* Caches the labels for the computer against its node.
*
Expand All @@ -76,7 +92,7 @@ public final void onOnline(final Computer computer, final TaskListener ignored)
*/
final void cacheLabels(final Computer computer) throws IOException, InterruptedException {
/* Cache the labels for the node */
nodeLabels.put(computer.getNode(), requestNodeLabels(computer));
nodePlatformProperties.put(computer, requestComputerOSProperties(computer));
}

/**
Expand All @@ -88,21 +104,22 @@ final void refreshModel(final Computer computer) {
if (computer != null) {
Node node = computer.getNode();
if (node != null) {
nodeLabels.put(node, getLabelsForNode(node));
node.getAssignedLabels();
}
}
}

/**
* Return collection of labels for computer.
* Return a Map of the properties of the OS of the node
*
* @param computer agent whose labels are returned
* @return collection of labels for computer
* @throws IOException on I/O error
* @throws InterruptedException on thread interruption
*/
@NonNull
private Collection<LabelAtom> requestNodeLabels(final Computer computer)
private PlatformDetails requestComputerOSProperties(final Computer computer)
throws IOException, InterruptedException {
final VirtualChannel channel = computer.getChannel();
if (null == channel) {
Expand All @@ -111,18 +128,82 @@ private Collection<LabelAtom> requestNodeLabels(final Computer computer)
// ask while the computer was asynchronously disconnecting.
throw new IOException("No virtual channel available");
}
final Collection<LabelAtom> result = new HashSet<>();
final Jenkins jenkins = Jenkins.getInstanceOrNull();

try {
final Set<String> labels = channel.call(new PlatformDetailsTask());
labels.forEach(
(label) -> {
result.add(jenkins.getLabelAtom(label));
});
return channel.call(new PlatformDetailsTask());
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to read labels", e);
throw e;
}
}

/**
* Return collection of generated labels for the given node.
*
* @param node Node whose labels should be generated
* @return Collection with labels
*/
Collection<LabelAtom> getLabelsForNode(final Node node) {

Set<LabelAtom> result = new HashSet<>();

Computer computer = node.toComputer();

if (computer == null) {
return result;
}

PlatformDetails pp = nodePlatformProperties.get(computer);

LabelConfig labelConfig = getLabelConfig(node);

final Jenkins jenkins = Jenkins.getInstanceOrNull();

if (labelConfig.isArchitecture()) {
result.add(jenkins.getLabelAtom(pp.getArchitecture()));
}

if (labelConfig.isName()) {
result.add(jenkins.getLabelAtom(pp.getName()));
}

if (labelConfig.isVersion()) {
result.add(jenkins.getLabelAtom(pp.getVersion()));
}

if (labelConfig.isNameVersion()) {
result.add(jenkins.getLabelAtom(pp.getNameVersion()));
}

if (labelConfig.isArchitectureName()) {
result.add(jenkins.getLabelAtom(pp.getArchitectureName()));
}

if (labelConfig.isArchitectureNameVersion()) {
result.add(jenkins.getLabelAtom(pp.getArchitectureNameVersion()));
}

return result;
}

/**
* Returns the labelConfig of the given node if defined or the globally configured one.
*
* @param node The node to check.
* @return The labelConfig to be used for the node
*/
private LabelConfig getLabelConfig(final Node node) {
LabelConfig labelConfig =
GlobalConfiguration.all()
.getInstance(PlatformLabelerGlobalConfiguration.class)
.getLabelConfig();

PlatformLabelerNodeProperty nodeProperty =
node.getNodeProperty(PlatformLabelerNodeProperty.class);

if (nodeProperty != null) {
labelConfig = nodeProperty.getLabelConfig();
}
return labelConfig;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.jvnet.hudson.plugins.platformlabeler;

import java.io.Serializable;

/** Stores the platform details of a node */
public class PlatformDetails implements Serializable {

private static final long serialVersionUID = 1L;

private final String name;
private final String architecture;
private final String version;
private final String architectureNameVersion;
private final String architectureName;
private final String nameVersion;

public PlatformDetails(String name, String architecture, String version) {
this.name = name;
this.architecture = architecture;
this.version = version;
architectureNameVersion = architecture + "-" + name + "-" + version;
architectureName = architecture + "-" + name;
nameVersion = name + "-" + version;
}

public String getName() {
return name;
}

public String getArchitecture() {
return architecture;
}

public String getVersion() {
return version;
}

public String getArchitectureNameVersion() {
return architectureNameVersion;
}

public String getArchitectureName() {
return architectureName;
}

public String getNameVersion() {
return nameVersion;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,12 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import jenkins.security.Roles;
import org.jenkinsci.remoting.RoleChecker;
import org.jenkinsci.remoting.RoleSensitive;

/** Compute labels based on details computed on the agent. */
class PlatformDetailsTask implements Callable<HashSet<String>, IOException> {
class PlatformDetailsTask implements Callable<PlatformDetails, IOException> {

private static final String RELEASE = "release";
/** Unknown field value string. Package protected for us by LsbRelease class */
Expand All @@ -56,7 +54,7 @@ class PlatformDetailsTask implements Callable<HashSet<String>, IOException> {
*/
@Override
public void checkRoles(final RoleChecker checker) throws SecurityException {
checker.check((RoleSensitive) this, Roles.SLAVE);
checker.check(this, Roles.SLAVE);
}

/**
Expand All @@ -66,7 +64,7 @@ public void checkRoles(final RoleChecker checker) throws SecurityException {
* @throws IOException on I/O error
*/
@Override
public HashSet<String> call() throws IOException {
public PlatformDetails call() throws IOException {
final String arch = System.getProperty("os.arch", UNKNOWN_VALUE_STRING);
final String name = System.getProperty("os.name", UNKNOWN_VALUE_STRING);
final String version = System.getProperty("os.version", UNKNOWN_VALUE_STRING);
Expand Down Expand Up @@ -128,17 +126,17 @@ private String checkLinux32Bit(@NonNull final String arch) {
}

/**
* Compute agent labels based on seed values provided as parameters.
* Compute agent OS properties based on seed values provided as parameters.
*
* @param arch architecture of the agent, as in "x86", "amd64", or "aarch64"
* @param name name of the operating system or distribution as in "OpenBSD", "FreeBSD", "Windows",
* or "Linux"
* @param version version of the operating system
* @return agent labels as a set of strings
* @return agent OS properties
* @throws IOException on I/O error
*/
@NonNull
protected HashSet<String> computeLabels(
protected PlatformDetails computeLabels(
@NonNull final String arch, @NonNull final String name, @NonNull final String version)
throws IOException {
LsbRelease release;
Expand All @@ -151,7 +149,7 @@ protected HashSet<String> computeLabels(
}

/**
* Compute agent labels based on seed values provided as parameters.
* Compute agent OS properties based on seed values provided as parameters.
*
* @param arch architecture of the agent, as in "x86", "amd64", or "aarch64"
* @param name name of the operating system or distribution as in "OpenBSD", "FreeBSD", "Windows",
Expand All @@ -162,7 +160,7 @@ protected HashSet<String> computeLabels(
* @throws IOException on I/O error
*/
@NonNull
protected HashSet<String> computeLabels(
protected PlatformDetails computeLabels(
@NonNull final String arch,
@NonNull final String name,
@NonNull final String version,
Expand Down Expand Up @@ -213,14 +211,8 @@ protected HashSet<String> computeLabels(
} else if (computedName.startsWith("mac")) {
computedName = "mac";
}
HashSet<String> result = new HashSet<>();
result.add(computedArch);
result.add(computedName);
result.add(computedVersion);
result.add(computedArch + "-" + computedName);
result.add(computedName + "-" + computedVersion);
result.add(computedArch + "-" + computedName + "-" + computedVersion);
return result;
PlatformDetails properties = new PlatformDetails(computedName, computedArch, computedVersion);
return properties;
}

/**
Expand Down

0 comments on commit 738cd2b

Please sign in to comment.