Implemented health checker for textures provider from Mojang's API

This commit is contained in:
ErickSkrauch 2020-04-30 23:16:22 +03:00
parent 2ea094bbf6
commit 4c21fc5c90
7 changed files with 111 additions and 24 deletions

View File

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New configuration param `QUEUE_STRATEGY` with the default value `periodic`. - New configuration param `QUEUE_STRATEGY` with the default value `periodic`.
- New configuration params: `MOJANG_API_BASE_URL` and `MOJANG_SESSION_SERVER_BASE_URL`, that allow you to spoof - New configuration params: `MOJANG_API_BASE_URL` and `MOJANG_SESSION_SERVER_BASE_URL`, that allow you to spoof
Mojang API base addresses. Mojang API base addresses.
- New health checker, that ensures that response for textures provider from Mojang's API is valid.
### Fixed ### Fixed
- Handle the case when there is no textures property in Mojang's response. - Handle the case when there is no textures property in Mojang's response.

View File

@ -189,6 +189,7 @@ func newMojangTexturesBatchUUIDsProviderFullBusStrategy(config *viper.Viper) *mo
} }
func newMojangTexturesRemoteUUIDsProvider( func newMojangTexturesRemoteUUIDsProvider(
container *di.Container,
config *viper.Viper, config *viper.Viper,
emitter mojangtextures.Emitter, emitter mojangtextures.Emitter,
) (*mojangtextures.RemoteApiUuidsProvider, error) { ) (*mojangtextures.RemoteApiUuidsProvider, error) {
@ -197,6 +198,20 @@ func newMojangTexturesRemoteUUIDsProvider(
return nil, fmt.Errorf("unable to parse remote url: %w", err) return nil, fmt.Errorf("unable to parse remote url: %w", err)
} }
if err := container.Provide(func(emitter es.Subscriber, config *viper.Viper) *namedHealthChecker {
config.SetDefault("healthcheck.mojang_api_textures_provider_cool_down_duration", time.Minute+10*time.Second)
return &namedHealthChecker{
Name: "mojang-api-textures-provider-response-checker",
Checker: es.MojangApiTexturesProviderResponseChecker(
emitter,
config.GetDuration("healthcheck.mojang_api_textures_provider_cool_down_duration"),
),
}
}); err != nil {
return nil, err
}
return &mojangtextures.RemoteApiUuidsProvider{ return &mojangtextures.RemoteApiUuidsProvider{
Emitter: emitter, Emitter: emitter,
Url: *remoteUrl, Url: *remoteUrl,

View File

@ -32,33 +32,16 @@ func DatabaseChecker(connection Pingable) healthcheck.CheckerFunc {
} }
func MojangBatchUuidsProviderResponseChecker(dispatcher Subscriber, resetDuration time.Duration) healthcheck.CheckerFunc { func MojangBatchUuidsProviderResponseChecker(dispatcher Subscriber, resetDuration time.Duration) healthcheck.CheckerFunc {
var mutex sync.Mutex errHolder := &expiringErrHolder{D: resetDuration}
var lastCallErr error
var expireTimer *time.Timer
dispatcher.Subscribe( dispatcher.Subscribe(
"mojang_textures:batch_uuids_provider:result", "mojang_textures:batch_uuids_provider:result",
func(usernames []string, profiles []*mojang.ProfileInfo, err error) { func(usernames []string, profiles []*mojang.ProfileInfo, err error) {
mutex.Lock() errHolder.Set(err)
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 { return func(ctx context.Context) error {
mutex.Lock() return errHolder.Get()
defer mutex.Unlock()
return lastCallErr
} }
} }
@ -82,3 +65,47 @@ func MojangBatchUuidsProviderQueueLengthChecker(dispatcher Subscriber, maxLength
return errors.New("the maximum number of tasks in the queue has been exceeded") return errors.New("the maximum number of tasks in the queue has been exceeded")
} }
} }
func MojangApiTexturesProviderResponseChecker(dispatcher Subscriber, resetDuration time.Duration) healthcheck.CheckerFunc {
errHolder := &expiringErrHolder{D: resetDuration}
dispatcher.Subscribe(
"mojang_textures:mojang_api_textures_provider:after_request",
func(uuid string, profile *mojang.SignedTexturesResponse, err error) {
errHolder.Set(err)
},
)
return func(ctx context.Context) error {
return errHolder.Get()
}
}
type expiringErrHolder struct {
D time.Duration
err error
l sync.Mutex
t *time.Timer
}
func (h *expiringErrHolder) Get() error {
h.l.Lock()
defer h.l.Unlock()
return h.err
}
func (h *expiringErrHolder) Set(err error) {
h.l.Lock()
defer h.l.Unlock()
if h.t != nil {
h.t.Stop()
h.t = nil
}
h.err = err
if err != nil {
h.t = time.AfterFunc(h.D, func() {
h.Set(nil)
})
}
}

View File

@ -56,7 +56,7 @@ func TestMojangBatchUuidsProviderChecker(t *testing.T) {
checker := MojangBatchUuidsProviderResponseChecker(d, time.Millisecond) checker := MojangBatchUuidsProviderResponseChecker(d, time.Millisecond)
assert.Nil(t, checker(context.Background())) assert.Nil(t, checker(context.Background()))
}) })
//
t.Run("when no error occurred", func(t *testing.T) { t.Run("when no error occurred", func(t *testing.T) {
d := dispatcher.New() d := dispatcher.New()
checker := MojangBatchUuidsProviderResponseChecker(d, time.Millisecond) checker := MojangBatchUuidsProviderResponseChecker(d, time.Millisecond)
@ -107,3 +107,40 @@ func TestMojangBatchUuidsProviderQueueLengthChecker(t *testing.T) {
} }
}) })
} }
func TestMojangApiTexturesProviderResponseChecker(t *testing.T) {
t.Run("empty state", func(t *testing.T) {
d := dispatcher.New()
checker := MojangApiTexturesProviderResponseChecker(d, time.Millisecond)
assert.Nil(t, checker(context.Background()))
})
t.Run("when no error occurred", func(t *testing.T) {
d := dispatcher.New()
checker := MojangApiTexturesProviderResponseChecker(d, time.Millisecond)
d.Emit("mojang_textures:mojang_api_textures_provider:after_request",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
&mojang.SignedTexturesResponse{},
nil,
)
assert.Nil(t, checker(context.Background()))
})
t.Run("when error occurred", func(t *testing.T) {
d := dispatcher.New()
checker := MojangApiTexturesProviderResponseChecker(d, time.Millisecond)
err := errors.New("some error occurred")
d.Emit("mojang_textures:mojang_api_textures_provider:after_request", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", nil, err)
assert.Equal(t, err, checker(context.Background()))
})
t.Run("should reset value after passed duration", func(t *testing.T) {
d := dispatcher.New()
checker := MojangApiTexturesProviderResponseChecker(d, 20*time.Millisecond)
err := errors.New("some error occurred")
d.Emit("mojang_textures:mojang_api_textures_provider:after_request", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", nil, err)
assert.Equal(t, err, checker(context.Background()))
time.Sleep(40 * time.Millisecond)
assert.Nil(t, checker(context.Background()))
})
}

View File

@ -196,10 +196,15 @@ func (suite *batchUuidsProviderTestSuite) TestShouldNotSendRequestWhenNoJobsAreR
close(done) close(done)
}) })
_ = suite.GetUuidAsync("username") // Schedule one username to run the queue r := suite.GetUuidAsync("username") // Schedule one username to run the queue
suite.Strategy.Iterate(0, 1) // Return no jobs and indicate that there is one job in queue suite.Strategy.Iterate(0, 1) // Return no jobs and indicate that there is one job in queue
<-done select {
case <-r:
// fail
case <-done:
return
}
} }
// Test written for multiple usernames to ensure that the error // Test written for multiple usernames to ensure that the error

View File

@ -13,7 +13,7 @@ type MojangApiTexturesProvider struct {
func (ctx *MojangApiTexturesProvider) GetTextures(uuid string) (*mojang.SignedTexturesResponse, error) { func (ctx *MojangApiTexturesProvider) GetTextures(uuid string) (*mojang.SignedTexturesResponse, error) {
ctx.Emit("mojang_textures:mojang_api_textures_provider:before_request", uuid) ctx.Emit("mojang_textures:mojang_api_textures_provider:before_request", uuid)
result, err := uuidToTextures(uuid, true) result, err := uuidToTextures(uuid, true)
ctx.Emit("mojang_textures:mojang_api_textures_provider:after_request", result, err) ctx.Emit("mojang_textures:mojang_api_textures_provider:after_request", uuid, result, err)
return result, err return result, err
} }

View File

@ -64,6 +64,7 @@ func (suite *mojangApiTexturesProviderTestSuite) TestGetTextures() {
).Once() ).Once()
suite.Emitter.On("Emit", suite.Emitter.On("Emit",
"mojang_textures:mojang_api_textures_provider:after_request", "mojang_textures:mojang_api_textures_provider:after_request",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
expectedResult, expectedResult,
nil, nil,
).Once() ).Once()
@ -85,6 +86,7 @@ func (suite *mojangApiTexturesProviderTestSuite) TestGetTexturesWithError() {
).Once() ).Once()
suite.Emitter.On("Emit", suite.Emitter.On("Emit",
"mojang_textures:mojang_api_textures_provider:after_request", "mojang_textures:mojang_api_textures_provider:after_request",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
expectedResponse, expectedResponse,
expectedError, expectedError,
).Once() ).Once()