forked from kubernetes/kubernetes
-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
UPSTREAM: <carry>: add etcd3RetryingProberMonitor for retrying etcd U…
…navailable errors for the etcd health checker client UPSTREAM: <carry>: replace newETCD3ProberMonitor with etcd3RetryingProberMonitor
- Loading branch information
1 parent
f2fdd39
commit 9827eb6
Showing
4 changed files
with
197 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory/retry_etcdprobemonitor.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package factory | ||
|
||
import ( | ||
"context" | ||
|
||
"k8s.io/apiserver/pkg/storage/etcd3/etcd3retry" | ||
"k8s.io/apiserver/pkg/storage/etcd3/metrics" | ||
"k8s.io/apiserver/pkg/storage/storagebackend" | ||
) | ||
|
||
type proberMonitor interface { | ||
Prober | ||
metrics.Monitor | ||
} | ||
|
||
type etcd3RetryingProberMonitor struct { | ||
delegate proberMonitor | ||
} | ||
|
||
func newRetryingETCD3ProberMonitor(c storagebackend.Config) (*etcd3RetryingProberMonitor, error) { | ||
delegate, err := newETCD3ProberMonitor(c) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &etcd3RetryingProberMonitor{delegate: delegate}, nil | ||
} | ||
|
||
func (t *etcd3RetryingProberMonitor) Probe(ctx context.Context) error { | ||
return etcd3retry.OnError(ctx, etcd3retry.DefaultRetry, etcd3retry.IsRetriableEtcdError, func() error { | ||
return t.delegate.Probe(ctx) | ||
}) | ||
} | ||
|
||
func (t *etcd3RetryingProberMonitor) Monitor(ctx context.Context) (metrics.StorageMetrics, error) { | ||
var ret metrics.StorageMetrics | ||
err := etcd3retry.OnError(ctx, etcd3retry.DefaultRetry, etcd3retry.IsRetriableEtcdError, func() error { | ||
var innerErr error | ||
ret, innerErr = t.delegate.Monitor(ctx) | ||
return innerErr | ||
}) | ||
return ret, err | ||
} | ||
|
||
func (t *etcd3RetryingProberMonitor) Close() error { | ||
return t.delegate.Close() | ||
} |
147 changes: 147 additions & 0 deletions
147
...ng/src/k8s.io/apiserver/pkg/storage/storagebackend/factory/retry_etcdprobemonitor_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package factory | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
etcdrpc "go.etcd.io/etcd/api/v3/v3rpc/rpctypes" | ||
|
||
"k8s.io/apiserver/pkg/storage/etcd3/metrics" | ||
) | ||
|
||
func getRetryScenarios() []struct { | ||
name string | ||
retryFnError func() error | ||
expectedRetries int | ||
expectedFinalError error | ||
} { | ||
return []struct { | ||
name string | ||
retryFnError func() error | ||
expectedRetries int | ||
expectedFinalError error | ||
}{ | ||
{ | ||
name: "retry ErrLeaderChanged", | ||
retryFnError: func() error { | ||
return etcdrpc.ErrLeaderChanged | ||
}, | ||
expectedRetries: 5, | ||
expectedFinalError: etcdrpc.ErrLeaderChanged, | ||
}, | ||
{ | ||
name: "retry ErrLeaderChanged a few times", | ||
retryFnError: func() func() error { | ||
retryCounter := -1 | ||
return func() error { | ||
retryCounter++ | ||
if retryCounter == 3 { | ||
return nil | ||
} | ||
return etcdrpc.ErrLeaderChanged | ||
} | ||
}(), | ||
expectedRetries: 3, | ||
}, | ||
{ | ||
name: "no retries", | ||
retryFnError: func() error { | ||
return nil | ||
}, | ||
}, | ||
{ | ||
name: "no retries for a random error", | ||
retryFnError: func() error { | ||
return fmt.Errorf("random error") | ||
}, | ||
expectedFinalError: fmt.Errorf("random error"), | ||
}, | ||
} | ||
} | ||
|
||
func TestEtcd3RetryingProber(t *testing.T) { | ||
for _, scenario := range getRetryScenarios() { | ||
t.Run(scenario.name, func(t *testing.T) { | ||
ctx := context.TODO() | ||
targetDelegate := &fakeEtcd3RetryingProberMonitor{ | ||
// we set it to -1 to indicate that the first | ||
// execution is not a retry | ||
actualRetries: -1, | ||
probeFn: scenario.retryFnError, | ||
} | ||
|
||
target := &etcd3RetryingProberMonitor{delegate: targetDelegate} | ||
err := target.Probe(ctx) | ||
|
||
if targetDelegate.actualRetries != scenario.expectedRetries { | ||
t.Errorf("Unexpected number of retries %v, expected %v", targetDelegate.actualRetries, scenario.expectedRetries) | ||
} | ||
if (err == nil && scenario.expectedFinalError != nil) || (err != nil && scenario.expectedFinalError == nil) { | ||
t.Errorf("Expected error %v, got %v", scenario.expectedFinalError, err) | ||
} | ||
if err != nil && scenario.expectedFinalError != nil && err.Error() != scenario.expectedFinalError.Error() { | ||
t.Errorf("Expected error %v, got %v", scenario.expectedFinalError, err) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestEtcd3RetryingMonitor(t *testing.T) { | ||
for _, scenario := range getRetryScenarios() { | ||
t.Run(scenario.name, func(t *testing.T) { | ||
ctx := context.TODO() | ||
expectedRetValue := int64(scenario.expectedRetries) | ||
targetDelegate := &fakeEtcd3RetryingProberMonitor{ | ||
// we set it to -1 to indicate that the first | ||
// execution is not a retry | ||
actualRetries: -1, | ||
monitorFn: func() func() (metrics.StorageMetrics, error) { | ||
retryCounter := -1 | ||
return func() (metrics.StorageMetrics, error) { | ||
retryCounter++ | ||
err := scenario.retryFnError() | ||
ret := metrics.StorageMetrics{int64(retryCounter)} | ||
return ret, err | ||
} | ||
}(), | ||
} | ||
|
||
target := &etcd3RetryingProberMonitor{delegate: targetDelegate} | ||
actualRetValue, err := target.Monitor(ctx) | ||
|
||
if targetDelegate.actualRetries != scenario.expectedRetries { | ||
t.Errorf("Unexpected number of retries %v, expected %v", targetDelegate.actualRetries, scenario.expectedRetries) | ||
} | ||
if (err == nil && scenario.expectedFinalError != nil) || (err != nil && scenario.expectedFinalError == nil) { | ||
t.Errorf("Expected error %v, got %v", scenario.expectedFinalError, err) | ||
} | ||
if err != nil && scenario.expectedFinalError != nil && err.Error() != scenario.expectedFinalError.Error() { | ||
t.Errorf("Expected error %v, got %v", scenario.expectedFinalError, err) | ||
} | ||
if actualRetValue.Size != expectedRetValue { | ||
t.Errorf("Unexpected value returned actual %v, expected %v", actualRetValue.Size, expectedRetValue) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
type fakeEtcd3RetryingProberMonitor struct { | ||
actualRetries int | ||
probeFn func() error | ||
monitorFn func() (metrics.StorageMetrics, error) | ||
} | ||
|
||
func (f *fakeEtcd3RetryingProberMonitor) Probe(_ context.Context) error { | ||
f.actualRetries++ | ||
return f.probeFn() | ||
} | ||
|
||
func (f *fakeEtcd3RetryingProberMonitor) Monitor(_ context.Context) (metrics.StorageMetrics, error) { | ||
f.actualRetries++ | ||
return f.monitorFn() | ||
} | ||
|
||
func (f *fakeEtcd3RetryingProberMonitor) Close() error { | ||
panic("not implemented") | ||
} |