| /* |
| Copyright 2017 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package handlers |
| |
| import ( |
| "context" |
| "fmt" |
| "math/rand" |
| "net/http" |
| "net/url" |
| "strings" |
| "time" |
| |
| "github.com/golang/glog" |
| |
| "k8s.io/apimachinery/pkg/api/errors" |
| "k8s.io/apimachinery/pkg/api/meta" |
| metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/fields" |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apiserver/pkg/endpoints/metrics" |
| "k8s.io/apiserver/pkg/endpoints/request" |
| "k8s.io/apiserver/pkg/registry/rest" |
| utiltrace "k8s.io/apiserver/pkg/util/trace" |
| ) |
| |
| // getterFunc performs a get request with the given context and object name. The request |
| // may be used to deserialize an options object to pass to the getter. |
| type getterFunc func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) |
| |
| // getResourceHandler is an HTTP handler function for get requests. It delegates to the |
| // passed-in getterFunc to perform the actual get. |
| func getResourceHandler(scope RequestScope, getter getterFunc) http.HandlerFunc { |
| return func(w http.ResponseWriter, req *http.Request) { |
| trace := utiltrace.New("Get " + req.URL.Path) |
| defer trace.LogIfLong(500 * time.Millisecond) |
| |
| namespace, name, err := scope.Namer.Name(req) |
| if err != nil { |
| scope.err(err, w, req) |
| return |
| } |
| ctx := req.Context() |
| ctx = request.WithNamespace(ctx, namespace) |
| |
| result, err := getter(ctx, name, req, trace) |
| if err != nil { |
| scope.err(err, w, req) |
| return |
| } |
| requestInfo, ok := request.RequestInfoFrom(ctx) |
| if !ok { |
| scope.err(fmt.Errorf("missing requestInfo"), w, req) |
| return |
| } |
| if err := setSelfLink(result, requestInfo, scope.Namer); err != nil { |
| scope.err(err, w, req) |
| return |
| } |
| |
| trace.Step("About to write a response") |
| transformResponseObject(ctx, scope, req, w, http.StatusOK, result) |
| } |
| } |
| |
| // GetResource returns a function that handles retrieving a single resource from a rest.Storage object. |
| func GetResource(r rest.Getter, e rest.Exporter, scope RequestScope) http.HandlerFunc { |
| return getResourceHandler(scope, |
| func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) { |
| // check for export |
| options := metav1.GetOptions{} |
| if values := req.URL.Query(); len(values) > 0 { |
| exports := metav1.ExportOptions{} |
| if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &exports); err != nil { |
| err = errors.NewBadRequest(err.Error()) |
| return nil, err |
| } |
| if exports.Export { |
| if e == nil { |
| return nil, errors.NewBadRequest(fmt.Sprintf("export of %q is not supported", scope.Resource.Resource)) |
| } |
| return e.Export(ctx, name, exports) |
| } |
| if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &options); err != nil { |
| err = errors.NewBadRequest(err.Error()) |
| return nil, err |
| } |
| } |
| if trace != nil { |
| trace.Step("About to Get from storage") |
| } |
| return r.Get(ctx, name, &options) |
| }) |
| } |
| |
| // GetResourceWithOptions returns a function that handles retrieving a single resource from a rest.Storage object. |
| func GetResourceWithOptions(r rest.GetterWithOptions, scope RequestScope, isSubresource bool) http.HandlerFunc { |
| return getResourceHandler(scope, |
| func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) { |
| opts, subpath, subpathKey := r.NewGetOptions() |
| trace.Step("About to process Get options") |
| if err := getRequestOptions(req, scope, opts, subpath, subpathKey, isSubresource); err != nil { |
| err = errors.NewBadRequest(err.Error()) |
| return nil, err |
| } |
| if trace != nil { |
| trace.Step("About to Get from storage") |
| } |
| return r.Get(ctx, name, opts) |
| }) |
| } |
| |
| // getRequestOptions parses out options and can include path information. The path information shouldn't include the subresource. |
| func getRequestOptions(req *http.Request, scope RequestScope, into runtime.Object, subpath bool, subpathKey string, isSubresource bool) error { |
| if into == nil { |
| return nil |
| } |
| |
| query := req.URL.Query() |
| if subpath { |
| newQuery := make(url.Values) |
| for k, v := range query { |
| newQuery[k] = v |
| } |
| |
| ctx := req.Context() |
| requestInfo, _ := request.RequestInfoFrom(ctx) |
| startingIndex := 2 |
| if isSubresource { |
| startingIndex = 3 |
| } |
| |
| p := strings.Join(requestInfo.Parts[startingIndex:], "/") |
| |
| // ensure non-empty subpaths correctly reflect a leading slash |
| if len(p) > 0 && !strings.HasPrefix(p, "/") { |
| p = "/" + p |
| } |
| |
| // ensure subpaths correctly reflect the presence of a trailing slash on the original request |
| if strings.HasSuffix(requestInfo.Path, "/") && !strings.HasSuffix(p, "/") { |
| p += "/" |
| } |
| |
| newQuery[subpathKey] = []string{p} |
| query = newQuery |
| } |
| return scope.ParameterCodec.DecodeParameters(query, scope.Kind.GroupVersion(), into) |
| } |
| |
| func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch bool, minRequestTimeout time.Duration) http.HandlerFunc { |
| return func(w http.ResponseWriter, req *http.Request) { |
| // For performance tracking purposes. |
| trace := utiltrace.New("List " + req.URL.Path) |
| |
| namespace, err := scope.Namer.Namespace(req) |
| if err != nil { |
| scope.err(err, w, req) |
| return |
| } |
| |
| // Watches for single objects are routed to this function. |
| // Treat a name parameter the same as a field selector entry. |
| hasName := true |
| _, name, err := scope.Namer.Name(req) |
| if err != nil { |
| hasName = false |
| } |
| |
| ctx := req.Context() |
| ctx = request.WithNamespace(ctx, namespace) |
| |
| opts := metainternalversion.ListOptions{} |
| if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &opts); err != nil { |
| err = errors.NewBadRequest(err.Error()) |
| scope.err(err, w, req) |
| return |
| } |
| |
| // transform fields |
| // TODO: DecodeParametersInto should do this. |
| if opts.FieldSelector != nil { |
| fn := func(label, value string) (newLabel, newValue string, err error) { |
| return scope.Convertor.ConvertFieldLabel(scope.Kind.GroupVersion().String(), scope.Kind.Kind, label, value) |
| } |
| if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil { |
| // TODO: allow bad request to set field causes based on query parameters |
| err = errors.NewBadRequest(err.Error()) |
| scope.err(err, w, req) |
| return |
| } |
| } |
| |
| if hasName { |
| // metadata.name is the canonical internal name. |
| // SelectionPredicate will notice that this is a request for |
| // a single object and optimize the storage query accordingly. |
| nameSelector := fields.OneTermEqualSelector("metadata.name", name) |
| |
| // Note that fieldSelector setting explicitly the "metadata.name" |
| // will result in reaching this branch (as the value of that field |
| // is propagated to requestInfo as the name parameter. |
| // That said, the allowed field selectors in this branch are: |
| // nil, fields.Everything and field selector matching metadata.name |
| // for our name. |
| if opts.FieldSelector != nil && !opts.FieldSelector.Empty() { |
| selectedName, ok := opts.FieldSelector.RequiresExactMatch("metadata.name") |
| if !ok || name != selectedName { |
| scope.err(errors.NewBadRequest("fieldSelector metadata.name doesn't match requested name"), w, req) |
| return |
| } |
| } else { |
| opts.FieldSelector = nameSelector |
| } |
| } |
| |
| if opts.Watch || forceWatch { |
| if rw == nil { |
| scope.err(errors.NewMethodNotSupported(scope.Resource.GroupResource(), "watch"), w, req) |
| return |
| } |
| // TODO: Currently we explicitly ignore ?timeout= and use only ?timeoutSeconds=. |
| timeout := time.Duration(0) |
| if opts.TimeoutSeconds != nil { |
| timeout = time.Duration(*opts.TimeoutSeconds) * time.Second |
| } |
| if timeout == 0 && minRequestTimeout > 0 { |
| timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0)) |
| } |
| glog.V(2).Infof("Starting watch for %s, rv=%s labels=%s fields=%s timeout=%s", req.URL.Path, opts.ResourceVersion, opts.LabelSelector, opts.FieldSelector, timeout) |
| |
| watcher, err := rw.Watch(ctx, &opts) |
| if err != nil { |
| scope.err(err, w, req) |
| return |
| } |
| requestInfo, _ := request.RequestInfoFrom(ctx) |
| metrics.RecordLongRunning(req, requestInfo, func() { |
| serveWatch(watcher, scope, req, w, timeout) |
| }) |
| return |
| } |
| |
| // Log only long List requests (ignore Watch). |
| defer trace.LogIfLong(500 * time.Millisecond) |
| trace.Step("About to List from storage") |
| result, err := r.List(ctx, &opts) |
| if err != nil { |
| scope.err(err, w, req) |
| return |
| } |
| trace.Step("Listing from storage done") |
| numberOfItems, err := setListSelfLink(result, ctx, req, scope.Namer) |
| if err != nil { |
| scope.err(err, w, req) |
| return |
| } |
| trace.Step("Self-linking done") |
| // Ensure empty lists return a non-nil items slice |
| if numberOfItems == 0 && meta.IsListType(result) { |
| if err := meta.SetList(result, []runtime.Object{}); err != nil { |
| scope.err(err, w, req) |
| return |
| } |
| } |
| |
| transformResponseObject(ctx, scope, req, w, http.StatusOK, result) |
| trace.Step(fmt.Sprintf("Writing http response done (%d items)", numberOfItems)) |
| } |
| } |