Skip to content

Commit

Permalink
Merge pull request #297 from ReconfigureIO/feature/simulation-reports…
Browse files Browse the repository at this point in the history
…-fixed

add ability to upload and retrieve simulation reports
  • Loading branch information
pwaller authored Nov 12, 2018
2 parents b8f02dc + e342d74 commit 35ffe7f
Show file tree
Hide file tree
Showing 18 changed files with 584 additions and 69 deletions.
8 changes: 8 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Platform v0.1.0

# Platform v0.1.0

## Features
- Added support for storing and retrieving Simulation Reports

## Bugfixes
2 changes: 1 addition & 1 deletion handlers/api/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ func (b Build) CreateReport(c *gin.Context) {

switch c.ContentType() {
case "application/vnd.reconfigure.io/reports-v1+json":
report := models.ReportV1{}
report := models.Report{}
c.BindJSON(&report)
err = buildRepo.StoreBuildReport(build, report)
default:
Expand Down
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
2 changes: 2 additions & 0 deletions migration/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/ReconfigureIO/platform/migration/migration201801260952"
"github.com/ReconfigureIO/platform/migration/migration201802231224"
"github.com/ReconfigureIO/platform/migration/migration201807191024"
"github.com/ReconfigureIO/platform/migration/migration201809061242"

"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
Expand All @@ -24,6 +25,7 @@ var migrations = []*gormigrate.Migration{
&migration201801260952.Migration,
&migration201802231224.Migration,
&migration201807191024.Migration,
&migration201809061242.Migration,
}

// MigrateSchema performs database migration.
Expand Down
20 changes: 20 additions & 0 deletions migration/migration201809061242/migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package migration201809061242

import (
"errors"

"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
gormigrate "gopkg.in/gormigrate.v1"
)

var Migration = gormigrate.Migration{
ID: "201809061242",
Migrate: func(tx *gorm.DB) error {
err := tx.AutoMigrate(&SimulationReport{}).Error
return err
},
Rollback: func(tx *gorm.DB) error {
return errors.New("Migration failed. Hit rollback conditions while adding Simulation Reports table to DB")
},
}
Loading

0 comments on commit 35ffe7f

Please sign in to comment.