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 }