Skip to content

Commit e772904

Browse files
committed
Test leaderelection code path with fake resource lock
1 parent 6a03836 commit e772904

File tree

3 files changed

+198
-71
lines changed

3 files changed

+198
-71
lines changed

pkg/leaderelection/fake/doc.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
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+
/*
18+
Package fake mocks a resource lock for testing purposes.
19+
Always returns leadership.
20+
*/
21+
package fake
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
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 fake
18+
19+
import (
20+
"os"
21+
"time"
22+
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/apimachinery/pkg/util/uuid"
25+
"k8s.io/client-go/rest"
26+
"k8s.io/client-go/tools/leaderelection/resourcelock"
27+
"sigs.k8s.io/controller-runtime/pkg/leaderelection"
28+
"sigs.k8s.io/controller-runtime/pkg/recorder"
29+
)
30+
31+
// NewResourceLock creates a new ResourceLock for use in testing
32+
// leader election.
33+
func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) {
34+
// Leader id, needs to be unique
35+
id, err := os.Hostname()
36+
if err != nil {
37+
return nil, err
38+
}
39+
id = id + "_" + string(uuid.NewUUID())
40+
41+
return &ResourceLock{
42+
id: id,
43+
record: resourcelock.LeaderElectionRecord{
44+
HolderIdentity: id,
45+
LeaseDurationSeconds: 15,
46+
AcquireTime: metav1.NewTime(time.Now()),
47+
RenewTime: metav1.NewTime(time.Now().Add(15 * time.Second)),
48+
LeaderTransitions: 1,
49+
},
50+
}, nil
51+
}
52+
53+
// ResourceLock implements the ResourceLockInterface.
54+
// By default returns that the current identity holds the lock.
55+
type ResourceLock struct {
56+
id string
57+
record resourcelock.LeaderElectionRecord
58+
}
59+
60+
// Get implements the ResourceLockInterface.
61+
func (f *ResourceLock) Get() (*resourcelock.LeaderElectionRecord, error) {
62+
return &f.record, nil
63+
}
64+
65+
// Create implements the ResourceLockInterface.
66+
func (f *ResourceLock) Create(ler resourcelock.LeaderElectionRecord) error {
67+
f.record = ler
68+
return nil
69+
}
70+
71+
// Update implements the ResourceLockInterface.
72+
func (f *ResourceLock) Update(ler resourcelock.LeaderElectionRecord) error {
73+
f.record = ler
74+
return nil
75+
}
76+
77+
// RecordEvent implements the ResourceLockInterface.
78+
func (f *ResourceLock) RecordEvent(s string) {
79+
return
80+
}
81+
82+
// Identity implements the ResourceLockInterface.
83+
func (f *ResourceLock) Identity() string {
84+
return f.id
85+
}
86+
87+
// Describe implements the ResourceLockInterface.
88+
func (f *ResourceLock) Describe() string {
89+
return f.id
90+
}

pkg/manager/manager_test.go

Lines changed: 87 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"sigs.k8s.io/controller-runtime/pkg/cache"
2929
"sigs.k8s.io/controller-runtime/pkg/cache/informertest"
3030
"sigs.k8s.io/controller-runtime/pkg/client"
31+
fakeleaderelection "sigs.k8s.io/controller-runtime/pkg/leaderelection/fake"
3132
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3233
"sigs.k8s.io/controller-runtime/pkg/recorder"
3334
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
@@ -114,88 +115,103 @@ var _ = Describe("manger.Manager", func() {
114115
})
115116

116117
Describe("Start", func() {
117-
It("should Start each Component", func(done Done) {
118-
m, err := New(cfg, Options{})
119-
Expect(err).NotTo(HaveOccurred())
120-
c1 := make(chan struct{})
121-
m.Add(RunnableFunc(func(s <-chan struct{}) error {
122-
defer close(c1)
123-
defer GinkgoRecover()
124-
return nil
125-
}))
118+
var startSuite = func(options Options) {
119+
It("should Start each Component", func(done Done) {
120+
m, err := New(cfg, options)
121+
Expect(err).NotTo(HaveOccurred())
122+
c1 := make(chan struct{})
123+
m.Add(RunnableFunc(func(s <-chan struct{}) error {
124+
defer close(c1)
125+
defer GinkgoRecover()
126+
return nil
127+
}))
126128

127-
c2 := make(chan struct{})
128-
m.Add(RunnableFunc(func(s <-chan struct{}) error {
129-
defer close(c2)
130-
defer GinkgoRecover()
131-
return nil
132-
}))
129+
c2 := make(chan struct{})
130+
m.Add(RunnableFunc(func(s <-chan struct{}) error {
131+
defer close(c2)
132+
defer GinkgoRecover()
133+
return nil
134+
}))
133135

134-
go func() {
135-
defer GinkgoRecover()
136-
Expect(m.Start(stop)).NotTo(HaveOccurred())
137-
}()
138-
<-c1
139-
<-c2
136+
go func() {
137+
defer GinkgoRecover()
138+
Expect(m.Start(stop)).NotTo(HaveOccurred())
139+
}()
140+
<-c1
141+
<-c2
140142

141-
close(done)
142-
})
143+
close(done)
144+
})
143145

144-
It("should stop when stop is called", func(done Done) {
145-
m, err := New(cfg, Options{})
146-
Expect(err).NotTo(HaveOccurred())
147-
s := make(chan struct{})
148-
close(s)
149-
Expect(m.Start(s)).NotTo(HaveOccurred())
146+
It("should stop when stop is called", func(done Done) {
147+
m, err := New(cfg, options)
148+
Expect(err).NotTo(HaveOccurred())
149+
s := make(chan struct{})
150+
close(s)
151+
Expect(m.Start(s)).NotTo(HaveOccurred())
150152

151-
close(done)
152-
})
153+
close(done)
154+
})
153155

154-
It("should return an error if it can't start the cache", func(done Done) {
155-
m, err := New(cfg, Options{})
156-
Expect(err).NotTo(HaveOccurred())
157-
mgr, ok := m.(*controllerManager)
158-
Expect(ok).To(BeTrue())
159-
mgr.startCache = func(stop <-chan struct{}) error {
160-
return fmt.Errorf("expected error")
161-
}
162-
Expect(m.Start(stop).Error()).To(ContainSubstring("expected error"))
156+
It("should return an error if it can't start the cache", func(done Done) {
157+
m, err := New(cfg, options)
158+
Expect(err).NotTo(HaveOccurred())
159+
mgr, ok := m.(*controllerManager)
160+
Expect(ok).To(BeTrue())
161+
mgr.startCache = func(stop <-chan struct{}) error {
162+
return fmt.Errorf("expected error")
163+
}
164+
Expect(m.Start(stop).Error()).To(ContainSubstring("expected error"))
163165

164-
close(done)
165-
})
166+
close(done)
167+
})
166168

167-
It("should return an error if any Components fail to Start", func(done Done) {
168-
m, err := New(cfg, Options{})
169-
Expect(err).NotTo(HaveOccurred())
170-
c1 := make(chan struct{})
171-
m.Add(RunnableFunc(func(s <-chan struct{}) error {
172-
defer GinkgoRecover()
173-
defer close(c1)
174-
return nil
175-
}))
169+
It("should return an error if any Components fail to Start", func(done Done) {
170+
m, err := New(cfg, options)
171+
Expect(err).NotTo(HaveOccurred())
172+
c1 := make(chan struct{})
173+
m.Add(RunnableFunc(func(s <-chan struct{}) error {
174+
defer GinkgoRecover()
175+
defer close(c1)
176+
return nil
177+
}))
176178

177-
c2 := make(chan struct{})
178-
m.Add(RunnableFunc(func(s <-chan struct{}) error {
179-
defer GinkgoRecover()
180-
defer close(c2)
181-
return fmt.Errorf("expected error")
182-
}))
179+
c2 := make(chan struct{})
180+
m.Add(RunnableFunc(func(s <-chan struct{}) error {
181+
defer GinkgoRecover()
182+
defer close(c2)
183+
return fmt.Errorf("expected error")
184+
}))
183185

184-
c3 := make(chan struct{})
185-
m.Add(RunnableFunc(func(s <-chan struct{}) error {
186-
defer GinkgoRecover()
187-
defer close(c3)
188-
return nil
189-
}))
186+
c3 := make(chan struct{})
187+
m.Add(RunnableFunc(func(s <-chan struct{}) error {
188+
defer GinkgoRecover()
189+
defer close(c3)
190+
return nil
191+
}))
190192

191-
go func() {
192-
defer GinkgoRecover()
193-
Expect(m.Start(stop)).NotTo(HaveOccurred())
194-
close(done)
195-
}()
196-
<-c1
197-
<-c2
198-
<-c3
193+
go func() {
194+
defer GinkgoRecover()
195+
Expect(m.Start(stop)).NotTo(HaveOccurred())
196+
close(done)
197+
}()
198+
<-c1
199+
<-c2
200+
<-c3
201+
})
202+
}
203+
204+
Context("with defaults", func() {
205+
startSuite(Options{})
206+
})
207+
208+
Context("with leaderelection enabled", func() {
209+
startSuite(Options{
210+
LeaderElection: true,
211+
LeaderElectionID: "controller-runtime",
212+
LeaderElectionNamespace: "default",
213+
newResourceLock: fakeleaderelection.NewResourceLock,
214+
})
199215
})
200216
})
201217

0 commit comments

Comments
 (0)