...

Source file src/k8s.io/client-go/testing/fixture_test.go

Documentation: k8s.io/client-go/testing

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

View as plain text