mirror of
https://github.com/openfaas/faas.git
synced 2025-06-19 12:36:40 +00:00
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:
committed by
Alex Ellis
parent
01841f605c
commit
88eea5f62e
@ -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 .
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 := ""
|
||||
|
||||
|
@ -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 {
|
||||
}
|
||||
|
45
gateway/handlers/probe_handler.go
Normal file
45
gateway/handlers/probe_handler.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
155
gateway/pkg/middleware/baseurlresolver_test.go
Normal file
155
gateway/pkg/middleware/baseurlresolver_test.go
Normal 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()
|
||||
}
|
||||
}
|
@ -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"
|
156
gateway/pkg/middleware/resolver.go
Normal file
156
gateway/pkg/middleware/resolver.go
Normal 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, "/")
|
||||
}
|
10
gateway/pkg/middleware/service_name.go
Normal file
10
gateway/pkg/middleware/service_name.go
Normal 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
|
||||
}
|
@ -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"
|
@ -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
58
gateway/probing/cache.go
Normal 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
116
gateway/probing/prober.go
Normal 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(),
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
25
gateway/types/retry.go
Normal 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
|
||||
}
|
43
gateway/types/retry_test.go
Normal file
43
gateway/types/retry_test.go
Normal 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)
|
||||
}
|
||||
}
|
3
gateway/vendor/github.com/cespare/xxhash/v2/go.mod
generated
vendored
3
gateway/vendor/github.com/cespare/xxhash/v2/go.mod
generated
vendored
@ -1,3 +0,0 @@
|
||||
module github.com/cespare/xxhash/v2
|
||||
|
||||
go 1.11
|
0
gateway/vendor/github.com/cespare/xxhash/v2/go.sum
generated
vendored
0
gateway/vendor/github.com/cespare/xxhash/v2/go.sum
generated
vendored
3
gateway/vendor/github.com/gorilla/mux/go.mod
generated
vendored
3
gateway/vendor/github.com/gorilla/mux/go.mod
generated
vendored
@ -1,3 +0,0 @@
|
||||
module github.com/gorilla/mux
|
||||
|
||||
go 1.12
|
8
gateway/vendor/github.com/nats-io/nats.go/go.mod
generated
vendored
8
gateway/vendor/github.com/nats-io/nats.go/go.mod
generated
vendored
@ -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
|
||||
)
|
11
gateway/vendor/github.com/nats-io/nats.go/go.sum
generated
vendored
11
gateway/vendor/github.com/nats-io/nats.go/go.sum
generated
vendored
@ -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=
|
5
gateway/vendor/github.com/nats-io/nkeys/go.mod
generated
vendored
5
gateway/vendor/github.com/nats-io/nkeys/go.mod
generated
vendored
@ -1,5 +0,0 @@
|
||||
module github.com/nats-io/nkeys
|
||||
|
||||
go 1.16
|
||||
|
||||
require golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b
|
7
gateway/vendor/github.com/nats-io/nkeys/go.sum
generated
vendored
7
gateway/vendor/github.com/nats-io/nkeys/go.sum
generated
vendored
@ -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=
|
9
gateway/vendor/github.com/nats-io/stan.go/go.mod
generated
vendored
9
gateway/vendor/github.com/nats-io/stan.go/go.mod
generated
vendored
@ -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
|
||||
)
|
42
gateway/vendor/github.com/nats-io/stan.go/go.sum
generated
vendored
42
gateway/vendor/github.com/nats-io/stan.go/go.sum
generated
vendored
@ -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=
|
9
gateway/vendor/github.com/prometheus/procfs/go.mod
generated
vendored
9
gateway/vendor/github.com/prometheus/procfs/go.mod
generated
vendored
@ -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
|
||||
)
|
8
gateway/vendor/github.com/prometheus/procfs/go.sum
generated
vendored
8
gateway/vendor/github.com/prometheus/procfs/go.sum
generated
vendored
@ -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=
|
11
gateway/vendor/go.uber.org/goleak/go.mod
generated
vendored
11
gateway/vendor/go.uber.org/goleak/go.mod
generated
vendored
@ -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
|
||||
)
|
30
gateway/vendor/go.uber.org/goleak/go.sum
generated
vendored
30
gateway/vendor/go.uber.org/goleak/go.sum
generated
vendored
@ -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=
|
3
gateway/vendor/golang.org/x/lint/go.mod
generated
vendored
3
gateway/vendor/golang.org/x/lint/go.mod
generated
vendored
@ -1,3 +0,0 @@
|
||||
module golang.org/x/lint
|
||||
|
||||
require golang.org/x/tools v0.0.0-20190311212946-11955173bddd
|
6
gateway/vendor/golang.org/x/lint/go.sum
generated
vendored
6
gateway/vendor/golang.org/x/lint/go.sum
generated
vendored
@ -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=
|
28
gateway/vendor/modules.txt
vendored
28
gateway/vendor/modules.txt
vendored
@ -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
|
||||
|
Reference in New Issue
Block a user