k8s.io/client-go@v0.22.2/tools/watch/until_test.go (about)

     1  /*
     2  Copyright 2016 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 watch
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"reflect"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	corev1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	"k8s.io/apimachinery/pkg/util/wait"
    32  	"k8s.io/apimachinery/pkg/watch"
    33  	fakeclient "k8s.io/client-go/kubernetes/fake"
    34  	"k8s.io/client-go/tools/cache"
    35  )
    36  
    37  type fakePod struct {
    38  }
    39  
    40  func (obj *fakePod) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind }
    41  func (obj *fakePod) DeepCopyObject() runtime.Object   { panic("DeepCopyObject not supported by fakePod") }
    42  
    43  func TestUntil(t *testing.T) {
    44  	fw := watch.NewFake()
    45  	go func() {
    46  		var obj *fakePod
    47  		fw.Add(obj)
    48  		fw.Modify(obj)
    49  	}()
    50  	conditions := []ConditionFunc{
    51  		func(event watch.Event) (bool, error) { return event.Type == watch.Added, nil },
    52  		func(event watch.Event) (bool, error) { return event.Type == watch.Modified, nil },
    53  	}
    54  
    55  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
    56  	defer cancel()
    57  
    58  	lastEvent, err := UntilWithoutRetry(ctx, fw, conditions...)
    59  	if err != nil {
    60  		t.Fatalf("expected nil error, got %#v", err)
    61  	}
    62  	if lastEvent == nil {
    63  		t.Fatal("expected an event")
    64  	}
    65  	if lastEvent.Type != watch.Modified {
    66  		t.Fatalf("expected MODIFIED event type, got %v", lastEvent.Type)
    67  	}
    68  	if got, isPod := lastEvent.Object.(*fakePod); !isPod {
    69  		t.Fatalf("expected a pod event, got %#v", got)
    70  	}
    71  }
    72  
    73  func TestUntilMultipleConditions(t *testing.T) {
    74  	fw := watch.NewFake()
    75  	go func() {
    76  		var obj *fakePod
    77  		fw.Add(obj)
    78  	}()
    79  	conditions := []ConditionFunc{
    80  		func(event watch.Event) (bool, error) { return event.Type == watch.Added, nil },
    81  		func(event watch.Event) (bool, error) { return event.Type == watch.Added, nil },
    82  	}
    83  
    84  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
    85  	defer cancel()
    86  
    87  	lastEvent, err := UntilWithoutRetry(ctx, fw, conditions...)
    88  	if err != nil {
    89  		t.Fatalf("expected nil error, got %#v", err)
    90  	}
    91  	if lastEvent == nil {
    92  		t.Fatal("expected an event")
    93  	}
    94  	if lastEvent.Type != watch.Added {
    95  		t.Fatalf("expected MODIFIED event type, got %v", lastEvent.Type)
    96  	}
    97  	if got, isPod := lastEvent.Object.(*fakePod); !isPod {
    98  		t.Fatalf("expected a pod event, got %#v", got)
    99  	}
   100  }
   101  
   102  func TestUntilMultipleConditionsFail(t *testing.T) {
   103  	fw := watch.NewFake()
   104  	go func() {
   105  		var obj *fakePod
   106  		fw.Add(obj)
   107  	}()
   108  	conditions := []ConditionFunc{
   109  		func(event watch.Event) (bool, error) { return event.Type == watch.Added, nil },
   110  		func(event watch.Event) (bool, error) { return event.Type == watch.Added, nil },
   111  		func(event watch.Event) (bool, error) { return event.Type == watch.Deleted, nil },
   112  	}
   113  
   114  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   115  	defer cancel()
   116  
   117  	lastEvent, err := UntilWithoutRetry(ctx, fw, conditions...)
   118  	if err != wait.ErrWaitTimeout {
   119  		t.Fatalf("expected ErrWaitTimeout error, got %#v", err)
   120  	}
   121  	if lastEvent == nil {
   122  		t.Fatal("expected an event")
   123  	}
   124  	if lastEvent.Type != watch.Added {
   125  		t.Fatalf("expected ADDED event type, got %v", lastEvent.Type)
   126  	}
   127  	if got, isPod := lastEvent.Object.(*fakePod); !isPod {
   128  		t.Fatalf("expected a pod event, got %#v", got)
   129  	}
   130  }
   131  
   132  func TestUntilTimeout(t *testing.T) {
   133  	fw := watch.NewFake()
   134  	go func() {
   135  		var obj *fakePod
   136  		fw.Add(obj)
   137  		fw.Modify(obj)
   138  	}()
   139  	conditions := []ConditionFunc{
   140  		func(event watch.Event) (bool, error) {
   141  			return event.Type == watch.Added, nil
   142  		},
   143  		func(event watch.Event) (bool, error) {
   144  			return event.Type == watch.Modified, nil
   145  		},
   146  	}
   147  
   148  	lastEvent, err := UntilWithoutRetry(context.Background(), fw, conditions...)
   149  	if err != nil {
   150  		t.Fatalf("expected nil error, got %#v", err)
   151  	}
   152  	if lastEvent == nil {
   153  		t.Fatal("expected an event")
   154  	}
   155  	if lastEvent.Type != watch.Modified {
   156  		t.Fatalf("expected MODIFIED event type, got %v", lastEvent.Type)
   157  	}
   158  	if got, isPod := lastEvent.Object.(*fakePod); !isPod {
   159  		t.Fatalf("expected a pod event, got %#v", got)
   160  	}
   161  }
   162  
   163  func TestUntilErrorCondition(t *testing.T) {
   164  	fw := watch.NewFake()
   165  	go func() {
   166  		var obj *fakePod
   167  		fw.Add(obj)
   168  	}()
   169  	expected := "something bad"
   170  	conditions := []ConditionFunc{
   171  		func(event watch.Event) (bool, error) { return event.Type == watch.Added, nil },
   172  		func(event watch.Event) (bool, error) { return false, errors.New(expected) },
   173  	}
   174  
   175  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
   176  	defer cancel()
   177  
   178  	_, err := UntilWithoutRetry(ctx, fw, conditions...)
   179  	if err == nil {
   180  		t.Fatal("expected an error")
   181  	}
   182  	if !strings.Contains(err.Error(), expected) {
   183  		t.Fatalf("expected %q in error string, got %q", expected, err.Error())
   184  	}
   185  }
   186  
   187  func TestUntilWithSync(t *testing.T) {
   188  	// FIXME: test preconditions
   189  	tt := []struct {
   190  		name             string
   191  		lw               *cache.ListWatch
   192  		preconditionFunc PreconditionFunc
   193  		conditionFunc    ConditionFunc
   194  		expectedErr      error
   195  		expectedEvent    *watch.Event
   196  	}{
   197  		{
   198  			name: "doesn't wait for sync with no precondition",
   199  			lw: &cache.ListWatch{
   200  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   201  					select {}
   202  				},
   203  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   204  					select {}
   205  				},
   206  			},
   207  			preconditionFunc: nil,
   208  			conditionFunc: func(e watch.Event) (bool, error) {
   209  				return true, nil
   210  			},
   211  			expectedErr:   errors.New("timed out waiting for the condition"),
   212  			expectedEvent: nil,
   213  		},
   214  		{
   215  			name: "waits indefinitely with precondition if it can't sync",
   216  			lw: &cache.ListWatch{
   217  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   218  					select {}
   219  				},
   220  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   221  					select {}
   222  				},
   223  			},
   224  			preconditionFunc: func(store cache.Store) (bool, error) {
   225  				return true, nil
   226  			},
   227  			conditionFunc: func(e watch.Event) (bool, error) {
   228  				return true, nil
   229  			},
   230  			expectedErr:   errors.New("UntilWithSync: unable to sync caches: context deadline exceeded"),
   231  			expectedEvent: nil,
   232  		},
   233  		{
   234  			name: "precondition can stop the loop",
   235  			lw: func() *cache.ListWatch {
   236  				fakeclient := fakeclient.NewSimpleClientset(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "first"}})
   237  
   238  				return &cache.ListWatch{
   239  					ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   240  						return fakeclient.CoreV1().Secrets("").List(context.TODO(), options)
   241  					},
   242  					WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   243  						return fakeclient.CoreV1().Secrets("").Watch(context.TODO(), options)
   244  					},
   245  				}
   246  			}(),
   247  			preconditionFunc: func(store cache.Store) (bool, error) {
   248  				_, exists, err := store.Get(&metav1.ObjectMeta{Namespace: "", Name: "first"})
   249  				if err != nil {
   250  					return true, err
   251  				}
   252  				if exists {
   253  					return true, nil
   254  				}
   255  				return false, nil
   256  			},
   257  			conditionFunc: func(e watch.Event) (bool, error) {
   258  				return true, errors.New("should never reach this")
   259  			},
   260  			expectedErr:   nil,
   261  			expectedEvent: nil,
   262  		},
   263  		{
   264  			name: "precondition lets it proceed to regular condition",
   265  			lw: func() *cache.ListWatch {
   266  				fakeclient := fakeclient.NewSimpleClientset(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "first"}})
   267  
   268  				return &cache.ListWatch{
   269  					ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   270  						return fakeclient.CoreV1().Secrets("").List(context.TODO(), options)
   271  					},
   272  					WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   273  						return fakeclient.CoreV1().Secrets("").Watch(context.TODO(), options)
   274  					},
   275  				}
   276  			}(),
   277  			preconditionFunc: func(store cache.Store) (bool, error) {
   278  				return false, nil
   279  			},
   280  			conditionFunc: func(e watch.Event) (bool, error) {
   281  				if e.Type == watch.Added {
   282  					return true, nil
   283  				}
   284  				panic("no other events are expected")
   285  			},
   286  			expectedErr:   nil,
   287  			expectedEvent: &watch.Event{Type: watch.Added, Object: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "first"}}},
   288  		},
   289  	}
   290  
   291  	for _, tc := range tt {
   292  		t.Run(tc.name, func(t *testing.T) {
   293  			// Informer waits for caches to sync by polling in 100ms intervals,
   294  			// timeout needs to be reasonably higher
   295  			ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
   296  			defer cancel()
   297  
   298  			event, err := UntilWithSync(ctx, tc.lw, &corev1.Secret{}, tc.preconditionFunc, tc.conditionFunc)
   299  
   300  			if !reflect.DeepEqual(err, tc.expectedErr) {
   301  				t.Errorf("expected error %#v, got %#v", tc.expectedErr, err)
   302  			}
   303  
   304  			if !reflect.DeepEqual(event, tc.expectedEvent) {
   305  				t.Errorf("expected event %#v, got %#v", tc.expectedEvent, event)
   306  			}
   307  		})
   308  	}
   309  }