k8s.io/apiserver@v0.31.1/pkg/endpoints/handlers/response.go (about)

     1  /*
     2  Copyright 2017 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 handlers
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"net/http"
    25  	"reflect"
    26  
    27  	"k8s.io/apimachinery/pkg/api/errors"
    28  	"k8s.io/apimachinery/pkg/api/meta"
    29  	metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    36  	"k8s.io/apimachinery/pkg/watch"
    37  	"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
    38  	"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
    39  	"k8s.io/apiserver/pkg/endpoints/metrics"
    40  	endpointsrequest "k8s.io/apiserver/pkg/endpoints/request"
    41  
    42  	klog "k8s.io/klog/v2"
    43  )
    44  
    45  // watchEmbeddedEncoder performs encoding of the embedded object.
    46  //
    47  // NOTE: watchEmbeddedEncoder is NOT thread-safe.
    48  type watchEmbeddedEncoder struct {
    49  	encoder runtime.Encoder
    50  
    51  	ctx context.Context
    52  
    53  	// target, if non-nil, configures transformation type.
    54  	// The other options are ignored if target is nil.
    55  	target       *schema.GroupVersionKind
    56  	tableOptions *metav1.TableOptions
    57  	scope        *RequestScope
    58  
    59  	// identifier of the encoder, computed lazily
    60  	identifier runtime.Identifier
    61  }
    62  
    63  func newWatchEmbeddedEncoder(ctx context.Context, encoder runtime.Encoder, target *schema.GroupVersionKind, tableOptions *metav1.TableOptions, scope *RequestScope) *watchEmbeddedEncoder {
    64  	return &watchEmbeddedEncoder{
    65  		encoder:      encoder,
    66  		ctx:          ctx,
    67  		target:       target,
    68  		tableOptions: tableOptions,
    69  		scope:        scope,
    70  	}
    71  }
    72  
    73  // Encode implements runtime.Encoder interface.
    74  func (e *watchEmbeddedEncoder) Encode(obj runtime.Object, w io.Writer) error {
    75  	if co, ok := obj.(runtime.CacheableObject); ok {
    76  		return co.CacheEncode(e.Identifier(), e.doEncode, w)
    77  	}
    78  	return e.doEncode(obj, w)
    79  }
    80  
    81  func (e *watchEmbeddedEncoder) doEncode(obj runtime.Object, w io.Writer) error {
    82  	result, err := doTransformObject(e.ctx, obj, e.tableOptions, e.target, e.scope)
    83  	if err != nil {
    84  		utilruntime.HandleError(fmt.Errorf("failed to transform object %v: %v", reflect.TypeOf(obj), err))
    85  		result = obj
    86  	}
    87  
    88  	// When we are tranforming to a table, use the original table options when
    89  	// we should print headers only on the first object - headers should be
    90  	// omitted on subsequent events.
    91  	if e.tableOptions != nil && !e.tableOptions.NoHeaders {
    92  		e.tableOptions.NoHeaders = true
    93  		// With options change, we should recompute the identifier.
    94  		// Clearing this will trigger lazy recompute when needed.
    95  		e.identifier = ""
    96  	}
    97  
    98  	return e.encoder.Encode(result, w)
    99  }
   100  
   101  // Identifier implements runtime.Encoder interface.
   102  func (e *watchEmbeddedEncoder) Identifier() runtime.Identifier {
   103  	if e.identifier == "" {
   104  		e.identifier = e.embeddedIdentifier()
   105  	}
   106  	return e.identifier
   107  }
   108  
   109  type watchEmbeddedEncoderIdentifier struct {
   110  	Name      string              `json:"name,omitempty"`
   111  	Encoder   string              `json:"encoder,omitempty"`
   112  	Target    string              `json:"target,omitempty"`
   113  	Options   metav1.TableOptions `json:"options,omitempty"`
   114  	NoHeaders bool                `json:"noHeaders,omitempty"`
   115  }
   116  
   117  func (e *watchEmbeddedEncoder) embeddedIdentifier() runtime.Identifier {
   118  	if e.target == nil {
   119  		// If no conversion is performed, we effective only use
   120  		// the embedded identifier.
   121  		return e.encoder.Identifier()
   122  	}
   123  	identifier := watchEmbeddedEncoderIdentifier{
   124  		Name:    "watch-embedded",
   125  		Encoder: string(e.encoder.Identifier()),
   126  		Target:  e.target.String(),
   127  	}
   128  	if e.target.Kind == "Table" && e.tableOptions != nil {
   129  		identifier.Options = *e.tableOptions
   130  		identifier.NoHeaders = e.tableOptions.NoHeaders
   131  	}
   132  
   133  	result, err := json.Marshal(identifier)
   134  	if err != nil {
   135  		klog.Fatalf("Failed marshaling identifier for watchEmbeddedEncoder: %v", err)
   136  	}
   137  	return runtime.Identifier(result)
   138  }
   139  
   140  // watchEncoder performs encoding of the watch events.
   141  //
   142  // NOTE: watchEncoder is NOT thread-safe.
   143  type watchEncoder struct {
   144  	ctx             context.Context
   145  	kind            schema.GroupVersionKind
   146  	embeddedEncoder runtime.Encoder
   147  	encoder         runtime.Encoder
   148  	framer          io.Writer
   149  
   150  	buffer      runtime.Splice
   151  	eventBuffer runtime.Splice
   152  
   153  	currentEmbeddedIdentifier runtime.Identifier
   154  	identifiers               map[watch.EventType]runtime.Identifier
   155  }
   156  
   157  func newWatchEncoder(ctx context.Context, kind schema.GroupVersionKind, embeddedEncoder runtime.Encoder, encoder runtime.Encoder, framer io.Writer) *watchEncoder {
   158  	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(),
   166  	}
   167  }
   168  
   169  // Encode encodes a given watch event.
   170  // NOTE: if events object is implementing the CacheableObject interface,
   171  //
   172  //	the serialized version is cached in that object [not the event itself].
   173  func (e *watchEncoder) Encode(event watch.Event) error {
   174  	encodeFunc := func(obj runtime.Object, w io.Writer) error {
   175  		return e.doEncode(obj, event, w)
   176  	}
   177  	if co, ok := event.Object.(runtime.CacheableObject); ok {
   178  		return co.CacheEncode(e.identifier(event.Type), encodeFunc, e.framer)
   179  	}
   180  	return encodeFunc(event.Object, e.framer)
   181  }
   182  
   183  func (e *watchEncoder) doEncode(obj runtime.Object, event watch.Event, w io.Writer) error {
   184  	defer e.buffer.Reset()
   185  
   186  	if err := e.embeddedEncoder.Encode(obj, e.buffer); err != nil {
   187  		return fmt.Errorf("unable to encode watch object %T: %v", obj, err)
   188  	}
   189  
   190  	// ContentType is not required here because we are defaulting to the serializer type.
   191  	outEvent := &metav1.WatchEvent{
   192  		Type:   string(event.Type),
   193  		Object: runtime.RawExtension{Raw: e.buffer.Bytes()},
   194  	}
   195  	metrics.WatchEventsSizes.WithContext(e.ctx).WithLabelValues(e.kind.Group, e.kind.Version, e.kind.Kind).Observe(float64(len(outEvent.Object.Raw)))
   196  
   197  	defer e.eventBuffer.Reset()
   198  	if err := e.encoder.Encode(outEvent, e.eventBuffer); err != nil {
   199  		return fmt.Errorf("unable to encode watch object %T: %v (%#v)", outEvent, err, e)
   200  	}
   201  
   202  	_, err := w.Write(e.eventBuffer.Bytes())
   203  	return err
   204  }
   205  
   206  type watchEncoderIdentifier struct {
   207  	Name            string `json:"name,omitempty"`
   208  	EmbeddedEncoder string `json:"embeddedEncoder,omitempty"`
   209  	Encoder         string `json:"encoder,omitempty"`
   210  	EventType       string `json:"eventType,omitempty"`
   211  }
   212  
   213  func (e *watchEncoder) identifier(eventType watch.EventType) runtime.Identifier {
   214  	// We need to take into account that in embeddedEncoder includes table
   215  	// transformer, then its identifier is dynamic. As a result, whenever
   216  	// the identifier of embeddedEncoder changes, we need to invalidate the
   217  	// whole identifiers cache.
   218  	// TODO(wojtek-t): Can we optimize it somehow?
   219  	if e.currentEmbeddedIdentifier != e.embeddedEncoder.Identifier() {
   220  		e.currentEmbeddedIdentifier = e.embeddedEncoder.Identifier()
   221  		e.identifiers = map[watch.EventType]runtime.Identifier{}
   222  	}
   223  	if _, ok := e.identifiers[eventType]; !ok {
   224  		e.identifiers[eventType] = e.typeIdentifier(eventType)
   225  	}
   226  	return e.identifiers[eventType]
   227  }
   228  
   229  func (e *watchEncoder) typeIdentifier(eventType watch.EventType) runtime.Identifier {
   230  	// The eventType is a non-standard pattern. This is coming from the fact
   231  	// that we're effectively serializing the whole watch event, but storing
   232  	// it in serializations of the Object within the watch event.
   233  	identifier := watchEncoderIdentifier{
   234  		Name:            "watch",
   235  		EmbeddedEncoder: string(e.embeddedEncoder.Identifier()),
   236  		Encoder:         string(e.encoder.Identifier()),
   237  		EventType:       string(eventType),
   238  	}
   239  
   240  	result, err := json.Marshal(identifier)
   241  	if err != nil {
   242  		klog.Fatalf("Failed marshaling identifier for watchEncoder: %v", err)
   243  	}
   244  	return runtime.Identifier(result)
   245  }
   246  
   247  // doTransformResponseObject is used for handling all requests, including watch.
   248  func doTransformObject(ctx context.Context, obj runtime.Object, opts interface{}, target *schema.GroupVersionKind, scope *RequestScope) (runtime.Object, error) {
   249  	if _, ok := obj.(*metav1.Status); ok {
   250  		return obj, nil
   251  	}
   252  
   253  	switch {
   254  	case target == nil:
   255  		// If we ever change that from a no-op, the identifier of
   256  		// the watchEmbeddedEncoder has to be adjusted accordingly.
   257  		return obj, nil
   258  
   259  	case target.Kind == "PartialObjectMetadata":
   260  		return asPartialObjectMetadata(obj, target.GroupVersion())
   261  
   262  	case target.Kind == "PartialObjectMetadataList":
   263  		return asPartialObjectMetadataList(obj, target.GroupVersion())
   264  
   265  	case target.Kind == "Table":
   266  		options, ok := opts.(*metav1.TableOptions)
   267  		if !ok {
   268  			return nil, fmt.Errorf("unexpected TableOptions, got %T", opts)
   269  		}
   270  		return asTable(ctx, obj, options, scope, target.GroupVersion())
   271  
   272  	default:
   273  		accepted, _ := negotiation.MediaTypesForSerializer(metainternalversionscheme.Codecs)
   274  		err := negotiation.NewNotAcceptableError(accepted)
   275  		return nil, err
   276  	}
   277  }
   278  
   279  // optionsForTransform will load and validate any additional query parameter options for
   280  // a conversion or return an error.
   281  func optionsForTransform(mediaType negotiation.MediaTypeOptions, req *http.Request) (interface{}, error) {
   282  	switch target := mediaType.Convert; {
   283  	case target == nil:
   284  	case target.Kind == "Table" && (target.GroupVersion() == metav1beta1.SchemeGroupVersion || target.GroupVersion() == metav1.SchemeGroupVersion):
   285  		opts := &metav1.TableOptions{}
   286  		if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), metav1.SchemeGroupVersion, opts); err != nil {
   287  			return nil, err
   288  		}
   289  		switch errs := validation.ValidateTableOptions(opts); len(errs) {
   290  		case 0:
   291  			return opts, nil
   292  		case 1:
   293  			return nil, errors.NewBadRequest(fmt.Sprintf("Unable to convert to Table as requested: %v", errs[0].Error()))
   294  		default:
   295  			return nil, errors.NewBadRequest(fmt.Sprintf("Unable to convert to Table as requested: %v", errs))
   296  		}
   297  	}
   298  	return nil, nil
   299  }
   300  
   301  // targetEncodingForTransform returns the appropriate serializer for the input media type
   302  func targetEncodingForTransform(scope *RequestScope, mediaType negotiation.MediaTypeOptions, req *http.Request) (schema.GroupVersionKind, runtime.NegotiatedSerializer, bool) {
   303  	switch target := mediaType.Convert; {
   304  	case target == nil:
   305  	case (target.Kind == "PartialObjectMetadata" || target.Kind == "PartialObjectMetadataList" || target.Kind == "Table") &&
   306  		(target.GroupVersion() == metav1beta1.SchemeGroupVersion || target.GroupVersion() == metav1.SchemeGroupVersion):
   307  		return *target, metainternalversionscheme.Codecs, true
   308  	}
   309  	return scope.Kind, scope.Serializer, false
   310  }
   311  
   312  // transformResponseObject takes an object loaded from storage and performs any necessary transformations.
   313  // Will write the complete response object.
   314  // transformResponseObject is used only for handling non-streaming requests.
   315  func transformResponseObject(ctx context.Context, scope *RequestScope, req *http.Request, w http.ResponseWriter, statusCode int, mediaType negotiation.MediaTypeOptions, result runtime.Object) {
   316  	options, err := optionsForTransform(mediaType, req)
   317  	if err != nil {
   318  		scope.err(err, w, req)
   319  		return
   320  	}
   321  
   322  	// ensure that for empty lists we don't return <nil> items.
   323  	// This is safe to modify without deep-copying the object, as
   324  	// List objects themselves are never cached.
   325  	if meta.IsListType(result) && meta.LenList(result) == 0 {
   326  		if err := meta.SetList(result, []runtime.Object{}); err != nil {
   327  			scope.err(err, w, req)
   328  			return
   329  		}
   330  	}
   331  
   332  	var obj runtime.Object
   333  	do := func() {
   334  		obj, err = doTransformObject(ctx, result, options, mediaType.Convert, scope)
   335  	}
   336  	endpointsrequest.TrackTransformResponseObjectLatency(ctx, do)
   337  
   338  	if err != nil {
   339  		scope.err(err, w, req)
   340  		return
   341  	}
   342  	kind, serializer, _ := targetEncodingForTransform(scope, mediaType, req)
   343  	responsewriters.WriteObjectNegotiated(serializer, scope, kind.GroupVersion(), w, req, statusCode, obj, false)
   344  }
   345  
   346  // errNotAcceptable indicates Accept negotiation has failed
   347  type errNotAcceptable struct {
   348  	message string
   349  }
   350  
   351  func newNotAcceptableError(message string) error {
   352  	return errNotAcceptable{message}
   353  }
   354  
   355  func (e errNotAcceptable) Error() string {
   356  	return e.message
   357  }
   358  
   359  func (e errNotAcceptable) Status() metav1.Status {
   360  	return metav1.Status{
   361  		Status:  metav1.StatusFailure,
   362  		Code:    http.StatusNotAcceptable,
   363  		Reason:  metav1.StatusReason("NotAcceptable"),
   364  		Message: e.Error(),
   365  	}
   366  }
   367  
   368  func asTable(ctx context.Context, result runtime.Object, opts *metav1.TableOptions, scope *RequestScope, groupVersion schema.GroupVersion) (runtime.Object, error) {
   369  	switch groupVersion {
   370  	case metav1beta1.SchemeGroupVersion, metav1.SchemeGroupVersion:
   371  	default:
   372  		return nil, newNotAcceptableError(fmt.Sprintf("no Table exists in group version %s", groupVersion))
   373  	}
   374  
   375  	obj, err := scope.TableConvertor.ConvertToTable(ctx, result, opts)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  
   380  	table := (*metav1.Table)(obj)
   381  
   382  	for i := range table.Rows {
   383  		item := &table.Rows[i]
   384  		switch opts.IncludeObject {
   385  		case metav1.IncludeObject:
   386  			item.Object.Object, err = scope.Convertor.ConvertToVersion(item.Object.Object, scope.Kind.GroupVersion())
   387  			if err != nil {
   388  				return nil, err
   389  			}
   390  		// TODO: rely on defaulting for the value here?
   391  		case metav1.IncludeMetadata, "":
   392  			m, err := meta.Accessor(item.Object.Object)
   393  			if err != nil {
   394  				return nil, err
   395  			}
   396  			// TODO: turn this into an internal type and do conversion in order to get object kind automatically set?
   397  			partial := meta.AsPartialObjectMetadata(m)
   398  			partial.GetObjectKind().SetGroupVersionKind(groupVersion.WithKind("PartialObjectMetadata"))
   399  			item.Object.Object = partial
   400  		case metav1.IncludeNone:
   401  			item.Object.Object = nil
   402  		default:
   403  			err = errors.NewBadRequest(fmt.Sprintf("unrecognized includeObject value: %q", opts.IncludeObject))
   404  			return nil, err
   405  		}
   406  	}
   407  
   408  	return table, nil
   409  }
   410  
   411  func asPartialObjectMetadata(result runtime.Object, groupVersion schema.GroupVersion) (runtime.Object, error) {
   412  	if meta.IsListType(result) {
   413  		err := newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadata, but the requested object is a list (%T)", result))
   414  		return nil, err
   415  	}
   416  	switch groupVersion {
   417  	case metav1beta1.SchemeGroupVersion, metav1.SchemeGroupVersion:
   418  	default:
   419  		return nil, newNotAcceptableError(fmt.Sprintf("no PartialObjectMetadataList exists in group version %s", groupVersion))
   420  	}
   421  	m, err := meta.Accessor(result)
   422  	if err != nil {
   423  		return nil, err
   424  	}
   425  	partial := meta.AsPartialObjectMetadata(m)
   426  	partial.GetObjectKind().SetGroupVersionKind(groupVersion.WithKind("PartialObjectMetadata"))
   427  	return partial, nil
   428  }
   429  
   430  func asPartialObjectMetadataList(result runtime.Object, groupVersion schema.GroupVersion) (runtime.Object, error) {
   431  	li, ok := result.(metav1.ListInterface)
   432  	if !ok {
   433  		return nil, newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadataList, but the requested object is not a list (%T)", result))
   434  	}
   435  
   436  	gvk := groupVersion.WithKind("PartialObjectMetadata")
   437  	switch {
   438  	case groupVersion == metav1beta1.SchemeGroupVersion:
   439  		list := &metav1beta1.PartialObjectMetadataList{}
   440  		err := meta.EachListItem(result, func(obj runtime.Object) error {
   441  			m, err := meta.Accessor(obj)
   442  			if err != nil {
   443  				return err
   444  			}
   445  			partial := meta.AsPartialObjectMetadata(m)
   446  			partial.GetObjectKind().SetGroupVersionKind(gvk)
   447  			list.Items = append(list.Items, *partial)
   448  			return nil
   449  		})
   450  		if err != nil {
   451  			return nil, err
   452  		}
   453  		list.ResourceVersion = li.GetResourceVersion()
   454  		list.Continue = li.GetContinue()
   455  		list.RemainingItemCount = li.GetRemainingItemCount()
   456  		return list, nil
   457  
   458  	case groupVersion == metav1.SchemeGroupVersion:
   459  		list := &metav1.PartialObjectMetadataList{}
   460  		err := meta.EachListItem(result, func(obj runtime.Object) error {
   461  			m, err := meta.Accessor(obj)
   462  			if err != nil {
   463  				return err
   464  			}
   465  			partial := meta.AsPartialObjectMetadata(m)
   466  			partial.GetObjectKind().SetGroupVersionKind(gvk)
   467  			list.Items = append(list.Items, *partial)
   468  			return nil
   469  		})
   470  		if err != nil {
   471  			return nil, err
   472  		}
   473  		list.ResourceVersion = li.GetResourceVersion()
   474  		list.Continue = li.GetContinue()
   475  		list.RemainingItemCount = li.GetRemainingItemCount()
   476  		return list, nil
   477  
   478  	default:
   479  		return nil, newNotAcceptableError(fmt.Sprintf("no PartialObjectMetadataList exists in group version %s", groupVersion))
   480  	}
   481  }