Skip to content

Commit 4797b08

Browse files
committed
leader to give up leadership lock on graceful shutdown
1 parent a3e5a32 commit 4797b08

File tree

4 files changed

+94
-18
lines changed

4 files changed

+94
-18
lines changed

extended/src/main/java/io/kubernetes/client/extended/leaderelection/LeaderElector.java

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -262,21 +262,44 @@ private boolean tryAcquireOrRenew() {
262262
return false;
263263
}
264264

265+
if (log.isDebugEnabled()) {
266+
log.debug("Lock not found, try to create it");
267+
}
268+
269+
// No Lock resource exists, try to get leadership by creating it
265270
return createLock(lock, leaderElectionRecord);
266271
}
267272

273+
// alright, we have an existing lock resource
274+
// 1. Is Lock Empty? --> try to get leadership by updating it
275+
// 2. Am I the Leader? --> update info and renew lease by updating it
276+
// 3. I am not the Leader?
277+
// 3.1 is Lock expired? --> try to get leadership by updating it
278+
// 3.2 Lock not expired? --> update info, try later
279+
268280
if (oldLeaderElectionRecord == null
269281
|| oldLeaderElectionRecord.getAcquireTime() == null
270282
|| oldLeaderElectionRecord.getRenewTime() == null
271283
|| oldLeaderElectionRecord.getHolderIdentity() == null) {
272-
return createLock(lock, leaderElectionRecord);
284+
// We found the lock resource with an empty LeaderElectionRecord, try to get leadership by updating it
285+
if (log.isDebugEnabled()) {
286+
log.debug("Update lock to get lease");
287+
}
288+
289+
if (oldLeaderElectionRecord != null) {
290+
// maintain the leaderTransitions
291+
leaderElectionRecord.setLeaderTransitions(oldLeaderElectionRecord.getLeaderTransitions() + 1);
292+
}
293+
294+
return updateLock(lock, leaderElectionRecord);
273295
}
274296

275-
// 2. Record obtained, check the Identity & Time
297+
// 2. Record obtained with LeaderElectionRecord, check the Identity & Time
276298
if (!oldLeaderElectionRecord.equals(this.observedRecord)) {
277299
this.observedRecord = oldLeaderElectionRecord;
278300
this.observedTimeMilliSeconds = System.currentTimeMillis();
279301
}
302+
280303
if (observedTimeMilliSeconds + config.getLeaseDuration().toMillis() > now.getTime()
281304
&& !isLeader()) {
282305
if (log.isDebugEnabled()) {
@@ -296,26 +319,20 @@ private boolean tryAcquireOrRenew() {
296319
leaderElectionRecord.setLeaderTransitions(oldLeaderElectionRecord.getLeaderTransitions() + 1);
297320
}
298321

299-
// update the lock itself
300322
if (log.isDebugEnabled()) {
301-
log.debug("Update lock acquire time to keep lease");
323+
log.debug("Update lock to renew lease");
302324
}
303-
boolean updateSuccess = config.getLock().update(leaderElectionRecord);
304-
if (!updateSuccess) {
305-
return false;
306-
}
307-
this.observedRecord = leaderElectionRecord;
308-
this.observedTimeMilliSeconds = System.currentTimeMillis();
309-
if (log.isDebugEnabled()) {
325+
326+
boolean renewalStatus = updateLock(lock, leaderElectionRecord);
327+
328+
if (renewalStatus && log.isDebugEnabled()) {
310329
log.debug("TryAcquireOrRenew return success");
311330
}
312-
return true;
331+
332+
return renewalStatus;
313333
}
314334

315335
private boolean createLock(Lock lock, LeaderElectionRecord leaderElectionRecord) {
316-
if (log.isDebugEnabled()) {
317-
log.debug("Lock not found, try to create it");
318-
}
319336
boolean createSuccess = lock.create(leaderElectionRecord);
320337
if (!createSuccess) {
321338
return false;
@@ -325,6 +342,16 @@ private boolean createLock(Lock lock, LeaderElectionRecord leaderElectionRecord)
325342
return true;
326343
}
327344

345+
private boolean updateLock(Lock lock, LeaderElectionRecord leaderElectionRecord) {
346+
boolean updateSuccess = lock.update(leaderElectionRecord);
347+
if (!updateSuccess) {
348+
return false;
349+
}
350+
this.observedRecord = leaderElectionRecord;
351+
this.observedTimeMilliSeconds = System.currentTimeMillis();
352+
return true;
353+
}
354+
328355
private boolean isLeader() {
329356
return this.config.getLock().identity().equals(this.observedRecord.getHolderIdentity());
330357
}
@@ -345,8 +372,49 @@ private void maybeReportTransition() {
345372

346373
@Override
347374
public void close() {
375+
log.info("Closing...");
348376
scheduledWorkers.shutdownNow();
349377
leaseWorkers.shutdownNow();
350378
hookWorkers.shutdownNow();
379+
380+
// If I am the leader, free the lock so that other candidates can take it immediately
381+
if (observedRecord != null && isLeader()) {
382+
383+
// First ensure that all executors have stopped
384+
try {
385+
boolean isTerminated = scheduledWorkers.awaitTermination(config.getRetryPeriod().getSeconds(), TimeUnit.SECONDS);
386+
if (!isTerminated) {
387+
log.warn("scheduledWorkers executor termination didn't finish.");
388+
return;
389+
}
390+
391+
isTerminated = leaseWorkers.awaitTermination(config.getRetryPeriod().getSeconds(), TimeUnit.SECONDS);
392+
if (!isTerminated) {
393+
log.warn("leaseWorkers executor termination didn't finish.");
394+
return;
395+
}
396+
397+
isTerminated = hookWorkers.awaitTermination(config.getRetryPeriod().getSeconds(), TimeUnit.SECONDS);
398+
if (!isTerminated) {
399+
log.warn("hookWorkers executor termination didn't finish.");
400+
return;
401+
}
402+
} catch (InterruptedException ex) {
403+
log.warn("Failed to ensure executors termination.", ex);
404+
return;
405+
}
406+
407+
log.info("Giving up the lock....");
408+
LeaderElectionRecord emptyRecord = new LeaderElectionRecord();
409+
// maintain leaderTransitions count
410+
emptyRecord.setLeaderTransitions(observedRecord.getLeaderTransitions());
411+
boolean status = this.config.getLock().update(emptyRecord);
412+
413+
if (!status) {
414+
log.warn("Failed to give up the lock.");
415+
}
416+
}
417+
418+
log.info("Closed");
351419
}
352420
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public ConfigMapLock(String namespace, String name, String identity, ApiClient a
5959
@Override
6060
public LeaderElectionRecord get() throws ApiException {
6161
V1ConfigMap configMap = coreV1Client.readNamespacedConfigMap(name, namespace, null, null, null);
62+
configMapRefer.set(configMap);
63+
6264
Map<String, String> annotations = configMap.getMetadata().getAnnotations();
6365
if (annotations == null || annotations.isEmpty()) {
6466
configMap.getMetadata().setAnnotations(new HashMap<>());
@@ -74,7 +76,7 @@ public LeaderElectionRecord get() throws ApiException {
7476
.getApiClient()
7577
.getJSON()
7678
.deserialize(recordRawStringContent, LeaderElectionRecord.class);
77-
configMapRefer.set(configMap);
79+
7880
return record;
7981
}
8082

extended/src/main/java/io/kubernetes/client/extended/leaderelection/resourcelock/EndpointsLock.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public EndpointsLock(String namespace, String name, String identity, ApiClient a
5959
@Override
6060
public LeaderElectionRecord get() throws ApiException {
6161
V1Endpoints endpoints = coreV1Client.readNamespacedEndpoints(name, namespace, null, null, null);
62+
endpointsRefer.set(endpoints);
63+
6264
Map<String, String> annotations = endpoints.getMetadata().getAnnotations();
6365
if (annotations == null || annotations.isEmpty()) {
6466
endpoints.getMetadata().setAnnotations(new HashMap<>());
@@ -74,7 +76,6 @@ public LeaderElectionRecord get() throws ApiException {
7476
.getApiClient()
7577
.getJSON()
7678
.deserialize(recordRawStringContent, LeaderElectionRecord.class);
77-
endpointsRefer.set(endpoints);
7879
return record;
7980
}
8081

extended/src/test/java/io/kubernetes/client/extended/leaderelection/LeaderElectionTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import static org.mockito.Mockito.*;
1818

1919
import io.kubernetes.client.openapi.ApiException;
20+
21+
import java.net.HttpURLConnection;
2022
import java.time.Duration;
2123
import java.util.ArrayList;
2224
import java.util.Date;
@@ -412,9 +414,12 @@ public MockResourceLock(String iden) {
412414
}
413415

414416
@Override
415-
public LeaderElectionRecord get() {
417+
public LeaderElectionRecord get() throws ApiException {
416418
lock.lock();
417419
try {
420+
if (leaderRecord == null) {
421+
throw new ApiException("Record Not Found", HttpURLConnection.HTTP_NOT_FOUND, null, null);
422+
}
418423
return leaderRecord;
419424
} finally {
420425
lock.unlock();

0 commit comments

Comments
 (0)