recycler_client_test.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package recyclerclient
  14. import (
  15. "fmt"
  16. "testing"
  17. v1 "k8s.io/api/core/v1"
  18. "k8s.io/apimachinery/pkg/api/errors"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. "k8s.io/apimachinery/pkg/watch"
  21. api "k8s.io/kubernetes/pkg/apis/core"
  22. )
  23. type testcase struct {
  24. // Input of the test
  25. name string
  26. existingPod *v1.Pod
  27. createPod *v1.Pod
  28. // eventSequence is list of events that are simulated during recycling. It
  29. // can be either event generated by a recycler pod or a state change of
  30. // the pod. (see newPodEvent and newEvent below).
  31. eventSequence []watch.Event
  32. // Expected output.
  33. // expectedEvents is list of events that were sent to the volume that was
  34. // recycled.
  35. expectedEvents []mockEvent
  36. expectedError string
  37. }
  38. func newPodEvent(eventtype watch.EventType, name string, phase v1.PodPhase, message string) watch.Event {
  39. return watch.Event{
  40. Type: eventtype,
  41. Object: newPod(name, phase, message),
  42. }
  43. }
  44. func newEvent(eventtype, message string) watch.Event {
  45. return watch.Event{
  46. Type: watch.Added,
  47. Object: &v1.Event{
  48. ObjectMeta: metav1.ObjectMeta{
  49. Namespace: metav1.NamespaceDefault,
  50. },
  51. Reason: "MockEvent",
  52. Message: message,
  53. Type: eventtype,
  54. },
  55. }
  56. }
  57. func newPod(name string, phase v1.PodPhase, message string) *v1.Pod {
  58. return &v1.Pod{
  59. ObjectMeta: metav1.ObjectMeta{
  60. Namespace: metav1.NamespaceDefault,
  61. Name: name,
  62. },
  63. Status: v1.PodStatus{
  64. Phase: phase,
  65. Message: message,
  66. },
  67. }
  68. }
  69. func TestRecyclerPod(t *testing.T) {
  70. tests := []testcase{
  71. {
  72. // Test recycler success with some events
  73. name: "RecyclerSuccess",
  74. createPod: newPod("podRecyclerSuccess", v1.PodPending, ""),
  75. eventSequence: []watch.Event{
  76. // Pod gets Running and Succeeded
  77. newPodEvent(watch.Added, "podRecyclerSuccess", v1.PodPending, ""),
  78. newEvent(v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerSuccess to 127.0.0.1"),
  79. newEvent(v1.EventTypeNormal, "Pulling image \"k8s.gcr.io/busybox\""),
  80. newEvent(v1.EventTypeNormal, "Successfully pulled image \"k8s.gcr.io/busybox\""),
  81. newEvent(v1.EventTypeNormal, "Created container with docker id 83d929aeac82"),
  82. newEvent(v1.EventTypeNormal, "Started container with docker id 83d929aeac82"),
  83. newPodEvent(watch.Modified, "podRecyclerSuccess", v1.PodRunning, ""),
  84. newPodEvent(watch.Modified, "podRecyclerSuccess", v1.PodSucceeded, ""),
  85. },
  86. expectedEvents: []mockEvent{
  87. {v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerSuccess to 127.0.0.1"},
  88. {v1.EventTypeNormal, "Pulling image \"k8s.gcr.io/busybox\""},
  89. {v1.EventTypeNormal, "Successfully pulled image \"k8s.gcr.io/busybox\""},
  90. {v1.EventTypeNormal, "Created container with docker id 83d929aeac82"},
  91. {v1.EventTypeNormal, "Started container with docker id 83d929aeac82"},
  92. },
  93. expectedError: "",
  94. },
  95. {
  96. // Test recycler failure with some events
  97. name: "RecyclerFailure",
  98. createPod: newPod("podRecyclerFailure", v1.PodPending, ""),
  99. eventSequence: []watch.Event{
  100. // Pod gets Running and Succeeded
  101. newPodEvent(watch.Added, "podRecyclerFailure", v1.PodPending, ""),
  102. newEvent(v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerFailure to 127.0.0.1"),
  103. 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"),
  104. 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"),
  105. newPodEvent(watch.Modified, "podRecyclerFailure", v1.PodRunning, ""),
  106. newPodEvent(watch.Modified, "podRecyclerFailure", v1.PodFailed, "Pod was active on the node longer than specified deadline"),
  107. },
  108. expectedEvents: []mockEvent{
  109. {v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerFailure to 127.0.0.1"},
  110. {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"},
  111. {v1.EventTypeWarning, "Error syncing pod, skipping: timeout expired waiting for volumes to attach/mount for pod \"default\"/\"recycler-for-podRecyclerFailure\". list of unattached/unmounted"},
  112. },
  113. expectedError: "failed to recycle volume: Pod was active on the node longer than specified deadline",
  114. },
  115. {
  116. // Recycler pod gets deleted
  117. name: "RecyclerDeleted",
  118. createPod: newPod("podRecyclerDeleted", v1.PodPending, ""),
  119. eventSequence: []watch.Event{
  120. // Pod gets Running and Succeeded
  121. newPodEvent(watch.Added, "podRecyclerDeleted", v1.PodPending, ""),
  122. newEvent(v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerDeleted to 127.0.0.1"),
  123. newPodEvent(watch.Deleted, "podRecyclerDeleted", v1.PodPending, ""),
  124. },
  125. expectedEvents: []mockEvent{
  126. {v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerDeleted to 127.0.0.1"},
  127. },
  128. expectedError: "failed to recycle volume: recycler pod was deleted",
  129. },
  130. {
  131. // Another recycler pod is already running
  132. name: "RecyclerRunning",
  133. existingPod: newPod("podOldRecycler", v1.PodRunning, ""),
  134. createPod: newPod("podNewRecycler", v1.PodFailed, "mock message"),
  135. eventSequence: []watch.Event{},
  136. expectedError: "old recycler pod found, will retry later",
  137. },
  138. }
  139. for _, test := range tests {
  140. t.Logf("Test %q", test.name)
  141. client := &mockRecyclerClient{
  142. events: test.eventSequence,
  143. pod: test.existingPod,
  144. }
  145. err := internalRecycleVolumeByWatchingPodUntilCompletion(test.createPod.Name, test.createPod, client)
  146. receivedError := ""
  147. if err != nil {
  148. receivedError = err.Error()
  149. }
  150. if receivedError != test.expectedError {
  151. t.Errorf("Test %q failed, expected error %q, got %q", test.name, test.expectedError, receivedError)
  152. continue
  153. }
  154. if !client.deletedCalled {
  155. t.Errorf("Test %q failed, expected deferred client.Delete to be called on recycler pod", test.name)
  156. continue
  157. }
  158. for i, expectedEvent := range test.expectedEvents {
  159. if len(client.receivedEvents) <= i {
  160. t.Errorf("Test %q failed, expected event %d: %q not received", test.name, i, expectedEvent.message)
  161. continue
  162. }
  163. receivedEvent := client.receivedEvents[i]
  164. if expectedEvent.eventtype != receivedEvent.eventtype {
  165. t.Errorf("Test %q failed, event %d does not match: expected eventtype %q, got %q", test.name, i, expectedEvent.eventtype, receivedEvent.eventtype)
  166. }
  167. if expectedEvent.message != receivedEvent.message {
  168. t.Errorf("Test %q failed, event %d does not match: expected message %q, got %q", test.name, i, expectedEvent.message, receivedEvent.message)
  169. }
  170. }
  171. for i := len(test.expectedEvents); i < len(client.receivedEvents); i++ {
  172. t.Errorf("Test %q failed, unexpected event received: %s, %q", test.name, client.receivedEvents[i].eventtype, client.receivedEvents[i].message)
  173. }
  174. }
  175. }
  176. type mockRecyclerClient struct {
  177. pod *v1.Pod
  178. deletedCalled bool
  179. receivedEvents []mockEvent
  180. events []watch.Event
  181. }
  182. type mockEvent struct {
  183. eventtype, message string
  184. }
  185. func (c *mockRecyclerClient) CreatePod(pod *v1.Pod) (*v1.Pod, error) {
  186. if c.pod == nil {
  187. c.pod = pod
  188. return c.pod, nil
  189. }
  190. // Simulate "already exists" error
  191. return nil, errors.NewAlreadyExists(api.Resource("pods"), pod.Name)
  192. }
  193. func (c *mockRecyclerClient) GetPod(name, namespace string) (*v1.Pod, error) {
  194. if c.pod != nil {
  195. return c.pod, nil
  196. }
  197. return nil, fmt.Errorf("pod does not exist")
  198. }
  199. func (c *mockRecyclerClient) DeletePod(name, namespace string) error {
  200. c.deletedCalled = true
  201. return nil
  202. }
  203. func (c *mockRecyclerClient) WatchPod(name, namespace string, stopChannel chan struct{}) (<-chan watch.Event, error) {
  204. eventCh := make(chan watch.Event, 0)
  205. go func() {
  206. for _, e := range c.events {
  207. eventCh <- e
  208. }
  209. }()
  210. return eventCh, nil
  211. }
  212. func (c *mockRecyclerClient) Event(eventtype, message string) {
  213. c.receivedEvents = append(c.receivedEvents, mockEvent{eventtype, message})
  214. }