From aa45703a99316d67a9825b7c051a37be5cc3ac80 Mon Sep 17 00:00:00 2001 From: Harsh Thakur Date: Thu, 10 Jun 2021 08:24:49 +0000 Subject: [PATCH] Enable prefix based routing (#2047) * Enable prefix based routing Signed-off-by: Harsh Thakur * Optimize checking condition Signed-off-by: Sanket Sudake * Disable few tests * disable router modification for now * run code generator * Collect fission dump in CI * Enable all tests back Signed-off-by: Sanket Sudake * Remove unwanted code Signed-off-by: Sanket Sudake * Add prefix support at more places and couple of todo's Signed-off-by: Sanket Sudake * Few more changes Signed-off-by: Sanket Sudake * Improve function trim support * Support for prefix based urls in fission function test Signed-off-by: Sanket Sudake * multi route for fission function test Signed-off-by: Sanket Sudake * Improve validations in trigger creations * Adjust leading / in url from fission Signed-off-by: Sanket Sudake * Change suburl to subpath for function test Signed-off-by: Sanket Sudake Co-authored-by: Sanket Sudake --- crds/v1/fission.io_httptriggers.yaml | 4 ++- pkg/apis/core/v1/types.go | 8 +++++ pkg/apis/core/v1/zz_generated.deepcopy.go | 5 ++++ pkg/canaryconfigmgr/canaryConfigMgr.go | 10 +++++-- pkg/controller/httpTriggerApi.go | 6 +++- pkg/fission-cli/cmd/function/command.go | 3 +- pkg/fission-cli/cmd/function/create.go | 10 +++---- pkg/fission-cli/cmd/function/test.go | 16 +++++----- pkg/fission-cli/cmd/httptrigger/command.go | 8 ++--- pkg/fission-cli/cmd/httptrigger/create.go | 23 +++++++++++++-- pkg/fission-cli/cmd/httptrigger/update.go | 29 ++++++++++++++++-- pkg/fission-cli/cmd/spec/list.go | 4 ++- pkg/fission-cli/cmd/spec/spec.go | 5 ---- pkg/fission-cli/flag/flag.go | 2 ++ pkg/fission-cli/flag/key/key.go | 2 ++ pkg/fission-cli/util/util.go | 8 +++++ pkg/router/functionHandler.go | 34 +++++++++++++++++----- pkg/router/httpTriggers.go | 11 +++++-- pkg/router/util/util.go | 3 ++ test/utils.sh | 2 +- 20 files changed, 149 insertions(+), 44 deletions(-) diff --git a/crds/v1/fission.io_httptriggers.yaml b/crds/v1/fission.io_httptriggers.yaml index a998a0c9..568dd275 100644 --- a/crds/v1/fission.io_httptriggers.yaml +++ b/crds/v1/fission.io_httptriggers.yaml @@ -79,12 +79,14 @@ spec: method: description: HTTP method to access a function. type: string + prefix: + description: 'Prefix with which functions are exposed. NOTE: Prefix takes precedence over URL/RelativeURL. Note that it does not treat slashes specially ("/foobar/" will be matched by the prefix "/foobar").' + type: string relativeurl: description: RelativeURL is the exposed URL for external client to access a function with. type: string required: - functionref - - relativeurl type: object required: - metadata diff --git a/pkg/apis/core/v1/types.go b/pkg/apis/core/v1/types.go index 020337e0..cd7b5dcb 100644 --- a/pkg/apis/core/v1/types.go +++ b/pkg/apis/core/v1/types.go @@ -648,8 +648,16 @@ type ( Host string `json:"host"` // RelativeURL is the exposed URL for external client to access a function with. + // +optional RelativeURL string `json:"relativeurl"` + // Prefix with which functions are exposed. + // NOTE: Prefix takes precedence over URL/RelativeURL. + // Note that it does not treat slashes specially ("/foobar/" will be matched by + // the prefix "/foobar"). + // +optional + Prefix *string `json:"prefix,omitempty"` + // HTTP method to access a function. // +optional Method string `json:"method"` diff --git a/pkg/apis/core/v1/zz_generated.deepcopy.go b/pkg/apis/core/v1/zz_generated.deepcopy.go index c2df0c2d..b700a329 100644 --- a/pkg/apis/core/v1/zz_generated.deepcopy.go +++ b/pkg/apis/core/v1/zz_generated.deepcopy.go @@ -507,6 +507,11 @@ func (in *HTTPTriggerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPTriggerSpec) DeepCopyInto(out *HTTPTriggerSpec) { *out = *in + if in.Prefix != nil { + in, out := &in.Prefix, &out.Prefix + *out = new(string) + **out = **in + } in.FunctionReference.DeepCopyInto(&out.FunctionReference) in.IngressConfig.DeepCopyInto(&out.IngressConfig) return diff --git a/pkg/canaryconfigmgr/canaryConfigMgr.go b/pkg/canaryconfigmgr/canaryConfigMgr.go index 1ebbb862..491c9b12 100644 --- a/pkg/canaryconfigmgr/canaryConfigMgr.go +++ b/pkg/canaryconfigmgr/canaryConfigMgr.go @@ -276,7 +276,13 @@ func (canaryCfgMgr *canaryConfigMgr) RollForwardOrBack(canaryConfig *fv1.CanaryC if triggerObj.Spec.FunctionReference.Type == fv1.FunctionReferenceTypeFunctionWeights && triggerObj.Spec.FunctionReference.FunctionWeights[canaryConfig.Spec.NewFunction] != 0 { - failurePercent, err := canaryCfgMgr.promClient.GetFunctionFailurePercentage(triggerObj.Spec.RelativeURL, triggerObj.Spec.Method, + var urlPath string + if triggerObj.Spec.Prefix != nil && *triggerObj.Spec.Prefix != "" { + urlPath = *triggerObj.Spec.Prefix + } else { + urlPath = triggerObj.Spec.RelativeURL + } + failurePercent, err := canaryCfgMgr.promClient.GetFunctionFailurePercentage(urlPath, triggerObj.Spec.Method, canaryConfig.Spec.NewFunction, canaryConfig.ObjectMeta.Namespace, canaryConfig.Spec.WeightIncrementDuration) if err != nil { @@ -298,7 +304,7 @@ func (canaryCfgMgr *canaryConfigMgr) RollForwardOrBack(canaryConfig *fv1.CanaryC if failurePercent == -1 { // this means there were no requests triggered to this url during this window. return here and check back // during next iteration - canaryCfgMgr.logger.Info("total requests received for url is 0", zap.String("url", triggerObj.Spec.RelativeURL)) + canaryCfgMgr.logger.Info("total requests received for url is 0", zap.String("url", urlPath)) return } diff --git a/pkg/controller/httpTriggerApi.go b/pkg/controller/httpTriggerApi.go index 57e2f368..395d8b38 100644 --- a/pkg/controller/httpTriggerApi.go +++ b/pkg/controller/httpTriggerApi.go @@ -132,7 +132,11 @@ func (a *API) checkHTTPTriggerDuplicates(t *fv1.HTTPTrigger) error { // Same resource. No need to check. continue } - if ht.Spec.RelativeURL == t.Spec.RelativeURL && ht.Spec.Method == t.Spec.Method && ht.Spec.Host == t.Spec.Host { + urlMatch := false + if ht.Spec.RelativeURL == t.Spec.RelativeURL || (ht.Spec.Prefix != nil && t.Spec.Prefix != nil && *ht.Spec.Prefix != "" && *ht.Spec.Prefix == *t.Spec.Prefix) { + urlMatch = true + } + if urlMatch && ht.Spec.Method == t.Spec.Method && ht.Spec.Host == t.Spec.Host { return ferror.MakeError(ferror.ErrorNameExists, fmt.Sprintf("HTTPTrigger with same Host, URL & method already exists (%v)", ht.ObjectMeta.Name)) diff --git a/pkg/fission-cli/cmd/function/command.go b/pkg/fission-cli/cmd/function/command.go index 1f665d87..8cc3b6cd 100644 --- a/pkg/fission-cli/cmd/function/command.go +++ b/pkg/fission-cli/cmd/function/command.go @@ -43,7 +43,7 @@ func Commands() *cobra.Command { flag.PkgSrcChecksum, flag.PkgDeployChecksum, flag.PkgInsecure, flag.FnBuildCmd, - flag.HtUrl, flag.HtMethod, + flag.HtUrl, flag.HtPrefix, flag.HtMethod, // flag for newdeploy to use. flag.RunTimeMinCPU, flag.RunTimeMaxCPU, flag.RunTimeMinMemory, @@ -150,6 +150,7 @@ func Commands() *cobra.Command { // for getting log from log database if // we failed to get logs from function pod. flag.FnLogDBType, + flag.FnSubPath, }, }) diff --git a/pkg/fission-cli/cmd/function/create.go b/pkg/fission-cli/cmd/function/create.go index 79bc25be..8528675c 100644 --- a/pkg/fission-cli/cmd/function/create.go +++ b/pkg/fission-cli/cmd/function/create.go @@ -18,7 +18,6 @@ package function import ( "fmt" - "strings" "github.com/pkg/errors" uuid "github.com/satori/go.uuid" @@ -339,13 +338,13 @@ func (opts *CreateSubCommand) run(input cli.Input) error { // Allow the user to specify an HTTP trigger while creating a function. triggerUrl := input.String(flagkey.HtUrl) - if len(triggerUrl) == 0 { + prefix := input.String(flagkey.HtPrefix) + if len(triggerUrl) == 0 && len(prefix) == 0 { return nil } - if !strings.HasPrefix(triggerUrl, "/") { - triggerUrl = fmt.Sprintf("/%s", triggerUrl) + if len(prefix) != 0 && len(triggerUrl) > 0 { + console.Warn("Prefix will take precedence over URL/RelativeURL") } - method, err := httptrigger.GetMethod(input.String(flagkey.HtMethod)) if err != nil { return errors.Wrap(err, "error getting HTTP trigger method") @@ -359,6 +358,7 @@ func (opts *CreateSubCommand) run(input cli.Input) error { }, Spec: fv1.HTTPTriggerSpec{ RelativeURL: triggerUrl, + Prefix: &prefix, Method: method, FunctionReference: fv1.FunctionReference{ Type: fv1.FunctionReferenceTypeFunctionName, diff --git a/pkg/fission-cli/cmd/function/test.go b/pkg/fission-cli/cmd/function/test.go index 2905720b..ab24a427 100644 --- a/pkg/fission-cli/cmd/function/test.go +++ b/pkg/fission-cli/cmd/function/test.go @@ -18,7 +18,6 @@ package function import ( "context" - "fmt" "io/ioutil" "net/http" "net/url" @@ -62,14 +61,17 @@ func (opts *TestSubCommand) do(input cli.Input) error { if err != nil { return err } - routerURL = "127.0.0.1:" + localRouterPort - - fnUri := m.Name - if m.Namespace != metav1.NamespaceDefault { - fnUri = fmt.Sprintf("%v/%v", m.Namespace, m.Name) + fnURL := "http://127.0.0.1:" + localRouterPort + util.UrlForFunction(m.Name, m.Namespace) + if input.IsSet(flagkey.FnSubPath) { + subPath := input.String(flagkey.FnSubPath) + if !strings.HasPrefix(subPath, "/") { + fnURL = fnURL + "/" + subPath + } else { + fnURL = fnURL + subPath + } } - functionUrl, err := url.Parse(fmt.Sprintf("http://%s/fission-function/%s", routerURL, fnUri)) + functionUrl, err := url.Parse(fnURL) if err != nil { return err } diff --git a/pkg/fission-cli/cmd/httptrigger/command.go b/pkg/fission-cli/cmd/httptrigger/command.go index cdefeb44..c98977e3 100644 --- a/pkg/fission-cli/cmd/httptrigger/command.go +++ b/pkg/fission-cli/cmd/httptrigger/command.go @@ -30,10 +30,10 @@ func Commands() *cobra.Command { RunE: wrapper.Wrapper(Create), } wrapper.SetFlags(createCmd, flag.FlagSet{ - Required: []flag.Flag{flag.HtUrl, flag.HtFnName}, - Optional: []flag.Flag{flag.HtName, flag.HtMethod, flag.HtIngress, + Required: []flag.Flag{flag.HtFnName}, + Optional: []flag.Flag{flag.HtUrl, flag.HtName, flag.HtMethod, flag.HtIngress, flag.HtIngressRule, flag.HtIngressAnnotation, flag.HtIngressTLS, - flag.HtFnWeight, flag.HtHost, flag.NamespaceFunction, flag.SpecSave, flag.SpecDry}, + flag.HtFnWeight, flag.HtHost, flag.NamespaceFunction, flag.SpecSave, flag.SpecDry, flag.HtPrefix}, }) getCmd := &cobra.Command{ @@ -56,7 +56,7 @@ func Commands() *cobra.Command { Required: []flag.Flag{flag.HtName}, Optional: []flag.Flag{flag.HtUrl, flag.HtFnName, flag.HtMethod, flag.HtIngress, flag.HtIngressRule, flag.HtIngressAnnotation, - flag.HtIngressTLS, flag.HtFnWeight, flag.HtHost, flag.NamespaceTrigger}, + flag.HtIngressTLS, flag.HtFnWeight, flag.HtHost, flag.NamespaceTrigger, flag.HtPrefix}, }) deleteCmd := &cobra.Command{ diff --git a/pkg/fission-cli/cmd/httptrigger/create.go b/pkg/fission-cli/cmd/httptrigger/create.go index 98bd991b..43119b7f 100644 --- a/pkg/fission-cli/cmd/httptrigger/create.go +++ b/pkg/fission-cli/cmd/httptrigger/create.go @@ -19,6 +19,7 @@ package httptrigger import ( "fmt" "net/http" + "os" "strings" "github.com/pkg/errors" @@ -87,10 +88,25 @@ func (opts *CreateSubCommand) complete(input cli.Input) error { } triggerUrl := input.String(flagkey.HtUrl) - if triggerUrl == "/" { + prefix := input.String(flagkey.HtPrefix) + + if triggerUrl == "" && prefix == "" { + console.Error("You need to supply either Prefix or URL/RelativeURL") + os.Exit(1) + } + + if triggerUrl != "" && prefix != "" { + console.Warn("Prefix will take precedence over URL/RelativeURL") + } + + if triggerUrl == "/" || prefix == "/" { return errors.New("url with only root path is not allowed") - } else if !strings.HasPrefix(triggerUrl, "/") { - triggerUrl = fmt.Sprintf("/%s", triggerUrl) + } + if triggerUrl != "" && !strings.HasPrefix(triggerUrl, "/") { + triggerUrl = "/" + triggerUrl + } + if prefix != "" && !strings.HasPrefix(prefix, "/") { + prefix = "/" + prefix } method, err := GetMethod(input.String(flagkey.HtMethod)) @@ -149,6 +165,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error { FunctionReference: *functionRef, CreateIngress: createIngress, IngressConfig: *ingressConfig, + Prefix: &prefix, }, } diff --git a/pkg/fission-cli/cmd/httptrigger/update.go b/pkg/fission-cli/cmd/httptrigger/update.go index 19f1760d..29041750 100644 --- a/pkg/fission-cli/cmd/httptrigger/update.go +++ b/pkg/fission-cli/cmd/httptrigger/update.go @@ -18,6 +18,7 @@ package httptrigger import ( "fmt" + "strings" "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -59,10 +60,26 @@ func (opts *UpdateSubCommand) complete(input cli.Input) error { return errors.Wrap(err, "error getting HTTP trigger") } - if input.IsSet(flagkey.HtUrl) { - ht.Spec.RelativeURL = input.String(flagkey.HtUrl) + triggerUrl := input.String(flagkey.HtUrl) + prefix := input.String(flagkey.HtPrefix) + + if triggerUrl != "" && prefix != "" { + console.Warn("Prefix will take precedence over URL/RelativeURL") } + if triggerUrl == "/" || prefix == "/" { + return errors.New("url with only root path is not allowed") + } + if triggerUrl != "" && !strings.HasPrefix(triggerUrl, "/") { + triggerUrl = "/" + triggerUrl + } + if prefix != "" && !strings.HasPrefix(prefix, "/") { + prefix = "/" + prefix + } + + ht.Spec.RelativeURL = triggerUrl + ht.Spec.Prefix = &prefix + if input.IsSet(flagkey.HtMethod) { ht.Spec.Method = input.String(flagkey.HtMethod) } @@ -98,9 +115,15 @@ func (opts *UpdateSubCommand) complete(input cli.Input) error { } if input.IsSet(flagkey.HtIngressRule) || input.IsSet(flagkey.HtIngressAnnotation) || input.IsSet(flagkey.HtIngressTLS) { + fallbackURL := "" + if ht.Spec.Prefix != nil && *ht.Spec.Prefix != "" { + fallbackURL = *ht.Spec.Prefix + } else { + fallbackURL = ht.Spec.RelativeURL + } ingress, err := GetIngressConfig( input.StringSlice(flagkey.HtIngressAnnotation), input.String(flagkey.HtIngressRule), - input.String(flagkey.HtIngressTLS), ht.Spec.RelativeURL, &ht.Spec.IngressConfig) + input.String(flagkey.HtIngressTLS), fallbackURL, &ht.Spec.IngressConfig) if err != nil { return errors.Wrap(err, "error parsing ingress configuration") } diff --git a/pkg/fission-cli/cmd/spec/list.go b/pkg/fission-cli/cmd/spec/list.go index 7e7c7c11..a3471ad1 100644 --- a/pkg/fission-cli/cmd/spec/list.go +++ b/pkg/fission-cli/cmd/spec/list.go @@ -317,10 +317,12 @@ func ShowHTTPTriggers(hts []fv1.HTTPTrigger) { host = trigger.Spec.IngressConfig.Host } path := trigger.Spec.RelativeURL + if trigger.Spec.Prefix != nil && *trigger.Spec.Prefix != "" { + path = *trigger.Spec.Prefix + } if len(trigger.Spec.IngressConfig.Path) > 0 { path = trigger.Spec.IngressConfig.Path } - var msg []string for k, v := range trigger.Spec.IngressConfig.Annotations { msg = append(msg, fmt.Sprintf("%v: %v", k, v)) diff --git a/pkg/fission-cli/cmd/spec/spec.go b/pkg/fission-cli/cmd/spec/spec.go index 0c234f7b..1c7f7260 100644 --- a/pkg/fission-cli/cmd/spec/spec.go +++ b/pkg/fission-cli/cmd/spec/spec.go @@ -566,11 +566,6 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error { return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc)) } - // TODO move to validator - if !strings.HasPrefix(v.Spec.RelativeURL, "/") { - v.Spec.RelativeURL = fmt.Sprintf("/%s", v.Spec.RelativeURL) - } - m = &v.ObjectMeta fr.HttpTriggers = append(fr.HttpTriggers, v) case "KubernetesWatchTrigger": diff --git a/pkg/fission-cli/flag/flag.go b/pkg/fission-cli/flag/flag.go index e809417d..737ee2b0 100644 --- a/pkg/fission-cli/flag/flag.go +++ b/pkg/fission-cli/flag/flag.go @@ -113,6 +113,7 @@ var ( FnConcurrency = Flag{Type: Int, Name: flagkey.FnConcurrency, Aliases: []string{"con"}, Usage: "Maximum number of pods specialized concurrently to serve requests", DefaultValue: 500} FnRequestsPerPod = Flag{Type: Int, Name: flagkey.FnRequestsPerPod, Aliases: []string{"rpp"}, Usage: "Maximum number of concurrent requests that can be served by a specialized pod", DefaultValue: 1} FnOnceOnly = Flag{Type: Bool, Name: flagkey.FnOnceOnly, Aliases: []string{"yolo"}, Usage: "Specifies if specialized pod will serve exactly one request in its lifetime"} + FnSubPath = Flag{Type: String, Name: flagkey.FnSubPath, Usage: "Sub Path to check if function internally supports routing"} HtName = Flag{Type: String, Name: flagkey.HtName, Usage: "HTTP trigger name"} HtMethod = Flag{Type: String, Name: flagkey.HtMethod, Usage: "HTTP Method: GET|POST|PUT|DELETE|HEAD", DefaultValue: http.MethodGet} @@ -125,6 +126,7 @@ var ( HtFnName = Flag{Type: StringSlice, Name: flagkey.HtFnName, Usage: "Name(s) of the function for this trigger. (If 2 functions are supplied with this flag, traffic gets routed to them based on weights supplied with --weight flag.)"} HtFnWeight = Flag{Type: IntSlice, Name: flagkey.HtFnWeight, Usage: "Weight for each function supplied with --function flag, in the same order. Used for canary deployment"} HtFnFilter = Flag{Type: String, Name: flagkey.HtFilter, Usage: "Name of the function for trigger(s)"} + HtPrefix = Flag{Type: String, Name: flagkey.HtPrefix, Usage: "Prefix with which functions are exposed. NOTE: Prefix takes precedence over URL/RelativeURL"} TtName = Flag{Type: String, Name: flagkey.TtName, Usage: "Time Trigger name"} TtCron = Flag{Type: String, Name: flagkey.TtCron, Usage: "Time trigger cron spec with each asterisk representing respectively second, minute, hour, the day of the month, month and day of the week. Also supports readable formats like '@every 5m', '@hourly'"} diff --git a/pkg/fission-cli/flag/key/key.go b/pkg/fission-cli/flag/key/key.go index 8d4caaab..c2338701 100644 --- a/pkg/fission-cli/flag/key/key.go +++ b/pkg/fission-cli/flag/key/key.go @@ -66,6 +66,7 @@ const ( FnConcurrency = "concurrency" FnRequestsPerPod = "requestsperpod" FnOnceOnly = "onceonly" + FnSubPath = "subpath" HtName = resourceName HtMethod = "method" @@ -78,6 +79,7 @@ const ( HtFnName = "function" HtFnWeight = "weight" HtFilter = HtFnName + HtPrefix = "prefix" TtName = resourceName TtCron = "cron" diff --git a/pkg/fission-cli/util/util.go b/pkg/fission-cli/util/util.go index 40e7f347..933c7b06 100644 --- a/pkg/fission-cli/util/util.go +++ b/pkg/fission-cli/util/util.go @@ -342,3 +342,11 @@ func UpdateMapFromStringSlice(dataMap *map[string]string, params []string) bool } return updated } + +func UrlForFunction(name, namespace string) string { + prefix := "/fission-function" + if namespace != metav1.NamespaceDefault { + prefix = fmt.Sprintf("/fission-function/%s", namespace) + } + return fmt.Sprintf("%v/%v", prefix, name) +} diff --git a/pkg/router/functionHandler.go b/pkg/router/functionHandler.go index 50993a21..0c26f71d 100644 --- a/pkg/router/functionHandler.go +++ b/pkg/router/functionHandler.go @@ -27,6 +27,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "strings" "time" "github.com/pkg/errors" @@ -39,6 +40,7 @@ import ( "github.com/fission/fission/pkg/error/network" executorClient "github.com/fission/fission/pkg/executor/client" "github.com/fission/fission/pkg/throttler" + "github.com/fission/fission/pkg/utils" ) const ( @@ -220,12 +222,26 @@ func (roundTripper *RetryingRoundTripper) RoundTrip(req *http.Request) (*http.Re req.URL.Scheme = roundTripper.serviceURL.Scheme req.URL.Host = roundTripper.serviceURL.Host - // To keep the function run container simple, it - // doesn't do any routing. In the future if we have - // multiple functions per container, we could use the - // function metadata here. - // leave the query string intact (req.URL.RawQuery) - req.URL.Path = "/" + // With addition of routing support from functions if function supports routing, + // 1. we trim prefix url and forward request + // 2. otherwise we just keep default request to root path + // We leave the query string intact (req.URL.RawQuery) where as we manipuate + // req.URL.Path according to httpTrigger specification. + prefixTrim := "" + functionURL := utils.UrlForFunction(fnMeta.Name, fnMeta.Namespace) + if roundTripper.funcHandler.httpTrigger != nil && roundTripper.funcHandler.httpTrigger.Spec.Prefix != nil && *roundTripper.funcHandler.httpTrigger.Spec.Prefix != "" { + prefixTrim = *roundTripper.funcHandler.httpTrigger.Spec.Prefix + } else if strings.HasPrefix(req.URL.Path, functionURL) { + prefixTrim = functionURL + } + if prefixTrim != "" { + req.URL.Path = strings.TrimPrefix(req.URL.Path, prefixTrim) + if !strings.HasPrefix(req.URL.Path, "/") { + req.URL.Path = "/" + req.URL.Path + } + } else { + req.URL.Path = "/" + } // Overwrite request host with internal host, // or request will be blocked in some situations @@ -608,7 +624,11 @@ func (fh functionHandler) collectFunctionMetric(start time.Time, rrt *RetryingRo } if fh.httpTrigger != nil { httpMetricLabels.host = fh.httpTrigger.Spec.Host - httpMetricLabels.path = fh.httpTrigger.Spec.RelativeURL + if fh.httpTrigger.Spec.Prefix != nil && *fh.httpTrigger.Spec.Prefix != "" { + httpMetricLabels.path = *fh.httpTrigger.Spec.Prefix + } else { + httpMetricLabels.path = fh.httpTrigger.Spec.RelativeURL + } } // Track metrics diff --git a/pkg/router/httpTriggers.go b/pkg/router/httpTriggers.go index 4e821861..8a2c9763 100644 --- a/pkg/router/httpTriggers.go +++ b/pkg/router/httpTriggers.go @@ -166,13 +166,18 @@ func (ts *HTTPTriggerSet) getRouter(fnTimeoutMap map[types.UID]int) *mux.Router fh.function = fn } } + var ht *mux.Route - ht := muxRouter.HandleFunc(trigger.Spec.RelativeURL, fh.handler) + if trigger.Spec.Prefix != nil && *trigger.Spec.Prefix != "" { + ht = muxRouter.PathPrefix(*trigger.Spec.Prefix).HandlerFunc(fh.handler) + } else { + ht = muxRouter.HandleFunc(trigger.Spec.RelativeURL, fh.handler) + } ht.Methods(trigger.Spec.Method) if trigger.Spec.Host != "" { ht.Host(trigger.Spec.Host) } - if trigger.Spec.RelativeURL == "/" && trigger.Spec.Method == "GET" { + if trigger.Spec.Prefix == nil && trigger.Spec.RelativeURL == "/" && trigger.Spec.Method == "GET" { homeHandled = true } } @@ -202,7 +207,7 @@ func (ts *HTTPTriggerSet) getRouter(fnTimeoutMap map[types.UID]int) *mux.Router functionTimeoutMap: fnTimeoutMap, unTapServiceTimeout: ts.unTapServiceTimeout, } - muxRouter.HandleFunc(utils.UrlForFunction(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace), fh.handler) + muxRouter.PathPrefix(utils.UrlForFunction(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace)).HandlerFunc(fh.handler) } // Healthz endpoint for the router. diff --git a/pkg/router/util/util.go b/pkg/router/util/util.go index d9aacf17..8c1fcf4f 100644 --- a/pkg/router/util/util.go +++ b/pkg/router/util/util.go @@ -27,6 +27,9 @@ import ( func GetIngressSpec(namespace string, trigger *fv1.HTTPTrigger) *v1beta1.Ingress { // TODO: remove backward compatibility host, path := trigger.Spec.Host, trigger.Spec.RelativeURL + if trigger.Spec.Prefix != nil && *trigger.Spec.Prefix != "" { + path = *trigger.Spec.Prefix + } if len(trigger.Spec.IngressConfig.Host) > 0 && len(trigger.Spec.IngressConfig.Path) > 0 { host, path = trigger.Spec.IngressConfig.Host, trigger.Spec.IngressConfig.Path } diff --git a/test/utils.sh b/test/utils.sh index 18d7c5a6..9a4c7b34 100755 --- a/test/utils.sh +++ b/test/utils.sh @@ -72,7 +72,7 @@ test_response() { set +e while true; do log "test_fn: call curl" - echo $(curl "$url") + echo curl "$url" resp=$(curl --silent --show-error "$url") # log "response:" $resp status_code=$?