k8s.io/client-go@v0.22.2/testing/fixture_test.go (about)

     1  /*
     2  Copyright 2015 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 testing
    18  
    19  import (
    20  	"fmt"
    21  	"math/rand"
    22  	"strconv"
    23  	"sync"
    24  	"testing"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  
    28  	"k8s.io/apimachinery/pkg/api/errors"
    29  	"k8s.io/apimachinery/pkg/api/meta"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  	runtime "k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/runtime/schema"
    33  	serializer "k8s.io/apimachinery/pkg/runtime/serializer"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/apimachinery/pkg/watch"
    36  )
    37  
    38  func getArbitraryResource(s schema.GroupVersionResource, name, namespace string) *unstructured.Unstructured {
    39  	return &unstructured.Unstructured{
    40  		Object: map[string]interface{}{
    41  			"kind":       s.Resource,
    42  			"apiVersion": s.Version,
    43  			"metadata": map[string]interface{}{
    44  				"name":            name,
    45  				"namespace":       namespace,
    46  				"generateName":    "test_generateName",
    47  				"uid":             "test_uid",
    48  				"resourceVersion": "test_resourceVersion",
    49  				"selfLink":        "test_selfLink",
    50  			},
    51  			"data": strconv.Itoa(rand.Int()),
    52  		},
    53  	}
    54  }
    55  
    56  func TestWatchCallNonNamespace(t *testing.T) {
    57  	testResource := schema.GroupVersionResource{Group: "", Version: "test_version", Resource: "test_kind"}
    58  	testObj := getArbitraryResource(testResource, "test_name", "test_namespace")
    59  	accessor, err := meta.Accessor(testObj)
    60  	if err != nil {
    61  		t.Fatalf("unexpected error: %v", err)
    62  	}
    63  	ns := accessor.GetNamespace()
    64  	scheme := runtime.NewScheme()
    65  	codecs := serializer.NewCodecFactory(scheme)
    66  	o := NewObjectTracker(scheme, codecs.UniversalDecoder())
    67  	watch, err := o.Watch(testResource, ns)
    68  	if err != nil {
    69  		t.Fatalf("test resource watch failed in %s: %v ", ns, err)
    70  	}
    71  	go func() {
    72  		err := o.Create(testResource, testObj, ns)
    73  		if err != nil {
    74  			t.Errorf("test resource creation failed: %v", err)
    75  		}
    76  	}()
    77  	out := <-watch.ResultChan()
    78  	assert.Equal(t, testObj, out.Object, "watched object mismatch")
    79  }
    80  
    81  func TestWatchCallAllNamespace(t *testing.T) {
    82  	testResource := schema.GroupVersionResource{Group: "", Version: "test_version", Resource: "test_kind"}
    83  	testObj := getArbitraryResource(testResource, "test_name", "test_namespace")
    84  	accessor, err := meta.Accessor(testObj)
    85  	if err != nil {
    86  		t.Fatalf("unexpected error: %v", err)
    87  	}
    88  	ns := accessor.GetNamespace()
    89  	scheme := runtime.NewScheme()
    90  	codecs := serializer.NewCodecFactory(scheme)
    91  	o := NewObjectTracker(scheme, codecs.UniversalDecoder())
    92  	w, err := o.Watch(testResource, "test_namespace")
    93  	if err != nil {
    94  		t.Fatalf("test resource watch failed in test_namespace: %v", err)
    95  	}
    96  	wAll, err := o.Watch(testResource, "")
    97  	if err != nil {
    98  		t.Fatalf("test resource watch failed in all namespaces: %v", err)
    99  	}
   100  	go func() {
   101  		err := o.Create(testResource, testObj, ns)
   102  		assert.NoError(t, err, "test resource creation failed")
   103  	}()
   104  	out := <-w.ResultChan()
   105  	outAll := <-wAll.ResultChan()
   106  	assert.Equal(t, watch.Added, out.Type, "watch event mismatch")
   107  	assert.Equal(t, watch.Added, outAll.Type, "watch event mismatch")
   108  	assert.Equal(t, testObj, out.Object, "watched created object mismatch")
   109  	assert.Equal(t, testObj, outAll.Object, "watched created object mismatch")
   110  	go func() {
   111  		err := o.Update(testResource, testObj, ns)
   112  		assert.NoError(t, err, "test resource updating failed")
   113  	}()
   114  	out = <-w.ResultChan()
   115  	outAll = <-wAll.ResultChan()
   116  	assert.Equal(t, watch.Modified, out.Type, "watch event mismatch")
   117  	assert.Equal(t, watch.Modified, outAll.Type, "watch event mismatch")
   118  	assert.Equal(t, testObj, out.Object, "watched updated object mismatch")
   119  	assert.Equal(t, testObj, outAll.Object, "watched updated object mismatch")
   120  	go func() {
   121  		err := o.Delete(testResource, "test_namespace", "test_name")
   122  		assert.NoError(t, err, "test resource deletion failed")
   123  	}()
   124  	out = <-w.ResultChan()
   125  	outAll = <-wAll.ResultChan()
   126  	assert.Equal(t, watch.Deleted, out.Type, "watch event mismatch")
   127  	assert.Equal(t, watch.Deleted, outAll.Type, "watch event mismatch")
   128  	assert.Equal(t, testObj, out.Object, "watched deleted object mismatch")
   129  	assert.Equal(t, testObj, outAll.Object, "watched deleted object mismatch")
   130  }
   131  
   132  func TestWatchCallMultipleInvocation(t *testing.T) {
   133  	cases := []struct {
   134  		name string
   135  		op   watch.EventType
   136  		ns   string
   137  	}{
   138  		{
   139  			"foo",
   140  			watch.Added,
   141  			"test_namespace",
   142  		},
   143  		{
   144  			"bar",
   145  			watch.Added,
   146  			"test_namespace",
   147  		},
   148  		{
   149  			"baz",
   150  			watch.Added,
   151  			"",
   152  		},
   153  		{
   154  			"bar",
   155  			watch.Modified,
   156  			"test_namespace",
   157  		},
   158  		{
   159  			"baz",
   160  			watch.Modified,
   161  			"",
   162  		},
   163  		{
   164  			"foo",
   165  			watch.Deleted,
   166  			"test_namespace",
   167  		},
   168  		{
   169  			"bar",
   170  			watch.Deleted,
   171  			"test_namespace",
   172  		},
   173  		{
   174  			"baz",
   175  			watch.Deleted,
   176  			"",
   177  		},
   178  	}
   179  
   180  	scheme := runtime.NewScheme()
   181  	codecs := serializer.NewCodecFactory(scheme)
   182  	testResource := schema.GroupVersionResource{Group: "", Version: "test_version", Resource: "test_kind"}
   183  
   184  	o := NewObjectTracker(scheme, codecs.UniversalDecoder())
   185  	watchNamespaces := []string{
   186  		"",
   187  		"",
   188  		"test_namespace",
   189  		"test_namespace",
   190  	}
   191  	var wg sync.WaitGroup
   192  	wg.Add(len(watchNamespaces))
   193  	for idx, watchNamespace := range watchNamespaces {
   194  		i := idx
   195  		watchNamespace := watchNamespace
   196  		w, err := o.Watch(testResource, watchNamespace)
   197  		if err != nil {
   198  			t.Fatalf("test resource watch failed in %s: %v", watchNamespace, err)
   199  		}
   200  		go func() {
   201  			assert.NoError(t, err, "watch invocation failed")
   202  			for _, c := range cases {
   203  				if watchNamespace == "" || c.ns == watchNamespace {
   204  					fmt.Printf("%#v %#v\n", c, i)
   205  					event := <-w.ResultChan()
   206  					accessor, err := meta.Accessor(event.Object)
   207  					if err != nil {
   208  						t.Errorf("unexpected error: %v", err)
   209  						break
   210  					}
   211  					assert.Equal(t, c.op, event.Type, "watch event mismatched")
   212  					assert.Equal(t, c.name, accessor.GetName(), "watched object mismatch")
   213  					assert.Equal(t, c.ns, accessor.GetNamespace(), "watched object mismatch")
   214  				}
   215  			}
   216  			wg.Done()
   217  		}()
   218  	}
   219  	for _, c := range cases {
   220  		switch c.op {
   221  		case watch.Added:
   222  			obj := getArbitraryResource(testResource, c.name, c.ns)
   223  			o.Create(testResource, obj, c.ns)
   224  		case watch.Modified:
   225  			obj := getArbitraryResource(testResource, c.name, c.ns)
   226  			o.Update(testResource, obj, c.ns)
   227  		case watch.Deleted:
   228  			o.Delete(testResource, c.ns, c.name)
   229  		}
   230  	}
   231  	wg.Wait()
   232  }
   233  
   234  func TestWatchAddAfterStop(t *testing.T) {
   235  	testResource := schema.GroupVersionResource{Group: "", Version: "test_version", Resource: "test_kind"}
   236  	testObj := getArbitraryResource(testResource, "test_name", "test_namespace")
   237  	accessor, err := meta.Accessor(testObj)
   238  	if err != nil {
   239  		t.Fatalf("unexpected error: %v", err)
   240  	}
   241  
   242  	ns := accessor.GetNamespace()
   243  	scheme := runtime.NewScheme()
   244  	codecs := serializer.NewCodecFactory(scheme)
   245  	o := NewObjectTracker(scheme, codecs.UniversalDecoder())
   246  	watch, err := o.Watch(testResource, ns)
   247  	if err != nil {
   248  		t.Errorf("watch creation failed: %v", err)
   249  	}
   250  
   251  	// When the watch is stopped it should ignore later events without panicking.
   252  	defer func() {
   253  		if r := recover(); r != nil {
   254  			t.Errorf("Watch panicked when it should have ignored create after stop: %v", r)
   255  		}
   256  	}()
   257  
   258  	watch.Stop()
   259  	err = o.Create(testResource, testObj, ns)
   260  	if err != nil {
   261  		t.Errorf("test resource creation failed: %v", err)
   262  	}
   263  }
   264  
   265  func TestPatchWithMissingObject(t *testing.T) {
   266  	nodesResource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "nodes"}
   267  
   268  	scheme := runtime.NewScheme()
   269  	codecs := serializer.NewCodecFactory(scheme)
   270  	o := NewObjectTracker(scheme, codecs.UniversalDecoder())
   271  	reaction := ObjectReaction(o)
   272  	action := NewRootPatchSubresourceAction(nodesResource, "node-1", types.StrategicMergePatchType, []byte(`{}`))
   273  	handled, node, err := reaction(action)
   274  	assert.True(t, handled)
   275  	assert.Nil(t, node)
   276  	assert.EqualError(t, err, `nodes "node-1" not found`)
   277  }
   278  
   279  func TestGetWithExactMatch(t *testing.T) {
   280  	scheme := runtime.NewScheme()
   281  	codecs := serializer.NewCodecFactory(scheme)
   282  
   283  	constructObject := func(s schema.GroupVersionResource, name, namespace string) (*unstructured.Unstructured, schema.GroupVersionResource) {
   284  		obj := getArbitraryResource(s, name, namespace)
   285  		gvks, _, err := scheme.ObjectKinds(obj)
   286  		assert.NoError(t, err)
   287  		gvr, _ := meta.UnsafeGuessKindToResource(gvks[0])
   288  		return obj, gvr
   289  	}
   290  
   291  	var err error
   292  	// Object with empty namespace
   293  	o := NewObjectTracker(scheme, codecs.UniversalDecoder())
   294  	nodeResource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "node"}
   295  	node, gvr := constructObject(nodeResource, "node", "")
   296  
   297  	assert.Nil(t, o.Add(node))
   298  
   299  	// Exact match
   300  	_, err = o.Get(gvr, "", "node")
   301  	assert.NoError(t, err)
   302  
   303  	// Unexpected namespace provided
   304  	_, err = o.Get(gvr, "ns", "node")
   305  	assert.Error(t, err)
   306  	errNotFound := errors.NewNotFound(gvr.GroupResource(), "node")
   307  	assert.EqualError(t, err, errNotFound.Error())
   308  
   309  	// Object with non-empty namespace
   310  	o = NewObjectTracker(scheme, codecs.UniversalDecoder())
   311  	podResource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pod"}
   312  	pod, gvr := constructObject(podResource, "pod", "default")
   313  	assert.Nil(t, o.Add(pod))
   314  
   315  	// Exact match
   316  	_, err = o.Get(gvr, "default", "pod")
   317  	assert.NoError(t, err)
   318  
   319  	// Missing namespace
   320  	_, err = o.Get(gvr, "", "pod")
   321  	assert.Error(t, err)
   322  	errNotFound = errors.NewNotFound(gvr.GroupResource(), "pod")
   323  	assert.EqualError(t, err, errNotFound.Error())
   324  }
   325  
   326  func Test_resourceCovers(t *testing.T) {
   327  	type args struct {
   328  		resource string
   329  		action   Action
   330  	}
   331  	tests := []struct {
   332  		name string
   333  		args args
   334  		want bool
   335  	}{
   336  		{
   337  			args: args{
   338  				resource: "*",
   339  				action:   ActionImpl{},
   340  			},
   341  			want: true,
   342  		},
   343  		{
   344  			args: args{
   345  				resource: "serviceaccounts",
   346  				action:   ActionImpl{},
   347  			},
   348  			want: false,
   349  		},
   350  		{
   351  			args: args{
   352  				resource: "serviceaccounts",
   353  				action: ActionImpl{
   354  					Resource: schema.GroupVersionResource{
   355  						Resource: "serviceaccounts",
   356  					},
   357  				},
   358  			},
   359  			want: true,
   360  		},
   361  		{
   362  			args: args{
   363  				resource: "serviceaccounts/token",
   364  				action: ActionImpl{
   365  					Resource: schema.GroupVersionResource{},
   366  				},
   367  			},
   368  			want: false,
   369  		},
   370  		{
   371  			args: args{
   372  				resource: "serviceaccounts/token",
   373  				action: ActionImpl{
   374  					Resource: schema.GroupVersionResource{
   375  						Resource: "serviceaccounts",
   376  					},
   377  				},
   378  			},
   379  			want: false,
   380  		},
   381  		{
   382  			args: args{
   383  				resource: "serviceaccounts/token",
   384  				action: ActionImpl{
   385  					Resource:    schema.GroupVersionResource{},
   386  					Subresource: "token",
   387  				},
   388  			},
   389  			want: false,
   390  		},
   391  		{
   392  			args: args{
   393  				resource: "serviceaccounts/token",
   394  				action: ActionImpl{
   395  					Resource: schema.GroupVersionResource{
   396  						Resource: "serviceaccounts",
   397  					},
   398  					Subresource: "token",
   399  				},
   400  			},
   401  			want: true,
   402  		},
   403  	}
   404  	for _, tt := range tests {
   405  		t.Run(tt.name, func(t *testing.T) {
   406  			if got := resourceCovers(tt.args.resource, tt.args.action); got != tt.want {
   407  				t.Errorf("resourceCovers() = %v, want %v", got, tt.want)
   408  			}
   409  		})
   410  	}
   411  }