-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
hooks.go
161 lines (140 loc) · 4.97 KB
/
hooks.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*
Copyright 2018 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package testing includes utilities for testing controllers.
package testing
import (
"fmt"
"sync/atomic"
"time"
"k8s.io/apimachinery/pkg/runtime"
kubetesting "k8s.io/client-go/testing"
)
// HookResult is the return value of hook functions.
type HookResult bool
const (
// HookComplete indicates the hook function completed, and WaitForHooks should
// not wait for it.
HookComplete HookResult = true
// HookIncomplete indicates the hook function is incomplete, and WaitForHooks
// should wait for it to complete.
HookIncomplete HookResult = false
)
/*
CreateHookFunc is a function for handling a Create hook. Its runtime.Object
parameter will be the Kubernetes resource created. The resource can be cast
to its actual type like this:
pod := obj.(*v1.Pod)
A return value of true marks the hook as completed. Returning false allows
the hook to run again when the next resource of the requested type is
created.
*/
type CreateHookFunc func(runtime.Object) HookResult
/*
UpdateHookFunc is a function for handling an update hook. its runtime.Object
parameter will be the Kubernetes resource updated. The resource can be cast
to its actual type like this:
pod := obj.(*v1.Pod)
A return value of true marks the hook as completed. Returning false allows
the hook to run again when the next resource of the requested type is
updated.
*/
type UpdateHookFunc func(runtime.Object) HookResult
/*
DeleteHookFunc is a function for handling a delete hook. Its name parameter will
be the name of the resource deleted. The resource itself is not available to
the reactor.
*/
type DeleteHookFunc func(string) HookResult
/*
Hooks is a utility struct that simplifies controller testing with fake
clients. A Hooks struct allows attaching hook functions to actions (create,
update, delete) on a specified resource type within a fake client and ensuring
that all hooks complete in a timely manner.
*/
type Hooks struct {
completionCh chan int32
completionIndex int32
}
// NewHooks returns a Hooks struct that can be used to attach hooks to one or
// more fake clients and wait for all hooks to complete.
// TODO(grantr): Allow validating that a hook never fires
func NewHooks() *Hooks {
return &Hooks{
completionCh: make(chan int32, 100),
completionIndex: -1,
}
}
// OnCreate attaches a create hook to the given Fake. The hook function is
// executed every time a resource of the given type is created.
func (h *Hooks) OnCreate(fake *kubetesting.Fake, resource string, rf CreateHookFunc) {
index := atomic.AddInt32(&h.completionIndex, 1)
fake.PrependReactor("create", resource, func(a kubetesting.Action) (bool, runtime.Object, error) {
obj := a.(kubetesting.CreateActionImpl).Object
if rf(obj) == HookComplete {
h.completionCh <- index
}
return false, nil, nil
})
}
// OnUpdate attaches an update hook to the given Fake. The hook function is
// executed every time a resource of the given type is updated.
func (h *Hooks) OnUpdate(fake *kubetesting.Fake, resource string, rf UpdateHookFunc) {
index := atomic.AddInt32(&h.completionIndex, 1)
fake.PrependReactor("update", resource, func(a kubetesting.Action) (bool, runtime.Object, error) {
obj := a.(kubetesting.UpdateActionImpl).Object
if rf(obj) == HookComplete {
h.completionCh <- index
}
return false, nil, nil
})
}
// OnDelete attaches a delete hook to the given Fake. The hook function is
// executed every time a resource of the given type is deleted.
func (h *Hooks) OnDelete(fake *kubetesting.Fake, resource string, rf DeleteHookFunc) {
index := atomic.AddInt32(&h.completionIndex, 1)
fake.PrependReactor("delete", resource, func(a kubetesting.Action) (bool, runtime.Object, error) {
name := a.(kubetesting.DeleteActionImpl).Name
if rf(name) == HookComplete {
h.completionCh <- index
}
return false, nil, nil
})
}
// WaitForHooks waits until all attached hooks have returned true at least once.
// If the given timeout expires before that happens, an error is returned.
func (h *Hooks) WaitForHooks(timeout time.Duration) error {
if h.completionIndex == -1 {
return nil
}
timer := time.After(timeout)
hookCompletions := make([]HookResult, h.completionIndex+1)
for {
res := true
for _, r := range hookCompletions {
if !r {
res = false
break
}
}
if res {
h.completionIndex = -1
return nil
}
select {
case i := <-h.completionCh:
hookCompletions[i] = HookComplete
case <-timer:
return fmt.Errorf("Timed out waiting for hooks to complete")
}
}
}