@@ -21,6 +21,7 @@ import (
21
21
"fmt"
22
22
"net/http"
23
23
"sort"
24
+ "strings"
24
25
"time"
25
26
26
27
"github.com/google/go-cmp/cmp"
@@ -117,14 +118,7 @@ var _ = SIGDescribe("API Streaming (aka. WatchList)", framework.WithSerial(), fe
117
118
featuregatetesting .SetFeatureGateDuringTest (ginkgo .GinkgoTB (), utilfeature .DefaultFeatureGate , featuregate .Feature (clientfeatures .WatchListClient ), true )
118
119
119
120
ginkgo .By (fmt .Sprintf ("Adding 5 secrets to %s namespace" , f .Namespace .Name ))
120
- var expectedSecrets []unstructured.Unstructured
121
- for i := 1 ; i <= 5 ; i ++ {
122
- unstructuredSecret , err := runtime .DefaultUnstructuredConverter .ToUnstructured (newSecret (fmt .Sprintf ("secret-%d" , i )))
123
- framework .ExpectNoError (err )
124
- secret , err := f .DynamicClient .Resource (v1 .SchemeGroupVersion .WithResource ("secrets" )).Namespace (f .Namespace .Name ).Create (ctx , & unstructured.Unstructured {Object : unstructuredSecret }, metav1.CreateOptions {})
125
- framework .ExpectNoError (err )
126
- expectedSecrets = append (expectedSecrets , * secret )
127
- }
121
+ expectedSecrets := addWellKnownUnstructuredSecrets (ctx , f )
128
122
129
123
rt , clientConfig := clientConfigWithRoundTripper (f )
130
124
wrappedDynamicClient , err := dynamic .NewForConfig (clientConfig )
@@ -171,16 +165,86 @@ var _ = SIGDescribe("API Streaming (aka. WatchList)", framework.WithSerial(), fe
171
165
expectedRequestMadeByMetaClient := getExpectedRequestMadeByClientFor (secretMetaList .GetResourceVersion ())
172
166
gomega .Expect (rt .actualRequests ).To (gomega .Equal (expectedRequestMadeByMetaClient ))
173
167
})
168
+
169
+ // Validates unsupported Accept headers in WatchList.
170
+ // Sets AcceptContentType to "application/json;as=Table", which the API doesn't support, returning a 406 error.
171
+ // After the 406, the client falls back to a regular list request.
172
+ ginkgo .It ("doesn't support receiving resources as Tables" , func (ctx context.Context ) {
173
+ featuregatetesting .SetFeatureGateDuringTest (ginkgo .GinkgoTB (), utilfeature .DefaultFeatureGate , featuregate .Feature (clientfeatures .WatchListClient ), true )
174
+
175
+ ginkgo .By (fmt .Sprintf ("Adding 5 secrets to %s namespace" , f .Namespace .Name ))
176
+ _ = addWellKnownUnstructuredSecrets (ctx , f )
177
+
178
+ rt , clientConfig := clientConfigWithRoundTripper (f )
179
+ modifiedClientConfig := dynamic .ConfigFor (clientConfig )
180
+ modifiedClientConfig .AcceptContentTypes = strings .Join ([]string {
181
+ fmt .Sprintf ("application/json;as=Table;v=%s;g=%s" , metav1 .SchemeGroupVersion .Version , metav1 .GroupName ),
182
+ }, "," )
183
+ modifiedClientConfig .GroupVersion = & v1 .SchemeGroupVersion
184
+ restClient , err := rest .RESTClientFor (modifiedClientConfig )
185
+ framework .ExpectNoError (err )
186
+ wrappedDynamicClient := dynamic .New (restClient )
187
+
188
+ // note that the client in case of an error (406) will fall back
189
+ // to a standard list request thus the overall call passes
190
+ ginkgo .By ("Streaming secrets as Table from the server" )
191
+ secretTable , err := wrappedDynamicClient .Resource (v1 .SchemeGroupVersion .WithResource ("secrets" )).Namespace (f .Namespace .Name ).List (ctx , metav1.ListOptions {})
192
+ framework .ExpectNoError (err )
193
+ gomega .Expect (secretTable .GetObjectKind ().GroupVersionKind ()).To (gomega .Equal (metav1 .SchemeGroupVersion .WithKind ("Table" )))
194
+
195
+ ginkgo .By ("Verifying if expected response was sent by the server" )
196
+ gomega .Expect (rt .actualResponseStatuses [0 ]).To (gomega .Equal ("406 Not Acceptable" ))
197
+ expectedRequestMadeByDynamicClient := getExpectedRequestMadeByClientWhenFallbackToListFor (secretTable .GetResourceVersion ())
198
+ gomega .Expect (rt .actualRequests ).To (gomega .Equal (expectedRequestMadeByDynamicClient ))
199
+
200
+ })
201
+
202
+ // Sets AcceptContentType to both "application/json;as=Table" and "application/json".
203
+ // Unlike the previous test, no 406 error occurs, as the API falls back to "application/json" and returns a valid response.
204
+ ginkgo .It ("falls backs to supported content type when when receiving resources as Tables was requested" , func (ctx context.Context ) {
205
+ featuregatetesting .SetFeatureGateDuringTest (ginkgo .GinkgoTB (), utilfeature .DefaultFeatureGate , featuregate .Feature (clientfeatures .WatchListClient ), true )
206
+
207
+ ginkgo .By (fmt .Sprintf ("Adding 5 secrets to %s namespace" , f .Namespace .Name ))
208
+ expectedSecrets := addWellKnownUnstructuredSecrets (ctx , f )
209
+
210
+ rt , clientConfig := clientConfigWithRoundTripper (f )
211
+ modifiedClientConfig := dynamic .ConfigFor (clientConfig )
212
+ modifiedClientConfig .AcceptContentTypes = strings .Join ([]string {
213
+ fmt .Sprintf ("application/json;as=Table;v=%s;g=%s" , metav1 .SchemeGroupVersion .Version , metav1 .GroupName ),
214
+ "application/json" ,
215
+ }, "," )
216
+ modifiedClientConfig .GroupVersion = & v1 .SchemeGroupVersion
217
+ restClient , err := rest .RESTClientFor (modifiedClientConfig )
218
+ framework .ExpectNoError (err )
219
+ wrappedDynamicClient := dynamic .New (restClient )
220
+
221
+ ginkgo .By ("Streaming secrets from the server" )
222
+ secretList , err := wrappedDynamicClient .Resource (v1 .SchemeGroupVersion .WithResource ("secrets" )).Namespace (f .Namespace .Name ).List (ctx , metav1.ListOptions {})
223
+ framework .ExpectNoError (err )
224
+
225
+ ginkgo .By ("Verifying if the secret list was properly streamed" )
226
+ streamedSecrets := secretList .Items
227
+ gomega .Expect (cmp .Equal (expectedSecrets , streamedSecrets )).To (gomega .BeTrueBecause ("data received via watchlist must match the added data" ))
228
+
229
+ ginkgo .By ("Verifying if expected requests were sent to the server" )
230
+ expectedRequestMadeByDynamicClient := getExpectedRequestMadeByClientFor (secretList .GetResourceVersion ())
231
+ gomega .Expect (rt .actualRequests ).To (gomega .Equal (expectedRequestMadeByDynamicClient ))
232
+ })
174
233
})
175
234
176
235
type roundTripper struct {
177
- actualRequests []string
178
- delegate http.RoundTripper
236
+ actualRequests []string
237
+ actualResponseStatuses []string
238
+ delegate http.RoundTripper
179
239
}
180
240
181
241
func (r * roundTripper ) RoundTrip (req * http.Request ) (* http.Response , error ) {
182
242
r .actualRequests = append (r .actualRequests , req .URL .RawQuery )
183
- return r .delegate .RoundTrip (req )
243
+ rsp , err := r .delegate .RoundTrip (req )
244
+ if rsp != nil {
245
+ r .actualResponseStatuses = append (r .actualResponseStatuses , rsp .Status )
246
+ }
247
+ return rsp , err
184
248
}
185
249
186
250
func (r * roundTripper ) Wrap (delegate http.RoundTripper ) http.RoundTripper {
@@ -211,10 +275,12 @@ func verifyStore(ctx context.Context, expectedSecrets []v1.Secret, store cache.S
211
275
framework .ExpectNoError (err )
212
276
}
213
277
278
+ // corresponds to a streaming request made by the client to stream the secrets
279
+ const expectedStreamingRequestMadeByClient string = "allowWatchBookmarks=true&resourceVersionMatch=NotOlderThan&sendInitialEvents=true&watch=true"
280
+
214
281
func getExpectedRequestMadeByClientFor (rv string ) []string {
215
282
expectedRequestMadeByClient := []string {
216
- // corresponds to a streaming request made by the client to stream the secrets
217
- "allowWatchBookmarks=true&resourceVersionMatch=NotOlderThan&sendInitialEvents=true&watch=true" ,
283
+ expectedStreamingRequestMadeByClient ,
218
284
}
219
285
if consistencydetector .IsDataConsistencyDetectionForWatchListEnabled () {
220
286
// corresponds to a standard list request made by the consistency detector build in into the client
@@ -223,6 +289,19 @@ func getExpectedRequestMadeByClientFor(rv string) []string {
223
289
return expectedRequestMadeByClient
224
290
}
225
291
292
+ func getExpectedRequestMadeByClientWhenFallbackToListFor (rv string ) []string {
293
+ expectedRequestMadeByClient := []string {
294
+ expectedStreamingRequestMadeByClient ,
295
+ // corresponds to a list request made by the client
296
+ "" ,
297
+ }
298
+ if consistencydetector .IsDataConsistencyDetectionForListEnabled () {
299
+ // corresponds to a standard list request made by the consistency detector build in into the client
300
+ expectedRequestMadeByClient = append (expectedRequestMadeByClient , fmt .Sprintf ("resourceVersion=%s&resourceVersionMatch=Exact" , rv ))
301
+ }
302
+ return expectedRequestMadeByClient
303
+ }
304
+
226
305
func addWellKnownSecrets (ctx context.Context , f * framework.Framework ) []v1.Secret {
227
306
ginkgo .By (fmt .Sprintf ("Adding 5 secrets to %s namespace" , f .Namespace .Name ))
228
307
var secrets []v1.Secret
@@ -234,6 +313,20 @@ func addWellKnownSecrets(ctx context.Context, f *framework.Framework) []v1.Secre
234
313
return secrets
235
314
}
236
315
316
+ // addWellKnownUnstructuredSecrets exists because secrets from addWellKnownSecrets
317
+ // don't have type info and cannot be converted.
318
+ func addWellKnownUnstructuredSecrets (ctx context.Context , f * framework.Framework ) []unstructured.Unstructured {
319
+ var secrets []unstructured.Unstructured
320
+ for i := 1 ; i <= 5 ; i ++ {
321
+ unstructuredSecret , err := runtime .DefaultUnstructuredConverter .ToUnstructured (newSecret (fmt .Sprintf ("secret-%d" , i )))
322
+ framework .ExpectNoError (err )
323
+ secret , err := f .DynamicClient .Resource (v1 .SchemeGroupVersion .WithResource ("secrets" )).Namespace (f .Namespace .Name ).Create (ctx , & unstructured.Unstructured {Object : unstructuredSecret }, metav1.CreateOptions {})
324
+ framework .ExpectNoError (err )
325
+ secrets = append (secrets , * secret )
326
+ }
327
+ return secrets
328
+ }
329
+
237
330
type byName []v1.Secret
238
331
239
332
func (a byName ) Len () int { return len (a ) }
0 commit comments