Support external URL for FaaS functions

This commit is contained in:
Alex
2017-07-25 09:33:12 +01:00
committed by Alex Ellis
parent f239033aa5
commit 35a15cff01
9 changed files with 113 additions and 49 deletions

View File

@ -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:

View File

@ -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 .

View File

@ -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 .

View File

@ -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 .

View File

@ -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 .

View File

@ -0,0 +1 @@
package handlers

View File

@ -4,29 +4,44 @@ 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
if config.UseExternalProvider() {
log.Printf("Binding to external function provider: %s", config.FunctionsProviderURL)
} else {
var err error
dockerClient, err = client.NewEnvClient()
if err != nil {
@ -37,34 +52,55 @@ func main() {
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)
r := mux.NewRouter()
// r.StrictSlash(false) // This didn't work, so register routes twice.
var faasHandlers handlerSet
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)
if config.UseExternalProvider() {
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")
reverseProxy := httputil.NewSingleHostReverseProxy(config.FunctionsProviderURL)
fs := http.FileServer(http.Dir("./assets/"))
r.PathPrefix("/ui/").Handler(http.StripPrefix("/ui", fs)).Methods("GET")
faasHandlers.Proxy = handler(reverseProxy)
faasHandlers.RoutelessProxy = handler(reverseProxy)
faasHandlers.Alert = handler(reverseProxy)
faasHandlers.ListFunctions = handler(reverseProxy)
faasHandlers.DeployFunction = handler(reverseProxy)
faasHandlers.DeleteFunction = handler(reverseProxy)
r.HandleFunc("/", faasHandlers.MakeProxy(metricsOptions, false, dockerClient, &logger)).Methods("POST")
metricsHandler := metrics.PrometheusHandler()
r.Handle("/metrics", metricsHandler)
} 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)
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.RoutelessProxy).Methods("POST")
metricsHandler := metrics.PrometheusHandler()
r.Handle("/metrics", metricsHandler)
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)
}
}

View File

@ -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 {

View File

@ -1,6 +1,8 @@
package main
package types
import (
"log"
"net/url"
"os"
"strconv"
"time"
@ -51,6 +53,14 @@ 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
}
@ -58,4 +68,10 @@ func (ReadConfig) Read(hasEnv HasEnv) GatewayConfig {
type GatewayConfig struct {
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
}