@@ -18,6 +18,7 @@ package handlers
18
18
19
19
import (
20
20
"context"
21
+ "encoding/base64"
21
22
"encoding/json"
22
23
"fmt"
23
24
"io"
@@ -38,8 +39,9 @@ import (
38
39
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
39
40
"k8s.io/apiserver/pkg/endpoints/metrics"
40
41
endpointsrequest "k8s.io/apiserver/pkg/endpoints/request"
42
+ "k8s.io/apiserver/pkg/storage"
41
43
42
- klog "k8s.io/klog/v2"
44
+ "k8s.io/klog/v2"
43
45
)
44
46
45
47
// watchEmbeddedEncoder performs encoding of the embedded object.
@@ -147,22 +149,25 @@ type watchEncoder struct {
147
149
encoder runtime.Encoder
148
150
framer io.Writer
149
151
152
+ watchListTransformerFn watchListTransformerFunction
153
+
150
154
buffer runtime.Splice
151
155
eventBuffer runtime.Splice
152
156
153
157
currentEmbeddedIdentifier runtime.Identifier
154
158
identifiers map [watch.EventType ]runtime.Identifier
155
159
}
156
160
157
- func newWatchEncoder (ctx context.Context , kind schema.GroupVersionKind , embeddedEncoder runtime.Encoder , encoder runtime.Encoder , framer io.Writer ) * watchEncoder {
161
+ func newWatchEncoder (ctx context.Context , kind schema.GroupVersionKind , embeddedEncoder runtime.Encoder , encoder runtime.Encoder , framer io.Writer , watchListTransformerFn watchListTransformerFunction ) * watchEncoder {
158
162
return & watchEncoder {
159
- ctx : ctx ,
160
- kind : kind ,
161
- embeddedEncoder : embeddedEncoder ,
162
- encoder : encoder ,
163
- framer : framer ,
164
- buffer : runtime .NewSpliceBuffer (),
165
- eventBuffer : runtime .NewSpliceBuffer (),
163
+ ctx : ctx ,
164
+ kind : kind ,
165
+ embeddedEncoder : embeddedEncoder ,
166
+ encoder : encoder ,
167
+ framer : framer ,
168
+ watchListTransformerFn : watchListTransformerFn ,
169
+ buffer : runtime .NewSpliceBuffer (),
170
+ eventBuffer : runtime .NewSpliceBuffer (),
166
171
}
167
172
}
168
173
@@ -174,6 +179,12 @@ func (e *watchEncoder) Encode(event watch.Event) error {
174
179
encodeFunc := func (obj runtime.Object , w io.Writer ) error {
175
180
return e .doEncode (obj , event , w )
176
181
}
182
+ if event .Type == watch .Bookmark {
183
+ // Bookmark objects are small, and we don't yet support serialization for them.
184
+ // Additionally, we need to additionally transform them to support watch-list feature
185
+ event = e .watchListTransformerFn (event )
186
+ return encodeFunc (event .Object , e .framer )
187
+ }
177
188
if co , ok := event .Object .(runtime.CacheableObject ); ok {
178
189
return co .CacheEncode (e .identifier (event .Type ), encodeFunc , e .framer )
179
190
}
@@ -479,3 +490,94 @@ func asPartialObjectMetadataList(result runtime.Object, groupVersion schema.Grou
479
490
return nil , newNotAcceptableError (fmt .Sprintf ("no PartialObjectMetadataList exists in group version %s" , groupVersion ))
480
491
}
481
492
}
493
+
494
+ // watchListTransformerFunction an optional function
495
+ // applied to watchlist bookmark events that transforms
496
+ // the embedded object before sending it to a client.
497
+ type watchListTransformerFunction func (watch.Event ) watch.Event
498
+
499
+ // watchListTransformer performs transformation of
500
+ // a special watchList bookmark event.
501
+ //
502
+ // The bookmark is annotated with InitialEventsListBlueprintAnnotationKey
503
+ // and contains an empty, versioned list that we must encode in the requested format
504
+ // (e.g., protobuf, JSON, CBOR) and then store as a base64-encoded string.
505
+ type watchListTransformer struct {
506
+ initialEventsListBlueprint runtime.Object
507
+ targetGVK * schema.GroupVersionKind
508
+ negotiatedEncoder runtime.Encoder
509
+ buffer runtime.Splice
510
+ }
511
+
512
+ // createWatchListTransformerIfRequested returns a transformer function for watchlist bookmark event.
513
+ func newWatchListTransformer (initialEventsListBlueprint runtime.Object , targetGVK * schema.GroupVersionKind , negotiatedEncoder runtime.Encoder ) * watchListTransformer {
514
+ return & watchListTransformer {
515
+ initialEventsListBlueprint : initialEventsListBlueprint ,
516
+ targetGVK : targetGVK ,
517
+ negotiatedEncoder : negotiatedEncoder ,
518
+ buffer : runtime .NewSpliceBuffer (),
519
+ }
520
+ }
521
+
522
+ func (e * watchListTransformer ) transform (event watch.Event ) watch.Event {
523
+ if e .initialEventsListBlueprint == nil {
524
+ return event
525
+ }
526
+ hasAnnotation , err := storage .HasInitialEventsEndBookmarkAnnotation (event .Object )
527
+ if err != nil {
528
+ return newWatchEventErrorFor (err )
529
+ }
530
+ if ! hasAnnotation {
531
+ return event
532
+ }
533
+
534
+ if err = e .encodeInitialEventsListBlueprint (event .Object ); err != nil {
535
+ return newWatchEventErrorFor (err )
536
+ }
537
+
538
+ return event
539
+ }
540
+
541
+ func (e * watchListTransformer ) encodeInitialEventsListBlueprint (object runtime.Object ) error {
542
+ initialEventsListBlueprint , err := e .transformInitialEventsListBlueprint ()
543
+ if err != nil {
544
+ return err
545
+ }
546
+
547
+ defer e .buffer .Reset ()
548
+ if err = e .negotiatedEncoder .Encode (initialEventsListBlueprint , e .buffer ); err != nil {
549
+ return err
550
+ }
551
+ encodedInitialEventsListBlueprint := e .buffer .Bytes ()
552
+
553
+ // the storage layer creates a deep copy of the obj before modifying it.
554
+ // since the object has the annotation, we can modify it directly.
555
+ objectMeta , err := meta .Accessor (object )
556
+ if err != nil {
557
+ return err
558
+ }
559
+ annotations := objectMeta .GetAnnotations ()
560
+ annotations [metav1 .InitialEventsListBlueprintAnnotationKey ] = base64 .StdEncoding .EncodeToString (encodedInitialEventsListBlueprint )
561
+ objectMeta .SetAnnotations (annotations )
562
+
563
+ return nil
564
+ }
565
+
566
+ func (e * watchListTransformer ) transformInitialEventsListBlueprint () (runtime.Object , error ) {
567
+ if e .targetGVK != nil && e .targetGVK .Kind == "PartialObjectMetadata" {
568
+ return asPartialObjectMetadataList (e .initialEventsListBlueprint , e .targetGVK .GroupVersion ())
569
+ }
570
+ return e .initialEventsListBlueprint , nil
571
+ }
572
+
573
+ func newWatchEventErrorFor (err error ) watch.Event {
574
+ return watch.Event {
575
+ Type : watch .Error ,
576
+ Object : & metav1.Status {
577
+ Status : metav1 .StatusFailure ,
578
+ Message : err .Error (),
579
+ Reason : metav1 .StatusReasonInternalError ,
580
+ Code : http .StatusInternalServerError ,
581
+ },
582
+ }
583
+ }
0 commit comments