Skip to content

Commit

Permalink
add report handlers and routes to Simulations
Browse files Browse the repository at this point in the history
  • Loading branch information
CampGareth committed Nov 12, 2018
1 parent c2a6c96 commit f471879
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 5 deletions.
79 changes: 78 additions & 1 deletion handlers/api/simulation.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package api

import (
"crypto/subtle"
"errors"
"fmt"

"github.com/ReconfigureIO/platform/middleware"
Expand All @@ -19,14 +21,16 @@ type Simulation struct {
AWS batch.Service
Events events.EventService
Storage storage.Service
Repo models.SimulationRepo
}

// NewSimulation creates a new Simulation.
func NewSimulation(events events.EventService, storageService storage.Service, awsSession batch.Service) Simulation {
func NewSimulation(events events.EventService, storageService storage.Service, awsSession batch.Service, repo models.SimulationRepo) Simulation {
return Simulation{
AWS: awsSession,
Events: events,
Storage: storageService,
Repo: repo,
}
}

Expand Down Expand Up @@ -192,6 +196,15 @@ func (s Simulation) canPostEvent(c *gin.Context, sim models.Simulation) bool {
return false
}

// isTokenAuthorized handles authentication and authorization for workers. On a
// job's (e.g. simulation) creation it is given a token which is also given to
// the worker that processes the job. When the worker sends events or reports to
// the API it includes this token in the request.
func isTokenAuthorized(c *gin.Context, correctToken string) bool {
gotToken, ok := c.GetQuery("token")
return ok && subtle.ConstantTimeCompare([]byte(gotToken), []byte(correctToken)) == 1
}

// CreateEvent creates a new event.
func (s Simulation) CreateEvent(c *gin.Context) {
sim, err := s.unauthOne(c)
Expand Down Expand Up @@ -221,6 +234,7 @@ func (s Simulation) CreateEvent(c *gin.Context) {
_, isUser := middleware.CheckUser(c)
if event.Status == models.StatusTerminated && isUser {
sugar.ErrResponse(c, 400, fmt.Sprintf("Users cannot post TERMINATED events, please upgrade to reco v0.3.1 or above"))
return
}

newEvent, err := BatchService{AWS: s.AWS}.AddEvent(&sim.BatchJob, event)
Expand All @@ -235,3 +249,66 @@ func (s Simulation) CreateEvent(c *gin.Context) {

sugar.SuccessResponse(c, 200, newEvent)
}

// Report fetches a simulation's report.
func (s Simulation) Report(c *gin.Context) {
user := middleware.GetUser(c)
var id string
if !bindID(c, &id) {
sugar.ErrResponse(c, 404, nil)
return
}
sim, err := s.Repo.ByIDForUser(id, user.ID)
if err != nil {
sugar.NotFoundOrError(c, err)
return
}

report, err := s.Repo.GetReport(sim.ID)
if err != nil {
sugar.NotFoundOrError(c, err)
return
}

sugar.SuccessResponse(c, 200, report)
}

// CreateReport creates simulation report.
func (s Simulation) CreateReport(c *gin.Context) {
var id string
if !bindID(c, &id) {
sugar.ErrResponse(c, 404, nil)
return
}
sim, err := s.Repo.ByID(id)
if err != nil {
sugar.NotFoundOrError(c, err)
return
}

if !isTokenAuthorized(c, sim.Token) {
c.AbortWithStatus(403)
return
}

if c.ContentType() != "application/vnd.reconfigure.io/reports-v1+json" {
err = errors.New("Not a valid report version")
sugar.ErrResponse(c, 400, err)
return
}

var report models.Report
err = c.BindJSON(&report)
if err != nil {
sugar.ErrResponse(c, 500, err)
return
}

err = s.Repo.StoreReport(sim.ID, report)
if err != nil {
sugar.ErrResponse(c, 500, err)
return
}

sugar.SuccessResponse(c, 200, nil)
}
49 changes: 48 additions & 1 deletion handlers/api/simulation_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package api

import (
"net/http/httptest"
"strings"
"testing"

"github.com/gin-gonic/gin"

"github.com/ReconfigureIO/platform/models"
"github.com/ReconfigureIO/platform/service/batch"
"github.com/golang/mock/gomock"
)

func Test_ServiceInterface(t *testing.T) {
var emptyReport = "{\"moduleName\":\"\",\"partName\":\"\",\"lutSummary\":{\"description\":\"\",\"used\":0,\"available\":0,\"utilisation\":0,\"detail\":null},\"regSummary\":{\"description\":\"\",\"used\":0,\"available\":0,\"utilisation\":0,\"detail\":null},\"blockRamSummary\":{\"description\":\"\",\"used\":0,\"available\":0,\"utilisation\":0,\"detail\":null},\"ultraRamSummary\":{\"description\":\"\",\"used\":0,\"available\":0,\"utilisation\":0},\"dspBlockSummary\":{\"description\":\"\",\"used\":0,\"available\":0,\"utilisation\":0},\"weightedAverage\":{\"description\":\"\",\"used\":0,\"available\":0,\"utilisation\":0}}"

func TestServiceInterface(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

Expand All @@ -18,3 +25,43 @@ func Test_ServiceInterface(t *testing.T) {
t.Error("unexpected result")
}
}

func TestSimulationReport(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
simRepo := models.NewMockSimulationRepo(mockCtrl)
simRepo.EXPECT().ByIDForUser("foosim", "foouser").Return(models.Simulation{ID: "foosim"}, nil)
simRepo.EXPECT().GetReport("foosim").Return(models.SimulationReport{}, nil)
s := Simulation{
Repo: simRepo,
}

c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Set("reco_user", models.User{ID: "foouser"})
c.Params = append(c.Params, gin.Param{Key: "id", Value: "foosim"})
s.Report(c)
if c.Writer.Status() != 200 {
t.Error("Expected 200 status, got: ", c.Writer.Status())
}
}

func TestSimulationCreateReport(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
simRepo := models.NewMockSimulationRepo(mockCtrl)
simRepo.EXPECT().ByID("foosim").Return(models.Simulation{ID: "foosim", Token: "footoken"}, nil)
simRepo.EXPECT().StoreReport("foosim", models.Report{}).Return(nil)

c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest("GET", "/?token=footoken", strings.NewReader(emptyReport))
c.Request.Header.Add("Content-Type", "application/vnd.reconfigure.io/reports-v1+json")
c.Params = append(c.Params, gin.Param{Key: "id", Value: "foosim"})

s := Simulation{
Repo: simRepo,
}
s.CreateReport(c)
if c.Writer.Status() != 200 {
t.Error("Expected 200 status, got: ", c.Writer.Status())
}
}
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/ReconfigureIO/platform/config"
"github.com/ReconfigureIO/platform/handlers/api"
"github.com/ReconfigureIO/platform/migration"
"github.com/ReconfigureIO/platform/models"
"github.com/ReconfigureIO/platform/routes"
"github.com/ReconfigureIO/platform/service/auth"
"github.com/ReconfigureIO/platform/service/auth/github"
Expand Down Expand Up @@ -144,7 +145,7 @@ func main() {
}

// routes
routes.SetupRoutes(conf.Reco, conf.SecretKey, r, db, awsSession, events, leads, storageService, deploy, publicProjectID, authService)
routes.SetupRoutes(conf.Reco, conf.SecretKey, r, db, awsSession, events, leads, storageService, deploy, publicProjectID, authService, models.SimulationDataSource(db))

// queue
var deploymentQueue queue.Queue
Expand Down
6 changes: 5 additions & 1 deletion routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/ReconfigureIO/platform/handlers/api"
"github.com/ReconfigureIO/platform/handlers/profile"
"github.com/ReconfigureIO/platform/middleware"
"github.com/ReconfigureIO/platform/models"
"github.com/ReconfigureIO/platform/service/auth"
"github.com/ReconfigureIO/platform/service/batch"
"github.com/ReconfigureIO/platform/service/deployment"
Expand All @@ -30,6 +31,7 @@ func SetupRoutes(
deploy deployment.Service,
publicProjectID string,
authService auth.Service,
simRepo models.SimulationRepo,
) *gin.Engine {

// setup common routes
Expand Down Expand Up @@ -102,14 +104,15 @@ func SetupRoutes(
projectRoute.GET("/:id", project.Get)
}

simulation := api.NewSimulation(events, storage, awsService)
simulation := api.NewSimulation(events, storage, awsService, simRepo)
simulationRoute := apiRoutes.Group("/simulations")
{
simulationRoute.GET("", simulation.List)
simulationRoute.POST("", simulation.Create)
simulationRoute.GET("/:id", simulation.Get)
simulationRoute.PUT("/:id/input", simulation.Input)
simulationRoute.GET("/:id/logs", simulation.Logs)
simulationRoute.GET("/:id/reports", simulation.Report)
}

graph := api.Graph{
Expand Down Expand Up @@ -153,6 +156,7 @@ func SetupRoutes(
reportRoutes := r.Group("", middleware.TokenAuth(db, events, config))
{
reportRoutes.POST("/builds/:id/reports", build.CreateReport)
reportRoutes.POST("/simulations/:id/reports", simulation.CreateReport)
}
return r
}
2 changes: 1 addition & 1 deletion routes/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestIndexHandler(t *testing.T) {
// Setup router
r := gin.Default()
r.LoadHTMLGlob("../templates/*")
r = SetupRoutes(config.RecoConfig{}, "secretKey", r, db, nil, events, nil, nil, nil, "foobar", &auth.NOPService{})
r = SetupRoutes(config.RecoConfig{}, "secretKey", r, db, nil, events, nil, nil, nil, "foobar", &auth.NOPService{}, nil)

// Create a mock request to the index.
req, err := http.NewRequest(http.MethodGet, "/", nil)
Expand Down

0 comments on commit f471879

Please sign in to comment.