-
Notifications
You must be signed in to change notification settings - Fork 58
Fix: bug: bug: False positive readiness check when API server watch channels close unexpectedly #670
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
base: main
Are you sure you want to change the base?
Fix: bug: bug: False positive readiness check when API server watch channels close unexpectedly #670
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 |
|---|---|---|
|
|
@@ -37,10 +37,10 @@ import ( | |
| ) | ||
|
|
||
| const ( | ||
| CreateKindFlag = "create-kind" | ||
| DomainFlag = "domain" | ||
| HostContainerNameFlag = "host-container-name" // REFACTOR? replace with host-container-name? | ||
| ExternalPortFlag = "external-port" // REFACTOR? replace with external-port? | ||
| CreateKindFlag = "create-kind" | ||
| DomainFlag = "domain" | ||
| HostContainerNameFlag = "host-container-name" // REFACTOR? replace with host-container-name? | ||
| ExternalPortFlag = "external-port" // REFACTOR? replace with external-port? | ||
| DefaultKindClusterName = "kind-kubeflex" // Default cluster name for kind clusters | ||
| ) | ||
|
|
||
|
|
@@ -58,7 +58,7 @@ func Command() *cobra.Command { | |
| domain, _ := flagset.GetString(DomainFlag) | ||
| externalPort, _ := flagset.GetInt(ExternalPortFlag) | ||
| hostContainer, _ := flagset.GetString(HostContainerNameFlag) | ||
|
|
||
| // Handle positional cluster name parameter | ||
| clusterName := DefaultKindClusterName // default | ||
| if len(args) > 0 { | ||
|
|
@@ -108,7 +108,6 @@ func ExecuteInit(ctx context.Context, kubeconfig, version, buildDate string, dom | |
| if err != nil { | ||
| return fmt.Errorf("error getting clientset: %v", err) | ||
| } | ||
| clientset := *clientsetp | ||
|
|
||
| util.PrintStatus(fmt.Sprintf("Kubeflex %s %s", version, buildDate), done, &wg, chattyStatus) | ||
| done <- true | ||
|
|
@@ -145,7 +144,7 @@ func ExecuteInit(ctx context.Context, kubeconfig, version, buildDate string, dom | |
|
|
||
| util.PrintStatus("Waiting for shared backend DB to become ready...", done, &wg, chattyStatus) | ||
| util.WaitForStatefulSetReady( | ||
| clientset, | ||
| clientsetp, | ||
| util.GeneratePSReplicaSetName(util.DBReleaseName), | ||
| util.SystemNamespace) | ||
|
Comment on lines
146
to
149
|
||
| done <- true | ||
|
|
@@ -159,7 +158,7 @@ func ExecuteInit(ctx context.Context, kubeconfig, version, buildDate string, dom | |
|
|
||
| util.PrintStatus("Waiting for kubeflex operator to become ready...", done, &wg, chattyStatus) | ||
| util.WaitForDeploymentReady( | ||
| clientset, | ||
| clientsetp, | ||
| util.GenerateOperatorDeploymentName(), | ||
| util.SystemNamespace) | ||
|
Comment on lines
160
to
163
|
||
| done <- true | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,143 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Copyright 2023 The KubeStellar 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 util | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "testing" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| v1 "k8s.io/api/apps/v1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| corev1 "k8s.io/api/core/v1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "k8s.io/apimachinery/pkg/watch" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "k8s.io/client-go/kubernetes/fake" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| k8stesting "k8s.io/client-go/testing" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func int32Ptr(i int32) *int32 { return &i } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TestWaitForDeploymentReady_ChannelClosedBeforeReady verifies that closing the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // watch channel before the deployment reaches readiness returns an error instead | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // of nil (false positive success). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func TestWaitForDeploymentReady_ChannelClosedBeforeReady(t *testing.T) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fakeClient := fake.NewSimpleClientset() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fw := watch.NewFake() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fakeClient.PrependWatchReactor("deployments", func(action k8stesting.Action) (bool, watch.Interface, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true, fw, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Close the channel immediately — simulates API server dropping the connection | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // before the deployment becomes ready. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| go fw.Stop() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| err := WaitForDeploymentReady(fakeClient, "test-deploy", "test-ns") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatal("expected error when watch channel closes before deployment is ready, got nil") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TestWaitForDeploymentReady_ReadyBeforeChannelClose verifies the happy path: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // deployment reports ready replicas and the function returns nil. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func TestWaitForDeploymentReady_ReadyBeforeChannelClose(t *testing.T) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fakeClient := fake.NewSimpleClientset() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fw := watch.NewFake() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fakeClient.PrependWatchReactor("deployments", func(action k8stesting.Action) (bool, watch.Interface, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true, fw, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deploy := &v1.Deployment{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ObjectMeta: metav1.ObjectMeta{Name: "test-deploy", Namespace: "test-ns"}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Spec: v1.DeploymentSpec{Replicas: int32Ptr(1)}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Status: v1.DeploymentStatus{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Replicas: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ReadyReplicas: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| go fw.Modify(deploy) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| err := WaitForDeploymentReady(fakeClient, "test-deploy", "test-ns") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatalf("expected nil when deployment is ready, got: %v", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TestWaitForStatefulSetReady_ChannelClosedBeforeReady verifies that closing the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // watch channel before the statefulset is ready returns an error. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func TestWaitForStatefulSetReady_ChannelClosedBeforeReady(t *testing.T) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fakeClient := fake.NewSimpleClientset() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fw := watch.NewFake() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fakeClient.PrependWatchReactor("statefulsets", func(action k8stesting.Action) (bool, watch.Interface, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true, fw, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| go fw.Stop() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| err := WaitForStatefulSetReady(fakeClient, "test-sts", "test-ns") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatal("expected error when watch channel closes before statefulset is ready, got nil") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TestWaitForStatefulSetReady_ReadyBeforeChannelClose verifies the happy path: | |
| // statefulset reports ready replicas and the function returns nil. | |
| func TestWaitForStatefulSetReady_ReadyBeforeChannelClose(t *testing.T) { | |
| fakeClient := fake.NewSimpleClientset() | |
| fw := watch.NewFake() | |
| fakeClient.PrependWatchReactor("statefulsets", func(action k8stesting.Action) (bool, watch.Interface, error) { | |
| return true, fw, nil | |
| }) | |
| sts := &v1.StatefulSet{ | |
| ObjectMeta: metav1.ObjectMeta{ | |
| Name: "test-sts", | |
| Namespace: "test-ns", | |
| }, | |
| Spec: v1.StatefulSetSpec{ | |
| Replicas: int32Ptr(3), | |
| }, | |
| Status: v1.StatefulSetStatus{ | |
| ReadyReplicas: 3, | |
| }, | |
| } | |
| go func() { | |
| fw.Modify(sts) | |
| }() | |
| err := WaitForStatefulSetReady(fakeClient, "test-sts", "test-ns") | |
| if err != nil { | |
| t.Fatalf("expected nil when statefulset becomes ready, got: %v", err) | |
| } | |
| } |
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.
The call to
WaitForNamespaceDeletion()does not check the returned error. Since the function signature was changed to return an error when the watch channel closes unexpectedly, this error must be checked and handled. The function can now fail and the caller needs to respond appropriately rather than silently ignoring the error.