Files
Venkateswarlu KomirisettiandGitHub 5006eb7a3d REST-based spark submitter service as pluggable submission strategy (#2976)
* feat: add REST-based spark submitter with mTLS support

- Add RestSparkSubmitter with mTLS, retry with exponential backoff, and
  HTTP+TLS startup verification
- Add typed SubmitErrorCode enum with structured error responses
- Implement operator-side idempotent retry (DRIVER_POD_ALREADY_EXISTS +
  matching submission_id = success)
- Switch retry logic from HTTP-status-based to error-code-based
- Add RestSubmitter feature gate to toggle between default and REST submitter
- Move feature gate check to start.go with newSparkSubmitter() helper
- Document contract, request/response payloads, and error codes

Signed-off-by: Venkateswarlu Komirisetti <[email protected]>

* feat: integrate server-side idempotency (duplicate_submission)

The submitter now handles duplicate submissions server-side, returning
200 OK with duplicate_submission=true instead of an error. Update the
operator client to treat both 201 and 200 as success, and update
DRIVER_POD_ALREADY_EXISTS to 409 (genuine conflict only).

Signed-off-by: Venkateswarlu Komirisetti <[email protected]>

* fix: check resp.Body.Close error to satisfy errcheck linter

Signed-off-by: Venkateswarlu Komirisetti <[email protected]>

* refactor: address PR review feedback for REST submitter

- Pass context from SetupSignalHandler to newSparkSubmitter for proper
  signal propagation and return error instead of calling os.Exit
- Move submitter-max-retries validation to PreRunE for early failure
- Extract buildDriverPodTemplate/buildExecutorPodTemplate as pure
  functions to ensure pod templates include owner refs for both
  spark-submit and REST submission paths
- Remove cert reload mention from comment (not yet implemented)

Signed-off-by: Venkateswarlu Komirisetti <[email protected]>

* refactor: simplify TLS flags and add submitter URL validation

Replace --submitter-tls-cert-dir with --submitter-tls-enabled flag and
direct path flags (--submitter-tls-cert-file, --submitter-tls-key-file,
--submitter-tls-ca-file). Group all REST submitter validations under
RestSubmitter feature gate check in PreRunE, including requiring
--submitter-service-url when the gate is enabled.

Signed-off-by: Venkateswarlu Komirisetti <[email protected]>

* style: fix variable alignment to pass go-fmt

Signed-off-by: Venkateswarlu Komirisetti <[email protected]>

---------

Signed-off-by: Venkateswarlu Komirisetti <[email protected]>
2026-06-22 15:42:54 +00:00

1383 lines
47 KiB
Go

/*
Copyright 2017 Google LLC
Licensed 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
https://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 sparkapplication
import (
"fmt"
"os"
"slices"
"strings"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
"sigs.k8s.io/yaml"
"github.com/kubeflow/spark-operator/v2/api/v1beta2"
"github.com/kubeflow/spark-operator/v2/pkg/common"
"github.com/kubeflow/spark-operator/v2/pkg/util"
)
func TestExecutorConfOption(t *testing.T) {
tests := []struct {
name string
app *v1beta2.SparkApplication
expected []string
}{
{
name: "minimal executor configuration",
app: &v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "spark-minimal",
},
Status: v1beta2.SparkApplicationStatus{
SubmissionID: "minimal-123",
},
Spec: v1beta2.SparkApplicationSpec{
Executor: v1beta2.ExecutorSpec{},
},
},
expected: []string{
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, common.LabelSparkAppName), "spark-minimal"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, common.LabelLaunchedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, common.LabelMutatedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, common.LabelSubmissionID), "minimal-123"),
},
},
{
name: "custom executor configs",
app: &v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "spark-executor",
},
Status: v1beta2.SparkApplicationStatus{
SubmissionID: "exec-123",
},
Spec: v1beta2.SparkApplicationSpec{
Executor: v1beta2.ExecutorSpec{
CoreRequest: ptr.To("2"),
SparkPodSpec: v1beta2.SparkPodSpec{
Image: ptr.To("custom-executor:v1"),
Cores: ptr.To[int32](4),
CoreLimit: ptr.To("4"),
Memory: ptr.To("4g"),
MemoryOverhead: ptr.To("1g"),
},
Instances: ptr.To[int32](3),
DeleteOnTermination: ptr.To(true),
},
},
},
expected: []string{
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, common.LabelSparkAppName), "spark-executor"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, common.LabelLaunchedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, common.LabelMutatedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, common.LabelSubmissionID), "exec-123"),
"--conf", fmt.Sprintf("%s=%s", common.SparkKubernetesExecutorContainerImage, "custom-executor:v1"),
"--conf", fmt.Sprintf("%s=%d", common.SparkExecutorCores, 4),
"--conf", fmt.Sprintf("%s=%s", common.SparkKubernetesExecutorLimitCores, "4"),
"--conf", fmt.Sprintf("%s=%s", common.SparkKubernetesExecutorRequestCores, "2"),
"--conf", fmt.Sprintf("%s=%s", common.SparkExecutorMemory, "4g"),
"--conf", fmt.Sprintf("%s=%s", common.SparkExecutorMemoryOverhead, "1g"),
"--conf", fmt.Sprintf("%s=%d", common.SparkExecutorInstances, 3),
"--conf", fmt.Sprintf("%s=%t", common.SparkKubernetesExecutorDeleteOnTermination, true),
},
},
{
name: "kueue labels on SparkApplication should not be propagated to executor conf",
app: &v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "spark-kueue",
Labels: map[string]string{
"environment": "production",
"kueue.x-k8s.io/queue-name": "high-priority",
},
},
Status: v1beta2.SparkApplicationStatus{
SubmissionID: "minimal-123",
},
Spec: v1beta2.SparkApplicationSpec{
Executor: v1beta2.ExecutorSpec{},
},
},
expected: []string{
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, "environment"), "production"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, common.LabelSparkAppName), "spark-kueue"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, common.LabelLaunchedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, common.LabelMutatedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesExecutorLabelTemplate, common.LabelSubmissionID), "minimal-123"),
},
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
options, err := executorConfOption(testCase.app)
if err != nil {
t.Fatal(err)
}
slices.Sort(options)
slices.Sort(testCase.expected)
assert.Equal(t, testCase.expected, options, "Executor configuration options do not match expected values")
})
}
}
func TestDriverVolumeMountsOption(t *testing.T) {
tests := []struct {
name string
app *v1beta2.SparkApplication
expected []string
}{
{
name: "basic local volume",
app: &v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "spark-volumes",
},
Spec: v1beta2.SparkApplicationSpec{
Volumes: []corev1.Volume{
{
Name: fmt.Sprintf("%stest", common.SparkLocalDirVolumePrefix),
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: "/tmp/test-path",
},
},
},
},
Driver: v1beta2.DriverSpec{
SparkPodSpec: v1beta2.SparkPodSpec{
VolumeMounts: []corev1.VolumeMount{
{
Name: fmt.Sprintf("%stest", common.SparkLocalDirVolumePrefix),
MountPath: "/tmp/mount-path",
ReadOnly: true,
},
},
},
},
},
},
expected: []string{
"--conf", fmt.Sprintf("%s=%s",
fmt.Sprintf(common.SparkKubernetesDriverVolumesMountPathTemplate, common.VolumeTypeHostPath, fmt.Sprintf("%stest", common.SparkLocalDirVolumePrefix)),
"/tmp/mount-path"),
"--conf", fmt.Sprintf("%s=%s",
fmt.Sprintf(common.SparkKubernetesDriverVolumesOptionsTemplate, common.VolumeTypeHostPath, fmt.Sprintf("%stest", common.SparkLocalDirVolumePrefix), "path"),
"/tmp/test-path"),
"--conf", fmt.Sprintf("%s=%s",
fmt.Sprintf(common.SparkKubernetesDriverVolumesMountReadOnlyTemplate, common.VolumeTypeHostPath, fmt.Sprintf("%stest", common.SparkLocalDirVolumePrefix)),
"true"),
},
},
{
name: "emptydir volume with size limit",
app: &v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "spark-emptydir",
},
Spec: v1beta2.SparkApplicationSpec{
Volumes: []corev1.Volume{
{
Name: fmt.Sprintf("%semptydir", common.SparkLocalDirVolumePrefix),
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{
SizeLimit: resource.NewQuantity(10*1024*1024*1024, resource.BinarySI), // 10Gi
},
},
},
},
Driver: v1beta2.DriverSpec{
SparkPodSpec: v1beta2.SparkPodSpec{
VolumeMounts: []corev1.VolumeMount{
{
Name: fmt.Sprintf("%semptydir", common.SparkLocalDirVolumePrefix),
MountPath: "/tmp/scratch",
ReadOnly: false,
},
},
},
},
},
},
expected: []string{
"--conf", fmt.Sprintf("%s=%s",
fmt.Sprintf(common.SparkKubernetesDriverVolumesMountPathTemplate, common.VolumeTypeEmptyDir, fmt.Sprintf("%semptydir", common.SparkLocalDirVolumePrefix)),
"/tmp/scratch"),
"--conf", fmt.Sprintf("%s=%s",
fmt.Sprintf(common.SparkKubernetesDriverVolumesOptionsTemplate, common.VolumeTypeEmptyDir, fmt.Sprintf("%semptydir", common.SparkLocalDirVolumePrefix), "sizeLimit"),
"10Gi"),
},
},
{
name: "emptydir volume with memory medium",
app: &v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "spark-emptydir-memory",
},
Spec: v1beta2.SparkApplicationSpec{
Volumes: []corev1.Volume{
{
Name: fmt.Sprintf("%semptydir", common.SparkLocalDirVolumePrefix),
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{
Medium: corev1.StorageMediumMemory,
},
},
},
},
Driver: v1beta2.DriverSpec{
SparkPodSpec: v1beta2.SparkPodSpec{
VolumeMounts: []corev1.VolumeMount{
{
Name: fmt.Sprintf("%semptydir", common.SparkLocalDirVolumePrefix),
MountPath: "/tmp/scratch",
ReadOnly: false,
},
},
},
},
},
},
expected: []string{
"--conf", fmt.Sprintf("%s=%s",
fmt.Sprintf(common.SparkKubernetesDriverVolumesMountPathTemplate, common.VolumeTypeEmptyDir, fmt.Sprintf("%semptydir", common.SparkLocalDirVolumePrefix)),
"/tmp/scratch"),
"--conf", fmt.Sprintf("%s=%s",
fmt.Sprintf(common.SparkKubernetesDriverVolumesOptionsTemplate, common.VolumeTypeEmptyDir, fmt.Sprintf("%semptydir", common.SparkLocalDirVolumePrefix), "medium"),
string(corev1.StorageMediumMemory)),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
options, err := driverVolumeMountsOption(tt.app)
if err != nil {
t.Fatal(err)
}
slices.Sort(options)
slices.Sort(tt.expected)
assert.Equal(t, tt.expected, options, "Driver volume mount options do not match expected values")
})
}
}
func TestDriverConfOption(t *testing.T) {
tests := []struct {
name string
app *v1beta2.SparkApplication
expected []string
}{
{
name: "basic driver configuration",
app: &v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "spark-test",
Labels: map[string]string{
"environment": "test",
"team": "spark",
},
},
Status: v1beta2.SparkApplicationStatus{
SubmissionID: "submission-id-123",
},
Spec: v1beta2.SparkApplicationSpec{
Driver: v1beta2.DriverSpec{
SparkPodSpec: v1beta2.SparkPodSpec{
Cores: ptr.To[int32](2),
CoreLimit: ptr.To("2"),
Memory: ptr.To("2g"),
Image: ptr.To("spark-driver:latest"),
ConfigMaps: []v1beta2.NamePath{{Name: "driver-config", Path: "/etc/config"}},
},
},
},
},
expected: []string{
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelSparkAppName), "spark-test"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelLaunchedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelMutatedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelSubmissionID), "submission-id-123"),
"--conf", fmt.Sprintf("%s=%s", common.SparkKubernetesDriverContainerImage, "spark-driver:latest"),
"--conf", fmt.Sprintf("%s=%d", common.SparkDriverCores, 2),
"--conf", fmt.Sprintf("%s=%s", common.SparkKubernetesDriverLimitCores, "2"),
"--conf", fmt.Sprintf("%s=%s", common.SparkDriverMemory, "2g"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, "environment"), "test"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, "team"), "spark"),
},
},
{
name: "minimal driver configuration",
app: &v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "spark-minimal",
},
Status: v1beta2.SparkApplicationStatus{
SubmissionID: "minimal-123",
},
Spec: v1beta2.SparkApplicationSpec{
Driver: v1beta2.DriverSpec{},
},
},
expected: []string{
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelSparkAppName), "spark-minimal"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelLaunchedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelMutatedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelSubmissionID), "minimal-123"),
},
},
{
name: "kueue labels on SparkApplication should not be propagated to driver conf",
app: &v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "spark-minimal",
Labels: map[string]string{
"environment": "production",
"kueue.x-k8s.io/queue-name": "high-priority",
},
},
Status: v1beta2.SparkApplicationStatus{
SubmissionID: "minimal-123",
},
Spec: v1beta2.SparkApplicationSpec{
Driver: v1beta2.DriverSpec{},
},
},
expected: []string{
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, "environment"), "production"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelSparkAppName), "spark-minimal"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelLaunchedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelMutatedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelSubmissionID), "minimal-123"),
},
},
{
name: "driver with labels and annotations",
app: &v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "spark-labels",
},
Status: v1beta2.SparkApplicationStatus{
SubmissionID: "labels-123",
},
Spec: v1beta2.SparkApplicationSpec{
Driver: v1beta2.DriverSpec{
SparkPodSpec: v1beta2.SparkPodSpec{
Labels: map[string]string{
"custom-label": "label-value",
"priority": "high",
},
Annotations: map[string]string{
"custom-annotation": "annotation-value",
"sidecar.istio.io/inject": "false",
},
},
ServiceLabels: map[string]string{
"service-label": "service-value",
"environment": "production",
},
ServiceAnnotations: map[string]string{
"service-annotation": "svc-annotation-value",
"prometheus.io/scrape": "true",
},
},
},
},
expected: []string{
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelSparkAppName), "spark-labels"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelLaunchedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelMutatedBySparkOperator), "true"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, common.LabelSubmissionID), "labels-123"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, "custom-label"), "label-value"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverLabelTemplate, "priority"), "high"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverAnnotationTemplate, "custom-annotation"), "annotation-value"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverAnnotationTemplate, "sidecar.istio.io/inject"), "false"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverServiceLabelTemplate, "service-label"), "service-value"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverServiceLabelTemplate, "environment"), "production"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverServiceAnnotationTemplate, "service-annotation"), "svc-annotation-value"),
"--conf", fmt.Sprintf("%s=%s", fmt.Sprintf(common.SparkKubernetesDriverServiceAnnotationTemplate, "prometheus.io/scrape"), "true"),
},
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
options, err := driverConfOption(testCase.app)
if err != nil {
t.Fatal(err)
}
slices.Sort(options)
slices.Sort(testCase.expected)
assert.Equal(t, testCase.expected, options, "Driver configuration options do not match expected values")
})
}
}
func TestDriverPodTemplateContents(t *testing.T) {
appName := "test"
uid := types.UID(uuid.New().String())
appOwnerReference := util.GetOwnerReference(&v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: appName,
UID: uid,
},
})
for name, tc := range map[string]struct {
app *v1beta2.SparkApplication
expectedTemplate corev1.PodTemplateSpec
}{
"no pod template": {
app: &v1beta2.SparkApplication{},
expectedTemplate: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{appOwnerReference},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: common.SparkDriverContainerName,
}},
},
},
},
"with pod template": {
app: &v1beta2.SparkApplication{
Spec: v1beta2.SparkApplicationSpec{
Driver: v1beta2.DriverSpec{
SparkPodSpec: v1beta2.SparkPodSpec{
Template: &corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{{Name: "owner-in-template"}},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: common.SparkDriverContainerName, Image: "image"},
},
},
},
},
},
},
},
expectedTemplate: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{
{Name: "owner-in-template"},
appOwnerReference,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: common.SparkDriverContainerName, Image: "image"},
},
},
},
},
"pod template already has owner references": {
app: &v1beta2.SparkApplication{
Spec: v1beta2.SparkApplicationSpec{
Driver: v1beta2.DriverSpec{
SparkPodSpec: v1beta2.SparkPodSpec{
Template: &corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{{Name: "owner-in-template"}, appOwnerReference},
},
},
},
},
},
},
expectedTemplate: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{
{Name: "owner-in-template"},
appOwnerReference,
},
},
},
},
} {
t.Run(name, func(t *testing.T) {
submissionID := "TestDriverPodTemplateOption-" + strings.ReplaceAll(name, " ", "_") + "-" + uuid.New().String()
tc.app.Name = appName
tc.app.UID = uid
tc.app.Status.SubmissionID = submissionID
_, err := driverPodTemplateOption(tc.app)
assert.NoError(t, err)
defer func() { _ = os.RemoveAll(fmt.Sprintf("/tmp/spark/%s", submissionID)) }()
expectedBytes, err := yaml.Marshal(tc.expectedTemplate)
assert.NoError(t, err)
actualBytes, err := os.ReadFile(fmt.Sprintf("/tmp/spark/%s/driver-pod-template.yaml", submissionID))
assert.NoError(t, err)
assert.Equal(t, string(expectedBytes), string(actualBytes))
})
}
}
func TestExecutorPodTemplateContents(t *testing.T) {
appName := "test"
uid := types.UID(uuid.New().String())
appNonControllerOwnerReference := util.GetOwnerReference(&v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: appName,
UID: uid,
},
})
appNonControllerOwnerReference.Controller = nil
appNonControllerOwnerReference.BlockOwnerDeletion = nil
for name, tc := range map[string]struct {
app *v1beta2.SparkApplication
expectedTemplate corev1.PodTemplateSpec
}{
"no pod template": {
app: &v1beta2.SparkApplication{},
expectedTemplate: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{appNonControllerOwnerReference},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: common.Spark3DefaultExecutorContainerName,
}},
},
},
},
"with pod template": {
app: &v1beta2.SparkApplication{
Spec: v1beta2.SparkApplicationSpec{
Executor: v1beta2.ExecutorSpec{
SparkPodSpec: v1beta2.SparkPodSpec{
Template: &corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{{Name: "owner-in-template"}},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: common.Spark3DefaultExecutorContainerName, Image: "image"},
},
},
},
},
},
},
},
expectedTemplate: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{
{Name: "owner-in-template"},
appNonControllerOwnerReference,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: common.Spark3DefaultExecutorContainerName, Image: "image"},
},
},
},
},
"pod template already has owner references": {
app: &v1beta2.SparkApplication{
Spec: v1beta2.SparkApplicationSpec{
Executor: v1beta2.ExecutorSpec{
SparkPodSpec: v1beta2.SparkPodSpec{
Template: &corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{{Name: "owner-in-template"}, appNonControllerOwnerReference},
},
},
},
},
},
},
expectedTemplate: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
OwnerReferences: []metav1.OwnerReference{
{Name: "owner-in-template"},
appNonControllerOwnerReference,
},
},
},
},
} {
t.Run(name, func(t *testing.T) {
submissionID := "TestExecutorPodTemplateOption-" + strings.ReplaceAll(name, " ", "_") + "-" + uuid.New().String()
tc.app.Name = appName
tc.app.UID = uid
tc.app.Status.SubmissionID = submissionID
_, err := executorPodTemplateOption(tc.app)
assert.NoError(t, err)
defer func() { _ = os.RemoveAll(fmt.Sprintf("/tmp/spark/%s", submissionID)) }()
expectedBytes, err := yaml.Marshal(tc.expectedTemplate)
assert.NoError(t, err)
actualBytes, err := os.ReadFile(fmt.Sprintf("/tmp/spark/%s/executor-pod-template.yaml", submissionID))
assert.NoError(t, err)
assert.Equal(t, string(expectedBytes), string(actualBytes))
})
}
}
func TestBuildSparkSubmitArgsSkipPodTemplates(t *testing.T) {
t.Setenv("KUBERNETES_SERVICE_HOST", "127.0.0.1")
t.Setenv("KUBERNETES_SERVICE_PORT", "6443")
app := &v1beta2.SparkApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "test-app",
Namespace: "default",
UID: "uid-123",
},
Spec: v1beta2.SparkApplicationSpec{
Type: v1beta2.SparkApplicationTypeScala,
Mode: v1beta2.DeployModeCluster,
MainApplicationFile: ptr.To("local:///app.jar"),
MainClass: ptr.To("org.example.Main"),
SparkVersion: "4.0.1",
},
Status: v1beta2.SparkApplicationStatus{
SubmissionID: "sub-123",
},
}
t.Run("includes pod template args when skipPodTemplates is false", func(t *testing.T) {
args, err := buildSparkSubmitArgs(app, false)
assert.NoError(t, err)
hasDriverTemplate := slices.ContainsFunc(args, func(s string) bool {
return strings.Contains(s, common.SparkKubernetesDriverPodTemplateFile)
})
hasExecutorTemplate := slices.ContainsFunc(args, func(s string) bool {
return strings.Contains(s, common.SparkKubernetesExecutorPodTemplateFile)
})
assert.True(t, hasDriverTemplate, "should include driver pod template conf")
assert.True(t, hasExecutorTemplate, "should include executor pod template conf")
})
t.Run("skips pod template args when skipPodTemplates is true", func(t *testing.T) {
args, err := buildSparkSubmitArgs(app, true)
assert.NoError(t, err)
hasDriverTemplate := slices.ContainsFunc(args, func(s string) bool {
return strings.Contains(s, common.SparkKubernetesDriverPodTemplateFile)
})
hasExecutorTemplate := slices.ContainsFunc(args, func(s string) bool {
return strings.Contains(s, common.SparkKubernetesExecutorPodTemplateFile)
})
assert.False(t, hasDriverTemplate, "should not include driver pod template conf")
assert.False(t, hasExecutorTemplate, "should not include executor pod template conf")
})
}
// import (
// "fmt"
// "os"
// "reflect"
// "sort"
// "strconv"
// "testing"
// "github.com/google/uuid"
// "github.com/stretchr/testify/assert"
// corev1 "k8s.io/api/core/v1"
// "k8s.io/apimachinery/pkg/api/resource"
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// "github.com/kubeflow/spark-operator/v2/api/v1beta2"
// "github.com/kubeflow/spark-operator/v2/pkg/common"
// "github.com/kubeflow/spark-operator/v2/pkg/util"
// )
// const (
// VolumeMountPathTemplate = "spark.kubernetes.%s.volumes.%s.%s.mount.path=%s"
// VolumeMountOptionPathTemplate = "spark.kubernetes.%s.volumes.%s.%s.options.%s=%s"
// SparkDriverLabelAnnotationTemplate = "spark.kubernetes.driver.label.sparkoperator.k8s.io/%s=%s"
// SparkDriverLabelTemplate = "spark.kubernetes.driver.label.%s=%s"
// SparkDriverServiceLabelTemplate = "spark.kubernetes.driver.service.label.%s=%s"
// SparkExecutorLabelAnnotationTemplate = "spark.kubernetes.executor.label.sparkoperator.k8s.io/%s=%s"
// SparkExecutorLabelTemplate = "spark.kubernetes.executor.label.%s=%s"
// )
// func TestAddLocalDir_HostPath(t *testing.T) {
// volumes := []corev1.Volume{
// {
// Name: "spark-local-dir-1",
// VolumeSource: corev1.VolumeSource{
// HostPath: &corev1.HostPathVolumeSource{
// Path: "/tmp/mnt",
// },
// },
// },
// }
// volumeMounts := []corev1.VolumeMount{
// {
// Name: "spark-local-dir-1",
// MountPath: "/tmp/mnt-1",
// },
// }
// app := &v1beta2.SparkApplication{
// ObjectMeta: metav1.ObjectMeta{
// Name: "spark-test",
// UID: "spark-test-1",
// },
// Spec: v1beta2.SparkApplicationSpec{
// Volumes: volumes,
// Driver: v1beta2.DriverSpec{
// SparkPodSpec: v1beta2.SparkPodSpec{
// VolumeMounts: volumeMounts,
// },
// },
// },
// }
// localDirOptions, err := addLocalDirConfOptions(app)
// if err != nil {
// t.Fatal(err)
// }
// assert.Equal(t, 0, len(app.Spec.Volumes))
// assert.Equal(t, 0, len(app.Spec.Driver.VolumeMounts))
// assert.Equal(t, 2, len(localDirOptions))
// assert.Equal(t, fmt.Sprintf(VolumeMountPathTemplate, "driver", "hostPath", volumes[0].Name, volumeMounts[0].MountPath), localDirOptions[0])
// assert.Equal(t, fmt.Sprintf(VolumeMountOptionPathTemplate, "driver", "hostPath", volumes[0].Name, "path", volumes[0].HostPath.Path), localDirOptions[1])
// }
// func TestAddLocalDir_PVC(t *testing.T) {
// volumes := []corev1.Volume{
// {
// Name: "spark-local-dir-1",
// VolumeSource: corev1.VolumeSource{
// PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
// ClaimName: "/tmp/mnt-1",
// },
// },
// },
// }
// volumeMounts := []corev1.VolumeMount{
// {
// Name: "spark-local-dir-1",
// MountPath: "/tmp/mnt-1",
// },
// }
// app := &v1beta2.SparkApplication{
// ObjectMeta: metav1.ObjectMeta{
// Name: "spark-test",
// UID: "spark-test-1",
// },
// Spec: v1beta2.SparkApplicationSpec{
// Volumes: volumes,
// Driver: v1beta2.DriverSpec{
// SparkPodSpec: v1beta2.SparkPodSpec{
// VolumeMounts: volumeMounts,
// },
// },
// },
// }
// localDirOptions, err := addLocalDirConfOptions(app)
// if err != nil {
// t.Fatal(err)
// }
// assert.Equal(t, 0, len(app.Spec.Volumes))
// assert.Equal(t, 0, len(app.Spec.Driver.VolumeMounts))
// assert.Equal(t, 2, len(localDirOptions))
// assert.Equal(t, fmt.Sprintf(VolumeMountPathTemplate, "driver", "persistentVolumeClaim", volumes[0].Name, volumeMounts[0].MountPath), localDirOptions[0])
// assert.Equal(t, fmt.Sprintf(VolumeMountOptionPathTemplate, "driver", "persistentVolumeClaim", volumes[0].Name, "claimName", volumes[0].PersistentVolumeClaim.ClaimName), localDirOptions[1])
// }
// func TestAddLocalDir_MixedVolumes(t *testing.T) {
// volumes := []corev1.Volume{
// {
// Name: "spark-local-dir-1",
// VolumeSource: corev1.VolumeSource{
// HostPath: &corev1.HostPathVolumeSource{
// Path: "/tmp/mnt-1",
// },
// },
// },
// {
// Name: "log-dir",
// VolumeSource: corev1.VolumeSource{
// HostPath: &corev1.HostPathVolumeSource{
// Path: "/var/log/spark",
// },
// },
// },
// }
// volumeMounts := []corev1.VolumeMount{
// {
// Name: "spark-local-dir-1",
// MountPath: "/tmp/mnt-1",
// },
// {
// Name: "log-dir",
// MountPath: "/var/log/spark",
// },
// }
// app := &v1beta2.SparkApplication{
// ObjectMeta: metav1.ObjectMeta{
// Name: "spark-test",
// UID: "spark-test-1",
// },
// Spec: v1beta2.SparkApplicationSpec{
// Volumes: volumes,
// Driver: v1beta2.DriverSpec{
// SparkPodSpec: v1beta2.SparkPodSpec{
// VolumeMounts: volumeMounts,
// },
// },
// },
// }
// localDirOptions, err := addLocalDirConfOptions(app)
// if err != nil {
// t.Fatal(err)
// }
// assert.Equal(t, 1, len(app.Spec.Volumes))
// assert.Equal(t, 1, len(app.Spec.Driver.VolumeMounts))
// assert.Equal(t, 2, len(localDirOptions))
// assert.Equal(t, fmt.Sprintf(VolumeMountPathTemplate, "driver", "hostPath", volumes[0].Name, volumeMounts[0].MountPath), localDirOptions[0])
// assert.Equal(t, fmt.Sprintf(VolumeMountOptionPathTemplate, "driver", "hostPath", volumes[0].Name, "path", volumes[0].HostPath.Path), localDirOptions[1])
// }
// func TestAddLocalDir_MultipleScratchVolumes(t *testing.T) {
// volumes := []corev1.Volume{
// {
// Name: "spark-local-dir-1",
// VolumeSource: corev1.VolumeSource{
// HostPath: &corev1.HostPathVolumeSource{
// Path: "/tmp/mnt-1",
// },
// },
// },
// {
// Name: "spark-local-dir-2",
// VolumeSource: corev1.VolumeSource{
// HostPath: &corev1.HostPathVolumeSource{
// Path: "/tmp/mnt-2",
// },
// },
// },
// }
// volumeMounts := []corev1.VolumeMount{
// {
// Name: "spark-local-dir-1",
// MountPath: "/tmp/mnt-1",
// },
// {
// Name: "spark-local-dir-2",
// MountPath: "/tmp/mnt-2",
// },
// }
// app := &v1beta2.SparkApplication{
// ObjectMeta: metav1.ObjectMeta{
// Name: "spark-test",
// UID: "spark-test-1",
// },
// Spec: v1beta2.SparkApplicationSpec{
// Volumes: volumes,
// Driver: v1beta2.DriverSpec{
// SparkPodSpec: v1beta2.SparkPodSpec{
// VolumeMounts: volumeMounts,
// },
// },
// },
// }
// localDirOptions, err := addLocalDirConfOptions(app)
// if err != nil {
// t.Fatal(err)
// }
// assert.Equal(t, 0, len(app.Spec.Volumes))
// assert.Equal(t, 0, len(app.Spec.Driver.VolumeMounts))
// assert.Equal(t, 4, len(localDirOptions))
// assert.Equal(t, fmt.Sprintf(VolumeMountPathTemplate, "driver", "hostPath", volumes[0].Name, volumeMounts[0].MountPath), localDirOptions[0])
// assert.Equal(t, fmt.Sprintf(VolumeMountOptionPathTemplate, "driver", "hostPath", volumes[0].Name, "path", volumes[0].HostPath.Path), localDirOptions[1])
// assert.Equal(t, fmt.Sprintf(VolumeMountPathTemplate, "driver", "hostPath", volumes[1].Name, volumeMounts[1].MountPath), localDirOptions[2])
// assert.Equal(t, fmt.Sprintf(VolumeMountOptionPathTemplate, "driver", "hostPath", volumes[1].Name, "path", volumes[1].HostPath.Path), localDirOptions[3])
// }
// func TestAddLocalDir_Executor(t *testing.T) {
// volumes := []corev1.Volume{
// {
// Name: "spark-local-dir-1",
// VolumeSource: corev1.VolumeSource{
// HostPath: &corev1.HostPathVolumeSource{
// Path: "/tmp/mnt",
// },
// },
// },
// }
// volumeMounts := []corev1.VolumeMount{
// {
// Name: "spark-local-dir-1",
// MountPath: "/tmp/mnt-1",
// },
// }
// app := &v1beta2.SparkApplication{
// ObjectMeta: metav1.ObjectMeta{
// Name: "spark-test",
// UID: "spark-test-1",
// },
// Spec: v1beta2.SparkApplicationSpec{
// Volumes: volumes,
// Executor: v1beta2.ExecutorSpec{
// SparkPodSpec: v1beta2.SparkPodSpec{
// VolumeMounts: volumeMounts,
// },
// },
// },
// }
// localDirOptions, err := addLocalDirConfOptions(app)
// if err != nil {
// t.Fatal(err)
// }
// assert.Equal(t, 0, len(app.Spec.Volumes))
// assert.Equal(t, 0, len(app.Spec.Executor.VolumeMounts))
// assert.Equal(t, 2, len(localDirOptions))
// assert.Equal(t, fmt.Sprintf(VolumeMountPathTemplate, "executor", "hostPath", volumes[0].Name, volumeMounts[0].MountPath), localDirOptions[0])
// assert.Equal(t, fmt.Sprintf(VolumeMountOptionPathTemplate, "executor", "hostPath", volumes[0].Name, "path", volumes[0].HostPath.Path), localDirOptions[1])
// }
// func TestAddLocalDir_Driver_Executor(t *testing.T) {
// volumes := []corev1.Volume{
// {
// Name: "spark-local-dir-1",
// VolumeSource: corev1.VolumeSource{
// HostPath: &corev1.HostPathVolumeSource{
// Path: "/tmp/mnt",
// },
// },
// },
// {
// Name: "test-volume",
// VolumeSource: corev1.VolumeSource{
// HostPath: &corev1.HostPathVolumeSource{
// Path: "/tmp/test",
// },
// },
// },
// }
// volumeMounts := []corev1.VolumeMount{
// {
// Name: "spark-local-dir-1",
// MountPath: "/tmp/mnt-1",
// },
// {
// Name: "test-volume",
// MountPath: "/tmp/test",
// },
// }
// app := &v1beta2.SparkApplication{
// ObjectMeta: metav1.ObjectMeta{
// Name: "spark-test",
// UID: "spark-test-1",
// },
// Spec: v1beta2.SparkApplicationSpec{
// Volumes: volumes,
// Driver: v1beta2.DriverSpec{
// SparkPodSpec: v1beta2.SparkPodSpec{
// VolumeMounts: volumeMounts,
// },
// },
// Executor: v1beta2.ExecutorSpec{
// SparkPodSpec: v1beta2.SparkPodSpec{
// VolumeMounts: volumeMounts,
// },
// },
// },
// }
// localDirOptions, err := addLocalDirConfOptions(app)
// if err != nil {
// t.Fatal(err)
// }
// assert.Equal(t, 1, len(app.Spec.Volumes))
// assert.Equal(t, 1, len(app.Spec.Driver.VolumeMounts))
// assert.Equal(t, 1, len(app.Spec.Executor.VolumeMounts))
// assert.Equal(t, 4, len(localDirOptions))
// assert.Equal(t, fmt.Sprintf(VolumeMountPathTemplate, "driver", "hostPath", volumes[0].Name, volumeMounts[0].MountPath), localDirOptions[0])
// assert.Equal(t, fmt.Sprintf(VolumeMountOptionPathTemplate, "driver", "hostPath", volumes[0].Name, "path", volumes[0].HostPath.Path), localDirOptions[1])
// assert.Equal(t, fmt.Sprintf(VolumeMountPathTemplate, "executor", "hostPath", volumes[0].Name, volumeMounts[0].MountPath), localDirOptions[2])
// assert.Equal(t, fmt.Sprintf(VolumeMountOptionPathTemplate, "executor", "hostPath", volumes[0].Name, "path", volumes[0].HostPath.Path), localDirOptions[3])
// }
// func TestAddEmptyDir_Driver_Executor_WithSizeLimit(t *testing.T) {
// sizeLimit := resource.MustParse("5Gi")
// volumes := []corev1.Volume{
// {
// Name: "spark-local-dir-1",
// VolumeSource: corev1.VolumeSource{
// EmptyDir: &corev1.EmptyDirVolumeSource{
// SizeLimit: &sizeLimit,
// },
// },
// },
// }
// volumeMounts := []corev1.VolumeMount{
// {
// Name: "spark-local-dir-1",
// MountPath: "/tmp/mnt-1",
// },
// }
// app := &v1beta2.SparkApplication{
// ObjectMeta: metav1.ObjectMeta{
// Name: "spark-test",
// UID: "spark-test-1",
// },
// Spec: v1beta2.SparkApplicationSpec{
// Volumes: volumes,
// Driver: v1beta2.DriverSpec{
// SparkPodSpec: v1beta2.SparkPodSpec{
// VolumeMounts: volumeMounts,
// },
// },
// Executor: v1beta2.ExecutorSpec{
// SparkPodSpec: v1beta2.SparkPodSpec{
// VolumeMounts: volumeMounts,
// },
// },
// },
// }
// localDirOptions, err := addLocalDirConfOptions(app)
// if err != nil {
// t.Fatal(err)
// }
// assert.Equal(t, 0, len(app.Spec.Volumes))
// assert.Equal(t, 0, len(app.Spec.Driver.VolumeMounts))
// assert.Equal(t, 0, len(app.Spec.Executor.VolumeMounts))
// assert.Equal(t, 4, len(localDirOptions))
// assert.Equal(t, fmt.Sprintf(VolumeMountPathTemplate, "driver", "emptyDir", volumes[0].Name, volumeMounts[0].MountPath), localDirOptions[0])
// assert.Equal(t, fmt.Sprintf(VolumeMountOptionPathTemplate, "driver", "emptyDir", volumes[0].Name, "sizeLimit", volumes[0].EmptyDir.SizeLimit.String()), localDirOptions[1])
// assert.Equal(t, fmt.Sprintf(VolumeMountPathTemplate, "executor", "emptyDir", volumes[0].Name, volumeMounts[0].MountPath), localDirOptions[2])
// assert.Equal(t, fmt.Sprintf(VolumeMountOptionPathTemplate, "executor", "emptyDir", volumes[0].Name, "sizeLimit", volumes[0].EmptyDir.SizeLimit.String()), localDirOptions[3])
// }
// func TestPopulateLabels_Driver_Executor(t *testing.T) {
// const (
// AppLabelKey = "app-label-key"
// AppLabelValue = "app-label-value"
// DriverLabelKey = "driver-label-key"
// DriverLabelValue = "driver-label-key"
// DriverServiceLabelKey = "driver-svc-label-key"
// DriverServiceLabelValue = "driver-svc-label-value"
// ExecutorLabelKey = "executor-label-key"
// ExecutorLabelValue = "executor-label-key"
// )
// app := &v1beta2.SparkApplication{
// ObjectMeta: metav1.ObjectMeta{
// Name: "spark-test",
// UID: "spark-test-1",
// Labels: map[string]string{AppLabelKey: AppLabelValue},
// },
// Spec: v1beta2.SparkApplicationSpec{
// Driver: v1beta2.DriverSpec{
// ServiceLabels: map[string]string{DriverServiceLabelKey: DriverServiceLabelValue},
// SparkPodSpec: v1beta2.SparkPodSpec{
// Labels: map[string]string{DriverLabelKey: DriverLabelValue},
// },
// },
// Executor: v1beta2.ExecutorSpec{
// SparkPodSpec: v1beta2.SparkPodSpec{
// Labels: map[string]string{ExecutorLabelKey: ExecutorLabelValue},
// },
// },
// },
// }
// submissionID := uuid.New().String()
// driverOptions, err := addDriverConfOptions(app, submissionID)
// if err != nil {
// t.Fatal(err)
// }
// assert.Equal(t, 6, len(driverOptions))
// sort.Strings(driverOptions)
// expectedDriverLabels := []string{
// fmt.Sprintf(SparkDriverLabelAnnotationTemplate, "launched-by-spark-operator", strconv.FormatBool(true)),
// fmt.Sprintf(SparkDriverLabelAnnotationTemplate, "app-name", "spark-test"),
// fmt.Sprintf(SparkDriverLabelAnnotationTemplate, "submission-id", submissionID),
// fmt.Sprintf(SparkDriverLabelTemplate, AppLabelKey, AppLabelValue),
// fmt.Sprintf(SparkDriverLabelTemplate, DriverLabelKey, DriverLabelValue),
// fmt.Sprintf(SparkDriverServiceLabelTemplate, DriverServiceLabelKey, DriverServiceLabelValue),
// }
// sort.Strings(expectedDriverLabels)
// if !reflect.DeepEqual(expectedDriverLabels, driverOptions) {
// t.Errorf("Executor labels: wanted %+q got %+q", expectedDriverLabels, driverOptions)
// }
// executorOptions, err := addExecutorConfOptions(app, submissionID)
// sort.Strings(executorOptions)
// if err != nil {
// t.Fatal(err)
// }
// assert.Equal(t, 5, len(executorOptions))
// expectedExecutorLabels := []string{
// fmt.Sprintf(SparkExecutorLabelAnnotationTemplate, "app-name", "spark-test"),
// fmt.Sprintf(SparkExecutorLabelAnnotationTemplate, "launched-by-spark-operator", strconv.FormatBool(true)),
// fmt.Sprintf(SparkExecutorLabelAnnotationTemplate, "submission-id", submissionID),
// fmt.Sprintf(SparkExecutorLabelTemplate, AppLabelKey, AppLabelValue),
// fmt.Sprintf(SparkExecutorLabelTemplate, ExecutorLabelKey, ExecutorLabelValue),
// }
// sort.Strings(expectedExecutorLabels)
// if !reflect.DeepEqual(expectedExecutorLabels, executorOptions) {
// t.Errorf("Executor labels: wanted %+q got %+q", expectedExecutorLabels, executorOptions)
// }
// }
// func TestPopulateLabelsOverride_Driver_Executor(t *testing.T) {
// const (
// AppLabelKey = "app-label-key"
// AppLabelValue = "app-label-value"
// DriverLabelKey = "driver-label-key"
// DriverLabelValue = "driver-label-key"
// DriverAppLabelOverride = "driver-app-label-override"
// ExecutorLabelKey = "executor-label-key"
// ExecutorLabelValue = "executor-label-key"
// ExecutorAppLabelOverride = "executor-app-label-override"
// )
// app := &v1beta2.SparkApplication{
// ObjectMeta: metav1.ObjectMeta{
// Name: "spark-test",
// UID: "spark-test-1",
// Labels: map[string]string{AppLabelKey: AppLabelValue},
// },
// Spec: v1beta2.SparkApplicationSpec{
// Driver: v1beta2.DriverSpec{
// SparkPodSpec: v1beta2.SparkPodSpec{
// Labels: map[string]string{DriverLabelKey: DriverLabelValue, AppLabelKey: DriverAppLabelOverride},
// },
// },
// Executor: v1beta2.ExecutorSpec{
// SparkPodSpec: v1beta2.SparkPodSpec{
// Labels: map[string]string{ExecutorLabelKey: ExecutorLabelValue, AppLabelKey: ExecutorAppLabelOverride},
// },
// },
// },
// }
// submissionID := uuid.New().String()
// driverOptions, err := addDriverConfOptions(app, submissionID)
// if err != nil {
// t.Fatal(err)
// }
// sort.Strings(driverOptions)
// assert.Equal(t, 5, len(driverOptions))
// expectedDriverLabels := []string{
// fmt.Sprintf(SparkDriverLabelTemplate, AppLabelKey, DriverAppLabelOverride),
// fmt.Sprintf(SparkDriverLabelTemplate, DriverLabelKey, DriverLabelValue),
// fmt.Sprintf(SparkDriverLabelAnnotationTemplate, "app-name", "spark-test"),
// fmt.Sprintf(SparkDriverLabelAnnotationTemplate, "launched-by-spark-operator", strconv.FormatBool(true)),
// fmt.Sprintf(SparkDriverLabelAnnotationTemplate, "submission-id", submissionID),
// }
// sort.Strings(expectedDriverLabels)
// if !reflect.DeepEqual(expectedDriverLabels, driverOptions) {
// t.Errorf("Executor labels: wanted %+q got %+q", expectedDriverLabels, driverOptions)
// }
// executorOptions, err := addExecutorConfOptions(app, submissionID)
// if err != nil {
// t.Fatal(err)
// }
// sort.Strings(executorOptions)
// assert.Equal(t, 5, len(executorOptions))
// expectedExecutorLabels := []string{
// fmt.Sprintf(SparkExecutorLabelTemplate, AppLabelKey, ExecutorAppLabelOverride),
// fmt.Sprintf(SparkExecutorLabelTemplate, ExecutorLabelKey, ExecutorLabelValue),
// fmt.Sprintf(SparkExecutorLabelAnnotationTemplate, "launched-by-spark-operator", strconv.FormatBool(true)),
// fmt.Sprintf(SparkExecutorLabelAnnotationTemplate, "app-name", "spark-test"),
// fmt.Sprintf(SparkExecutorLabelAnnotationTemplate, "submission-id", submissionID),
// }
// sort.Strings(expectedExecutorLabels)
// if !reflect.DeepEqual(expectedExecutorLabels, executorOptions) {
// t.Errorf("Executor labels: wanted %+q got %+q", expectedExecutorLabels, executorOptions)
// }
// }
// func TestDynamicAllocationOptions(t *testing.T) {
// app := &v1beta2.SparkApplication{
// ObjectMeta: metav1.ObjectMeta{
// Name: "spark-test",
// UID: "spark-test-1",
// },
// Spec: v1beta2.SparkApplicationSpec{},
// }
// options := addDynamicAllocationConfOptions(app)
// assert.Equal(t, 0, len(options))
// app = &v1beta2.SparkApplication{
// ObjectMeta: metav1.ObjectMeta{
// Name: "spark-test",
// UID: "spark-test-1",
// },
// Spec: v1beta2.SparkApplicationSpec{
// DynamicAllocation: &v1beta2.DynamicAllocation{
// Enabled: true,
// InitialExecutors: ptr.To[int32](2),
// MinExecutors: ptr.To[int32](0),
// MaxExecutors: ptr.To[int32](10),
// ShuffleTrackingTimeout: ptr.To[int64](6000000),
// },
// },
// }
// options = addDynamicAllocationConfOptions(app)
// assert.Equal(t, 6, len(options))
// assert.Equal(t, fmt.Sprintf("%s=true", common.SparkDynamicAllocationEnabled), options[0])
// assert.Equal(t, fmt.Sprintf("%s=true", common.SparkDynamicAllocationShuffleTrackingEnabled), options[1])
// assert.Equal(t, fmt.Sprintf("%s=2", common.SparkDynamicAllocationInitialExecutors), options[2])
// assert.Equal(t, fmt.Sprintf("%s=0", common.SparkDynamicAllocationMinExecutors), options[3])
// assert.Equal(t, fmt.Sprintf("%s=10", common.SparkDynamicAllocationMaxExecutors), options[4])
// assert.Equal(t, fmt.Sprintf("%s=6000000", common.SparkDynamicAllocationShuffleTrackingTimeout), options[5])
// }
// func TestProxyUserArg(t *testing.T) {
// const (
// host = "localhost"
// port = "6443"
// )
// if err := os.Setenv(common.EnvKubernetesServiceHost, host); err != nil {
// t.Fatal(err)
// }
// if err := os.Setenv(common.EnvKubernetesServicePort, port); err != nil {
// t.Fatal(err)
// }
// app := &v1beta2.SparkApplication{
// ObjectMeta: metav1.ObjectMeta{
// Name: "spark-test",
// UID: "spark-test-1",
// },
// Spec: v1beta2.SparkApplicationSpec{
// Mode: v1beta2.ClusterMode,
// ProxyUser: ptr.To("foo"),
// },
// }
// submissionID := uuid.New().String()
// driverPodName := app.GetDriverPodName()
// args, err := buildSubmissionCommandArgs(app, driverPodName, submissionID)
// if err != nil {
// t.Fatal(err)
// }
// assert.Equal(t, "--master", args[0])
// assert.Equal(t, fmt.Sprintf("k8s://https://%s:%s", host, port), args[1])
// assert.Equal(t, "--deploy-mode", args[2])
// assert.Equal(t, string(v1beta2.ClusterMode), args[3])
// assert.Equal(t, "--proxy-user", args[4])
// assert.Equal(t, "foo", args[5])
// }
// func Test_getMasterURL(t *testing.T) {
// setEnv := func(host string, port string) {
// if err := os.Setenv(common.EnvKubernetesServiceHost, host); err != nil {
// t.Fatal(err)
// }
// if err := os.Setenv(common.EnvKubernetesServicePort, port); err != nil {
// t.Fatal(err)
// }
// }
// tests := []struct {
// name string
// host string
// port string
// want string
// wantErr assert.ErrorAssertionFunc
// }{
// {
// name: "should return a valid master url when IPv4 address is used",
// host: "localhost",
// port: "6443",
// want: "k8s://https://localhost:6443",
// wantErr: assert.NoError,
// },
// {
// name: "should return a valid master url when IPv6 address is used",
// host: "::1",
// port: "6443",
// want: "k8s://https://[::1]:6443",
// wantErr: assert.NoError,
// },
// {
// name: "should throw an error when the host is empty",
// host: "",
// port: "6443",
// want: "",
// wantErr: assert.Error,
// },
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// setEnv(tt.host, tt.port)
// got, err := getMasterURL()
// if !tt.wantErr(t, err, "getMasterURL()") {
// return
// }
// assert.Equalf(t, tt.want, got, "getMasterURL()")
// })
// }
// }