Skip to content

Commit 983338f

Browse files
authored
[JENKINS-68155] Fix state of previous labels when updating a node label (#10501)
2 parents 65c5f54 + 69a0bef commit 983338f

File tree

4 files changed

+73
-1
lines changed

4 files changed

+73
-1
lines changed

core/src/main/java/hudson/model/Node.java

+9
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,15 @@ public Set<LabelAtom> getAssignedLabels() {
341341
return Collections.unmodifiableSet(r);
342342
}
343343

344+
/**
345+
* @return the labels to be trimmed for this node.
346+
*/
347+
@NonNull
348+
@Restricted(NoExternalUse.class)
349+
public Set<LabelAtom> drainLabelsToTrim() {
350+
return new HashSet<>(getAssignedLabels());
351+
}
352+
344353
/**
345354
* Return all the labels assigned dynamically to this node.
346355
* This calls all the LabelFinder implementations with the node converts

core/src/main/java/hudson/model/Slave.java

+23
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,15 @@
6363
import java.util.ArrayList;
6464
import java.util.Collection;
6565
import java.util.Collections;
66+
import java.util.HashSet;
6667
import java.util.List;
6768
import java.util.Objects;
6869
import java.util.Set;
6970
import java.util.jar.JarFile;
7071
import java.util.jar.Manifest;
7172
import java.util.logging.Level;
7273
import java.util.logging.Logger;
74+
import java.util.stream.Collectors;
7375
import jenkins.model.Jenkins;
7476
import jenkins.security.MasterToSlaveCallable;
7577
import jenkins.slaves.WorkspaceLocator;
@@ -340,6 +342,23 @@ public String getLabelString() {
340342
return Util.fixNull(label).trim();
341343
}
342344

345+
private transient Set<LabelAtom> previouslyAssignedLabels = new HashSet<>();
346+
347+
/**
348+
* @return the labels to be trimmed for this node. This includes current and previous labels that were applied before the last call to this method.
349+
*/
350+
@Override
351+
@NonNull
352+
@Restricted(NoExternalUse.class)
353+
public Set<LabelAtom> drainLabelsToTrim() {
354+
var result = new HashSet<>(super.drainLabelsToTrim());
355+
synchronized (previouslyAssignedLabels) {
356+
result.addAll(previouslyAssignedLabels);
357+
previouslyAssignedLabels.clear();
358+
}
359+
return result;
360+
}
361+
343362
@Override
344363
@DataBoundSetter
345364
public void setLabelString(String labelString) throws IOException {
@@ -349,6 +368,9 @@ public void setLabelString(String labelString) throws IOException {
349368
}
350369

351370
private void _setLabelString(String labelString) {
371+
synchronized (this.previouslyAssignedLabels) {
372+
this.previouslyAssignedLabels.addAll(getAssignedLabels().stream().filter(Objects::nonNull).collect(Collectors.toSet()));
373+
}
352374
this.label = Util.fixNull(labelString).trim();
353375
this.labelAtomSet = Collections.unmodifiableSet(Label.parse(label));
354376
}
@@ -611,6 +633,7 @@ public int hashCode() {
611633
protected Object readResolve() {
612634
if (nodeProperties == null)
613635
nodeProperties = new DescribableList<>(this);
636+
previouslyAssignedLabels = new HashSet<>();
614637
_setLabelString(label);
615638
return this;
616639
}

core/src/main/java/jenkins/model/Jenkins.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -2246,7 +2246,7 @@ public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getGlobalNodePro
22462246
*/
22472247
void trimLabels(Node... nodes) {
22482248
Set<LabelAtom> includedLabels = new HashSet<>();
2249-
Arrays.stream(nodes).filter(Objects::nonNull).forEach(n -> includedLabels.addAll(n.getAssignedLabels()));
2249+
Arrays.stream(nodes).filter(Objects::nonNull).forEach(n -> includedLabels.addAll(n.drainLabelsToTrim()));
22502250
trimLabels(includedLabels);
22512251
}
22522252

test/src/test/java/hudson/model/labels/LabelAtomTest.java

+40
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package hudson.model.labels;
22

33
import static org.hamcrest.MatcherAssert.assertThat;
4+
import static org.hamcrest.Matchers.contains;
45
import static org.hamcrest.Matchers.containsInAnyOrder;
56
import static org.hamcrest.Matchers.empty;
67
import static org.hamcrest.Matchers.is;
8+
import static org.junit.Assert.assertNotNull;
79

810
import hudson.model.Label;
911
import hudson.model.Node;
@@ -12,6 +14,7 @@
1214
import java.util.List;
1315
import org.junit.Rule;
1416
import org.junit.Test;
17+
import org.jvnet.hudson.test.Issue;
1518
import org.jvnet.hudson.test.JenkinsRule;
1619

1720
public class LabelAtomTest {
@@ -69,6 +72,43 @@ public void isEmpty() throws Exception {
6972
assertThat(l2.isEmpty(), is(false));
7073
}
7174

75+
@Test
76+
@Issue("JENKINS-68155")
77+
public void changeNodeLabel() throws Exception {
78+
var slave = j.createSlave("node", "label linux", null);
79+
var label = Label.get("label");
80+
assertNotNull(label);
81+
assertThat(label.getNodes(), contains(slave));
82+
var osLabel = Label.get("linux");
83+
assertNotNull(osLabel);
84+
assertThat(osLabel.getNodes(), contains(slave));
85+
slave.setLabelString("label2 linux");
86+
j.jenkins.updateNode(slave);
87+
label = Label.get("label");
88+
assertNotNull(label);
89+
assertThat(label.getNodes(), empty());
90+
var label2 = Label.get("label2");
91+
assertNotNull(label2);
92+
assertThat(label2.getNodes(), contains(slave));
93+
osLabel = Label.get("linux");
94+
assertNotNull(osLabel);
95+
assertThat(osLabel.getNodes(), contains(slave));
96+
}
97+
98+
@Test
99+
@Issue("JENKINS-68155")
100+
public void removeNodeLabel() throws Exception {
101+
var slave = j.createSlave("node", "label", null);
102+
var label = Label.get("label");
103+
assertNotNull(label);
104+
assertThat(label.getNodes(), contains(slave));
105+
slave.setLabelString(null);
106+
j.jenkins.updateNode(slave);
107+
label = Label.get("label");
108+
assertNotNull(label);
109+
assertThat(label.getNodes(), empty());
110+
}
111+
72112
private static class TestCloud extends Cloud {
73113

74114
private final List<Label> labels;

0 commit comments

Comments
 (0)