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


##########
spark-operator/src/main/java/org/apache/spark/k8s/operator/decorators/DriverResourceDecorator.java:
##########
@@ -69,13 +109,33 @@ public <T extends HasMetadata> T decorate(T resource) {
     }
     if (!ownerReferenceExists) {
       log.debug("Adding OwnerReference to driver for secondary resource");
-      ObjectMeta metaData =
+      Map<String, String> resourceLabels = resource.getMetadata().getLabels();
+      Map<String, String> driverPodLabels = 
driverPod.getMetadata().getLabels();
+      ObjectMetaBuilder builder =
           new ObjectMetaBuilder(resource.getMetadata())
-              .addToOwnerReferences(buildOwnerReferenceTo(driverPod))
-              .addToLabels(driverPod.getMetadata().getLabels())
-              .build();
-      resource.setMetadata(metaData);
+              .addToOwnerReferences(buildOwnerReferenceTo(driverPod));
+      if (driverPodLabels != null) {
+        driverPodLabels.forEach(
+            (key, value) -> {
+              boolean resourceSetsKey = resourceLabels != null && 
resourceLabels.containsKey(key);

Review Comment:
   This turns a previously working configuration into a failure. Spark 
substitutes `{{APP_ID}}` only in the driver pod labels 
(`KubernetesConf.labels`), not in `serviceLabels` or the PVC labels. If an app 
sets both `spark.kubernetes.driver.label.run={{APP_ID}}` and 
`spark.kubernetes.driver.service.label.run={{APP_ID}}`, 1.0 overwrote the raw 
service value with the substituted driver-pod value. With this change, the raw 
`{{APP_ID}}` is kept, the Service creation in `AppInitStep` fails with `422`, 
and the app ends in `SchedulingFailure`. The migration guide documents it, but 
could we avoid the regression instead?



##########
spark-operator/src/main/java/org/apache/spark/k8s/operator/decorators/DriverResourceDecorator.java:
##########
@@ -45,10 +50,45 @@
 public class DriverResourceDecorator implements ResourceDecorator {
   private final Pod driverPod;
 
+  /**
+   * Spark-owned driver label for the application ID, used as the service 
selector. Spark sets it;
+   * the operator only propagates it. Package-private so tests can exercise 
the managed-label keys.
+   */
+  static final String LABEL_SPARK_APP_SELECTOR = "spark-app-selector";

Review Comment:
   Could you move `spark-app-selector` and `spark-app-name` to `Constants`, 
next to `LABEL_SPARK_ROLE_NAME` and `LABEL_SPARK_VERSION_NAME`, which are also 
Spark-owned keys? Then the test does not need to import package-private 
internals of this class.



##########
spark-operator/src/main/java/org/apache/spark/k8s/operator/decorators/DriverResourceDecorator.java:
##########
@@ -69,13 +109,33 @@ public <T extends HasMetadata> T decorate(T resource) {
     }
     if (!ownerReferenceExists) {
       log.debug("Adding OwnerReference to driver for secondary resource");
-      ObjectMeta metaData =
+      Map<String, String> resourceLabels = resource.getMetadata().getLabels();
+      Map<String, String> driverPodLabels = 
driverPod.getMetadata().getLabels();
+      ObjectMetaBuilder builder =
           new ObjectMetaBuilder(resource.getMetadata())
-              .addToOwnerReferences(buildOwnerReferenceTo(driverPod))
-              .addToLabels(driverPod.getMetadata().getLabels())
-              .build();
-      resource.setMetadata(metaData);
+              .addToOwnerReferences(buildOwnerReferenceTo(driverPod));
+      if (driverPodLabels != null) {
+        driverPodLabels.forEach(

Review Comment:
   As the PR description says, the root cause is that every driver-pod label, 
including the ones injected by admission webhooks (e.g. Istio 
`security.istio.io/tlsMode`, `service.istio.io/canonical-name`) and 
`spark.kubernetes.driver.label.*`, is copied to all Services, Ingresses, 
ConfigMaps and PVCs. This PR only changes the precedence on a key collision, so 
users still need to set each injected key on each resource to keep it off. 
Propagating only the `isManagedLabel(key)` labels, which are what GC and Spark 
bookkeeping need, would fix the reported Istio issue directly and also avoid 
the `{{APP_ID}}` regression.



##########
spark-operator/src/main/java/org/apache/spark/k8s/operator/decorators/DriverResourceDecorator.java:
##########
@@ -69,13 +109,33 @@ public <T extends HasMetadata> T decorate(T resource) {
     }
     if (!ownerReferenceExists) {
       log.debug("Adding OwnerReference to driver for secondary resource");
-      ObjectMeta metaData =
+      Map<String, String> resourceLabels = resource.getMetadata().getLabels();
+      Map<String, String> driverPodLabels = 
driverPod.getMetadata().getLabels();
+      ObjectMetaBuilder builder =
           new ObjectMetaBuilder(resource.getMetadata())
-              .addToOwnerReferences(buildOwnerReferenceTo(driverPod))
-              .addToLabels(driverPod.getMetadata().getLabels())
-              .build();
-      resource.setMetadata(metaData);
+              .addToOwnerReferences(buildOwnerReferenceTo(driverPod));
+      if (driverPodLabels != null) {
+        driverPodLabels.forEach(
+            (key, value) -> {

Review Comment:
   nit. This can be simpler with a merged map, e.g. `merged = new 
HashMap<>(resourceLabels)` (or an empty map) and `driverPodLabels.forEach((k, 
v) -> { if (isManagedLabel(k)) merged.put(k, v); else merged.putIfAbsent(k, v); 
})`. It expresses the precedence directly without `resourceSetsKey`.



##########
spark-operator-api/src/main/java/org/apache/spark/k8s/operator/Constants.java:
##########
@@ -32,17 +32,22 @@ public final class Constants {
   /** The API version for Spark K8s Operator CRD resources. */
   public static final String API_VERSION = "v1";
 
+  /** The shared prefix for all operator-defined labels. */
+  public static final String LABEL_SPARK_OPERATOR_PREFIX = "spark.operator/";
+
   /** The label for the Spark Application name. */
-  public static final String LABEL_SPARK_APPLICATION_NAME = 
"spark.operator/spark-app-name";
+  public static final String LABEL_SPARK_APPLICATION_NAME =

Review Comment:
   The rewrite of the four existing constants is unrelated to this fix and 
makes the literal label values harder to grep (`spark.operator/spark-app-name` 
no longer appears verbatim). `ANNOTATION_POD_SETS_HASH` in `KueueWorkloadUtils` 
still uses a literal, too. Shall we keep the existing constants as they are and 
add only the new prefix constant?



##########
spark-operator/src/test/java/org/apache/spark/k8s/operator/decorators/DriverResourceDecoratorTest.java:
##########
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.spark.k8s.operator.decorators;
+
+import static 
org.apache.spark.k8s.operator.Constants.LABEL_SPARK_APPLICATION_NAME;
+import static 
org.apache.spark.k8s.operator.Constants.LABEL_SPARK_OPERATOR_NAME;
+import static org.apache.spark.k8s.operator.Constants.LABEL_SPARK_ROLE_NAME;
+import static org.apache.spark.k8s.operator.Constants.LABEL_SPARK_VERSION_NAME;
+import static 
org.apache.spark.k8s.operator.decorators.DriverResourceDecorator.LABEL_SPARK_APP_NAME;
+import static 
org.apache.spark.k8s.operator.decorators.DriverResourceDecorator.LABEL_SPARK_APP_SELECTOR;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Map;
+
+import io.fabric8.kubernetes.api.model.OwnerReference;
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodBuilder;
+import io.fabric8.kubernetes.api.model.Service;
+import io.fabric8.kubernetes.api.model.ServiceBuilder;
+import org.junit.jupiter.api.Test;
+
+class DriverResourceDecoratorTest {
+
+  private static Pod driverPod() {
+    return new PodBuilder()
+        .withKind("Pod")
+        .withApiVersion("v1")
+        .withNewMetadata()
+        .withName("spark-driver")
+        .withUid("driver-uid")
+        .addToLabels(LABEL_SPARK_OPERATOR_NAME, "spark-operator")
+        .addToLabels(LABEL_SPARK_APPLICATION_NAME, "app1")
+        // Prefix-matched managed label that is not one of the exact-match 
keys.
+        .addToLabels("spark.operator/submission-id", "sub-1")
+        .addToLabels(LABEL_SPARK_ROLE_NAME, "driver")
+        .addToLabels(LABEL_SPARK_APP_SELECTOR, "spark-app-selector-1")
+        .addToLabels(LABEL_SPARK_VERSION_NAME, "4.2.0")
+        .addToLabels(LABEL_SPARK_APP_NAME, "app1-name")
+        .addToLabels("driver-only-label", "driver-value")   // neutral driver 
only label

Review Comment:
   nit. Formatting: please use a single space before the trailing comment here. 
The javadoc of `driverLabelsPropagatedAndResourceLabelWinsOnCollision` leaves 
`kept.` alone on a line, and `Keep this line.` in 
`resourceWithoutLabelsInheritsDriverPodLabels` can be removed since the 
preceding sentence already explains why.



##########
docs/migration_guide.md:
##########
@@ -167,6 +167,33 @@ affect building the project, CI, tests or examples.
   `driverStartTimeoutMillis` was disabled. Since `DriverStartTimedOut` is an 
infrastructure failure
   while the others are not, `restartPolicy: OnInfrastructureFailure` no longer 
restarts such an
   application. Use `OnFailure` to restart it.
+- Since 1.1.0, a label set on a secondary resource of the driver takes 
precedence over a label of
+  the same key on the driver pod, instead of being overwritten by it. This 
affects the user-defined
+  labels in `spec.driverServiceIngressList` (`serviceMetadata`, 
`ingressMetadata`),
+  `spec.driverHttpRouteList` (`serviceMetadata`, `httpRouteMetadata`), 
`spec.driverGrpcRouteList`
+  (`serviceMetadata`, `grpcRouteMetadata`) and `spec.configMapSpecs`, as well 
as the other
+  Kubernetes resources of the driver, e.g. its service with
+  `spark.kubernetes.driver.service.label.*` and its on-demand PVCs with
+  `spark.kubernetes.driver.volumes.persistentVolumeClaim.<name>.label.*`. 1.0 
overwrote these labels
+  with the driver-pod labels of the same key, including the ones injected into 
the driver pod by
+  admission webhooks. The operator- and Spark-managed identity labels, i.e. 
any `spark.operator/`
+  key, `spark-role`, `spark-version`, `spark-app-selector` and 
`spark-app-name`, still take the
+  driver-pod value, since they identify the resources of an application, e.g. 
Spark deletes its
+  services, PVCs and executor config maps by `spark-app-selector` and 
`spark-role`. To restore the
+  behavior before 1.1.0 for a given key, remove it from the secondary resource 
so it inherits the
+  driver-pod label. The operator still copies every other driver-pod label 
onto the secondary
+  resources, including labels that admission webhooks inject into the driver 
pod, e.g. Istio's
+  `security.istio.io/tlsMode` and `service.istio.io/canonical-name`; only a 
key the resource sets
+  itself is kept. To keep such an injected label off a secondary resource, set 
that key on the
+  resource, e.g. in `serviceMetadata`, so the resource's own value wins. Note 
that Spark

Review Comment:
   This item is a single 26-line paragraph which mixes the behavior change, the 
managed keys, the workaround, the webhook limitation and the `{{APP_ID}}` 
caveat. The lines from here are also wrapped in the middle of a sentence (`Note 
that Spark` / `substitutes {{APP_ID}} only in` / `driver pod labels`), unlike 
the other items. Could you rewrap it at 100 columns and split it into shorter 
sentences or sub-items, so that the `{{APP_ID}}` failure is not missed?



##########
spark-operator/src/main/java/org/apache/spark/k8s/operator/decorators/DriverResourceDecorator.java:
##########
@@ -69,13 +109,33 @@ public <T extends HasMetadata> T decorate(T resource) {
     }
     if (!ownerReferenceExists) {
       log.debug("Adding OwnerReference to driver for secondary resource");
-      ObjectMeta metaData =
+      Map<String, String> resourceLabels = resource.getMetadata().getLabels();
+      Map<String, String> driverPodLabels = 
driverPod.getMetadata().getLabels();
+      ObjectMetaBuilder builder =
           new ObjectMetaBuilder(resource.getMetadata())
-              .addToOwnerReferences(buildOwnerReferenceTo(driverPod))
-              .addToLabels(driverPod.getMetadata().getLabels())
-              .build();
-      resource.setMetadata(metaData);
+              .addToOwnerReferences(buildOwnerReferenceTo(driverPod));
+      if (driverPodLabels != null) {
+        driverPodLabels.forEach(
+            (key, value) -> {
+              boolean resourceSetsKey = resourceLabels != null && 
resourceLabels.containsKey(key);
+              if (isManagedLabel(key) || !resourceSetsKey) {

Review Comment:
   When a resource sets a managed key (any `spark.operator/*`, `spark-role`, 
`spark-version`, `spark-app-selector`, `spark-app-name`) with a different 
value, its value is replaced silently. For example, `spark.operator/team: x` in 
`serviceMetadata` disappears if the driver pod has the same key. Shall we log 
the discarded value so users can find out why their label is not applied?



##########
spark-operator/src/main/java/org/apache/spark/k8s/operator/decorators/DriverResourceDecorator.java:
##########
@@ -45,10 +50,45 @@
 public class DriverResourceDecorator implements ResourceDecorator {
   private final Pod driverPod;
 
+  /**
+   * Spark-owned driver label for the application ID, used as the service 
selector. Spark sets it;
+   * the operator only propagates it. Package-private so tests can exercise 
the managed-label keys.
+   */
+  static final String LABEL_SPARK_APP_SELECTOR = "spark-app-selector";
+
+  /**
+   * Spark-owned driver label for the application name. Spark sets it; the 
operator only propagates
+   * it. Package-private so tests can exercise the managed-label keys.
+   */
+  static final String LABEL_SPARK_APP_NAME = "spark-app-name";
+
+  /**
+   * Operator- and Spark-managed identity labels whose driver-pod value must 
always propagate to
+   * secondary resources unchanged. A resource's own label of the same key 
must never override them.
+   * Any label key under the {@value #MANAGED_LABEL_PREFIX} prefix is 
protected the same way.
+   *
+   * <p>The Spark-owned keys mirror Spark's reserved preset labels; keep them 
in sync with
+   * {@code org.apache.spark.deploy.k8s.KubernetesDriverConf#labels} (its 
{@code presetLabels}).
+   */
+  private static final Set<String> MANAGED_LABEL_KEYS =
+      Set.of(
+          LABEL_SPARK_ROLE_NAME,
+          LABEL_SPARK_VERSION_NAME,
+          LABEL_SPARK_APP_SELECTOR,
+          LABEL_SPARK_APP_NAME);
+
+  /** Any label key starting with this prefix is treated as operator-managed 
and protected. */
+  private static final String MANAGED_LABEL_PREFIX = 
LABEL_SPARK_OPERATOR_PREFIX;

Review Comment:
   `MANAGED_LABEL_PREFIX` is only an alias of `LABEL_SPARK_OPERATOR_PREFIX`. 
Shall we use `LABEL_SPARK_OPERATOR_PREFIX` directly?



##########
spark-operator/src/test/java/org/apache/spark/k8s/operator/reconciler/reconcilesteps/AppInitStepTest.java:
##########
@@ -266,6 +266,97 @@ void 
createdPreResourcesPatchedWithOwnerReferencesToDriver() {
     
Assertions.assertTrue(decoratedConfigMap.getMetadata().getManagedFields().isEmpty());
   }
 
+  @Test
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  void preResourceCollidingLabelsAreResolvedBeforeServerSideApply() {

Review Comment:
   This copies about 50 lines of the mock setup of 
`createdPreResourcesPatchedWithOwnerReferencesToDriver`, and the label 
assertions repeat what `DriverResourceDecoratorTest` covers. Could you add the 
colliding labels to the existing test, or extract a shared helper, so that only 
the decorate-before-`serverSideApply` ordering is verified here?



-- 
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