Skip to content

Commit 8079d67

Browse files
committed
UT for leader graceful shutdown giving up leadership lock
1 parent 4797b08 commit 8079d67

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.kubernetes.client.extended.leaderelection;
15+
16+
import org.junit.Test;
17+
18+
import java.time.Duration;
19+
import java.util.concurrent.CountDownLatch;
20+
import java.util.concurrent.TimeUnit;
21+
22+
/**
23+
* Leader Election tests using "simulated" locks created by {@link LockSmith}
24+
*/
25+
public class LeaderElectorTest
26+
{
27+
/**
28+
* Tests that when a leader candidate is stopped gracefully, second candidate immediately becomes
29+
* leader.
30+
*/
31+
@Test(timeout = 20000L)
32+
public void testLeaderGracefulShutdown() throws Exception
33+
{
34+
LockSmith lockSmith = new LockSmith();
35+
36+
CountDownLatch startBeingLeader1 = new CountDownLatch(1);
37+
CountDownLatch stopBeingLeader1 = new CountDownLatch(1);
38+
39+
LeaderElector leaderElector1 = makeAndRunLeaderElectorAsync(lockSmith, "candidate1", startBeingLeader1, stopBeingLeader1);
40+
41+
// wait for candidate1 to become leader
42+
startBeingLeader1.await();
43+
44+
CountDownLatch startBeingLeader2 = new CountDownLatch(1);
45+
CountDownLatch stopBeingLeader2 = new CountDownLatch(1);
46+
47+
LeaderElector leaderElector2 = makeAndRunLeaderElectorAsync(lockSmith, "candidate2", startBeingLeader2, stopBeingLeader2);
48+
49+
leaderElector1.close();
50+
51+
// ensure stopBeingLeader hook is called
52+
stopBeingLeader1.await();
53+
54+
// wait for candidate2 to become leader
55+
startBeingLeader2.await();
56+
57+
leaderElector2.close();
58+
}
59+
60+
private LeaderElector makeAndRunLeaderElectorAsync(LockSmith lockSmith, String lockIdentity, CountDownLatch startBeingLeader, CountDownLatch stopBeingLeader)
61+
{
62+
LeaderElectionConfig leaderElectionConfig =
63+
new LeaderElectionConfig(
64+
lockSmith.makeLock(lockIdentity),
65+
Duration.ofMillis(TimeUnit.MINUTES.toMillis(1)),
66+
Duration.ofMillis(TimeUnit.SECONDS.toMillis(51)),
67+
Duration.ofMillis(TimeUnit.SECONDS.toMillis(3))
68+
);
69+
LeaderElector leaderElector = new LeaderElector(leaderElectionConfig);
70+
71+
Thread thread = new Thread(
72+
() -> leaderElector.run(
73+
() -> startBeingLeader.countDown(), () -> stopBeingLeader.countDown()
74+
),
75+
String.format("%s-leader-elector-main", lockIdentity)
76+
);
77+
thread.setDaemon(true);
78+
thread.start();
79+
80+
return leaderElector;
81+
}
82+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.kubernetes.client.extended.leaderelection;
15+
16+
import io.kubernetes.client.openapi.ApiException;
17+
18+
import java.net.HttpURLConnection;
19+
import java.util.concurrent.atomic.AtomicReference;
20+
21+
/**
22+
* Makes simulated {@link Lock} objects that behave as if they were backed by real API server.
23+
*/
24+
public class LockSmith
25+
{
26+
private AtomicReference<Resource> lockResourceRef = new AtomicReference<>();
27+
28+
public Lock makeLock(String identity)
29+
{
30+
return new SimulatedLock(identity);
31+
}
32+
33+
private class SimulatedLock implements Lock
34+
{
35+
private final String identity;
36+
37+
public SimulatedLock(String identity)
38+
{
39+
this.identity = identity;
40+
}
41+
42+
@Override
43+
public LeaderElectionRecord get() throws ApiException
44+
{
45+
if (lockResourceRef.get() == null) {
46+
throw new ApiException("Record Not Found", HttpURLConnection.HTTP_NOT_FOUND, null, null);
47+
}
48+
49+
return lockResourceRef.get().record;
50+
}
51+
52+
@Override
53+
public boolean create(LeaderElectionRecord record)
54+
{
55+
return lockResourceRef.compareAndSet(null, new Resource(record));
56+
}
57+
58+
@Override
59+
public boolean update(LeaderElectionRecord record)
60+
{
61+
Resource res = lockResourceRef.get();
62+
if (res == null) {
63+
return false;
64+
} else {
65+
Resource newResource = new Resource(res.version + 1, record);
66+
return lockResourceRef.compareAndSet(res, newResource);
67+
}
68+
}
69+
70+
@Override
71+
public String identity()
72+
{
73+
return identity;
74+
}
75+
76+
@Override
77+
public String describe()
78+
{
79+
return "simulated/lock";
80+
}
81+
}
82+
83+
private static class Resource
84+
{
85+
final int version;
86+
final LeaderElectionRecord record;
87+
88+
public Resource(LeaderElectionRecord record)
89+
{
90+
this.version = 0;
91+
this.record = record;
92+
}
93+
94+
public Resource(int version, LeaderElectionRecord record)
95+
{
96+
this.version = version;
97+
this.record = record;
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)