k8s.io/apiserver@v0.31.1/pkg/endpoints/handlers/get.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  	"fmt"
    22  	"math/rand"
    23  	"net/http"
    24  	"net/url"
    25  	"strings"
    26  	"time"
    27  
    28  	"go.opentelemetry.io/otel/attribute"
    29  
    30  	"k8s.io/apimachinery/pkg/api/errors"
    31  	"k8s.io/apimachinery/pkg/api/meta"
    32  	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
    33  	metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
    34  	metainternalversionvalidation "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/fields"
    37  	"k8s.io/apimachinery/pkg/runtime"
    38  	"k8s.io/apimachinery/pkg/runtime/schema"
    39  	"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
    40  	"k8s.io/apiserver/pkg/endpoints/metrics"
    41  	"k8s.io/apiserver/pkg/endpoints/request"
    42  	"k8s.io/apiserver/pkg/features"
    43  	"k8s.io/apiserver/pkg/registry/rest"
    44  	"k8s.io/apiserver/pkg/server/routine"
    45  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    46  	"k8s.io/component-base/tracing"
    47  	"k8s.io/klog/v2"
    48  )
    49  
    50  // getterFunc performs a get request with the given context and object name. The request
    51  // may be used to deserialize an options object to pass to the getter.
    52  type getterFunc func(ctx context.Context, name string, req *http.Request) (runtime.Object, error)
    53  
    54  // getResourceHandler is an HTTP handler function for get requests. It delegates to the
    55  // passed-in getterFunc to perform the actual get.
    56  func getResourceHandler(scope *RequestScope, getter getterFunc) http.HandlerFunc {
    57  	return func(w http.ResponseWriter, req *http.Request) {
    58  		ctx := req.Context()
    59  		ctx, span := tracing.Start(ctx, "Get", traceFields(req)...)
    60  		defer span.End(500 * time.Millisecond)
    61  
    62  		namespace, name, err := scope.Namer.Name(req)
    63  		if err != nil {
    64  			scope.err(err, w, req)
    65  			return
    66  		}
    67  		ctx = request.WithNamespace(ctx, namespace)
    68  
    69  		outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
    70  		if err != nil {
    71  			scope.err(err, w, req)
    72  			return
    73  		}
    74  
    75  		result, err := getter(ctx, name, req)
    76  		if err != nil {
    77  			scope.err(err, w, req)
    78  			return
    79  		}
    80  
    81  		span.AddEvent("About to write a response")
    82  		defer span.AddEvent("Writing http response done")
    83  		transformResponseObject(ctx, scope, req, w, http.StatusOK, outputMediaType, result)
    84  	}
    85  }
    86  
    87  // GetResource returns a function that handles retrieving a single resource from a rest.Storage object.
    88  func GetResource(r rest.Getter, scope *RequestScope) http.HandlerFunc {
    89  	return getResourceHandler(scope,
    90  		func(ctx context.Context, name string, req *http.Request) (runtime.Object, error) {
    91  			// check for export
    92  			options := metav1.GetOptions{}
    93  			if values := req.URL.Query(); len(values) > 0 {
    94  				if len(values["export"]) > 0 {
    95  					exportBool := true
    96  					exportStrings := values["export"]
    97  					err := runtime.Convert_Slice_string_To_bool(&exportStrings, &exportBool, nil)
    98  					if err != nil {
    99  						return nil, errors.NewBadRequest(fmt.Sprintf("the export parameter cannot be parsed: %v", err))
   100  					}
   101  					if exportBool {
   102  						return nil, errors.NewBadRequest("the export parameter, deprecated since v1.14, is no longer supported")
   103  					}
   104  				}
   105  				if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &options); err != nil {
   106  					err = errors.NewBadRequest(err.Error())
   107  					return nil, err
   108  				}
   109  			}
   110  			tracing.SpanFromContext(ctx).AddEvent("About to Get from storage")
   111  			return r.Get(ctx, name, &options)
   112  		})
   113  }
   114  
   115  // GetResourceWithOptions returns a function that handles retrieving a single resource from a rest.Storage object.
   116  func GetResourceWithOptions(r rest.GetterWithOptions, scope *RequestScope, isSubresource bool) http.HandlerFunc {
   117  	return getResourceHandler(scope,
   118  		func(ctx context.Context, name string, req *http.Request) (runtime.Object, error) {
   119  			opts, subpath, subpathKey := r.NewGetOptions()
   120  			span := tracing.SpanFromContext(ctx)
   121  			span.AddEvent("About to process Get options")
   122  			if err := getRequestOptions(req, scope, opts, subpath, subpathKey, isSubresource); err != nil {
   123  				err = errors.NewBadRequest(err.Error())
   124  				return nil, err
   125  			}
   126  			span.AddEvent("About to Get from storage")
   127  			return r.Get(ctx, name, opts)
   128  		})
   129  }
   130  
   131  // getRequestOptions parses out options and can include path information.  The path information shouldn't include the subresource.
   132  func getRequestOptions(req *http.Request, scope *RequestScope, into runtime.Object, subpath bool, subpathKey string, isSubresource bool) error {
   133  	if into == nil {
   134  		return nil
   135  	}
   136  
   137  	query := req.URL.Query()
   138  	if subpath {
   139  		newQuery := make(url.Values)
   140  		for k, v := range query {
   141  			newQuery[k] = v
   142  		}
   143  
   144  		ctx := req.Context()
   145  		requestInfo, _ := request.RequestInfoFrom(ctx)
   146  		startingIndex := 2
   147  		if isSubresource {
   148  			startingIndex = 3
   149  		}
   150  
   151  		p := strings.Join(requestInfo.Parts[startingIndex:], "/")
   152  
   153  		// ensure non-empty subpaths correctly reflect a leading slash
   154  		if len(p) > 0 && !strings.HasPrefix(p, "/") {
   155  			p = "/" + p
   156  		}
   157  
   158  		// ensure subpaths correctly reflect the presence of a trailing slash on the original request
   159  		if strings.HasSuffix(requestInfo.Path, "/") && !strings.HasSuffix(p, "/") {
   160  			p += "/"
   161  		}
   162  
   163  		newQuery[subpathKey] = []string{p}
   164  		query = newQuery
   165  	}
   166  	return scope.ParameterCodec.DecodeParameters(query, scope.Kind.GroupVersion(), into)
   167  }
   168  
   169  func ListResource(r rest.Lister, rw rest.Watcher, scope *RequestScope, forceWatch bool, minRequestTimeout time.Duration) http.HandlerFunc {
   170  	return func(w http.ResponseWriter, req *http.Request) {
   171  		ctx := req.Context()
   172  		// For performance tracking purposes.
   173  		ctx, span := tracing.Start(ctx, "List", traceFields(req)...)
   174  
   175  		namespace, err := scope.Namer.Namespace(req)
   176  		if err != nil {
   177  			scope.err(err, w, req)
   178  			return
   179  		}
   180  
   181  		// Watches for single objects are routed to this function.
   182  		// Treat a name parameter the same as a field selector entry.
   183  		hasName := true
   184  		_, name, err := scope.Namer.Name(req)
   185  		if err != nil {
   186  			hasName = false
   187  		}
   188  
   189  		ctx = request.WithNamespace(ctx, namespace)
   190  
   191  		outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
   192  		if err != nil {
   193  			scope.err(err, w, req)
   194  			return
   195  		}
   196  
   197  		opts := metainternalversion.ListOptions{}
   198  		if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &opts); err != nil {
   199  			err = errors.NewBadRequest(err.Error())
   200  			scope.err(err, w, req)
   201  			return
   202  		}
   203  
   204  		metainternalversion.SetListOptionsDefaults(&opts, utilfeature.DefaultFeatureGate.Enabled(features.WatchList))
   205  		if errs := metainternalversionvalidation.ValidateListOptions(&opts, utilfeature.DefaultFeatureGate.Enabled(features.WatchList)); len(errs) > 0 {
   206  			err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "ListOptions"}, "", errs)
   207  			scope.err(err, w, req)
   208  			return
   209  		}
   210  
   211  		// transform fields
   212  		// TODO: DecodeParametersInto should do this.
   213  		if opts.FieldSelector != nil {
   214  			fn := func(label, value string) (newLabel, newValue string, err error) {
   215  				return scope.Convertor.ConvertFieldLabel(scope.Kind, label, value)
   216  			}
   217  			if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil {
   218  				// TODO: allow bad request to set field causes based on query parameters
   219  				err = errors.NewBadRequest(err.Error())
   220  				scope.err(err, w, req)
   221  				return
   222  			}
   223  		}
   224  
   225  		if hasName {
   226  			// metadata.name is the canonical internal name.
   227  			// SelectionPredicate will notice that this is a request for
   228  			// a single object and optimize the storage query accordingly.
   229  			nameSelector := fields.OneTermEqualSelector("metadata.name", name)
   230  
   231  			// Note that fieldSelector setting explicitly the "metadata.name"
   232  			// will result in reaching this branch (as the value of that field
   233  			// is propagated to requestInfo as the name parameter.
   234  			// That said, the allowed field selectors in this branch are:
   235  			// nil, fields.Everything and field selector matching metadata.name
   236  			// for our name.
   237  			if opts.FieldSelector != nil && !opts.FieldSelector.Empty() {
   238  				selectedName, ok := opts.FieldSelector.RequiresExactMatch("metadata.name")
   239  				if !ok || name != selectedName {
   240  					scope.err(errors.NewBadRequest("fieldSelector metadata.name doesn't match requested name"), w, req)
   241  					return
   242  				}
   243  			} else {
   244  				opts.FieldSelector = nameSelector
   245  			}
   246  		}
   247  
   248  		if opts.Watch || forceWatch {
   249  			if rw == nil {
   250  				scope.err(errors.NewMethodNotSupported(scope.Resource.GroupResource(), "watch"), w, req)
   251  				return
   252  			}
   253  			// TODO: Currently we explicitly ignore ?timeout= and use only ?timeoutSeconds=.
   254  			timeout := time.Duration(0)
   255  			if opts.TimeoutSeconds != nil {
   256  				timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
   257  			}
   258  			if timeout == 0 && minRequestTimeout > 0 {
   259  				timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0))
   260  			}
   261  			klog.V(3).InfoS("Starting watch", "path", req.URL.Path, "resourceVersion", opts.ResourceVersion, "labels", opts.LabelSelector, "fields", opts.FieldSelector, "timeout", timeout)
   262  			ctx, cancel := context.WithTimeout(ctx, timeout)
   263  			defer func() { cancel() }()
   264  			watcher, err := rw.Watch(ctx, &opts)
   265  			if err != nil {
   266  				scope.err(err, w, req)
   267  				return
   268  			}
   269  			handler, err := serveWatchHandler(watcher, scope, outputMediaType, req, w, timeout, metrics.CleanListScope(ctx, &opts))
   270  			if err != nil {
   271  				scope.err(err, w, req)
   272  				return
   273  			}
   274  			// Invalidate cancel() to defer until serve() is complete.
   275  			deferredCancel := cancel
   276  			cancel = func() {}
   277  
   278  			serve := func() {
   279  				defer deferredCancel()
   280  				requestInfo, _ := request.RequestInfoFrom(ctx)
   281  				metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() {
   282  					defer watcher.Stop()
   283  					handler.ServeHTTP(w, req)
   284  				})
   285  			}
   286  
   287  			// Run watch serving in a separate goroutine to allow freeing current stack memory
   288  			t := routine.TaskFrom(req.Context())
   289  			if t != nil {
   290  				t.Func = serve
   291  			} else {
   292  				serve()
   293  			}
   294  			return
   295  		}
   296  
   297  		// Log only long List requests (ignore Watch).
   298  		defer span.End(500 * time.Millisecond)
   299  		span.AddEvent("About to List from storage")
   300  		result, err := r.List(ctx, &opts)
   301  		if err != nil {
   302  			scope.err(err, w, req)
   303  			return
   304  		}
   305  		span.AddEvent("Listing from storage done")
   306  		defer span.AddEvent("Writing http response done", attribute.Int("count", meta.LenList(result)))
   307  		transformResponseObject(ctx, scope, req, w, http.StatusOK, outputMediaType, result)
   308  	}
   309  }