From 07cf981251d46081c34616b178a0c60863001c87 Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Wed, 5 Nov 2025 12:22:30 -0500 Subject: [PATCH 1/2] Add PriorityClassName support to PodConfig in DataProtectionApplication - Introduced PriorityClassName field in PodConfig struct to allow specification of pod priority. - Updated CRD and related manifests to include PriorityClassName description. - Modified NodeAgent and Velero deployment builders to utilize PriorityClassName from DataProtectionApplication spec. - Enhanced tests to validate PriorityClassName functionality in NodeAgent and Velero deployments. --- .../dataprotectionapplication_types.go | 3 + ...enshift.io_dataprotectionapplications.yaml | 9 +++ ...enshift.io_dataprotectionapplications.yaml | 9 +++ internal/controller/nodeagent.go | 6 ++ internal/controller/nodeagent_test.go | 29 +++++++++ internal/controller/velero.go | 6 ++ internal/controller/velero_test.go | 61 ++++++++++++++----- 7 files changed, 107 insertions(+), 16 deletions(-) diff --git a/api/v1alpha1/dataprotectionapplication_types.go b/api/v1alpha1/dataprotectionapplication_types.go index e165247ad3b..f28a8eebc59 100644 --- a/api/v1alpha1/dataprotectionapplication_types.go +++ b/api/v1alpha1/dataprotectionapplication_types.go @@ -391,6 +391,9 @@ type PodConfig struct { // env defines the list of environment variables to be supplied to podSpec // +optional Env []corev1.EnvVar `json:"env,omitempty"` + // priorityClassName defines the PriorityClass name to be applied to the pod + // +optional + PriorityClassName string `json:"priorityClassName,omitempty"` } type NodeAgentCommonFields struct { diff --git a/bundle/manifests/oadp.openshift.io_dataprotectionapplications.yaml b/bundle/manifests/oadp.openshift.io_dataprotectionapplications.yaml index d9bf255fb42..dcee8b520ab 100644 --- a/bundle/manifests/oadp.openshift.io_dataprotectionapplications.yaml +++ b/bundle/manifests/oadp.openshift.io_dataprotectionapplications.yaml @@ -539,6 +539,9 @@ spec: type: string description: nodeSelector defines the nodeSelector to be supplied to podSpec type: object + priorityClassName: + description: priorityClassName defines the PriorityClass name to be applied to the pod + type: string resourceAllocations: description: resourceAllocations defines the CPU, Memory and ephemeral-storage resource allocations for the Pod nullable: true @@ -893,6 +896,9 @@ spec: type: string description: nodeSelector defines the nodeSelector to be supplied to podSpec type: object + priorityClassName: + description: priorityClassName defines the PriorityClass name to be applied to the pod + type: string resourceAllocations: description: resourceAllocations defines the CPU, Memory and ephemeral-storage resource allocations for the Pod nullable: true @@ -1416,6 +1422,9 @@ spec: type: string description: nodeSelector defines the nodeSelector to be supplied to podSpec type: object + priorityClassName: + description: priorityClassName defines the PriorityClass name to be applied to the pod + type: string resourceAllocations: description: resourceAllocations defines the CPU, Memory and ephemeral-storage resource allocations for the Pod nullable: true diff --git a/config/crd/bases/oadp.openshift.io_dataprotectionapplications.yaml b/config/crd/bases/oadp.openshift.io_dataprotectionapplications.yaml index c4c1c02d8c2..f445d73384e 100644 --- a/config/crd/bases/oadp.openshift.io_dataprotectionapplications.yaml +++ b/config/crd/bases/oadp.openshift.io_dataprotectionapplications.yaml @@ -539,6 +539,9 @@ spec: type: string description: nodeSelector defines the nodeSelector to be supplied to podSpec type: object + priorityClassName: + description: priorityClassName defines the PriorityClass name to be applied to the pod + type: string resourceAllocations: description: resourceAllocations defines the CPU, Memory and ephemeral-storage resource allocations for the Pod nullable: true @@ -893,6 +896,9 @@ spec: type: string description: nodeSelector defines the nodeSelector to be supplied to podSpec type: object + priorityClassName: + description: priorityClassName defines the PriorityClass name to be applied to the pod + type: string resourceAllocations: description: resourceAllocations defines the CPU, Memory and ephemeral-storage resource allocations for the Pod nullable: true @@ -1416,6 +1422,9 @@ spec: type: string description: nodeSelector defines the nodeSelector to be supplied to podSpec type: object + priorityClassName: + description: priorityClassName defines the PriorityClass name to be applied to the pod + type: string resourceAllocations: description: resourceAllocations defines the CPU, Memory and ephemeral-storage resource allocations for the Pod nullable: true diff --git a/internal/controller/nodeagent.go b/internal/controller/nodeagent.go index 32a60dc8e11..fa28ae28162 100644 --- a/internal/controller/nodeagent.go +++ b/internal/controller/nodeagent.go @@ -422,6 +422,12 @@ func (r *DataProtectionApplicationReconciler) buildNodeAgentDaemonset(ds *appsv1 install.WithNodeAgentConfigMap(configMapName), install.WithLabels(map[string]string{NodeAgentCMVersionLabel: configMapGeneration}), install.WithBackupRepoConfigMap(backupRepoConfigMapName), + install.WithPriorityClassName(func() string { + if dpa.Spec.Configuration.NodeAgent != nil && dpa.Spec.Configuration.NodeAgent.PodConfig != nil { + return dpa.Spec.Configuration.NodeAgent.PodConfig.PriorityClassName + } + return "" + }()), ) ds.TypeMeta = installDs.TypeMeta var err error diff --git a/internal/controller/nodeagent_test.go b/internal/controller/nodeagent_test.go index 92aa9c8df6f..addc5096601 100644 --- a/internal/controller/nodeagent_test.go +++ b/internal/controller/nodeagent_test.go @@ -256,6 +256,7 @@ type TestBuiltNodeAgentDaemonSetOptions struct { toleration []corev1.Toleration nodeSelector map[string]string disableFsBackup *bool + priorityClassName string } func createTestBuiltNodeAgentDaemonSet(options TestBuiltNodeAgentDaemonSetOptions) *appsv1.DaemonSet { @@ -568,6 +569,10 @@ func createTestBuiltNodeAgentDaemonSet(options TestBuiltNodeAgentDaemonSetOption testBuiltNodeAgentDaemonSet.Spec.Template.Spec.Containers[0].Args = append(testBuiltNodeAgentDaemonSet.Spec.Template.Spec.Containers[0].Args, fmt.Sprintf("--log-format=%s", *options.logFormat)) } + if len(options.priorityClassName) > 0 { + testBuiltNodeAgentDaemonSet.Spec.Template.Spec.PriorityClassName = options.priorityClassName + } + return testBuiltNodeAgentDaemonSet } @@ -707,6 +712,30 @@ func TestDPAReconciler_buildNodeAgentDaemonset(t *testing.T) { annotations: map[string]string{"test-annotation": "awesome annotation"}, }), }, + { + name: "valid DPA CR with PodConfig PriorityClassName, NodeAgent DaemonSet is built with PriorityClassName", + dpa: createTestDpaWith( + nil, + oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{}, + NodeAgent: &oadpv1alpha1.NodeAgentConfig{ + NodeAgentCommonFields: oadpv1alpha1.NodeAgentCommonFields{ + PodConfig: &oadpv1alpha1.PodConfig{ + PriorityClassName: "node-agent-critical", + }, + }, + UploaderType: "kopia", + }, + }, + }, + ), + clientObjects: []client.Object{testGenericInfrastructure}, + nodeAgentDaemonSet: testNodeAgentDaemonSet.DeepCopy(), + wantNodeAgentDaemonSet: createTestBuiltNodeAgentDaemonSet(TestBuiltNodeAgentDaemonSetOptions{ + priorityClassName: "node-agent-critical", + }), + }, { name: "valid DPA CR with DataMoverPrepareTimeout, NodeAgent DaemonSet is built with DataMoverPrepareTimeout", dpa: createTestDpaWith( diff --git a/internal/controller/velero.go b/internal/controller/velero.go index 5a4b565d42e..ee20fecf90c 100644 --- a/internal/controller/velero.go +++ b/internal/controller/velero.go @@ -203,6 +203,12 @@ func (r *DataProtectionApplicationReconciler) buildVeleroDeployment(veleroDeploy // our secrets are appended to containers/volumeMounts in credentials.AppendPluginSpecificSpecs function install.WithSecret(false), install.WithServiceAccountName(common.Velero), + install.WithPriorityClassName(func() string { + if dpa.Spec.Configuration.Velero.PodConfig != nil { + return dpa.Spec.Configuration.Velero.PodConfig.PriorityClassName + } + return "" + }()), ) veleroDeploymentName := veleroDeployment.Name diff --git a/internal/controller/velero_test.go b/internal/controller/velero_test.go index 057d4f7960c..eb4f99c44c8 100644 --- a/internal/controller/velero_test.go +++ b/internal/controller/velero_test.go @@ -615,22 +615,23 @@ func createTestDpaWith( } type TestBuiltVeleroDeploymentOptions struct { - args []string - customLabels map[string]string - labels map[string]string - annotations map[string]string - metricsPort int - initContainers []corev1.Container - volumes []corev1.Volume - volumeMounts []corev1.VolumeMount - env []corev1.EnvVar - dnsPolicy corev1.DNSPolicy - dnsConfig *corev1.PodDNSConfig - resourceLimits corev1.ResourceList - resourceRequests corev1.ResourceList - toleration []corev1.Toleration - nodeSelector map[string]string - loadAffinity []corev1.NodeSelectorTerm + args []string + customLabels map[string]string + labels map[string]string + annotations map[string]string + metricsPort int + initContainers []corev1.Container + volumes []corev1.Volume + volumeMounts []corev1.VolumeMount + env []corev1.EnvVar + dnsPolicy corev1.DNSPolicy + dnsConfig *corev1.PodDNSConfig + resourceLimits corev1.ResourceList + resourceRequests corev1.ResourceList + toleration []corev1.Toleration + nodeSelector map[string]string + loadAffinity []corev1.NodeSelectorTerm + priorityClassName string } func createTestBuiltVeleroDeployment(options TestBuiltVeleroDeploymentOptions) *appsv1.Deployment { @@ -770,6 +771,10 @@ func createTestBuiltVeleroDeployment(options TestBuiltVeleroDeploymentOptions) * testBuiltVeleroDeployment.Spec.Template.Spec.DNSConfig = options.dnsConfig } + if len(options.priorityClassName) > 0 { + testBuiltVeleroDeployment.Spec.Template.Spec.PriorityClassName = options.priorityClassName + } + return testBuiltVeleroDeployment } @@ -1030,6 +1035,30 @@ func TestDPAReconciler_buildVeleroDeployment(t *testing.T) { }, }), }, + { + name: "valid DPA CR with PodConfig PriorityClassName, Velero Deployment is built with PriorityClassName", + dpa: createTestDpaWith( + nil, + oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + PodConfig: &oadpv1alpha1.PodConfig{ + PriorityClassName: "velero-critical", + }, + }, + }, + }, + ), + veleroDeployment: testVeleroDeployment.DeepCopy(), + wantVeleroDeployment: createTestBuiltVeleroDeployment(TestBuiltVeleroDeploymentOptions{ + priorityClassName: "velero-critical", + args: []string{ + defaultFileSystemBackupTimeout, + defaultRestoreResourcePriorities, + defaultDisableInformerCache, + }, + }), + }, { name: "valid DPA CR with noDefaultBackupLocation, all default plugins and unsupportedOverrides operatorType MTC, Velero Deployment is built with secret volumes", dpa: createTestDpaWith( From 13f98510cb68fccbe35c81f880af8940eefd1027 Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Wed, 5 Nov 2025 12:43:44 -0500 Subject: [PATCH 2/2] Add test cases for NodeAgent and Velero deployments without PriorityClassName --- internal/controller/nodeagent_test.go | 22 ++++++++++++++++++++++ internal/controller/velero_test.go | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/internal/controller/nodeagent_test.go b/internal/controller/nodeagent_test.go index addc5096601..334e0e1716c 100644 --- a/internal/controller/nodeagent_test.go +++ b/internal/controller/nodeagent_test.go @@ -736,6 +736,28 @@ func TestDPAReconciler_buildNodeAgentDaemonset(t *testing.T) { priorityClassName: "node-agent-critical", }), }, + { + name: "valid DPA CR with PodConfig but no PriorityClassName, NodeAgent DaemonSet is built without PriorityClassName", + dpa: createTestDpaWith( + nil, + oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{}, + NodeAgent: &oadpv1alpha1.NodeAgentConfig{ + NodeAgentCommonFields: oadpv1alpha1.NodeAgentCommonFields{ + PodConfig: &oadpv1alpha1.PodConfig{}, + }, + UploaderType: "kopia", + }, + }, + }, + ), + clientObjects: []client.Object{testGenericInfrastructure}, + nodeAgentDaemonSet: testNodeAgentDaemonSet.DeepCopy(), + wantNodeAgentDaemonSet: createTestBuiltNodeAgentDaemonSet(TestBuiltNodeAgentDaemonSetOptions{ + priorityClassName: "", + }), + }, { name: "valid DPA CR with DataMoverPrepareTimeout, NodeAgent DaemonSet is built with DataMoverPrepareTimeout", dpa: createTestDpaWith( diff --git a/internal/controller/velero_test.go b/internal/controller/velero_test.go index eb4f99c44c8..4b0fb26cdfd 100644 --- a/internal/controller/velero_test.go +++ b/internal/controller/velero_test.go @@ -1059,6 +1059,28 @@ func TestDPAReconciler_buildVeleroDeployment(t *testing.T) { }, }), }, + { + name: "valid DPA CR with PodConfig but no PriorityClassName, Velero Deployment is built without PriorityClassName", + dpa: createTestDpaWith( + nil, + oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + PodConfig: &oadpv1alpha1.PodConfig{}, + }, + }, + }, + ), + veleroDeployment: testVeleroDeployment.DeepCopy(), + wantVeleroDeployment: createTestBuiltVeleroDeployment(TestBuiltVeleroDeploymentOptions{ + priorityClassName: "", + args: []string{ + defaultFileSystemBackupTimeout, + defaultRestoreResourcePriorities, + defaultDisableInformerCache, + }, + }), + }, { name: "valid DPA CR with noDefaultBackupLocation, all default plugins and unsupportedOverrides operatorType MTC, Velero Deployment is built with secret volumes", dpa: createTestDpaWith(