Skip to content

Commit 1d1e5af

Browse files
committed
Get Manager tests to 100% coverage
1 parent 4358f24 commit 1d1e5af

File tree

4 files changed

+224
-42
lines changed

4 files changed

+224
-42
lines changed

pkg/controller/controller.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ type controller struct {
109109
// defaults to cache.WaitForCacheSync
110110
waitForCache func(stopCh <-chan struct{}, cacheSyncs ...cache.InformerSynced) bool
111111

112+
// started is true if the Controller has been started
113+
started bool
114+
112115
// TODO(pwittrock): Consider initializing a logger with the controller name as the tag
113116
}
114117

@@ -178,6 +181,8 @@ func (c *controller) Start(stop <-chan struct{}) error {
178181
}, c.jitterPeriod, stop)
179182
}
180183

184+
c.started = true
185+
181186
<-stop
182187
log.Info("Stopping workers", "controller", c.name)
183188
return nil

pkg/controller/controller_suite_integration_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"testing"
2121
"time"
2222

23+
"github.com/kubernetes-sigs/controller-runtime/pkg/controller"
2324
"github.com/kubernetes-sigs/controller-runtime/pkg/internal/informer"
2425
logf "github.com/kubernetes-sigs/controller-runtime/pkg/runtime/log"
2526
"github.com/kubernetes-sigs/controller-runtime/pkg/test"
@@ -46,6 +47,7 @@ var _ = BeforeSuite(func() {
4647

4748
var err error
4849
cfg, err = testenv.Start()
50+
controller.TestConfig = cfg
4951
Expect(err).NotTo(HaveOccurred())
5052

5153
time.Sleep(1 * time.Second)

pkg/controller/manager.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ import (
2121
"sync"
2222

2323
"github.com/kubernetes-sigs/controller-runtime/pkg/client"
24-
"github.com/kubernetes-sigs/controller-runtime/pkg/client/config"
2524
"github.com/kubernetes-sigs/controller-runtime/pkg/controller/reconcile"
2625
"github.com/kubernetes-sigs/controller-runtime/pkg/internal/apiutil"
2726
"github.com/kubernetes-sigs/controller-runtime/pkg/internal/informer"
2827
"github.com/kubernetes-sigs/controller-runtime/pkg/runtime/inject"
2928
"k8s.io/api/apps/v1"
29+
"k8s.io/apimachinery/pkg/api/meta"
3030
"k8s.io/apimachinery/pkg/runtime"
3131
"k8s.io/client-go/kubernetes/scheme"
3232
"k8s.io/client-go/rest"
@@ -92,8 +92,12 @@ func (cm *controllerManager) NewController(ca Args, r reconcile.Reconcile) (Cont
9292
cm.mu.Lock()
9393
defer cm.mu.Unlock()
9494

95+
if r == nil {
96+
return nil, fmt.Errorf("must specify Reconcile")
97+
}
98+
9599
if len(ca.Name) == 0 {
96-
return nil, fmt.Errorf("must specify name for Controller")
100+
return nil, fmt.Errorf("must specify Name for Controller")
97101
}
98102

99103
if ca.MaxConcurrentReconciles <= 0 {
@@ -196,6 +200,9 @@ type ManagerArgs struct {
196200
// Scheme is the scheme used to resolve runtime.Objects to GroupVersionKinds / Resources
197201
// Defaults to the kubernetes/client-go scheme.Scheme
198202
Scheme *runtime.Scheme
203+
204+
// Mapper is the rest mapper used to map go types to Kubernetes APIs
205+
MapperProvider func(c *rest.Config) (meta.RESTMapper, error)
199206
}
200207

201208
// NewManager returns a new fully initialized Manager.
@@ -204,11 +211,7 @@ func NewManager(args ManagerArgs) (Manager, error) {
204211

205212
// Initialize a rest.config if none was specified
206213
if cm.config == nil {
207-
var err error
208-
cm.config, err = config.GetConfig()
209-
if err != nil {
210-
return nil, err
211-
}
214+
return nil, fmt.Errorf("must specify Config")
212215
}
213216

214217
// Use the Kubernetes client-go scheme if none is specified
@@ -228,7 +231,10 @@ func NewManager(args ManagerArgs) (Manager, error) {
228231
objCache := client.NewObjectCache(spi.KnownInformersByType(), cm.scheme)
229232
spi.Callbacks = append(spi.Callbacks, objCache)
230233

231-
mapper, err := apiutil.NewDiscoveryRESTMapper(cm.config)
234+
if args.MapperProvider == nil {
235+
args.MapperProvider = apiutil.NewDiscoveryRESTMapper
236+
}
237+
mapper, err := args.MapperProvider(cm.config)
232238
if err != nil {
233239
log.Error(err, "Failed to get API Group-Resources")
234240
return nil, err

pkg/controller/manager_test.go

Lines changed: 203 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,91 +17,260 @@ limitations under the License.
1717
package controller
1818

1919
import (
20+
"fmt"
21+
22+
"github.com/kubernetes-sigs/controller-runtime/pkg/client"
23+
"github.com/kubernetes-sigs/controller-runtime/pkg/controller/reconcile"
24+
"github.com/kubernetes-sigs/controller-runtime/pkg/internal/informer"
25+
"github.com/kubernetes-sigs/controller-runtime/pkg/internal/informer/informertest"
2026
. "github.com/onsi/ginkgo"
27+
. "github.com/onsi/gomega"
28+
"k8s.io/apimachinery/pkg/api/meta"
29+
"k8s.io/apimachinery/pkg/runtime"
30+
"k8s.io/client-go/rest"
31+
"k8s.io/client-go/tools/cache"
2132
)
2233

34+
var TestConfig *rest.Config
35+
2336
var _ = Describe("controller", func() {
37+
var stop chan struct{}
38+
39+
rec := reconcile.Func(func(reconcile.Request) (reconcile.Result, error) {
40+
return reconcile.Result{}, nil
41+
})
2442
BeforeEach(func() {
43+
stop = make(chan struct{})
2544
})
2645

2746
AfterEach(func() {
47+
close(stop)
2848
})
2949

3050
Describe("Creating a Manager", func() {
3151

32-
It("should return an error if Name is not Specified", func() {
52+
It("should return an error if there is no Config", func() {
53+
m, err := NewManager(ManagerArgs{})
54+
Expect(m).To(BeNil())
55+
Expect(err.Error()).To(ContainSubstring("must specify Config"))
3356

3457
})
3558

3659
It("should return an error if it can't create a RestMapper", func() {
37-
38-
})
39-
40-
It("should return an error if it cannot get a Config", func() {
41-
42-
})
43-
44-
It("should default the Config if none is specified", func() {
60+
expected := fmt.Errorf("expected error: RestMapper")
61+
m, err := NewManager(ManagerArgs{
62+
Config: TestConfig,
63+
MapperProvider: func(c *rest.Config) (meta.RESTMapper, error) { return nil, expected },
64+
})
65+
Expect(m).To(BeNil())
66+
Expect(err).To(Equal(expected))
4567

4668
})
4769
})
4870

4971
Describe("Staring a Manager", func() {
5072

5173
It("should Start each Controller", func() {
52-
74+
// TODO(community): write this
5375
})
5476

55-
It("should return an error if any Controllers fail to stop", func() {
56-
77+
It("should return an error if any Controllers fail to stop", func(done Done) {
78+
m, err := NewManager(ManagerArgs{Config: TestConfig})
79+
Expect(err).NotTo(HaveOccurred())
80+
c, err := m.NewController(Args{Name: "foo"}, rec)
81+
Expect(err).NotTo(HaveOccurred())
82+
ctrl, ok := c.(*controller)
83+
Expect(ok).To(BeTrue())
84+
85+
// Make Controller startup fail
86+
ctrl.waitForCache = func(stopCh <-chan struct{}, cacheSyncs ...cache.InformerSynced) bool { return false }
87+
err = m.Start(stop)
88+
Expect(err).To(HaveOccurred())
89+
Expect(err.Error()).To(ContainSubstring("caches to sync"))
90+
91+
close(done)
5792
})
5893
})
5994

6095
Describe("Manager", func() {
6196
It("should provide a function to get the Config", func() {
62-
97+
m, err := NewManager(ManagerArgs{Config: TestConfig})
98+
Expect(err).NotTo(HaveOccurred())
99+
mrg, ok := m.(*controllerManager)
100+
Expect(ok).To(BeTrue())
101+
Expect(m.GetConfig()).To(Equal(mrg.config))
63102
})
64103

65104
It("should provide a function to get the Client", func() {
66-
105+
m, err := NewManager(ManagerArgs{Config: TestConfig})
106+
Expect(err).NotTo(HaveOccurred())
107+
mrg, ok := m.(*controllerManager)
108+
Expect(ok).To(BeTrue())
109+
Expect(m.GetClient()).To(Equal(mrg.client))
67110
})
68111

69112
It("should provide a function to get the Scheme", func() {
70-
113+
m, err := NewManager(ManagerArgs{Config: TestConfig})
114+
Expect(err).NotTo(HaveOccurred())
115+
mrg, ok := m.(*controllerManager)
116+
Expect(ok).To(BeTrue())
117+
Expect(m.GetScheme()).To(Equal(mrg.scheme))
71118
})
72119

73120
It("should provide a function to get the FieldIndexer", func() {
74-
75-
})
76-
})
77-
78-
Describe("Creating a Controller", func() {
79-
80-
It("should immediately start the Controller if the ControllerManager has already Started", func() {
81-
82-
})
83-
84-
It("should provide an inject function for providing dependencies", func() {
85-
121+
m, err := NewManager(ManagerArgs{Config: TestConfig})
122+
Expect(err).NotTo(HaveOccurred())
123+
mrg, ok := m.(*controllerManager)
124+
Expect(ok).To(BeTrue())
125+
Expect(m.GetFieldIndexer()).To(Equal(mrg.fieldIndexes))
86126
})
87127
})
88128

89129
Describe("Creating a Controller", func() {
90130
It("should return an error if Name is not Specified", func() {
91-
92-
})
93-
94-
It("should return an error if it cannot get a Config", func() {
95-
131+
m, err := NewManager(ManagerArgs{Config: TestConfig})
132+
Expect(err).NotTo(HaveOccurred())
133+
c, err := m.NewController(Args{}, rec)
134+
Expect(c).To(BeNil())
135+
Expect(err.Error()).To(ContainSubstring("must specify Name for Controller"))
96136
})
97137

98-
It("should default the Config if none is specified", func() {
138+
It("should return an error if Reconcile is not Specified", func() {
139+
m, err := NewManager(ManagerArgs{Config: TestConfig})
140+
Expect(err).NotTo(HaveOccurred())
141+
c, err := m.NewController(Args{Name: "foo"}, nil)
142+
Expect(c).To(BeNil())
143+
Expect(err.Error()).To(ContainSubstring("must specify Reconcile"))
99144

100145
})
101146

102147
It("should immediately start the Controller if the ControllerManager has already Started", func() {
103-
148+
m, err := NewManager(ManagerArgs{Config: TestConfig})
149+
Expect(err).NotTo(HaveOccurred())
150+
mrg, ok := m.(*controllerManager)
151+
Expect(ok).To(BeTrue())
152+
153+
// Make Controller startup fail
154+
go func() {
155+
defer GinkgoRecover()
156+
Expect(m.Start(stop)).NotTo(HaveOccurred())
157+
}()
158+
Eventually(func() bool { return mrg.started }).Should(BeTrue())
159+
160+
c, err := m.NewController(Args{Name: "Foo"}, rec)
161+
Expect(err).NotTo(HaveOccurred())
162+
ctrl, ok := c.(*controller)
163+
Expect(ok).To(BeTrue())
164+
165+
// Wait for Controller to start
166+
Eventually(func() bool { return ctrl.started }).Should(BeTrue())
104167
})
105168

169+
It("should provide an inject function for providing dependencies", func(done Done) {
170+
m, err := NewManager(ManagerArgs{Config: TestConfig})
171+
Expect(err).NotTo(HaveOccurred())
172+
mrg, ok := m.(*controllerManager)
173+
Expect(ok).To(BeTrue())
174+
175+
mrg.informers = &informertest.FakeInformers{}
176+
177+
c, err := m.NewController(Args{Name: "foo"}, rec)
178+
Expect(err).NotTo(HaveOccurred())
179+
ctrl, ok := c.(*controller)
180+
Expect(ok).To(BeTrue())
181+
182+
By("Injecting the dependencies")
183+
err = ctrl.inject(&injectable{
184+
scheme: func(scheme *runtime.Scheme) error {
185+
defer GinkgoRecover()
186+
Expect(scheme).To(Equal(mrg.scheme))
187+
return nil
188+
},
189+
config: func(config *rest.Config) error {
190+
defer GinkgoRecover()
191+
Expect(config).To(Equal(mrg.config))
192+
return nil
193+
},
194+
client: func(client client.Interface) error {
195+
defer GinkgoRecover()
196+
Expect(client).To(Equal(mrg.client))
197+
return nil
198+
},
199+
informers: func(informers informer.Informers) error {
200+
defer GinkgoRecover()
201+
Expect(informers).To(Equal(mrg.informers))
202+
return nil
203+
},
204+
})
205+
Expect(err).NotTo(HaveOccurred())
206+
207+
By("Returning an error if dependency injection fails")
208+
209+
expected := fmt.Errorf("expected error")
210+
err = ctrl.inject(&injectable{
211+
client: func(client client.Interface) error {
212+
return expected
213+
},
214+
})
215+
Expect(err).To(Equal(expected))
216+
217+
err = ctrl.inject(&injectable{
218+
scheme: func(scheme *runtime.Scheme) error {
219+
return expected
220+
},
221+
})
222+
Expect(err).To(Equal(expected))
223+
224+
err = ctrl.inject(&injectable{
225+
config: func(config *rest.Config) error {
226+
return expected
227+
},
228+
})
229+
Expect(err).To(Equal(expected))
230+
231+
err = ctrl.inject(&injectable{
232+
informers: func(informers informer.Informers) error {
233+
return expected
234+
},
235+
})
236+
Expect(err).To(Equal(expected))
237+
238+
close(done)
239+
})
106240
})
107241
})
242+
243+
type injectable struct {
244+
scheme func(scheme *runtime.Scheme) error
245+
client func(client.Interface) error
246+
config func(config *rest.Config) error
247+
informers func(informer.Informers) error
248+
}
249+
250+
func (i *injectable) InjectInformers(informers informer.Informers) error {
251+
if i.informers == nil {
252+
return nil
253+
}
254+
return i.informers(informers)
255+
}
256+
257+
func (i *injectable) InjectConfig(config *rest.Config) error {
258+
if i.config == nil {
259+
return nil
260+
}
261+
return i.config(config)
262+
}
263+
264+
func (i *injectable) InjectClient(c client.Interface) error {
265+
if i.client == nil {
266+
return nil
267+
}
268+
return i.client(c)
269+
}
270+
271+
func (i *injectable) InjectScheme(scheme *runtime.Scheme) error {
272+
if i.scheme == nil {
273+
return nil
274+
}
275+
return i.scheme(scheme)
276+
}

0 commit comments

Comments
 (0)