Repository: kubeflow/spark-operator

Issue:
Customize Ingress URL with Spark application ID
### What feature you would like to be added?

For now, users are able to customize driver Ingress URL by setting `--ingress-url-format="example.com/{{$appNamespace}}/{{$appId}}"` in Spark operator. It would be useful if we can customize ingress URL by Spark application ID, like `example.com/{{$appId}}'`.

### Why is this needed?

Customize Ingress URL with Spark app ID.

### Describe the solution you would like

_No response_

### Describe alternatives you have considered

_No response_

### Additional context

_No response_

### Love this feature?

Give it a 👍 We prioritize the features with most 👍

Discussion:

Solution patch:
diff --git a/.github/workflows/trivy-image-scanning.yaml b/.github/workflows/trivy-image-scanning.yaml
index 734e4cb..9747a6d 100644
--- a/.github/workflows/trivy-image-scanning.yaml
+++ b/.github/workflows/trivy-image-scanning.yaml
@@ -15,7 +15,7 @@ jobs:
         run: make print-IMAGE >> $GITHUB_ENV
 
       - name: trivy scan for github security tab
-        uses: aquasecurity/trivy-action@0.30.0
+        uses: aquasecurity/trivy-action@0.31.0
         with:
           image-ref: '${{ env.IMAGE }}'
           format: 'sarif'
diff --git a/charts/spark-operator-chart/templates/controller/_helpers.tpl b/charts/spark-operator-chart/templates/controller/_helpers.tpl
index ccb86da..ba8efc2 100644
--- a/charts/spark-operator-chart/templates/controller/_helpers.tpl
+++ b/charts/spark-operator-chart/templates/controller/_helpers.tpl
@@ -172,10 +172,11 @@ Create the role policy rules for the controller in every Spark job namespace
   - ingresses
   verbs:
   - get
-  - create
-  - delete
   - list
   - watch
+  - create
+  - update
+  - delete
 - apiGroups:
   - sparkoperator.k8s.io
   resources:
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 819a2f3..3818f9e 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -62,7 +62,6 @@ rules:
   - delete
   - get
   - list
-  - patch
   - update
   - watch
 - apiGroups:
diff --git a/internal/controller/sparkapplication/controller.go b/internal/controller/sparkapplication/controller.go
index b4741d2..9d02616 100644
--- a/internal/controller/sparkapplication/controller.go
+++ b/internal/controller/sparkapplication/controller.go
@@ -114,8 +114,8 @@ func NewReconciler(
 // +kubebuilder:rbac:groups=,resources=nodes,verbs=get
 // +kubebuilder:rbac:groups=,resources=events,verbs=create;update;patch
 // +kubebuilder:rbac:groups=,resources=resourcequotas,verbs=get;list;watch
-// +kubebuilder:rbac:groups=extensions,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=extensions,resources=ingresses,verbs=get;list;watch;create;update;delete
+// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;delete
 // +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get
 // +kubebuilder:rbac:groups=sparkoperator.k8s.io,resources=sparkapplications,verbs=get;list;watch;create;update;patch;delete
 // +kubebuilder:rbac:groups=sparkoperator.k8s.io,resources=sparkapplications/status,verbs=get;update;patch
@@ -297,9 +297,64 @@ func (r *Reconciler) reconcileSubmittedSparkApplication(ctx context.Context, req
 			if err := r.updateSparkApplicationState(ctx, app); err != nil {
 				return err
 			}
+
+			// Create web UI service for spark applications if enabled.
+			if r.options.EnableUIService {
+				service, err := r.createWebUIService(app)
+				if err != nil {
+					return fmt.Errorf("failed to create web UI service: %v", err)
+				}
+				app.Status.DriverInfo.WebUIServiceName = service.serviceName
+				app.Status.DriverInfo.WebUIPort = service.servicePort
+				app.Status.DriverInfo.WebUIAddress = fmt.Sprintf("%s:%d", service.serviceIP, app.Status.DriverInfo.WebUIPort)
+
+				// Create UI Ingress if ingress-format is set.
+				if r.options.IngressURLFormat != "" {
+					// We are going to want to use an ingress url.
+					ingressURL, err := getDriverIngressURL(r.options.IngressURLFormat, app)
+					if err != nil {
+						return fmt.Errorf("failed to get ingress url: %v", err)
+					}
+					// need to ensure the spark.ui variables are configured correctly if a subPath is used.
+					if ingressURL.Path != "" {
+						if app.Spec.SparkConf == nil {
+							app.Spec.SparkConf = make(map[string]string)
+						}
+						app.Spec.SparkConf[common.SparkUIProxyBase] = ingressURL.Path
+						app.Spec.SparkConf[common.SparkUIProxyRedirectURI] = "/"
+					}
+					ingress, err := r.createWebUIIngress(app, *service, ingressURL, r.options.IngressClassName)
+					if err != nil {
+						return fmt.Errorf("failed to create web UI ingress: %v", err)
+					}
+					app.Status.DriverInfo.WebUIIngressAddress = ingress.ingressURL.String()
+					app.Status.DriverInfo.WebUIIngressName = ingress.ingressName
+				}
+			}
+
+			for _, driverIngressConfiguration := range app.Spec.DriverIngressOptions {
+				service, err := r.createDriverIngressServiceFromConfiguration(app, &driverIngressConfiguration)
+				if err != nil {
+					return fmt.Errorf("failed to create driver ingress service for SparkApplication: %v", err)
+				}
+				// Create ingress if ingress-format is set.
+				if driverIngressConfiguration.IngressURLFormat != "" {
+					// We are going to want to use an ingress url.
+					ingressURL, err := getDriverIngressURL(driverIngressConfiguration.IngressURLFormat, app)
+					if err != nil {
+						return fmt.Errorf("failed to get driver ingress url: %v", err)
+					}
+					_, err = r.createDriverIngress(app, &driverIngressConfiguration, *service, ingressURL, r.options.IngressClassName)
+					if err != nil {
+						return fmt.Errorf("failed to create driver ingress: %v", err)
+					}
+				}
+			}
+
 			if err := r.updateSparkApplicationStatus(ctx, app); err != nil {
 				return err
 			}
+
 			return nil
 		},
 	)
@@ -687,63 +742,6 @@ func (r *Reconciler) submitSparkApplication(ctx context.Context, app *v1beta2.Sp
 		}
 	}
 
-	// Create web UI service for spark applications if enabled.
-	if r.options.EnableUIService {
-		service, err := r.createWebUIService(app)
-		if err != nil {
-			return fmt.Errorf("failed to create web UI service: %v", err)
-		}
-		app.Status.DriverInfo.WebUIServiceName = service.serviceName
-		app.Status.DriverInfo.WebUIPort = service.servicePort
-		app.Status.DriverInfo.WebUIAddress = fmt.Sprintf("%s:%d", service.serviceIP, app.Status.DriverInfo.WebUIPort)
-		logger.Info("Created web UI service for SparkApplication", "name", app.Name, "namespace", app.Namespace)
-
-		// Create UI Ingress if ingress-format is set.
-		if r.options.IngressURLFormat != "" {
-			// We are going to want to use an ingress url.
-			ingressURL, err := getDriverIngressURL(r.options.IngressURLFormat, app.Name, app.Namespace)
-			if err != nil {
-				return fmt.Errorf("failed to get ingress url: %v", err)
-			}
-			// need to ensure the spark.ui variables are configured correctly if a subPath is used.
-			if ingressURL.Path != "" {
-				if app.Spec.SparkConf == nil {
-					app.Spec.SparkConf = make(map[string]string)
-				}
-				app.Spec.SparkConf[common.SparkUIProxyBase] = ingressURL.Path
-				app.Spec.SparkConf[common.SparkUIProxyRedirectURI] = "/"
-			}
-			ingress, err := r.createWebUIIngress(app, *service, ingressURL, r.options.IngressClassName)
-			if err != nil {
-				return fmt.Errorf("failed to create web UI ingress: %v", err)
-			}
-			app.Status.DriverInfo.WebUIIngressAddress = ingress.ingressURL.String()
-			app.Status.DriverInfo.WebUIIngressName = ingress.ingressName
-			logger.Info("Created web UI ingress for SparkApplication", "name", app.Name, "namespace", app.Namespace)
-		}
-	}
-
-	for _, driverIngressConfiguration := range app.Spec.DriverIngressOptions {
-		logger.Info("Creating driver ingress service for SparkApplication", "name", app.Name, "namespace", app.Namespace)
-		service, err := r.createDriverIngressServiceFromConfiguration(app, &driverIngressConfiguration)
-		if err != nil {
-			return fmt.Errorf("failed to create driver ingress service for SparkApplication: %v", err)
-		}
-		// Create ingress if ingress-format is set.
-		if driverIngressConfiguration.IngressURLFormat != "" {
-			// We are going to want to use an ingress url.
-			ingressURL, err := getDriverIngressURL(driverIngressConfiguration.IngressURLFormat, app.Name, app.Namespace)
-			if err != nil {
-				return fmt.Errorf("failed to get driver ingress url: %v", err)
-			}
-			ingress, err := r.createDriverIngress(app, &driverIngressConfiguration, *service, ingressURL, r.options.IngressClassName)
-			if err != nil {
-				return fmt.Errorf("failed to create driver ingress: %v", err)
-			}
-			logger.V(1).Info("Created driver ingress for SparkApplication", "name", app.Name, "namespace", app.Namespace, "ingressName", ingress.ingressName, "ingressURL", ingress.ingressURL)
-		}
-	}
-
 	defer func() {
 		if err := r.cleanUpPodTemplateFiles(app); err != nil {
 			logger.Error(fmt.Errorf("failed to clean up pod template files: %v", err), "name", app.Name, "namespace", app.Namespace)
diff --git a/internal/controller/sparkapplication/driveringress.go b/internal/controller/sparkapplication/driveringress.go
index 7416308..dab7155 100644
--- a/internal/controller/sparkapplication/driveringress.go
+++ b/internal/controller/sparkapplication/driveringress.go
@@ -55,11 +55,20 @@ type SparkIngress struct {
 	ingressTLS       []networkingv1.IngressTLS
 }
 
-var ingressAppNameURLRegex = regexp.MustCompile(`{{\s*[$]appName\s*}}`)
-var ingressAppNamespaceURLRegex = regexp.MustCompile(`{{\s*[$]appNamespace\s*}}`)
+var (
+	ingressAppNameURLRegex      = regexp.MustCompile(`{{\s*[$]appName\s*}}`)
+	ingressAppNamespaceURLRegex = regexp.MustCompile(`{{\s*[$]appNamespace\s*}}`)
+	ingressAppIdURLRegex        = regexp.MustCompile(`{{\s*[$]appId\s*}}`)
+)
+
+func getDriverIngressURL(ingressURLFormat string, app *v1beta2.SparkApplication) (*url.URL, error) {
+	if app == nil {
+		return nil, fmt.Errorf("app cannot be nil")
+	}
 
-func getDriverIngressURL(ingressURLFormat string, appName string, appNamespace string) (*url.URL, error) {
-	ingressURL := ingressAppNamespaceURLRegex.ReplaceAllString(ingressAppNameURLRegex.ReplaceAllString(ingressURLFormat, appName), appNamespace)
+	ingressURL := ingressAppNameURLRegex.ReplaceAllString(ingressURLFormat, app.Name)
+	ingressURL = ingressAppNamespaceURLRegex.ReplaceAllString(ingressURL, app.Namespace)
+	ingressURL = ingressAppIdURLRegex.ReplaceAllString(ingressURL, app.Status.SparkApplicationID)
 	parsedURL, err := url.Parse(ingressURL)
 	if err != nil {
 		return nil, err
@@ -145,10 +154,19 @@ func (r *Reconciler) createDriverIngressV1(app *v1beta2.SparkApplication, servic
 		ingress.Spec.IngressClassName = &ingressClassName
 	}
 
-	logger.Info("Creating networking.v1/Ingress for SparkApplication web UI", "name", app.Name, "namespace", app.Namespace, "ingressName", ingress.Name)
 	if err := r.client.Create(context.TODO(), ingress); err != nil {
-		return nil, fmt.Errorf("failed to create ingress %s/%s: %v", ingress.Namespace, ingress.Name, err)
+		if !errors.IsAlreadyExists(err) {
+			return nil, fmt.Errorf("failed to create ingress %s/%s: %v", ingress.Namespace, ingress.Name, err)
+		}
+
+		if err := r.client.Update(context.TODO(), ingress); err != nil {
+			return nil, fmt.Errorf("failed to update ingress %s/%s: %v", ingress.Namespace, ingress.Name, err)
+		}
+		logger.Info("Updated networking.v1/Ingress for SparkApplication", "name", app.Name, "namespace", app.Namespace, "ingressName", ingress.Name)
+	} else {
+		logger.Info("Created networking.v1/Ingress for SparkApplication", "name", app.Name, "namespace", app.Namespace, "ingressName", ingress.Name)
 	}
+
 	return &SparkIngress{
 		ingressName:      ingress.Name,
 		ingressURL:       ingressURL,
@@ -212,10 +230,19 @@ func (r *Reconciler) createDriverIngressLegacy(app *v1beta2.SparkApplication, se
 	if len(ingressTLSHosts) != 0 {
 		ingress.Spec.TLS = convertIngressTLSHostsToLegacy(ingressTLSHosts)
 	}
-	logger.Info("Creating extensions.v1beta1/Ingress for SparkApplication web UI", "name", app.Name, "namespace", app.Namespace, "ingressName", ingress.Name)
 	if err := r.client.Create(context.TODO(), ingress); err != nil {
-		return nil, fmt.Errorf("failed to create ingress %s/%s: %v", ingress.Namespace, ingress.Name, err)
+		if !errors.IsAlreadyExists(err) {
+			return nil, fmt.Errorf("failed to create ingress %s/%s: %v", ingress.Namespace, ingress.Name, err)
+		}
+
+		if err := r.client.Update(context.TODO(), ingress); err != nil {
+			return nil, fmt.Errorf("failed to update ingress %s/%s: %v", ingress.Namespace, ingress.Name, err)
+		}
+		logger.Info("Updated extensions.v1beta1/Ingress for SparkApplication", "name", app.Name, "namespace", app.Namespace, "ingressName", ingress.Name)
+	} else {
+		logger.Info("Created extensions.v1beta1/Ingress for SparkApplication", "name", app.Name, "namespace", app.Namespace, "ingressName", ingress.Name)
 	}
+
 	return &SparkIngress{
 		ingressName: ingress.Name,
 		ingressURL:  ingressURL,
@@ -288,6 +315,9 @@ func (r *Reconciler) createDriverIngressService(
 		if err := r.client.Update(context.TODO(), service); err != nil {
 			return nil, err
 		}
+		logger.Info("Updated service for SparkApplication", "name", app.Name, "namespace", app.Namespace, "serviceName", service.Name)
+	} else {
+		logger.Info("Created service for SparkApplication", "name", app.Name, "namespace", app.Namespace, "serviceName", service.Name)
 	}
 
 	return &SparkService{



Test patch:
diff --git a/internal/controller/sparkapplication/driveringress_test.go b/internal/controller/sparkapplication/driveringress_test.go
index 498ecc3..6b8ed50 100644
--- a/internal/controller/sparkapplication/driveringress_test.go
+++ b/internal/controller/sparkapplication/driveringress_test.go
@@ -16,698 +16,396 @@ limitations under the License.
 
 package sparkapplication
 
-// func TestCreateDriverIngressService(t *testing.T) {
-// 	type testcase struct {
-// 		name             string
-// 		app              *v1beta2.SparkApplication
-// 		expectedServices []SparkService
-// 		expectedSelector map[string]string
-// 		expectError      bool
-// 	}
-// 	testFn := func(test testcase, t *testing.T) {
-// 		fakeClient := fake.NewSimpleClientset()
-// 		util.IngressCapabilities = map[string]bool{"networking.k8s.io/v1": true}
-// 		if len(test.expectedServices) != len(test.app.Spec.DriverIngressOptions) {
-// 			t.Errorf("%s: size of test.expectedServices (%d) and test.app.Spec.DriverIngressOptions (%d) is different for %s",
-// 				test.name, len(test.expectedServices), len(test.app.Spec.DriverIngressOptions), test.app.Name)
-// 		}
-// 		for i, driverIngressConfiguration := range test.app.Spec.DriverIngressOptions {
-// 			sparkService, err := createDriverIngressServiceFromConfiguration(test.app, &driverIngressConfiguration, fakeClient)
-// 			if err != nil {
-// 				if test.expectError {
-// 					return
-// 				}
-// 				t.Fatal(err)
-// 			}
-// 			expectedService := test.expectedServices[i]
-// 			if sparkService.serviceName != expectedService.serviceName {
-// 				t.Errorf("%s: for service name wanted %s got %s", test.name, expectedService.serviceName, sparkService.serviceName)
-// 			}
-// 			service, err := fakeClient.CoreV1().
-// 				Services(test.app.Namespace).
-// 				Get(context.TODO(), sparkService.serviceName, metav1.GetOptions{})
-// 			if err != nil {
-// 				if test.expectError {
-// 					return
-// 				}
-// 				t.Fatal(err)
-// 			}
-// 			if service.Labels[common.SparkAppNameLabel] != test.app.Name {
-// 				t.Errorf("%s: service of app %s has the wrong labels", test.name, test.app.Name)
-// 			}
-// 			if !reflect.DeepEqual(test.expectedSelector, service.Spec.Selector) {
-// 				t.Errorf("%s: for label selector wanted %s got %s", test.name, test.expectedSelector, service.Spec.Selector)
-// 			}
-// 			if service.Spec.Type != expectedService.serviceType {
-// 				t.Errorf("%s: for service type wanted %s got %s", test.name, expectedService.serviceType, service.Spec.Type)
-// 			}
-// 			if len(service.Spec.Ports) != 1 {
-// 				t.Errorf("%s: wanted a single port got %d ports", test.name, len(service.Spec.Ports))
-// 			}
-// 			port := service.Spec.Ports[0]
-// 			if port.Port != expectedService.servicePort {
-// 				t.Errorf("%s: unexpected port wanted %d got %d", test.name, expectedService.servicePort, port.Port)
-// 			}
-// 			if port.Name != expectedService.servicePortName {
-// 				t.Errorf("%s: unexpected port name wanted %s got %s", test.name, expectedService.servicePortName, port.Name)
-// 			}
-// 			serviceAnnotations := service.ObjectMeta.Annotations
-// 			if !reflect.DeepEqual(serviceAnnotations, expectedService.serviceAnnotations) {
-// 				t.Errorf("%s: unexpected annotations wanted %s got %s", test.name, expectedService.serviceAnnotations, serviceAnnotations)
-// 			}
-// 			serviceLabels := service.ObjectMeta.Labels
-// 			if !reflect.DeepEqual(serviceLabels, expectedService.serviceLabels) {
-// 				t.Errorf("%s: unexpected labels wanted %s got %s", test.name, expectedService.serviceLabels, serviceLabels)
-// 			}
-// 		}
-// 	}
-// 	serviceNameFormat := "%s-driver-%d"
-// 	portNameFormat := "driver-ing-%d"
-// 	app1 := &v1beta2.SparkApplication{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name:      "foo1",
-// 			Namespace: "default",
-// 			UID:       "foo-123",
-// 		},
-// 		Spec: v1beta2.SparkApplicationSpec{
-// 			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
-// 				{
-// 					ServicePort: util.Int32Ptr(8888),
-// 				},
-// 			},
-// 		},
-// 		Status: v1beta2.SparkApplicationStatus{
-// 			SparkApplicationID: "foo-1",
-// 			ExecutionAttempts:  1,
-// 		},
-// 	}
-// 	app2 := &v1beta2.SparkApplication{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name:      "foo2",
-// 			Namespace: "default",
-// 			UID:       "foo-123",
-// 		},
-// 		Spec: v1beta2.SparkApplicationSpec{
-// 			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
-// 				{
-// 					ServicePort: util.Int32Ptr(8888),
-// 				},
-// 			},
-// 		},
-// 		Status: v1beta2.SparkApplicationStatus{
-// 			SparkApplicationID: "foo-2",
-// 			ExecutionAttempts:  2,
-// 		},
-// 	}
-// 	app3 := &v1beta2.SparkApplication{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name:      "foo3",
-// 			Namespace: "default",
-// 			UID:       "foo-123",
-// 		},
-// 		Spec: v1beta2.SparkApplicationSpec{
-// 			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
-// 				{
-// 					ServicePort: nil,
-// 				},
-// 			},
-// 		},
-// 		Status: v1beta2.SparkApplicationStatus{
-// 			SparkApplicationID: "foo-3",
-// 		},
-// 	}
-// 	var appPort int32 = 80
-// 	app4 := &v1beta2.SparkApplication{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name:      "foo4",
-// 			Namespace: "default",
-// 			UID:       "foo-123",
-// 		},
-// 		Spec: v1beta2.SparkApplicationSpec{
-// 			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
-// 				{
-// 					ServicePort: &appPort,
-// 				},
-// 			},
-// 			SparkConf: map[string]string{
-// 				sparkUIPortConfigurationKey: "4041",
-// 			},
-// 		},
-// 		Status: v1beta2.SparkApplicationStatus{
-// 			SparkApplicationID: "foo-3",
-// 		},
-// 	}
-// 	var serviceTypeNodePort apiv1.ServiceType = apiv1.ServiceTypeNodePort
-// 	app5 := &v1beta2.SparkApplication{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name:      "foo5",
-// 			Namespace: "default",
-// 			UID:       "foo-123",
-// 		},
-// 		Spec: v1beta2.SparkApplicationSpec{
-// 			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
-// 				{
-// 					ServicePort: util.Int32Ptr(8888),
-// 					ServiceType: &serviceTypeNodePort,
-// 				},
-// 			},
-// 		},
-// 		Status: v1beta2.SparkApplicationStatus{
-// 			SparkApplicationID: "foo-2",
-// 			ExecutionAttempts:  2,
-// 		},
-// 	}
-// 	appPortName := "http-spark-test"
-// 	app6 := &v1beta2.SparkApplication{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name:      "foo6",
-// 			Namespace: "default",
-// 			UID:       "foo-123",
-// 		},
-// 		Spec: v1beta2.SparkApplicationSpec{
-// 			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
-// 				{
-// 					ServicePort:     &appPort,
-// 					ServicePortName: &appPortName,
-// 				},
-// 			},
-// 		},
-// 		Status: v1beta2.SparkApplicationStatus{
-// 			SparkApplicationID: "foo-6",
-// 		},
-// 	}
-// 	app7 := &v1beta2.SparkApplication{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name:      "foo7",
-// 			Namespace: "default",
-// 			UID:       "foo-123",
-// 		},
-// 		Spec: v1beta2.SparkApplicationSpec{
-// 			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
-// 				{
-// 					ServicePort: util.Int32Ptr(8888),
-// 					ServiceAnnotations: map[string]string{
-// 						"key": "value",
-// 					},
-// 				},
-// 			},
-// 		},
-// 		Status: v1beta2.SparkApplicationStatus{
-// 			SparkApplicationID: "foo-7",
-// 			ExecutionAttempts:  1,
-// 		},
-// 	}
-// 	app8 := &v1beta2.SparkApplication{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name:      "foo8",
-// 			Namespace: "default",
-// 			UID:       "foo-123",
-// 		},
-// 		Spec: v1beta2.SparkApplicationSpec{
-// 			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
-// 				{
-// 					ServicePort: util.Int32Ptr(8888),
-// 					ServiceLabels: map[string]string{
-// 						"sparkoperator.k8s.io/app-name": "foo8",
-// 						"key":                           "value",
-// 					},
-// 				},
-// 			},
-// 		},
-// 		Status: v1beta2.SparkApplicationStatus{
-// 			SparkApplicationID: "foo-8",
-// 			ExecutionAttempts:  1,
-// 		},
-// 	}
-// 	testcases := []testcase{
-// 		{
-// 			name: "service with custom serviceport and serviceport and target port are same",
-// 			app:  app1,
-// 			expectedServices: []SparkService{
-// 				{
-// 					serviceName:     fmt.Sprintf(serviceNameFormat, app1.GetName(), *app1.Spec.DriverIngressOptions[0].ServicePort),
-// 					serviceType:     apiv1.ServiceTypeClusterIP,
-// 					servicePortName: fmt.Sprintf(portNameFormat, *app1.Spec.DriverIngressOptions[0].ServicePort),
-// 					servicePort:     *app1.Spec.DriverIngressOptions[0].ServicePort,
-// 					serviceLabels: map[string]string{
-// 						"sparkoperator.k8s.io/app-name": "foo1",
-// 					},
-// 					targetPort: intstr.IntOrString{
-// 						Type:   intstr.Int,
-// 						IntVal: int32(*app1.Spec.DriverIngressOptions[0].ServicePort),
-// 					},
-// 				},
-// 			},
-// 			expectedSelector: map[string]string{
-// 				common.SparkAppNameLabel: "foo1",
-// 				common.SparkRoleLabel:    common.SparkDriverRole,
-// 			},
-// 			expectError: false,
-// 		},
-// 		{
-// 			name: "service with default port",
-// 			app:  app2,
-// 			expectedServices: []SparkService{
-// 				{
-// 					serviceName:     fmt.Sprintf(serviceNameFormat, app2.GetName(), *app2.Spec.DriverIngressOptions[0].ServicePort),
-// 					serviceType:     apiv1.ServiceTypeClusterIP,
-// 					servicePortName: fmt.Sprintf(portNameFormat, *app2.Spec.DriverIngressOptions[0].ServicePort),
-// 					servicePort:     int32(*app2.Spec.DriverIngressOptions[0].ServicePort),
-// 					serviceLabels: map[string]string{
-// 						"sparkoperator.k8s.io/app-name": "foo2",
-// 					},
-// 				},
-// 			},
-// 			expectedSelector: map[string]string{
-// 				common.SparkAppNameLabel: "foo2",
-// 				common.SparkRoleLabel:    common.SparkDriverRole,
-// 			},
-// 			expectError: false,
-// 		},
-// 		{
-// 			name: "service with custom serviceport and serviceport and target port are different",
-// 			app:  app4,
-// 			expectedServices: []SparkService{
-// 				{
-// 					serviceName:     fmt.Sprintf(serviceNameFormat, app4.GetName(), *app4.Spec.DriverIngressOptions[0].ServicePort),
-// 					serviceType:     apiv1.ServiceTypeClusterIP,
-// 					servicePortName: fmt.Sprintf(portNameFormat, *app4.Spec.DriverIngressOptions[0].ServicePort),
-// 					servicePort:     80,
-// 					serviceLabels: map[string]string{
-// 						"sparkoperator.k8s.io/app-name": "foo4",
-// 					},
-// 					targetPort: intstr.IntOrString{
-// 						Type:   intstr.Int,
-// 						IntVal: int32(4041),
-// 					},
-// 				},
-// 			},
-// 			expectedSelector: map[string]string{
-// 				common.SparkAppNameLabel: "foo4",
-// 				common.SparkRoleLabel:    common.SparkDriverRole,
-// 			},
-// 			expectError: false,
-// 		},
-// 		{
-// 			name: "service with custom servicetype",
-// 			app:  app5,
-// 			expectedServices: []SparkService{
-// 				{
-// 					serviceName:     fmt.Sprintf(serviceNameFormat, app5.GetName(), *app5.Spec.DriverIngressOptions[0].ServicePort),
-// 					serviceType:     apiv1.ServiceTypeNodePort,
-// 					servicePortName: fmt.Sprintf(portNameFormat, *app5.Spec.DriverIngressOptions[0].ServicePort),
-// 					servicePort:     *app5.Spec.DriverIngressOptions[0].ServicePort,
-// 					serviceLabels: map[string]string{
-// 						"sparkoperator.k8s.io/app-name": "foo5",
-// 					},
-// 				},
-// 			},
-// 			expectedSelector: map[string]string{
-// 				common.SparkAppNameLabel: "foo5",
-// 				common.SparkRoleLabel:    common.SparkDriverRole,
-// 			},
-// 			expectError: false,
-// 		},
-// 		{
-// 			name: "service with custom serviceportname",
-// 			app:  app6,
-// 			expectedServices: []SparkService{
-// 				{
-// 					serviceName:     fmt.Sprintf(serviceNameFormat, app6.GetName(), *app6.Spec.DriverIngressOptions[0].ServicePort),
-// 					serviceType:     apiv1.ServiceTypeClusterIP,
-// 					servicePortName: "http-spark-test",
-// 					servicePort:     int32(80),
-// 					serviceLabels: map[string]string{
-// 						"sparkoperator.k8s.io/app-name": "foo6",
-// 					},
-// 				},
-// 			},
-// 			expectedSelector: map[string]string{
-// 				common.SparkAppNameLabel: "foo6",
-// 				common.SparkRoleLabel:    common.SparkDriverRole,
-// 			},
-// 			expectError: false,
-// 		},
-// 		{
-// 			name: "service with annotation",
-// 			app:  app7,
-// 			expectedServices: []SparkService{
-// 				{
-// 					serviceName:     fmt.Sprintf(serviceNameFormat, app7.GetName(), *app7.Spec.DriverIngressOptions[0].ServicePort),
-// 					serviceType:     apiv1.ServiceTypeClusterIP,
-// 					servicePortName: fmt.Sprintf(portNameFormat, *app7.Spec.DriverIngressOptions[0].ServicePort),
-// 					servicePort:     *app7.Spec.DriverIngressOptions[0].ServicePort,
-// 					serviceAnnotations: map[string]string{
-// 						"key": "value",
-// 					},
-// 					serviceLabels: map[string]string{
-// 						"sparkoperator.k8s.io/app-name": "foo7",
-// 					},
-// 					targetPort: intstr.IntOrString{
-// 						Type:   intstr.Int,
-// 						IntVal: int32(4041),
-// 					},
-// 				},
-// 			},
-// 			expectedSelector: map[string]string{
-// 				common.SparkAppNameLabel: "foo7",
-// 				common.SparkRoleLabel:    common.SparkDriverRole,
-// 			},
-// 			expectError: false,
-// 		},
-// 		{
-// 			name: "service with custom labels",
-// 			app:  app8,
-// 			expectedServices: []SparkService{
-// 				{
-// 					serviceName:     fmt.Sprintf(serviceNameFormat, app8.GetName(), *app8.Spec.DriverIngressOptions[0].ServicePort),
-// 					serviceType:     apiv1.ServiceTypeClusterIP,
-// 					servicePortName: fmt.Sprintf(portNameFormat, *app8.Spec.DriverIngressOptions[0].ServicePort),
-// 					servicePort:     *app8.Spec.DriverIngressOptions[0].ServicePort,
-// 					serviceLabels: map[string]string{
-// 						"sparkoperator.k8s.io/app-name": "foo8",
-// 						"key":                           "value",
-// 					},
-// 					targetPort: intstr.IntOrString{
-// 						Type:   intstr.Int,
-// 						IntVal: int32(4041),
-// 					},
-// 				},
-// 			},
-// 			expectedSelector: map[string]string{
-// 				common.SparkAppNameLabel: "foo8",
-// 				common.SparkRoleLabel:    common.SparkDriverRole,
-// 			},
-// 			expectError: false,
-// 		},
-// 		{
-// 			name:             "service with bad port configurations",
-// 			app:              app3,
-// 			expectError:      true,
-// 			expectedServices: []SparkService{{}},
-// 		},
-// 	}
-// 	for _, test := range testcases {
-// 		testFn(test, t)
-// 	}
-// }
+import (
+	"fmt"
+	"net/url"
+	"testing"
 
-// func TestCreateDriverIngress(t *testing.T) {
-// 	type testcase struct {
-// 		name              string
-// 		app               *v1beta2.SparkApplication
-// 		expectedIngresses []SparkIngress
-// 		expectError       bool
-// 	}
+	"github.com/kubeflow/spark-operator/v2/api/v1beta2"
+	"github.com/kubeflow/spark-operator/v2/pkg/common"
+	"github.com/kubeflow/spark-operator/v2/pkg/util"
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+)
 
-// 	testFn := func(test testcase, t *testing.T, ingressURLFormat string, ingressClassName string) {
-// 		fakeClient := fake.NewSimpleClientset()
-// 		if len(test.expectedIngresses) != len(test.app.Spec.DriverIngressOptions) {
-// 			t.Errorf("%s: size of test.expectedIngresses (%d) and test.app.Spec.DriverIngressOptions (%d) is different for %s",
-// 				test.name, len(test.expectedIngresses), len(test.app.Spec.DriverIngressOptions), test.app.Name)
-// 		}
-// 		for i, driverIngressConfiguration := range test.app.Spec.DriverIngressOptions {
-// 			sparkService, err := createDriverIngressServiceFromConfiguration(test.app, &driverIngressConfiguration, fakeClient)
-// 			if err != nil {
-// 				t.Fatal(err)
-// 			}
-// 			ingressURL, err := getDriverIngressURL(ingressURLFormat, test.app.Name, test.app.Namespace)
-// 			if err != nil {
-// 				t.Fatal(err)
-// 			}
-// 			sparkIngress, err := createDriverIngress(test.app, &driverIngressConfiguration, *sparkService, ingressURL, ingressClassName, fakeClient)
-// 			if err != nil {
-// 				if test.expectError {
-// 					return
-// 				}
-// 				t.Fatal(err)
-// 			}
-// 			expectedIngress := test.expectedIngresses[i]
-// 			if sparkIngress.ingressName != expectedIngress.ingressName {
-// 				t.Errorf("Ingress name wanted %s got %s", expectedIngress.ingressName, sparkIngress.ingressName)
-// 			}
-// 			if sparkIngress.ingressURL.String() != expectedIngress.ingressURL.String() {
-// 				t.Errorf("Ingress URL wanted %s got %s", expectedIngress.ingressURL, sparkIngress.ingressURL)
-// 			}
-// 			ingress, err := fakeClient.NetworkingV1().Ingresses(test.app.Namespace).
-// 				Get(context.TODO(), sparkIngress.ingressName, metav1.GetOptions{})
-// 			if err != nil {
-// 				t.Fatal(err)
-// 			}
-// 			if len(ingress.Annotations) != 0 {
-// 				for key, value := range ingress.Annotations {
-// 					if expectedIngress.annotations[key] != ingress.Annotations[key] {
-// 						t.Errorf("Expected annotation: %s=%s but found : %s=%s", key, value, key, ingress.Annotations[key])
-// 					}
-// 				}
-// 			}
-// 			if len(ingress.Spec.TLS) != 0 {
-// 				for _, ingressTls := range ingress.Spec.TLS {
-// 					if ingressTls.Hosts[0] != expectedIngress.ingressTLS[0].Hosts[0] {
-// 						t.Errorf("Expected ingressTls host: %s but found : %s", expectedIngress.ingressTLS[0].Hosts[0], ingressTls.Hosts[0])
-// 					}
-// 					if ingressTls.SecretName != expectedIngress.ingressTLS[0].SecretName {
-// 						t.Errorf("Expected ingressTls secretName: %s but found : %s", expectedIngress.ingressTLS[0].SecretName, ingressTls.SecretName)
-// 					}
-// 				}
-// 			}
-// 			if ingress.Labels[common.SparkAppNameLabel] != test.app.Name {
-// 				t.Errorf("Ingress of app %s has the wrong labels", test.app.Name)
-// 			}
+func TestCreateDriverIngressService(t *testing.T) {
+	type testcase struct {
+		name             string
+		app              *v1beta2.SparkApplication
+		expectedServices []SparkService
+		expectedSelector map[string]string
+		expectError      bool
+	}
+	testFn := func(tc testcase, t *testing.T) {
+		fakeClient := fake.NewFakeClient()
+		reconciler := Reconciler{
+			client: fakeClient,
+		}
+		t.Logf("test case: %s", tc.name)
 
-// 			if len(ingress.Spec.Rules) != 1 {
-// 				t.Errorf("No Ingress rules found.")
-// 			}
-// 			ingressRule := ingress.Spec.Rules[0]
-// 			// If we have a path, then the ingress adds capture groups
-// 			if ingressRule.IngressRuleValue.HTTP.Paths[0].Path != "" && ingressRule.IngressRuleValue.HTTP.Paths[0].Path != "/" {
-// 				expectedIngress.ingressURL.Path = expectedIngress.ingressURL.Path + "(/|$)(.*)"
-// 			}
-// 			if ingressRule.Host+ingressRule.IngressRuleValue.HTTP.Paths[0].Path != expectedIngress.ingressURL.Host+expectedIngress.ingressURL.Path {
-// 				t.Errorf("Ingress of app %s has the wrong host %s", ingressRule.Host+ingressRule.IngressRuleValue.HTTP.Paths[0].Path, expectedIngress.ingressURL.Host+expectedIngress.ingressURL.Path)
-// 			}
+		if len(tc.app.Spec.DriverIngressOptions) == 0 {
+			t.Fatalf("Test case %s has no DriverIngressOptions", tc.name)
+		}
 
-// 			if len(ingressRule.IngressRuleValue.HTTP.Paths) != 1 {
-// 				t.Errorf("No Ingress paths found.")
-// 			}
-// 			ingressPath := ingressRule.IngressRuleValue.HTTP.Paths[0]
-// 			if ingressPath.Backend.Service.Name != sparkService.serviceName {
-// 				t.Errorf("Service name wanted %s got %s", sparkService.serviceName, ingressPath.Backend.Service.Name)
-// 			}
-// 			if *ingressPath.PathType != networkingv1.PathTypeImplementationSpecific {
-// 				t.Errorf("PathType wanted %s got %s", networkingv1.PathTypeImplementationSpecific, *ingressPath.PathType)
-// 			}
-// 			if ingressPath.Backend.Service.Port.Number != sparkService.servicePort {
-// 				t.Errorf("Service port wanted %v got %v", sparkService.servicePort, ingressPath.Backend.Service.Port.Number)
-// 			}
-// 		}
-// 	}
+		ingressOptions := tc.app.Spec.DriverIngressOptions[0]
+		ingressConfig, err := reconciler.createDriverIngressServiceFromConfiguration(tc.app, &ingressOptions)
 
-// 	ingressNameFormat := "%s-ing-%d"
-// 	var appPort int32 = 80
-// 	app1 := &v1beta2.SparkApplication{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name:      "foo",
-// 			Namespace: "default",
-// 			UID:       "foo-123",
-// 		},
-// 		Spec: v1beta2.SparkApplicationSpec{
-// 			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
-// 				{
-// 					ServicePort: &appPort,
-// 				},
-// 			},
-// 		},
-// 		Status: v1beta2.SparkApplicationStatus{
-// 			SparkApplicationID: "foo-1",
-// 			DriverInfo: v1beta2.DriverInfo{
-// 				WebUIServiceName: "blah-service",
-// 			},
-// 		},
-// 	}
-// 	app2 := &v1beta2.SparkApplication{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name:      "foo",
-// 			Namespace: "default",
-// 			UID:       "foo-123",
-// 		},
-// 		Spec: v1beta2.SparkApplicationSpec{
-// 			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
-// 				{
-// 					ServicePort: &appPort,
-// 					IngressAnnotations: map[string]string{
-// 						"kubernetes.io/ingress.class":                    "nginx",
-// 						"nginx.ingress.kubernetes.io/force-ssl-redirect": "true",
-// 					},
-// 				},
-// 			},
-// 		},
-// 		Status: v1beta2.SparkApplicationStatus{
-// 			SparkApplicationID: "foo-1",
-// 			DriverInfo: v1beta2.DriverInfo{
-// 				WebUIServiceName: "blah-service",
-// 			},
-// 		},
-// 	}
-// 	app3 := &v1beta2.SparkApplication{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name:      "foo",
-// 			Namespace: "default",
-// 			UID:       "foo-123",
-// 		},
-// 		Spec: v1beta2.SparkApplicationSpec{
-// 			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
-// 				{
-// 					ServicePort: &appPort,
-// 					IngressAnnotations: map[string]string{
-// 						"kubernetes.io/ingress.class":                    "nginx",
-// 						"nginx.ingress.kubernetes.io/force-ssl-redirect": "true",
-// 					},
-// 					IngressTLS: []networkingv1.IngressTLS{
-// 						{Hosts: []string{"host1", "host2"}, SecretName: "secret"},
-// 					},
-// 				},
-// 			},
-// 		},
-// 		Status: v1beta2.SparkApplicationStatus{
-// 			SparkApplicationID: "foo-1",
-// 			DriverInfo: v1beta2.DriverInfo{
-// 				WebUIServiceName: "blah-service",
-// 			},
-// 		},
-// 	}
-// 	app4 := &v1beta2.SparkApplication{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name:      "foo",
-// 			Namespace: "default",
-// 			UID:       "foo-123",
-// 		},
-// 		Spec: v1beta2.SparkApplicationSpec{
-// 			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
-// 				{
-// 					ServicePort: &appPort,
-// 					IngressAnnotations: map[string]string{
-// 						"kubernetes.io/ingress.class": "nginx",
-// 					},
-// 					IngressTLS: []networkingv1.IngressTLS{
-// 						{Hosts: []string{"host1", "host2"}, SecretName: ""},
-// 					},
-// 				},
-// 			},
-// 		},
-// 		Status: v1beta2.SparkApplicationStatus{
-// 			SparkApplicationID: "foo-1",
-// 			DriverInfo: v1beta2.DriverInfo{
-// 				WebUIServiceName: "blah-service",
-// 			},
-// 		},
-// 	}
+		if tc.expectError {
+			assert.Error(t, err, "Expected an error but got none")
+			return
+		}
 
-// 	testcases := []testcase{
-// 		{
-// 			name: "simple ingress object",
-// 			app:  app1,
-// 			expectedIngresses: []SparkIngress{
-// 				{
-// 					ingressName: fmt.Sprintf(ingressNameFormat, app1.GetName(), *app1.Spec.DriverIngressOptions[0].ServicePort),
-// 					ingressURL:  parseURLAndAssertError(app1.GetName()+".ingress.clusterName.com", t),
-// 				},
-// 			},
-// 			expectError: false,
-// 		},
-// 		{
-// 			name: "ingress with annotations and without tls configuration",
-// 			app:  app2,
-// 			expectedIngresses: []SparkIngress{
-// 				{
-// 					ingressName: fmt.Sprintf(ingressNameFormat, app2.GetName(), *app2.Spec.DriverIngressOptions[0].ServicePort),
-// 					ingressURL:  parseURLAndAssertError(app2.GetName()+".ingress.clusterName.com", t),
-// 					annotations: map[string]string{
-// 						"kubernetes.io/ingress.class":                    "nginx",
-// 						"nginx.ingress.kubernetes.io/force-ssl-redirect": "true",
-// 					},
-// 				},
-// 			},
-// 			expectError: false,
-// 		},
-// 		{
-// 			name: "ingress with annotations and tls configuration",
-// 			app:  app3,
-// 			expectedIngresses: []SparkIngress{
-// 				{
-// 					ingressName: fmt.Sprintf(ingressNameFormat, app3.GetName(), *app3.Spec.DriverIngressOptions[0].ServicePort),
-// 					ingressURL:  parseURLAndAssertError(app3.GetName()+".ingress.clusterName.com", t),
-// 					annotations: map[string]string{
-// 						"kubernetes.io/ingress.class":                    "nginx",
-// 						"nginx.ingress.kubernetes.io/force-ssl-redirect": "true",
-// 					},
-// 					ingressTLS: []networkingv1.IngressTLS{
-// 						{Hosts: []string{"host1", "host2"}, SecretName: "secret"},
-// 					},
-// 				},
-// 			},
-// 			expectError: false,
-// 		},
-// 		{
-// 			name: "ingress with incomplete list of annotations",
-// 			app:  app4,
-// 			expectedIngresses: []SparkIngress{
-// 				{
-// 					ingressName: fmt.Sprintf(ingressNameFormat, app4.GetName(), *app4.Spec.DriverIngressOptions[0].ServicePort),
-// 					ingressURL:  parseURLAndAssertError(app3.GetName()+".ingress.clusterName.com", t),
-// 					annotations: map[string]string{
-// 						"kubernetes.io/ingress.class":                    "nginx",
-// 						"nginx.ingress.kubernetes.io/force-ssl-redirect": "true",
-// 					},
-// 					ingressTLS: []networkingv1.IngressTLS{
-// 						{Hosts: []string{"host1", "host2"}, SecretName: ""},
-// 					},
-// 				},
-// 			},
-// 			expectError: true,
-// 		},
-// 	}
+		assert.NoError(t, err, "Failed to create driver ingress service")
 
-// 	for _, test := range testcases {
-// 		testFn(test, t, "{{$appName}}.ingress.clusterName.com", "")
-// 	}
+		// Verify the service matches expected values
+		if len(tc.expectedServices) > 0 {
+			expectedService := tc.expectedServices[0]
 
-// 	testcases = []testcase{
-// 		{
-// 			name: "simple ingress object with ingress URL Format with path",
-// 			app:  app1,
-// 			expectedIngresses: []SparkIngress{
-// 				{
-// 					ingressName: fmt.Sprintf(ingressNameFormat, app1.GetName(), *app1.Spec.DriverIngressOptions[0].ServicePort),
-// 					ingressURL:  parseURLAndAssertError("ingress.clusterName.com/"+app1.GetNamespace()+"/"+app1.GetName(), t),
-// 					annotations: map[string]string{
-// 						"nginx.ingress.kubernetes.io/rewrite-target": "/$2",
-// 					},
-// 				},
-// 			},
-// 			expectError: false,
-// 		},
-// 	}
+			assert.Equal(t, expectedService.serviceName, ingressConfig.serviceName, "Service name mismatch")
+			assert.Equal(t, expectedService.serviceType, ingressConfig.serviceType, "Service type mismatch")
+			assert.Equal(t, expectedService.servicePort, ingressConfig.servicePort, "Service port mismatch")
+			assert.Equal(t, expectedService.servicePortName, ingressConfig.servicePortName, "Service port name mismatch")
 
-// 	for _, test := range testcases {
-// 		testFn(test, t, "ingress.clusterName.com/{{$appNamespace}}/{{$appName}}", "")
-// 	}
+			if expectedService.targetPort.IntVal != 0 {
+				assert.Equal(t, expectedService.targetPort.IntVal, ingressConfig.targetPort.IntVal, "Target port mismatch")
+			}
 
-// 	testcases = []testcase{
-// 		{
-// 			name: "simple ingress object with ingressClassName set",
-// 			app:  app1,
-// 			expectedIngresses: []SparkIngress{
-// 				{
-// 					ingressName:      fmt.Sprintf(ingressNameFormat, app1.GetName(), *app1.Spec.DriverIngressOptions[0].ServicePort),
-// 					ingressURL:       parseURLAndAssertError(app1.GetName()+".ingress.clusterName.com", t),
-// 					ingressClassName: "nginx",
-// 				},
-// 			},
-// 			expectError: false,
-// 		},
-// 	}
-// 	for _, test := range testcases {
-// 		testFn(test, t, "{{$appName}}.ingress.clusterName.com", "nginx")
-// 	}
-// }
+			// Check service annotations if specified
+			if len(expectedService.serviceAnnotations) > 0 {
+				assert.Equal(t, expectedService.serviceAnnotations, ingressConfig.serviceAnnotations, "Service annotations mismatch")
+			}
+
+			// Check service labels if specified
+			if len(expectedService.serviceLabels) > 0 {
+				for k, v := range expectedService.serviceLabels {
+					assert.Equal(t, v, ingressConfig.serviceLabels[k], "Service label %s mismatch", k)
+				}
+			}
+		}
+
+		// Test ingress creation
+		driverUrl, err := url.Parse("http://localhost")
+		assert.NoError(t, err, "Failed to parse driver ingress url")
+		_, err = reconciler.createDriverIngress(tc.app, &ingressOptions, *ingressConfig, driverUrl, "ingressClass")
+
+		if tc.expectError {
+			assert.Error(t, err, "Expected an error but got none")
+		} else {
+			assert.NoError(t, err, "Failed to create driver ingress")
+		}
+	}
+
+	serviceNameFormat := "%s-driver-%d"
+	portNameFormat := "driver-ing-%d"
+	app1 := &v1beta2.SparkApplication{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo1",
+			Namespace: "default",
+			UID:       "foo-123",
+		},
+		Spec: v1beta2.SparkApplicationSpec{
+			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
+				{
+					ServicePort:   util.Int32Ptr(8888),
+					ServiceLabels: map[string]string{"foo": "bar"},
+				},
+			},
+		},
+		Status: v1beta2.SparkApplicationStatus{
+			SparkApplicationID: "foo-1",
+			ExecutionAttempts:  1,
+		},
+	}
+	app2 := &v1beta2.SparkApplication{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo2",
+			Namespace: "default",
+			UID:       "foo-123",
+		},
+		Spec: v1beta2.SparkApplicationSpec{
+			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
+				{
+					ServicePort: util.Int32Ptr(8888),
+				},
+			},
+		},
+		Status: v1beta2.SparkApplicationStatus{
+			SparkApplicationID: "foo-2",
+			ExecutionAttempts:  2,
+		},
+	}
+	app3 := &v1beta2.SparkApplication{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo3",
+			Namespace: "default",
+			UID:       "foo-123",
+		},
+		Spec: v1beta2.SparkApplicationSpec{
+			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
+				{
+					ServicePort: nil,
+				},
+			},
+		},
+		Status: v1beta2.SparkApplicationStatus{
+			SparkApplicationID: "foo-3",
+		},
+	}
+	var appPort int32 = 80
+	app4 := &v1beta2.SparkApplication{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo4",
+			Namespace: "default",
+			UID:       "foo-123",
+		},
+		Spec: v1beta2.SparkApplicationSpec{
+			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
+				{
+					ServicePort: util.Int32Ptr(4041),
+				},
+			},
+		},
+		Status: v1beta2.SparkApplicationStatus{
+			SparkApplicationID: "foo-3",
+		},
+	}
+	var serviceTypeNodePort corev1.ServiceType = corev1.ServiceTypeNodePort
+	app5 := &v1beta2.SparkApplication{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo5",
+			Namespace: "default",
+			UID:       "foo-123",
+		},
+		Spec: v1beta2.SparkApplicationSpec{
+			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
+				{
+					ServicePort: util.Int32Ptr(8888),
+					ServiceType: &serviceTypeNodePort,
+				},
+			},
+		},
+		Status: v1beta2.SparkApplicationStatus{
+			SparkApplicationID: "foo-2",
+			ExecutionAttempts:  2,
+		},
+	}
+	appPortName := "http-spark-test"
+	app6 := &v1beta2.SparkApplication{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo6",
+			Namespace: "default",
+			UID:       "foo-123",
+		},
+		Spec: v1beta2.SparkApplicationSpec{
+			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
+				{
+					ServicePort:     &appPort,
+					ServicePortName: &appPortName,
+				},
+			},
+		},
+		Status: v1beta2.SparkApplicationStatus{
+			SparkApplicationID: "foo-6",
+		},
+	}
+	app7 := &v1beta2.SparkApplication{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo7",
+			Namespace: "default",
+			UID:       "foo-123",
+		},
+		Spec: v1beta2.SparkApplicationSpec{
+			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
+				{
+					ServicePort: util.Int32Ptr(8888),
+					ServiceAnnotations: map[string]string{
+						"key": "value",
+					},
+				},
+			},
+		},
+		Status: v1beta2.SparkApplicationStatus{
+			SparkApplicationID: "foo-7",
+			ExecutionAttempts:  1,
+		},
+	}
+	app8 := &v1beta2.SparkApplication{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo8",
+			Namespace: "default",
+			UID:       "foo-123",
+		},
+		Spec: v1beta2.SparkApplicationSpec{
+			DriverIngressOptions: []v1beta2.DriverIngressConfiguration{
+				{
+					ServicePort: util.Int32Ptr(8888),
+					ServiceLabels: map[string]string{
+						"foo": "bar",
+					},
+				},
+			},
+		},
+		Status: v1beta2.SparkApplicationStatus{
+			SparkApplicationID: "foo-8",
+			ExecutionAttempts:  1,
+		},
+	}
+	testcases := []testcase{
+		{
+			name: "service with custom serviceport and serviceport and target port are same",
+			app:  app1,
+			expectedServices: []SparkService{
+				{
+					serviceName:     fmt.Sprintf(serviceNameFormat, app1.GetName(), *app1.Spec.DriverIngressOptions[0].ServicePort),
+					serviceType:     corev1.ServiceTypeClusterIP,
+					servicePortName: fmt.Sprintf(portNameFormat, *app1.Spec.DriverIngressOptions[0].ServicePort),
+					servicePort:     *app1.Spec.DriverIngressOptions[0].ServicePort,
+					serviceLabels: map[string]string{
+						"foo": "bar",
+					},
+					targetPort: intstr.IntOrString{
+						Type:   intstr.Int,
+						IntVal: *app1.Spec.DriverIngressOptions[0].ServicePort,
+					},
+				},
+			},
+			expectedSelector: map[string]string{
+				common.LabelSparkAppName: "foo1",
+				common.LabelSparkRole:    common.SparkRoleDriver,
+			},
+			expectError: false,
+		},
+		{
+			name: "service with default port",
+			app:  app2,
+			expectedServices: []SparkService{
+				{
+					serviceName:     fmt.Sprintf(serviceNameFormat, app2.GetName(), *app2.Spec.DriverIngressOptions[0].ServicePort),
+					serviceType:     corev1.ServiceTypeClusterIP,
+					servicePortName: fmt.Sprintf(portNameFormat, *app2.Spec.DriverIngressOptions[0].ServicePort),
+					servicePort:     *app2.Spec.DriverIngressOptions[0].ServicePort,
+				},
+			},
+			expectedSelector: map[string]string{
+				common.LabelSparkAppName: "foo2",
+				common.LabelSparkRole:    common.SparkRoleDriver,
+			},
+			expectError: false,
+		},
+		{
+			name: "service with custom serviceport and serviceport and target port are different",
+			app:  app4,
+			expectedServices: []SparkService{
+				{
+					serviceName:     fmt.Sprintf(serviceNameFormat, app4.GetName(), *app4.Spec.DriverIngressOptions[0].ServicePort),
+					serviceType:     corev1.ServiceTypeClusterIP,
+					servicePortName: fmt.Sprintf(portNameFormat, *app4.Spec.DriverIngressOptions[0].ServicePort),
+					servicePort:     4041,
+					targetPort: intstr.IntOrString{
+						Type:   intstr.Int,
+						IntVal: int32(4041),
+					},
+				},
+			},
+			expectedSelector: map[string]string{
+				common.LabelSparkAppName: "foo4",
+				common.LabelSparkRole:    common.SparkRoleDriver,
+			},
+			expectError: false,
+		},
+		{
+			name: "service with custom servicetype",
+			app:  app5,
+			expectedServices: []SparkService{
+				{
+					serviceName:     fmt.Sprintf(serviceNameFormat, app5.GetName(), *app5.Spec.DriverIngressOptions[0].ServicePort),
+					serviceType:     corev1.ServiceTypeNodePort,
+					servicePortName: fmt.Sprintf(portNameFormat, *app5.Spec.DriverIngressOptions[0].ServicePort),
+					servicePort:     *app5.Spec.DriverIngressOptions[0].ServicePort,
+				},
+			},
+			expectedSelector: map[string]string{
+				common.LabelSparkAppName: "foo5",
+				common.LabelSparkRole:    common.SparkRoleDriver,
+			},
+			expectError: false,
+		},
+		{
+			name: "service with custom serviceportname",
+			app:  app6,
+			expectedServices: []SparkService{
+				{
+					serviceName:     fmt.Sprintf(serviceNameFormat, app6.GetName(), *app6.Spec.DriverIngressOptions[0].ServicePort),
+					serviceType:     corev1.ServiceTypeClusterIP,
+					servicePortName: "http-spark-test",
+					servicePort:     int32(80),
+				},
+			},
+			expectedSelector: map[string]string{
+				common.LabelSparkAppName: "foo6",
+				common.LabelSparkRole:    common.SparkRoleDriver,
+			},
+			expectError: false,
+		},
+		{
+			name: "service with annotation",
+			app:  app7,
+			expectedServices: []SparkService{
+				{
+					serviceName:     fmt.Sprintf(serviceNameFormat, app7.GetName(), *app7.Spec.DriverIngressOptions[0].ServicePort),
+					serviceType:     corev1.ServiceTypeClusterIP,
+					servicePortName: fmt.Sprintf(portNameFormat, *app7.Spec.DriverIngressOptions[0].ServicePort),
+					servicePort:     *app7.Spec.DriverIngressOptions[0].ServicePort,
+					serviceAnnotations: map[string]string{
+						"key": "value",
+					},
+					targetPort: intstr.IntOrString{
+						Type:   intstr.Int,
+						IntVal: int32(8888),
+					},
+				},
+			},
+			expectedSelector: map[string]string{
+				common.LabelSparkAppName: "foo7",
+				common.LabelSparkRole:    common.SparkRoleDriver,
+			},
+			expectError: false,
+		},
+		{
+			name: "service with custom labels",
+			app:  app8,
+			expectedServices: []SparkService{
+				{
+					serviceName:     fmt.Sprintf(serviceNameFormat, app8.GetName(), *app8.Spec.DriverIngressOptions[0].ServicePort),
+					serviceType:     corev1.ServiceTypeClusterIP,
+					servicePortName: fmt.Sprintf(portNameFormat, *app8.Spec.DriverIngressOptions[0].ServicePort),
+					servicePort:     *app8.Spec.DriverIngressOptions[0].ServicePort,
+					serviceLabels: map[string]string{
+						"foo": "bar",
+					},
+					targetPort: intstr.IntOrString{
+						Type:   intstr.Int,
+						IntVal: int32(8888),
+					},
+				},
+			},
+			expectedSelector: map[string]string{
+				common.LabelSparkAppName: "foo8",
+				common.LabelSparkRole:    common.SparkRoleDriver,
+			},
+			expectError: false,
+		},
+		{
+			name:             "service with bad port configurations",
+			app:              app3,
+			expectError:      true,
+			expectedServices: []SparkService{{}},
+		},
+	}
+	for _, test := range testcases {
+		testFn(test, t)
+	}
+}