Feature for probing functions

Introduces a single-flight call to a function's health
endpoint to verify that it is registered with an Istio
sidecar (Envoy) before letting the invocation through.

Results are cached for 5 seconds, before a probe is
required again.

Tested without Istio, with probe_functions environment
variable set to true, I saw a probe execute in the logs.

Fixes: #1721 for Istio users.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
This commit is contained in:
Alex Ellis (OpenFaaS Ltd)
2022-06-29 10:09:01 +01:00
committed by Alex Ellis
parent 01841f605c
commit 88eea5f62e
43 changed files with 784 additions and 468 deletions

View File

@ -29,6 +29,7 @@ COPY types types
COPY plugin plugin COPY plugin plugin
COPY version version COPY version version
COPY scaling scaling COPY scaling scaling
COPY probing probing
COPY pkg pkg COPY pkg pkg
COPY main.go . COPY main.go .

View File

@ -1,15 +1,34 @@
module github.com/openfaas/faas/gateway module github.com/openfaas/faas/gateway
go 1.16 go 1.17
require ( require (
github.com/docker/distribution v2.8.1+incompatible github.com/docker/distribution v2.8.1+incompatible
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/hashicorp/golang-lru v0.5.1 // indirect
github.com/openfaas/faas-provider v0.18.7 github.com/openfaas/faas-provider v0.18.7
github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9 github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9
github.com/prometheus/client_golang v1.11.1 github.com/prometheus/client_golang v1.11.1
github.com/prometheus/client_model v0.2.0 github.com/prometheus/client_model v0.2.0
go.uber.org/goleak v1.1.10 go.uber.org/goleak v1.1.10
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/golang-lru v0.5.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30 // indirect
github.com/nats-io/nkeys v0.3.0 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/nats-io/stan.go v0.9.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect
golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
golang.org/x/tools v0.0.0-20210106214847-113979e3529a // indirect
google.golang.org/protobuf v1.26.0 // indirect
) )

View File

@ -11,6 +11,7 @@ import (
"math" "math"
"net/http" "net/http"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/requests" "github.com/openfaas/faas/gateway/requests"
"github.com/openfaas/faas/gateway/scaling" "github.com/openfaas/faas/gateway/scaling"
) )
@ -19,23 +20,24 @@ import (
func MakeAlertHandler(service scaling.ServiceQuery, defaultNamespace string) http.HandlerFunc { func MakeAlertHandler(service scaling.ServiceQuery, defaultNamespace string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
log.Println("Alert received.") if r.Body == nil {
http.Error(w, "A body is required for this endpoint", http.StatusBadRequest)
return
}
body, readErr := ioutil.ReadAll(r.Body) defer r.Body.Close()
log.Println(string(body)) body, err := ioutil.ReadAll(r.Body)
if err != nil {
if readErr != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Unable to read alert.")) w.Write([]byte("Unable to read alert."))
log.Println(readErr) log.Println(err)
return return
} }
var req requests.PrometheusAlert var req requests.PrometheusAlert
err := json.Unmarshal(body, &req) if err := json.Unmarshal(body, &req); err != nil {
if err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Unable to parse alert, bad format.")) w.Write([]byte("Unable to parse alert, bad format."))
log.Println(err) log.Println(err)
@ -73,7 +75,7 @@ func handleAlerts(req *requests.PrometheusAlert, service scaling.ServiceQuery, d
func scaleService(alert requests.PrometheusInnerAlert, service scaling.ServiceQuery, defaultNamespace string) error { func scaleService(alert requests.PrometheusInnerAlert, service scaling.ServiceQuery, defaultNamespace string) error {
var err error var err error
serviceName, namespace := getNamespace(defaultNamespace, alert.Labels.FunctionName) serviceName, namespace := middleware.GetNamespace(defaultNamespace, alert.Labels.FunctionName)
if len(serviceName) > 0 { if len(serviceName) > 0 {
queryResponse, getErr := service.GetReplicas(serviceName, namespace) queryResponse, getErr := service.GetReplicas(serviceName, namespace)

View File

@ -1,82 +0,0 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package handlers
import (
"fmt"
"log"
"net/http"
"net/url"
"strings"
"testing"
)
func TestSingleHostBaseURLResolver(t *testing.T) {
urlVal, _ := url.Parse("http://upstream:8080/")
r := SingleHostBaseURLResolver{BaseURL: urlVal.String()}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
resolved := r.Resolve(req)
want := "http://upstream:8080"
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}
const watchdogPort = 8080
func TestFunctionAsHostBaseURLResolver_WithNamespaceOverride(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc."
namespace := "openfaas-fn"
newNS := "production-fn"
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix, FunctionNamespace: namespace}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello."+newNS, nil)
resolved := r.Resolve(req)
newSuffix := strings.Replace(suffix, namespace, newNS, -1)
want := fmt.Sprintf("http://hello.%s:%d", newSuffix, watchdogPort)
log.Println(want)
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}
func TestFunctionAsHostBaseURLResolver_WithSuffix(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc."
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
resolved := r.Resolve(req)
want := fmt.Sprintf("http://hello.%s:%d", suffix, watchdogPort)
log.Println(want)
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}
func TestFunctionAsHostBaseURLResolver_WithoutSuffix(t *testing.T) {
suffix := ""
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
resolved := r.Resolve(req)
want := fmt.Sprintf("http://hello%s:%d", suffix, watchdogPort)
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}

View File

@ -10,40 +10,17 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"regexp"
"strings"
"time" "time"
"github.com/openfaas/faas/gateway/pkg/middleware" "github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/types" "github.com/openfaas/faas/gateway/types"
) )
// functionMatcher parses out the service name (group 1) and rest of path (group 2).
var functionMatcher = regexp.MustCompile("^/?(?:async-)?function/([^/?]+)([^?]*)")
// Indices and meta-data for functionMatcher regex parts
const (
hasPathCount = 3
routeIndex = 0 // routeIndex corresponds to /function/ or /async-function/
nameIndex = 1 // nameIndex is the function name
pathIndex = 2 // pathIndex is the path i.e. /employee/:id/
)
// BaseURLResolver URL resolver for upstream requests
type BaseURLResolver interface {
Resolve(r *http.Request) string
}
// URLPathTransformer Transform the incoming URL path for upstream requests
type URLPathTransformer interface {
Transform(r *http.Request) string
}
// MakeForwardingProxyHandler create a handler which forwards HTTP requests // MakeForwardingProxyHandler create a handler which forwards HTTP requests
func MakeForwardingProxyHandler(proxy *types.HTTPClientReverseProxy, func MakeForwardingProxyHandler(proxy *types.HTTPClientReverseProxy,
notifiers []HTTPNotifier, notifiers []HTTPNotifier,
baseURLResolver BaseURLResolver, baseURLResolver middleware.BaseURLResolver,
urlPathTransformer URLPathTransformer, urlPathTransformer middleware.URLPathTransformer,
serviceAuthInjector middleware.AuthInjector) http.HandlerFunc { serviceAuthInjector middleware.AuthInjector) http.HandlerFunc {
writeRequestURI := false writeRequestURI := false
@ -165,80 +142,6 @@ func deleteHeaders(target *http.Header, exclude *[]string) {
} }
} }
// SingleHostBaseURLResolver resolves URLs against a single BaseURL
type SingleHostBaseURLResolver struct {
BaseURL string
}
// Resolve the base URL for a request
func (s SingleHostBaseURLResolver) Resolve(r *http.Request) string {
baseURL := s.BaseURL
if strings.HasSuffix(baseURL, "/") {
baseURL = baseURL[0 : len(baseURL)-1]
}
return baseURL
}
// FunctionAsHostBaseURLResolver resolves URLs using a function from the URL as a host
type FunctionAsHostBaseURLResolver struct {
FunctionSuffix string
FunctionNamespace string
}
// Resolve the base URL for a request
func (f FunctionAsHostBaseURLResolver) Resolve(r *http.Request) string {
svcName := getServiceName(r.URL.Path)
const watchdogPort = 8080
var suffix string
if len(f.FunctionSuffix) > 0 {
if index := strings.LastIndex(svcName, "."); index > -1 && len(svcName) > index+1 {
suffix = strings.Replace(f.FunctionSuffix, f.FunctionNamespace, "", -1)
} else {
suffix = "." + f.FunctionSuffix
}
}
return fmt.Sprintf("http://%s%s:%d", svcName, suffix, watchdogPort)
}
// TransparentURLPathTransformer passes the requested URL path through untouched.
type TransparentURLPathTransformer struct {
}
// Transform returns the URL path unchanged.
func (f TransparentURLPathTransformer) Transform(r *http.Request) string {
return r.URL.Path
}
// FunctionPrefixTrimmingURLPathTransformer removes the "/function/servicename/" prefix from the URL path.
type FunctionPrefixTrimmingURLPathTransformer struct {
}
// Transform removes the "/function/servicename/" prefix from the URL path.
func (f FunctionPrefixTrimmingURLPathTransformer) Transform(r *http.Request) string {
ret := r.URL.Path
if ret != "" {
// When forwarding to a function, since the `/function/xyz` portion
// of a path like `/function/xyz/rest/of/path` is only used or needed
// by the Gateway, we want to trim it down to `/rest/of/path` for the
// upstream request. In the following regex, in the case of a match
// the r.URL.Path will be at `0`, the function name at `1` and the
// rest of the path (the part we are interested in) at `2`.
matcher := functionMatcher.Copy()
parts := matcher.FindStringSubmatch(ret)
if len(parts) == hasPathCount {
ret = parts[pathIndex]
}
}
return ret
}
// Hop-by-hop headers. These are removed when sent to the backend. // Hop-by-hop headers. These are removed when sent to the backend.
// As of RFC 7230, hop-by-hop headers are required to appear in the // As of RFC 7230, hop-by-hop headers are required to appear in the
// Connection header field. These are the headers defined by the // Connection header field. These are the headers defined by the

View File

@ -10,6 +10,8 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"testing" "testing"
"github.com/openfaas/faas/gateway/pkg/middleware"
) )
func Test_buildUpstreamRequest_Body_Method_Query(t *testing.T) { func Test_buildUpstreamRequest_Body_Method_Query(t *testing.T) {
@ -170,7 +172,7 @@ func Test_getServiceName(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
service := getServiceName(u.Path) service := middleware.GetServiceName(u.Path)
if service != s.serviceName { if service != s.serviceName {
t.Fatalf("Incorrect service name - want: %s, got: %s", s.serviceName, service) t.Fatalf("Incorrect service name - want: %s, got: %s", s.serviceName, service)
} }
@ -195,7 +197,7 @@ func Test_buildUpstreamRequest_WithPathNoQuery(t *testing.T) {
t.Fail() t.Fail()
} }
transformer := FunctionPrefixTrimmingURLPathTransformer{} transformer := middleware.FunctionPrefixTrimmingURLPathTransformer{}
transformedPath := transformer.Transform(request) transformedPath := transformer.Transform(request)
wantTransformedPath := functionPath wantTransformedPath := functionPath
@ -251,7 +253,7 @@ func Test_buildUpstreamRequest_WithNoPathNoQuery(t *testing.T) {
t.Fail() t.Fail()
} }
transformer := FunctionPrefixTrimmingURLPathTransformer{} transformer := middleware.FunctionPrefixTrimmingURLPathTransformer{}
transformedPath := transformer.Transform(request) transformedPath := transformer.Transform(request)
wantTransformedPath := "/" wantTransformedPath := "/"
@ -305,7 +307,7 @@ func Test_buildUpstreamRequest_WithPathAndQuery(t *testing.T) {
t.Fail() t.Fail()
} }
transformer := FunctionPrefixTrimmingURLPathTransformer{} transformer := middleware.FunctionPrefixTrimmingURLPathTransformer{}
transformedPath := transformer.Transform(request) transformedPath := transformer.Transform(request)
wantTransformedPath := functionPath wantTransformedPath := functionPath

View File

@ -3,10 +3,13 @@
package handlers package handlers
import "testing" import (
"github.com/openfaas/faas/gateway/pkg/middleware"
"testing"
)
func Test_getNamespace_Default(t *testing.T) { func Test_getNamespace_Default(t *testing.T) {
root, ns := getNamespace("openfaas-fn", "figlet.openfaas-fn") root, ns := middleware.GetNamespace("openfaas-fn", "figlet.openfaas-fn")
wantRoot := "figlet" wantRoot := "figlet"
wantNs := "openfaas-fn" wantNs := "openfaas-fn"
@ -19,7 +22,7 @@ func Test_getNamespace_Default(t *testing.T) {
} }
func Test_getNamespace_Override(t *testing.T) { func Test_getNamespace_Override(t *testing.T) {
root, ns := getNamespace("fn", "figlet.fn") root, ns := middleware.GetNamespace("fn", "figlet.fn")
wantRoot := "figlet" wantRoot := "figlet"
wantNs := "fn" wantNs := "fn"
@ -32,7 +35,7 @@ func Test_getNamespace_Override(t *testing.T) {
} }
func Test_getNamespace_Empty(t *testing.T) { func Test_getNamespace_Empty(t *testing.T) {
root, ns := getNamespace("", "figlet") root, ns := middleware.GetNamespace("", "figlet")
wantRoot := "figlet" wantRoot := "figlet"
wantNs := "" wantNs := ""

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/openfaas/faas/gateway/metrics" "github.com/openfaas/faas/gateway/metrics"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
@ -49,7 +50,7 @@ type PrometheusFunctionNotifier struct {
// Notify records metrics in Prometheus // Notify records metrics in Prometheus
func (p PrometheusFunctionNotifier) Notify(method string, URL string, originalURL string, statusCode int, event string, duration time.Duration) { func (p PrometheusFunctionNotifier) Notify(method string, URL string, originalURL string, statusCode int, event string, duration time.Duration) {
serviceName := getServiceName(originalURL) serviceName := middleware.GetServiceName(originalURL)
if len(p.FunctionNamespace) > 0 { if len(p.FunctionNamespace) > 0 {
if !strings.Contains(serviceName, ".") { if !strings.Contains(serviceName, ".") {
serviceName = fmt.Sprintf("%s.%s", serviceName, p.FunctionNamespace) serviceName = fmt.Sprintf("%s.%s", serviceName, p.FunctionNamespace)
@ -74,24 +75,6 @@ func (p PrometheusFunctionNotifier) Notify(method string, URL string, originalUR
} }
func getServiceName(urlValue string) string {
var serviceName string
forward := "/function/"
if strings.HasPrefix(urlValue, forward) {
// With a path like `/function/xyz/rest/of/path?q=a`, the service
// name we wish to locate is just the `xyz` portion. With a positive
// match on the regex below, it will return a three-element slice.
// The item at index `0` is the same as `urlValue`, at `1`
// will be the service name we need, and at `2` the rest of the path.
matcher := functionMatcher.Copy()
matches := matcher.FindStringSubmatch(urlValue)
if len(matches) == hasPathCount {
serviceName = matches[nameIndex]
}
}
return strings.Trim(serviceName, "/")
}
// LoggingNotifier notifies a log about a request // LoggingNotifier notifies a log about a request
type LoggingNotifier struct { type LoggingNotifier struct {
} }

View File

@ -0,0 +1,45 @@
package handlers
import (
"fmt"
"net/http"
"golang.org/x/sync/singleflight"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/probing"
)
func MakeProbeHandler(prober probing.FunctionProber, cache probing.ProbeCacher, resolver middleware.BaseURLResolver, next http.HandlerFunc, defaultNamespace string) http.HandlerFunc {
group := singleflight.Group{}
return func(w http.ResponseWriter, r *http.Request) {
functionName, namespace := middleware.GetNamespace(defaultNamespace, middleware.GetServiceName(r.URL.String()))
key := fmt.Sprintf("Probe-%s.%s", functionName, namespace)
res, _, _ := group.Do(key, func() (interface{}, error) {
cached, hit := cache.Get(functionName, namespace)
var probeResult probing.FunctionProbeResult
if hit && cached != nil && cached.Available {
probeResult = *cached
} else {
probeResult = prober.Probe(functionName, namespace)
cache.Set(functionName, namespace, &probeResult)
}
return probeResult, nil
})
fnRes := res.(probing.FunctionProbeResult)
if !fnRes.Available {
http.Error(w, fmt.Sprintf("unable to probe function endpoint %s", fnRes.Error),
http.StatusServiceUnavailable)
return
}
next(w, r)
}
}

View File

@ -14,6 +14,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
ftypes "github.com/openfaas/faas-provider/types" ftypes "github.com/openfaas/faas-provider/types"
"github.com/openfaas/faas/gateway/metrics" "github.com/openfaas/faas/gateway/metrics"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/scaling" "github.com/openfaas/faas/gateway/scaling"
) )
@ -21,7 +22,7 @@ import (
const queueAnnotation = "com.openfaas.queue" const queueAnnotation = "com.openfaas.queue"
// MakeQueuedProxy accepts work onto a queue // MakeQueuedProxy accepts work onto a queue
func MakeQueuedProxy(metrics metrics.MetricOptions, queuer ftypes.RequestQueuer, pathTransformer URLPathTransformer, defaultNS string, functionQuery scaling.FunctionQuery) http.HandlerFunc { func MakeQueuedProxy(metrics metrics.MetricOptions, queuer ftypes.RequestQueuer, pathTransformer middleware.URLPathTransformer, defaultNS string, functionQuery scaling.FunctionQuery) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if r.Body != nil { if r.Body != nil {
defer r.Body.Close() defer r.Body.Close()

View File

@ -7,18 +7,11 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"strings"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/scaling" "github.com/openfaas/faas/gateway/scaling"
) )
func getNamespace(defaultNamespace, fullName string) (string, string) {
if index := strings.LastIndex(fullName, "."); index > -1 {
return fullName[:index], fullName[index+1:]
}
return fullName, defaultNamespace
}
// MakeScalingHandler creates handler which can scale a function from // MakeScalingHandler creates handler which can scale a function from
// zero to N replica(s). After scaling the next http.HandlerFunc will // zero to N replica(s). After scaling the next http.HandlerFunc will
// be called. If the function is not ready after the configured // be called. If the function is not ready after the configured
@ -28,7 +21,7 @@ func MakeScalingHandler(next http.HandlerFunc, scaler scaling.FunctionScaler, co
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
functionName, namespace := getNamespace(defaultNamespace, getServiceName(r.URL.String())) functionName, namespace := middleware.GetNamespace(defaultNamespace, middleware.GetServiceName(r.URL.String()))
res := scaler.Scale(functionName, namespace) res := scaler.Scale(functionName, namespace)

View File

@ -15,6 +15,7 @@ import (
"github.com/openfaas/faas/gateway/metrics" "github.com/openfaas/faas/gateway/metrics"
"github.com/openfaas/faas/gateway/pkg/middleware" "github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/plugin" "github.com/openfaas/faas/gateway/plugin"
"github.com/openfaas/faas/gateway/probing"
"github.com/openfaas/faas/gateway/scaling" "github.com/openfaas/faas/gateway/scaling"
"github.com/openfaas/faas/gateway/types" "github.com/openfaas/faas/gateway/types"
"github.com/openfaas/faas/gateway/version" "github.com/openfaas/faas/gateway/version"
@ -92,15 +93,16 @@ func main() {
functionNotifiers := []handlers.HTTPNotifier{loggingNotifier, prometheusNotifier} functionNotifiers := []handlers.HTTPNotifier{loggingNotifier, prometheusNotifier}
forwardingNotifiers := []handlers.HTTPNotifier{loggingNotifier, prometheusServiceNotifier} forwardingNotifiers := []handlers.HTTPNotifier{loggingNotifier, prometheusServiceNotifier}
quietNotifier := []handlers.HTTPNotifier{prometheusServiceNotifier}
urlResolver := handlers.SingleHostBaseURLResolver{BaseURL: config.FunctionsProviderURL.String()} urlResolver := middleware.SingleHostBaseURLResolver{BaseURL: config.FunctionsProviderURL.String()}
var functionURLResolver handlers.BaseURLResolver var functionURLResolver middleware.BaseURLResolver
var functionURLTransformer handlers.URLPathTransformer var functionURLTransformer middleware.URLPathTransformer
nilURLTransformer := handlers.TransparentURLPathTransformer{} nilURLTransformer := middleware.TransparentURLPathTransformer{}
trimURLTransformer := handlers.FunctionPrefixTrimmingURLPathTransformer{} trimURLTransformer := middleware.FunctionPrefixTrimmingURLPathTransformer{}
if config.DirectFunctions { if config.DirectFunctions {
functionURLResolver = handlers.FunctionAsHostBaseURLResolver{ functionURLResolver = middleware.FunctionAsHostBaseURLResolver{
FunctionSuffix: config.DirectFunctionsSuffix, FunctionSuffix: config.DirectFunctionsSuffix,
FunctionNamespace: config.Namespace, FunctionNamespace: config.Namespace,
} }
@ -118,6 +120,21 @@ func main() {
decorateExternalAuth := handlers.MakeExternalAuthHandler decorateExternalAuth := handlers.MakeExternalAuthHandler
// externalServiceQuery is used to query metadata from the provider about a function
externalServiceQuery := plugin.NewExternalServiceQuery(*config.FunctionsProviderURL, serviceAuthInjector)
scalingConfig := scaling.ScalingConfig{
MaxPollCount: uint(1000),
SetScaleRetries: uint(20),
FunctionPollInterval: time.Millisecond * 100,
CacheExpiry: time.Millisecond * 250, // freshness of replica values before going stale
ServiceQuery: externalServiceQuery,
}
// This cache can be used to query a function's annotations.
functionAnnotationCache := scaling.NewFunctionCache(scalingConfig.CacheExpiry)
cachedFunctionQuery := scaling.NewCachedFunctionQuery(functionAnnotationCache, externalServiceQuery)
faasHandlers.Proxy = handlers.MakeCallIDMiddleware( faasHandlers.Proxy = handlers.MakeCallIDMiddleware(
handlers.MakeForwardingProxyHandler(reverseProxy, functionNotifiers, functionURLResolver, functionURLTransformer, nil), handlers.MakeForwardingProxyHandler(reverseProxy, functionNotifiers, functionURLResolver, functionURLTransformer, nil),
) )
@ -133,27 +150,26 @@ func main() {
faasHandlers.NamespaceListerHandler = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector) faasHandlers.NamespaceListerHandler = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector)
externalServiceQuery := plugin.NewExternalServiceQuery(*config.FunctionsProviderURL, serviceAuthInjector)
faasHandlers.Alert = handlers.MakeNotifierWrapper( faasHandlers.Alert = handlers.MakeNotifierWrapper(
handlers.MakeAlertHandler(externalServiceQuery, config.Namespace), handlers.MakeAlertHandler(externalServiceQuery, config.Namespace),
forwardingNotifiers, quietNotifier,
) )
faasHandlers.LogProxyHandler = handlers.NewLogHandlerFunc(*config.LogsProviderURL, config.WriteTimeout) faasHandlers.LogProxyHandler = handlers.NewLogHandlerFunc(*config.LogsProviderURL, config.WriteTimeout)
scalingConfig := scaling.ScalingConfig{ functionProxy := faasHandlers.Proxy
MaxPollCount: uint(1000),
SetScaleRetries: uint(20), if config.ProbeFunctions {
FunctionPollInterval: time.Millisecond * 100, prober := probing.NewFunctionProber(cachedFunctionQuery, functionURLResolver)
CacheExpiry: time.Millisecond * 250, // freshness of replica values before going stale // Default of 5 seconds between refreshing probes for function invocations
ServiceQuery: externalServiceQuery, probeCache := probing.NewProbeCache(time.Second * 5)
functionProxy = handlers.MakeProbeHandler(prober, probeCache, functionURLResolver, functionProxy, config.Namespace)
} }
functionProxy := faasHandlers.Proxy
if config.ScaleFromZero { if config.ScaleFromZero {
scalingFunctionCache := scaling.NewFunctionCache(scalingConfig.CacheExpiry) scalingFunctionCache := scaling.NewFunctionCache(scalingConfig.CacheExpiry)
scaler := scaling.NewFunctionScaler(scalingConfig, scalingFunctionCache) scaler := scaling.NewFunctionScaler(scalingConfig, scalingFunctionCache)
functionProxy = handlers.MakeScalingHandler(faasHandlers.Proxy, scaler, scalingConfig, config.Namespace) functionProxy = handlers.MakeScalingHandler(functionProxy, scaler, scalingConfig, config.Namespace)
} }
if config.UseNATS() { if config.UseNATS() {
@ -168,11 +184,8 @@ func main() {
log.Fatalln(queueErr) log.Fatalln(queueErr)
} }
queueFunctionCache := scaling.NewFunctionCache(scalingConfig.CacheExpiry)
functionQuery := scaling.NewCachedFunctionQuery(queueFunctionCache, externalServiceQuery)
faasHandlers.QueuedProxy = handlers.MakeNotifierWrapper( faasHandlers.QueuedProxy = handlers.MakeNotifierWrapper(
handlers.MakeCallIDMiddleware(handlers.MakeQueuedProxy(metricsOptions, natsQueue, trimURLTransformer, config.Namespace, functionQuery)), handlers.MakeCallIDMiddleware(handlers.MakeQueuedProxy(metricsOptions, natsQueue, trimURLTransformer, config.Namespace, cachedFunctionQuery)),
forwardingNotifiers, forwardingNotifiers,
) )
} }

View File

@ -0,0 +1,155 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package middleware
import (
"fmt"
"log"
"net/http"
"net/url"
"strings"
"testing"
)
func Test_SingleHostBaseURLResolver_BuildURL(t *testing.T) {
newNamespace := "production-fn"
function := "figlet"
r := SingleHostBaseURLResolver{BaseURL: "http://faas-netes.openfaas:8080"}
want := "http://faas-netes.openfaas:8080/function/figlet.production-fn/healthz"
got := r.BuildURL(function, newNamespace, "/healthz", true)
if got != want {
t.Fatalf("r.URL failed, want: %s got: %s", want, got)
}
}
func Test_SingleHostBaseURLResolver_BuildURL_DefaultNamespace(t *testing.T) {
newNamespace := "openfaas-fn"
function := "figlet"
r := SingleHostBaseURLResolver{BaseURL: "http://faas-netes.openfaas:8080"}
want := "http://faas-netes.openfaas:8080/function/figlet.openfaas-fn/_/health"
got := r.BuildURL(function, newNamespace, "/_/health", true)
if got != want {
t.Fatalf("r.URL failed, want: %s got: %s", want, got)
}
}
func TestSingleHostBaseURLResolver(t *testing.T) {
urlVal, _ := url.Parse("http://upstream:8080/")
r := SingleHostBaseURLResolver{BaseURL: urlVal.String()}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
resolved := r.Resolve(req)
want := "http://upstream:8080"
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}
const watchdogPort = 8080
func TestURL_NonDefaultNamespaceWithPath(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc"
namespace := "openfaas-fn"
newNamespace := "production-fn"
function := "figlet"
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix, FunctionNamespace: namespace}
want := "http://figlet.production-fn.local.cluster.svc:8080/healthz"
got := r.BuildURL(function, newNamespace, "/healthz", true)
if got != want {
t.Fatalf("r.URL failed, want: %s got: %s", want, got)
}
}
func TestURL_NonDefaultNamespaceWithout(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc"
namespace := "openfaas-fn"
newNamespace := "production-fn"
function := "figlet"
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix, FunctionNamespace: namespace}
want := "http://figlet.production-fn.local.cluster.svc:8080"
got := r.BuildURL(function, newNamespace, "", true)
if got != want {
t.Fatalf("r.URL failed, want: %s got: %s", want, got)
}
}
func TestURL_DefaultNamespaceWithPath(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc"
namespace := "openfaas-fn"
newNamespace := "production-fn"
function := "figlet"
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix, FunctionNamespace: namespace}
want := "http://figlet.production-fn.local.cluster.svc:8080/_/health"
got := r.BuildURL(function, newNamespace, "/_/health", true)
if got != want {
t.Fatalf("r.URL failed, want: %s got: %s", want, got)
}
}
func TestFunctionAsHostBaseURLResolver_WithNamespaceOverride(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc."
namespace := "openfaas-fn"
newNS := "production-fn"
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix, FunctionNamespace: namespace}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello."+newNS, nil)
resolved := r.Resolve(req)
newSuffix := strings.Replace(suffix, namespace, newNS, -1)
want := fmt.Sprintf("http://hello.%s:%d", newSuffix, watchdogPort)
log.Println(want)
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}
func TestFunctionAsHostBaseURLResolver_WithSuffix(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc."
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
resolved := r.Resolve(req)
want := fmt.Sprintf("http://hello.%s:%d", suffix, watchdogPort)
log.Println(want)
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}
func TestFunctionAsHostBaseURLResolver_WithoutSuffix(t *testing.T) {
suffix := ""
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
resolved := r.Resolve(req)
want := fmt.Sprintf("http://hello%s:%d", suffix, watchdogPort)
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved. // Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
package handlers package middleware
import ( import (
"net/http" "net/http"

View File

@ -0,0 +1,156 @@
package middleware
import (
"fmt"
"net/http"
"net/url"
"path"
"regexp"
"strings"
)
// functionMatcher parses out the service name (group 1) and rest of path (group 2).
var functionMatcher = regexp.MustCompile("^/?(?:async-)?function/([^/?]+)([^?]*)")
// Indices and meta-data for functionMatcher regex parts
const (
hasPathCount = 3
routeIndex = 0 // routeIndex corresponds to /function/ or /async-function/
nameIndex = 1 // nameIndex is the function name
pathIndex = 2 // pathIndex is the path i.e. /employee/:id/
)
// BaseURLResolver URL resolver for upstream requests
type BaseURLResolver interface {
Resolve(r *http.Request) string
BuildURL(function, namespace, healthPath string, directFunctions bool) string
}
// URLPathTransformer Transform the incoming URL path for upstream requests
type URLPathTransformer interface {
Transform(r *http.Request) string
}
// SingleHostBaseURLResolver resolves URLs against a single BaseURL
type SingleHostBaseURLResolver struct {
BaseURL string
}
func (s SingleHostBaseURLResolver) BuildURL(function, namespace, healthPath string, directFunctions bool) string {
u, _ := url.Parse(s.BaseURL)
base := fmt.Sprintf("/function/%s.%s/", function, namespace)
if len(healthPath) > 0 {
u.Path = path.Join(base, healthPath)
} else {
u.Path = base
}
return u.String()
}
// Resolve the base URL for a request
func (s SingleHostBaseURLResolver) Resolve(r *http.Request) string {
baseURL := s.BaseURL
if strings.HasSuffix(baseURL, "/") {
baseURL = baseURL[0 : len(baseURL)-1]
}
return baseURL
}
// FunctionAsHostBaseURLResolver resolves URLs using a function from the URL as a host
type FunctionAsHostBaseURLResolver struct {
FunctionSuffix string
FunctionNamespace string
}
// Resolve the base URL for a request
func (f FunctionAsHostBaseURLResolver) Resolve(r *http.Request) string {
svcName := GetServiceName(r.URL.Path)
const watchdogPort = 8080
var suffix string
if len(f.FunctionSuffix) > 0 {
if index := strings.LastIndex(svcName, "."); index > -1 && len(svcName) > index+1 {
suffix = strings.Replace(f.FunctionSuffix, f.FunctionNamespace, "", -1)
} else {
suffix = "." + f.FunctionSuffix
}
}
return fmt.Sprintf("http://%s%s:%d", svcName, suffix, watchdogPort)
}
func (f FunctionAsHostBaseURLResolver) BuildURL(function, namespace, healthPath string, directFunctions bool) string {
svcName := function
const watchdogPort = 8080
var suffix string
if len(f.FunctionSuffix) > 0 {
suffix = strings.Replace(f.FunctionSuffix, f.FunctionNamespace, namespace, 1)
}
u, _ := url.Parse(fmt.Sprintf("http://%s.%s:%d", svcName, suffix, watchdogPort))
if len(healthPath) > 0 {
u.Path = healthPath
}
return u.String()
}
// TransparentURLPathTransformer passes the requested URL path through untouched.
type TransparentURLPathTransformer struct {
}
// Transform returns the URL path unchanged.
func (f TransparentURLPathTransformer) Transform(r *http.Request) string {
return r.URL.Path
}
// FunctionPrefixTrimmingURLPathTransformer removes the "/function/servicename/" prefix from the URL path.
type FunctionPrefixTrimmingURLPathTransformer struct {
}
// Transform removes the "/function/servicename/" prefix from the URL path.
func (f FunctionPrefixTrimmingURLPathTransformer) Transform(r *http.Request) string {
ret := r.URL.Path
if ret != "" {
// When forwarding to a function, since the `/function/xyz` portion
// of a path like `/function/xyz/rest/of/path` is only used or needed
// by the Gateway, we want to trim it down to `/rest/of/path` for the
// upstream request. In the following regex, in the case of a match
// the r.URL.Path will be at `0`, the function name at `1` and the
// rest of the path (the part we are interested in) at `2`.
matcher := functionMatcher.Copy()
parts := matcher.FindStringSubmatch(ret)
if len(parts) == hasPathCount {
ret = parts[pathIndex]
}
}
return ret
}
func GetServiceName(urlValue string) string {
var serviceName string
forward := "/function/"
if strings.HasPrefix(urlValue, forward) {
// With a path like `/function/xyz/rest/of/path?q=a`, the service
// name we wish to locate is just the `xyz` portion. With a positive
// match on the regex below, it will return a three-element slice.
// The item at index `0` is the same as `urlValue`, at `1`
// will be the service name we need, and at `2` the rest of the path.
matcher := functionMatcher.Copy()
matches := matcher.FindStringSubmatch(urlValue)
if len(matches) == hasPathCount {
serviceName = matches[nameIndex]
}
}
return strings.Trim(serviceName, "/")
}

View File

@ -0,0 +1,10 @@
package middleware
import "strings"
func GetNamespace(defaultNamespace, fullName string) (string, string) {
if index := strings.LastIndex(fullName, "."); index > -1 {
return fullName[:index], fullName[index+1:]
}
return fullName, defaultNamespace
}

View File

@ -1,7 +1,7 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved. // Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
package handlers package middleware
import ( import (
"net/http" "net/http"

View File

@ -32,7 +32,7 @@ type ExternalServiceQuery struct {
// NewExternalServiceQuery proxies service queries to external plugin via HTTP // NewExternalServiceQuery proxies service queries to external plugin via HTTP
func NewExternalServiceQuery(externalURL url.URL, authInjector middleware.AuthInjector) scaling.ServiceQuery { func NewExternalServiceQuery(externalURL url.URL, authInjector middleware.AuthInjector) scaling.ServiceQuery {
timeout := 5 * time.Second timeout := 3 * time.Second
proxyClient := http.Client{ proxyClient := http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
@ -82,35 +82,28 @@ func (s ExternalServiceQuery) GetReplicas(serviceName, serviceNamespace string)
res, err := s.ProxyClient.Do(req) res, err := s.ProxyClient.Do(req)
if err != nil { if err != nil {
log.Printf("Unable to connect to %s, error: %s", urlPath, err) log.Println(urlPath, err)
} else { return emptyServiceQueryResponse, err
var body []byte }
var bytesOut []byte
if res.Body != nil { if res.Body != nil {
bytesOut, _ = ioutil.ReadAll(res.Body)
defer res.Body.Close() defer res.Body.Close()
body, _ = ioutil.ReadAll(res.Body)
} }
if res.StatusCode == http.StatusOK { if res.StatusCode == http.StatusOK {
err = json.Unmarshal(body, &function) if err := json.Unmarshal(bytesOut, &function); err != nil {
if err != nil { log.Printf("Unable to unmarshal: %q, %s", string(bytesOut), err)
log.Printf("Unable to unmarshal %s, error: %s", string(body), err) return emptyServiceQueryResponse, err
} }
log.Printf("GetReplicas [%s.%s] took: %fs", // log.Printf("GetReplicas [%s.%s] took: %fs", serviceName, serviceNamespace, time.Since(start).Seconds())
serviceName,
serviceNamespace,
time.Since(start).Seconds())
} else { } else {
log.Printf("GetReplicas [%s.%s] took: %fs, code: %d", log.Printf("GetReplicas [%s.%s] took: %fs, code: %d\n", serviceName, serviceNamespace, time.Since(start).Seconds(), res.StatusCode)
serviceName, return emptyServiceQueryResponse, fmt.Errorf("server returned non-200 status code (%d) for function, %s, body: %s", res.StatusCode, serviceName, string(bytesOut))
serviceNamespace,
time.Since(start).Seconds(),
res.StatusCode)
return emptyServiceQueryResponse, fmt.Errorf("server returned non-200 status code (%d) for function, %s", res.StatusCode, serviceName)
}
} }
minReplicas := uint64(scaling.DefaultMinReplicas) minReplicas := uint64(scaling.DefaultMinReplicas)
@ -128,7 +121,7 @@ func (s ExternalServiceQuery) GetReplicas(serviceName, serviceNamespace string)
extractedScalingFactor := extractLabelValue(labels[scaling.ScalingFactorLabel], scalingFactor) extractedScalingFactor := extractLabelValue(labels[scaling.ScalingFactorLabel], scalingFactor)
targetLoad = extractLabelValue(labels[scaling.TargetLoadLabel], targetLoad) targetLoad = extractLabelValue(labels[scaling.TargetLoadLabel], targetLoad)
if extractedScalingFactor > 0 && extractedScalingFactor <= 100 { if extractedScalingFactor >= 0 && extractedScalingFactor <= 100 {
scalingFactor = extractedScalingFactor scalingFactor = extractedScalingFactor
} else { } else {
log.Printf("Bad Scaling Factor: %d, is not in range of [0 - 100]. Will fallback to %d", extractedScalingFactor, scalingFactor) log.Printf("Bad Scaling Factor: %d, is not in range of [0 - 100]. Will fallback to %d", extractedScalingFactor, scalingFactor)

58
gateway/probing/cache.go Normal file
View File

@ -0,0 +1,58 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package probing
import (
"fmt"
"sync"
"time"
)
// ProbeCacher queries functions and caches the results
type ProbeCacher interface {
Set(functionName, namespace string, result *FunctionProbeResult)
Get(functionName, namespace string) (result *FunctionProbeResult, hit bool)
}
// ProbeCache provides a cache of Probe replica counts
type ProbeCache struct {
Cache map[string]*FunctionProbeResult
Expiry time.Duration
Sync sync.RWMutex
}
// NewProbeCache creates a function cache to query function metadata
func NewProbeCache(cacheExpiry time.Duration) ProbeCacher {
return &ProbeCache{
Cache: make(map[string]*FunctionProbeResult),
Expiry: cacheExpiry,
}
}
// Set replica count for functionName
func (fc *ProbeCache) Set(functionName, namespace string, result *FunctionProbeResult) {
fc.Sync.Lock()
defer fc.Sync.Unlock()
fc.Cache[functionName+"."+namespace] = result
}
func (fc *ProbeCache) Get(functionName, namespace string) (*FunctionProbeResult, bool) {
result := &FunctionProbeResult{
Available: false,
Error: fmt.Errorf("unavailable in cache"),
}
hit := false
fc.Sync.RLock()
defer fc.Sync.RUnlock()
if val, exists := fc.Cache[functionName+"."+namespace]; exists {
hit = val.Expired(fc.Expiry) == false
result = val
}
return result, hit
}

116
gateway/probing/prober.go Normal file
View File

@ -0,0 +1,116 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package probing
import (
"fmt"
"log"
"net/http"
"time"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/scaling"
"github.com/openfaas/faas/gateway/types"
)
// NewFunctionProber create a new scaler with the specified
// ScalingConfig
func NewFunctionProber(functionQuery scaling.FunctionQuery, resolver middleware.BaseURLResolver) FunctionProber {
// if directFunctions {
return &FunctionHTTPProber{
Query: functionQuery,
Resolver: resolver,
}
}
// FunctionHTTPProber probes a function's health endpoint
type FunctionHTTPProber struct {
Query scaling.FunctionQuery
Resolver middleware.BaseURLResolver
DirectFunctions bool
}
type FunctionNonProber struct {
}
func (f *FunctionNonProber) Probe(functionName, namespace string) FunctionProbeResult {
return FunctionProbeResult{
Found: true,
Available: true,
}
}
type FunctionProber interface {
Probe(functionName, namespace string) FunctionProbeResult
}
// FunctionProbeResult holds the result of scaling from zero
type FunctionProbeResult struct {
Available bool
Error error
Found bool
Duration time.Duration
Updated time.Time
}
// Expired find out whether the cache item has expired with
// the given expiry duration from when it was stored.
func (res *FunctionProbeResult) Expired(expiry time.Duration) bool {
return time.Now().After(res.Updated.Add(expiry))
}
// Scale scales a function from zero replicas to 1 or the value set in
// the minimum replicas metadata
func (f *FunctionHTTPProber) Probe(functionName, namespace string) FunctionProbeResult {
start := time.Now()
cachedResponse, _ := f.Query.Get(functionName, namespace)
probePath := "/_/health"
if cachedResponse.Annotations != nil {
if v, ok := (*cachedResponse.Annotations)["com.openfaas.http.path"]; ok && len(v) > 0 {
probePath = v
}
}
maxCount := 10
pollInterval := time.Millisecond * 50
err := types.Retry(func(attempt int) error {
u := f.Resolver.BuildURL(functionName, namespace, probePath, true)
r, _ := http.NewRequest(http.MethodGet, u, nil)
r.Header.Set("User-Agent", "com.openfaas.gateway/probe")
resp, err := http.DefaultClient.Do(r)
if err != nil {
return err
}
log.Printf("[Probe] %s => %d", u, resp.StatusCode)
if resp.StatusCode == http.StatusOK {
return nil
}
return fmt.Errorf("failed with status: %s", resp.Status)
}, "Probe", maxCount, pollInterval)
if err != nil {
return FunctionProbeResult{
Error: err,
Available: false,
Found: true,
Duration: time.Since(start),
Updated: time.Now(),
}
}
return FunctionProbeResult{
Error: nil,
Available: true,
Found: true,
Duration: time.Since(start),
Updated: time.Now(),
}
}

View File

@ -49,8 +49,6 @@ func (c *CachedFunctionQuery) Get(fn string, ns string) (ServiceQueryResponse, e
return c.serviceQuery.GetReplicas(fn, ns) return c.serviceQuery.GetReplicas(fn, ns)
}) })
log.Printf("Result: %v %v", queryResponse, err)
if err != nil { if err != nil {
return ServiceQueryResponse{}, err return ServiceQueryResponse{}, err
} }

View File

@ -5,6 +5,7 @@ import (
"log" "log"
"time" "time"
"github.com/openfaas/faas/gateway/types"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
) )
@ -80,7 +81,7 @@ func (f *FunctionScaler) Scale(functionName, namespace string) FunctionScaleResu
minReplicas = queryResponse.MinReplicas minReplicas = queryResponse.MinReplicas
} }
scaleResult := backoff(func(attempt int) error { scaleResult := types.Retry(func(attempt int) error {
res, err, _ := f.SingleFlight.Do(getKey, func() (interface{}, error) { res, err, _ := f.SingleFlight.Do(getKey, func() (interface{}, error) {
return f.Config.ServiceQuery.GetReplicas(functionName, namespace) return f.Config.ServiceQuery.GetReplicas(functionName, namespace)
@ -114,7 +115,7 @@ func (f *FunctionScaler) Scale(functionName, namespace string) FunctionScaleResu
return nil return nil
}, int(f.Config.SetScaleRetries), f.Config.FunctionPollInterval) }, "Scale", int(f.Config.SetScaleRetries), f.Config.FunctionPollInterval)
if scaleResult != nil { if scaleResult != nil {
return FunctionScaleResult{ return FunctionScaleResult{
@ -171,23 +172,3 @@ func (f *FunctionScaler) Scale(functionName, namespace string) FunctionScaleResu
Duration: time.Since(start), Duration: time.Since(start),
} }
} }
type routine func(attempt int) error
func backoff(r routine, attempts int, interval time.Duration) error {
var err error
for i := 0; i < attempts; i++ {
res := r(i)
if res != nil {
err = res
log.Printf("Attempt: %d, had error: %s\n", i, res)
} else {
err = nil
break
}
time.Sleep(interval)
}
return err
}

View File

@ -175,6 +175,8 @@ func (ReadConfig) Read(hasEnv HasEnv) (*GatewayConfig, error) {
} }
} }
cfg.ProbeFunctions = parseBoolValue(hasEnv.Getenv("probe_functions"))
return &cfg, nil return &cfg, nil
} }
@ -243,6 +245,9 @@ type GatewayConfig struct {
// Namespace for endpoints // Namespace for endpoints
Namespace string Namespace string
// ProbeFunctions requires the gateway to probe the health endpoint of a function before invoking it
ProbeFunctions bool
} }
// UseNATS Use NATSor not // UseNATS Use NATSor not

View File

@ -137,6 +137,38 @@ func TestRead_DirectFunctionsOverride(t *testing.T) {
} }
} }
func TestRead_ProbeFunctions_Default(t *testing.T) {
defaults := NewEnvBucket()
readConfig := ReadConfig{}
defaults.Setenv("probe_functions", "")
want := false
config, _ := readConfig.Read(defaults)
got := config.ProbeFunctions
if want != got {
t.Logf("ProbeFunctions want %v, but got %v", want, got)
t.Fail()
}
}
func TestRead_ProbeFunctions_Enabled(t *testing.T) {
defaults := NewEnvBucket()
readConfig := ReadConfig{}
defaults.Setenv("probe_functions", "true")
want := true
config, _ := readConfig.Read(defaults)
got := config.ProbeFunctions
if want != got {
t.Logf("ProbeFunctions want %v, but got %v", want, got)
t.Fail()
}
}
func TestRead_ScaleZeroDefaultAndOverride(t *testing.T) { func TestRead_ScaleZeroDefaultAndOverride(t *testing.T) {
defaults := NewEnvBucket() defaults := NewEnvBucket()
readConfig := ReadConfig{} readConfig := ReadConfig{}

25
gateway/types/retry.go Normal file
View File

@ -0,0 +1,25 @@
package types
import (
"log"
"time"
)
type routine func(attempt int) error
func Retry(r routine, label string, attempts int, interval time.Duration) error {
var err error
for i := 0; i < attempts; i++ {
res := r(i)
if res != nil {
err = res
log.Printf("[%s]: %d/%d, error: %s\n", label, i, attempts, res)
} else {
err = nil
break
}
time.Sleep(interval)
}
return err
}

View File

@ -0,0 +1,43 @@
package types
import (
"fmt"
"testing"
"time"
)
func Test_retry_early_success(t *testing.T) {
called := 0
maxRetries := 10
routine := func(i int) error {
called++
if called == 5 {
return nil
}
return fmt.Errorf("not called 5 times yet")
}
Retry(routine, "test", maxRetries, time.Millisecond*5)
want := 5
if called != want {
t.Errorf("want: %d, got: %d", want, called)
}
}
func Test_retry_until_max_attempts(t *testing.T) {
called := 0
maxRetries := 10
routine := func(i int) error {
called++
return fmt.Errorf("unable to pass condition for routine")
}
Retry(routine, "test", maxRetries, time.Millisecond*5)
want := maxRetries
if called != want {
t.Errorf("want: %d, got: %d", want, called)
}
}

View File

@ -1,3 +0,0 @@
module github.com/cespare/xxhash/v2
go 1.11

View File

View File

@ -1,3 +0,0 @@
module github.com/gorilla/mux
go 1.12

View File

@ -1,8 +0,0 @@
module github.com/nats-io/nats.go
go 1.16
require (
github.com/nats-io/nkeys v0.3.0
github.com/nats-io/nuid v1.0.1
)

View File

@ -1,11 +0,0 @@
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -1,5 +0,0 @@
module github.com/nats-io/nkeys
go 1.16
require golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b

View File

@ -1,7 +0,0 @@
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -1,9 +0,0 @@
module github.com/nats-io/stan.go
go 1.14
require (
github.com/gogo/protobuf v1.3.2
github.com/nats-io/nats.go v1.11.0
github.com/nats-io/nuid v1.0.1
)

View File

@ -1,42 +0,0 @@
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/nats-io/nats.go v1.11.0 h1:L263PZkrmkRJRJT2YHU8GwWWvEvmr9/LUKuJTXsF32k=
github.com/nats-io/nats.go v1.11.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -1,9 +0,0 @@
module github.com/prometheus/procfs
go 1.13
require (
github.com/google/go-cmp v0.5.4
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
)

View File

@ -1,8 +0,0 @@
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -1,11 +0,0 @@
module go.uber.org/goleak
go 1.13
require (
github.com/kr/pretty v0.1.0 // indirect
github.com/stretchr/testify v1.4.0
golang.org/x/lint v0.0.0-20190930215403-16217165b5de
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

View File

@ -1,30 +0,0 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11 h1:Yq9t9jnGoR+dBuitxdo9l6Q7xh/zOyNnYUtDKaQ3x0E=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -1,3 +0,0 @@
module golang.org/x/lint
require golang.org/x/tools v0.0.0-20190311212946-11955173bddd

View File

@ -1,6 +0,0 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=

View File

@ -1,84 +1,100 @@
# github.com/beorn7/perks v1.0.1 # github.com/beorn7/perks v1.0.1
## explicit; go 1.11
github.com/beorn7/perks/quantile github.com/beorn7/perks/quantile
# github.com/cespare/xxhash/v2 v2.1.1 # github.com/cespare/xxhash/v2 v2.1.1
## explicit; go 1.11
github.com/cespare/xxhash/v2 github.com/cespare/xxhash/v2
# github.com/docker/distribution v2.8.1+incompatible # github.com/docker/distribution v2.8.1+incompatible
## explicit ## explicit
github.com/docker/distribution/uuid github.com/docker/distribution/uuid
# github.com/gogo/protobuf v1.3.2 # github.com/gogo/protobuf v1.3.2
## explicit; go 1.15
github.com/gogo/protobuf/gogoproto github.com/gogo/protobuf/gogoproto
github.com/gogo/protobuf/proto github.com/gogo/protobuf/proto
github.com/gogo/protobuf/protoc-gen-gogo/descriptor github.com/gogo/protobuf/protoc-gen-gogo/descriptor
# github.com/golang/protobuf v1.5.2 # github.com/golang/protobuf v1.5.2
## explicit; go 1.9
github.com/golang/protobuf/proto github.com/golang/protobuf/proto
github.com/golang/protobuf/ptypes github.com/golang/protobuf/ptypes
github.com/golang/protobuf/ptypes/any github.com/golang/protobuf/ptypes/any
github.com/golang/protobuf/ptypes/duration github.com/golang/protobuf/ptypes/duration
github.com/golang/protobuf/ptypes/timestamp github.com/golang/protobuf/ptypes/timestamp
# github.com/gorilla/mux v1.8.0 # github.com/gorilla/mux v1.8.0
## explicit ## explicit; go 1.12
github.com/gorilla/mux github.com/gorilla/mux
# github.com/hashicorp/golang-lru v0.5.1 # github.com/hashicorp/golang-lru v0.5.1
## explicit ## explicit
# github.com/matttproud/golang_protobuf_extensions v1.0.1 # github.com/matttproud/golang_protobuf_extensions v1.0.1
## explicit
github.com/matttproud/golang_protobuf_extensions/pbutil github.com/matttproud/golang_protobuf_extensions/pbutil
# github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30 # github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30
## explicit; go 1.16
github.com/nats-io/nats.go github.com/nats-io/nats.go
github.com/nats-io/nats.go/encoders/builtin github.com/nats-io/nats.go/encoders/builtin
github.com/nats-io/nats.go/util github.com/nats-io/nats.go/util
# github.com/nats-io/nkeys v0.3.0 # github.com/nats-io/nkeys v0.3.0
## explicit; go 1.16
github.com/nats-io/nkeys github.com/nats-io/nkeys
# github.com/nats-io/nuid v1.0.1 # github.com/nats-io/nuid v1.0.1
## explicit
github.com/nats-io/nuid github.com/nats-io/nuid
# github.com/nats-io/stan.go v0.9.0 # github.com/nats-io/stan.go v0.9.0
## explicit; go 1.14
github.com/nats-io/stan.go github.com/nats-io/stan.go
github.com/nats-io/stan.go/pb github.com/nats-io/stan.go/pb
# github.com/openfaas/faas-provider v0.18.7 # github.com/openfaas/faas-provider v0.18.7
## explicit ## explicit; go 1.16
github.com/openfaas/faas-provider/auth github.com/openfaas/faas-provider/auth
github.com/openfaas/faas-provider/types github.com/openfaas/faas-provider/types
# github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9 # github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9
## explicit ## explicit; go 1.16
github.com/openfaas/nats-queue-worker/handler github.com/openfaas/nats-queue-worker/handler
github.com/openfaas/nats-queue-worker/nats github.com/openfaas/nats-queue-worker/nats
# github.com/prometheus/client_golang v1.11.1 # github.com/prometheus/client_golang v1.11.1
## explicit ## explicit; go 1.13
github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus
github.com/prometheus/client_golang/prometheus/internal github.com/prometheus/client_golang/prometheus/internal
github.com/prometheus/client_golang/prometheus/promhttp github.com/prometheus/client_golang/prometheus/promhttp
# github.com/prometheus/client_model v0.2.0 # github.com/prometheus/client_model v0.2.0
## explicit ## explicit; go 1.9
github.com/prometheus/client_model/go github.com/prometheus/client_model/go
# github.com/prometheus/common v0.26.0 # github.com/prometheus/common v0.26.0
## explicit; go 1.11
github.com/prometheus/common/expfmt github.com/prometheus/common/expfmt
github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg
github.com/prometheus/common/model github.com/prometheus/common/model
# github.com/prometheus/procfs v0.6.0 # github.com/prometheus/procfs v0.6.0
## explicit; go 1.13
github.com/prometheus/procfs github.com/prometheus/procfs
github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/fs
github.com/prometheus/procfs/internal/util github.com/prometheus/procfs/internal/util
# go.uber.org/goleak v1.1.10 # go.uber.org/goleak v1.1.10
## explicit ## explicit; go 1.13
go.uber.org/goleak go.uber.org/goleak
go.uber.org/goleak/internal/stack go.uber.org/goleak/internal/stack
# golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e # golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
## explicit; go 1.17
golang.org/x/crypto/ed25519 golang.org/x/crypto/ed25519
golang.org/x/crypto/ed25519/internal/edwards25519 golang.org/x/crypto/ed25519/internal/edwards25519
# golang.org/x/lint v0.0.0-20190930215403-16217165b5de # golang.org/x/lint v0.0.0-20190930215403-16217165b5de
## explicit
golang.org/x/lint golang.org/x/lint
golang.org/x/lint/golint golang.org/x/lint/golint
# golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f # golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
## explicit ## explicit
golang.org/x/sync/singleflight golang.org/x/sync/singleflight
# golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 # golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
## explicit; go 1.17
golang.org/x/sys/internal/unsafeheader golang.org/x/sys/internal/unsafeheader
golang.org/x/sys/unix golang.org/x/sys/unix
golang.org/x/sys/windows golang.org/x/sys/windows
# golang.org/x/tools v0.0.0-20210106214847-113979e3529a # golang.org/x/tools v0.0.0-20210106214847-113979e3529a
## explicit; go 1.12
golang.org/x/tools/go/ast/astutil golang.org/x/tools/go/ast/astutil
golang.org/x/tools/go/gcexportdata golang.org/x/tools/go/gcexportdata
golang.org/x/tools/go/internal/gcimporter golang.org/x/tools/go/internal/gcimporter
# google.golang.org/protobuf v1.26.0 # google.golang.org/protobuf v1.26.0
## explicit; go 1.9
google.golang.org/protobuf/encoding/prototext google.golang.org/protobuf/encoding/prototext
google.golang.org/protobuf/encoding/protowire google.golang.org/protobuf/encoding/protowire
google.golang.org/protobuf/internal/descfmt google.golang.org/protobuf/internal/descfmt

2
go.mod
View File

@ -1,3 +1,3 @@
module github.com/openfaas/faas module github.com/openfaas/faas
go 1.15 go 1.17