From 35a15cff01688acac2ab371caf493b508ab2cb33 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 25 Jul 2017 09:33:12 +0100 Subject: [PATCH] Support external URL for FaaS functions --- docker-compose.yml | 2 +- gateway/Dockerfile.build | 10 +-- gateway/Dockerfile.build.armhf | 2 +- gateway/Dockerfile.multistage | 2 +- gateway/Dockerfile.newbuild | 2 +- gateway/handlers/externalprovider.go | 1 + gateway/server.go | 103 +++++++++++++++++++-------- gateway/tests/config_test.go | 18 ++++- gateway/{ => types}/readconfig.go | 22 +++++- 9 files changed, 113 insertions(+), 49 deletions(-) create mode 100644 gateway/handlers/externalprovider.go rename gateway/{ => types}/readconfig.go (66%) diff --git a/docker-compose.yml b/docker-compose.yml index 0a122beb..6c2bfd0a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: - "/var/run/docker.sock:/var/run/docker.sock" ports: - 8080:8080 - image: functions/gateway:0.5.6 + image: functions/gateway:latest-dev networks: - functions environment: diff --git a/gateway/Dockerfile.build b/gateway/Dockerfile.build index 2b9b18a7..acc223cc 100644 --- a/gateway/Dockerfile.build +++ b/gateway/Dockerfile.build @@ -1,13 +1,5 @@ FROM golang:1.7.5 -#RUN go get -d github.com/docker/docker/api/types \ -# && go get -d github.com/docker/docker/api/types/filters \ -# && go get -d github.com/docker/docker/api/types/swarm \ -# && go get -d github.com/docker/docker/client \ -# && go get github.com/gorilla/mux \ -# && go get github.com/prometheus/client_golang/prometheus -#RUN go get -d github.com/Sirupsen/logrus - WORKDIR /go/src/github.com/alexellis/faas/gateway COPY vendor vendor @@ -17,7 +9,7 @@ COPY metrics metrics COPY requests requests COPY tests tests COPY server.go . -COPY readconfig.go . +COPY types types RUN go test -v ./tests && \ CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . diff --git a/gateway/Dockerfile.build.armhf b/gateway/Dockerfile.build.armhf index cc7de611..8583a3ae 100644 --- a/gateway/Dockerfile.build.armhf +++ b/gateway/Dockerfile.build.armhf @@ -16,7 +16,7 @@ COPY tests tests COPY handlers handlers COPY server.go . -COPY readconfig.go . +COPY types types RUN go test -v ./tests && \ CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . diff --git a/gateway/Dockerfile.multistage b/gateway/Dockerfile.multistage index 66d0697d..bde2f876 100644 --- a/gateway/Dockerfile.multistage +++ b/gateway/Dockerfile.multistage @@ -8,7 +8,7 @@ COPY metrics metrics COPY requests requests COPY tests tests COPY server.go . -COPY readconfig.go . +COPY types types RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gateway . diff --git a/gateway/Dockerfile.newbuild b/gateway/Dockerfile.newbuild index e51fa31e..e1930f42 100644 --- a/gateway/Dockerfile.newbuild +++ b/gateway/Dockerfile.newbuild @@ -9,7 +9,7 @@ COPY metrics metrics COPY requests requests COPY tests tests COPY server.go . -COPY readconfig.go . +COPY types types RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gateway . diff --git a/gateway/handlers/externalprovider.go b/gateway/handlers/externalprovider.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/gateway/handlers/externalprovider.go @@ -0,0 +1 @@ +package handlers diff --git a/gateway/server.go b/gateway/server.go index 6ae36666..9d2b1ccd 100644 --- a/gateway/server.go +++ b/gateway/server.go @@ -4,67 +4,103 @@ import ( "context" "log" "net/http" - - "github.com/Sirupsen/logrus" - faasHandlers "github.com/alexellis/faas/gateway/handlers" - "github.com/alexellis/faas/gateway/metrics" - "github.com/docker/docker/client" + "net/http/httputil" "fmt" + "github.com/Sirupsen/logrus" + internalHandlers "github.com/alexellis/faas/gateway/handlers" + "github.com/alexellis/faas/gateway/metrics" + "github.com/alexellis/faas/gateway/types" + "github.com/docker/docker/client" + "github.com/gorilla/mux" ) +type handlerSet struct { + Proxy http.HandlerFunc + DeployFunction http.HandlerFunc + DeleteFunction http.HandlerFunc + ListFunctions http.HandlerFunc + Alert http.HandlerFunc + RoutelessProxy http.HandlerFunc +} + func main() { logger := logrus.Logger{} logrus.SetFormatter(&logrus.TextFormatter{}) - osEnv := OsEnv{} - readConfig := ReadConfig{} + osEnv := types.OsEnv{} + readConfig := types.ReadConfig{} config := readConfig.Read(osEnv) log.Printf("HTTP Read Timeout: %s", config.ReadTimeout) log.Printf("HTTP Write Timeout: %s", config.WriteTimeout) var dockerClient *client.Client - var err error - dockerClient, err = client.NewEnvClient() - if err != nil { - log.Fatal("Error with Docker client.") + + if config.UseExternalProvider() { + log.Printf("Binding to external function provider: %s", config.FunctionsProviderURL) + } else { + var err error + dockerClient, err = client.NewEnvClient() + if err != nil { + log.Fatal("Error with Docker client.") + } + dockerVersion, err := dockerClient.ServerVersion(context.Background()) + if err != nil { + log.Fatal("Error with Docker server.\n", err) + } + log.Printf("Docker API version: %s, %s\n", dockerVersion.APIVersion, dockerVersion.Version) } - dockerVersion, err := dockerClient.ServerVersion(context.Background()) - if err != nil { - log.Fatal("Error with Docker server.\n", err) - } - log.Printf("Docker API version: %s, %s\n", dockerVersion.APIVersion, dockerVersion.Version) metricsOptions := metrics.BuildMetricsOptions() metrics.RegisterMetrics(metricsOptions) + var faasHandlers handlerSet + + if config.UseExternalProvider() { + + reverseProxy := httputil.NewSingleHostReverseProxy(config.FunctionsProviderURL) + + faasHandlers.Proxy = handler(reverseProxy) + faasHandlers.RoutelessProxy = handler(reverseProxy) + faasHandlers.Alert = handler(reverseProxy) + faasHandlers.ListFunctions = handler(reverseProxy) + faasHandlers.DeployFunction = handler(reverseProxy) + faasHandlers.DeleteFunction = handler(reverseProxy) + + } else { + faasHandlers.Proxy = internalHandlers.MakeProxy(metricsOptions, true, dockerClient, &logger) + faasHandlers.RoutelessProxy = internalHandlers.MakeProxy(metricsOptions, true, dockerClient, &logger) + faasHandlers.Alert = internalHandlers.MakeAlertHandler(dockerClient) + faasHandlers.ListFunctions = internalHandlers.MakeFunctionReader(metricsOptions, dockerClient) + faasHandlers.DeployFunction = internalHandlers.MakeNewFunctionHandler(metricsOptions, dockerClient) + faasHandlers.DeleteFunction = internalHandlers.MakeDeleteFunctionHandler(metricsOptions, dockerClient) + + // This could exist in a separate process - records the replicas of each swarm service. + functionLabel := "function" + metrics.AttachSwarmWatcher(dockerClient, metricsOptions, functionLabel) + } + r := mux.NewRouter() + // r.StrictSlash(false) // This didn't work, so register routes twice. + r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}", faasHandlers.Proxy) + r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}/", faasHandlers.Proxy) - functionHandler := faasHandlers.MakeProxy(metricsOptions, true, dockerClient, &logger) - r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}", functionHandler) - r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}/", functionHandler) - - r.HandleFunc("/system/alert", faasHandlers.MakeAlertHandler(dockerClient)) - r.HandleFunc("/system/functions", faasHandlers.MakeFunctionReader(metricsOptions, dockerClient)).Methods("GET") - r.HandleFunc("/system/functions", faasHandlers.MakeNewFunctionHandler(metricsOptions, dockerClient)).Methods("POST") - r.HandleFunc("/system/functions", faasHandlers.MakeDeleteFunctionHandler(metricsOptions, dockerClient)).Methods("DELETE") + r.HandleFunc("/system/alert", faasHandlers.Alert) + r.HandleFunc("/system/functions", faasHandlers.ListFunctions).Methods("GET") + r.HandleFunc("/system/functions", faasHandlers.DeployFunction).Methods("POST") + r.HandleFunc("/system/functions", faasHandlers.DeleteFunction).Methods("DELETE") fs := http.FileServer(http.Dir("./assets/")) r.PathPrefix("/ui/").Handler(http.StripPrefix("/ui", fs)).Methods("GET") - r.HandleFunc("/", faasHandlers.MakeProxy(metricsOptions, false, dockerClient, &logger)).Methods("POST") + r.HandleFunc("/", faasHandlers.RoutelessProxy).Methods("POST") metricsHandler := metrics.PrometheusHandler() r.Handle("/metrics", metricsHandler) - - // This could exist in a separate process - records the replicas of each swarm service. - functionLabel := "function" - metrics.AttachSwarmWatcher(dockerClient, metricsOptions, functionLabel) - r.Handle("/", http.RedirectHandler("/ui/", http.StatusMovedPermanently)).Methods("GET") tcpPort := 8080 @@ -79,3 +115,10 @@ func main() { log.Fatal(s.ListenAndServe()) } + +func handler(p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + log.Printf("Forwarding [%s] to %s", r.Method, r.URL.String()) + p.ServeHTTP(w, r) + } +} diff --git a/gateway/tests/config_test.go b/gateway/tests/config_test.go index 2060e6da..bd7c918a 100644 --- a/gateway/tests/config_test.go +++ b/gateway/tests/config_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - gateway "github.com/alexellis/faas/gateway" + "github.com/alexellis/faas/gateway/types" ) type EnvBucket struct { @@ -28,9 +28,21 @@ func (e EnvBucket) Setenv(key string, value string) { e.Items[key] = value } +func TestRead_UseExternalProvider_Defaults(t *testing.T) { + defaults := NewEnvBucket() + readConfig := types.ReadConfig{} + + config := readConfig.Read(defaults) + + if config.UseExternalProvider() != false { + t.Log("Default for UseExternalProvider should be false") + t.Fail() + } +} + func TestRead_EmptyTimeoutConfig(t *testing.T) { defaults := NewEnvBucket() - readConfig := gateway.ReadConfig{} + readConfig := types.ReadConfig{} config := readConfig.Read(defaults) @@ -49,7 +61,7 @@ func TestRead_ReadAndWriteTimeoutConfig(t *testing.T) { defaults.Setenv("read_timeout", "10") defaults.Setenv("write_timeout", "60") - readConfig := gateway.ReadConfig{} + readConfig := types.ReadConfig{} config := readConfig.Read(defaults) if (config.ReadTimeout) != time.Duration(10)*time.Second { diff --git a/gateway/readconfig.go b/gateway/types/readconfig.go similarity index 66% rename from gateway/readconfig.go rename to gateway/types/readconfig.go index 30eda67c..04d6ff90 100644 --- a/gateway/readconfig.go +++ b/gateway/types/readconfig.go @@ -1,6 +1,8 @@ -package main +package types import ( + "log" + "net/url" "os" "strconv" "time" @@ -51,11 +53,25 @@ func (ReadConfig) Read(hasEnv HasEnv) GatewayConfig { cfg.ReadTimeout = time.Duration(readTimeout) * time.Second cfg.WriteTimeout = time.Duration(writeTimeout) * time.Second + if len(hasEnv.Getenv("functions_provider_url")) > 0 { + var err error + cfg.FunctionsProviderURL, err = url.Parse(hasEnv.Getenv("functions_provider_url")) + if err != nil { + log.Fatal("If functions_provider_url is provided, then it should be a valid URL.", err) + } + } + return cfg } // GatewayConfig for the process. type GatewayConfig struct { - ReadTimeout time.Duration - WriteTimeout time.Duration + ReadTimeout time.Duration + WriteTimeout time.Duration + FunctionsProviderURL *url.URL +} + +// UseExternalProvider decide whether to bypass built-in Docker Swarm engine +func (g *GatewayConfig) UseExternalProvider() bool { + return g.FunctionsProviderURL != nil }