Merge branch 'worker_health_status'. Resolves #16

This commit is contained in:
ErickSkrauch 2020-04-10 16:38:51 +03:00
commit b0ba94751a
No known key found for this signature in database
GPG Key ID: 669339FCBB30EE0E
7 changed files with 193 additions and 9 deletions

9
Gopkg.lock generated
View File

@ -39,6 +39,14 @@
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
digest = "1:2e7c296138d042515eb2995fe58026eaef2c08f660a5f36584faecf34eea3cf0"
name = "github.com/etherlabsio/healthcheck"
packages = ["."]
pruneopts = ""
revision = "dd3d2fd8c3f620a32b7f3cd9b4f0d2f7d0875ab1"
version = "2.0.3"
[[projects]]
digest = "1:9f1e571696860f2b4f8a241b43ce91c6085e7aaed849ccca53f590a4dc7b95bd"
name = "github.com/fsnotify/fsnotify"
@ -311,6 +319,7 @@
"github.com/SermoDigital/jose/crypto",
"github.com/SermoDigital/jose/jws",
"github.com/asaskevich/EventBus",
"github.com/etherlabsio/healthcheck",
"github.com/getsentry/raven-go",
"github.com/gorilla/mux",
"github.com/h2non/gock",

View File

@ -41,6 +41,10 @@ ignored = ["github.com/elyby/chrly"]
source = "https://github.com/erickskrauch/EventBus.git"
branch = "publish_nil_values"
[[constraint]]
name = "github.com/etherlabsio/healthcheck"
version = "2.0.3"
# Testing dependencies
[[constraint]]

View File

@ -4,7 +4,9 @@ import (
"fmt"
"log"
"os"
"time"
"github.com/etherlabsio/healthcheck"
"github.com/mono83/slf/wd"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -51,6 +53,22 @@ var workerCmd = &cobra.Command{
Emitter: dispatcher,
UUIDsProvider: uuidsProvider,
}).CreateHandler()
handler.Handle("/healthcheck", healthcheck.Handler(
healthcheck.WithChecker(
"mojang-batch-uuids-provider-response",
eventsubscribers.MojangBatchUuidsProviderResponseChecker(
dispatcher,
viper.GetDuration("healthcheck.mojang_batch_uuids_provider_cool_down_duration"),
),
),
healthcheck.WithChecker(
"mojang-batch-uuids-provider-queue-length",
eventsubscribers.MojangBatchUuidsProviderQueueLengthChecker(
dispatcher,
viper.GetInt("healthcheck.mojang_batch_uuids_provider_queue_length_limit"),
),
),
)).Methods("GET")
finishChan := make(chan bool)
go func() {
@ -73,4 +91,6 @@ var workerCmd = &cobra.Command{
func init() {
RootCmd.AddCommand(workerCmd)
viper.SetDefault("healthcheck.mojang_batch_uuids_provider_cool_down_duration", time.Minute)
viper.SetDefault("healthcheck.mojang_batch_uuids_provider_queue_length_limit", 50)
}

View File

@ -0,0 +1,64 @@
package eventsubscribers
import (
"context"
"errors"
"sync"
"time"
"github.com/etherlabsio/healthcheck"
"github.com/elyby/chrly/api/mojang"
)
func MojangBatchUuidsProviderResponseChecker(dispatcher Subscriber, resetDuration time.Duration) healthcheck.CheckerFunc {
var mutex sync.Mutex
var lastCallErr error
var expireTimer *time.Timer
dispatcher.Subscribe(
"mojang_textures:batch_uuids_provider:result",
func(usernames []string, profiles []*mojang.ProfileInfo, err error) {
mutex.Lock()
defer mutex.Unlock()
lastCallErr = err
if expireTimer != nil {
expireTimer.Stop()
}
expireTimer = time.AfterFunc(resetDuration, func() {
mutex.Lock()
lastCallErr = nil
mutex.Unlock()
})
},
)
return func(ctx context.Context) error {
mutex.Lock()
defer mutex.Unlock()
return lastCallErr
}
}
func MojangBatchUuidsProviderQueueLengthChecker(dispatcher Subscriber, maxLength int) healthcheck.CheckerFunc {
var mutex sync.Mutex
queueLength := 0
dispatcher.Subscribe("mojang_textures:batch_uuids_provider:round", func(usernames []string, tasksInQueue int) {
mutex.Lock()
queueLength = tasksInQueue
mutex.Unlock()
})
return func(ctx context.Context) error {
mutex.Lock()
defer mutex.Unlock()
if queueLength < maxLength {
return nil
}
return errors.New("the maximum number of tasks in the queue has been exceeded")
}
}

View File

@ -0,0 +1,71 @@
package eventsubscribers
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/elyby/chrly/api/mojang"
"github.com/elyby/chrly/bootstrap"
)
func TestMojangBatchUuidsProviderChecker(t *testing.T) {
t.Run("empty state", func(t *testing.T) {
dispatcher := bootstrap.CreateEventDispatcher()
checker := MojangBatchUuidsProviderResponseChecker(dispatcher, time.Millisecond)
assert.Nil(t, checker(context.Background()))
})
t.Run("when no error occurred", func(t *testing.T) {
dispatcher := bootstrap.CreateEventDispatcher()
checker := MojangBatchUuidsProviderResponseChecker(dispatcher, time.Millisecond)
dispatcher.Emit("mojang_textures:batch_uuids_provider:result", []string{"username"}, []*mojang.ProfileInfo{}, nil)
assert.Nil(t, checker(context.Background()))
})
t.Run("when error occurred", func(t *testing.T) {
dispatcher := bootstrap.CreateEventDispatcher()
checker := MojangBatchUuidsProviderResponseChecker(dispatcher, time.Millisecond)
err := errors.New("some error occurred")
dispatcher.Emit("mojang_textures:batch_uuids_provider:result", []string{"username"}, nil, err)
assert.Equal(t, err, checker(context.Background()))
})
t.Run("should reset value after passed duration", func(t *testing.T) {
dispatcher := bootstrap.CreateEventDispatcher()
checker := MojangBatchUuidsProviderResponseChecker(dispatcher, 20*time.Millisecond)
err := errors.New("some error occurred")
dispatcher.Emit("mojang_textures:batch_uuids_provider:result", []string{"username"}, nil, err)
assert.Equal(t, err, checker(context.Background()))
time.Sleep(40 * time.Millisecond)
assert.Nil(t, checker(context.Background()))
})
}
func TestMojangBatchUuidsProviderQueueLengthChecker(t *testing.T) {
t.Run("empty state", func(t *testing.T) {
dispatcher := bootstrap.CreateEventDispatcher()
checker := MojangBatchUuidsProviderQueueLengthChecker(dispatcher, 10)
assert.Nil(t, checker(context.Background()))
})
t.Run("less than allowed limit", func(t *testing.T) {
dispatcher := bootstrap.CreateEventDispatcher()
checker := MojangBatchUuidsProviderQueueLengthChecker(dispatcher, 10)
dispatcher.Emit("mojang_textures:batch_uuids_provider:round", []string{"username"}, 9)
assert.Nil(t, checker(context.Background()))
})
t.Run("greater than allowed limit", func(t *testing.T) {
dispatcher := bootstrap.CreateEventDispatcher()
checker := MojangBatchUuidsProviderQueueLengthChecker(dispatcher, 10)
dispatcher.Emit("mojang_textures:batch_uuids_provider:round", []string{"username"}, 10)
checkResult := checker(context.Background())
if assert.Error(t, checkResult) {
assert.Equal(t, "the maximum number of tasks in the queue has been exceeded", checkResult.Error())
}
})
}

View File

@ -111,12 +111,13 @@ func (ctx *BatchUuidsProvider) queueRound() {
usernames = append(usernames, job.username)
}
ctx.Emit("mojang_textures:batch_uuids_provider:round", usernames, queueSize - len(jobs))
ctx.Emit("mojang_textures:batch_uuids_provider:round", usernames, queueSize-len(jobs))
if len(usernames) == 0 {
return
}
profiles, err := usernamesToUuids(usernames)
ctx.Emit("mojang_textures:batch_uuids_provider:result", usernames, profiles, err)
for _, job := range jobs {
go func(job *jobItem) {
response := &jobResult{}

View File

@ -155,13 +155,16 @@ func TestBatchUuidsProvider(t *testing.T) {
}
func (suite *batchUuidsProviderTestSuite) TestGetUuidForOneUsername() {
expectedUsernames := []string{"username"}
expectedResult := &mojang.ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username"}
expectedResponse := []*mojang.ProfileInfo{expectedResult}
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", []string{"username"}, 0).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", expectedUsernames, 0).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", expectedUsernames, expectedResponse, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Once()
suite.MojangApi.On("UsernamesToUuids", []string{"username"}).Once().Return([]*mojang.ProfileInfo{expectedResult}, nil)
suite.MojangApi.On("UsernamesToUuids", expectedUsernames).Once().Return([]*mojang.ProfileInfo{expectedResult}, nil)
resultChan := suite.GetUuidAsync("username")
@ -173,14 +176,17 @@ func (suite *batchUuidsProviderTestSuite) TestGetUuidForOneUsername() {
}
func (suite *batchUuidsProviderTestSuite) TestGetUuidForTwoUsernames() {
expectedUsernames := []string{"username1", "username2"}
expectedResult1 := &mojang.ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username1"}
expectedResult2 := &mojang.ProfileInfo{Id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", Name: "username2"}
expectedResponse := []*mojang.ProfileInfo{expectedResult1, expectedResult2}
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", []string{"username1", "username2"}, 0).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", expectedUsernames, 0).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", expectedUsernames, expectedResponse, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Once()
suite.MojangApi.On("UsernamesToUuids", []string{"username1", "username2"}).Once().Return([]*mojang.ProfileInfo{
suite.MojangApi.On("UsernamesToUuids", expectedUsernames).Once().Return([]*mojang.ProfileInfo{
expectedResult1,
expectedResult2,
}, nil)
@ -205,13 +211,18 @@ func (suite *batchUuidsProviderTestSuite) TestGetUuidForMoreThan10Usernames() {
usernames[i] = randStr(8)
}
// In this test we're not testing response, so always return an empty resultset
expectedResponse := []*mojang.ProfileInfo{}
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Twice()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", usernames[0:10], 2).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", usernames[0:10], expectedResponse, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", usernames[10:12], 0).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", usernames[10:12], expectedResponse, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Twice()
suite.MojangApi.On("UsernamesToUuids", usernames[0:10]).Once().Return([]*mojang.ProfileInfo{}, nil)
suite.MojangApi.On("UsernamesToUuids", usernames[10:12]).Once().Return([]*mojang.ProfileInfo{}, nil)
suite.MojangApi.On("UsernamesToUuids", usernames[0:10]).Once().Return(expectedResponse, nil)
suite.MojangApi.On("UsernamesToUuids", usernames[10:12]).Once().Return(expectedResponse, nil)
channels := make([]chan *batchUuidsProviderGetUuidResult, len(usernames))
for i, username := range usernames {
@ -229,6 +240,7 @@ func (suite *batchUuidsProviderTestSuite) TestGetUuidForMoreThan10Usernames() {
func (suite *batchUuidsProviderTestSuite) TestDoNothingWhenNoTasks() {
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Times(3)
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", []string{"username"}, 0).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", []string{"username"}, mock.Anything, nil).Once()
var nilStringSlice []string
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", nilStringSlice, 0).Twice()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Times(3)
@ -250,13 +262,16 @@ func (suite *batchUuidsProviderTestSuite) TestDoNothingWhenNoTasks() {
}
func (suite *batchUuidsProviderTestSuite) TestGetUuidForTwoUsernamesWithAnError() {
expectedUsernames := []string{"username1", "username2"}
expectedError := &mojang.TooManyRequestsError{}
var nilProfilesResponse []*mojang.ProfileInfo
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", []string{"username1", "username2"}, 0).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", expectedUsernames, 0).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", expectedUsernames, nilProfilesResponse, expectedError).Once()
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Once()
suite.MojangApi.On("UsernamesToUuids", []string{"username1", "username2"}).Once().Return(nil, expectedError)
suite.MojangApi.On("UsernamesToUuids", expectedUsernames).Once().Return(nil, expectedError)
resultChan1 := suite.GetUuidAsync("username1")
resultChan2 := suite.GetUuidAsync("username2")