1
2
3
4 package watcher
5
6 import (
7 "context"
8 "fmt"
9 "testing"
10 "time"
11
12 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/require"
14 apierrors "k8s.io/apimachinery/pkg/api/errors"
15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16 "k8s.io/apimachinery/pkg/apis/testapigroup"
17 "k8s.io/apimachinery/pkg/runtime"
18 "k8s.io/apimachinery/pkg/runtime/schema"
19 "k8s.io/apimachinery/pkg/test"
20 "k8s.io/apimachinery/pkg/watch"
21 dynamicfake "k8s.io/client-go/dynamic/fake"
22 clienttesting "k8s.io/client-go/testing"
23 "k8s.io/client-go/tools/cache"
24 "k8s.io/klog/v2"
25 "sigs.k8s.io/cli-utils/pkg/testutil"
26 )
27
28 func TestResourceNotFoundError(t *testing.T) {
29 carpGVK := schema.GroupVersionKind{
30 Group: "foo",
31 Version: "v1",
32 Kind: "Carp",
33 }
34 exampleGR := schema.GroupResource{
35 Group: carpGVK.Group,
36 Resource: "carps",
37 }
38 namespace := "example-ns"
39
40 testCases := []struct {
41 name string
42 setup func(*dynamicfake.FakeDynamicClient)
43 errorHandler func(t *testing.T, err error)
44 }{
45 {
46 name: "List resource not found error",
47 setup: func(fakeClient *dynamicfake.FakeDynamicClient) {
48 fakeClient.PrependReactor("list", exampleGR.Resource, func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
49 listAction := action.(clienttesting.ListAction)
50 if listAction.GetNamespace() != namespace {
51 assert.Fail(t, "Received unexpected LIST namespace: %s", listAction.GetNamespace())
52 return false, nil, nil
53 }
54
55
56
57 err = newGenericServerResponse(action, newNotFoundResourceStatusError(action))
58 return true, nil, err
59 })
60 },
61 errorHandler: func(t *testing.T, err error) {
62 switch {
63 case apierrors.IsNotFound(err):
64 t.Logf("Received expected typed NotFound error: %v", err)
65 default:
66
67 t.Errorf("Expected typed NotFound error, but got a different error: %v", err)
68 }
69 },
70 },
71 {
72 name: "Watch resource not found error",
73 setup: func(fakeClient *dynamicfake.FakeDynamicClient) {
74 fakeClient.PrependWatchReactor(exampleGR.Resource, func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
75
76
77
78 err = newGenericServerResponse(action, newNotFoundResourceStatusError(action))
79 return true, nil, err
80 })
81 },
82 errorHandler: func(t *testing.T, err error) {
83 switch {
84 case apierrors.IsNotFound(err):
85
86
87 t.Logf("Received expected typed NotFound error: %v", err)
88 default:
89
90 t.Errorf("Expected typed NotFound error, but got a different error: %v", err)
91 }
92 },
93 },
94 {
95 name: "List resource forbidden error",
96 setup: func(fakeClient *dynamicfake.FakeDynamicClient) {
97 fakeClient.PrependReactor("list", exampleGR.Resource, func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
98 listAction := action.(clienttesting.ListAction)
99 if listAction.GetNamespace() != namespace {
100 assert.Fail(t, "Received unexpected LIST namespace: %s", listAction.GetNamespace())
101 return false, nil, nil
102 }
103
104
105
106 err = newGenericServerResponse(action, newForbiddenResourceStatusError(action))
107 return true, nil, err
108 })
109 },
110 errorHandler: func(t *testing.T, err error) {
111 switch {
112 case apierrors.IsForbidden(err):
113 t.Logf("Received expected typed Forbidden error: %v", err)
114 default:
115
116 t.Errorf("Expected typed Forbidden error, but got a different error: %v", err)
117 }
118 },
119 },
120 {
121 name: "Watch resource forbidden error",
122 setup: func(fakeClient *dynamicfake.FakeDynamicClient) {
123 fakeClient.PrependWatchReactor(exampleGR.Resource, func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
124
125
126
127 err = newGenericServerResponse(action, newForbiddenResourceStatusError(action))
128 return true, nil, err
129 })
130 },
131 errorHandler: func(t *testing.T, err error) {
132 switch {
133 case apierrors.IsForbidden(err):
134
135
136 t.Logf("Received expected typed Forbidden error: %v", err)
137 default:
138
139 t.Errorf("Expected typed Forbidden error, but got a different error: %v", err)
140 }
141 },
142 },
143 }
144
145 for _, tc := range testCases {
146 t.Run(tc.name, func(t *testing.T) {
147 scheme := runtime.NewScheme()
148 scheme.AddKnownTypes(metav1.SchemeGroupVersion, &metav1.Status{})
149
150
151 scheme.AddKnownTypes(carpGVK.GroupVersion(), &testapigroup.Carp{}, &testapigroup.CarpList{}, &test.List{})
152
153
154 fakeClient := dynamicfake.NewSimpleDynamicClient(scheme)
155
156
157 fakeClient.PrependReactor("*", "*", func(a clienttesting.Action) (bool, runtime.Object, error) {
158 klog.V(3).Infof("FakeDynamicClient: %T{ Verb: %q, Resource: %q, Namespace: %q }",
159 a, a.GetVerb(), a.GetResource().Resource, a.GetNamespace())
160 return false, nil, nil
161 })
162 fakeClient.PrependWatchReactor("*", func(a clienttesting.Action) (bool, watch.Interface, error) {
163 klog.V(3).Infof("FakeDynamicClient: %T{ Verb: %q, Resource: %q, Namespace: %q }",
164 a, a.GetVerb(), a.GetResource().Resource, a.GetNamespace())
165 return false, nil, nil
166 })
167
168 tc.setup(fakeClient)
169
170 informerFactory := NewDynamicInformerFactory(fakeClient, 0)
171
172 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
173 defer cancel()
174
175 fakeMapper := testutil.NewFakeRESTMapper(carpGVK)
176 mapping, err := fakeMapper.RESTMapping(carpGVK.GroupKind())
177 require.NoError(t, err)
178
179 informer := informerFactory.NewInformer(ctx, mapping, namespace)
180
181 err = informer.SetWatchErrorHandler(func(_ *cache.Reflector, err error) {
182 tc.errorHandler(t, err)
183
184 cancel()
185 })
186 require.NoError(t, err)
187
188
189 informer.Run(ctx.Done())
190 })
191 }
192 }
193
194
195
196
197 func newForbiddenResourceStatusError(action clienttesting.Action) *apierrors.StatusError {
198 username := "unused"
199 verb := action.GetVerb()
200 resource := action.GetResource().Resource
201 if subresource := action.GetSubresource(); len(subresource) > 0 {
202 resource = resource + "/" + subresource
203 }
204 apiGroup := action.GetResource().Group
205 namespace := action.GetNamespace()
206
207
208 err := fmt.Errorf("User %q cannot %s resource %q in API group %q in the namespace %q",
209 username, verb, resource, apiGroup, namespace)
210
211 qualifiedResource := action.GetResource().GroupResource()
212 name := ""
213 return apierrors.NewForbidden(qualifiedResource, name, err)
214 }
215
216
217
218 func newNotFoundResourceStatusError(action clienttesting.Action) *apierrors.StatusError {
219 qualifiedResource := action.GetResource().GroupResource()
220 name := ""
221 return apierrors.NewNotFound(qualifiedResource, name)
222 }
223
224
225 func newGenericServerResponse(action clienttesting.Action, statusError *apierrors.StatusError) *apierrors.StatusError {
226 errorCode := int(statusError.ErrStatus.Code)
227 verb := action.GetVerb()
228 qualifiedResource := action.GetResource().GroupResource()
229 name := statusError.ErrStatus.Details.Name
230
231 return apierrors.NewGenericServerResponse(errorCode, verb, qualifiedResource, name, statusError.Error(), -1, false)
232 }
233
View as plain text