Skip to content

Commit 8bc8b11

Browse files
authored
Merge pull request kubernetes#95939 from dprotaso/leaderelection-release
Address scenario where releasing a resource lock fails if a prior update fails or gets cancelled
2 parents d4771b9 + 5e7ed7b commit 8bc8b11

File tree

4 files changed

+146
-7
lines changed

4 files changed

+146
-7
lines changed

staging/src/k8s.io/client-go/tools/leaderelection/leaderelection_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,3 +1072,129 @@ func TestReleaseLeaseConfigMaps(t *testing.T) {
10721072
func TestReleaseLeaseLeases(t *testing.T) {
10731073
testReleaseLease(t, "leases")
10741074
}
1075+
1076+
func TestReleaseOnCancellation_Endpoints(t *testing.T) {
1077+
testReleaseOnCancellation(t, "endpoints")
1078+
}
1079+
1080+
func TestReleaseOnCancellation_ConfigMaps(t *testing.T) {
1081+
testReleaseOnCancellation(t, "configmaps")
1082+
}
1083+
1084+
func TestReleaseOnCancellation_Leases(t *testing.T) {
1085+
testReleaseOnCancellation(t, "leases")
1086+
}
1087+
1088+
func testReleaseOnCancellation(t *testing.T, objectType string) {
1089+
var (
1090+
onNewLeader = make(chan struct{})
1091+
onRenewCalled = make(chan struct{})
1092+
onRenewResume = make(chan struct{})
1093+
onRelease = make(chan struct{})
1094+
1095+
lockObj runtime.Object
1096+
updates int
1097+
)
1098+
1099+
resourceLockConfig := rl.ResourceLockConfig{
1100+
Identity: "baz",
1101+
EventRecorder: &record.FakeRecorder{},
1102+
}
1103+
c := &fake.Clientset{}
1104+
1105+
c.AddReactor("get", objectType, func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
1106+
if lockObj != nil {
1107+
return true, lockObj, nil
1108+
}
1109+
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
1110+
})
1111+
1112+
// create lock
1113+
c.AddReactor("create", objectType, func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
1114+
lockObj = action.(fakeclient.CreateAction).GetObject()
1115+
return true, lockObj, nil
1116+
})
1117+
1118+
c.AddReactor("update", objectType, func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
1119+
updates++
1120+
1121+
// Second update (first renew) should return our canceled error
1122+
// FakeClient doesn't do anything with the context so we're doing this ourselves
1123+
if updates == 2 {
1124+
close(onRenewCalled)
1125+
<-onRenewResume
1126+
return true, nil, context.Canceled
1127+
} else if updates == 3 {
1128+
close(onRelease)
1129+
}
1130+
1131+
lockObj = action.(fakeclient.UpdateAction).GetObject()
1132+
return true, lockObj, nil
1133+
1134+
})
1135+
1136+
c.AddReactor("*", "*", func(action fakeclient.Action) (bool, runtime.Object, error) {
1137+
t.Errorf("unreachable action. testclient called too many times: %+v", action)
1138+
return true, nil, fmt.Errorf("unreachable action")
1139+
})
1140+
1141+
lock, err := rl.New(objectType, "foo", "bar", c.CoreV1(), c.CoordinationV1(), resourceLockConfig)
1142+
if err != nil {
1143+
t.Fatal("resourcelock.New() = ", err)
1144+
}
1145+
1146+
lec := LeaderElectionConfig{
1147+
Lock: lock,
1148+
LeaseDuration: 15 * time.Second,
1149+
RenewDeadline: 2 * time.Second,
1150+
RetryPeriod: 1 * time.Second,
1151+
1152+
// This is what we're testing
1153+
ReleaseOnCancel: true,
1154+
1155+
Callbacks: LeaderCallbacks{
1156+
OnNewLeader: func(identity string) {},
1157+
OnStoppedLeading: func() {},
1158+
OnStartedLeading: func(context.Context) {
1159+
close(onNewLeader)
1160+
},
1161+
},
1162+
}
1163+
1164+
elector, err := NewLeaderElector(lec)
1165+
if err != nil {
1166+
t.Fatal("Failed to create leader elector: ", err)
1167+
}
1168+
1169+
ctx, cancel := context.WithCancel(context.Background())
1170+
1171+
go elector.Run(ctx)
1172+
1173+
// Wait for us to become the leader
1174+
select {
1175+
case <-onNewLeader:
1176+
case <-time.After(10 * time.Second):
1177+
t.Fatal("failed to become the leader")
1178+
}
1179+
1180+
// Wait for renew (update) to be invoked
1181+
select {
1182+
case <-onRenewCalled:
1183+
case <-time.After(10 * time.Second):
1184+
t.Fatal("the elector failed to renew the lock")
1185+
}
1186+
1187+
// Cancel the context - stopping the elector while
1188+
// it's running
1189+
cancel()
1190+
1191+
// Resume the update call to return the cancellation
1192+
// which should trigger the release flow
1193+
close(onRenewResume)
1194+
1195+
select {
1196+
case <-onRelease:
1197+
case <-time.After(10 * time.Second):
1198+
t.Fatal("the lock was not released")
1199+
}
1200+
}

staging/src/k8s.io/client-go/tools/leaderelection/resourcelock/configmaplock.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,12 @@ func (cml *ConfigMapLock) Update(ctx context.Context, ler LeaderElectionRecord)
9393
cml.cm.Annotations = make(map[string]string)
9494
}
9595
cml.cm.Annotations[LeaderElectionRecordAnnotationKey] = string(recordBytes)
96-
cml.cm, err = cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Update(ctx, cml.cm, metav1.UpdateOptions{})
97-
return err
96+
cm, err := cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Update(ctx, cml.cm, metav1.UpdateOptions{})
97+
if err != nil {
98+
return err
99+
}
100+
cml.cm = cm
101+
return nil
98102
}
99103

100104
// RecordEvent in leader election while adding meta-data

staging/src/k8s.io/client-go/tools/leaderelection/resourcelock/endpointslock.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,12 @@ func (el *EndpointsLock) Update(ctx context.Context, ler LeaderElectionRecord) e
8888
el.e.Annotations = make(map[string]string)
8989
}
9090
el.e.Annotations[LeaderElectionRecordAnnotationKey] = string(recordBytes)
91-
el.e, err = el.Client.Endpoints(el.EndpointsMeta.Namespace).Update(ctx, el.e, metav1.UpdateOptions{})
92-
return err
91+
e, err := el.Client.Endpoints(el.EndpointsMeta.Namespace).Update(ctx, el.e, metav1.UpdateOptions{})
92+
if err != nil {
93+
return err
94+
}
95+
el.e = e
96+
return nil
9397
}
9498

9599
// RecordEvent in leader election while adding meta-data

staging/src/k8s.io/client-go/tools/leaderelection/resourcelock/leaselock.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,14 @@ func (ll *LeaseLock) Update(ctx context.Context, ler LeaderElectionRecord) error
7171
return errors.New("lease not initialized, call get or create first")
7272
}
7373
ll.lease.Spec = LeaderElectionRecordToLeaseSpec(&ler)
74-
var err error
75-
ll.lease, err = ll.Client.Leases(ll.LeaseMeta.Namespace).Update(ctx, ll.lease, metav1.UpdateOptions{})
76-
return err
74+
75+
lease, err := ll.Client.Leases(ll.LeaseMeta.Namespace).Update(ctx, ll.lease, metav1.UpdateOptions{})
76+
if err != nil {
77+
return err
78+
}
79+
80+
ll.lease = lease
81+
return nil
7782
}
7883

7984
// RecordEvent in leader election while adding meta-data

0 commit comments

Comments
 (0)