mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Merge branch 'resolves_issue_37' into 'master'
Resolves issue 37 Closes #37 See merge request !88
This commit is contained in:
@@ -1,96 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gitlab-odx.oracle.com/odx/functions/api"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/models"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/runner/common"
|
||||
)
|
||||
|
||||
func (s *Server) handleRouteCreate(c *gin.Context) {
|
||||
ctx := c.MustGet("ctx").(context.Context)
|
||||
log := common.Logger(ctx)
|
||||
|
||||
var wroute models.RouteWrapper
|
||||
|
||||
err := c.BindJSON(&wroute)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug(models.ErrInvalidJSON)
|
||||
c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON))
|
||||
return
|
||||
}
|
||||
|
||||
if wroute.Route == nil {
|
||||
log.WithError(err).Debug(models.ErrInvalidJSON)
|
||||
c.JSON(http.StatusBadRequest, simpleError(models.ErrRoutesMissingNew))
|
||||
return
|
||||
}
|
||||
|
||||
wroute.Route.AppName = c.MustGet(api.AppName).(string)
|
||||
|
||||
wroute.Route.SetDefaults()
|
||||
|
||||
if err := wroute.Validate(false); err != nil {
|
||||
log.WithError(err).Debug(models.ErrRoutesCreate)
|
||||
c.JSON(http.StatusBadRequest, simpleError(err))
|
||||
return
|
||||
}
|
||||
|
||||
// err = s.Runner.EnsureImageExists(ctx, &task.Config{
|
||||
// Image: wroute.Route.Image,
|
||||
// })
|
||||
// if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, simpleError(models.ErrUsableImage))
|
||||
// return
|
||||
// }
|
||||
|
||||
app, err := s.Datastore.GetApp(ctx, wroute.Route.AppName)
|
||||
if err != nil && err != models.ErrAppsNotFound {
|
||||
log.WithError(err).Error(models.ErrAppsGet)
|
||||
c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsGet))
|
||||
return
|
||||
} else if app == nil {
|
||||
// Create a new application and add the route to that new application
|
||||
newapp := &models.App{Name: wroute.Route.AppName}
|
||||
if err := newapp.Validate(); err != nil {
|
||||
log.Error(err)
|
||||
c.JSON(http.StatusInternalServerError, simpleError(err))
|
||||
return
|
||||
}
|
||||
|
||||
err = s.FireBeforeAppCreate(ctx, newapp)
|
||||
if err != nil {
|
||||
log.WithError(err).Error(models.ErrAppsCreate)
|
||||
c.JSON(http.StatusInternalServerError, simpleError(ErrInternalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
_, err = s.Datastore.InsertApp(ctx, newapp)
|
||||
if err != nil {
|
||||
log.WithError(err).Error(models.ErrRoutesCreate)
|
||||
c.JSON(http.StatusInternalServerError, simpleError(ErrInternalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
err = s.FireAfterAppCreate(ctx, newapp)
|
||||
if err != nil {
|
||||
log.WithError(err).Error(models.ErrRoutesCreate)
|
||||
c.JSON(http.StatusInternalServerError, simpleError(ErrInternalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
route, err := s.Datastore.InsertRoute(ctx, wroute.Route)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
s.cacheRefresh(route)
|
||||
|
||||
c.JSON(http.StatusOK, routeResponse{"Route successfully created", route})
|
||||
}
|
||||
150
api/server/routes_create_update.go
Normal file
150
api/server/routes_create_update.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gitlab-odx.oracle.com/odx/functions/api"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/models"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/runner/common"
|
||||
)
|
||||
|
||||
/* handleRouteCreateOrUpdate is used to handle POST PUT and PATCH for routes.
|
||||
Post will only create route if its not there and create app if its not.
|
||||
create only
|
||||
Post does not skip validation of zero values
|
||||
Put will create app if its not there and if route is there update if not it will create new route.
|
||||
update if exists or create if not exists
|
||||
Put does not skip validation of zero values
|
||||
Patch will not create app if it does not exist since the route needs to exist as well...
|
||||
update only
|
||||
Patch accepts partial updates / skips validation of zero values.
|
||||
*/
|
||||
func (s *Server) handleRouteCreateOrUpdate(c *gin.Context) {
|
||||
ctx := c.MustGet("ctx").(context.Context)
|
||||
log := common.Logger(ctx)
|
||||
method := strings.ToUpper(c.Request.Method)
|
||||
|
||||
var wroute models.RouteWrapper
|
||||
|
||||
err, resperr := s.bindAndValidate(ctx, c, method, &wroute)
|
||||
if err != nil || resperr != nil {
|
||||
log.WithError(err).Debug(resperr)
|
||||
c.JSON(http.StatusBadRequest, simpleError(resperr))
|
||||
return
|
||||
}
|
||||
|
||||
// Create the app if it does not exist.
|
||||
err, resperr = s.ensureApp(ctx, c, &wroute, method)
|
||||
if err != nil || resperr != nil {
|
||||
log.WithError(err).Debug(resperr)
|
||||
handleErrorResponse(c, resperr)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := s.updateOrInsertRoute(ctx, method, wroute)
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
s.cacheRefresh(resp.Route)
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// 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, c *gin.Context, wroute *models.RouteWrapper, method string) (error, error) {
|
||||
if !(method == http.MethodPost || method == http.MethodPut) {
|
||||
return nil, nil
|
||||
}
|
||||
var app *models.App
|
||||
var err error
|
||||
app, err = s.Datastore.GetApp(ctx, wroute.Route.AppName)
|
||||
if err != nil && err != models.ErrAppsNotFound {
|
||||
return err, models.ErrAppsGet
|
||||
} else if app == nil {
|
||||
// Create a new application
|
||||
newapp := &models.App{Name: wroute.Route.AppName}
|
||||
if err = newapp.Validate(); err != nil {
|
||||
return err, err
|
||||
}
|
||||
|
||||
err = s.FireBeforeAppCreate(ctx, newapp)
|
||||
if err != nil {
|
||||
return err, models.ErrAppsCreate
|
||||
}
|
||||
|
||||
_, err = s.Datastore.InsertApp(ctx, newapp)
|
||||
if err != nil {
|
||||
return err, models.ErrAppsCreate
|
||||
}
|
||||
|
||||
err = s.FireAfterAppCreate(ctx, newapp)
|
||||
if err != nil {
|
||||
return err, models.ErrAppsCreate
|
||||
}
|
||||
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
/* bindAndValidate binds the RouteWrapper to the json from the request and validates that it is correct.
|
||||
If it is a put or patch it makes sure that the path in the url matches the provideed one in the body.
|
||||
Defaults are set and if patch skipZero is true for validating the RouteWrapper
|
||||
*/
|
||||
func (s *Server) bindAndValidate(ctx context.Context, c *gin.Context, method string, wroute *models.RouteWrapper) (error, error) {
|
||||
err := c.BindJSON(wroute)
|
||||
if err != nil {
|
||||
return err, models.ErrInvalidJSON
|
||||
}
|
||||
|
||||
if wroute.Route == nil {
|
||||
return err, 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))
|
||||
|
||||
if wroute.Route.Path != "" && wroute.Route.Path != p {
|
||||
return models.ErrRoutesPathImmutable, models.ErrRoutesPathImmutable
|
||||
}
|
||||
wroute.Route.Path = p
|
||||
}
|
||||
|
||||
wroute.Route.SetDefaults()
|
||||
|
||||
if err = wroute.Validate(method == http.MethodPatch); err != nil {
|
||||
return models.ErrRoutesCreate, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// updateOrInsertRoute will either update or insert the route respective the method.
|
||||
func (s *Server) updateOrInsertRoute(ctx context.Context, method string, wroute models.RouteWrapper) (routeResponse, error) {
|
||||
var route *models.Route
|
||||
var err error
|
||||
resp := routeResponse{"Route successfully created", nil}
|
||||
up := routeResponse{"Route successfully updated", nil}
|
||||
|
||||
switch method {
|
||||
case http.MethodPost:
|
||||
route, err = s.Datastore.InsertRoute(ctx, wroute.Route)
|
||||
case http.MethodPut:
|
||||
route, err = s.Datastore.UpdateRoute(ctx, wroute.Route)
|
||||
if err == models.ErrRoutesNotFound {
|
||||
// try insert then
|
||||
route, err = s.Datastore.InsertRoute(ctx, wroute.Route)
|
||||
}
|
||||
case http.MethodPatch:
|
||||
// When patching if there is an error around the app we will return one and the update fails.
|
||||
route, err = s.Datastore.UpdateRoute(ctx, wroute.Route)
|
||||
resp = up
|
||||
}
|
||||
resp.Route = route
|
||||
return resp, err
|
||||
}
|
||||
@@ -7,60 +7,108 @@ import (
|
||||
"testing"
|
||||
|
||||
"gitlab-odx.oracle.com/odx/functions/api/datastore"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/logs"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/models"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/mqs"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/logs"
|
||||
)
|
||||
|
||||
type routeTestCase struct {
|
||||
ds models.Datastore
|
||||
logDB models.FnLog
|
||||
method string
|
||||
path string
|
||||
body string
|
||||
expectedCode int
|
||||
expectedError error
|
||||
}
|
||||
|
||||
func (test *routeTestCase) run(t *testing.T, i int, buf *bytes.Buffer) {
|
||||
rnr, cancel := testRunner(t)
|
||||
srv := testServer(test.ds, &mqs.Mock{}, test.logDB, rnr)
|
||||
|
||||
body := bytes.NewBuffer([]byte(test.body))
|
||||
_, rec := routerRequest(t, srv.Router, test.method, test.path, body)
|
||||
|
||||
if rec.Code != test.expectedCode {
|
||||
t.Log(buf.String())
|
||||
t.Errorf("Test %d: Expected status code to be %d but was %d",
|
||||
i, test.expectedCode, rec.Code)
|
||||
}
|
||||
|
||||
if test.expectedError != nil {
|
||||
resp := getErrorResponse(t, rec)
|
||||
if resp.Error == nil {
|
||||
t.Log(buf.String())
|
||||
t.Errorf("Test %d: Expected error message to have `%s`, but it was nil",
|
||||
i, test.expectedError)
|
||||
} else if !strings.Contains(resp.Error.Message, test.expectedError.Error()) {
|
||||
t.Log(buf.String())
|
||||
t.Errorf("Test %d: Expected error message to have `%s`, but it was `%s`",
|
||||
i, test.expectedError, resp.Error.Message)
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
buf.Reset()
|
||||
}
|
||||
|
||||
func TestRouteCreate(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
|
||||
for i, test := range []struct {
|
||||
mock models.Datastore
|
||||
logDB models.FnLog
|
||||
path string
|
||||
body string
|
||||
expectedCode int
|
||||
expectedError error
|
||||
}{
|
||||
for i, test := range []routeTestCase{
|
||||
// errors
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes", ``, http.StatusBadRequest, models.ErrInvalidJSON},
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes", `{ }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes", `{ "path": "/myroute" }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes", `{ "route": { } }`, http.StatusBadRequest, models.ErrRoutesValidationMissingPath},
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes", `{ "route": { "path": "/myroute" } }`, http.StatusBadRequest, models.ErrRoutesValidationMissingImage},
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes", `{ "route": { "image": "funcy/hello" } }`, http.StatusBadRequest, models.ErrRoutesValidationMissingPath},
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes", `{ "route": { "image": "funcy/hello", "path": "myroute" } }`, http.StatusBadRequest, models.ErrRoutesValidationInvalidPath},
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/$/routes", `{ "route": { "image": "funcy/hello", "path": "/myroute" } }`, http.StatusInternalServerError, models.ErrAppsValidationInvalidName},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", ``, http.StatusBadRequest, models.ErrInvalidJSON},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "path": "/myroute" }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { } }`, http.StatusBadRequest, models.ErrRoutesValidationMissingPath},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "path": "/myroute" } }`, http.StatusBadRequest, models.ErrRoutesValidationMissingImage},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "image": "funcy/hello" } }`, http.StatusBadRequest, models.ErrRoutesValidationMissingPath},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "image": "funcy/hello", "path": "myroute" } }`, http.StatusBadRequest, models.ErrRoutesValidationInvalidPath},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/$/routes", `{ "route": { "image": "funcy/hello", "path": "/myroute" } }`, http.StatusInternalServerError, models.ErrAppsValidationInvalidName},
|
||||
{datastore.NewMockInit(nil,
|
||||
[]*models.Route{
|
||||
{
|
||||
AppName: "a",
|
||||
Path: "/myroute",
|
||||
},
|
||||
}, nil, nil,
|
||||
), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "image": "funcy/hello", "path": "/myroute" } }`, http.StatusConflict, models.ErrRoutesAlreadyExists},
|
||||
|
||||
// success
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes", `{ "route": { "image": "funcy/hello", "path": "/myroute" } }`, http.StatusOK, nil},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPost, "/v1/apps/a/routes", `{ "route": { "image": "funcy/hello", "path": "/myroute" } }`, http.StatusOK, nil},
|
||||
} {
|
||||
rnr, cancel := testRunner(t)
|
||||
srv := testServer(test.mock, &mqs.Mock{}, test.logDB, rnr)
|
||||
test.run(t, i, buf)
|
||||
}
|
||||
}
|
||||
|
||||
body := bytes.NewBuffer([]byte(test.body))
|
||||
_, rec := routerRequest(t, srv.Router, "POST", test.path, body)
|
||||
func TestRoutePut(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
|
||||
if rec.Code != test.expectedCode {
|
||||
t.Log(buf.String())
|
||||
t.Errorf("Test %d: Expected status code to be %d but was %d",
|
||||
i, test.expectedCode, rec.Code)
|
||||
}
|
||||
for i, test := range []routeTestCase{
|
||||
// errors
|
||||
{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" }`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { } }`, http.StatusBadRequest, models.ErrRoutesValidationMissingImage},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "path": "/myroute" } }`, http.StatusBadRequest, models.ErrRoutesValidationMissingImage},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "funcy/hello", "path": "myroute" } }`, http.StatusBadRequest, models.ErrRoutesPathImmutable},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "funcy/hello", "path": "diffRoute" } }`, http.StatusBadRequest, models.ErrRoutesPathImmutable},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/$/routes/myroute", `{ "route": { "image": "funcy/hello", "path": "/myroute" } }`, http.StatusInternalServerError, models.ErrAppsValidationInvalidName},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "type": "invalid-type" } }`, http.StatusBadRequest, models.ErrRoutesValidationInvalidType},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "format": "invalid-format" } }`, http.StatusBadRequest, models.ErrRoutesValidationInvalidFormat},
|
||||
|
||||
if test.expectedError != nil {
|
||||
resp := getErrorResponse(t, rec)
|
||||
if resp.Error == nil {
|
||||
t.Log(buf.String())
|
||||
t.Errorf("Test %d: Expected error message to have `%s`, but it was nil",
|
||||
i, test.expectedError)
|
||||
} else if !strings.Contains(resp.Error.Message, test.expectedError.Error()) {
|
||||
t.Log(buf.String())
|
||||
t.Errorf("Test %d: Expected error message to have `%s`, but it was `%s`",
|
||||
i, test.expectedError, resp.Error.Message)
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
// success
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "funcy/hello", "path": "/myroute" } }`, http.StatusOK, nil},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPut, "/v1/apps/a/routes/myroute", `{ "route": { "image": "funcy/hello" } }`, http.StatusOK, nil},
|
||||
} {
|
||||
test.run(t, i, buf)
|
||||
test.ds = datastore.NewMockInit(nil,
|
||||
[]*models.Route{
|
||||
{
|
||||
AppName: "a",
|
||||
Path: "/myroute",
|
||||
},
|
||||
}, nil, nil,
|
||||
)
|
||||
test.run(t, i, buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,12 +123,12 @@ func TestRouteDelete(t *testing.T) {
|
||||
expectedCode int
|
||||
expectedError error
|
||||
}{
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes/missing", "", http.StatusNotFound, nil},
|
||||
{datastore.NewMock(), logs.NewMock(), "/v1/apps/a/routes/missing", "", http.StatusNotFound, models.ErrRoutesNotFound},
|
||||
{datastore.NewMockInit(nil,
|
||||
[]*models.Route{
|
||||
{Path: "/myroute", AppName: "a"},
|
||||
}, nil, nil,
|
||||
), logs.NewMock(),"/v1/apps/a/routes/myroute", "", http.StatusOK, nil},
|
||||
), logs.NewMock(), "/v1/apps/a/routes/myroute", "", http.StatusOK, nil},
|
||||
} {
|
||||
rnr, cancel := testRunner(t)
|
||||
srv := testServer(test.ds, &mqs.Mock{}, test.logDB, rnr)
|
||||
@@ -186,19 +234,12 @@ func TestRouteGet(t *testing.T) {
|
||||
func TestRouteUpdate(t *testing.T) {
|
||||
buf := setLogBuffer()
|
||||
|
||||
for i, test := range []struct {
|
||||
ds models.Datastore
|
||||
logDB models.FnLog
|
||||
path string
|
||||
body string
|
||||
expectedCode int
|
||||
expectedError error
|
||||
}{
|
||||
for i, test := range []routeTestCase{
|
||||
// errors
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes/myroute/do", ``, http.StatusBadRequest, models.ErrInvalidJSON},
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes/myroute/do", `{}`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes/myroute/do", `{ "route": { "type": "invalid-type" } }`, http.StatusBadRequest, nil},
|
||||
{datastore.NewMock(), logs.NewMock(),"/v1/apps/a/routes/myroute/do", `{ "route": { "format": "invalid-format" } }`, http.StatusBadRequest, nil},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPatch, "/v1/apps/a/routes/myroute/do", ``, http.StatusBadRequest, models.ErrInvalidJSON},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPatch, "/v1/apps/a/routes/myroute/do", `{}`, http.StatusBadRequest, models.ErrRoutesMissingNew},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPatch, "/v1/apps/a/routes/myroute/do", `{ "route": { "type": "invalid-type" } }`, http.StatusBadRequest, models.ErrRoutesValidationInvalidType},
|
||||
{datastore.NewMock(), logs.NewMock(), http.MethodPatch, "/v1/apps/a/routes/myroute/do", `{ "route": { "format": "invalid-format" } }`, http.StatusBadRequest, models.ErrRoutesValidationInvalidFormat},
|
||||
|
||||
// success
|
||||
{datastore.NewMockInit(nil,
|
||||
@@ -208,7 +249,7 @@ func TestRouteUpdate(t *testing.T) {
|
||||
Path: "/myroute/do",
|
||||
},
|
||||
}, nil, nil,
|
||||
), logs.NewMock(),"/v1/apps/a/routes/myroute/do", `{ "route": { "image": "funcy/hello" } }`, http.StatusOK, nil},
|
||||
), logs.NewMock(), http.MethodPatch, "/v1/apps/a/routes/myroute/do", `{ "route": { "image": "funcy/hello" } }`, http.StatusOK, nil},
|
||||
|
||||
// Addresses #381
|
||||
{datastore.NewMockInit(nil,
|
||||
@@ -218,31 +259,8 @@ func TestRouteUpdate(t *testing.T) {
|
||||
Path: "/myroute/do",
|
||||
},
|
||||
}, nil, nil,
|
||||
), logs.NewMock(),"/v1/apps/a/routes/myroute/do", `{ "route": { "path": "/otherpath" } }`, http.StatusBadRequest, nil},
|
||||
), logs.NewMock(), http.MethodPatch, "/v1/apps/a/routes/myroute/do", `{ "route": { "path": "/otherpath" } }`, http.StatusBadRequest, models.ErrRoutesPathImmutable},
|
||||
} {
|
||||
rnr, cancel := testRunner(t)
|
||||
|
||||
srv := testServer(test.ds, &mqs.Mock{}, test.logDB, rnr)
|
||||
|
||||
body := bytes.NewBuffer([]byte(test.body))
|
||||
|
||||
_, rec := routerRequest(t, srv.Router, "PATCH", test.path, body)
|
||||
|
||||
if rec.Code != test.expectedCode {
|
||||
t.Log(buf.String())
|
||||
t.Errorf("Test %d: Expected status code to be %d but was %d: %s",
|
||||
i, test.expectedCode, rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
if test.expectedError != nil {
|
||||
resp := getErrorResponse(t, rec)
|
||||
|
||||
if !strings.Contains(resp.Error.Message, test.expectedError.Error()) {
|
||||
t.Log(buf.String())
|
||||
t.Errorf("Test %d: Expected error message to have `%s`",
|
||||
i, test.expectedError.Error())
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
test.run(t, i, buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gitlab-odx.oracle.com/odx/functions/api"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/models"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/runner/common"
|
||||
)
|
||||
|
||||
func (s *Server) handleRouteUpdate(c *gin.Context) {
|
||||
ctx := c.MustGet("ctx").(context.Context)
|
||||
log := common.Logger(ctx)
|
||||
|
||||
var wroute models.RouteWrapper
|
||||
|
||||
err := c.BindJSON(&wroute)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug(models.ErrInvalidJSON)
|
||||
c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON))
|
||||
return
|
||||
}
|
||||
|
||||
if wroute.Route == nil {
|
||||
log.Debug(models.ErrRoutesMissingNew)
|
||||
c.JSON(http.StatusBadRequest, simpleError(models.ErrRoutesMissingNew))
|
||||
return
|
||||
}
|
||||
|
||||
if wroute.Route.Path != "" {
|
||||
log.Debug(models.ErrRoutesPathImmutable)
|
||||
c.JSON(http.StatusBadRequest, simpleError(models.ErrRoutesPathImmutable))
|
||||
return
|
||||
}
|
||||
|
||||
// fmt.Printf("ROUTE BOUND: %+v", *wroute.Route)
|
||||
|
||||
wroute.Route.AppName = c.MustGet(api.AppName).(string)
|
||||
wroute.Route.Path = path.Clean(c.MustGet(api.Path).(string))
|
||||
|
||||
wroute.Route.SetDefaults()
|
||||
|
||||
if err := wroute.Validate(true); err != nil {
|
||||
log.WithError(err).Debug(models.ErrRoutesUpdate)
|
||||
c.JSON(http.StatusBadRequest, simpleError(err))
|
||||
return
|
||||
}
|
||||
|
||||
if wroute.Route.Image != "" {
|
||||
// This was checking that an image exists, but it's too slow of an operation. Checks at runtime now.
|
||||
// err = s.Runner.EnsureImageExists(ctx, &task.Config{
|
||||
// Image: wroute.Route.Image,
|
||||
// })
|
||||
// if err != nil {
|
||||
// log.WithError(err).Debug(models.ErrRoutesUpdate)
|
||||
// c.JSON(http.StatusBadRequest, simpleError(models.ErrUsableImage))
|
||||
// return
|
||||
// }
|
||||
}
|
||||
|
||||
route, err := s.Datastore.UpdateRoute(ctx, wroute.Route)
|
||||
if err == models.ErrRoutesNotFound {
|
||||
// try insert then
|
||||
route, err = s.Datastore.InsertRoute(ctx, wroute.Route)
|
||||
}
|
||||
if err != nil {
|
||||
handleErrorResponse(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
s.cacheRefresh(route)
|
||||
|
||||
c.JSON(http.StatusOK, routeResponse{"Route successfully updated", route})
|
||||
}
|
||||
@@ -19,30 +19,30 @@ import (
|
||||
"gitlab-odx.oracle.com/odx/functions/api"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/datastore"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/id"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/logs"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/models"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/mqs"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/runner"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/runner/common"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/server/internal/routecache"
|
||||
"gitlab-odx.oracle.com/odx/functions/api/logs"
|
||||
)
|
||||
|
||||
const (
|
||||
EnvLogLevel = "log_level"
|
||||
EnvMQURL = "mq_url"
|
||||
EnvDBURL = "db_url"
|
||||
EnvLOGDBURL = "logstore_url"
|
||||
EnvLOGDBURL = "logstore_url"
|
||||
EnvPort = "port" // be careful, Gin expects this variable to be "port"
|
||||
EnvAPIURL = "api_url"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
Datastore models.Datastore
|
||||
Runner *runner.Runner
|
||||
Router *gin.Engine
|
||||
MQ models.MessageQueue
|
||||
Enqueue models.Enqueue
|
||||
LogDB models.FnLog
|
||||
Datastore models.Datastore
|
||||
Runner *runner.Runner
|
||||
Router *gin.Engine
|
||||
MQ models.MessageQueue
|
||||
Enqueue models.Enqueue
|
||||
LogDB models.FnLog
|
||||
|
||||
apiURL string
|
||||
|
||||
@@ -317,9 +317,10 @@ func (s *Server) bindHandlers(ctx context.Context) {
|
||||
apps := v1.Group("/apps/:app")
|
||||
{
|
||||
apps.GET("/routes", s.handleRouteList)
|
||||
apps.POST("/routes", s.handleRouteCreate)
|
||||
apps.POST("/routes", s.handleRouteCreateOrUpdate)
|
||||
apps.GET("/routes/*route", s.handleRouteGet)
|
||||
apps.PATCH("/routes/*route", s.handleRouteUpdate)
|
||||
apps.PATCH("/routes/*route", s.handleRouteCreateOrUpdate)
|
||||
apps.PUT("/routes/*route", s.handleRouteCreateOrUpdate)
|
||||
apps.DELETE("/routes/*route", s.handleRouteDelete)
|
||||
apps.GET("/calls/*route", s.handleCallList)
|
||||
}
|
||||
@@ -369,6 +370,6 @@ type fnCallsResponse struct {
|
||||
}
|
||||
|
||||
type fnCallLogResponse struct {
|
||||
Message string `json:"message"`
|
||||
Log *models.FnCallLog `json:"log"`
|
||||
Message string `json:"message"`
|
||||
Log *models.FnCallLog `json:"log"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user