Skip to content

Commit e1a316f

Browse files
shawkinsmanusa
authored andcommitted
fix #4638: adding the ability to set the full object meta
also adding a leadership integration test
1 parent 8d52ae2 commit e1a316f

File tree

8 files changed

+107
-83
lines changed

8 files changed

+107
-83
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#### Improvements
2222
* Fix #4675: adding a fully client side timeout for informer watches
2323
* Fix #3805: DeletionTimestamp and Finalizer support in Mock server.
24+
* Fix #4638: adding a way to set the full object meta on a leadership election lock, this can be used to set owner references
2425
* Fix #4644: generate CRDs in parallel and optimize code
2526
* Fix #4659: added a generic support(apiversion, kind) method in addition to the class based check
2627
* Fix #4724: Private configuration classes cause trouble with Java native (reflection)

kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extended/leaderelection/resourcelock/ConfigMapLock.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ public ConfigMapLock(String configMapNamespace, String configMapName, String ide
3333
super(configMapNamespace, configMapName, identity);
3434
}
3535

36+
public ConfigMapLock(ObjectMeta meta, String identity) {
37+
super(meta, identity);
38+
}
39+
3640
@Override
3741
protected Class<ConfigMap> getKind() {
3842
return ConfigMap.class;

kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extended/leaderelection/resourcelock/LeaseLock.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ public LeaseLock(String leaseNamespace, String leaseName, String identity) {
2929
super(leaseNamespace, leaseName, identity);
3030
}
3131

32+
public LeaseLock(ObjectMeta meta, String identity) {
33+
super(meta, identity);
34+
}
35+
3236
@Override
3337
protected Class<Lease> getKind() {
3438
return Lease.class;

kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extended/leaderelection/resourcelock/ResourceLock.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,17 @@
2929

3030
public abstract class ResourceLock<T extends HasMetadata> implements Lock {
3131

32-
private final String namespace;
33-
private final String name;
32+
private final ObjectMeta meta;
3433
private final String identity;
3534

3635
public ResourceLock(String namespace, String name, String identity) {
37-
this.namespace = Objects.requireNonNull(namespace, "namespace is required");
38-
this.name = Objects.requireNonNull(name, "name is required");
36+
this(new ObjectMetaBuilder().withNamespace(namespace).withName(name).build(), identity);
37+
}
38+
39+
public ResourceLock(ObjectMeta meta, String identity) {
40+
this.meta = meta;
41+
Objects.requireNonNull(meta.getNamespace(), "namespace is required");
42+
Objects.requireNonNull(meta.getName(), "name is required");
3943
this.identity = Objects.requireNonNull(identity, "identity is required");
4044
}
4145

@@ -47,7 +51,7 @@ public LeaderElectionRecord get(KubernetesClient client) {
4751
}
4852

4953
private Optional<T> getResource(KubernetesClient client) {
50-
return Optional.ofNullable(client.resources(getKind()).inNamespace(namespace).withName(name).get());
54+
return Optional.ofNullable(client.resources(getKind()).inNamespace(meta.getNamespace()).withName(meta.getName()).get());
5155
}
5256

5357
@Override
@@ -66,7 +70,6 @@ public void update(KubernetesClient client, LeaderElectionRecord leaderElectionR
6670
*
6771
* @param leaderElectionRecord
6872
* @param meta not null
69-
* @param current may be null
7073
* @return
7174
*/
7275
protected abstract T toResource(LeaderElectionRecord leaderElectionRecord, ObjectMeta meta);
@@ -80,7 +83,7 @@ protected LeaderElectionRecord toRecordInternal(T resource) {
8083
protected abstract LeaderElectionRecord toRecord(T resource);
8184

8285
protected ObjectMeta getObjectMeta(Serializable version) {
83-
return new ObjectMetaBuilder().withNamespace(namespace).withName(name).withResourceVersion((String) version).build();
86+
return new ObjectMetaBuilder(meta).withResourceVersion((String) version).build();
8487
}
8588

8689
/**
@@ -96,7 +99,7 @@ public String identity() {
9699
*/
97100
@Override
98101
public String describe() {
99-
return String.format("%sLock: %s - %s (%s)", getKind().getSimpleName(), namespace, name, identity);
102+
return String.format("%sLock: %s - %s (%s)", getKind().getSimpleName(), meta.getNamespace(), meta.getName(), identity);
100103
}
101104

102105
}

kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/extended/leaderelection/resourcelock/ConfigMapLockTest.java

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,62 +16,33 @@
1616
package io.fabric8.kubernetes.client.extended.leaderelection.resourcelock;
1717

1818
import io.fabric8.kubernetes.api.model.ConfigMap;
19-
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
20-
import io.fabric8.kubernetes.api.model.ConfigMapList;
2119
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
2220
import io.fabric8.kubernetes.client.KubernetesClient;
23-
import io.fabric8.kubernetes.client.dsl.MixedOperation;
24-
import io.fabric8.kubernetes.client.dsl.ReplaceDeletable;
25-
import io.fabric8.kubernetes.client.dsl.Resource;
26-
import org.junit.jupiter.api.AfterEach;
21+
import io.fabric8.kubernetes.client.dsl.base.PatchContext;
2722
import org.junit.jupiter.api.BeforeEach;
2823
import org.junit.jupiter.api.Test;
2924
import org.junit.jupiter.api.function.Executable;
30-
import org.mockito.Answers;
31-
import org.mockito.ArgumentMatchers;
32-
import org.mockito.Mockito;
3325

3426
import java.time.Duration;
3527
import java.time.ZoneId;
3628
import java.time.ZonedDateTime;
3729
import java.util.Collections;
38-
import java.util.HashMap;
3930

4031
import static org.junit.jupiter.api.Assertions.assertEquals;
4132
import static org.junit.jupiter.api.Assertions.assertNotNull;
4233
import static org.junit.jupiter.api.Assertions.assertThrows;
4334
import static org.mockito.ArgumentMatchers.any;
44-
import static org.mockito.ArgumentMatchers.anyString;
45-
import static org.mockito.ArgumentMatchers.eq;
4635
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
4736
import static org.mockito.Mockito.mock;
48-
import static org.mockito.Mockito.times;
4937
import static org.mockito.Mockito.verify;
50-
import static org.mockito.Mockito.when;
5138

5239
class ConfigMapLockTest {
5340

5441
private KubernetesClient kc;
55-
private MixedOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> configMaps;
56-
private ConfigMapBuilder configMapBuilder;
57-
private ConfigMapBuilder.MetadataNested<ConfigMapBuilder> metadata;
5842

5943
@BeforeEach
6044
void setUp() {
6145
kc = mock(KubernetesClient.class, RETURNS_DEEP_STUBS);
62-
configMaps = mock(MixedOperation.class, RETURNS_DEEP_STUBS);
63-
configMapBuilder = Mockito.mock(ConfigMapBuilder.class, RETURNS_DEEP_STUBS);
64-
metadata = mock(ConfigMapBuilder.MetadataNested.class, RETURNS_DEEP_STUBS);
65-
when(kc.configMaps().inNamespace(anyString())).thenReturn(configMaps);
66-
when(configMapBuilder.editOrNewMetadata()).thenReturn(metadata);
67-
}
68-
69-
@AfterEach
70-
void tearDown() {
71-
metadata = null;
72-
configMapBuilder = null;
73-
configMaps = null;
74-
kc = null;
7546
}
7647

7748
@Test
@@ -102,15 +73,14 @@ void missingIdentityShouldThrowException() {
10273
void getWithExistingConfigMapShouldReturnLeaderElectionRecord() {
10374
// Given
10475
final ConfigMap cm = new ConfigMap();
105-
when(configMaps.withName(ArgumentMatchers.eq("name")).get()).thenReturn(cm);
10676
cm.setMetadata(new ObjectMetaBuilder()
10777
.withAnnotations(
10878
Collections.singletonMap("control-plane.alpha.kubernetes.io/leader",
10979
"{\"holderIdentity\":\"1337\",\"leaseDuration\":15,\"acquireTime\":1445401740,\"renewTime\":1445412480}"))
11080
.withResourceVersion("313373").build());
11181
final ConfigMapLock lock = new ConfigMapLock("namespace", "name", "1337");
11282
// When
113-
final LeaderElectionRecord result = lock.get(kc);
83+
final LeaderElectionRecord result = lock.toRecordInternal(cm);
11484
// Then
11585
assertNotNull(result);
11686
assertEquals("313373", result.getVersion());
@@ -128,26 +98,20 @@ void createWithValidLeaderElectionRecordShouldSendPostRequest() throws Exception
12898
// When
12999
lock.create(kc, record);
130100
// Then
131-
verify(configMaps.withName("name"), times(1)).create(any(ConfigMap.class));
101+
verify(kc.resource(any(ConfigMap.class))).create();
132102
}
133103

134104
@Test
135-
void updateWithValidLeaderElectionRecordShouldSendPutRequest() throws Exception {
105+
void updateWithValidLeaderElectionRecordShouldSendPatchRequest() throws Exception {
136106
// Given
137-
final Resource<ConfigMap> configMapResource = configMaps.withName("name");
138-
final ReplaceDeletable<ConfigMap> replaceable = mock(ReplaceDeletable.class, Answers.RETURNS_DEEP_STUBS);
139-
when(configMapResource.lockResourceVersion(any())).thenReturn(replaceable);
140-
final ConfigMap configMapInTheCluster = new ConfigMap();
141-
configMapInTheCluster.setMetadata(new ObjectMetaBuilder().withAnnotations(new HashMap<>()).build());
142-
when(configMapResource.get()).thenReturn(configMapInTheCluster);
143107
final LeaderElectionRecord record = new LeaderElectionRecord(
144108
"1337", Duration.ofSeconds(1), ZonedDateTime.now(), ZonedDateTime.now(), 0);
145109
record.setVersion("313373");
146110
final ConfigMapLock lock = new ConfigMapLock("namespace", "name", "1337");
147111
// When
148112
lock.update(kc, record);
149113
// Then
150-
verify(replaceable, times(1)).replace(eq(configMapInTheCluster));
114+
verify(kc.resource(any(ConfigMap.class))).patch(any(PatchContext.class));
151115
}
152116

153117
@Test

kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/extended/leaderelection/resourcelock/LeaseLockTest.java

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,11 @@
1818
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
1919
import io.fabric8.kubernetes.api.model.coordination.v1.Lease;
2020
import io.fabric8.kubernetes.api.model.coordination.v1.LeaseBuilder;
21-
import io.fabric8.kubernetes.api.model.coordination.v1.LeaseList;
22-
import io.fabric8.kubernetes.api.model.coordination.v1.LeaseSpec;
2321
import io.fabric8.kubernetes.client.KubernetesClient;
24-
import io.fabric8.kubernetes.client.dsl.MixedOperation;
25-
import io.fabric8.kubernetes.client.dsl.ReplaceDeletable;
26-
import io.fabric8.kubernetes.client.dsl.Resource;
22+
import io.fabric8.kubernetes.client.dsl.base.PatchContext;
2723
import org.junit.jupiter.api.BeforeEach;
2824
import org.junit.jupiter.api.Test;
2925
import org.junit.jupiter.api.function.Executable;
30-
import org.mockito.Answers;
3126

3227
import java.time.Duration;
3328
import java.time.ZoneId;
@@ -37,32 +32,17 @@
3732
import static org.junit.jupiter.api.Assertions.assertNotNull;
3833
import static org.junit.jupiter.api.Assertions.assertThrows;
3934
import static org.mockito.ArgumentMatchers.any;
40-
import static org.mockito.ArgumentMatchers.anyString;
41-
import static org.mockito.ArgumentMatchers.eq;
4235
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
4336
import static org.mockito.Mockito.mock;
44-
import static org.mockito.Mockito.times;
4537
import static org.mockito.Mockito.verify;
46-
import static org.mockito.Mockito.when;
4738

4839
class LeaseLockTest {
4940

5041
private KubernetesClient kc;
51-
private MixedOperation<Lease, LeaseList, Resource<Lease>> leases;
52-
private LeaseBuilder leaserBuilder;
53-
private LeaseBuilder.MetadataNested<LeaseBuilder> metadata;
54-
private LeaseBuilder.SpecNested<LeaseBuilder> spec;
5542

5643
@BeforeEach
5744
void setUp() {
5845
kc = mock(KubernetesClient.class, RETURNS_DEEP_STUBS);
59-
leases = mock(MixedOperation.class, RETURNS_DEEP_STUBS);
60-
leaserBuilder = mock(LeaseBuilder.class, RETURNS_DEEP_STUBS);
61-
metadata = mock(LeaseBuilder.MetadataNested.class, RETURNS_DEEP_STUBS);
62-
spec = mock(LeaseBuilder.SpecNested.class, RETURNS_DEEP_STUBS);
63-
when(kc.leases().inNamespace(anyString())).thenReturn(leases);
64-
when(leaserBuilder.withNewMetadata()).thenReturn(metadata);
65-
when(leaserBuilder.withNewSpec()).thenReturn(spec);
6646
}
6747

6848
@Test
@@ -99,11 +79,10 @@ void getWithExistingLeaseShouldReturnLeaderElectionRecord() {
9979
.withRenewTime(ZonedDateTime.of(2015, 10, 21, 7, 28, 0, 0, ZoneId.of("UTC")))
10080
.withLeaseTransitions(0)
10181
.endSpec().build();
102-
when(leases.withName(eq("name")).get()).thenReturn(lease);
10382
lease.setMetadata(new ObjectMetaBuilder().withResourceVersion("313373").build());
10483
final LeaseLock lock = new LeaseLock("namespace", "name", "1337");
10584
// When
106-
final LeaderElectionRecord result = lock.get(kc);
85+
final LeaderElectionRecord result = lock.toRecordInternal(lease);
10786
// Then
10887
assertNotNull(result);
10988
assertEquals("313373", result.getVersion());
@@ -121,27 +100,21 @@ void createWithValidLeaderElectionRecordShouldSendPostRequest() throws Exception
121100
// When
122101
lock.create(kc, record);
123102
// Then
124-
verify(leases.withName("name"), times(1)).create(any(Lease.class));
103+
verify(kc.resource(any(Lease.class))).create();
125104
}
126105

127106
@Test
128-
void updateWithValidLeaderElectionRecordShouldSendPutRequest() throws Exception {
107+
void updateWithValidLeaderElectionRecordShouldSendPatchRequest() throws Exception {
129108
// Given
130-
final Resource<Lease> leaseResource = leases.withName("name");
131-
final ReplaceDeletable<Lease> replaceable = mock(ReplaceDeletable.class, Answers.RETURNS_DEEP_STUBS);
132-
when(leaseResource.lockResourceVersion(any())).thenReturn(replaceable);
133-
final Lease leaseInTheCluster = new Lease();
134-
leaseInTheCluster.setSpec(new LeaseSpec());
135-
when(leaseResource.get()).thenReturn(leaseInTheCluster);
136109
final LeaderElectionRecord record = new LeaderElectionRecord(
137110
"1337", Duration.ofSeconds(1), ZonedDateTime.now(), ZonedDateTime.now(), 0);
138111
record.setVersion("313373");
139112
final LeaseLock lock = new LeaseLock("namespace", "name", "1337");
113+
Lease lease = lock.toResource(record, lock.getObjectMeta("313373"));
140114
// When
141115
lock.update(kc, record);
142116
// Then
143-
verify(replaceable, times(1)).replace(eq(leaseInTheCluster));
144-
assertEquals("1337", leaseInTheCluster.getSpec().getHolderIdentity());
117+
verify(kc.resource(lease)).patch(any(PatchContext.class));
145118
}
146119

147120
@Test

kubernetes-examples/src/main/java/io/fabric8/kubernetes/examples/LeaderElectionExamples.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ private LeaderElector leader(String id, Function<String, Lock> lockSupplier) {
186186
.withLeaderCallbacks(new LeaderCallbacks(
187187
() -> System.out.printf("\r%1$s: I just became leader!!!%n", id),
188188
() -> {
189-
leaderReference.updateAndGet(s -> id.equals(s)?null:s);
189+
leaderReference.updateAndGet(s -> id.equals(s) ? null : s);
190190
System.out.printf("\r%1$s: I just lost my leadership :(%n", id);
191191
},
192192
leaderReference::set))
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Copyright (C) 2015 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.fabric8.kubernetes;
18+
19+
import io.fabric8.junit.jupiter.api.RequireK8sSupport;
20+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
21+
import io.fabric8.kubernetes.api.model.coordination.v1.Lease;
22+
import io.fabric8.kubernetes.client.KubernetesClient;
23+
import io.fabric8.kubernetes.client.extended.leaderelection.LeaderCallbacks;
24+
import io.fabric8.kubernetes.client.extended.leaderelection.LeaderElectionConfigBuilder;
25+
import io.fabric8.kubernetes.client.extended.leaderelection.LeaderElector;
26+
import io.fabric8.kubernetes.client.extended.leaderelection.resourcelock.LeaseLock;
27+
import org.junit.jupiter.api.Test;
28+
29+
import java.time.Duration;
30+
import java.util.UUID;
31+
import java.util.concurrent.CompletableFuture;
32+
import java.util.concurrent.ExecutionException;
33+
import java.util.concurrent.TimeUnit;
34+
import java.util.concurrent.TimeoutException;
35+
36+
import static org.junit.jupiter.api.Assertions.assertEquals;
37+
import static org.junit.jupiter.api.Assertions.assertTrue;
38+
39+
@RequireK8sSupport(Lease.class)
40+
class LeaderIT {
41+
42+
KubernetesClient client;
43+
44+
@Test
45+
void testLeadershipCycle() throws InterruptedException, ExecutionException, TimeoutException {
46+
final String lockIdentity = UUID.randomUUID().toString();
47+
CompletableFuture<Void> leading = new CompletableFuture<>();
48+
CompletableFuture<Void> stopped = new CompletableFuture<>();
49+
CompletableFuture<String> changed = new CompletableFuture<>();
50+
LeaderElector leader = client.leaderElector()
51+
.withConfig(
52+
new LeaderElectionConfigBuilder()
53+
.withReleaseOnCancel()
54+
.withName("Sample Leader Election configuration")
55+
.withLeaseDuration(Duration.ofSeconds(30L))
56+
.withLock(new LeaseLock(new ObjectMetaBuilder().withName("sample").withNamespace(client.getNamespace()).build(),
57+
lockIdentity))
58+
.withRenewDeadline(Duration.ofSeconds(10L))
59+
.withRetryPeriod(Duration.ofSeconds(2L))
60+
.withLeaderCallbacks(new LeaderCallbacks(
61+
() -> leading.complete(null),
62+
() -> stopped.complete(null),
63+
changed::complete))
64+
.build())
65+
.build();
66+
CompletableFuture<?> f = leader.start();
67+
68+
leading.get(10, TimeUnit.SECONDS);
69+
assertTrue(!stopped.isDone());
70+
f.cancel(true);
71+
stopped.get(10, TimeUnit.SECONDS);
72+
assertEquals(lockIdentity, changed.getNow(null));
73+
}
74+
75+
}

0 commit comments

Comments
 (0)