Skip to content

Commit

Permalink
Implement listing scheduler events endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Luiz Miranda committed Sep 10, 2021
1 parent 9a61703 commit a43574d
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 26 deletions.
52 changes: 29 additions & 23 deletions api/api_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,36 @@ import (
loginMocks "github.com/topfreegames/maestro/login/mocks"
"github.com/topfreegames/maestro/mocks"
"github.com/topfreegames/maestro/models"
storageMock "github.com/topfreegames/maestro/storage/mock"
"k8s.io/client-go/kubernetes/fake"
metricsFake "k8s.io/metrics/pkg/client/clientset/versioned/fake"
)

var (
app *api.App
clientset *fake.Clientset
metricsClientset *metricsFake.Clientset
config *viper.Viper
hook *test.Hook
logger *logrus.Logger
mockCtrl *gomock.Controller
mockDb *pgmocks.MockDB
mockCtxWrapper *pgmocks.MockCtxWrapper
mockPipeline *redismocks.MockPipeliner
mockRedisClient *redismocks.MockRedisClient
mockRedisTraceWrapper *redismocks.MockTraceWrapper
mockClientset *fake.Clientset
mockPortChooser *mocks.MockPortChooser
mockEventForwarder1 *eventforwardermock.MockEventForwarder
mockEventForwarder2 *eventforwardermock.MockEventForwarder
mockEventForwarder3 *eventforwardermock.MockEventForwarder
mockEventForwarder4 *eventforwardermock.MockEventForwarder
mockEventForwarder5 *eventforwardermock.MockEventForwarder
mockLogin *loginMocks.MockLogin
mockClock *clockmocks.MockClock
mmr *models.MixedMetricsReporter
allStatus = []string{
app *api.App
clientset *fake.Clientset
metricsClientset *metricsFake.Clientset
config *viper.Viper
hook *test.Hook
logger *logrus.Logger
mockCtrl *gomock.Controller
mockDb *pgmocks.MockDB
mockCtxWrapper *pgmocks.MockCtxWrapper
mockPipeline *redismocks.MockPipeliner
mockRedisClient *redismocks.MockRedisClient
mockRedisTraceWrapper *redismocks.MockTraceWrapper
mockClientset *fake.Clientset
mockPortChooser *mocks.MockPortChooser
mockEventForwarder1 *eventforwardermock.MockEventForwarder
mockEventForwarder2 *eventforwardermock.MockEventForwarder
mockEventForwarder3 *eventforwardermock.MockEventForwarder
mockEventForwarder4 *eventforwardermock.MockEventForwarder
mockEventForwarder5 *eventforwardermock.MockEventForwarder
mockLogin *loginMocks.MockLogin
mockClock *clockmocks.MockClock
mockSchedulerEventStorage *storageMock.MockSchedulerEventStorage
mmr *models.MixedMetricsReporter
allStatus = []string{
models.StatusCreating,
models.StatusReady,
models.StatusOccupied,
Expand Down Expand Up @@ -108,10 +110,14 @@ var _ = BeforeEach(func() {

mockRedisClient.EXPECT().Ping().Return(redis.NewStatusResult("PONG", nil)).AnyTimes()
app, err = api.NewApp("0.0.0.0", 9998, config, logger, false, "", mockDb, mockCtxWrapper, mockRedisClient, mockRedisTraceWrapper, clientset, metricsClientset)

Expect(err).NotTo(HaveOccurred())

mockLogin = loginMocks.NewMockLogin(mockCtrl)
app.Login = mockLogin

mockSchedulerEventStorage = storageMock.NewMockSchedulerEventStorage(mockCtrl)
app.SchedulerEventStorage = mockSchedulerEventStorage
})

var _ = AfterEach(func() {
Expand Down
18 changes: 16 additions & 2 deletions api/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import (
"context"
e "errors"
"fmt"
goredis "github.com/go-redis/redis"
"github.com/topfreegames/maestro/api/auth"
"io"
"net"
"net/http"
Expand All @@ -22,6 +20,10 @@ import (
"syscall"
"time"

goredis "github.com/go-redis/redis"
"github.com/topfreegames/maestro/api/auth"
"github.com/topfreegames/maestro/storage"

raven "github.com/getsentry/raven-go"
newrelic "github.com/newrelic/go-agent"
"github.com/topfreegames/extensions/middleware"
Expand Down Expand Up @@ -73,6 +75,7 @@ type App struct {
EmailDomains []string
Forwarders []*eventforwarder.Info
SchedulerCache *models.SchedulerCache
SchedulerEventStorage storage.SchedulerEventStorage
William *william.WilliamAuth
gracefulShutdown *gracefulShutdown
}
Expand Down Expand Up @@ -202,6 +205,17 @@ func (a *App) getRouter() *mux.Router {
NewParamMiddleware(func() interface{} { return &models.SchedulerParams{} }),
).ServeHTTP).Methods("GET").Name("schedulerStatus")

r.HandleFunc("/scheduler/{schedulerName}/events", Chain(
NewSchedulerEventHandler(a),
NewAccessMiddleware(a),
NewAuthMiddleware(a, auth.SchedulerPathResolver("GetScheduler", "schedulerName")),
NewMetricsReporterMiddleware(a),
NewSentryMiddleware(),
NewNewRelicMiddleware(a),
NewDogStatsdMiddleware(a),
NewParamMiddleware(func() interface{} { return &models.SchedulerEventsParams{} }),
).ServeHTTP).Methods("GET").Name("schedulerEvents")

r.HandleFunc("/scheduler/{schedulerName}/config", Chain(
NewGetSchedulerConfigHandler(a),
NewAccessMiddleware(a),
Expand Down
57 changes: 57 additions & 0 deletions api/scheduler_event_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package api

import (
"encoding/json"
"net/http"
"strconv"

"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"github.com/topfreegames/extensions/middleware"
)

// SchedulerEventHandler returns the scheduler events
type SchedulerEventHandler struct {
App *App
}

// NewSchedulerEventHandler returns an instance of SchedulerEventHandler
func NewSchedulerEventHandler(a *App) *SchedulerEventHandler {
m := &SchedulerEventHandler{App: a}
return m
}

func (g *SchedulerEventHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
schedulerName := vars["schedulerName"]

l := middleware.GetLogger(r.Context())
logger := l.WithFields(logrus.Fields{
"source": "SchedulerEventHandler",
"operation": "get scheduler events",
"scheduler": schedulerName,
})

page, err := strconv.Atoi(r.URL.Query().Get("page"))
if err != nil {
errorMsg := "failed to convert page param to integer"
logger.WithError(err).Error(errorMsg)
g.App.HandleError(w, http.StatusBadRequest, errorMsg, err)
return
}

logger.Info("Fetching scheduler events")

events, err := g.App.SchedulerEventStorage.LoadSchedulerEvents(schedulerName, page)
if err != nil {
WriteJSON(w, http.StatusInternalServerError, map[string]interface{}{
"success": false,
"description": err.Error(),
})
return
}

bts, _ := json.Marshal(events)
WriteBytes(w, http.StatusOK, bts)
logger.Info("Successfully wrote status response")
}
118 changes: 118 additions & 0 deletions api/scheduler_event_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// maestro
// +build unit
// https://github.com/topfreegames/maestro
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license
// Copyright © 2017 Top Free Games <backend@tfgco.com>

package api_test

import (
"encoding/json"
"errors"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"fmt"
"net/http"
"net/http/httptest"
"time"

"github.com/golang/mock/gomock"
"github.com/topfreegames/maestro/login"
"github.com/topfreegames/maestro/models"
)

var _ = Describe("Scheduler Event Handler", func() {
var (
recorder *httptest.ResponseRecorder
)

BeforeEach(func() {
recorder = httptest.NewRecorder()
})

Context("When authentication is ok", func() {
BeforeEach(func() {
mockDb.EXPECT().Query(gomock.Any(), `SELECT access_token, refresh_token, expiry, token_type
FROM users
WHERE key_access_token = ?`, gomock.Any()).
Do(func(destToken *login.DestinationToken, query string, modifier string) {
destToken.RefreshToken = "refresh-token"
}).AnyTimes()
mockLogin.EXPECT().Authenticate(gomock.Any(), app.DBClient.DB).Return("user@example.com", http.StatusOK, nil).AnyTimes()
mockCtxWrapper.EXPECT().WithContext(gomock.Any(), app.DBClient.DB).Return(app.DBClient.DB).AnyTimes()
})

Describe("GET /schedulers/:name/events", func() {
It("should list scheduler events", func() {
page := 1
schedulerName := "scheduler-name-1"
url := fmt.Sprintf("http://%s/scheduler/%s/events?page=%d", app.Address, schedulerName, page)
request, err := http.NewRequest("GET", url, nil)
Expect(err).NotTo(HaveOccurred())

createdAtString := "2020-01-01T00:00:00.000Z"
createdAt, err := time.Parse(time.RFC3339Nano, createdAtString)
mockSchedulerEventStorage.EXPECT().LoadSchedulerEvents(schedulerName, page).Return([]*models.SchedulerEvent{
{
Name: "UPDATE_STARTED",
SchedulerName: schedulerName,
CreatedAt: createdAt,
Metadata: map[string]interface{}{
"reason": "update",
},
},
}, nil)
app.Router.ServeHTTP(recorder, request)
Expect(recorder.Code).To(Equal(http.StatusOK))

resp := make([]map[string]interface{}, 1)
err = json.Unmarshal(recorder.Body.Bytes(), &resp)
Expect(err).NotTo(HaveOccurred())
Expect(resp[0]).To(HaveKeyWithValue("name", "UPDATE_STARTED"))
Expect(resp[0]).To(HaveKeyWithValue("schedulerName", "scheduler-name-1"))
Expect(resp[0]).To(HaveKeyWithValue("createdAt", "2020-01-01T00:00:00Z"))
Expect(resp[0]).To(HaveKeyWithValue("metadata", map[string]interface{}{
"reason": "update",
}))
})

It("should return bad request when page is not informed", func() {
schedulerName := "scheduler-name-1"
url := fmt.Sprintf("http://%s/scheduler/%s/events", app.Address, schedulerName)
request, err := http.NewRequest("GET", url, nil)
Expect(err).NotTo(HaveOccurred())

app.Router.ServeHTTP(recorder, request)
Expect(recorder.Code).To(Equal(http.StatusBadRequest))
})

It("should return bad request when page is not an integer", func() {
page := "not_an_integer"
schedulerName := "scheduler-name-1"
url := fmt.Sprintf("http://%s/scheduler/%s/events?page=%s", app.Address, schedulerName, page)
request, err := http.NewRequest("GET", url, nil)
Expect(err).NotTo(HaveOccurred())

app.Router.ServeHTTP(recorder, request)
Expect(recorder.Code).To(Equal(http.StatusBadRequest))
})

It("should return internal server error when storage fails to retrieve data", func() {
page := 1
schedulerName := "scheduler-name-1"
url := fmt.Sprintf("http://%s/scheduler/%s/events?page=%d", app.Address, schedulerName, page)
request, err := http.NewRequest("GET", url, nil)
Expect(err).NotTo(HaveOccurred())

mockSchedulerEventStorage.EXPECT().LoadSchedulerEvents(schedulerName, page).Return(nil, errors.New("Failed to retrieve events"))

app.Router.ServeHTTP(recorder, request)
Expect(recorder.Code).To(Equal(http.StatusInternalServerError))
})
})
})
})
5 changes: 5 additions & 0 deletions models/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ type SchedulerParams struct {
SchedulerName string `json:"schedulerName" valid:"required"`
}

// SchedulerParams is the struct that defines the params for scheduler routes
type SchedulerEventsParams struct {
SchedulerName string `json:"schedulerName" valid:"required"`
}

// SchedulerLockParams is the struct that defines the params for scheduler locks routes
type SchedulerLockParams struct {
SchedulerName string `json:"schedulerName" valid:"required"`
Expand Down
2 changes: 1 addition & 1 deletion storage/mock/mock_scheduler_event_storage.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a43574d

Please sign in to comment.