From df40c876e3ac327479778b59781e098500775577 Mon Sep 17 00:00:00 2001 From: Dimitar Petrov Date: Fri, 23 Aug 2019 08:52:46 +0300 Subject: [PATCH] Async Healthcheck (#301) * Async health check * Tests adaptation * Make health configurable externally * Install health later * Export ConvertStatus function * Fix health settings validation * Attach logger to health * Address PR comments * Configuration per indicator and refactoring * Add health status listener * Minor tweaks * Fix indicator interval type * Fix tests * Remove unused import * Extract indicator configuration and address PR comments * Add error to panic * Address PR comments * minor fix * rename storage indicator --- Gopkg.lock | 25 ++++ Gopkg.toml | 6 +- api/healthcheck/composite_indicator.go | 68 --------- api/healthcheck/composite_indicator_test.go | 87 ------------ api/healthcheck/healthcheck_controller.go | 48 ++++++- .../healthcheck_controller_test.go | 133 ++++++++++++++++-- config/config.go | 5 +- config/config_test.go | 74 ++++++++++ pkg/health/aggregation_policy.go | 41 ------ pkg/health/aggregation_policy_test.go | 67 --------- .../healthfakes/fake_aggregation_policy.go | 110 --------------- pkg/health/healthfakes/fake_indicator.go | 131 ++++++++--------- pkg/health/ping.go | 29 ---- pkg/health/registry_test.go | 33 +---- pkg/health/types.go | 108 +++++++++----- pkg/sm/sm.go | 61 ++++++-- storage/healthcheck.go | 40 ++++-- storage/healthcheck_test.go | 37 +++-- storage/interfaces.go | 14 +- storage/postgres/storage.go | 2 +- storage/postgres/storage_test.go | 2 +- storage/storagefakes/fake_pinger.go | 84 ++++++----- storage/storagefakes/fake_storage.go | 83 ++++++----- 23 files changed, 612 insertions(+), 676 deletions(-) delete mode 100644 api/healthcheck/composite_indicator.go delete mode 100644 api/healthcheck/composite_indicator_test.go delete mode 100644 pkg/health/aggregation_policy.go delete mode 100644 pkg/health/aggregation_policy_test.go delete mode 100644 pkg/health/healthfakes/fake_aggregation_policy.go delete mode 100644 pkg/health/ping.go diff --git a/Gopkg.lock b/Gopkg.lock index 3d8c4cd45..9d7656807 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -8,6 +8,28 @@ pruneopts = "UT" revision = "852fc940e4b9b895dc144b88ee8f6e39228127b0" +[[projects]] + digest = "1:12156f9d8523585d9d875a03c4b5c6c6374f7370716cd814aa6781732ac942c5" + name = "github.com/InVisionApp/go-health" + packages = [ + ".", + "checkers", + ] + pruneopts = "UT" + revision = "06e1878ab5a8acf6e841861c6cbfcc4d3036add0" + version = "v2.1.0" + +[[projects]] + digest = "1:be2deb3cba3d2526b3692d80483f59df6462aabc875aace2db263f2664f30670" + name = "github.com/InVisionApp/go-logger" + packages = [ + ".", + "shims/logrus", + ] + pruneopts = "UT" + revision = "c377c6c3f6a48a6366517e86f77bafea18454ff1" + version = "v1.0.1" + [[projects]] digest = "1:f780d408067189c4c42b53f7bb24ebf8fd2a1e4510b813ed6e79dd6563e38cc5" name = "github.com/ajg/form" @@ -680,6 +702,9 @@ analyzer-version = 1 input-imports = [ "github.com/DATA-DOG/go-sqlmock", + "github.com/InVisionApp/go-health", + "github.com/InVisionApp/go-health/checkers", + "github.com/InVisionApp/go-logger/shims/logrus", "github.com/benjamintf1/unmarshalledmatchers", "github.com/cloudfoundry-community/go-cfenv", "github.com/coreos/go-oidc", diff --git a/Gopkg.toml b/Gopkg.toml index 9a361e541..4b182bedb 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -108,4 +108,8 @@ # Refer to issue https://github.com/golang/dep/issues/1799 [[override]] name = "gopkg.in/fsnotify.v1" -source = "https://github.com/fsnotify/fsnotify.git" \ No newline at end of file +source = "https://github.com/fsnotify/fsnotify.git" + +[[constraint]] + name = "github.com/InVisionApp/go-health" + version = "v2.1.0" \ No newline at end of file diff --git a/api/healthcheck/composite_indicator.go b/api/healthcheck/composite_indicator.go deleted file mode 100644 index 1e2a9962c..000000000 --- a/api/healthcheck/composite_indicator.go +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2018 The Service Manager Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package healthcheck - -import ( - "strings" - - "github.com/Peripli/service-manager/pkg/health" -) - -const defaultCompositeName = "composite" - -// compositeIndicator aggregates multiple health indicators and provides one detailed health -type compositeIndicator struct { - aggregationPolicy health.AggregationPolicy - indicators []health.Indicator -} - -// newCompositeIndicator returns a new compositeIndicator for the provided health indicators -func newCompositeIndicator(indicators []health.Indicator, aggregator health.AggregationPolicy) health.Indicator { - return &compositeIndicator{ - aggregationPolicy: aggregator, - indicators: indicators, - } -} - -// Name returns the name of the compositeIndicator -func (i *compositeIndicator) Name() string { - if len(i.indicators) == 0 { - return defaultCompositeName - } - return aggregateIndicatorNames(i.indicators) -} - -// Health returns the aggregated health of all health indicators -func (i *compositeIndicator) Health() *health.Health { - healths := make(map[string]*health.Health) - for _, indicator := range i.indicators { - healths[indicator.Name()] = indicator.Health() - } - return i.aggregationPolicy.Apply(healths) -} - -func aggregateIndicatorNames(indicators []health.Indicator) string { - indicatorsCnt := len(indicators) - builder := strings.Builder{} - for index, indicator := range indicators { - builder.WriteString(indicator.Name()) - if index < indicatorsCnt-1 { - builder.WriteString(", ") - } - } - return builder.String() -} diff --git a/api/healthcheck/composite_indicator_test.go b/api/healthcheck/composite_indicator_test.go deleted file mode 100644 index d27df5259..000000000 --- a/api/healthcheck/composite_indicator_test.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2018 The Service Manager Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package healthcheck - -import ( - "testing" - - "github.com/Peripli/service-manager/pkg/health/healthfakes" - - "github.com/Peripli/service-manager/pkg/health" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestNewCompositeIndicator(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Healthcheck Suite") -} - -var _ = Describe("Healthcheck Composite indicator", func() { - - Context("New Composite indicator", func() { - When("No indicators are provided ", func() { - It("Has default name", func() { - indicator := newCompositeIndicator(nil, nil) - Expect(indicator.Name()).To(Equal(defaultCompositeName)) - }) - }) - - When("Has indicators", func() { - It("Has the name of the indicator", func() { - fakeIndicator1 := &healthfakes.FakeIndicator{} - fakeIndicator1.NameReturns("fake1") - - fakeIndicator2 := &healthfakes.FakeIndicator{} - fakeIndicator2.NameReturns("fake2") - indicators := []health.Indicator{fakeIndicator1, fakeIndicator2} - indicator := newCompositeIndicator(indicators, nil) - - Expect(indicator.Name()).To(Equal(aggregateIndicatorNames(indicators))) - }) - }) - }) - - When("Checking health", func() { - Context("With empty indicators", func() { - It("Returns unknown status", func() { - indicator := newCompositeIndicator(nil, &health.DefaultAggregationPolicy{}) - h := indicator.Health() - Expect(h.Status).To(Equal(health.StatusUnknown)) - Expect(h.Details["error"]).ToNot(BeNil()) - }) - }) - - Context("With provided indicators", func() { - It("Aggregates the healths", func() { - testIndicator := &healthfakes.FakeIndicator{} - testIndicator.HealthReturns(health.New().Up()) - testIndicator.NameReturns("fake") - - aggregationPolicy := &healthfakes.FakeAggregationPolicy{} - defaultAggregationPolicy := &health.DefaultAggregationPolicy{} - aggregationPolicy.ApplyStub = defaultAggregationPolicy.Apply - - indicator := newCompositeIndicator([]health.Indicator{testIndicator}, aggregationPolicy) - invocationsCnt := aggregationPolicy.ApplyCallCount() - health := indicator.Health() - Expect(aggregationPolicy.ApplyCallCount()).To(Equal(invocationsCnt + 1)) - Expect(health.Details[testIndicator.Name()]).ToNot(BeNil()) - }) - }) - }) -}) diff --git a/api/healthcheck/healthcheck_controller.go b/api/healthcheck/healthcheck_controller.go index 90f7ddcbf..9e55f611e 100644 --- a/api/healthcheck/healthcheck_controller.go +++ b/api/healthcheck/healthcheck_controller.go @@ -17,23 +17,26 @@ package healthcheck import ( + h "github.com/InVisionApp/go-health" + "github.com/Peripli/service-manager/pkg/health" "github.com/Peripli/service-manager/pkg/util" "net/http" - "github.com/Peripli/service-manager/pkg/health" "github.com/Peripli/service-manager/pkg/log" "github.com/Peripli/service-manager/pkg/web" ) // controller platform controller type controller struct { - indicator health.Indicator + health h.IHealth + thresholds map[string]int64 } -// NewController returns a new healthcheck controller with the given indicators and aggregation policy -func NewController(indicators []health.Indicator, aggregator health.AggregationPolicy) web.Controller { +// NewController returns a new healthcheck controller with the given health and thresholds +func NewController(health h.IHealth, thresholds map[string]int64) web.Controller { return &controller{ - indicator: newCompositeIndicator(indicators, aggregator), + health: health, + thresholds: thresholds, } } @@ -41,8 +44,9 @@ func NewController(indicators []health.Indicator, aggregator health.AggregationP func (c *controller) healthCheck(r *web.Request) (*web.Response, error) { ctx := r.Context() logger := log.C(ctx) - logger.Debugf("Performing health check with %s...", c.indicator.Name()) - healthResult := c.indicator.Health() + logger.Debugf("Performing health check...") + healthState, _, _ := c.health.State() + healthResult := c.aggregate(healthState) var status int if healthResult.Status == health.StatusUp { status = http.StatusOK @@ -51,3 +55,33 @@ func (c *controller) healthCheck(r *web.Request) (*web.Response, error) { } return util.NewJSONResponse(status, healthResult) } + +func (c *controller) aggregate(overallState map[string]h.State) *health.Health { + if len(overallState) == 0 { + return health.New().WithStatus(health.StatusUp) + } + overallStatus := health.StatusUp + for name, state := range overallState { + if state.Fatal && state.ContiguousFailures >= c.thresholds[name] { + overallStatus = health.StatusDown + break + } + } + details := make(map[string]interface{}) + for name, state := range overallState { + state.Status = convertStatus(state.Status) + details[name] = state + } + return health.New().WithStatus(overallStatus).WithDetails(details) +} + +func convertStatus(status string) string { + switch status { + case "ok": + return string(health.StatusUp) + case "failed": + return string(health.StatusDown) + default: + return string(health.StatusUnknown) + } +} diff --git a/api/healthcheck/healthcheck_controller_test.go b/api/healthcheck/healthcheck_controller_test.go index 6d39f3a3c..fc353d341 100644 --- a/api/healthcheck/healthcheck_controller_test.go +++ b/api/healthcheck/healthcheck_controller_test.go @@ -17,14 +17,11 @@ package healthcheck import ( "fmt" - "net/http" - "testing" - - "github.com/Peripli/service-manager/pkg/health/healthfakes" - + h "github.com/InVisionApp/go-health" "github.com/Peripli/service-manager/pkg/health" - "github.com/Peripli/service-manager/pkg/web" + "net/http" + "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -55,24 +52,130 @@ var _ = Describe("Healthcheck controller", func() { }) }) - When("health returns unknown", func() { - It("should respond with 503", func() { - assertResponse(health.StatusUnknown, http.StatusServiceUnavailable) - }) - }) - When("health returns up", func() { It("should respond with 200", func() { assertResponse(health.StatusUp, http.StatusOK) }) }) }) + + Describe("aggregation", func() { + var c *controller + var healths map[string]h.State + var thresholds map[string]int64 + + BeforeEach(func() { + healths = map[string]h.State{ + "test1": {Status: "ok"}, + "test2": {Status: "ok"}, + } + thresholds = map[string]int64{ + "test1": 3, + "test2": 3, + } + c = &controller{ + health: HealthFake{}, + thresholds: thresholds, + } + }) + + When("No healths are provided", func() { + It("Returns UP", func() { + aggregatedHealth := c.aggregate(nil) + Expect(aggregatedHealth.Status).To(Equal(health.StatusUp)) + }) + }) + + When("At least one health is DOWN more than threshold times and is Fatal", func() { + It("Returns DOWN", func() { + healths["test3"] = h.State{Status: "failed", Fatal: true, ContiguousFailures: 4} + c.thresholds["test3"] = 3 + aggregatedHealth := c.aggregate(healths) + Expect(aggregatedHealth.Status).To(Equal(health.StatusDown)) + }) + }) + + When("At least one health is DOWN and is not Fatal", func() { + It("Returns UP", func() { + healths["test3"] = h.State{Status: "failed", Fatal: false, ContiguousFailures: 4} + aggregatedHealth := c.aggregate(healths) + Expect(aggregatedHealth.Status).To(Equal(health.StatusUp)) + }) + }) + + When("There is DOWN healths but not more than threshold times in a row", func() { + It("Returns UP", func() { + healths["test3"] = h.State{Status: "failed"} + c.thresholds["test3"] = 3 + aggregatedHealth := c.aggregate(healths) + Expect(aggregatedHealth.Status).To(Equal(health.StatusUp)) + }) + }) + + When("All healths are UP", func() { + It("Returns UP", func() { + aggregatedHealth := c.aggregate(healths) + Expect(aggregatedHealth.Status).To(Equal(health.StatusUp)) + }) + }) + + When("Aggregating healths", func() { + It("Includes them as overall details", func() { + aggregatedHealth := c.aggregate(healths) + for name, h := range healths { + h.Status = convertStatus(h.Status) + Expect(aggregatedHealth.Details[name]).To(Equal(h)) + } + }) + }) + }) }) func createController(status health.Status) *controller { - indicator := &healthfakes.FakeIndicator{} - indicator.HealthReturns(health.New().WithStatus(status)) + stringStatus := "ok" + var contiguousFailures int64 = 0 + if status == health.StatusDown { + stringStatus = "failed" + contiguousFailures = 1 + } + return &controller{ - indicator: indicator, + health: HealthFake{ + state: map[string]h.State{ + "test1": {Status: stringStatus, Fatal: true, ContiguousFailures: contiguousFailures}, + }, + }, + thresholds: map[string]int64{ + "test1": 1, + }, } } + +type HealthFake struct { + state map[string]h.State + failed bool + err error +} + +func (hf HealthFake) AddChecks(cfgs []*h.Config) error { + return nil +} + +func (hf HealthFake) AddCheck(cfg *h.Config) error { + return nil +} + +func (hf HealthFake) Start() error { + return nil +} + +func (hf HealthFake) Stop() error { + return nil +} + +func (hf HealthFake) State() (map[string]h.State, bool, error) { + return hf.state, hf.failed, hf.err +} +func (hf HealthFake) Failed() bool { + return hf.failed +} diff --git a/config/config.go b/config/config.go index f9e28f561..2d156f401 100644 --- a/config/config.go +++ b/config/config.go @@ -23,6 +23,7 @@ import ( "github.com/Peripli/service-manager/api" "github.com/Peripli/service-manager/pkg/env" + "github.com/Peripli/service-manager/pkg/health" "github.com/Peripli/service-manager/pkg/log" "github.com/Peripli/service-manager/pkg/server" "github.com/Peripli/service-manager/pkg/ws" @@ -38,6 +39,7 @@ type Settings struct { API *api.Settings WebSocket *ws.Settings HTTPClient *httpclient.Settings + Health *health.Settings } // AddPFlags adds the SM config flags to the provided flag set @@ -55,6 +57,7 @@ func DefaultSettings() *Settings { API: api.DefaultSettings(), WebSocket: ws.DefaultSettings(), HTTPClient: httpclient.DefaultSettings(), + Health: health.DefaultSettings(), } } @@ -82,7 +85,7 @@ func NewForEnv(env env.Environment) (*Settings, error) { func (c *Settings) Validate() error { validatable := []interface { Validate() error - }{c.Server, c.Storage, c.Log, c.API, c.WebSocket} + }{c.Server, c.Storage, c.Log, c.Health, c.API, c.WebSocket} for _, item := range validatable { if err := item.Validate(); err != nil { diff --git a/config/config_test.go b/config/config_test.go index ac4a24a7e..a4f32fd3f 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -18,6 +18,7 @@ package config_test import ( "fmt" + "github.com/Peripli/service-manager/pkg/health" "testing" "time" @@ -44,11 +45,25 @@ var _ = Describe("config", func() { ) Describe("Validate", func() { + var fatal bool + var failuresThreshold int64 + var interval time.Duration + assertErrorDuringValidate := func() { err = config.Validate() Expect(err).To(HaveOccurred()) } + registerIndicatorSettings := func() { + indicatorSettings := &health.IndicatorSettings{ + Fatal: fatal, + FailuresThreshold: failuresThreshold, + Interval: interval, + } + + config.Health.Indicators["test"] = indicatorSettings + } + BeforeEach(func() { config = cfg.DefaultSettings() config.Storage.URI = "postgres://postgres:postgres@localhost:5555/postgres?sslmode=disable" @@ -56,6 +71,65 @@ var _ = Describe("config", func() { config.API.ClientID = "sm" config.API.SkipSSLValidation = true config.Storage.EncryptionKey = "ejHjRNHbS0NaqARSRvnweVV9zcmhQEa8" + + fatal = true + failuresThreshold = 1 + interval = 30 * time.Second + }) + + Context("health indicator with negative threshold", func() { + It("should be considered invalid", func() { + failuresThreshold = -1 + registerIndicatorSettings() + assertErrorDuringValidate() + }) + }) + + Context("health indicator with 0 threshold", func() { + It("should be considered invalid if it is fatal", func() { + failuresThreshold = 0 + registerIndicatorSettings() + assertErrorDuringValidate() + }) + }) + + Context("health indicator with 0 threshold", func() { + It("should be considered valid if it is not fatal", func() { + fatal = false + failuresThreshold = 0 + registerIndicatorSettings() + err := config.Validate() + Expect(err).ShouldNot(HaveOccurred()) + }) + }) + + Context("health indicator with positive threshold", func() { + It("should be considered invalid if it is not fatal", func() { + fatal = false + failuresThreshold = 3 + registerIndicatorSettings() + assertErrorDuringValidate() + }) + }) + + Context("health indicator with interval less than 30", func() { + It("should be considered invalid", func() { + interval = 15 * time.Second + registerIndicatorSettings() + assertErrorDuringValidate() + }) + }) + + Context("health indicator with positive threshold and interval >= 30", func() { + It("should be considered valid", func() { + interval = 30 * time.Second + failuresThreshold = 3 + registerIndicatorSettings() + + err := config.Validate() + + Expect(err).ShouldNot(HaveOccurred()) + }) }) Context("when config is valid", func() { diff --git a/pkg/health/aggregation_policy.go b/pkg/health/aggregation_policy.go deleted file mode 100644 index 3fba18ec1..000000000 --- a/pkg/health/aggregation_policy.go +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2018 The Service Manager Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package health - -// DefaultAggregationPolicy aggregates the healths by constructing a new Health based on the given -// where the overall health status is negative if one of the healths is negative and positive if all are positive -type DefaultAggregationPolicy struct { -} - -// Apply aggregates the given healths -func (*DefaultAggregationPolicy) Apply(healths map[string]*Health) *Health { - if len(healths) == 0 { - return New().WithDetail("error", "no health indicators registered").Unknown() - } - overallStatus := StatusUp - for _, health := range healths { - if health.Status == StatusDown { - overallStatus = StatusDown - break - } - } - details := make(map[string]interface{}) - for k, v := range healths { - details[k] = v - } - return New().WithStatus(overallStatus).WithDetails(details) -} diff --git a/pkg/health/aggregation_policy_test.go b/pkg/health/aggregation_policy_test.go deleted file mode 100644 index da9c0d365..000000000 --- a/pkg/health/aggregation_policy_test.go +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2018 The Service Manager Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package health - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var _ = Describe("Healthcheck AggregationPolicy", func() { - - aggregationPolicy := &DefaultAggregationPolicy{} - var healths map[string]*Health - - BeforeEach(func() { - healths = map[string]*Health{ - "test1": New().Up(), - "test2": New().Up(), - } - }) - - When("No healths are provided", func() { - It("Returns UNKNOWN and an error detail", func() { - aggregatedHealth := aggregationPolicy.Apply(nil) - Expect(aggregatedHealth.Status).To(Equal(StatusUnknown)) - Expect(aggregatedHealth.Details["error"]).ToNot(BeNil()) - }) - }) - - When("At least one health is DOWN", func() { - It("Returns DOWN", func() { - healths["test3"] = New().Down() - aggregatedHealth := aggregationPolicy.Apply(healths) - Expect(aggregatedHealth.Status).To(Equal(StatusDown)) - }) - }) - - When("All healths are UP", func() { - It("Returns UP", func() { - aggregatedHealth := aggregationPolicy.Apply(healths) - Expect(aggregatedHealth.Status).To(Equal(StatusUp)) - }) - }) - - When("Aggregating healths", func() { - It("Includes them as overall details", func() { - aggregatedHealth := aggregationPolicy.Apply(healths) - for name, h := range healths { - Expect(aggregatedHealth.Details[name]).To(Equal(h)) - } - }) - }) -}) diff --git a/pkg/health/healthfakes/fake_aggregation_policy.go b/pkg/health/healthfakes/fake_aggregation_policy.go deleted file mode 100644 index 45a1f9504..000000000 --- a/pkg/health/healthfakes/fake_aggregation_policy.go +++ /dev/null @@ -1,110 +0,0 @@ -// Code generated by counterfeiter. DO NOT EDIT. -package healthfakes - -import ( - "sync" - - "github.com/Peripli/service-manager/pkg/health" -) - -type FakeAggregationPolicy struct { - ApplyStub func(map[string]*health.Health) *health.Health - applyMutex sync.RWMutex - applyArgsForCall []struct { - arg1 map[string]*health.Health - } - applyReturns struct { - result1 *health.Health - } - applyReturnsOnCall map[int]struct { - result1 *health.Health - } - invocations map[string][][]interface{} - invocationsMutex sync.RWMutex -} - -func (fake *FakeAggregationPolicy) Apply(arg1 map[string]*health.Health) *health.Health { - fake.applyMutex.Lock() - ret, specificReturn := fake.applyReturnsOnCall[len(fake.applyArgsForCall)] - fake.applyArgsForCall = append(fake.applyArgsForCall, struct { - arg1 map[string]*health.Health - }{arg1}) - fake.recordInvocation("Apply", []interface{}{arg1}) - fake.applyMutex.Unlock() - if fake.ApplyStub != nil { - return fake.ApplyStub(arg1) - } - if specificReturn { - return ret.result1 - } - fakeReturns := fake.applyReturns - return fakeReturns.result1 -} - -func (fake *FakeAggregationPolicy) ApplyCallCount() int { - fake.applyMutex.RLock() - defer fake.applyMutex.RUnlock() - return len(fake.applyArgsForCall) -} - -func (fake *FakeAggregationPolicy) ApplyCalls(stub func(map[string]*health.Health) *health.Health) { - fake.applyMutex.Lock() - defer fake.applyMutex.Unlock() - fake.ApplyStub = stub -} - -func (fake *FakeAggregationPolicy) ApplyArgsForCall(i int) map[string]*health.Health { - fake.applyMutex.RLock() - defer fake.applyMutex.RUnlock() - argsForCall := fake.applyArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *FakeAggregationPolicy) ApplyReturns(result1 *health.Health) { - fake.applyMutex.Lock() - defer fake.applyMutex.Unlock() - fake.ApplyStub = nil - fake.applyReturns = struct { - result1 *health.Health - }{result1} -} - -func (fake *FakeAggregationPolicy) ApplyReturnsOnCall(i int, result1 *health.Health) { - fake.applyMutex.Lock() - defer fake.applyMutex.Unlock() - fake.ApplyStub = nil - if fake.applyReturnsOnCall == nil { - fake.applyReturnsOnCall = make(map[int]struct { - result1 *health.Health - }) - } - fake.applyReturnsOnCall[i] = struct { - result1 *health.Health - }{result1} -} - -func (fake *FakeAggregationPolicy) Invocations() map[string][][]interface{} { - fake.invocationsMutex.RLock() - defer fake.invocationsMutex.RUnlock() - fake.applyMutex.RLock() - defer fake.applyMutex.RUnlock() - copiedInvocations := map[string][][]interface{}{} - for key, value := range fake.invocations { - copiedInvocations[key] = value - } - return copiedInvocations -} - -func (fake *FakeAggregationPolicy) recordInvocation(key string, args []interface{}) { - fake.invocationsMutex.Lock() - defer fake.invocationsMutex.Unlock() - if fake.invocations == nil { - fake.invocations = map[string][][]interface{}{} - } - if fake.invocations[key] == nil { - fake.invocations[key] = [][]interface{}{} - } - fake.invocations[key] = append(fake.invocations[key], args) -} - -var _ health.AggregationPolicy = new(FakeAggregationPolicy) diff --git a/pkg/health/healthfakes/fake_indicator.go b/pkg/health/healthfakes/fake_indicator.go index 5959dc549..5636134e2 100644 --- a/pkg/health/healthfakes/fake_indicator.go +++ b/pkg/health/healthfakes/fake_indicator.go @@ -8,16 +8,6 @@ import ( ) type FakeIndicator struct { - HealthStub func() *health.Health - healthMutex sync.RWMutex - healthArgsForCall []struct { - } - healthReturns struct { - result1 *health.Health - } - healthReturnsOnCall map[int]struct { - result1 *health.Health - } NameStub func() string nameMutex sync.RWMutex nameArgsForCall []struct { @@ -28,60 +18,20 @@ type FakeIndicator struct { nameReturnsOnCall map[int]struct { result1 string } - invocations map[string][][]interface{} - invocationsMutex sync.RWMutex -} - -func (fake *FakeIndicator) Health() *health.Health { - fake.healthMutex.Lock() - ret, specificReturn := fake.healthReturnsOnCall[len(fake.healthArgsForCall)] - fake.healthArgsForCall = append(fake.healthArgsForCall, struct { - }{}) - fake.recordInvocation("Health", []interface{}{}) - fake.healthMutex.Unlock() - if fake.HealthStub != nil { - return fake.HealthStub() + StatusStub func() (interface{}, error) + statusMutex sync.RWMutex + statusArgsForCall []struct { } - if specificReturn { - return ret.result1 + statusReturns struct { + result1 interface{} + result2 error } - fakeReturns := fake.healthReturns - return fakeReturns.result1 -} - -func (fake *FakeIndicator) HealthCallCount() int { - fake.healthMutex.RLock() - defer fake.healthMutex.RUnlock() - return len(fake.healthArgsForCall) -} - -func (fake *FakeIndicator) HealthCalls(stub func() *health.Health) { - fake.healthMutex.Lock() - defer fake.healthMutex.Unlock() - fake.HealthStub = stub -} - -func (fake *FakeIndicator) HealthReturns(result1 *health.Health) { - fake.healthMutex.Lock() - defer fake.healthMutex.Unlock() - fake.HealthStub = nil - fake.healthReturns = struct { - result1 *health.Health - }{result1} -} - -func (fake *FakeIndicator) HealthReturnsOnCall(i int, result1 *health.Health) { - fake.healthMutex.Lock() - defer fake.healthMutex.Unlock() - fake.HealthStub = nil - if fake.healthReturnsOnCall == nil { - fake.healthReturnsOnCall = make(map[int]struct { - result1 *health.Health - }) + statusReturnsOnCall map[int]struct { + result1 interface{} + result2 error } - fake.healthReturnsOnCall[i] = struct { - result1 *health.Health - }{result1} + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex } func (fake *FakeIndicator) Name() string { @@ -136,13 +86,68 @@ func (fake *FakeIndicator) NameReturnsOnCall(i int, result1 string) { }{result1} } +func (fake *FakeIndicator) Status() (interface{}, error) { + fake.statusMutex.Lock() + ret, specificReturn := fake.statusReturnsOnCall[len(fake.statusArgsForCall)] + fake.statusArgsForCall = append(fake.statusArgsForCall, struct { + }{}) + fake.recordInvocation("Status", []interface{}{}) + fake.statusMutex.Unlock() + if fake.StatusStub != nil { + return fake.StatusStub() + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.statusReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeIndicator) StatusCallCount() int { + fake.statusMutex.RLock() + defer fake.statusMutex.RUnlock() + return len(fake.statusArgsForCall) +} + +func (fake *FakeIndicator) StatusCalls(stub func() (interface{}, error)) { + fake.statusMutex.Lock() + defer fake.statusMutex.Unlock() + fake.StatusStub = stub +} + +func (fake *FakeIndicator) StatusReturns(result1 interface{}, result2 error) { + fake.statusMutex.Lock() + defer fake.statusMutex.Unlock() + fake.StatusStub = nil + fake.statusReturns = struct { + result1 interface{} + result2 error + }{result1, result2} +} + +func (fake *FakeIndicator) StatusReturnsOnCall(i int, result1 interface{}, result2 error) { + fake.statusMutex.Lock() + defer fake.statusMutex.Unlock() + fake.StatusStub = nil + if fake.statusReturnsOnCall == nil { + fake.statusReturnsOnCall = make(map[int]struct { + result1 interface{} + result2 error + }) + } + fake.statusReturnsOnCall[i] = struct { + result1 interface{} + result2 error + }{result1, result2} +} + func (fake *FakeIndicator) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.healthMutex.RLock() - defer fake.healthMutex.RUnlock() fake.nameMutex.RLock() defer fake.nameMutex.RUnlock() + fake.statusMutex.RLock() + defer fake.statusMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/pkg/health/ping.go b/pkg/health/ping.go deleted file mode 100644 index c145376ea..000000000 --- a/pkg/health/ping.go +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2018 The Service Manager Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package health - -// pingIndicator is a default indicator that always returns up -type pingIndicator struct { -} - -func (*pingIndicator) Name() string { - return "ping" -} - -func (*pingIndicator) Health() *Health { - return New().Up() -} diff --git a/pkg/health/registry_test.go b/pkg/health/registry_test.go index faf4d8525..1bdbbc203 100644 --- a/pkg/health/registry_test.go +++ b/pkg/health/registry_test.go @@ -30,27 +30,13 @@ var _ = Describe("Healthcheck Registry", func() { }) When("Constructing default registry", func() { - It("Has ping indicator and default aggregation policy", func() { + It("Has empty indicators", func() { indicators := registry.HealthIndicators - Expect(indicators).To(ConsistOf(&pingIndicator{})) - - policy := registry.HealthAggregationPolicy - Expect(policy).To(BeAssignableToTypeOf(&DefaultAggregationPolicy{})) - }) - }) - - Context("Register aggregation policy", func() { - It("Overrides the previous", func() { - policy := registry.HealthAggregationPolicy - Expect(policy).To(BeAssignableToTypeOf(&DefaultAggregationPolicy{})) - - registry.HealthAggregationPolicy = &testAggregationPolicy{} - policy = registry.HealthAggregationPolicy - Expect(policy).To(BeAssignableToTypeOf(&testAggregationPolicy{})) + Expect(len(indicators)).To(Equal(0)) }) }) - Context("Register health indicator", func() { + When("Register health indicator", func() { It("Adds a new indicator", func() { preAddIndicators := registry.HealthIndicators @@ -65,20 +51,13 @@ var _ = Describe("Healthcheck Registry", func() { }) }) -type testAggregationPolicy struct { -} - -func (*testAggregationPolicy) Apply(healths map[string]*Health) *Health { - return New().Up() -} - type testIndicator struct { } -func (*testIndicator) Name() string { +func (i *testIndicator) Name() string { return "test" } -func (*testIndicator) Health() *Health { - return New().Up() +func (i *testIndicator) Status() (interface{}, error) { + return nil, nil } diff --git a/pkg/health/types.go b/pkg/health/types.go index 6ed339b17..7c1dfba13 100644 --- a/pkg/health/types.go +++ b/pkg/health/types.go @@ -16,6 +16,66 @@ package health +import ( + "fmt" + "github.com/InVisionApp/go-health" + "github.com/Peripli/service-manager/pkg/log" + "time" +) + +// Settings type to be loaded from the environment +type Settings struct { + Indicators map[string]*IndicatorSettings `mapstructure:"indicators,omitempty"` +} + +// DefaultSettings returns default values for health settings +func DefaultSettings() *Settings { + emptySettings := make(map[string]*IndicatorSettings) + return &Settings{ + Indicators: emptySettings, + } +} + +// Validate validates health settings +func (s *Settings) Validate() error { + for _, v := range s.Indicators { + if err := v.Validate(); err != nil { + return err + } + } + return nil +} + +// IndicatorSettings type to be loaded from the environment +type IndicatorSettings struct { + Fatal bool `mapstructure:"fatal" description:"if the indicator affects the overall status, if false not failures_threshold expected"` + FailuresThreshold int64 `mapstructure:"failures_threshold" description:"number of failures in a row that will affect overall status"` + Interval time.Duration `mapstructure:"interval" description:"time between health checks of components"` +} + +// DefaultIndicatorSettings returns default values for indicator settings +func DefaultIndicatorSettings() *IndicatorSettings { + return &IndicatorSettings{ + Fatal: true, + FailuresThreshold: 3, + Interval: 60 * time.Second, + } +} + +// Validate validates indicator settings +func (is *IndicatorSettings) Validate() error { + if !is.Fatal && is.FailuresThreshold != 0 { + return fmt.Errorf("validate Settings: FailuresThreshold not applicable for non-fatal indicators") + } + if is.Fatal && is.FailuresThreshold <= 0 { + return fmt.Errorf("validate Settings: FailuresThreshold must be > 0 for fatal indicators") + } + if is.Interval < 30*time.Second { + return fmt.Errorf("validate Settings: Minimum interval is 30 seconds") + } + return nil +} + // Status represents the overall health status of a component type Status string @@ -28,6 +88,16 @@ const ( StatusUnknown Status = "UNKNOWN" ) +type StatusListener struct{} + +func (sl *StatusListener) HealthCheckFailed(state *health.State) { + log.D().Errorf("Health check for %v failed with: %v", state.Name, state.Err) +} + +func (sl *StatusListener) HealthCheckRecovered(state *health.State, numberOfFailures int64, unavailableDuration float64) { + log.D().Infof("Health check for %v recovered after %v failures and was unavailable for %v seconds roughly", state.Name, numberOfFailures, unavailableDuration) +} + // Health contains information about the health of a component. type Health struct { Status Status `json:"status"` @@ -60,24 +130,6 @@ func (h *Health) WithDetail(key string, val interface{}) *Health { return h } -// Up sets the health status to up -func (h *Health) Up() *Health { - h.Status = StatusUp - return h -} - -// Down sets the health status to down -func (h *Health) Down() *Health { - h.Status = StatusDown - return h -} - -// Unknown sets the health status to unknown -func (h *Health) Unknown() *Health { - h.Status = StatusUnknown - return h -} - // WithDetails adds the given details to the health func (h *Health) WithDetails(details map[string]interface{}) *Health { for k, v := range details { @@ -91,30 +143,20 @@ func (h *Health) WithDetails(details map[string]interface{}) *Health { type Indicator interface { // Name returns the name of the component Name() string - // Health returns the health of the component - Health() *Health -} -// AggregationPolicy is an interface to provide aggregated health information -//go:generate counterfeiter . AggregationPolicy -type AggregationPolicy interface { - // Apply processes the given healths to build a single health - Apply(healths map[string]*Health) *Health + // Status returns the health information of the component + Status() (interface{}, error) } -// NewDefaultRegistry returns a default health registry with a single ping indicator and a default aggregation policy +// NewDefaultRegistry returns a default empty health registry func NewDefaultRegistry() *Registry { return &Registry{ - HealthIndicators: []Indicator{&pingIndicator{}}, - HealthAggregationPolicy: &DefaultAggregationPolicy{}, + HealthIndicators: make([]Indicator, 0), } } -// Registry is an interface to store and fetch health indicators +// Registry is a struct to store health indicators type Registry struct { // HealthIndicators are the currently registered health indicators HealthIndicators []Indicator - - // HealthAggregationPolicy is the registered health aggregationPolicy - HealthAggregationPolicy AggregationPolicy } diff --git a/pkg/sm/sm.go b/pkg/sm/sm.go index 4cf1e70ed..c9b946af3 100644 --- a/pkg/sm/sm.go +++ b/pkg/sm/sm.go @@ -21,12 +21,14 @@ import ( "crypto/tls" "database/sql" "fmt" + h "github.com/InVisionApp/go-health" + l "github.com/InVisionApp/go-logger/shims/logrus" + "github.com/Peripli/service-manager/api/osb" + "github.com/Peripli/service-manager/pkg/health" "net" "net/http" "sync" - "github.com/Peripli/service-manager/api/osb" - "github.com/Peripli/service-manager/storage/catalog" "github.com/Peripli/service-manager/pkg/security" @@ -132,7 +134,12 @@ func New(ctx context.Context, cancel context.CancelFunc, cfg *config.Settings) ( return nil, fmt.Errorf("error creating core api: %s", err) } - API.HealthIndicators = append(API.HealthIndicators, &storage.HealthIndicator{Pinger: storage.PingFunc(smStorage.Ping)}) + storageHealthIndicator, err := storage.NewSQLHealthIndicator(storage.PingFunc(smStorage.PingContext)) + if err != nil { + return nil, fmt.Errorf("error creating storage health indicator: %s", err) + } + + API.HealthIndicators = append(API.HealthIndicators, storageHealthIndicator) notificationCleaner := &storage.NotificationCleaner{ Storage: interceptableRepository, @@ -174,9 +181,11 @@ func New(ctx context.Context, cancel context.CancelFunc, cfg *config.Settings) ( // Build builds the Service Manager func (smb *ServiceManagerBuilder) Build() *ServiceManager { - // setup server and add relevant global middleware - smb.installHealth() + if err := smb.installHealth(); err != nil { + log.C(smb.ctx).Panic(err) + } + // setup server and add relevant global middleware srv := server.New(smb.cfg.Server, smb.API) srv.Use(filters.NewRecoveryMiddleware()) @@ -189,10 +198,46 @@ func (smb *ServiceManagerBuilder) Build() *ServiceManager { } } -func (smb *ServiceManagerBuilder) installHealth() { - if len(smb.HealthIndicators) > 0 { - smb.RegisterControllers(healthcheck.NewController(smb.HealthIndicators, smb.HealthAggregationPolicy)) +func (smb *ServiceManagerBuilder) installHealth() error { + healthz := h.New() + logger := log.C(smb.ctx).Logger + + healthz.Logger = l.New(logger) + healthz.StatusListener = &health.StatusListener{} + + thresholds := make(map[string]int64) + + for _, indicator := range smb.HealthIndicators { + settings, ok := smb.cfg.Health.Indicators[indicator.Name()] + if !ok { + settings = health.DefaultIndicatorSettings() + } + if err := healthz.AddCheck(&h.Config{ + Name: indicator.Name(), + Checker: indicator, + Interval: settings.Interval, + Fatal: settings.Fatal, + }); err != nil { + return err + } + thresholds[indicator.Name()] = settings.FailuresThreshold } + + smb.RegisterControllers(healthcheck.NewController(healthz, thresholds)) + + if err := healthz.Start(); err != nil { + return err + } + + util.StartInWaitGroupWithContext(smb.ctx, func(c context.Context) { + <-c.Done() + log.C(c).Debug("Context cancelled. Stopping health checks...") + if err := healthz.Stop(); err != nil { + log.C(c).Error(err) + } + }, smb.wg) + + return nil } // Run starts the Service Manager diff --git a/storage/healthcheck.go b/storage/healthcheck.go index 3d70320ca..d74d07396 100644 --- a/storage/healthcheck.go +++ b/storage/healthcheck.go @@ -16,24 +16,34 @@ package storage -import "github.com/Peripli/service-manager/pkg/health" +import ( + "github.com/InVisionApp/go-health/checkers" + "github.com/Peripli/service-manager/pkg/health" +) -// HealthIndicator returns a new indicator for the storage -type HealthIndicator struct { - Pinger Pinger +// NewSQLHealthIndicator returns new health indicator for sql storage given a ping function +func NewSQLHealthIndicator(pingFunc PingFunc) (health.Indicator, error) { + sqlConfig := &checkers.SQLConfig{ + Pinger: pingFunc, + } + sqlChecker, err := checkers.NewSQL(sqlConfig) + if err != nil { + return nil, err + } + + indicator := &SQLHealthIndicator{ + SQL: sqlChecker, + } + + return indicator, nil } -// Name returns the name of the storage component -func (i *HealthIndicator) Name() string { - return "storage" +// SQLHealthIndicator returns a new indicator for SQL storage +type SQLHealthIndicator struct { + *checkers.SQL } -// Health returns the health of the storage component -func (i *HealthIndicator) Health() *health.Health { - err := i.Pinger.Ping() - healthz := health.New() - if err != nil { - return healthz.WithError(err).WithDetail("message", "TransactionalRepository ping failed") - } - return healthz.Up() +// Name returns the name of the storage component +func (i *SQLHealthIndicator) Name() string { + return "storage" } diff --git a/storage/healthcheck_test.go b/storage/healthcheck_test.go index 4d6eb8490..fcc4da2ba 100644 --- a/storage/healthcheck_test.go +++ b/storage/healthcheck_test.go @@ -17,27 +17,24 @@ package storage_test import ( + "context" "fmt" - "github.com/Peripli/service-manager/pkg/health" "github.com/Peripli/service-manager/storage" - "github.com/Peripli/service-manager/storage/storagefakes" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("Healthcheck", func() { - var healthIndicator *storage.HealthIndicator - var pinger *storagefakes.FakePinger + var healthIndicator health.Indicator BeforeEach(func() { - pinger = &storagefakes.FakePinger{} - pinger.PingStub = func() error { + ping := func(ctx context.Context) error { return nil } - healthIndicator = &storage.HealthIndicator{ - Pinger: pinger, - } + var err error + healthIndicator, err = storage.NewSQLHealthIndicator(storage.PingFunc(ping)) + Expect(err).ShouldNot(HaveOccurred()) }) Context("Name", func() { @@ -47,27 +44,25 @@ var _ = Describe("Healthcheck", func() { }) Context("Ping does not return error", func() { - It("Returns health status UP", func() { - healthz := healthIndicator.Health() - Expect(healthz.Status).To(Equal(health.StatusUp)) + It("Status doest not contains error", func() { + _, err := healthIndicator.Status() + Expect(err).ShouldNot(HaveOccurred()) }) }) Context("Ping returns error", func() { expectedError := fmt.Errorf("could not connect to database") BeforeEach(func() { - pinger.PingStub = func() error { + ping := func(ctx context.Context) error { return expectedError } - }) - It("Returns status DOWN", func() { - healthz := healthIndicator.Health() - Expect(healthz.Status).To(Equal(health.StatusDown)) + var err error + healthIndicator, err = storage.NewSQLHealthIndicator(storage.PingFunc(ping)) + Expect(err).ShouldNot(HaveOccurred()) }) It("Contains error", func() { - healthz := healthIndicator.Health() - errorDetails := healthz.Details["error"] - Expect(errorDetails).To(Equal(expectedError)) + _, err := healthIndicator.Status() + Expect(err).Should(HaveOccurred()) + Expect(err).To(Equal(expectedError)) }) - }) }) diff --git a/storage/interfaces.go b/storage/interfaces.go index 508324e42..7eb24b0dd 100644 --- a/storage/interfaces.go +++ b/storage/interfaces.go @@ -138,16 +138,16 @@ type OpenCloser interface { // Pinger allows pinging the storage to check liveliness //go:generate counterfeiter . Pinger type Pinger interface { - // Ping verifies a connection to the database is still alive, establishing a connection if necessary. - Ping() error + // PingContext verifies a connection to the database is still alive, establishing a connection if necessary. + PingContext(context.Context) error } // PingFunc is an adapter that allows to use regular functions as Pinger -type PingFunc func() error +type PingFunc func(context.Context) error -// Ping allows PingFunc to act as a Pinger -func (mf PingFunc) Ping() error { - return mf() +// PingContext allows PingFunc to act as a Pinger +func (mf PingFunc) PingContext(ctx context.Context) error { + return mf(ctx) } type Repository interface { @@ -182,8 +182,8 @@ type TransactionalRepositoryDecorator func(TransactionalRepository) (Transaction //go:generate counterfeiter . Storage type Storage interface { OpenCloser - Pinger TransactionalRepository + Pinger Introduce(entity Entity) } diff --git a/storage/postgres/storage.go b/storage/postgres/storage.go index 9072977f0..3c7681d9b 100644 --- a/storage/postgres/storage.go +++ b/storage/postgres/storage.go @@ -138,7 +138,7 @@ func (ps *Storage) updateSchema(migrationsURL, pgDriverName string) error { return err } -func (ps *Storage) Ping() error { +func (ps *Storage) PingContext(ctx context.Context) error { ps.checkOpen() return ps.state.Get() } diff --git a/storage/postgres/storage_test.go b/storage/postgres/storage_test.go index 5b0a8a351..ac1cecd98 100644 --- a/storage/postgres/storage_test.go +++ b/storage/postgres/storage_test.go @@ -70,7 +70,7 @@ var _ = Describe("Postgres Storage", func() { Describe("Ping", func() { Context("Called with uninitialized db", func() { It("Should panic", func() { - Expect(func() { pgStorage.Ping() }).To(Panic()) + Expect(func() { pgStorage.PingContext(context.Background()) }).To(Panic()) }) }) }) diff --git a/storage/storagefakes/fake_pinger.go b/storage/storagefakes/fake_pinger.go index a8549274c..ea4f34a7e 100644 --- a/storage/storagefakes/fake_pinger.go +++ b/storage/storagefakes/fake_pinger.go @@ -2,74 +2,84 @@ package storagefakes import ( + "context" "sync" "github.com/Peripli/service-manager/storage" ) type FakePinger struct { - PingStub func() error - pingMutex sync.RWMutex - pingArgsForCall []struct { + PingContextStub func(context.Context) error + pingContextMutex sync.RWMutex + pingContextArgsForCall []struct { + arg1 context.Context } - pingReturns struct { + pingContextReturns struct { result1 error } - pingReturnsOnCall map[int]struct { + pingContextReturnsOnCall map[int]struct { result1 error } invocations map[string][][]interface{} invocationsMutex sync.RWMutex } -func (fake *FakePinger) Ping() error { - fake.pingMutex.Lock() - ret, specificReturn := fake.pingReturnsOnCall[len(fake.pingArgsForCall)] - fake.pingArgsForCall = append(fake.pingArgsForCall, struct { - }{}) - fake.recordInvocation("Ping", []interface{}{}) - fake.pingMutex.Unlock() - if fake.PingStub != nil { - return fake.PingStub() +func (fake *FakePinger) PingContext(arg1 context.Context) error { + fake.pingContextMutex.Lock() + ret, specificReturn := fake.pingContextReturnsOnCall[len(fake.pingContextArgsForCall)] + fake.pingContextArgsForCall = append(fake.pingContextArgsForCall, struct { + arg1 context.Context + }{arg1}) + fake.recordInvocation("PingContext", []interface{}{arg1}) + fake.pingContextMutex.Unlock() + if fake.PingContextStub != nil { + return fake.PingContextStub(arg1) } if specificReturn { return ret.result1 } - fakeReturns := fake.pingReturns + fakeReturns := fake.pingContextReturns return fakeReturns.result1 } -func (fake *FakePinger) PingCallCount() int { - fake.pingMutex.RLock() - defer fake.pingMutex.RUnlock() - return len(fake.pingArgsForCall) +func (fake *FakePinger) PingContextCallCount() int { + fake.pingContextMutex.RLock() + defer fake.pingContextMutex.RUnlock() + return len(fake.pingContextArgsForCall) } -func (fake *FakePinger) PingCalls(stub func() error) { - fake.pingMutex.Lock() - defer fake.pingMutex.Unlock() - fake.PingStub = stub +func (fake *FakePinger) PingContextCalls(stub func(context.Context) error) { + fake.pingContextMutex.Lock() + defer fake.pingContextMutex.Unlock() + fake.PingContextStub = stub } -func (fake *FakePinger) PingReturns(result1 error) { - fake.pingMutex.Lock() - defer fake.pingMutex.Unlock() - fake.PingStub = nil - fake.pingReturns = struct { +func (fake *FakePinger) PingContextArgsForCall(i int) context.Context { + fake.pingContextMutex.RLock() + defer fake.pingContextMutex.RUnlock() + argsForCall := fake.pingContextArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakePinger) PingContextReturns(result1 error) { + fake.pingContextMutex.Lock() + defer fake.pingContextMutex.Unlock() + fake.PingContextStub = nil + fake.pingContextReturns = struct { result1 error }{result1} } -func (fake *FakePinger) PingReturnsOnCall(i int, result1 error) { - fake.pingMutex.Lock() - defer fake.pingMutex.Unlock() - fake.PingStub = nil - if fake.pingReturnsOnCall == nil { - fake.pingReturnsOnCall = make(map[int]struct { +func (fake *FakePinger) PingContextReturnsOnCall(i int, result1 error) { + fake.pingContextMutex.Lock() + defer fake.pingContextMutex.Unlock() + fake.PingContextStub = nil + if fake.pingContextReturnsOnCall == nil { + fake.pingContextReturnsOnCall = make(map[int]struct { result1 error }) } - fake.pingReturnsOnCall[i] = struct { + fake.pingContextReturnsOnCall[i] = struct { result1 error }{result1} } @@ -77,8 +87,8 @@ func (fake *FakePinger) PingReturnsOnCall(i int, result1 error) { func (fake *FakePinger) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.pingMutex.RLock() - defer fake.pingMutex.RUnlock() + fake.pingContextMutex.RLock() + defer fake.pingContextMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/storage/storagefakes/fake_storage.go b/storage/storagefakes/fake_storage.go index dc16ce5de..1cb6fa4f7 100644 --- a/storage/storagefakes/fake_storage.go +++ b/storage/storagefakes/fake_storage.go @@ -108,14 +108,15 @@ type FakeStorage struct { openReturnsOnCall map[int]struct { result1 error } - PingStub func() error - pingMutex sync.RWMutex - pingArgsForCall []struct { + PingContextStub func(context.Context) error + pingContextMutex sync.RWMutex + pingContextArgsForCall []struct { + arg1 context.Context } - pingReturns struct { + pingContextReturns struct { result1 error } - pingReturnsOnCall map[int]struct { + pingContextReturnsOnCall map[int]struct { result1 error } UpdateStub func(context.Context, types.Object, query.LabelChanges, ...query.Criterion) (types.Object, error) @@ -601,54 +602,62 @@ func (fake *FakeStorage) OpenReturnsOnCall(i int, result1 error) { }{result1} } -func (fake *FakeStorage) Ping() error { - fake.pingMutex.Lock() - ret, specificReturn := fake.pingReturnsOnCall[len(fake.pingArgsForCall)] - fake.pingArgsForCall = append(fake.pingArgsForCall, struct { - }{}) - fake.recordInvocation("Ping", []interface{}{}) - fake.pingMutex.Unlock() - if fake.PingStub != nil { - return fake.PingStub() +func (fake *FakeStorage) PingContext(arg1 context.Context) error { + fake.pingContextMutex.Lock() + ret, specificReturn := fake.pingContextReturnsOnCall[len(fake.pingContextArgsForCall)] + fake.pingContextArgsForCall = append(fake.pingContextArgsForCall, struct { + arg1 context.Context + }{arg1}) + fake.recordInvocation("PingContext", []interface{}{arg1}) + fake.pingContextMutex.Unlock() + if fake.PingContextStub != nil { + return fake.PingContextStub(arg1) } if specificReturn { return ret.result1 } - fakeReturns := fake.pingReturns + fakeReturns := fake.pingContextReturns return fakeReturns.result1 } -func (fake *FakeStorage) PingCallCount() int { - fake.pingMutex.RLock() - defer fake.pingMutex.RUnlock() - return len(fake.pingArgsForCall) +func (fake *FakeStorage) PingContextCallCount() int { + fake.pingContextMutex.RLock() + defer fake.pingContextMutex.RUnlock() + return len(fake.pingContextArgsForCall) } -func (fake *FakeStorage) PingCalls(stub func() error) { - fake.pingMutex.Lock() - defer fake.pingMutex.Unlock() - fake.PingStub = stub +func (fake *FakeStorage) PingContextCalls(stub func(context.Context) error) { + fake.pingContextMutex.Lock() + defer fake.pingContextMutex.Unlock() + fake.PingContextStub = stub +} + +func (fake *FakeStorage) PingContextArgsForCall(i int) context.Context { + fake.pingContextMutex.RLock() + defer fake.pingContextMutex.RUnlock() + argsForCall := fake.pingContextArgsForCall[i] + return argsForCall.arg1 } -func (fake *FakeStorage) PingReturns(result1 error) { - fake.pingMutex.Lock() - defer fake.pingMutex.Unlock() - fake.PingStub = nil - fake.pingReturns = struct { +func (fake *FakeStorage) PingContextReturns(result1 error) { + fake.pingContextMutex.Lock() + defer fake.pingContextMutex.Unlock() + fake.PingContextStub = nil + fake.pingContextReturns = struct { result1 error }{result1} } -func (fake *FakeStorage) PingReturnsOnCall(i int, result1 error) { - fake.pingMutex.Lock() - defer fake.pingMutex.Unlock() - fake.PingStub = nil - if fake.pingReturnsOnCall == nil { - fake.pingReturnsOnCall = make(map[int]struct { +func (fake *FakeStorage) PingContextReturnsOnCall(i int, result1 error) { + fake.pingContextMutex.Lock() + defer fake.pingContextMutex.Unlock() + fake.PingContextStub = nil + if fake.pingContextReturnsOnCall == nil { + fake.pingContextReturnsOnCall = make(map[int]struct { result1 error }) } - fake.pingReturnsOnCall[i] = struct { + fake.pingContextReturnsOnCall[i] = struct { result1 error }{result1} } @@ -738,8 +747,8 @@ func (fake *FakeStorage) Invocations() map[string][][]interface{} { defer fake.listMutex.RUnlock() fake.openMutex.RLock() defer fake.openMutex.RUnlock() - fake.pingMutex.RLock() - defer fake.pingMutex.RUnlock() + fake.pingContextMutex.RLock() + defer fake.pingContextMutex.RUnlock() fake.updateMutex.RLock() defer fake.updateMutex.RUnlock() copiedInvocations := map[string][][]interface{}{}