1
16
17 package recyclerclient
18
19 import (
20 "fmt"
21 "testing"
22
23 v1 "k8s.io/api/core/v1"
24 "k8s.io/apimachinery/pkg/api/errors"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/watch"
27 api "k8s.io/kubernetes/pkg/apis/core"
28 )
29
30 type testcase struct {
31
32 name string
33 existingPod *v1.Pod
34 createPod *v1.Pod
35
36
37
38 eventSequence []watch.Event
39
40
41
42
43 expectedEvents []mockEvent
44 expectedError string
45 }
46
47 func newPodEvent(eventtype watch.EventType, name string, phase v1.PodPhase, message string) watch.Event {
48 return watch.Event{
49 Type: eventtype,
50 Object: newPod(name, phase, message),
51 }
52 }
53
54 func newEvent(eventtype, message string) watch.Event {
55 return watch.Event{
56 Type: watch.Added,
57 Object: &v1.Event{
58 ObjectMeta: metav1.ObjectMeta{
59 Namespace: metav1.NamespaceDefault,
60 },
61 Reason: "MockEvent",
62 Message: message,
63 Type: eventtype,
64 },
65 }
66 }
67
68 func newPod(name string, phase v1.PodPhase, message string) *v1.Pod {
69 return &v1.Pod{
70 ObjectMeta: metav1.ObjectMeta{
71 Namespace: metav1.NamespaceDefault,
72 Name: name,
73 },
74 Status: v1.PodStatus{
75 Phase: phase,
76 Message: message,
77 },
78 }
79 }
80
81 func TestRecyclerPod(t *testing.T) {
82 tests := []testcase{
83 {
84
85 name: "RecyclerSuccess",
86 createPod: newPod("podRecyclerSuccess", v1.PodPending, ""),
87 eventSequence: []watch.Event{
88
89 newPodEvent(watch.Added, "podRecyclerSuccess", v1.PodPending, ""),
90 newEvent(v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerSuccess to 127.0.0.1"),
91 newEvent(v1.EventTypeNormal, "Pulling image \"registry.k8s.io/busybox\""),
92 newEvent(v1.EventTypeNormal, "Successfully pulled image \"registry.k8s.io/busybox\""),
93 newEvent(v1.EventTypeNormal, "Created container with docker id 83d929aeac82"),
94 newEvent(v1.EventTypeNormal, "Started container with docker id 83d929aeac82"),
95 newPodEvent(watch.Modified, "podRecyclerSuccess", v1.PodRunning, ""),
96 newPodEvent(watch.Modified, "podRecyclerSuccess", v1.PodSucceeded, ""),
97 },
98 expectedEvents: []mockEvent{
99 {v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerSuccess to 127.0.0.1"},
100 {v1.EventTypeNormal, "Pulling image \"registry.k8s.io/busybox\""},
101 {v1.EventTypeNormal, "Successfully pulled image \"registry.k8s.io/busybox\""},
102 {v1.EventTypeNormal, "Created container with docker id 83d929aeac82"},
103 {v1.EventTypeNormal, "Started container with docker id 83d929aeac82"},
104 },
105 expectedError: "",
106 },
107 {
108
109 name: "RecyclerFailure",
110 createPod: newPod("podRecyclerFailure", v1.PodPending, ""),
111 eventSequence: []watch.Event{
112
113 newPodEvent(watch.Added, "podRecyclerFailure", v1.PodPending, ""),
114 newEvent(v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerFailure to 127.0.0.1"),
115 newEvent(v1.EventTypeWarning, "Unable to mount volumes for pod \"recycler-for-podRecyclerFailure_default(3c9809e5-347c-11e6-a79b-3c970e965218)\": timeout expired waiting for volumes to attach/mount"),
116 newEvent(v1.EventTypeWarning, "Error syncing pod, skipping: timeout expired waiting for volumes to attach/mount for pod \"default\"/\"recycler-for-podRecyclerFailure\". list of unattached/unmounted"),
117 newPodEvent(watch.Modified, "podRecyclerFailure", v1.PodRunning, ""),
118 newPodEvent(watch.Modified, "podRecyclerFailure", v1.PodFailed, "Pod was active on the node longer than specified deadline"),
119 },
120 expectedEvents: []mockEvent{
121 {v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerFailure to 127.0.0.1"},
122 {v1.EventTypeWarning, "Unable to mount volumes for pod \"recycler-for-podRecyclerFailure_default(3c9809e5-347c-11e6-a79b-3c970e965218)\": timeout expired waiting for volumes to attach/mount"},
123 {v1.EventTypeWarning, "Error syncing pod, skipping: timeout expired waiting for volumes to attach/mount for pod \"default\"/\"recycler-for-podRecyclerFailure\". list of unattached/unmounted"},
124 },
125 expectedError: "failed to recycle volume: Pod was active on the node longer than specified deadline",
126 },
127 {
128
129 name: "RecyclerDeleted",
130 createPod: newPod("podRecyclerDeleted", v1.PodPending, ""),
131 eventSequence: []watch.Event{
132
133 newPodEvent(watch.Added, "podRecyclerDeleted", v1.PodPending, ""),
134 newEvent(v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerDeleted to 127.0.0.1"),
135 newPodEvent(watch.Deleted, "podRecyclerDeleted", v1.PodPending, ""),
136 },
137 expectedEvents: []mockEvent{
138 {v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerDeleted to 127.0.0.1"},
139 },
140 expectedError: "failed to recycle volume: recycler pod was deleted",
141 },
142 {
143
144 name: "RecyclerRunning",
145 existingPod: newPod("podOldRecycler", v1.PodRunning, ""),
146 createPod: newPod("podNewRecycler", v1.PodFailed, "mock message"),
147 eventSequence: []watch.Event{},
148 expectedError: "old recycler pod found, will retry later",
149 },
150 }
151
152 for _, test := range tests {
153 t.Logf("Test %q", test.name)
154 client := &mockRecyclerClient{
155 events: test.eventSequence,
156 pod: test.existingPod,
157 }
158 err := internalRecycleVolumeByWatchingPodUntilCompletion(test.createPod.Name, test.createPod, client)
159 receivedError := ""
160 if err != nil {
161 receivedError = err.Error()
162 }
163 if receivedError != test.expectedError {
164 t.Errorf("Test %q failed, expected error %q, got %q", test.name, test.expectedError, receivedError)
165 continue
166 }
167 if !client.deletedCalled {
168 t.Errorf("Test %q failed, expected deferred client.Delete to be called on recycler pod", test.name)
169 continue
170 }
171 for i, expectedEvent := range test.expectedEvents {
172 if len(client.receivedEvents) <= i {
173 t.Errorf("Test %q failed, expected event %d: %q not received", test.name, i, expectedEvent.message)
174 continue
175 }
176 receivedEvent := client.receivedEvents[i]
177 if expectedEvent.eventtype != receivedEvent.eventtype {
178 t.Errorf("Test %q failed, event %d does not match: expected eventtype %q, got %q", test.name, i, expectedEvent.eventtype, receivedEvent.eventtype)
179 }
180 if expectedEvent.message != receivedEvent.message {
181 t.Errorf("Test %q failed, event %d does not match: expected message %q, got %q", test.name, i, expectedEvent.message, receivedEvent.message)
182 }
183 }
184 for i := len(test.expectedEvents); i < len(client.receivedEvents); i++ {
185 t.Errorf("Test %q failed, unexpected event received: %s, %q", test.name, client.receivedEvents[i].eventtype, client.receivedEvents[i].message)
186 }
187 }
188 }
189
190 type mockRecyclerClient struct {
191 pod *v1.Pod
192 deletedCalled bool
193 receivedEvents []mockEvent
194 events []watch.Event
195 }
196
197 type mockEvent struct {
198 eventtype, message string
199 }
200
201 func (c *mockRecyclerClient) CreatePod(pod *v1.Pod) (*v1.Pod, error) {
202 if c.pod == nil {
203 c.pod = pod
204 return c.pod, nil
205 }
206
207 return nil, errors.NewAlreadyExists(api.Resource("pods"), pod.Name)
208 }
209
210 func (c *mockRecyclerClient) GetPod(name, namespace string) (*v1.Pod, error) {
211 if c.pod != nil {
212 return c.pod, nil
213 }
214 return nil, fmt.Errorf("pod does not exist")
215 }
216
217 func (c *mockRecyclerClient) DeletePod(name, namespace string) error {
218 c.deletedCalled = true
219 return nil
220 }
221
222 func (c *mockRecyclerClient) WatchPod(name, namespace string, stopChannel chan struct{}) (<-chan watch.Event, error) {
223 eventCh := make(chan watch.Event)
224 go func() {
225 for _, e := range c.events {
226 eventCh <- e
227 }
228 }()
229 return eventCh, nil
230 }
231
232 func (c *mockRecyclerClient) Event(eventtype, message string) {
233 c.receivedEvents = append(c.receivedEvents, mockEvent{eventtype, message})
234 }
235
View as plain text