1919
2020package org .apache .spark .k8s .operator .decorators ;
2121
22+ import static org .apache .spark .k8s .operator .Constants .LABEL_SPARK_APPLICATION_NAME ;
23+ import static org .apache .spark .k8s .operator .Constants .LABEL_SPARK_OPERATOR_NAME ;
24+ import static org .apache .spark .k8s .operator .Constants .LABEL_SPARK_ROLE_NAME ;
2225import static org .junit .jupiter .api .Assertions .assertEquals ;
2326import static org .junit .jupiter .api .Assertions .assertNotNull ;
2427import static org .junit .jupiter .api .Assertions .assertTrue ;
@@ -41,83 +44,100 @@ private static Pod driverPod() {
4144 .withNewMetadata ()
4245 .withName ("spark-driver" )
4346 .withUid ("driver-uid" )
44- .addToLabels ("spark-app-name" , "app1" )
45- .addToLabels ("istio.io/use-waypoint" , "none" )
47+ .addToLabels (LABEL_SPARK_OPERATOR_NAME , "spark-operator" )
48+ .addToLabels (LABEL_SPARK_APPLICATION_NAME , "app1" )
49+ // Prefix-matched managed label that is not one of the exact-match keys.
50+ .addToLabels ("spark.operator/submission-id" , "sub-1" )
51+ .addToLabels (LABEL_SPARK_ROLE_NAME , "driver" )
52+ .addToLabels ("spark-app-selector" , "spark-app-selector-1" )
53+ .addToLabels ("overlapping-label" , "driver-value" )
54+ .endMetadata ()
55+ .build ();
56+ }
57+
58+ /** Builds a Service carrying only the given metadata labels (decorator reads metadata only). */
59+ private static Service service (Map <String , String > labels ) {
60+ return new ServiceBuilder ()
61+ .withNewMetadata ()
62+ .withName ("app1-connect-svc" )
63+ .addToLabels (labels )
4664 .endMetadata ()
4765 .build ();
4866 }
4967
5068 /**
51- * Regression: a label set explicitly on the resource (e.g. a per-service waypoint label) must win
52- * over a driver-pod label of the same key, not be overwritten by it .
69+ * Driver-pod labels whose key is absent on the resource are propagated onto the resource, while a
70+ * label the resource sets itself still wins on key collision (e.g. a per-service waypoint label) .
5371 */
5472 @ Test
55- void perServiceLabelWinsOverDriverPodLabel () {
56- Service service =
57- new ServiceBuilder ()
58- .withNewMetadata ()
59- .withName ("app1-connect-svc" )
60- .addToLabels ("istio.io/use-waypoint" , "waypoint-ns1" )
61- .endMetadata ()
62- .withNewSpec ()
63- .endSpec ()
64- .build ();
73+ void driverLabelsPropagatedAndResourceLabelWinsOnCollision () {
74+ Service service = service (Map .of ("overlapping-label" , "resource-value" ));
6575
6676 new DriverResourceDecorator (driverPod ()).decorate (service );
6777
6878 assertEquals (
69- "waypoint-ns1" , service .getMetadata ().getLabels ().get ("istio.io/use-waypoint" ));
79+ Map .of (
80+ LABEL_SPARK_OPERATOR_NAME , "spark-operator" ,
81+ LABEL_SPARK_APPLICATION_NAME , "app1" ,
82+ "spark.operator/submission-id" , "sub-1" ,
83+ LABEL_SPARK_ROLE_NAME , "driver" ,
84+ "spark-app-selector" , "spark-app-selector-1" ,
85+ "overlapping-label" , "resource-value" ),
86+ service .getMetadata ().getLabels ());
7087 }
7188
72- /** Driver-pod labels whose key is absent on the resource are still propagated (GC identity) . */
89+ /** A resource whose label map is null inherits the driver-pod labels without an NPE . */
7390 @ Test
74- void driverPodOnlyLabelsArePropagated () {
75- Service service =
76- new ServiceBuilder ()
77- .withNewMetadata ()
78- .withName ("app1-connect-svc" )
79- .addToLabels ("istio.io/use-waypoint" , "waypoint-ns1" )
80- .endMetadata ()
81- .withNewSpec ()
82- .endSpec ()
83- .build ();
91+ void resourceWithoutLabelsInheritsDriverPodLabels () {
92+ Service service = service (Map .of ());
93+ // withNewMetadata() seeds an empty map, so clear it to exercise the null-labels path.
94+ service .getMetadata ().setLabels (null );
8495
8596 new DriverResourceDecorator (driverPod ()).decorate (service );
8697
8798 Map <String , String > labels = service .getMetadata ().getLabels ();
88- assertEquals ("app1" , labels .get ("spark-app-name" ));
89- assertEquals ("waypoint-ns1 " , labels .get ("istio.io/use-waypoint" ));
99+ assertEquals ("app1" , labels .get (LABEL_SPARK_APPLICATION_NAME ));
100+ assertEquals ("driver " , labels .get (LABEL_SPARK_ROLE_NAME ));
90101 }
91102
92- /** A resource with no labels of its own simply inherits the driver-pod labels (no NPE). */
103+ /**
104+ * Managed identity labels (exact-match keys and anything under the {@code spark.operator} prefix)
105+ * must keep the driver-pod value even when the resource sets a different value, so owner mapping
106+ * and selectors are preserved, while a non-managed label is still allowed to win.
107+ */
93108 @ Test
94- void resourceWithoutLabelsInheritsDriverPodLabels () {
109+ void managedLabelsAreNotOverriddenByResource () {
95110 Service service =
96- new ServiceBuilder ()
97- .withNewMetadata ()
98- .withName ("app1-driver-svc" )
99- .endMetadata ()
100- .withNewSpec ()
101- .endSpec ()
102- .build ();
111+ service (
112+ Map .of (
113+ LABEL_SPARK_OPERATOR_NAME , "rogue-operator" ,
114+ LABEL_SPARK_APPLICATION_NAME , "rogue-app" ,
115+ "spark.operator/submission-id" , "rogue-sub" ,
116+ LABEL_SPARK_ROLE_NAME , "rogue-role" ,
117+ "spark-app-selector" , "rogue-selector" ,
118+ "overlapping-label" , "resource-value" ));
103119
104120 new DriverResourceDecorator (driverPod ()).decorate (service );
105121
106- Map <String , String > labels = service .getMetadata ().getLabels ();
107- assertEquals ("none" , labels .get ("istio.io/use-waypoint" ));
108- assertEquals ("app1" , labels .get ("spark-app-name" ));
122+ assertEquals (
123+ Map .of (
124+ LABEL_SPARK_OPERATOR_NAME , "spark-operator" ,
125+ LABEL_SPARK_APPLICATION_NAME , "app1" ,
126+ "spark.operator/submission-id" , "sub-1" ,
127+ LABEL_SPARK_ROLE_NAME , "driver" ,
128+ "spark-app-selector" , "spark-app-selector-1" ,
129+ "overlapping-label" , "resource-value" ),
130+ service .getMetadata ().getLabels ());
109131 }
110132
133+ /**
134+ * A resource with no existing owner reference gets exactly one pointing at the driver pod (by
135+ * name and uid, with blockOwnerDeletion), which is what drives cascading garbage collection when
136+ * the driver pod is deleted.
137+ */
111138 @ Test
112139 void addsOwnerReferenceToDriver () {
113- Service service =
114- new ServiceBuilder ()
115- .withNewMetadata ()
116- .withName ("app1-connect-svc" )
117- .endMetadata ()
118- .withNewSpec ()
119- .endSpec ()
120- .build ();
140+ Service service = service (Map .of ());
121141
122142 new DriverResourceDecorator (driverPod ()).decorate (service );
123143
@@ -130,30 +150,31 @@ void addsOwnerReferenceToDriver() {
130150 }
131151
132152 /**
133- * When the resource already carries the driver-pod owner reference, decoration is a no-op, so an
134- * existing per-service label is left untouched (and no duplicate owner reference is added).
153+ * When the resource already carries the driver-pod owner reference, decoration is a no-op: the
154+ * labels are left exactly as they were (no driver-pod labels added) and no duplicate owner
155+ * reference is appended.
135156 */
136157 @ Test
137158 void skipsWhenOwnerReferenceAlreadyExists () {
138159 Service service =
139160 new ServiceBuilder ()
140161 .withNewMetadata ()
141162 .withName ("app1-connect-svc" )
142- .addToLabels ("istio.io/use-waypoint " , "waypoint-ns1 " )
163+ .addToLabels ("overlapping-label " , "resource-value " )
143164 .addNewOwnerReference ()
144165 .withKind ("Pod" )
145166 .withName ("spark-driver" )
146167 .withUid ("driver-uid" )
147168 .endOwnerReference ()
148169 .endMetadata ()
149- .withNewSpec ()
150- .endSpec ()
151170 .build ();
152171
153172 new DriverResourceDecorator (driverPod ()).decorate (service );
154173
155- assertEquals ( 1 , service . getMetadata (). getOwnerReferences (). size ());
174+ // Labels are untouched: no driver-pod labels were merged in.
156175 assertEquals (
157- "waypoint-ns1" , service .getMetadata ().getLabels ().get ("istio.io/use-waypoint" ));
176+ Map .of ("overlapping-label" , "resource-value" ), service .getMetadata ().getLabels ());
177+ // And no duplicate owner reference was appended.
178+ assertEquals (1 , service .getMetadata ().getOwnerReferences ().size ());
158179 }
159180}
0 commit comments