...

Source file src/k8s.io/kubernetes/pkg/volume/util/recyclerclient/recycler_client_test.go

Documentation: k8s.io/kubernetes/pkg/volume/util/recyclerclient

     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  }
   235  

View as plain text