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 version version
COPY scaling scaling
COPY probing probing
COPY pkg pkg
COPY main.go .

View File

@ -1,15 +1,34 @@
module github.com/openfaas/faas/gateway
go 1.16
go 1.17
require (
github.com/docker/distribution v2.8.1+incompatible
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/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9
github.com/prometheus/client_golang v1.11.1
github.com/prometheus/client_model v0.2.0
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"
"net/http"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/requests"
"github.com/openfaas/faas/gateway/scaling"
)
@ -19,23 +20,24 @@ import (
func MakeAlertHandler(service scaling.ServiceQuery, defaultNamespace string) http.HandlerFunc {
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))
if readErr != nil {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Unable to read alert."))
log.Println(readErr)
log.Println(err)
return
}
var req requests.PrometheusAlert
err := json.Unmarshal(body, &req)
if err != nil {
if err := json.Unmarshal(body, &req); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Unable to parse alert, bad format."))
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 {
var err error
serviceName, namespace := getNamespace(defaultNamespace, alert.Labels.FunctionName)
serviceName, namespace := middleware.GetNamespace(defaultNamespace, alert.Labels.FunctionName)
if len(serviceName) > 0 {
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"
"net/http"
"os"
"regexp"
"strings"
"time"
"github.com/openfaas/faas/gateway/pkg/middleware"
"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
func MakeForwardingProxyHandler(proxy *types.HTTPClientReverseProxy,
notifiers []HTTPNotifier,
baseURLResolver BaseURLResolver,
urlPathTransformer URLPathTransformer,
baseURLResolver middleware.BaseURLResolver,
urlPathTransformer middleware.URLPathTransformer,
serviceAuthInjector middleware.AuthInjector) http.HandlerFunc {
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.
// As of RFC 7230, hop-by-hop headers are required to appear in the
// Connection header field. These are the headers defined by the

View File

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

View File

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

View File

@ -8,6 +8,7 @@ import (
"time"
"github.com/openfaas/faas/gateway/metrics"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/prometheus/client_golang/prometheus"
)
@ -49,7 +50,7 @@ type PrometheusFunctionNotifier struct {
// Notify records metrics in Prometheus
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 !strings.Contains(serviceName, ".") {
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
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"
ftypes "github.com/openfaas/faas-provider/types"
"github.com/openfaas/faas/gateway/metrics"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/scaling"
)
@ -21,7 +22,7 @@ import (
const queueAnnotation = "com.openfaas.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) {
if r.Body != nil {
defer r.Body.Close()

View File

@ -7,18 +7,11 @@ import (
"fmt"
"log"
"net/http"
"strings"
"github.com/openfaas/faas/gateway/pkg/middleware"
"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
// zero to N replica(s). After scaling the next http.HandlerFunc will
// 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) {
functionName, namespace := getNamespace(defaultNamespace, getServiceName(r.URL.String()))
functionName, namespace := middleware.GetNamespace(defaultNamespace, middleware.GetServiceName(r.URL.String()))
res := scaler.Scale(functionName, namespace)

View File

@ -15,6 +15,7 @@ import (
"github.com/openfaas/faas/gateway/metrics"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/plugin"
"github.com/openfaas/faas/gateway/probing"
"github.com/openfaas/faas/gateway/scaling"
"github.com/openfaas/faas/gateway/types"
"github.com/openfaas/faas/gateway/version"
@ -92,15 +93,16 @@ func main() {
functionNotifiers := []handlers.HTTPNotifier{loggingNotifier, prometheusNotifier}
forwardingNotifiers := []handlers.HTTPNotifier{loggingNotifier, prometheusServiceNotifier}
quietNotifier := []handlers.HTTPNotifier{prometheusServiceNotifier}
urlResolver := handlers.SingleHostBaseURLResolver{BaseURL: config.FunctionsProviderURL.String()}
var functionURLResolver handlers.BaseURLResolver
var functionURLTransformer handlers.URLPathTransformer
nilURLTransformer := handlers.TransparentURLPathTransformer{}
trimURLTransformer := handlers.FunctionPrefixTrimmingURLPathTransformer{}
urlResolver := middleware.SingleHostBaseURLResolver{BaseURL: config.FunctionsProviderURL.String()}
var functionURLResolver middleware.BaseURLResolver
var functionURLTransformer middleware.URLPathTransformer
nilURLTransformer := middleware.TransparentURLPathTransformer{}
trimURLTransformer := middleware.FunctionPrefixTrimmingURLPathTransformer{}
if config.DirectFunctions {
functionURLResolver = handlers.FunctionAsHostBaseURLResolver{
functionURLResolver = middleware.FunctionAsHostBaseURLResolver{
FunctionSuffix: config.DirectFunctionsSuffix,
FunctionNamespace: config.Namespace,
}
@ -118,6 +120,21 @@ func main() {
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(
handlers.MakeForwardingProxyHandler(reverseProxy, functionNotifiers, functionURLResolver, functionURLTransformer, nil),
)
@ -133,27 +150,26 @@ func main() {
faasHandlers.NamespaceListerHandler = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector)
externalServiceQuery := plugin.NewExternalServiceQuery(*config.FunctionsProviderURL, serviceAuthInjector)
faasHandlers.Alert = handlers.MakeNotifierWrapper(
handlers.MakeAlertHandler(externalServiceQuery, config.Namespace),
forwardingNotifiers,
quietNotifier,
)
faasHandlers.LogProxyHandler = handlers.NewLogHandlerFunc(*config.LogsProviderURL, config.WriteTimeout)
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,
functionProxy := faasHandlers.Proxy
if config.ProbeFunctions {
prober := probing.NewFunctionProber(cachedFunctionQuery, functionURLResolver)
// Default of 5 seconds between refreshing probes for function invocations
probeCache := probing.NewProbeCache(time.Second * 5)
functionProxy = handlers.MakeProbeHandler(prober, probeCache, functionURLResolver, functionProxy, config.Namespace)
}
functionProxy := faasHandlers.Proxy
if config.ScaleFromZero {
scalingFunctionCache := scaling.NewFunctionCache(scalingConfig.CacheExpiry)
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() {
@ -168,11 +184,8 @@ func main() {
log.Fatalln(queueErr)
}
queueFunctionCache := scaling.NewFunctionCache(scalingConfig.CacheExpiry)
functionQuery := scaling.NewCachedFunctionQuery(queueFunctionCache, externalServiceQuery)
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,
)
}

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.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package handlers
package middleware
import (
"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.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package handlers
package middleware
import (
"net/http"

View File

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

View File

@ -5,6 +5,7 @@ import (
"log"
"time"
"github.com/openfaas/faas/gateway/types"
"golang.org/x/sync/singleflight"
)
@ -80,7 +81,7 @@ func (f *FunctionScaler) Scale(functionName, namespace string) FunctionScaleResu
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) {
return f.Config.ServiceQuery.GetReplicas(functionName, namespace)
@ -114,7 +115,7 @@ func (f *FunctionScaler) Scale(functionName, namespace string) FunctionScaleResu
return nil
}, int(f.Config.SetScaleRetries), f.Config.FunctionPollInterval)
}, "Scale", int(f.Config.SetScaleRetries), f.Config.FunctionPollInterval)
if scaleResult != nil {
return FunctionScaleResult{
@ -171,23 +172,3 @@ func (f *FunctionScaler) Scale(functionName, namespace string) FunctionScaleResu
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
}
@ -243,6 +245,9 @@ type GatewayConfig struct {
// Namespace for endpoints
Namespace string
// ProbeFunctions requires the gateway to probe the health endpoint of a function before invoking it
ProbeFunctions bool
}
// 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) {
defaults := NewEnvBucket()
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
## explicit; go 1.11
github.com/beorn7/perks/quantile
# github.com/cespare/xxhash/v2 v2.1.1
## explicit; go 1.11
github.com/cespare/xxhash/v2
# github.com/docker/distribution v2.8.1+incompatible
## explicit
github.com/docker/distribution/uuid
# github.com/gogo/protobuf v1.3.2
## explicit; go 1.15
github.com/gogo/protobuf/gogoproto
github.com/gogo/protobuf/proto
github.com/gogo/protobuf/protoc-gen-gogo/descriptor
# github.com/golang/protobuf v1.5.2
## explicit; go 1.9
github.com/golang/protobuf/proto
github.com/golang/protobuf/ptypes
github.com/golang/protobuf/ptypes/any
github.com/golang/protobuf/ptypes/duration
github.com/golang/protobuf/ptypes/timestamp
# github.com/gorilla/mux v1.8.0
## explicit
## explicit; go 1.12
github.com/gorilla/mux
# github.com/hashicorp/golang-lru v0.5.1
## explicit
# github.com/matttproud/golang_protobuf_extensions v1.0.1
## explicit
github.com/matttproud/golang_protobuf_extensions/pbutil
# 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/encoders/builtin
github.com/nats-io/nats.go/util
# github.com/nats-io/nkeys v0.3.0
## explicit; go 1.16
github.com/nats-io/nkeys
# github.com/nats-io/nuid v1.0.1
## explicit
github.com/nats-io/nuid
# 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/pb
# github.com/openfaas/faas-provider v0.18.7
## explicit
## explicit; go 1.16
github.com/openfaas/faas-provider/auth
github.com/openfaas/faas-provider/types
# 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/nats
# 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/internal
github.com/prometheus/client_golang/prometheus/promhttp
# github.com/prometheus/client_model v0.2.0
## explicit
## explicit; go 1.9
github.com/prometheus/client_model/go
# github.com/prometheus/common v0.26.0
## explicit; go 1.11
github.com/prometheus/common/expfmt
github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg
github.com/prometheus/common/model
# github.com/prometheus/procfs v0.6.0
## explicit; go 1.13
github.com/prometheus/procfs
github.com/prometheus/procfs/internal/fs
github.com/prometheus/procfs/internal/util
# go.uber.org/goleak v1.1.10
## explicit
## explicit; go 1.13
go.uber.org/goleak
go.uber.org/goleak/internal/stack
# golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
## explicit; go 1.17
golang.org/x/crypto/ed25519
golang.org/x/crypto/ed25519/internal/edwards25519
# golang.org/x/lint v0.0.0-20190930215403-16217165b5de
## explicit
golang.org/x/lint
golang.org/x/lint/golint
# golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
## explicit
golang.org/x/sync/singleflight
# golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
## explicit; go 1.17
golang.org/x/sys/internal/unsafeheader
golang.org/x/sys/unix
golang.org/x/sys/windows
# 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/gcexportdata
golang.org/x/tools/go/internal/gcimporter
# google.golang.org/protobuf v1.26.0
## explicit; go 1.9
google.golang.org/protobuf/encoding/prototext
google.golang.org/protobuf/encoding/protowire
google.golang.org/protobuf/internal/descfmt

2
go.mod
View File

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