-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix(cache): restore Dataset to Bound after CacheRuntime recovers from an outage #6162
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: master
Are you sure you want to change the base?
Changes from 1 commit
49b2a77
2e2b62a
426ed33
1594e4f
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 |
|---|---|---|
|
|
@@ -338,6 +338,71 @@ var _ = Describe("CacheEngine Sync Tests", Label("pkg.ddc.cache.engine.sync_test | |
| }) | ||
| }) | ||
|
|
||
| Context("when runtime is ready but dataset was left Failed by a previous outage", func() { | ||
| BeforeEach(func() { | ||
| dataset.Status.Phase = datav1alpha1.FailedDatasetPhase | ||
|
|
||
| masterReplicas := int32(1) | ||
| masterSts := &workloadv1alpha1.AdvancedStatefulSet{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: "test-runtime-master", Namespace: "default"}, | ||
| Spec: workloadv1alpha1.AdvancedStatefulSetSpec{ | ||
| Replicas: &masterReplicas, | ||
| Template: corev1.PodTemplateSpec{ | ||
| Spec: corev1.PodSpec{ | ||
| Containers: []corev1.Container{{Name: "master", Image: "test-master:latest"}}, | ||
| }, | ||
| }, | ||
| }, | ||
| Status: workloadv1alpha1.AdvancedStatefulSetStatus{ReadyReplicas: 1, CurrentReplicas: 1, AvailableReplicas: 1}, | ||
| } | ||
|
|
||
| workerReplicas := int32(2) | ||
| workerSts := &workloadv1alpha1.AdvancedStatefulSet{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: "test-runtime-worker", Namespace: "default"}, | ||
| Spec: workloadv1alpha1.AdvancedStatefulSetSpec{ | ||
| Replicas: &workerReplicas, | ||
| Template: corev1.PodTemplateSpec{ | ||
| Spec: corev1.PodSpec{ | ||
| Containers: []corev1.Container{{Name: "worker", Image: "test-worker:latest"}}, | ||
| }, | ||
| }, | ||
| }, | ||
| Status: workloadv1alpha1.AdvancedStatefulSetStatus{ReadyReplicas: 2, CurrentReplicas: 2, AvailableReplicas: 2}, | ||
| } | ||
|
|
||
| clientDs := &appsv1.DaemonSet{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: "test-runtime-client", Namespace: "default"}, | ||
| Spec: appsv1.DaemonSetSpec{ | ||
| Template: corev1.PodTemplateSpec{ | ||
| Spec: corev1.PodSpec{ | ||
| Containers: []corev1.Container{{Name: "client", Image: "test-client:latest"}}, | ||
| }, | ||
| }, | ||
| }, | ||
| Status: appsv1.DaemonSetStatus{NumberReady: 0, DesiredNumberScheduled: 0}, | ||
| } | ||
|
|
||
| engine.Client = fake.NewClientBuilder(). | ||
| WithScheme(CacheEngineTestScheme). | ||
| WithObjects(dataset, runtimeObj, runtimeClass, masterSts, workerSts, clientDs). | ||
| WithStatusSubresource(dataset, runtimeObj). | ||
| Build() | ||
| }) | ||
|
|
||
| It("should restore the dataset phase to Bound", func() { | ||
| err := engine.Sync(ctx) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| updatedDataset := &datav1alpha1.Dataset{} | ||
| err = engine.Client.Get(context.Background(), types.NamespacedName{ | ||
| Name: "test-runtime", | ||
| Namespace: "default", | ||
| }, updatedDataset) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(updatedDataset.Status.Phase).To(Equal(datav1alpha1.BoundDatasetPhase)) | ||
|
Collaborator
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. This pins the phase but not the condition. idx, cond := utils.GetDatasetCondition(updatedDataset.Status.Conditions, datav1alpha1.DatasetReady)
Expect(idx).NotTo(Equal(-1))
Expect(cond.Status).To(Equal(corev1.ConditionTrue))It would also help to seed the Dataset with a One path this case can't reach: the shared fixture leaves |
||
| }) | ||
| }) | ||
|
|
||
| Context("when runtime is ready with ReportSummary configured", func() { | ||
| var patches *gomonkey.Patches | ||
|
|
||
|
|
||
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.
UpdateDatasetStatus(BoundDatasetPhase, ...)isn't a cheap phase write. For the Bound case,dataset.go:49-55callsGetCacheStates, which execs into the master pod viaExecCommandInContainerWithTimeoutwith a 20s timeout floor (MinExecutionTimeoutSeconds). That call sits outsidepermitSyncEngineStatus, and only theelse ifbelow still carries the guard, so the restore path issues exactly the sort of unthrottled RPC that the comment on line 38 says the limiter exists to bound.Alluxio is a useful comparison here, since it does the same recovery and is also called outside the limiter (
base/syncs.go:63intoalluxio/health_check.go:81). That's fine there for two reasons the cache engine's version lacks: itsUpdateDatasetStatuswrites only phase, condition, mounts and runtimes with no RPC, and it wraps the transition inif phase != dataset.Status.Phase, so calling it again costs nothing. Cache states get refreshed separately byUpdateCacheOfDataset().I measured this rather than guessing, and I want to be straight about the size of it. In a unit test, driving three not-ready to ready flaps inside a single 5s window produces 3 execs where at most 1 should happen. On a real cluster it's much milder: across five induced worker outages in 47s, the change added one exec attempt (6 versus 5 without it), because a real recovery cycle takes longer than 5s and so tends to get its own window. So this is not a production hazard, and I'm not claiming it is.
Even so, I'd rather see it handled in this PR than carried forward, because the fix is small and lives in code you're already touching. Making the restore phase-only and moving the idempotence check into the helper does it, and then this call site no longer needs the
GetDatasetabove either:One approach I'd avoid: simply wrapping the restore in
permitSyncEngineStatus. That makes recovery wait on the limiter rather than making it cheap, so it swaps this for a slower fix.Smaller point about the current shape: on the reconcile that restores the phase,
syncDatasetCacheStatesgets skipped entirely, because the restore takes theifand the sync sits in theelse if.Harness and captured output, if it's useful: https://github.com/cheyang/fluid/tree/verify/cacheruntime-dataset-phase-restore/docs/verification/cacheruntime-dataset-phase-restore