Merge pull request #126038 from mprahl/retry-watcher-forbidden

Stop the RetryWatcher when failing due to permissions issue

Kubernetes-commit: e54c8ef2024e638d721242224f6f925b15ee43f5
This commit is contained in:
Kubernetes Publisher 2024-08-19 06:12:51 -07:00
commit 79827ce1df
2 changed files with 65 additions and 0 deletions

View File

@ -128,6 +128,35 @@ func (rw *RetryWatcher) doReceive() (bool, time.Duration) {
return false, 0
}
// Check if the watch failed due to the client not having permission to watch the resource or the credentials
// being invalid (e.g. expired token).
if apierrors.IsForbidden(err) || apierrors.IsUnauthorized(err) {
// Add more detail since the forbidden message returned by the Kubernetes API is just "unknown".
klog.ErrorS(err, msg+": ensure the client has valid credentials and watch permissions on the resource")
if apiStatus, ok := err.(apierrors.APIStatus); ok {
statusErr := apiStatus.Status()
sent := rw.send(watch.Event{
Type: watch.Error,
Object: &statusErr,
})
if !sent {
// This likely means the RetryWatcher is stopping but return false so the caller to doReceive can
// verify this and potentially retry.
klog.Error("Failed to send the Unauthorized or Forbidden watch event")
return false, 0
}
} else {
// This should never happen since apierrors only handles apierrors.APIStatus. Still, this is an
// unrecoverable error, so still allow it to return true below.
klog.ErrorS(err, msg+": encountered an unexpected Unauthorized or Forbidden error type")
}
return true, 0
}
klog.ErrorS(err, msg)
// Retry
return false, 0

View File

@ -288,6 +288,42 @@ func TestRetryWatcher(t *testing.T) {
},
},
},
{
name: "fails on Forbidden",
initialRV: "5",
watchClient: &cache.ListWatch{
WatchFunc: func() func(options metav1.ListOptions) (watch.Interface, error) {
return func(options metav1.ListOptions) (watch.Interface, error) {
return nil, apierrors.NewForbidden(schema.GroupResource{}, "", errors.New("unknown"))
}
}(),
},
watchCount: 1,
expected: []watch.Event{
{
Type: watch.Error,
Object: &apierrors.NewForbidden(schema.GroupResource{}, "", errors.New("unknown")).ErrStatus,
},
},
},
{
name: "fails on Unauthorized",
initialRV: "5",
watchClient: &cache.ListWatch{
WatchFunc: func() func(options metav1.ListOptions) (watch.Interface, error) {
return func(options metav1.ListOptions) (watch.Interface, error) {
return nil, apierrors.NewUnauthorized("")
}
}(),
},
watchCount: 1,
expected: []watch.Event{
{
Type: watch.Error,
Object: &apierrors.NewUnauthorized("").ErrStatus,
},
},
},
{
name: "recovers from timeout error",
initialRV: "5",