Files
fn-serverless/api/models/route.go
Reed Allman caba9e0ec6 more strict configuration of routes
* idle_timeout max of 1h
* timeout max of 120s for sync, 1h for async
* max memory of 8GB
* do full route validation before call invocation
* ensure that idle_timeout >= timeout

we are now doing validation of updating route inside of the database
transaction, which is what we should have been doing all along really.
we need this behavior to ensure that the idle timeout is longer than the
timeout, among other benefits (like not updating the most recent version of
the existing struct and overwriting previous updates, yay). since we have
this, we can get rid of the weird skipZero behavior on validate too and
validate the real deal holyfield.

validating the route before making the call is handy so that we don't do weird
things like run a func that wants to use 300GB of RAM and run for 3 weeks.

closes #192
closes #344
closes #162
2017-09-21 04:04:34 -07:00

186 lines
3.7 KiB
Go

package models
import (
"net/http"
"net/url"
"path"
"strings"
)
const (
DefaultTimeout = 30 // seconds
DefaultIdleTimeout = 30 // seconds
DefaultMemory = 128 // MB
MaxSyncTimeout = 120 // 2 minutes
MaxAsyncTimeout = 3600 // 1 hour
MaxIdleTimeout = MaxAsyncTimeout
MaxMemory = 1024 * 8 // 8GB TODO should probably be a var of machine max?
)
type Routes []*Route
type Route struct {
AppName string `json:"app_name" db:"app_name"`
Path string `json:"path" db:"path"`
Image string `json:"image" db:"image"`
Memory uint64 `json:"memory" db:"memory"`
Headers Headers `json:"headers" db:"headers"`
Type string `json:"type" db:"type"`
Format string `json:"format" db":format"`
Timeout int32 `json:"timeout" db:"timeout"`
IdleTimeout int32 `json:"idle_timeout" db:"idle_timeout"`
Config Config `json:"config" db:"config"`
}
// SetDefaults sets zeroed field to defaults.
func (r *Route) SetDefaults() {
if r.Memory == 0 {
r.Memory = DefaultMemory
}
if r.Type == TypeNone {
r.Type = TypeSync
}
if r.Format == "" {
r.Format = FormatDefault
}
if r.Headers == nil {
r.Headers = Headers(http.Header{})
}
if r.Config == nil {
r.Config = map[string]string{}
}
if r.Timeout == 0 {
r.Timeout = DefaultTimeout
}
if r.IdleTimeout == 0 {
r.IdleTimeout = DefaultIdleTimeout
}
}
// Validate validates all field values, returning the first error, if any.
func (r *Route) Validate() error {
if r.AppName == "" {
return ErrRoutesMissingAppName
}
if r.Path == "" {
return ErrRoutesMissingPath
} else {
u, err := url.Parse(r.Path)
if err != nil {
return ErrPathMalformed
}
if strings.Contains(u.Path, ":") {
return ErrFoundDynamicURL
}
if !path.IsAbs(u.Path) {
return ErrRoutesInvalidPath
}
}
if r.Image == "" {
return ErrRoutesMissingImage
}
if r.Type != TypeAsync && r.Type != TypeSync {
return ErrRoutesInvalidType
}
if r.Format != FormatDefault && r.Format != FormatHTTP {
return ErrRoutesInvalidFormat
}
if r.Timeout <= 0 ||
(r.Type == TypeSync && r.Timeout > MaxSyncTimeout) ||
(r.Type == TypeAsync && r.Timeout > MaxAsyncTimeout) {
return ErrRoutesInvalidTimeout
}
if r.IdleTimeout <= 0 || r.IdleTimeout > MaxIdleTimeout {
return ErrRoutesInvalidIdleTimeout
}
if r.Timeout > r.IdleTimeout {
return ErrRoutesTimeoutLongerThanIdle
}
if r.Memory < 1 || r.Memory > MaxMemory {
return ErrRoutesInvalidMemory
}
return nil
}
func (r *Route) Clone() *Route {
var clone Route
clone.AppName = r.AppName
clone.Path = r.Path
clone.Update(r)
return &clone
}
// Update updates fields in r with non-zero field values from new.
// 0-length slice Header values, and empty-string Config values trigger removal of map entry.
func (r *Route) Update(new *Route) {
if new.Image != "" {
r.Image = new.Image
}
if new.Memory != 0 {
r.Memory = new.Memory
}
if new.Type != "" {
r.Type = new.Type
}
if new.Timeout != 0 {
r.Timeout = new.Timeout
}
if new.IdleTimeout != 0 {
r.IdleTimeout = new.IdleTimeout
}
if new.Format != "" {
r.Format = new.Format
}
if new.Headers != nil {
if r.Headers == nil {
r.Headers = Headers(make(http.Header))
}
for k, v := range new.Headers {
if len(v) == 0 {
http.Header(r.Headers).Del(k)
} else {
r.Headers[k] = v
}
}
}
if new.Config != nil {
if r.Config == nil {
r.Config = make(Config)
}
for k, v := range new.Config {
if v == "" {
delete(r.Config, k)
} else {
r.Config[k] = v
}
}
}
}
type RouteFilter struct {
PathPrefix string // this is prefix match TODO
AppName string // this is exact match (important for security)
Image string // this is exact match
Cursor string
PerPage int
}