Skip to content

Commit

Permalink
Implement a new static mode
Browse files Browse the repository at this point in the history
This mode allows the users to use the watchdog as a static server for
serving static files. The added benefit over a plain file server is that
they get the RED metrics already implemented by the watchdog.

Signed-off-by: Matias Pan <[email protected]>
  • Loading branch information
matipan authored and alexellis committed Aug 13, 2019
1 parent e4748bd commit 28449bb
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 58 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ See also: [Classic Watchdog](https://github.com/openfaas/faas/tree/master/watchd

History/context: the original watchdog supported mode the Serializing fork mode only and Afterburn was available for testing via a pull request.

When the of-watchdog is complete this version will support four modes as listed below. We may consolidate or remove some of these modes before going to 1.0 so please consider modes 2-4 experimental.
When the of-watchdog is complete this version will support five modes as listed below. We may consolidate or remove some of these modes before going to 1.0 so please consider modes 2-4 experimental.

### 1. HTTP (mode=http)

Expand Down Expand Up @@ -143,6 +143,10 @@ Vastly accelerated processing speed but requires a client library for each langu

* Exec timeout: not supported.

### 5. Static (mode=static)

This mode starts an HTTP file server for serving static content found at the directory specified by `static_path`.

## Configuration

Environmental variables:
Expand All @@ -152,6 +156,7 @@ Environmental variables:
| Option | Implemented | Usage |
|------------------------|--------------|-------------------------------|
| `function_process` | Yes | Process to execute a server in `http` mode or to be executed for each request in the other modes. For non `http` mode the process must accept input via STDIN and print output via STDOUT. Alias: `fprocess` |
| `static_path` | Yes | Absolute or relative path to the directory that will be served if `mode="static"` |
| `read_timeout` | Yes | HTTP timeout for reading the payload from the client caller (in seconds) |
| `write_timeout` | Yes | HTTP timeout for writing a response body from your function (in seconds) |
| `exec_timeout` | Yes | Exec timeout for process exec'd for each incoming request (in seconds). Disabled if set to 0. |
Expand Down
11 changes: 9 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type WatchdogConfig struct {
OperationalMode int
SuppressLock bool
UpstreamURL string
StaticPath string

// BufferHTTPBody buffers the HTTP body in memory
// to prevent transfer type of chunked encoding
Expand Down Expand Up @@ -48,7 +49,7 @@ func (w WatchdogConfig) Process() (string, []string) {
}

// New create config based upon environmental variables.
func New(env []string) (WatchdogConfig, error) {
func New(env []string) WatchdogConfig {

envMap := mapEnv(env)

Expand All @@ -74,11 +75,17 @@ func New(env []string) (WatchdogConfig, error) {
contentType = val
}

staticPath := "/home/app/public"
if val, exists := envMap["static_path"]; exists {
staticPath = val
}

config := WatchdogConfig{
TCPPort: getInt(envMap, "port", 8080),
HTTPReadTimeout: getDuration(envMap, "read_timeout", time.Second*10),
HTTPWriteTimeout: getDuration(envMap, "write_timeout", time.Second*10),
FunctionProcess: functionProcess,
StaticPath: staticPath,
InjectCGIHeaders: true,
ExecTimeout: getDuration(envMap, "exec_timeout", time.Second*10),
OperationalMode: ModeStreaming,
Expand All @@ -94,7 +101,7 @@ func New(env []string) (WatchdogConfig, error) {
config.OperationalMode = WatchdogModeConst(val)
}

return config, nil
return config
}

func mapEnv(env []string) map[string]string {
Expand Down
9 changes: 8 additions & 1 deletion config/config_modes.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ const (
// ModeAfterBurn for performance tuning
ModeAfterBurn = 3

//ModeHTTP for routing requests over HTTP
// ModeHTTP for routing requests over HTTP
ModeHTTP = 4

// ModeStatic for serving static content
ModeStatic = 5
)

// WatchdogModeConst as a const int
Expand All @@ -25,6 +28,8 @@ func WatchdogModeConst(mode string) int {
return ModeSerializing
case "http":
return ModeHTTP
case "static":
return ModeStatic
default:
return 0
}
Expand All @@ -41,6 +46,8 @@ func WatchdogMode(mode int) string {
return "serializing"
case ModeHTTP:
return "http"
case ModeStatic:
return "static"
default:
return "unknown"
}
Expand Down
72 changes: 24 additions & 48 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,22 @@ import (
)

func TestNew(t *testing.T) {
defaults, err := New([]string{})
if err != nil {
t.Errorf("Expected no errors")
}
defaults := New([]string{})
if defaults.TCPPort != 8080 {
t.Errorf("Want TCPPort: 8080, got: %d", defaults.TCPPort)
}
}

func Test_OperationalMode_Default(t *testing.T) {
defaults, err := New([]string{})
if err != nil {
t.Errorf("Expected no errors")
}
defaults := New([]string{})
if defaults.OperationalMode != ModeStreaming {
t.Errorf("Want %s. got: %s", WatchdogMode(ModeStreaming), WatchdogMode(defaults.OperationalMode))
}
}
func Test_BufferHttpModeDefaultsToFalse(t *testing.T) {
env := []string{}

actual, err := New(env)
if err != nil {
t.Errorf("Expected no errors")
}
actual := New(env)
want := false
if actual.BufferHTTPBody != want {
t.Errorf("Want %v. got: %v", want, actual.BufferHTTPBody)
Expand All @@ -42,10 +33,7 @@ func Test_BufferHttpMode_CanBeSetToTrue(t *testing.T) {
"buffer_http=true",
}

actual, err := New(env)
if err != nil {
t.Errorf("Expected no errors")
}
actual := New(env)
want := true
if actual.BufferHTTPBody != want {
t.Errorf("Want %v. got: %v", want, actual.BufferHTTPBody)
Expand All @@ -57,23 +45,29 @@ func Test_OperationalMode_AfterBurn(t *testing.T) {
"mode=afterburn",
}

actual, err := New(env)
if err != nil {
t.Errorf("Expected no errors")
}
actual := New(env)

if actual.OperationalMode != ModeAfterBurn {
t.Errorf("Want %s. got: %s", WatchdogMode(ModeAfterBurn), WatchdogMode(actual.OperationalMode))
}
}

func Test_OperationalMode_Static(t *testing.T) {
env := []string{
"mode=static",
}

actual := New(env)

if actual.OperationalMode != ModeStatic {
t.Errorf("Want %s. got: %s", WatchdogMode(ModeStatic), WatchdogMode(actual.OperationalMode))
}
}

func Test_ContentType_Default(t *testing.T) {
env := []string{}

actual, err := New(env)
if err != nil {
t.Errorf("Expected no errors")
}
actual := New(env)

if actual.ContentType != "application/octet-stream" {
t.Errorf("Default (ContentType) Want %s. got: %s", actual.ContentType, "octet-stream")
Expand All @@ -85,10 +79,7 @@ func Test_ContentType_Override(t *testing.T) {
"content_type=application/json",
}

actual, err := New(env)
if err != nil {
t.Errorf("Expected no errors")
}
actual := New(env)

if actual.ContentType != "application/json" {
t.Errorf("(ContentType) Want %s. got: %s", actual.ContentType, "application/json")
Expand All @@ -100,10 +91,7 @@ func Test_FunctionProcessLegacyName(t *testing.T) {
"fprocess=env",
}

actual, err := New(env)
if err != nil {
t.Errorf("Expected no errors")
}
actual := New(env)

if actual.FunctionProcess != "env" {
t.Errorf("Want %s. got: %s", "env", actual.FunctionProcess)
Expand All @@ -115,10 +103,7 @@ func Test_FunctionProcessAlternativeName(t *testing.T) {
"function_process=env",
}

actual, err := New(env)
if err != nil {
t.Errorf("Expected no errors")
}
actual := New(env)

if actual.FunctionProcess != "env" {
t.Errorf("Want %s. got: %s", "env", actual.FunctionProcess)
Expand Down Expand Up @@ -161,10 +146,7 @@ func Test_FunctionProcess_Arguments(t *testing.T) {

for _, testCase := range cases {
t.Run(testCase.scenario, func(t *testing.T) {
actual, err := New([]string{testCase.env})
if err != nil {
t.Errorf("Expected no errors")
}
actual := New([]string{testCase.env})

process, args := actual.Process()
if process != testCase.wantProcess {
Expand Down Expand Up @@ -192,10 +174,7 @@ func Test_PortOverride(t *testing.T) {
"port=8081",
}

actual, err := New(env)
if err != nil {
t.Errorf("Expected no errors")
}
actual := New(env)

if actual.TCPPort != 8081 {
t.Errorf("Want %d. got: %d", 8081, actual.TCPPort)
Expand Down Expand Up @@ -241,10 +220,7 @@ func Test_Timeouts(t *testing.T) {
}

for _, testCase := range cases {
actual, err := New(testCase.env)
if err != nil {
t.Errorf("(%s) Expected no errors", testCase.name)
}
actual := New(testCase.env)
if testCase.readTimeout != actual.HTTPReadTimeout {
t.Errorf("(%s) HTTPReadTimeout want: %s, got: %s", testCase.name, actual.HTTPReadTimeout, testCase.readTimeout)
}
Expand Down
19 changes: 13 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,9 @@ var (
func main() {
atomic.StoreInt32(&acceptingConnections, 0)

watchdogConfig, configErr := config.New(os.Environ())
if configErr != nil {
fmt.Fprintf(os.Stderr, configErr.Error())
os.Exit(-1)
}
watchdogConfig := config.New(os.Environ())

if len(watchdogConfig.FunctionProcess) == 0 {
if len(watchdogConfig.FunctionProcess) == 0 && watchdogConfig.OperationalMode != config.ModeStatic {
fmt.Fprintf(os.Stderr, "Provide a \"function_process\" or \"fprocess\" environmental variable for your function.\n")
os.Exit(-1)
}
Expand Down Expand Up @@ -150,6 +146,8 @@ func buildRequestHandler(watchdogConfig config.WatchdogConfig) http.Handler {
case config.ModeHTTP:
requestHandler = makeHTTPRequestHandler(watchdogConfig)
break
case config.ModeStatic:
requestHandler = makeStaticRequestHandler(watchdogConfig)
default:
log.Panicf("unknown watchdog mode: %d", watchdogConfig.OperationalMode)
break
Expand Down Expand Up @@ -338,6 +336,15 @@ func makeHTTPRequestHandler(watchdogConfig config.WatchdogConfig) func(http.Resp
}
}

func makeStaticRequestHandler(watchdogConfig config.WatchdogConfig) http.HandlerFunc {
if watchdogConfig.StaticPath == "" {
log.Fatal(`For mode=static you must specify the "static_path" to serve`)
}

log.Printf("Serving files at %s", watchdogConfig.StaticPath)
return http.FileServer(http.Dir(watchdogConfig.StaticPath)).ServeHTTP
}

func lockFilePresent() bool {
path := filepath.Join(os.TempDir(), ".lock")
if _, err := os.Stat(path); os.IsNotExist(err) {
Expand Down

0 comments on commit 28449bb

Please sign in to comment.