k8s.io/kubernetes@v1.29.3/pkg/volume/util/recyclerclient/recycler_client_test.go (about) 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 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 // Input of the test 32 name string 33 existingPod *v1.Pod 34 createPod *v1.Pod 35 // eventSequence is list of events that are simulated during recycling. It 36 // can be either event generated by a recycler pod or a state change of 37 // the pod. (see newPodEvent and newEvent below). 38 eventSequence []watch.Event 39 40 // Expected output. 41 // expectedEvents is list of events that were sent to the volume that was 42 // recycled. 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 // Test recycler success with some events 85 name: "RecyclerSuccess", 86 createPod: newPod("podRecyclerSuccess", v1.PodPending, ""), 87 eventSequence: []watch.Event{ 88 // Pod gets Running and Succeeded 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 // Test recycler failure with some events 109 name: "RecyclerFailure", 110 createPod: newPod("podRecyclerFailure", v1.PodPending, ""), 111 eventSequence: []watch.Event{ 112 // Pod gets Running and Succeeded 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 // Recycler pod gets deleted 129 name: "RecyclerDeleted", 130 createPod: newPod("podRecyclerDeleted", v1.PodPending, ""), 131 eventSequence: []watch.Event{ 132 // Pod gets Running and Succeeded 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 // Another recycler pod is already running 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 // Simulate "already exists" error 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 }