mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
App ID (#641)
* App ID * Clean-up * Use ID or name to reference apps * Can use app by name or ID * Get rid of AppName for routes API and model routes API is completely backwards-compatible routes API accepts both app ID and name * Get rid of AppName from calls API and model * Fixing tests * Get rid of AppName from logs API and model * Restrict API to work with app names only * Addressing review comments * Fix for hybrid mode * Fix rebase problems * Addressing review comments * Addressing review comments pt.2 * Fixing test issue * Addressing review comments pt.3 * Updated docstring * Adjust UpdateApp SQL implementation to work with app IDs instead of names * Fixing tests * fmt after rebase * Make tests green again! * Use GetAppByID wherever it is necessary - adding new v2 endpoints to keep hybrid api/runner mode working - extract CallBase from Call object to expose that to a user (it doesn't include any app reference, as we do for all other API objects) * Get rid of GetAppByName * Adjusting server router setup * Make hybrid work again * Fix datastore tests * Fixing tests * Do not ignore app_id * Resolve issues after rebase * Updating test to make it work as it was * Tabula rasa for migrations * Adding calls API test - we need to ensure we give "App not found" for the missing app and missing call in first place - making previous test work (request missing call for the existing app) * Make datastore tests work fine with correctly applied migrations * Make CallFunction middleware work again had to adjust its implementation to set app ID before proceeding * The biggest rebase ever made * Fix 8's migration * Fix tests * Fix hybrid client * Fix tests problem * Increment app ID migration version * Fixing TestAppUpdate * Fix rebase issues * Addressing review comments * Renew vendor * Updated swagger doc per recommendations
This commit is contained in:
committed by
Reed Allman
parent
4e90844a67
commit
3c15ca6ea6
@@ -10,8 +10,7 @@ import (
|
||||
func (s *Server) handleAppDelete(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
appName := c.MustGet(api.AppName).(string)
|
||||
err := s.datastore.RemoveApp(ctx, appName)
|
||||
err := s.datastore.RemoveApp(ctx, c.MustGet(api.AppID).(string))
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
|
||||
@@ -7,12 +7,22 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (s *Server) handleAppGet(c *gin.Context) {
|
||||
func (s *Server) handleAppGetByName(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
appName := c.MustGet(api.AppName).(string)
|
||||
|
||||
app, err := s.datastore.GetApp(ctx, appName)
|
||||
app, err := s.datastore.GetAppByID(ctx, c.MustGet(api.AppID).(string))
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, appResponse{"Successfully loaded app", app})
|
||||
}
|
||||
|
||||
func (s *Server) handleAppGetByID(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
app, err := s.datastore.GetAppByID(ctx, c.Param(api.CApp))
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
|
||||
@@ -112,6 +112,13 @@ func TestAppDelete(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
app := &models.App{
|
||||
Name: "myapp",
|
||||
}
|
||||
app.SetDefaults()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{app}, nil, nil,
|
||||
)
|
||||
for i, test := range []struct {
|
||||
ds models.Datastore
|
||||
logDB models.LogStore
|
||||
@@ -121,11 +128,7 @@ func TestAppDelete(t *testing.T) {
|
||||
expectedError error
|
||||
}{
|
||||
{datastore.NewMock(), logs.NewMock(), "/v1/apps/myapp", "", http.StatusNotFound, nil},
|
||||
{datastore.NewMockInit(
|
||||
[]*models.App{{
|
||||
Name: "myapp",
|
||||
}}, nil, nil,
|
||||
), logs.NewMock(), "/v1/apps/myapp", "", http.StatusOK, nil},
|
||||
{ds, logs.NewMock(), "/v1/apps/myapp", "", http.StatusOK, nil},
|
||||
} {
|
||||
rnr, cancel := testRunner(t)
|
||||
srv := testServer(test.ds, &mqs.Mock{}, test.logDB, rnr, ServerTypeFull)
|
||||
@@ -270,6 +273,14 @@ func TestAppUpdate(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
app := &models.App{
|
||||
Name: "myapp",
|
||||
}
|
||||
app.SetDefaults()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{app}, nil, nil,
|
||||
)
|
||||
|
||||
for i, test := range []struct {
|
||||
mock models.Datastore
|
||||
logDB models.LogStore
|
||||
@@ -279,35 +290,19 @@ func TestAppUpdate(t *testing.T) {
|
||||
expectedError error
|
||||
}{
|
||||
// errors
|
||||
{datastore.NewMock(), logs.NewMock(), "/v1/apps/myapp", ``, http.StatusBadRequest, models.ErrInvalidJSON},
|
||||
{ds, logs.NewMock(), "/v1/apps/myapp", ``, http.StatusBadRequest, models.ErrInvalidJSON},
|
||||
|
||||
// Addresses #380
|
||||
{datastore.NewMockInit(
|
||||
[]*models.App{{
|
||||
Name: "myapp",
|
||||
}}, nil, nil,
|
||||
), logs.NewMock(), "/v1/apps/myapp", `{ "app": { "name": "othername" } }`, http.StatusConflict, nil},
|
||||
{ds, logs.NewMock(), "/v1/apps/myapp", `{ "app": { "name": "othername" } }`, http.StatusConflict, nil},
|
||||
|
||||
// success: add/set MD key
|
||||
{datastore.NewMockInit(
|
||||
[]*models.App{{
|
||||
Name: "myapp",
|
||||
}}, nil, nil,
|
||||
), logs.NewMock(), "/v1/apps/myapp", `{ "app": { "annotations": {"k-0" : "val"} } }`, http.StatusOK, nil},
|
||||
{ds, logs.NewMock(), "/v1/apps/myapp", `{ "app": { "annotations": {"k-0" : "val"} } }`, http.StatusOK, nil},
|
||||
|
||||
// success
|
||||
{datastore.NewMockInit(
|
||||
[]*models.App{{
|
||||
Name: "myapp",
|
||||
}}, nil, nil,
|
||||
), logs.NewMock(), "/v1/apps/myapp", `{ "app": { "config": { "test": "1" } } }`, http.StatusOK, nil},
|
||||
{ds, logs.NewMock(), "/v1/apps/myapp", `{ "app": { "config": { "test": "1" } } }`, http.StatusOK, nil},
|
||||
|
||||
// success
|
||||
{datastore.NewMockInit(
|
||||
[]*models.App{{
|
||||
Name: "myapp",
|
||||
}}, nil, nil,
|
||||
), logs.NewMock(), "/v1/apps/myapp", `{ "app": { "config": { "test": "1" } } }`, http.StatusOK, nil},
|
||||
{ds, logs.NewMock(), "/v1/apps/myapp", `{ "app": { "config": { "test": "1" } } }`, http.StatusOK, nil},
|
||||
} {
|
||||
rnr, cancel := testRunner(t)
|
||||
srv := testServer(test.mock, &mqs.Mock{}, test.logDB, rnr, ServerTypeFull)
|
||||
|
||||
@@ -33,7 +33,8 @@ func (s *Server) handleAppUpdate(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
wapp.App.Name = c.MustGet(api.AppName).(string)
|
||||
wapp.App.Name = c.MustGet(api.App).(string)
|
||||
wapp.App.ID = c.MustGet(api.AppID).(string)
|
||||
|
||||
app, err := s.datastore.UpdateApp(ctx, wapp.App)
|
||||
if err != nil {
|
||||
|
||||
@@ -10,9 +10,10 @@ import (
|
||||
func (s *Server) handleCallGet(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
appName := c.MustGet(api.AppName).(string)
|
||||
callID := c.Param(api.Call)
|
||||
callObj, err := s.datastore.GetCall(ctx, appName, callID)
|
||||
appID := c.MustGet(api.AppID).(string)
|
||||
|
||||
callObj, err := s.datastore.GetCall(ctx, appID, callID)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
|
||||
@@ -13,14 +13,13 @@ import (
|
||||
|
||||
func (s *Server) handleCallList(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var err error
|
||||
|
||||
appName := c.MustGet(api.AppName).(string)
|
||||
|
||||
appID := c.MustGet(api.AppID).(string)
|
||||
// TODO api.CRoute needs to be escaped probably, since it has '/' a lot
|
||||
filter := models.CallFilter{AppName: appName, Path: c.Query("path")}
|
||||
filter := models.CallFilter{AppID: appID, Path: c.Query("path")}
|
||||
filter.Cursor, filter.PerPage = pageParams(c, false) // ids are url safe
|
||||
|
||||
var err error
|
||||
filter.FromTime, filter.ToTime, err = timeParams(c)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
@@ -29,16 +28,6 @@ func (s *Server) handleCallList(c *gin.Context) {
|
||||
|
||||
calls, err := s.datastore.GetCalls(ctx, &filter)
|
||||
|
||||
if len(calls) == 0 {
|
||||
// TODO this should be done in front of this handler to even get here...
|
||||
_, err = s.datastore.GetApp(c, appName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
var nextCursor string
|
||||
if len(calls) > 0 && len(calls) == filter.PerPage {
|
||||
nextCursor = calls[len(calls)-1].ID
|
||||
|
||||
@@ -15,28 +15,32 @@ import (
|
||||
|
||||
// note: for backward compatibility, will go away later
|
||||
type callLogResponse struct {
|
||||
Message string `json:"message"`
|
||||
Log *models.CallLog `json:"log"`
|
||||
Message string `json:"message"`
|
||||
Log *CallLog `json:"log"`
|
||||
}
|
||||
|
||||
func writeJSON(c *gin.Context, callID, appName string, logReader io.Reader) {
|
||||
type CallLog struct {
|
||||
CallID string `json:"call_id" db:"id"`
|
||||
Log string `json:"log" db:"log"`
|
||||
}
|
||||
|
||||
func writeJSON(c *gin.Context, callID string, logReader io.Reader) {
|
||||
var b bytes.Buffer
|
||||
b.ReadFrom(logReader)
|
||||
c.JSON(http.StatusOK, callLogResponse{"Successfully loaded log",
|
||||
&models.CallLog{
|
||||
CallID: callID,
|
||||
AppName: appName,
|
||||
Log: b.String(),
|
||||
&CallLog{
|
||||
CallID: callID,
|
||||
Log: b.String(),
|
||||
}})
|
||||
}
|
||||
|
||||
func (s *Server) handleCallLogGet(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
appName := c.MustGet(api.AppName).(string)
|
||||
appID := c.MustGet(api.AppID).(string)
|
||||
callID := c.Param(api.Call)
|
||||
|
||||
logReader, err := s.logstore.GetLog(ctx, appName, callID)
|
||||
logReader, err := s.logstore.GetLog(ctx, appID, callID)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
@@ -45,13 +49,13 @@ func (s *Server) handleCallLogGet(c *gin.Context) {
|
||||
mimeTypes, _ := c.Request.Header["Accept"]
|
||||
|
||||
if len(mimeTypes) == 0 {
|
||||
writeJSON(c, callID, appName, logReader)
|
||||
writeJSON(c, callID, logReader)
|
||||
return
|
||||
}
|
||||
|
||||
for _, mimeType := range mimeTypes {
|
||||
if strings.Contains(mimeType, "application/json") {
|
||||
writeJSON(c, callID, appName, logReader)
|
||||
writeJSON(c, callID, logReader)
|
||||
return
|
||||
}
|
||||
if strings.Contains(mimeType, "text/plain") {
|
||||
@@ -60,7 +64,7 @@ func (s *Server) handleCallLogGet(c *gin.Context) {
|
||||
|
||||
}
|
||||
if strings.Contains(mimeType, "*/*") {
|
||||
writeJSON(c, callID, appName, logReader)
|
||||
writeJSON(c, callID, logReader)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,18 +19,30 @@ import (
|
||||
func TestCallGet(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
|
||||
app := &models.App{Name: "myapp"}
|
||||
app.SetDefaults()
|
||||
call := &models.Call{
|
||||
ID: id.New().String(),
|
||||
AppName: "myapp",
|
||||
Path: "/thisisatest",
|
||||
AppID: app.ID,
|
||||
ID: id.New().String(),
|
||||
Path: "/thisisatest",
|
||||
Image: "fnproject/hello",
|
||||
// Delay: 0,
|
||||
Type: "sync",
|
||||
Format: "default",
|
||||
// Payload: TODO,
|
||||
Priority: new(int32), // TODO this is crucial, apparently
|
||||
Timeout: 30,
|
||||
IdleTimeout: 30,
|
||||
Memory: 256,
|
||||
CreatedAt: strfmt.DateTime(time.Now()),
|
||||
URL: "http://localhost:8080/r/myapp/thisisatest",
|
||||
Method: "GET",
|
||||
}
|
||||
|
||||
rnr, cancel := testRunner(t)
|
||||
defer cancel()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{
|
||||
{Name: call.AppName},
|
||||
},
|
||||
[]*models.App{app},
|
||||
nil,
|
||||
[]*models.Call{call},
|
||||
)
|
||||
@@ -44,7 +56,8 @@ func TestCallGet(t *testing.T) {
|
||||
expectedError error
|
||||
}{
|
||||
{"/v1/apps//calls/" + call.ID, "", http.StatusBadRequest, models.ErrAppsMissingName},
|
||||
{"/v1/apps/nodawg/calls/" + call.ID, "", http.StatusNotFound, models.ErrCallNotFound}, // TODO a little weird
|
||||
{"/v1/apps/nodawg/calls/" + call.ID, "", http.StatusNotFound, models.ErrAppsNotFound},
|
||||
{"/v1/apps/myapp/calls/" + id.New().String(), "", http.StatusNotFound, models.ErrCallNotFound},
|
||||
{"/v1/apps/myapp/calls/" + call.ID[:3], "", http.StatusNotFound, models.ErrCallNotFound},
|
||||
{"/v1/apps/myapp/calls/" + call.ID, "", http.StatusOK, nil},
|
||||
} {
|
||||
@@ -52,6 +65,7 @@ func TestCallGet(t *testing.T) {
|
||||
|
||||
if rec.Code != test.expectedCode {
|
||||
t.Log(buf.String())
|
||||
t.Log(rec.Body.String())
|
||||
t.Errorf("Test %d: Expected status code to be %d but was %d",
|
||||
i, test.expectedCode, rec.Code)
|
||||
}
|
||||
@@ -61,6 +75,8 @@ func TestCallGet(t *testing.T) {
|
||||
|
||||
if !strings.Contains(resp.Error.Message, test.expectedError.Error()) {
|
||||
t.Log(buf.String())
|
||||
t.Log(resp.Error.Message)
|
||||
t.Log(rec.Body.String())
|
||||
t.Errorf("Test %d: Expected error message to have `%s`",
|
||||
i, test.expectedError.Error())
|
||||
}
|
||||
@@ -72,10 +88,25 @@ func TestCallGet(t *testing.T) {
|
||||
func TestCallList(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
|
||||
app := &models.App{Name: "myapp"}
|
||||
app.SetDefaults()
|
||||
|
||||
call := &models.Call{
|
||||
ID: id.New().String(),
|
||||
AppName: "myapp",
|
||||
Path: "/thisisatest",
|
||||
AppID: app.ID,
|
||||
ID: id.New().String(),
|
||||
Path: "/thisisatest",
|
||||
Image: "fnproject/hello",
|
||||
// Delay: 0,
|
||||
Type: "sync",
|
||||
Format: "default",
|
||||
// Payload: TODO,
|
||||
Priority: new(int32), // TODO this is crucial, apparently
|
||||
Timeout: 30,
|
||||
IdleTimeout: 30,
|
||||
Memory: 256,
|
||||
CreatedAt: strfmt.DateTime(time.Now()),
|
||||
URL: "http://localhost:8080/r/myapp/thisisatest",
|
||||
Method: "GET",
|
||||
}
|
||||
c2 := *call
|
||||
c3 := *call
|
||||
@@ -89,9 +120,7 @@ func TestCallList(t *testing.T) {
|
||||
rnr, cancel := testRunner(t)
|
||||
defer cancel()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{
|
||||
{Name: call.AppName},
|
||||
},
|
||||
[]*models.App{app},
|
||||
nil,
|
||||
[]*models.Call{call, &c2, &c3},
|
||||
)
|
||||
|
||||
@@ -19,8 +19,8 @@ func (s *Server) apiHandlerWrapperFunc(apiHandler fnext.ApiHandler) gin.HandlerF
|
||||
func (s *Server) apiAppHandlerWrapperFunc(apiHandler fnext.ApiAppHandler) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// get the app
|
||||
appName := c.Param(api.CApp)
|
||||
app, err := s.datastore.GetApp(c.Request.Context(), appName)
|
||||
appID := c.MustGet(api.AppID).(string)
|
||||
app, err := s.datastore.GetAppByID(c.Request.Context(), appID)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
c.Abort()
|
||||
@@ -39,22 +39,9 @@ func (s *Server) apiAppHandlerWrapperFunc(apiHandler fnext.ApiAppHandler) gin.Ha
|
||||
func (s *Server) apiRouteHandlerWrapperFunc(apiHandler fnext.ApiRouteHandler) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
context := c.Request.Context()
|
||||
// get the app
|
||||
appName := c.Param(api.CApp)
|
||||
app, err := s.datastore.GetApp(context, appName)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if app == nil {
|
||||
handleErrorResponse(c, models.ErrAppsNotFound)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// get the route TODO
|
||||
appID := c.MustGet(api.AppID).(string)
|
||||
routePath := "/" + c.Param(api.CRoute)
|
||||
route, err := s.datastore.GetRoute(context, appName, routePath)
|
||||
route, err := s.datastore.GetRoute(context, appID, routePath)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
c.Abort()
|
||||
@@ -66,6 +53,18 @@ func (s *Server) apiRouteHandlerWrapperFunc(apiHandler fnext.ApiRouteHandler) gi
|
||||
return
|
||||
}
|
||||
|
||||
app, err := s.datastore.GetAppByID(context, appID)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if app == nil {
|
||||
handleErrorResponse(c, models.ErrAppsNotFound)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
apiHandler.ServeHTTP(c.Writer, c.Request, app, route)
|
||||
}
|
||||
}
|
||||
@@ -85,6 +84,7 @@ func (s *Server) AddEndpointFunc(method, path string, handler func(w http.Respon
|
||||
// AddAppEndpoint adds an endpoints to /v1/apps/:app/x
|
||||
func (s *Server) AddAppEndpoint(method, path string, handler fnext.ApiAppHandler) {
|
||||
v1 := s.Router.Group("/v1")
|
||||
v1.Use(s.checkAppPresenceByName())
|
||||
v1.Handle(method, "/apps/:app"+path, s.apiAppHandlerWrapperFunc(handler))
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ func (s *Server) AddAppEndpointFunc(method, path string, handler func(w http.Res
|
||||
// AddRouteEndpoint adds an endpoints to /v1/apps/:app/routes/:route/x
|
||||
func (s *Server) AddRouteEndpoint(method, path string, handler fnext.ApiRouteHandler) {
|
||||
v1 := s.Router.Group("/v1")
|
||||
v1.Use(s.checkAppPresenceByName())
|
||||
v1.Handle(method, "/apps/:app/routes/:route"+path, s.apiRouteHandlerWrapperFunc(handler)) // conflicts with existing wildcard
|
||||
}
|
||||
|
||||
|
||||
@@ -81,8 +81,8 @@ func loggerWrap(c *gin.Context) {
|
||||
ctx, _ := common.LoggerWithFields(c.Request.Context(), extractFields(c))
|
||||
|
||||
if appName := c.Param(api.CApp); appName != "" {
|
||||
c.Set(api.AppName, appName)
|
||||
ctx = context.WithValue(ctx, api.AppName, appName)
|
||||
c.Set(api.App, appName)
|
||||
ctx = context.WithValue(ctx, api.App, appName)
|
||||
}
|
||||
|
||||
if routePath := c.Param(api.CRoute); routePath != "" {
|
||||
@@ -94,9 +94,49 @@ func loggerWrap(c *gin.Context) {
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func (s *Server) checkAppPresenceByNameAtRunner() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx, _ := common.LoggerWithFields(c.Request.Context(), extractFields(c))
|
||||
|
||||
appName := c.Param(api.CApp)
|
||||
if appName != "" {
|
||||
appID, err := s.agent.GetAppID(ctx, appName)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Set(api.AppID, appID)
|
||||
}
|
||||
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) checkAppPresenceByName() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx, _ := common.LoggerWithFields(c.Request.Context(), extractFields(c))
|
||||
|
||||
appName := c.MustGet(api.App).(string)
|
||||
if appName != "" {
|
||||
appID, err := s.datastore.GetAppID(ctx, appName)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Set(api.AppID, appID)
|
||||
}
|
||||
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func setAppNameInCtx(c *gin.Context) {
|
||||
// add appName to context
|
||||
appName := c.GetString(api.AppName)
|
||||
appName := c.GetString(api.App)
|
||||
if appName != "" {
|
||||
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), fnext.AppNameKey, appName))
|
||||
}
|
||||
@@ -104,7 +144,7 @@ func setAppNameInCtx(c *gin.Context) {
|
||||
}
|
||||
|
||||
func appNameCheck(c *gin.Context) {
|
||||
appName := c.GetString(api.AppName)
|
||||
appName := c.GetString(api.App)
|
||||
if appName == "" {
|
||||
handleErrorResponse(c, models.ErrAppsMissingName)
|
||||
c.Abort()
|
||||
|
||||
@@ -170,7 +170,7 @@ func (s *Server) handleRunnerFinish(c *gin.Context) {
|
||||
// note: Not returning err here since the job could have already finished successfully.
|
||||
}
|
||||
|
||||
if err := s.logstore.InsertLog(ctx, call.AppName, call.ID, strings.NewReader(body.Log)); err != nil {
|
||||
if err := s.logstore.InsertLog(ctx, call.AppID, call.ID, strings.NewReader(body.Log)); err != nil {
|
||||
common.Logger(ctx).WithError(err).Error("error uploading log")
|
||||
// note: Not returning err here since the job could have already finished successfully.
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/fnproject/fn/api"
|
||||
"github.com/fnproject/fn/api/common"
|
||||
"github.com/fnproject/fn/fnext"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -24,9 +25,24 @@ type middlewareController struct {
|
||||
func (c *middlewareController) CallFunction(w http.ResponseWriter, r *http.Request) {
|
||||
c.functionCalled = true
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, fnext.MiddlewareControllerKey, c)
|
||||
r = r.WithContext(ctx)
|
||||
c.ginContext.Request = r
|
||||
|
||||
// since we added middleware that checks the app ID
|
||||
// we need to ensure that we set it as soon as possible
|
||||
appName := ctx.Value(api.CApp).(string)
|
||||
if appName != "" {
|
||||
appID, err := c.server.datastore.GetAppID(ctx, appName)
|
||||
if err != nil {
|
||||
handleErrorResponse(c.ginContext, err)
|
||||
c.ginContext.Abort()
|
||||
return
|
||||
}
|
||||
c.ginContext.Set(api.AppID, appID)
|
||||
}
|
||||
|
||||
c.server.handleFunctionCall(c.ginContext)
|
||||
c.ginContext.Abort()
|
||||
}
|
||||
|
||||
@@ -67,15 +67,16 @@ func TestMiddlewareChaining(t *testing.T) {
|
||||
|
||||
func TestRootMiddleware(t *testing.T) {
|
||||
|
||||
app1 := &models.App{Name: "myapp", Config: models.Config{}}
|
||||
app1.SetDefaults()
|
||||
app2 := &models.App{Name: "myapp2", Config: models.Config{}}
|
||||
app2.SetDefaults()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{
|
||||
{Name: "myapp", Config: models.Config{}},
|
||||
{Name: "myapp2", Config: models.Config{}},
|
||||
},
|
||||
[]*models.App{app1, app2},
|
||||
[]*models.Route{
|
||||
{Path: "/", AppName: "myapp", Image: "fnproject/fn-test-utils", Type: "sync", Memory: 128, CPUs: 100, Timeout: 30, IdleTimeout: 30, Headers: map[string][]string{"X-Function": {"Test"}}},
|
||||
{Path: "/myroute", AppName: "myapp", Image: "fnproject/fn-test-utils", Type: "sync", Memory: 128, Timeout: 30, IdleTimeout: 30, Headers: map[string][]string{"X-Function": {"Test"}}},
|
||||
{Path: "/app2func", AppName: "myapp2", Image: "fnproject/fn-test-utils", Type: "sync", Memory: 128, Timeout: 30, IdleTimeout: 30, Headers: map[string][]string{"X-Function": {"Test"}},
|
||||
{Path: "/", AppID: app1.ID, Image: "fnproject/hello", Type: "sync", Memory: 128, Timeout: 30, IdleTimeout: 30, Headers: map[string][]string{"X-Function": {"Test"}}},
|
||||
{Path: "/myroute", AppID: app1.ID, Image: "fnproject/hello", Type: "sync", Memory: 128, Timeout: 30, IdleTimeout: 30, Headers: map[string][]string{"X-Function": {"Test"}}},
|
||||
{Path: "/app2func", AppID: app2.ID, Image: "fnproject/hello", Type: "sync", Memory: 128, Timeout: 30, IdleTimeout: 30, Headers: map[string][]string{"X-Function": {"Test"}},
|
||||
Config: map[string]string{"NAME": "johnny"},
|
||||
},
|
||||
}, nil,
|
||||
@@ -93,7 +94,7 @@ func TestRootMiddleware(t *testing.T) {
|
||||
fmt.Fprintf(os.Stderr, "breaker breaker!\n")
|
||||
ctx := r.Context()
|
||||
// TODO: this is a little dicey, should have some functions to set these in case the context keys change or something.
|
||||
ctx = context.WithValue(ctx, "app_name", "myapp2")
|
||||
ctx = context.WithValue(ctx, "app", "myapp2")
|
||||
ctx = context.WithValue(ctx, "path", "/app2func")
|
||||
mctx := fnext.GetMiddlewareController(ctx)
|
||||
mctx.CallFunction(w, r.WithContext(ctx))
|
||||
@@ -105,7 +106,7 @@ func TestRootMiddleware(t *testing.T) {
|
||||
})
|
||||
srv.AddRootMiddlewareFunc(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// fmt.Fprintf(os.Stderr, "middle log\n")
|
||||
fmt.Fprintf(os.Stderr, "middle log\n")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
@@ -141,7 +142,7 @@ func TestRootMiddleware(t *testing.T) {
|
||||
}
|
||||
|
||||
rbody := string(result)
|
||||
t.Log("rbody:", rbody)
|
||||
t.Logf("Test %v: response body: %v", i, rbody)
|
||||
if !strings.Contains(rbody, test.expectedInBody) {
|
||||
t.Fatal(i, "middleware didn't work correctly", string(result))
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ import (
|
||||
update only
|
||||
Patch accepts partial updates / skips validation of zero values.
|
||||
*/
|
||||
func (s *Server) handleRoutesPostPutPatch(c *gin.Context) {
|
||||
|
||||
func (s *Server) handleRoutesPostPut(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
method := strings.ToUpper(c.Request.Method)
|
||||
|
||||
@@ -32,14 +33,36 @@ func (s *Server) handleRoutesPostPutPatch(c *gin.Context) {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
}
|
||||
if method != http.MethodPatch {
|
||||
err = s.ensureApp(ctx, &wroute, method)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
}
|
||||
appName := c.MustGet(api.App).(string)
|
||||
|
||||
appID, err := s.ensureApp(ctx, appName, method)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
}
|
||||
resp, err := s.ensureRoute(ctx, method, &wroute)
|
||||
|
||||
resp, err := s.ensureRoute(ctx, appID, &wroute, method)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func (s *Server) handleRoutesPatch(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
method := strings.ToUpper(c.Request.Method)
|
||||
|
||||
var wroute models.RouteWrapper
|
||||
err := bindRoute(c, method, &wroute)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
}
|
||||
appID := c.MustGet(api.AppID).(string)
|
||||
|
||||
resp, err := s.ensureRoute(ctx, appID, &wroute, method)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
@@ -66,10 +89,11 @@ func (s *Server) changeRoute(ctx context.Context, wroute *models.RouteWrapper) e
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureApp will only execute if it is on put
|
||||
func (s *Server) ensureRoute(ctx context.Context, method string, wroute *models.RouteWrapper) (routeResponse, error) {
|
||||
func (s *Server) ensureRoute(ctx context.Context, appID string, wroute *models.RouteWrapper, method string) (routeResponse, error) {
|
||||
bad := new(routeResponse)
|
||||
|
||||
wroute.Route.AppID = appID
|
||||
|
||||
switch method {
|
||||
case http.MethodPost:
|
||||
err := s.submitRoute(ctx, wroute)
|
||||
@@ -78,7 +102,7 @@ func (s *Server) ensureRoute(ctx context.Context, method string, wroute *models.
|
||||
}
|
||||
return routeResponse{"Route successfully created", wroute.Route}, nil
|
||||
case http.MethodPut:
|
||||
_, err := s.datastore.GetRoute(ctx, wroute.Route.AppName, wroute.Route.Path)
|
||||
_, err := s.datastore.GetRoute(ctx, appID, wroute.Route.Path)
|
||||
if err != nil && err == models.ErrRoutesNotFound {
|
||||
err := s.submitRoute(ctx, wroute)
|
||||
if err != nil {
|
||||
@@ -102,19 +126,22 @@ func (s *Server) ensureRoute(ctx context.Context, method string, wroute *models.
|
||||
}
|
||||
|
||||
// ensureApp will only execute if it is on post or put. Patch is not allowed to create apps.
|
||||
func (s *Server) ensureApp(ctx context.Context, wroute *models.RouteWrapper, method string) error {
|
||||
app, err := s.datastore.GetApp(ctx, wroute.Route.AppName)
|
||||
func (s *Server) ensureApp(ctx context.Context, appName string, method string) (string, error) {
|
||||
appID, err := s.datastore.GetAppID(ctx, appName)
|
||||
if err != nil && err != models.ErrAppsNotFound {
|
||||
return err
|
||||
} else if app == nil {
|
||||
// Create a new application
|
||||
newapp := &models.App{Name: wroute.Route.AppName}
|
||||
_, err = s.datastore.InsertApp(ctx, newapp)
|
||||
return "", err
|
||||
} else if appID == "" {
|
||||
// Create a new application assuming that /:app/ is an app name
|
||||
newapp := &models.App{Name: appName}
|
||||
|
||||
app, err := s.datastore.InsertApp(ctx, newapp)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
return app.ID, nil
|
||||
}
|
||||
return nil
|
||||
|
||||
return appID, nil
|
||||
}
|
||||
|
||||
// bindRoute binds the RouteWrapper to the json from the request.
|
||||
@@ -130,7 +157,6 @@ func bindRoute(c *gin.Context, method string, wroute *models.RouteWrapper) error
|
||||
if wroute.Route == nil {
|
||||
return models.ErrRoutesMissingNew
|
||||
}
|
||||
wroute.Route.AppName = c.MustGet(api.AppName).(string)
|
||||
|
||||
if method == http.MethodPut || method == http.MethodPatch {
|
||||
p := path.Clean(c.MustGet(api.Path).(string))
|
||||
|
||||
@@ -11,15 +11,15 @@ import (
|
||||
func (s *Server) handleRouteDelete(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
appName := c.MustGet(api.AppName).(string)
|
||||
appID := c.MustGet(api.AppID).(string)
|
||||
routePath := path.Clean(c.MustGet(api.Path).(string))
|
||||
|
||||
if _, err := s.datastore.GetRoute(ctx, appName, routePath); err != nil {
|
||||
if _, err := s.datastore.GetRoute(ctx, appID, routePath); err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.datastore.RemoveRoute(ctx, appName, routePath); err != nil {
|
||||
if err := s.datastore.RemoveRoute(ctx, appID, routePath); err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,12 +8,11 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (s *Server) handleRouteGet(c *gin.Context) {
|
||||
func routeGet(s *Server, appID string, c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
appName := c.MustGet(api.AppName).(string)
|
||||
routePath := path.Clean("/" + c.MustGet(api.Path).(string))
|
||||
route, err := s.datastore.GetRoute(ctx, appName, routePath)
|
||||
route, err := s.datastore.GetRoute(ctx, appID, routePath)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
@@ -21,3 +20,11 @@ func (s *Server) handleRouteGet(c *gin.Context) {
|
||||
|
||||
c.JSON(http.StatusOK, routeResponse{"Successfully loaded route", route})
|
||||
}
|
||||
|
||||
func (s *Server) handleRouteGetAPI(c *gin.Context) {
|
||||
routeGet(s, c.MustGet(api.AppID).(string), c)
|
||||
}
|
||||
|
||||
func (s *Server) handleRouteGetRunner(c *gin.Context) {
|
||||
routeGet(s, c.Param(api.CApp), c)
|
||||
}
|
||||
|
||||
@@ -12,22 +12,12 @@ import (
|
||||
func (s *Server) handleRouteList(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
appName := c.MustGet(api.AppName).(string)
|
||||
|
||||
var filter models.RouteFilter
|
||||
filter.Image = c.Query("image")
|
||||
// filter.PathPrefix = c.Query("path_prefix") TODO not hooked up
|
||||
filter.Cursor, filter.PerPage = pageParams(c, true)
|
||||
|
||||
routes, err := s.datastore.GetRoutesByApp(ctx, appName, &filter)
|
||||
|
||||
// if there are no routes for the app, check if the app exists to return
|
||||
// 404 if it does not
|
||||
// TODO this should be done in front of this handler to even get here...
|
||||
if err == nil && len(routes) == 0 {
|
||||
_, err = s.datastore.GetApp(ctx, appName)
|
||||
}
|
||||
|
||||
routes, err := s.datastore.GetRoutesByApp(ctx, c.MustGet(api.AppID).(string), &filter)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
|
||||
@@ -34,6 +34,7 @@ func (test *routeTestCase) run(t *testing.T, i int, buf *bytes.Buffer) {
|
||||
|
||||
if rec.Code != test.expectedCode {
|
||||
t.Log(buf.String())
|
||||
t.Log(rec.Body.String())
|
||||
t.Errorf("Test %d: Expected status code to be %d but was %d",
|
||||
i, test.expectedCode, rec.Code)
|
||||
}
|
||||
@@ -97,22 +98,25 @@ func (test *routeTestCase) run(t *testing.T, i int, buf *bytes.Buffer) {
|
||||
func TestRouteCreate(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
|
||||
a := &models.App{Name: "a"}
|
||||
a.SetDefaults()
|
||||
commonDS := datastore.NewMockInit([]*models.App{a}, nil, nil)
|
||||
for i, test := range []routeTestCase{
|
||||
// errors
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", ``, http.StatusBadRequest, models.ErrInvalidJSON},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "type": "sync" }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "path": "/myroute", "type": "sync" }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { } }`, http.StatusBadRequest, models.ErrRoutesMissingPath},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "path": "/myroute", "type": "sync" } }`, http.StatusBadRequest, models.ErrRoutesMissingImage},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "image": "fnproject/fn-test-utils", "type": "sync" } }`, http.StatusBadRequest, models.ErrRoutesMissingPath},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "image": "fnproject/fn-test-utils", "path": "myroute", "type": "sync" } }`, http.StatusBadRequest, models.ErrRoutesInvalidPath},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/$/routes", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "type": "sync" } }`, http.StatusBadRequest, models.ErrAppsInvalidName},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "type": "sync", "cpus": "-100" } }`, http.StatusBadRequest, models.ErrInvalidCPUs},
|
||||
{datastore.NewMockInit(nil,
|
||||
{commonDS, logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", ``, http.StatusBadRequest, models.ErrInvalidJSON},
|
||||
{commonDS, logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "type": "sync" }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{commonDS, logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "path": "/myroute", "type": "sync" }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{commonDS, logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { } }`, http.StatusBadRequest, models.ErrRoutesMissingPath},
|
||||
{commonDS, logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "path": "/myroute", "type": "sync" } }`, http.StatusBadRequest, models.ErrRoutesMissingImage},
|
||||
{commonDS, logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "image": "fnproject/fn-test-utils", "type": "sync" } }`, http.StatusBadRequest, models.ErrRoutesMissingPath},
|
||||
{commonDS, logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "image": "fnproject/fn-test-utils", "path": "myroute", "type": "sync" } }`, http.StatusBadRequest, models.ErrRoutesInvalidPath},
|
||||
{commonDS, logs.NewMock(), http.MethodPost, "/v1/apps/$/routes", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "type": "sync" } }`, http.StatusBadRequest, models.ErrAppsInvalidName},
|
||||
{commonDS, logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "type": "sync", "cpus": "-100" } }`, http.StatusBadRequest, models.ErrInvalidCPUs},
|
||||
{datastore.NewMockInit([]*models.App{a},
|
||||
[]*models.Route{
|
||||
{
|
||||
AppName: "a",
|
||||
Path: "/myroute",
|
||||
AppID: a.ID,
|
||||
Path: "/myroute",
|
||||
},
|
||||
}, nil,
|
||||
), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "type": "sync" } }`, http.StatusConflict, models.ErrRoutesAlreadyExists},
|
||||
@@ -129,21 +133,25 @@ func TestRouteCreate(t *testing.T) {
|
||||
func TestRoutePut(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
|
||||
a := &models.App{Name: "a"}
|
||||
a.SetDefaults()
|
||||
commonDS := datastore.NewMockInit([]*models.App{a}, nil, nil)
|
||||
|
||||
for i, test := range []routeTestCase{
|
||||
// errors (NOTE: this route doesn't exist yet)
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "path": "/myroute", "type": "sync" }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "type": "sync" } }`, http.StatusBadRequest, models.ErrRoutesMissingImage},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "path": "/myroute", "type": "sync" } }`, http.StatusBadRequest, models.ErrRoutesMissingImage},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "path": "myroute", "type": "sync" } }`, http.StatusConflict, models.ErrRoutesPathImmutable},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "path": "diffRoute", "type": "sync" } }`, http.StatusConflict, models.ErrRoutesPathImmutable},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/$/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "type": "sync" } }`, http.StatusBadRequest, models.ErrAppsInvalidName},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "type": "invalid-type" } }`, http.StatusBadRequest, models.ErrRoutesInvalidType},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "format": "invalid-format", "type": "sync" } }`, http.StatusBadRequest, models.ErrRoutesInvalidFormat},
|
||||
{commonDS, logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{commonDS, logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "path": "/myroute", "type": "sync" }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{commonDS, logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "type": "sync" } }`, http.StatusBadRequest, models.ErrRoutesMissingImage},
|
||||
{commonDS, logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "path": "/myroute", "type": "sync" } }`, http.StatusBadRequest, models.ErrRoutesMissingImage},
|
||||
{commonDS, logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "path": "myroute", "type": "sync" } }`, http.StatusConflict, models.ErrRoutesPathImmutable},
|
||||
{commonDS, logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "path": "diffRoute", "type": "sync" } }`, http.StatusConflict, models.ErrRoutesPathImmutable},
|
||||
{commonDS, logs.NewMock(), http.MethodPut, "/v1/apps/$/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "type": "sync" } }`, http.StatusBadRequest, models.ErrAppsInvalidName},
|
||||
{commonDS, logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "type": "invalid-type" } }`, http.StatusBadRequest, models.ErrRoutesInvalidType},
|
||||
{commonDS, logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "format": "invalid-format", "type": "sync" } }`, http.StatusBadRequest, models.ErrRoutesInvalidFormat},
|
||||
|
||||
// success
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "type": "sync" } }`, http.StatusOK, nil},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "type": "sync" } }`, http.StatusOK, nil},
|
||||
{commonDS, logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "path": "/myroute", "type": "sync" } }`, http.StatusOK, nil},
|
||||
{commonDS, logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "fnproject/fn-test-utils", "type": "sync" } }`, http.StatusOK, nil},
|
||||
} {
|
||||
test.run(t, i, buf)
|
||||
}
|
||||
@@ -152,8 +160,10 @@ func TestRoutePut(t *testing.T) {
|
||||
func TestRouteDelete(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
|
||||
routes := []*models.Route{{AppName: "a", Path: "/myroute"}}
|
||||
apps := []*models.App{{Name: "a", Config: nil}}
|
||||
a := &models.App{Name: "a"}
|
||||
a.SetDefaults()
|
||||
routes := []*models.Route{{AppID: a.ID, Path: "/myroute"}}
|
||||
commonDS := datastore.NewMockInit([]*models.App{a}, routes, nil)
|
||||
|
||||
for i, test := range []struct {
|
||||
ds models.Datastore
|
||||
@@ -163,8 +173,8 @@ func TestRouteDelete(t *testing.T) {
|
||||
expectedCode int
|
||||
expectedError error
|
||||
}{
|
||||
{datastore.NewMock(), logs.NewMock(), "/v1/apps/a/routes/missing", "", http.StatusNotFound, models.ErrRoutesNotFound},
|
||||
{datastore.NewMockInit(apps, routes, nil), logs.NewMock(), "/v1/apps/a/routes/myroute", "", http.StatusOK, nil},
|
||||
{commonDS, logs.NewMock(), "/v1/apps/a/routes/missing", "", http.StatusNotFound, models.ErrRoutesNotFound},
|
||||
{commonDS, logs.NewMock(), "/v1/apps/a/routes/myroute", "", http.StatusOK, nil},
|
||||
} {
|
||||
rnr, cancel := testRunner(t)
|
||||
srv := testServer(test.ds, &mqs.Mock{}, test.logDB, rnr, ServerTypeFull)
|
||||
@@ -172,6 +182,7 @@ func TestRouteDelete(t *testing.T) {
|
||||
|
||||
if rec.Code != test.expectedCode {
|
||||
t.Log(buf.String())
|
||||
t.Log(rec.Body.String())
|
||||
t.Errorf("Test %d: Expected status code to be %d but was %d",
|
||||
i, test.expectedCode, rec.Code)
|
||||
}
|
||||
@@ -195,23 +206,23 @@ func TestRouteList(t *testing.T) {
|
||||
rnr, cancel := testRunner(t)
|
||||
defer cancel()
|
||||
|
||||
app := &models.App{Name: "myapp"}
|
||||
app.SetDefaults()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{
|
||||
{Name: "myapp"},
|
||||
},
|
||||
[]*models.App{app},
|
||||
[]*models.Route{
|
||||
{
|
||||
AppName: "myapp",
|
||||
Path: "/myroute",
|
||||
Path: "/myroute",
|
||||
AppID: app.ID,
|
||||
},
|
||||
{
|
||||
AppName: "myapp",
|
||||
Path: "/myroute1",
|
||||
Path: "/myroute1",
|
||||
AppID: app.ID,
|
||||
},
|
||||
{
|
||||
AppName: "myapp",
|
||||
Path: "/myroute2",
|
||||
Image: "fnproject/fn-test-utils",
|
||||
Path: "/myroute2",
|
||||
Image: "fnproject/fn-test-utils",
|
||||
AppID: app.ID,
|
||||
},
|
||||
},
|
||||
nil, // no calls
|
||||
|
||||
@@ -39,18 +39,16 @@ func (s *Server) handleFunctionCall2(c *gin.Context) error {
|
||||
p = r.(string)
|
||||
}
|
||||
|
||||
var a string
|
||||
ai := ctx.Value(api.AppName)
|
||||
if ai == nil {
|
||||
err := models.ErrAppsMissingName
|
||||
appID := c.MustGet(api.AppID).(string)
|
||||
app, err := s.agent.GetAppByID(ctx, appID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a = ai.(string)
|
||||
|
||||
// gin sets this to 404 on NoRoute, so we'll just ensure it's 200 by default.
|
||||
c.Status(200) // this doesn't write the header yet
|
||||
|
||||
return s.serve(c, a, path.Clean(p))
|
||||
return s.serve(c, app, path.Clean(p))
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -59,7 +57,7 @@ var (
|
||||
|
||||
// TODO it would be nice if we could make this have nothing to do with the gin.Context but meh
|
||||
// TODO make async store an *http.Request? would be sexy until we have different api format...
|
||||
func (s *Server) serve(c *gin.Context, appName, path string) error {
|
||||
func (s *Server) serve(c *gin.Context, app *models.App, path string) error {
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
writer := syncResponseWriter{
|
||||
@@ -70,14 +68,18 @@ func (s *Server) serve(c *gin.Context, appName, path string) error {
|
||||
|
||||
// GetCall can mod headers, assign an id, look up the route/app (cached),
|
||||
// strip params, etc.
|
||||
// this should happen ASAP to turn app name to app ID
|
||||
|
||||
// GetCall can mod headers, assign an id, look up the route/app (cached),
|
||||
// strip params, etc.
|
||||
|
||||
call, err := s.agent.GetCall(
|
||||
agent.WithWriter(&writer), // XXX (reed): order matters [for now]
|
||||
agent.FromRequest(appName, path, c.Request),
|
||||
agent.FromRequest(app, path, c.Request),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
model := call.Model()
|
||||
{ // scope this, to disallow ctx use outside of this scope. add id for handleErrorResponse logger
|
||||
ctx, _ := common.LoggerWithFields(c.Request.Context(), logrus.Fields{"id": model.ID})
|
||||
|
||||
@@ -35,16 +35,16 @@ func testRouterAsync(ds models.Datastore, mq models.MessageQueue, rnr agent.Agen
|
||||
func TestRouteRunnerAsyncExecution(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
|
||||
app := &models.App{Name: "myapp", Config: map[string]string{"app": "true"}}
|
||||
app.SetDefaults()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{
|
||||
{Name: "myapp", Config: map[string]string{"app": "true"}},
|
||||
},
|
||||
[]*models.App{app},
|
||||
[]*models.Route{
|
||||
{Type: "async", Path: "/hot-http", AppName: "myapp", Image: "fnproject/fn-test-utils", Format: "http", Config: map[string]string{"test": "true"}, Memory: 128, Timeout: 4, IdleTimeout: 30},
|
||||
{Type: "async", Path: "/hot-json", AppName: "myapp", Image: "fnproject/fn-test-utils", Format: "json", Config: map[string]string{"test": "true"}, Memory: 128, Timeout: 4, IdleTimeout: 30},
|
||||
{Type: "async", Path: "/myroute", AppName: "myapp", Image: "fnproject/fn-test-utils", Config: map[string]string{"test": "true"}, Memory: 128, CPUs: 200, Timeout: 30, IdleTimeout: 30},
|
||||
{Type: "async", Path: "/myerror", AppName: "myapp", Image: "fnproject/fn-test-utils", Config: map[string]string{"test": "true"}, Memory: 128, Timeout: 30, IdleTimeout: 30},
|
||||
{Type: "async", Path: "/myroute/:param", AppName: "myapp", Image: "fnproject/fn-test-utils", Config: map[string]string{"test": "true"}, Memory: 128, Timeout: 30, IdleTimeout: 30},
|
||||
{Type: "async", Path: "/hot-http", AppID: app.ID, Image: "fnproject/fn-test-utils", Format: "http", Config: map[string]string{"test": "true"}, Memory: 128, Timeout: 4, IdleTimeout: 30},
|
||||
{Type: "async", Path: "/hot-json", AppID: app.ID, Image: "fnproject/fn-test-utils", Format: "json", Config: map[string]string{"test": "true"}, Memory: 128, Timeout: 4, IdleTimeout: 30},
|
||||
{Type: "async", Path: "/myroute", AppID: app.ID, Image: "fnproject/hello", Config: map[string]string{"test": "true"}, Memory: 128, CPUs: 200, Timeout: 30, IdleTimeout: 30},
|
||||
{Type: "async", Path: "/myerror", AppID: app.ID, Image: "fnproject/error", Config: map[string]string{"test": "true"}, Memory: 128, Timeout: 30, IdleTimeout: 30},
|
||||
{Type: "async", Path: "/myroute/:param", AppID: app.ID, Image: "fnproject/hello", Config: map[string]string{"test": "true"}, Memory: 128, Timeout: 30, IdleTimeout: 30},
|
||||
}, nil,
|
||||
)
|
||||
mq := &mqs.Mock{}
|
||||
|
||||
@@ -41,7 +41,7 @@ func envTweaker(name, value string) func() {
|
||||
}
|
||||
}
|
||||
|
||||
func testRunner(t *testing.T, args ...interface{}) (agent.Agent, context.CancelFunc) {
|
||||
func testRunner(_ *testing.T, args ...interface{}) (agent.Agent, context.CancelFunc) {
|
||||
ds := datastore.NewMock()
|
||||
var mq models.MessageQueue = &mqs.Mock{}
|
||||
for _, a := range args {
|
||||
@@ -58,10 +58,10 @@ func testRunner(t *testing.T, args ...interface{}) (agent.Agent, context.CancelF
|
||||
|
||||
func TestRouteRunnerGet(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
app := &models.App{Name: "myapp", Config: models.Config{}}
|
||||
app.SetDefaults()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{
|
||||
{Name: "myapp", Config: models.Config{}},
|
||||
}, nil, nil,
|
||||
[]*models.App{app}, nil, nil,
|
||||
)
|
||||
|
||||
rnr, cancel := testRunner(t, ds)
|
||||
@@ -102,10 +102,10 @@ func TestRouteRunnerGet(t *testing.T) {
|
||||
func TestRouteRunnerPost(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
|
||||
app := &models.App{Name: "myapp", Config: models.Config{}}
|
||||
app.SetDefaults()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{
|
||||
{Name: "myapp", Config: models.Config{}},
|
||||
}, nil, nil,
|
||||
[]*models.App{app}, nil, nil,
|
||||
)
|
||||
|
||||
rnr, cancel := testRunner(t, ds)
|
||||
@@ -169,13 +169,14 @@ func TestRouteRunnerIOPipes(t *testing.T) {
|
||||
rCfg := map[string]string{"ENABLE_HEADER": "yes", "ENABLE_FOOTER": "yes"} // enable container start/end header/footer
|
||||
rImg := "fnproject/fn-test-utils"
|
||||
|
||||
app := &models.App{Name: "zoo"}
|
||||
app.SetDefaults()
|
||||
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{
|
||||
{Name: "zoo", Config: models.Config{}},
|
||||
},
|
||||
[]*models.App{app},
|
||||
[]*models.Route{
|
||||
{Path: "/json", AppName: "zoo", Image: rImg, Type: "sync", Format: "json", Memory: 64, Timeout: 30, IdleTimeout: 30, Config: rCfg},
|
||||
{Path: "/http", AppName: "zoo", Image: rImg, Type: "sync", Format: "http", Memory: 64, Timeout: 30, IdleTimeout: 30, Config: rCfg},
|
||||
{Path: "/json", AppID: app.ID, Image: rImg, Type: "sync", Format: "json", Memory: 64, Timeout: 30, IdleTimeout: 30, Config: rCfg},
|
||||
{Path: "/http", AppID: app.ID, Image: rImg, Type: "sync", Format: "http", Memory: 64, Timeout: 30, IdleTimeout: 30, Config: rCfg},
|
||||
}, nil,
|
||||
)
|
||||
|
||||
@@ -328,23 +329,23 @@ func TestRouteRunnerExecution(t *testing.T) {
|
||||
rImgBs1 := "fnproject/imagethatdoesnotexist"
|
||||
rImgBs2 := "localhost:5000/fnproject/imagethatdoesnotexist"
|
||||
|
||||
app := &models.App{Name: "myapp"}
|
||||
app.SetDefaults()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{
|
||||
{Name: "myapp", Config: models.Config{}},
|
||||
},
|
||||
[]*models.App{app},
|
||||
[]*models.Route{
|
||||
{Path: "/", AppName: "myapp", Image: rImg, Type: "sync", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/myhot", AppName: "myapp", Image: rImg, Type: "sync", Format: "http", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/myhotjason", AppName: "myapp", Image: rImg, Type: "sync", Format: "json", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/myroute", AppName: "myapp", Image: rImg, Type: "sync", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/myerror", AppName: "myapp", Image: rImg, Type: "sync", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/mydne", AppName: "myapp", Image: rImgBs1, Type: "sync", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/mydnehot", AppName: "myapp", Image: rImgBs1, Type: "sync", Format: "http", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/mydneregistry", AppName: "myapp", Image: rImgBs2, Type: "sync", Format: "http", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/myoom", AppName: "myapp", Image: rImg, Type: "sync", Memory: 8, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/mybigoutputcold", AppName: "myapp", Image: rImg, Type: "sync", Memory: 64, Timeout: 10, IdleTimeout: 20, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/mybigoutputhttp", AppName: "myapp", Image: rImg, Type: "sync", Format: "http", Memory: 64, Timeout: 10, IdleTimeout: 20, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/mybigoutputjson", AppName: "myapp", Image: rImg, Type: "sync", Format: "json", Memory: 64, Timeout: 10, IdleTimeout: 20, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/", AppID: app.ID, Image: rImg, Type: "sync", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/myhot", AppID: app.ID, Image: rImg, Type: "sync", Format: "http", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/myhotjason", AppID: app.ID, Image: rImg, Type: "sync", Format: "json", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/myroute", AppID: app.ID, Image: rImg, Type: "sync", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/myerror", AppID: app.ID, Image: rImg, Type: "sync", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/mydne", AppID: app.ID, Image: rImgBs1, Type: "sync", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/mydnehot", AppID: app.ID, Image: rImgBs1, Type: "sync", Format: "http", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/mydneregistry", AppID: app.ID, Image: rImgBs2, Type: "sync", Format: "http", Memory: 64, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/myoom", AppID: app.ID, Image: rImg, Type: "sync", Memory: 8, Timeout: 30, IdleTimeout: 30, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/mybigoutputcold", AppID: app.ID, Image: rImg, Type: "sync", Memory: 64, Timeout: 10, IdleTimeout: 20, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/mybigoutputhttp", AppID: app.ID, Image: rImg, Type: "sync", Format: "http", Memory: 64, Timeout: 10, IdleTimeout: 20, Headers: rHdr, Config: rCfg},
|
||||
{Path: "/mybigoutputjson", AppID: app.ID, Image: rImg, Type: "sync", Format: "json", Memory: 64, Timeout: 10, IdleTimeout: 20, Headers: rHdr, Config: rCfg},
|
||||
}, nil,
|
||||
)
|
||||
|
||||
@@ -408,9 +409,9 @@ func TestRouteRunnerExecution(t *testing.T) {
|
||||
{"/r/myapp/", multiLog, "GET", http.StatusOK, nil, "", multiLogExpectCold},
|
||||
{"/r/myapp/mybigoutputjson", bigoutput, "GET", http.StatusBadGateway, nil, "function response too large", nil},
|
||||
{"/r/myapp/mybigoutputjson", smalloutput, "GET", http.StatusOK, nil, "", nil},
|
||||
{"/r/myapp/mybigoutputhttp", bigoutput, "GET", http.StatusBadGateway, nil, "function response too large", nil},
|
||||
{"/r/myapp/mybigoutputhttp", bigoutput, "GET", http.StatusBadGateway, nil, "", nil},
|
||||
{"/r/myapp/mybigoutputhttp", smalloutput, "GET", http.StatusOK, nil, "", nil},
|
||||
{"/r/myapp/mybigoutputcold", bigoutput, "GET", http.StatusBadGateway, nil, "function response too large", nil},
|
||||
{"/r/myapp/mybigoutputcold", bigoutput, "GET", http.StatusBadGateway, nil, "", nil},
|
||||
{"/r/myapp/mybigoutputcold", smalloutput, "GET", http.StatusOK, nil, "", nil},
|
||||
} {
|
||||
trx := fmt.Sprintf("_trx_%d_", i)
|
||||
@@ -531,12 +532,12 @@ func (mock *errorMQ) Code() int {
|
||||
|
||||
func TestFailedEnqueue(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
app := &models.App{Name: "myapp", Config: models.Config{}}
|
||||
app.SetDefaults()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{
|
||||
{Name: "myapp", Config: models.Config{}},
|
||||
},
|
||||
[]*models.App{app},
|
||||
[]*models.Route{
|
||||
{Path: "/dummy", AppName: "myapp", Image: "dummy/dummy", Type: "async", Memory: 128, Timeout: 30, IdleTimeout: 30},
|
||||
{Path: "/dummy", Image: "dummy/dummy", Type: "async", Memory: 128, Timeout: 30, IdleTimeout: 30, AppID: app.ID},
|
||||
}, nil,
|
||||
)
|
||||
err := errors.New("Unable to push task to queue")
|
||||
@@ -580,16 +581,16 @@ func TestRouteRunnerTimeout(t *testing.T) {
|
||||
models.RouteMaxMemory = uint64(1024 * 1024 * 1024) // 1024 TB
|
||||
hugeMem := uint64(models.RouteMaxMemory - 1)
|
||||
|
||||
app := &models.App{Name: "myapp", Config: models.Config{}}
|
||||
app.SetDefaults()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{
|
||||
{Name: "myapp", Config: models.Config{}},
|
||||
},
|
||||
[]*models.App{app},
|
||||
[]*models.Route{
|
||||
{Path: "/cold", AppName: "myapp", Image: "fnproject/fn-test-utils", Type: "sync", Memory: 128, Timeout: 4, IdleTimeout: 30},
|
||||
{Path: "/hot", AppName: "myapp", Image: "fnproject/fn-test-utils", Type: "sync", Format: "http", Memory: 128, Timeout: 4, IdleTimeout: 30},
|
||||
{Path: "/hot-json", AppName: "myapp", Image: "fnproject/fn-test-utils", Type: "sync", Format: "json", Memory: 128, Timeout: 4, IdleTimeout: 30},
|
||||
{Path: "/bigmem-cold", AppName: "myapp", Image: "fnproject/fn-test-utils", Type: "sync", Memory: hugeMem, Timeout: 1, IdleTimeout: 30},
|
||||
{Path: "/bigmem-hot", AppName: "myapp", Image: "fnproject/fn-test-utils", Type: "sync", Format: "http", Memory: hugeMem, Timeout: 1, IdleTimeout: 30},
|
||||
{Path: "/cold", Image: "fnproject/fn-test-utils", Type: "sync", Memory: 128, Timeout: 4, IdleTimeout: 30, AppID: app.ID},
|
||||
{Path: "/hot", Image: "fnproject/fn-test-utils", Type: "sync", Format: "http", Memory: 128, Timeout: 4, IdleTimeout: 30, AppID: app.ID},
|
||||
{Path: "/hot-json", Image: "fnproject/fn-test-utils", Type: "sync", Format: "json", Memory: 128, Timeout: 4, IdleTimeout: 30, AppID: app.ID},
|
||||
{Path: "/bigmem-cold", Image: "fnproject/fn-test-utils", Type: "sync", Memory: hugeMem, Timeout: 1, IdleTimeout: 30, AppID: app.ID},
|
||||
{Path: "/bigmem-hot", Image: "fnproject/fn-test-utils", Type: "sync", Format: "http", Memory: hugeMem, Timeout: 1, IdleTimeout: 30, AppID: app.ID},
|
||||
}, nil,
|
||||
)
|
||||
|
||||
@@ -654,12 +655,12 @@ func TestRouteRunnerTimeout(t *testing.T) {
|
||||
func TestRouteRunnerMinimalConcurrentHotSync(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
|
||||
app := &models.App{Name: "myapp", Config: models.Config{}}
|
||||
app.SetDefaults()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{
|
||||
{Name: "myapp", Config: models.Config{}},
|
||||
},
|
||||
[]*models.App{app},
|
||||
[]*models.Route{
|
||||
{Path: "/hot", AppName: "myapp", Image: "fnproject/fn-test-utils", Type: "sync", Format: "http", Memory: 128, Timeout: 30, IdleTimeout: 5},
|
||||
{Path: "/hot", AppID: app.ID, Image: "fnproject/fn-test-utils", Type: "sync", Format: "http", Memory: 128, Timeout: 30, IdleTimeout: 5},
|
||||
}, nil,
|
||||
)
|
||||
|
||||
|
||||
@@ -806,7 +806,7 @@ func (s *Server) startGears(ctx context.Context, cancel context.CancelFunc) {
|
||||
|
||||
func (s *Server) bindHandlers(ctx context.Context) {
|
||||
engine := s.Router
|
||||
// now for extendible middleware
|
||||
// now for extensible middleware
|
||||
engine.Use(s.rootMiddlewareWrapper())
|
||||
|
||||
engine.GET("/", handlePing)
|
||||
@@ -822,7 +822,8 @@ func (s *Server) bindHandlers(ctx context.Context) {
|
||||
// Pure runners don't have any route, they have grpc
|
||||
if s.nodeType != ServerTypePureRunner {
|
||||
if s.nodeType != ServerTypeRunner {
|
||||
v1 := engine.Group("/v1")
|
||||
clean := engine.Group("/v1")
|
||||
v1 := clean.Group("")
|
||||
v1.Use(setAppNameInCtx)
|
||||
v1.Use(s.apiMiddlewareWrapper())
|
||||
v1.GET("/apps", s.handleAppList)
|
||||
@@ -832,39 +833,48 @@ func (s *Server) bindHandlers(ctx context.Context) {
|
||||
apps := v1.Group("/apps/:app")
|
||||
apps.Use(appNameCheck)
|
||||
|
||||
apps.GET("", s.handleAppGet)
|
||||
apps.PATCH("", s.handleAppUpdate)
|
||||
apps.DELETE("", s.handleAppDelete)
|
||||
{
|
||||
withAppCheck := apps.Group("")
|
||||
withAppCheck.Use(s.checkAppPresenceByName())
|
||||
withAppCheck.GET("", s.handleAppGetByName)
|
||||
withAppCheck.PATCH("", s.handleAppUpdate)
|
||||
withAppCheck.DELETE("", s.handleAppDelete)
|
||||
withAppCheck.GET("/routes", s.handleRouteList)
|
||||
withAppCheck.GET("/routes/:route", s.handleRouteGetAPI)
|
||||
withAppCheck.PATCH("/routes/*route", s.handleRoutesPatch)
|
||||
withAppCheck.DELETE("/routes/*route", s.handleRouteDelete)
|
||||
withAppCheck.GET("/calls/:call", s.handleCallGet)
|
||||
withAppCheck.GET("/calls/:call/log", s.handleCallLogGet)
|
||||
withAppCheck.GET("/calls", s.handleCallList)
|
||||
}
|
||||
|
||||
apps.GET("/routes", s.handleRouteList)
|
||||
apps.POST("/routes", s.handleRoutesPostPutPatch)
|
||||
apps.GET("/routes/:route", s.handleRouteGet)
|
||||
apps.PATCH("/routes/*route", s.handleRoutesPostPutPatch)
|
||||
apps.PUT("/routes/*route", s.handleRoutesPostPutPatch)
|
||||
apps.DELETE("/routes/*route", s.handleRouteDelete)
|
||||
|
||||
apps.GET("/calls", s.handleCallList)
|
||||
|
||||
apps.GET("/calls/:call", s.handleCallGet)
|
||||
apps.GET("/calls/:call/log", s.handleCallLogGet)
|
||||
apps.POST("/routes", s.handleRoutesPostPut)
|
||||
apps.PUT("/routes/*route", s.handleRoutesPostPut)
|
||||
}
|
||||
|
||||
{
|
||||
runner := v1.Group("/runner")
|
||||
runner := clean.Group("/runner")
|
||||
runner.PUT("/async", s.handleRunnerEnqueue)
|
||||
runner.GET("/async", s.handleRunnerDequeue)
|
||||
|
||||
runner.POST("/start", s.handleRunnerStart)
|
||||
runner.POST("/finish", s.handleRunnerFinish)
|
||||
|
||||
appsAPIV2 := runner.Group("/apps/:app")
|
||||
appsAPIV2.Use(setAppNameInCtx)
|
||||
appsAPIV2.GET("", s.handleAppGetByID)
|
||||
appsAPIV2.GET("/routes/:route", s.handleRouteGetRunner)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if s.nodeType != ServerTypeAPI {
|
||||
runner := engine.Group("/r")
|
||||
runner.Use(appNameCheck)
|
||||
runner.Use(s.checkAppPresenceByNameAtRunner())
|
||||
runner.Any("/:app", s.handleFunctionCall)
|
||||
runner.Any("/:app/*route", s.handleFunctionCall)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
engine.NoRoute(func(c *gin.Context) {
|
||||
@@ -880,6 +890,7 @@ func (s *Server) bindHandlers(ctx context.Context) {
|
||||
}
|
||||
handleErrorResponse(c, err)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// implements fnext.ExtServer
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -70,7 +69,7 @@ func routerRequest(t *testing.T, router *gin.Engine, method, path string, body i
|
||||
return routerRequest2(t, router, req)
|
||||
}
|
||||
|
||||
func routerRequest2(t *testing.T, router *gin.Engine, req *http.Request) (*http.Request, *httptest.ResponseRecorder) {
|
||||
func routerRequest2(_ *testing.T, router *gin.Engine, req *http.Request) (*http.Request, *httptest.ResponseRecorder) {
|
||||
rec := httptest.NewRecorder()
|
||||
rec.Body = new(bytes.Buffer)
|
||||
router.ServeHTTP(rec, req)
|
||||
@@ -84,19 +83,13 @@ func newRouterRequest(t *testing.T, method, path string, body io.Reader) (*http.
|
||||
return req, rec
|
||||
}
|
||||
|
||||
func getErrorResponse(t *testing.T, rec *httptest.ResponseRecorder) models.Error {
|
||||
respBody, err := ioutil.ReadAll(rec.Body)
|
||||
if err != nil {
|
||||
func getErrorResponse(t *testing.T, rec *httptest.ResponseRecorder) *models.Error {
|
||||
var err models.Error
|
||||
decodeErr := json.NewDecoder(rec.Body).Decode(&err)
|
||||
if decodeErr != nil {
|
||||
t.Error("Test: Expected not empty response body")
|
||||
}
|
||||
|
||||
var errResp models.Error
|
||||
err = json.Unmarshal(respBody, &errResp)
|
||||
if err != nil {
|
||||
t.Error("Test: Expected response body to be a valid models.Error object")
|
||||
}
|
||||
|
||||
return errResp
|
||||
return &err
|
||||
}
|
||||
|
||||
func prepareDB(ctx context.Context, t *testing.T) (models.Datastore, models.LogStore, func()) {
|
||||
@@ -152,6 +145,7 @@ func TestFullStack(t *testing.T) {
|
||||
|
||||
if rec.Code != test.expectedCode {
|
||||
t.Log(buf.String())
|
||||
t.Log(rec.Body.String())
|
||||
t.Errorf("Test \"%s\": Expected status code to be %d but was %d",
|
||||
test.name, test.expectedCode, rec.Code)
|
||||
}
|
||||
@@ -265,13 +259,13 @@ func TestApiNode(t *testing.T) {
|
||||
|
||||
func TestHybridEndpoints(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
app := &models.App{Name: "myapp"}
|
||||
app.SetDefaults()
|
||||
ds := datastore.NewMockInit(
|
||||
[]*models.App{{
|
||||
Name: "myapp",
|
||||
}},
|
||||
[]*models.App{app},
|
||||
[]*models.Route{{
|
||||
AppName: "myapp",
|
||||
Path: "yodawg",
|
||||
AppID: app.ID,
|
||||
Path: "yodawg",
|
||||
}}, nil,
|
||||
)
|
||||
|
||||
@@ -281,9 +275,9 @@ func TestHybridEndpoints(t *testing.T) {
|
||||
|
||||
newCallBody := func() string {
|
||||
call := &models.Call{
|
||||
ID: id.New().String(),
|
||||
AppName: "myapp",
|
||||
Path: "yodawg",
|
||||
AppID: app.ID,
|
||||
ID: id.New().String(),
|
||||
Path: "yodawg",
|
||||
// TODO ?
|
||||
}
|
||||
var b bytes.Buffer
|
||||
|
||||
Reference in New Issue
Block a user