Remove V1 endpoints and Routes (#1210)

Largely a removal job, however many tests, particularly system level
ones relied on Routes. These have been migrated to use Fns.

* Add 410 response to swagger
* No app names in log tags
* Adding constraint in GetCall for FnID
* Adding test to check FnID is required on call
* Add fn_id to call selector
* Fix text in docker mem warning
* Correct buildConfig func name
* Test fix up
* Removing CPU setting from Agent test

CPU setting has been deprecated, but the code base is still riddled
with it. This just removes it from this layer. Really we need to
remove it from Call.

* Remove fn id check on calls
* Reintroduce fn id required on call
* Adding fnID to calls for execute test
* Correct setting of app id in middleware
* Removes root middlewares ability to redirect fun invocations
* Add over sized test check
* Removing call fn id check
This commit is contained in:
Tom Coupland
2018-09-17 16:44:51 +01:00
committed by Owen Cliffe
parent 6a01dae923
commit d56a49b321
82 changed files with 572 additions and 5558 deletions

View File

@@ -31,7 +31,7 @@ const (
var possibleStatuses = [...]string{"delayed", "queued", "running", "success", "error", "cancelled"}
// Call is a representation of a specific invocation of a route.
// Call is a representation of a specific invocation of a fn.
type Call struct {
// Unique identifier representing a specific call.
ID string `json:"id" db:"id"`
@@ -73,9 +73,6 @@ type Call struct {
// - client_request - Request was cancelled by a client.
Status string `json:"status" db:"status"`
// Path of the route that is responsible for this call
Path string `json:"path" db:"path"`
// Name of Docker image to use.
Image string `json:"image,omitempty" db:"-"`
@@ -124,7 +121,7 @@ type Call struct {
// Config is the set of configuration variables for the call
Config Config `json:"config,omitempty" db:"-"`
// Annotations is the set of annotations for the app/route of the call.
// Annotations is the set of annotations for the app/fn of the call.
Annotations Annotations `json:"annotations,omitempty" db:"-"`
// Headers are headers from the request that created this call
@@ -163,8 +160,6 @@ type Call struct {
}
type CallFilter struct {
Path string // match
AppID string // match
FnID string //match
FromTime common.DateTime
ToTime common.DateTime

View File

@@ -33,29 +33,6 @@ type Datastore interface {
// Returns ErrAppsNotFound if an App is not found.
RemoveApp(ctx context.Context, appID string) error
// GetRoute looks up a matching Route for appName and the literal request route routePath.
// Returns ErrDatastoreEmptyAppName when appName is empty, and ErrDatastoreEmptyRoutePath when
// routePath is empty.
// Returns ErrRoutesNotFound when no matching route is found.
GetRoute(ctx context.Context, appID, routePath string) (*Route, error)
// GetRoutesByApp gets a slice of routes for a appName, optionally filtering on filter (filter.AppName is ignored).
// Returns ErrDatastoreEmptyAppName if appName is empty.
GetRoutesByApp(ctx context.Context, appID string, filter *RouteFilter) ([]*Route, error)
// InsertRoute inserts a route. Returns ErrDatastoreEmptyRoute when route is nil, and ErrDatastoreEmptyAppName
// or ErrDatastoreEmptyRoutePath for empty AppName or Path.
// Returns ErrRoutesAlreadyExists if the exact route.Path already exists
InsertRoute(ctx context.Context, route *Route) (*Route, error)
// UpdateRoute updates route's Config and Header fields. Returns ErrDatastoreEmptyRoute when route is nil, and
// ErrDatastoreEmptyAppName or ErrDatastoreEmptyRoutePath for empty AppName or Path.
UpdateRoute(ctx context.Context, route *Route) (*Route, error)
// RemoveRoute removes a route. Returns ErrDatastoreEmptyAppID when appName is empty, and
// ErrDatastoreEmptyRoutePath when routePath is empty. Returns ErrRoutesNotFound when no route exists.
RemoveRoute(ctx context.Context, appID, routePath string) error
// InsertFn inserts a new function if one does not exist, applying any defaults necessary,
InsertFn(ctx context.Context, fn *Fn) (*Fn, error)

View File

@@ -77,62 +77,10 @@ var (
code: http.StatusBadRequest,
error: errors.New("Invalid payload"),
}
ErrDatastoreEmptyRoute = err{
code: http.StatusBadRequest,
error: errors.New("Missing route"),
}
ErrRoutesAlreadyExists = err{
code: http.StatusConflict,
error: errors.New("Route already exists"),
}
ErrRoutesMissingNew = err{
code: http.StatusBadRequest,
error: errors.New("Missing new route"),
}
ErrRoutesNotFound = err{
code: http.StatusNotFound,
error: errors.New("Route not found"),
}
ErrRoutesPathImmutable = err{
code: http.StatusConflict,
error: errors.New("Could not update - path is immutable"),
}
ErrFoundDynamicURL = err{
code: http.StatusBadRequest,
error: errors.New("Dynamic URL is not allowed"),
}
ErrRoutesInvalidPath = err{
code: http.StatusBadRequest,
error: errors.New("Invalid route path format"),
}
ErrRoutesInvalidType = err{
code: http.StatusBadRequest,
error: errors.New("Invalid route Type"),
}
ErrRoutesInvalidFormat = err{
code: http.StatusBadRequest,
error: errors.New("Invalid route Format"),
}
ErrRoutesMissingAppID = err{
code: http.StatusBadRequest,
error: errors.New("Missing route AppName"),
}
ErrRoutesMissingImage = err{
code: http.StatusBadRequest,
error: errors.New("Missing route Image"),
}
ErrRoutesInvalidImage = err{
code: http.StatusBadRequest,
error: errors.New("Invalid route Image"),
}
ErrRoutesMissingName = err{
code: http.StatusBadRequest,
error: errors.New("Missing route Name"),
}
ErrRoutesMissingPath = err{
code: http.StatusBadRequest,
error: errors.New("Missing route Path"),
}
ErrPathMalformed = err{
code: http.StatusBadRequest,
error: errors.New("Path malformed"),
@@ -145,21 +93,9 @@ var (
code: http.StatusBadRequest,
error: errors.New("from_time is not an epoch time"),
}
ErrRoutesInvalidTimeout = err{
code: http.StatusBadRequest,
error: fmt.Errorf("timeout value is out of range. Sync should be between 0 and %d, async should be between 0 and %d", MaxSyncTimeout, MaxAsyncTimeout),
}
ErrRoutesInvalidIdleTimeout = err{
code: http.StatusBadRequest,
error: fmt.Errorf("idle_timeout value is out of range. It should be between 0 and %d", MaxIdleTimeout),
}
ErrRoutesInvalidMemory = err{
code: http.StatusBadRequest,
error: fmt.Errorf("memory value is out of range. It should be between 0 and %d", RouteMaxMemory),
}
ErrInvalidMemory = err{
code: http.StatusBadRequest,
error: fmt.Errorf("memory value is out of range. It should be between 0 and %d", RouteMaxMemory),
error: fmt.Errorf("memory value is out of range. It should be between 0 and %d", MaxMemory),
}
ErrCallResourceTooBig = err{
code: http.StatusBadRequest,
@@ -178,14 +114,6 @@ var (
code: http.StatusNotFound,
error: errors.New("Call log not found"),
}
ErrInvokeNotSupported = err{
code: http.StatusBadRequest,
error: errors.New("Invoking routes /r/ is not supported on nodes configured as type API"),
}
ErrAPINotSupported = err{
code: http.StatusBadRequest,
error: errors.New("Invoking api /v1/ requests is not supported on nodes configured as type Runner"),
}
ErrPathNotFound = err{
code: http.StatusNotFound,
error: errors.New("Path not found"),

View File

@@ -18,6 +18,13 @@ var (
MaxTimeout int32 = 300 // 5m
MaxIdleTimeout int32 = 3600 // 1h
DefaultTimeout int32 = 30 // seconds
DefaultIdleTimeout int32 = 30 // seconds
DefaultMemory uint64 = 128 // MB
MaxSyncTimeout = 120 // 2 minutes
MaxAsyncTimeout = 3600 // 1 hour
ErrFnsIDMismatch = err{
code: http.StatusBadRequest,
error: errors.New("Fn ID in path does not match that in body"),
@@ -50,6 +57,10 @@ var (
code: http.StatusBadRequest,
error: errors.New("Missing image on Fn"),
}
ErrFnsInvalidImage = err{
code: http.StatusBadRequest,
error: errors.New("Invalid Fn image"),
}
ErrFnsInvalidFormat = err{
code: http.StatusBadRequest,
error: errors.New("Invalid format on Fn"),

View File

@@ -16,7 +16,7 @@ type LogStore interface {
// TODO we should probably allow deletion of a range of logs (also calls)?
// common cases for deletion will be:
// * route gets nuked
// * fn gets nuked
// * app gets nuked
// * call+logs getting cleaned up periodically
@@ -24,16 +24,9 @@ type LogStore interface {
// exists.
InsertCall(ctx context.Context, call *Call) error
// GetCall returns a call at a certain id and app name.
GetCall1(ctx context.Context, appId, callID string) (*Call, error)
// GetCall2 returns a call at a certain id
GetCall(ctx context.Context, fnID, callID string) (*Call, error)
// GetCalls returns a list of calls that satisfy the given CallFilter. If no
// calls exist, an empty list and a nil error are returned.
GetCalls1(ctx context.Context, filter *CallFilter) ([]*Call, error)
// GetCalls returns a list of calls that satisfy the given CallFilter. If no
// calls exist, an empty list and a nil error are returned.
GetCalls(ctx context.Context, filter *CallFilter) (*CallList, error)

View File

@@ -1,249 +0,0 @@
package models
import (
"net/http"
"net/url"
"path"
"strings"
"time"
"github.com/fnproject/fn/api/common"
)
const (
DefaultTimeout = 30 // seconds
DefaultIdleTimeout = 30 // seconds
DefaultMemory = 128 // MB
MaxSyncTimeout = 120 // 2 minutes
MaxAsyncTimeout = 3600 // 1 hour
)
var RouteMaxMemory = uint64(8 * 1024)
type Routes []*Route
type Route struct {
AppID string `json:"app_id" db:"app_id"`
Path string `json:"path" db:"path"`
Image string `json:"image" db:"image"`
Memory uint64 `json:"memory" db:"memory"`
CPUs MilliCPUs `json:"cpus" db:"cpus"`
Headers Headers `json:"headers,omitempty" 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"`
TmpFsSize uint32 `json:"tmpfs_size" db:"tmpfs_size"`
Config Config `json:"config,omitempty" db:"config"`
Annotations Annotations `json:"annotations,omitempty" db:"annotations"`
CreatedAt common.DateTime `json:"created_at,omitempty" db:"created_at"`
UpdatedAt common.DateTime `json:"updated_at,omitempty" db:"updated_at"`
}
// 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 {
// keeps the json from being 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.AppID == "" {
return ErrRoutesMissingAppID
}
if r.Path == "" {
return ErrRoutesMissingPath
}
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 && r.Format != FormatJSON && r.Format != FormatCloudEvent {
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.Memory < 1 || r.Memory > RouteMaxMemory {
return ErrRoutesInvalidMemory
}
err = r.Annotations.Validate()
if err != nil {
return err
}
return nil
}
func (r *Route) Clone() *Route {
clone := new(Route)
*clone = *r // shallow copy
// now deep copy the maps
if r.Config != nil {
clone.Config = make(Config, len(r.Config))
for k, v := range r.Config {
clone.Config[k] = v
}
}
if r.Headers != nil {
clone.Headers = make(Headers, len(r.Headers))
for k, v := range r.Headers {
// TODO technically, we need to deep copy this slice...
clone.Headers[k] = v
}
}
return clone
}
func (r1 *Route) Equals(r2 *Route) bool {
// start off equal, check equivalence of each field.
// the RHS of && won't eval if eq==false so config/headers checking is lazy
eq := true
eq = eq && r1.AppID == r2.AppID
eq = eq && r1.Path == r2.Path
eq = eq && r1.Image == r2.Image
eq = eq && r1.Memory == r2.Memory
eq = eq && r1.CPUs == r2.CPUs
eq = eq && r1.Headers.Equals(r2.Headers)
eq = eq && r1.Type == r2.Type
eq = eq && r1.Format == r2.Format
eq = eq && r1.Timeout == r2.Timeout
eq = eq && r1.IdleTimeout == r2.IdleTimeout
eq = eq && r1.TmpFsSize == r2.TmpFsSize
eq = eq && r1.Config.Equals(r2.Config)
eq = eq && r1.Annotations.Equals(r2.Annotations)
// NOTE: datastore tests are not very fun to write with timestamp checks,
// and these are not values the user may set so we kind of don't care.
//eq = eq && time.Time(r1.CreatedAt).Equal(time.Time(r2.CreatedAt))
//eq = eq && time.Time(r2.UpdatedAt).Equal(time.Time(r2.UpdatedAt))
return eq
}
// Update updates fields in r with non-zero field values from new, and sets
// updated_at if any of the fields change. 0-length slice Header values, and
// empty-string Config values trigger removal of map entry.
func (r *Route) Update(patch *Route) {
original := r.Clone()
if patch.Image != "" {
r.Image = patch.Image
}
if patch.Memory != 0 {
r.Memory = patch.Memory
}
if patch.CPUs != 0 {
r.CPUs = patch.CPUs
}
if patch.Type != "" {
r.Type = patch.Type
}
if patch.Timeout != 0 {
r.Timeout = patch.Timeout
}
if patch.IdleTimeout != 0 {
r.IdleTimeout = patch.IdleTimeout
}
if patch.TmpFsSize != 0 {
r.TmpFsSize = patch.TmpFsSize
}
if patch.Format != "" {
r.Format = patch.Format
}
if patch.Headers != nil {
if r.Headers == nil {
r.Headers = Headers(make(http.Header))
}
for k, v := range patch.Headers {
if len(v) == 0 {
http.Header(r.Headers).Del(k)
} else {
r.Headers[k] = v
}
}
}
if patch.Config != nil {
if r.Config == nil {
r.Config = make(Config)
}
for k, v := range patch.Config {
if v == "" {
delete(r.Config, k)
} else {
r.Config[k] = v
}
}
}
r.Annotations = r.Annotations.MergeChange(patch.Annotations)
if !r.Equals(original) {
r.UpdatedAt = common.DateTime(time.Now())
}
}
type RouteFilter struct {
PathPrefix string // this is prefix match TODO
AppID string // this is exact match (important for security)
Image string // this is exact match
Cursor string
PerPage int
}

View File

@@ -1,44 +0,0 @@
package models
import (
"github.com/fnproject/fn/api/id"
"testing"
)
func TestRouteSimple(t *testing.T) {
route1 := &Route{
AppID: id.New().String(),
Path: "/some",
Image: "foo",
Memory: 128,
CPUs: 100,
Type: "sync",
Format: "http",
Timeout: 10,
IdleTimeout: 10,
TmpFsSize: 10,
}
err := route1.Validate()
if err != nil {
t.Fatal("should not have failed, got: ", err)
}
route2 := &Route{
AppID: id.New().String(),
Path: "/some",
Image: "foo",
Memory: 128,
CPUs: 100,
Type: "sync",
Format: "nonsense",
Timeout: 10,
IdleTimeout: 10,
}
err = route2.Validate()
if err == nil {
t.Fatalf("should have failed route: %#v", route2)
}
}

View File

@@ -1,12 +0,0 @@
package models
type RouteWrapper struct {
Route *Route `json:"route"`
}
func (m *RouteWrapper) Validate() error {
if m.Route != nil {
return m.Route.Validate()
}
return nil
}