dongjoon-hyun commented on code in PR #909:
URL: 
https://github.com/apache/spark-kubernetes-operator/pull/909#discussion_r4161309308


##########
spark-operator/src/main/java/org/apache/spark/k8s/operator/reconciler/reconcilesteps/ClusterInitStep.java:
##########
@@ -68,29 +68,35 @@ public ReconcileProgress reconcile(
       return proceed();
     }
     SparkCluster cluster = context.getResource();
-    // A cluster whose master StatefulSet already exists has been requested 
before (e.g. the status
-    // update to RunningHealthy failed), so let it complete its initialization 
even if suspended.
     if (cluster.getSpec().isSuspend()) {
+      // Unlike a first attempt, a cluster resumed from Suspended or queued 
again after an eviction
+      // has persisted this Submitted state, which would keep saying that it 
is resumed or queued
+      // again. It goes back to Suspended instead, where ClusterSuspendStep 
deletes the master and
+      // worker StatefulSets by name and releases whatever it holds only after 
its pods are gone.
+      // That happens whether or not the master was requested, so it is not 
looked up.
+      if (cluster.getStatus().getStateTransitionHistory().lastKey() > 0) {

Review Comment:
   **[P1]** This is a follow-up of P8 of round 2. The new paragraph of the 
user-facing section covers part of it, but `What changes were proposed in this 
pull request?` still says the following, which becomes the commit message.
   
   ```
   - The requeue behavior is unchanged: both cases are still retried with the 
default interval, and the resource is neither held nor started.
   ```
   
   That no longer holds for a suspended `SparkCluster` which was resumed from 
`Suspended` or queued again after an eviction. Before this PR, a failed lookup 
kept it in `Submitted` and retried it with the default interval, while now it 
goes to `Suspended` here without the lookup. A first attempt whose master 
exists also goes to `Suspended` at L90-97 now, instead of completing its 
initialization to `RunningHealthy`. That section mentions neither change, and 
the new paragraph doesn't say that a resumed or requeued cluster is no longer 
looked up at all. In addition:
   
   - `How was this patch tested?` ends with `Pass the CIs with the newly added 
test cases:` and nothing after it. It could list the two new parameterized 
tests, and say that `suspendAfterMasterRequestedGoesToSuspended` replaces 
`suspendAfterMasterRequestedCompletesInitialization` and that 
`resumedClusterSuspendedAgainGoesBackToSuspended` now asserts that the master 
is not looked up.
   - nit. The doc changes are not mentioned, and `spec.suspend` and these 
events are not released yet in 1.0.0, which the template asks to clarify.
   
   Could you update the description once more?



##########
spark-operator/src/main/java/org/apache/spark/k8s/operator/reconciler/reconcilesteps/SuspendUtils.java:
##########
@@ -88,4 +91,37 @@ static ReconcileProgress holdForSuspend(final BaseContext<?> 
context, final Stri
     return completeAndRequeueAfter(

Review Comment:
   **[P2]** P1 of round 2 makes the Javadoc of this method stale. L48-49 still 
say:
   
   ```
      * queued with outlives the Workload. Callers keep their own guard for 
resources requested
      * before, which must complete their initialization instead of being held.
   ```
   
   That now holds for `AppInitStep` only, since `ClusterInitStep` L90-97 sends 
a suspended first attempt whose master exists to `Suspended` rather than 
completing its initialization, as `suspendAfterMasterRequestedGoesToSuspended` 
asserts. The same commit already dropped the matching `Like the suspend hold, a 
master requested before must complete its initialization` from the Javadoc of 
`ClusterInitStep.holdForKueueAdmission`, so only this end is left. L48-49 are 
not in the diff, so I'm leaving this here. How about the following?
   
   ```java
      * queued with outlives the Workload. Callers keep their own guard for 
resources requested
      * before, which are not held: an application whose driver was requested 
completes its
      * initialization, while a cluster whose master was requested goes to 
Suspended instead.
   ```



##########
spark-operator/src/main/java/org/apache/spark/k8s/operator/reconciler/reconcilesteps/ClusterInitStep.java:
##########
@@ -68,29 +68,35 @@ public ReconcileProgress reconcile(
       return proceed();
     }
     SparkCluster cluster = context.getResource();
-    // A cluster whose master StatefulSet already exists has been requested 
before (e.g. the status
-    // update to RunningHealthy failed), so let it complete its initialization 
even if suspended.
     if (cluster.getSpec().isSuspend()) {
+      // Unlike a first attempt, a cluster resumed from Suspended or queued 
again after an eviction
+      // has persisted this Submitted state, which would keep saying that it 
is resumed or queued
+      // again. It goes back to Suspended instead, where ClusterSuspendStep 
deletes the master and
+      // worker StatefulSets by name and releases whatever it holds only after 
its pods are gone.
+      // That happens whether or not the master was requested, so it is not 
looked up.
+      if (cluster.getStatus().getStateTransitionHistory().lastKey() > 0) {
+        return appendStateAndImmediateRequeue(
+            context, statusRecorder, new ClusterState(Suspended, 
CLUSTER_SUSPENDED_MESSAGE));
+      }
       final boolean masterRequested;
       try {
         masterRequested = isMasterRequested(context);
       } catch (KubernetesClientException e) {
         // Whether the master is live is unknown, not answered. Holding would 
claim in an event
         // that none was requested, and would release the Kueue quota of a 
running master, so
         // look again with the steady-state interval instead.
-        log.warn("Failed to check whether the master of a suspended cluster 
exists.", e);
-        return completeAndDefaultRequeue();
+        return SuspendUtils.retryAfterCheckFailure(context, e, "master");
       }
-      if (!masterRequested) {
-        // Unlike a first attempt, a cluster resumed from Suspended has 
persisted this Submitted
-        // state, which would keep saying that it is resumed. It goes back to 
Suspended instead,
-        // where ClusterSuspendStep releases whatever it holds only after its 
pods are gone.
-        if (cluster.getStatus().getStateTransitionHistory().lastKey() > 0) {
-          return appendStateAndImmediateRequeue(
-              context, statusRecorder, new ClusterState(Suspended, 
CLUSTER_SUSPENDED_MESSAGE));
-        }
-        return SuspendUtils.holdForSuspend(context, "master and workers");
+      if (masterRequested) {
+        // A first attempt whose master StatefulSet already exists has been 
requested before, e.g.
+        // the status update to RunningHealthy failed. It goes to Suspended as 
well, rather than
+        // completing its initialization: that would apply every resource 
again only for
+        // ClusterSuspendStep to delete them, and a failure to apply them 
would keep the cluster
+        // from being suspended, or fail it, although it was only suspended.
+        return appendStateAndImmediateRequeue(
+            context, statusRecorder, new ClusterState(Suspended, 
CLUSTER_SUSPENDED_MESSAGE));

Review Comment:
   **[P5]** nit. This exit is the same as L78-79, and their comments frame the 
same outcome in opposite terms: L72 starts with `Unlike a first attempt`, while 
L91-92 say that a first attempt whose master exists `goes to Suspended as 
well`. The rule is now that only a first attempt whose master is known to be 
absent is held. How about nesting the lookup under `lastKey() == 0` and keeping 
a single transition with one merged comment? It behaves the same: a resumed or 
requeued cluster still skips the lookup, which 
`resumedClusterSuspendedAgainGoesBackToSuspended` checks, and only a 
`KubernetesClientException` is caught.
   
   ```java
       if (cluster.getSpec().isSuspend()) {
         if (cluster.getStatus().getStateTransitionHistory().lastKey() == 0) {
           final boolean masterRequested;
           try {
             masterRequested = isMasterRequested(context);
           } catch (KubernetesClientException e) {
             // (the current comment)
             return SuspendUtils.retryAfterCheckFailure(context, e, "master");
           }
           if (!masterRequested) {
             return SuspendUtils.holdForSuspend(context, "master and workers");
           }
         }
         // (the comments of L72-76 and L91-95, merged)
         return appendStateAndImmediateRequeue(
             context, statusRecorder, new ClusterState(Suspended, 
CLUSTER_SUSPENDED_MESSAGE));
       }
   ```



##########
spark-operator/src/main/java/org/apache/spark/k8s/operator/reconciler/reconcilesteps/ClusterInitStep.java:
##########
@@ -68,29 +68,35 @@ public ReconcileProgress reconcile(
       return proceed();
     }
     SparkCluster cluster = context.getResource();
-    // A cluster whose master StatefulSet already exists has been requested 
before (e.g. the status
-    // update to RunningHealthy failed), so let it complete its initialization 
even if suspended.
     if (cluster.getSpec().isSuspend()) {
+      // Unlike a first attempt, a cluster resumed from Suspended or queued 
again after an eviction
+      // has persisted this Submitted state, which would keep saying that it 
is resumed or queued
+      // again. It goes back to Suspended instead, where ClusterSuspendStep 
deletes the master and
+      // worker StatefulSets by name and releases whatever it holds only after 
its pods are gone.
+      // That happens whether or not the master was requested, so it is not 
looked up.
+      if (cluster.getStatus().getStateTransitionHistory().lastKey() > 0) {
+        return appendStateAndImmediateRequeue(
+            context, statusRecorder, new ClusterState(Suspended, 
CLUSTER_SUSPENDED_MESSAGE));
+      }
       final boolean masterRequested;
       try {
         masterRequested = isMasterRequested(context);
       } catch (KubernetesClientException e) {
         // Whether the master is live is unknown, not answered. Holding would 
claim in an event
         // that none was requested, and would release the Kueue quota of a 
running master, so
         // look again with the steady-state interval instead.
-        log.warn("Failed to check whether the master of a suspended cluster 
exists.", e);
-        return completeAndDefaultRequeue();
+        return SuspendUtils.retryAfterCheckFailure(context, e, "master");

Review Comment:
   **[P4]** A design question, which can be a follow-up. After P1 of round 2, 
this lookup only decides between `holdForSuspend` and `Suspended`, and the 
comments at L72-76 and L91-95 explain that `Suspended` is correct whether or 
not the master was requested: `ClusterSuspendStep` deletes the `StatefulSet`s 
by name, which is a no-op for a missing master, and releases the `Workload` 
only after the pods are gone. So a failed lookup could go to `Suspended` as 
well. That would persist a status, which `kubectl get` shows even though events 
are disabled by default, end the retry, and release a pending `Workload` 
instead of keeping it while it may be admitted 
(`docs/spark_custom_resources.md` L692-694).
   
   On the other hand, a cluster which may never have started would then show 
`Suspended` (`its master and workers are being released`) instead of the 
unpersisted `Submitted` which the Suspend section describes, and 
`isTransientError` counts a single 429 or 500 as persistent, so one throttled 
read would leave it there until it is resumed. The same RBAC or API server 
failure also tends to fail the status patch and the deletions, and P4 of round 
1 kept `SuspendCheckFailed` for exactly this first attempt. So I'm fine with 
keeping the current behavior. In that case, could the comment here say why such 
a cluster doesn't go to `Suspended` either, since the comments around it 
suggest that it could?



##########
spark-operator/src/main/java/org/apache/spark/k8s/operator/reconciler/reconcilesteps/ClusterInitStep.java:
##########
@@ -68,29 +68,35 @@ public ReconcileProgress reconcile(
       return proceed();
     }
     SparkCluster cluster = context.getResource();
-    // A cluster whose master StatefulSet already exists has been requested 
before (e.g. the status
-    // update to RunningHealthy failed), so let it complete its initialization 
even if suspended.
     if (cluster.getSpec().isSuspend()) {
+      // Unlike a first attempt, a cluster resumed from Suspended or queued 
again after an eviction
+      // has persisted this Submitted state, which would keep saying that it 
is resumed or queued
+      // again. It goes back to Suspended instead, where ClusterSuspendStep 
deletes the master and
+      // worker StatefulSets by name and releases whatever it holds only after 
its pods are gone.
+      // That happens whether or not the master was requested, so it is not 
looked up.
+      if (cluster.getStatus().getStateTransitionHistory().lastKey() > 0) {
+        return appendStateAndImmediateRequeue(
+            context, statusRecorder, new ClusterState(Suspended, 
CLUSTER_SUSPENDED_MESSAGE));
+      }
       final boolean masterRequested;
       try {
         masterRequested = isMasterRequested(context);
       } catch (KubernetesClientException e) {
         // Whether the master is live is unknown, not answered. Holding would 
claim in an event
         // that none was requested, and would release the Kueue quota of a 
running master, so
         // look again with the steady-state interval instead.
-        log.warn("Failed to check whether the master of a suspended cluster 
exists.", e);
-        return completeAndDefaultRequeue();
+        return SuspendUtils.retryAfterCheckFailure(context, e, "master");
       }
-      if (!masterRequested) {
-        // Unlike a first attempt, a cluster resumed from Suspended has 
persisted this Submitted
-        // state, which would keep saying that it is resumed. It goes back to 
Suspended instead,
-        // where ClusterSuspendStep releases whatever it holds only after its 
pods are gone.
-        if (cluster.getStatus().getStateTransitionHistory().lastKey() > 0) {
-          return appendStateAndImmediateRequeue(
-              context, statusRecorder, new ClusterState(Suspended, 
CLUSTER_SUSPENDED_MESSAGE));
-        }
-        return SuspendUtils.holdForSuspend(context, "master and workers");
+      if (masterRequested) {

Review Comment:
   **[P3]** This adds another path from `Submitted` to `Suspended`, which the 
docs don't show yet. Neither line below is in the diff, so I'm leaving this 
here.
   
   - `docs/architecture.md` L206 labels the only `Submitted --> Suspended` edge 
`spec.suspend=true after resume`. A first attempt whose master already exists 
now takes it as well, from its unpersisted `Submitted` straight to `Suspended`, 
without `RunningHealthy`. The label also misses a cluster queued again after a 
Kueue eviction, which predates this PR.
   - `docs/spark_custom_resources.md` L588-589 say "Setting it to `true` again 
before the master is requested moves the cluster back to `Suspended`." Since 
L77-80, a resumed or requeued cluster goes back to `Suspended` whether or not 
the master was requested, so `before the master is requested` is no longer the 
condition.
   
   How about `Submitted --> Suspended : spec.suspend=true after resume or Kueue 
requeue, or once the master is requested` and "Setting it to `true` again moves 
the cluster back to `Suspended`."?



##########
spark-operator/src/main/java/org/apache/spark/k8s/operator/reconciler/reconcilesteps/ClusterInitStep.java:
##########
@@ -68,29 +68,35 @@ public ReconcileProgress reconcile(
       return proceed();
     }
     SparkCluster cluster = context.getResource();
-    // A cluster whose master StatefulSet already exists has been requested 
before (e.g. the status
-    // update to RunningHealthy failed), so let it complete its initialization 
even if suspended.
     if (cluster.getSpec().isSuspend()) {
+      // Unlike a first attempt, a cluster resumed from Suspended or queued 
again after an eviction
+      // has persisted this Submitted state, which would keep saying that it 
is resumed or queued
+      // again. It goes back to Suspended instead, where ClusterSuspendStep 
deletes the master and
+      // worker StatefulSets by name and releases whatever it holds only after 
its pods are gone.
+      // That happens whether or not the master was requested, so it is not 
looked up.
+      if (cluster.getStatus().getStateTransitionHistory().lastKey() > 0) {
+        return appendStateAndImmediateRequeue(
+            context, statusRecorder, new ClusterState(Suspended, 
CLUSTER_SUSPENDED_MESSAGE));
+      }
       final boolean masterRequested;
       try {
         masterRequested = isMasterRequested(context);
       } catch (KubernetesClientException e) {
         // Whether the master is live is unknown, not answered. Holding would 
claim in an event
         // that none was requested, and would release the Kueue quota of a 
running master, so
         // look again with the steady-state interval instead.
-        log.warn("Failed to check whether the master of a suspended cluster 
exists.", e);
-        return completeAndDefaultRequeue();
+        return SuspendUtils.retryAfterCheckFailure(context, e, "master");
       }
-      if (!masterRequested) {
-        // Unlike a first attempt, a cluster resumed from Suspended has 
persisted this Submitted
-        // state, which would keep saying that it is resumed. It goes back to 
Suspended instead,
-        // where ClusterSuspendStep releases whatever it holds only after its 
pods are gone.
-        if (cluster.getStatus().getStateTransitionHistory().lastKey() > 0) {
-          return appendStateAndImmediateRequeue(
-              context, statusRecorder, new ClusterState(Suspended, 
CLUSTER_SUSPENDED_MESSAGE));
-        }
-        return SuspendUtils.holdForSuspend(context, "master and workers");
+      if (masterRequested) {
+        // A first attempt whose master StatefulSet already exists has been 
requested before, e.g.
+        // the status update to RunningHealthy failed. It goes to Suspended as 
well, rather than
+        // completing its initialization: that would apply every resource 
again only for
+        // ClusterSuspendStep to delete them, and a failure to apply them 
would keep the cluster
+        // from being suspended, or fail it, although it was only suspended.

Review Comment:
   **[P6]** nit. `ClusterSuspendStep.releaseResources` deletes only the 
`StatefulSet`s and the HPA and PDB of the workers, and keeps the Services and 
the NetworkPolicy (`The Services and the NetworkPolicy are kept, since they 
hold no quota and are applied again on resume.`), so `only for 
ClusterSuspendStep to delete them` overstates it. The same goes for 
`ClusterSuspendStep deletes what was applied` in the comment of 
`suspendAfterMasterRequestedGoesToSuspended`. Also, `since it is held or goes 
to Suspended first` at L220 leaves out a failed lookup, which returns 
`retryAfterCheckFailure` at L88. How about the following, and e.g. `since every 
suspend branch of reconcile returns first` at L220?
   
   ```suggestion
           // A first attempt whose master StatefulSet already exists has been 
requested before, e.g.
           // the status update to RunningHealthy failed. It goes to Suspended 
as well, rather than
           // completing its initialization: that would apply every resource 
again only for
           // ClusterSuspendStep to release the master and workers, and a 
failure to apply them would
           // keep the cluster from being suspended, or fail it, although it 
was only suspended.
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to