-
Notifications
You must be signed in to change notification settings - Fork 271
NO-JIRA: Make creation of resources in KMS to resilient to disruptions in SNO #2437
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,6 @@ import ( | |
| "k8s.io/apimachinery/pkg/util/wait" | ||
| "k8s.io/client-go/dynamic" | ||
| "k8s.io/client-go/kubernetes" | ||
| "k8s.io/client-go/util/retry" | ||
|
|
||
| configv1 "github.com/openshift/api/config/v1" | ||
| configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" | ||
|
|
@@ -102,7 +101,7 @@ func SetAndWaitForEncryptionType(ctx context.Context, t testing.TB, provider Enc | |
|
|
||
| var previousEncryption configv1.APIServerEncryption | ||
| var needsUpdate bool | ||
| err = onErrorWithTimeout(waitPollTimeout, retry.DefaultBackoff, orError(errors.IsConflict, transientAPIError), func() error { | ||
| err = onErrorWithTimeout(waitPollTimeout, orError(errors.IsConflict, transientAPIError), func() error { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does it make sense to use the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually it makes sense. But let me know what you think about #2437 (comment) |
||
| apiServer, err := clientSet.ApiServerConfig.Get(ctx, "cluster", metav1.GetOptions{}) | ||
| if err != nil { | ||
| return err | ||
|
|
@@ -278,15 +277,7 @@ func GetLastKeyMeta(t testing.TB, kubeClient kubernetes.Interface, namespace, la | |
| // so we set the timeout to 5 min just in case | ||
| pollTimeout := time.Minute * 5 | ||
|
|
||
| // set the number of step to high value | ||
| // we should stop on timeout otherwise the backoff returns after 5 steps | ||
| // and we never wait the timeout value | ||
| backOff := retry.DefaultBackoff | ||
| backOff.Steps = 9999 | ||
|
|
||
| // in theory the max time we tolerate disruption on an SNO cluster is 60 seconds | ||
| // so we set the timeout to 5 min just in case | ||
| err := onErrorWithTimeout(pollTimeout, backOff, func(err error) bool { | ||
| err := onErrorWithTimeout(pollTimeout, func(err error) bool { | ||
| if !transientAPIError(err) { | ||
| t.Logf("error = %v is not retriable, failed to get the metadata from the last encryption key", err) | ||
| return false | ||
|
|
@@ -344,7 +335,7 @@ func ForceKeyRotation(t testing.TB, updateUnsupportedConfig UpdateUnsupportedCon | |
| return err | ||
| } | ||
|
|
||
| return onErrorWithTimeout(wait.ForeverTestTimeout, retry.DefaultBackoff, orError(errors.IsConflict, transientAPIError), func() error { | ||
| return onErrorWithTimeout(wait.ForeverTestTimeout, orError(errors.IsConflict, transientAPIError), func() error { | ||
| return updateUnsupportedConfig(raw) | ||
| }) | ||
| } | ||
|
|
@@ -510,18 +501,24 @@ func CreateAndStoreWellKnownSecretOfLife(t testing.TB, clientSet ClientSet, name | |
| t.Helper() | ||
| ctx := context.TODO() | ||
|
|
||
| oldSecret, err := clientSet.Kube.CoreV1().Secrets(namespace).Get(ctx, wellKnownSecretOfLifeName, metav1.GetOptions{}) | ||
| if err != nil && !errors.IsNotFound(err) { | ||
| t.Fatalf("Failed to check if the secret already exists: %v", err) | ||
| } | ||
| if oldSecret != nil && len(oldSecret.Name) > 0 { | ||
| t.Log("The secret already exists, removing it first") | ||
| require.NoError(t, clientSet.Kube.CoreV1().Secrets(namespace).Delete(ctx, oldSecret.Name, metav1.DeleteOptions{})) | ||
| } | ||
|
|
||
| t.Logf("Creating %q in %s namespace", wellKnownSecretOfLifeName, namespace) | ||
| rawSecret := WellKnownSecretOfLife(t, namespace) | ||
| secret, err := clientSet.Kube.CoreV1().Secrets(namespace).Create(ctx, rawSecret.(*corev1.Secret), metav1.CreateOptions{}) | ||
| var secret *corev1.Secret | ||
| err := onErrorWithTimeout(waitPollTimeout, transientAPIError, func() error { | ||
| existing, err := clientSet.Kube.CoreV1().Secrets(namespace).Get(ctx, wellKnownSecretOfLifeName, metav1.GetOptions{}) | ||
| if err != nil && !errors.IsNotFound(err) { | ||
| return err | ||
| } | ||
| if err == nil { | ||
| secret = existing | ||
| return nil | ||
| } | ||
| rawSecret := WellKnownSecretOfLife(t, namespace) | ||
| secret, err = clientSet.Kube.CoreV1().Secrets(namespace).Create(ctx, rawSecret.(*corev1.Secret), metav1.CreateOptions{}) | ||
| if errors.IsAlreadyExists(err) { | ||
| secret, err = clientSet.Kube.CoreV1().Secrets(namespace).Get(ctx, wellKnownSecretOfLifeName, metav1.GetOptions{}) | ||
| } | ||
| return err | ||
| }) | ||
| require.NoError(t, err) | ||
| return secret | ||
| } | ||
|
|
@@ -605,16 +602,28 @@ func CreateAndStoreWellKnownRouteOfLife(ctx context.Context, t testing.TB, cs Cl | |
| obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(route) | ||
| require.NoError(t, err) | ||
|
|
||
| created, err := cs.DynamicClient.Resource(wellKnownRouteGVR).Namespace(ns).Create(ctx, &unstructured.Unstructured{Object: obj}, metav1.CreateOptions{}) | ||
| if errors.IsAlreadyExists(err) { | ||
| // Leftover from a previous run, or a parallel create race. | ||
| t.Log("The route already exists, reusing it") | ||
| return route | ||
| } | ||
| require.NoError(t, err) | ||
|
|
||
| var result routev1.Route | ||
| err = runtime.DefaultUnstructuredConverter.FromUnstructured(created.Object, &result) | ||
| err = onErrorWithTimeout(waitPollTimeout, transientAPIError, func() error { | ||
| existing, err := cs.DynamicClient.Resource(wellKnownRouteGVR).Namespace(ns).Get(ctx, wellKnownRouteOfLifeName, metav1.GetOptions{}) | ||
| if err != nil && !errors.IsNotFound(err) { | ||
| return err | ||
| } | ||
| if err == nil { | ||
| return runtime.DefaultUnstructuredConverter.FromUnstructured(existing.Object, &result) | ||
| } | ||
| created, err := cs.DynamicClient.Resource(wellKnownRouteGVR).Namespace(ns).Create(ctx, &unstructured.Unstructured{Object: obj}, metav1.CreateOptions{}) | ||
| if errors.IsAlreadyExists(err) { | ||
| existing, err = cs.DynamicClient.Resource(wellKnownRouteGVR).Namespace(ns).Get(ctx, wellKnownRouteOfLifeName, metav1.GetOptions{}) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return runtime.DefaultUnstructuredConverter.FromUnstructured(existing.Object, &result) | ||
| } | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return runtime.DefaultUnstructuredConverter.FromUnstructured(created.Object, &result) | ||
| }) | ||
| require.NoError(t, err) | ||
| return &result | ||
| } | ||
|
|
@@ -662,25 +671,33 @@ func CreateAndStoreWellKnownTokenOfLife(ctx context.Context, t testing.TB, cs Cl | |
| t.Helper() | ||
| tokens := cs.DynamicClient.Resource(wellKnownOAuthAccessTokenGVR) | ||
|
|
||
| oldToken, err := tokens.Get(ctx, wellKnownTokenOfLifeName, metav1.GetOptions{}) | ||
| if err != nil && !errors.IsNotFound(err) { | ||
| t.Fatalf("Failed to check if the token already exists: %v", err) | ||
| } | ||
| if oldToken != nil && len(oldToken.GetName()) > 0 { | ||
| t.Log("The access token already exists, removing it first") | ||
| require.NoError(t, tokens.Delete(ctx, oldToken.GetName(), metav1.DeleteOptions{})) | ||
| } | ||
|
|
||
| t.Logf("Creating %q at cluster scope level", wellKnownTokenOfLifeName) | ||
| token := WellKnownTokenOfLife(t, "") | ||
| obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(token) | ||
| require.NoError(t, err) | ||
|
|
||
| created, err := tokens.Create(ctx, &unstructured.Unstructured{Object: obj}, metav1.CreateOptions{}) | ||
| require.NoError(t, err) | ||
|
|
||
| var result oauthapiv1.OAuthAccessToken | ||
| err = runtime.DefaultUnstructuredConverter.FromUnstructured(created.Object, &result) | ||
| err = onErrorWithTimeout(waitPollTimeout, transientAPIError, func() error { | ||
| existing, err := tokens.Get(ctx, wellKnownTokenOfLifeName, metav1.GetOptions{}) | ||
| if err != nil && !errors.IsNotFound(err) { | ||
| return err | ||
| } | ||
| if err == nil { | ||
| return runtime.DefaultUnstructuredConverter.FromUnstructured(existing.Object, &result) | ||
| } | ||
| created, err := tokens.Create(ctx, &unstructured.Unstructured{Object: obj}, metav1.CreateOptions{}) | ||
| if errors.IsAlreadyExists(err) { | ||
| existing, err = tokens.Get(ctx, wellKnownTokenOfLifeName, metav1.GetOptions{}) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return runtime.DefaultUnstructuredConverter.FromUnstructured(existing.Object, &result) | ||
| } | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return runtime.DefaultUnstructuredConverter.FromUnstructured(created.Object, &result) | ||
| }) | ||
| require.NoError(t, err) | ||
| return &result | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🌐 Web query:
For k8s.io/apimachinery v0.36.2, confirm that wait.PollUntilContextTimeout only cancels the context passed to its condition and that blocking API calls must receive that context to stop at the polling deadline.💡 Result:
For k8s.io/apimachinery v0.36.2, it is correct that wait.PollUntilContextTimeout only manages the context passed to its internal polling logic, and blocking API calls inside your condition function must accept and respect that context to stop at the polling deadline [1][2]. In v0.36.2, PollUntilContextTimeout works by deriving a child context with a timeout from the parent context provided by the caller [1][3]: deadlineCtx, deadlineCancel:= context.WithTimeout(ctx, timeout) defer deadlineCancel return loopConditionUntilContext(deadlineCtx,...) The function creates this deadline-bound context internally to enforce the timeout [1]. When the timeout expires or the parent context is cancelled, this derived context is cancelled [1]. However, the polling loop itself does not automatically force-terminate or interrupt blocking operations (such as network requests or database queries) occurring inside your condition function [2][4]. To ensure your code respects the timeout deadline: 1. You must accept the context provided by the condition function signature (ConditionWithContextFunc) [1]. 2. You must pass this context into any blocking API calls (e.g., client-go requests, database queries, or other I/O operations) [2][5]. If you fail to propagate this context into your blocking operations, those operations will continue to run even after PollUntilContextTimeout has reached its deadline, potentially causing resource leaks or delayed error handling [5][4]. Top results: [1], [2], [5]
Citations:
🏁 Script executed:
Repository: openshift/library-go
Length of output: 15565
🏁 Script executed:
Repository: openshift/library-go
Length of output: 26952
Propagate the retry deadline to
fn.onErrorWithTimeoutdiscards the context supplied byPollUntilContextTimeoutand callsfn()without it. The retried KubernetesList,Get, andCreatecalls therefore use unrelated contexts. A blocked request can continue beyond the polling timeout. Changefnto acceptcontext.Contextand pass the polling context to each API call.🤖 Prompt for AI Agents
Source: Path instructions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be a good move However, I want to keep the changes minimal to keep this PR focused, as previous version did not propagate context anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ardaguclu, understood. Keeping this pull request focused is reasonable.The context propagation gap remains. Would you like me to open a follow-up GitHub issue for it?
You are interacting with an AI system.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I don't. Thank you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ardaguclu, acknowledged. No follow-up issue will be created.You are interacting with an AI system.