Skip to content

Commit 373fe4a

Browse files
kaovilaiclaude
andcommitted
Implement priority class support for Velero components
- Add --server-priority-class-name flag for Velero server deployment - Add --node-agent-priority-class-name flag for node agent daemonset - Add ServerPriorityClassName and NodeAgentPriorityClassName to VeleroOptions - Add PriorityClassName field to JobConfigs struct for maintenance jobs - Maintenance jobs get priority class from configmap (global config only) - Data mover pods get priority class from node-agent-configmap - Add tests for priority class functionality This allows different Velero components to use different priority classes based on their importance and resource requirements. Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> codespell Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> Fix PVC creation in priority class e2e test Add missing AccessModes field to PersistentVolumeClaim creation. The PVC now includes ReadWriteOnce access mode as required by Kubernetes API. Also fix missing newline at end of priority_class.go utility file. 🤖 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> Add e2e test for priority class support This adds comprehensive e2e tests to verify that priority classes are correctly applied to all Velero components: - Velero server deployment uses the specified priority class - Node agent daemonset uses the specified priority class - Data mover pods inherit priority class from node agent - Maintenance jobs inherit priority class from server deployment The test creates two priority classes (high and low), installs Velero with the priority class flags, and verifies that all components have the expected priority classes set. 🤖 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 9113f17 commit 373fe4a

38 files changed

Lines changed: 1992 additions & 33 deletions

.github/workflows/e2e-test-kind.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ jobs:
6464
echo "matrix={\
6565
\"k8s\":$(wget -q -O - "https://hub.docker.com/v2/namespaces/kindest/repositories/node/tags?page_size=50" | grep -o '"name": *"[^"]*' | grep -o '[^"]*$' | grep -v -E "alpha|beta" | grep -E "v[1-9]\.(2[5-9]|[3-9][0-9])" | awk -F. '{if(!a[$1"."$2]++)print $1"."$2"."$NF}' | sort -r | sed s/v//g | jq -R -c -s 'split("\n")[:-1]'),\
6666
\"labels\":[\
67-
\"Basic && (ClusterResource || NodePort || StorageClass)\", \
67+
\"Basic && (ClusterResource || NodePort || StorageClass || PriorityClass)\", \
6868
\"ResourceFiltering && !Restic\", \
6969
\"ResourceModifier || (Backups && BackupsSync) || PrivilegesMgmt || OrderedResources\", \
7070
\"(NamespaceMapping && Single && Restic) || (NamespaceMapping && Multiple && Restic)\"\
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implement PriorityClass Support

pkg/cmd/cli/install/install.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package install
1818

1919
import (
20+
"context"
2021
"fmt"
2122
"os"
2223
"path/filepath"
@@ -26,6 +27,7 @@ import (
2627
"github.com/vmware-tanzu/velero/pkg/uploader"
2728

2829
"github.com/pkg/errors"
30+
"github.com/sirupsen/logrus"
2931
"github.com/spf13/cobra"
3032
"github.com/spf13/pflag"
3133
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -91,6 +93,8 @@ type Options struct {
9193
ItemBlockWorkerCount int
9294
NodeAgentDisableHostPath bool
9395
kubeletRootDir string
96+
ServerPriorityClassName string
97+
NodeAgentPriorityClassName string
9498
}
9599

96100
// BindFlags adds command line values to the options struct.
@@ -194,6 +198,18 @@ func (o *Options) BindFlags(flags *pflag.FlagSet) {
194198
o.ItemBlockWorkerCount,
195199
"Number of worker threads to process ItemBlocks. Default is one. Optional.",
196200
)
201+
flags.StringVar(
202+
&o.ServerPriorityClassName,
203+
"server-priority-class-name",
204+
o.ServerPriorityClassName,
205+
"Priority class name for the Velero server deployment. Optional.",
206+
)
207+
flags.StringVar(
208+
&o.NodeAgentPriorityClassName,
209+
"node-agent-priority-class-name",
210+
o.NodeAgentPriorityClassName,
211+
"Priority class name for the node agent daemonset. Optional.",
212+
)
197213
}
198214

199215
// NewInstallOptions instantiates a new, default InstallOptions struct.
@@ -301,6 +317,8 @@ func (o *Options) AsVeleroOptions() (*install.VeleroOptions, error) {
301317
ItemBlockWorkerCount: o.ItemBlockWorkerCount,
302318
KubeletRootDir: o.kubeletRootDir,
303319
NodeAgentDisableHostPath: o.NodeAgentDisableHostPath,
320+
ServerPriorityClassName: o.ServerPriorityClassName,
321+
NodeAgentPriorityClassName: o.NodeAgentPriorityClassName,
304322
}, nil
305323
}
306324

@@ -389,6 +407,23 @@ func (o *Options) Run(c *cobra.Command, f client.Factory) error {
389407
if err != nil {
390408
return err
391409
}
410+
411+
// Get Kubernetes client for priority class validation
412+
kubeClient, err := f.KubeClient()
413+
if err != nil {
414+
return err
415+
}
416+
417+
// Validate priority classes if specified
418+
logger := logrus.New()
419+
logger.SetOutput(os.Stdout)
420+
if o.ServerPriorityClassName != "" {
421+
kubeutil.ValidatePriorityClass(context.Background(), kubeClient, o.ServerPriorityClassName, logger.WithField("component", "server"))
422+
}
423+
if o.NodeAgentPriorityClassName != "" {
424+
kubeutil.ValidatePriorityClass(context.Background(), kubeClient, o.NodeAgentPriorityClassName, logger.WithField("component", "node-agent"))
425+
}
426+
392427
errorMsg := fmt.Sprintf("\n\nError installing Velero. Use `kubectl logs deploy/velero -n %s` to check the deploy logs", o.Namespace)
393428

394429
err = install.Install(dynamicFactory, kbClient, resources, os.Stdout)
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
Copyright the Velero contributors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package install
18+
19+
import (
20+
"testing"
21+
22+
"github.com/spf13/pflag"
23+
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/require"
25+
)
26+
27+
func TestPriorityClassNameFlag(t *testing.T) {
28+
// Test that the flag is properly defined
29+
o := NewInstallOptions()
30+
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
31+
o.BindFlags(flags)
32+
33+
// Verify the server priority class flag exists
34+
serverFlag := flags.Lookup("server-priority-class-name")
35+
assert.NotNil(t, serverFlag, "server-priority-class-name flag should exist")
36+
assert.Equal(t, "Priority class name for the Velero server deployment. Optional.", serverFlag.Usage)
37+
38+
// Verify the node agent priority class flag exists
39+
nodeAgentFlag := flags.Lookup("node-agent-priority-class-name")
40+
assert.NotNil(t, nodeAgentFlag, "node-agent-priority-class-name flag should exist")
41+
assert.Equal(t, "Priority class name for the node agent daemonset. Optional.", nodeAgentFlag.Usage)
42+
43+
// Test with values for both server and node agent
44+
testCases := []struct {
45+
name string
46+
serverPriorityClassName string
47+
nodeAgentPriorityClassName string
48+
expectedServerValue string
49+
expectedNodeAgentValue string
50+
}{
51+
{
52+
name: "with both priority class names",
53+
serverPriorityClassName: "high-priority",
54+
nodeAgentPriorityClassName: "medium-priority",
55+
expectedServerValue: "high-priority",
56+
expectedNodeAgentValue: "medium-priority",
57+
},
58+
{
59+
name: "with only server priority class name",
60+
serverPriorityClassName: "high-priority",
61+
nodeAgentPriorityClassName: "",
62+
expectedServerValue: "high-priority",
63+
expectedNodeAgentValue: "",
64+
},
65+
{
66+
name: "with only node agent priority class name",
67+
serverPriorityClassName: "",
68+
nodeAgentPriorityClassName: "medium-priority",
69+
expectedServerValue: "",
70+
expectedNodeAgentValue: "medium-priority",
71+
},
72+
{
73+
name: "without priority class names",
74+
serverPriorityClassName: "",
75+
nodeAgentPriorityClassName: "",
76+
expectedServerValue: "",
77+
expectedNodeAgentValue: "",
78+
},
79+
}
80+
81+
for _, tc := range testCases {
82+
t.Run(tc.name, func(t *testing.T) {
83+
o := NewInstallOptions()
84+
o.ServerPriorityClassName = tc.serverPriorityClassName
85+
o.NodeAgentPriorityClassName = tc.nodeAgentPriorityClassName
86+
87+
veleroOptions, err := o.AsVeleroOptions()
88+
require.NoError(t, err)
89+
assert.Equal(t, tc.expectedServerValue, veleroOptions.ServerPriorityClassName)
90+
assert.Equal(t, tc.expectedNodeAgentValue, veleroOptions.NodeAgentPriorityClassName)
91+
})
92+
}
93+
}

pkg/cmd/cli/nodeagent/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ func (s *nodeAgentServer) run() {
340340
s.config.dataMoverPrepareTimeout,
341341
s.logger,
342342
s.metrics,
343+
s.config.nodeAgentConfig,
343344
)
344345
if err := dataUploadReconciler.SetupWithManager(s.mgr); err != nil {
345346
s.logger.WithError(err).Fatal("Unable to create the data upload controller")
@@ -364,6 +365,7 @@ func (s *nodeAgentServer) run() {
364365
s.config.dataMoverPrepareTimeout,
365366
s.logger,
366367
s.metrics,
368+
s.config.nodeAgentConfig,
367369
)
368370

369371
if err := dataDownloadReconciler.SetupWithManager(s.mgr); err != nil {

pkg/controller/data_download_controller.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ type DataDownloadReconciler struct {
7171
preparingTimeout time.Duration
7272
metrics *metrics.ServerMetrics
7373
cancelledDataDownload map[string]time.Time
74+
nodeAgentConfigMap string
7475
}
7576

7677
func NewDataDownloadReconciler(
@@ -86,6 +87,7 @@ func NewDataDownloadReconciler(
8687
preparingTimeout time.Duration,
8788
logger logrus.FieldLogger,
8889
metrics *metrics.ServerMetrics,
90+
nodeAgentConfigMap string,
8991
) *DataDownloadReconciler {
9092
return &DataDownloadReconciler{
9193
client: client,
@@ -103,6 +105,7 @@ func NewDataDownloadReconciler(
103105
preparingTimeout: preparingTimeout,
104106
metrics: metrics,
105107
cancelledDataDownload: make(map[string]time.Time),
108+
nodeAgentConfigMap: nodeAgentConfigMap,
106109
}
107110
}
108111

@@ -892,6 +895,8 @@ func (r *DataDownloadReconciler) setupExposeParam(dd *velerov2alpha1api.DataDown
892895
NodeOS: nodeOS,
893896
RestorePVCConfig: r.restorePVCConfig,
894897
LoadAffinity: affinity,
898+
NodeAgentNamespace: dd.Namespace,
899+
NodeAgentConfigMap: r.nodeAgentConfigMap,
895900
}, nil
896901
}
897902

pkg/controller/data_download_controller_test.go

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,7 @@ func initDataDownloadReconcilerWithError(t *testing.T, objects []any, needError
130130

131131
dataPathMgr := datapath.NewManager(1)
132132

133-
return NewDataDownloadReconciler(
134-
&fakeClient,
135-
nil,
136-
fakeKubeClient,
137-
dataPathMgr,
138-
nil,
139-
nil,
140-
nodeagent.RestorePVC{},
141-
corev1api.ResourceRequirements{},
142-
"test-node",
143-
time.Minute*5,
144-
velerotest.NewLogger(),
145-
metrics.NewServerMetrics()), nil
133+
return NewDataDownloadReconciler(&fakeClient, nil, fakeKubeClient, dataPathMgr, nil, nil, nodeagent.RestorePVC{}, corev1api.ResourceRequirements{}, "test-node", time.Minute*5, velerotest.NewLogger(), metrics.NewServerMetrics(), ""), nil
146134
}
147135

148136
func TestDataDownloadReconcile(t *testing.T) {

pkg/controller/data_upload_controller.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ type DataUploadReconciler struct {
8181
preparingTimeout time.Duration
8282
metrics *metrics.ServerMetrics
8383
cancelledDataUpload map[string]time.Time
84+
nodeAgentConfigMap string
8485
}
8586

8687
func NewDataUploadReconciler(
@@ -98,6 +99,7 @@ func NewDataUploadReconciler(
9899
preparingTimeout time.Duration,
99100
log logrus.FieldLogger,
100101
metrics *metrics.ServerMetrics,
102+
nodeAgentConfigMap string,
101103
) *DataUploadReconciler {
102104
return &DataUploadReconciler{
103105
client: client,
@@ -122,6 +124,7 @@ func NewDataUploadReconciler(
122124
preparingTimeout: preparingTimeout,
123125
metrics: metrics,
124126
cancelledDataUpload: make(map[string]time.Time),
127+
nodeAgentConfigMap: nodeAgentConfigMap,
125128
}
126129
}
127130

@@ -973,6 +976,8 @@ func (r *DataUploadReconciler) setupExposeParam(du *velerov2alpha1api.DataUpload
973976
BackupPVCConfig: r.backupPVCConfig,
974977
Resources: r.podResources,
975978
NodeOS: nodeOS,
979+
NodeAgentNamespace: du.Namespace,
980+
NodeAgentConfigMap: r.nodeAgentConfigMap,
976981
}, nil
977982
}
978983

pkg/controller/data_upload_controller_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ func initDataUploaderReconcilerWithError(needError ...error) (*DataUploadReconci
249249
time.Minute*5,
250250
velerotest.NewLogger(),
251251
metrics.NewServerMetrics(),
252+
"",
252253
), nil
253254
}
254255

pkg/exposer/csi_snapshot.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ type CSISnapshotExposeParam struct {
8282

8383
// NodeOS specifies the OS of node that the source volume is attaching
8484
NodeOS string
85+
86+
// NodeAgentNamespace is the namespace where the node agent is running
87+
NodeAgentNamespace string
88+
89+
// NodeAgentConfigMap is the name of the ConfigMap containing node agent configurations
90+
NodeAgentConfigMap string
8591
}
8692

8793
// CSISnapshotExposeWaitParam define the input param for WaitExposed of CSI snapshots
@@ -224,6 +230,8 @@ func (e *csiSnapshotExposer) Expose(ctx context.Context, ownerObject corev1api.O
224230
backupPVCReadOnly,
225231
spcNoRelabeling,
226232
csiExposeParam.NodeOS,
233+
csiExposeParam.NodeAgentNamespace,
234+
csiExposeParam.NodeAgentConfigMap,
227235
)
228236
if err != nil {
229237
return errors.Wrap(err, "error to create backup pod")
@@ -538,6 +546,8 @@ func (e *csiSnapshotExposer) createBackupPod(
538546
backupPVCReadOnly bool,
539547
spcNoRelabeling bool,
540548
nodeOS string,
549+
nodeAgentNamespace string,
550+
nodeAgentConfigMap string,
541551
) (*corev1api.Pod, error) {
542552
podName := ownerObject.Name
543553

@@ -549,6 +559,16 @@ func (e *csiSnapshotExposer) createBackupPod(
549559
return nil, errors.Wrap(err, "error to get inherited pod info from node-agent")
550560
}
551561

562+
// Get the priority class name from node-agent-configmap if available
563+
priorityClassName, err := kube.GetDataMoverPriorityClassName(ctx, nodeAgentNamespace, e.kubeClient, nodeAgentConfigMap)
564+
if err != nil {
565+
e.log.WithError(err).Warn("Failed to get priority class name from node-agent-configmap, using empty value")
566+
priorityClassName = ""
567+
}
568+
if priorityClassName != "" {
569+
e.log.Debugf("Setting priority class %q for data mover pod %s", priorityClassName, podName)
570+
}
571+
552572
var gracePeriod int64
553573
volumeMounts, volumeDevices, volumePath := kube.MakePodPVCAttachment(volumeName, backupPVC.Spec.VolumeMode, backupPVCReadOnly)
554574
volumeMounts = append(volumeMounts, podInfo.volumeMounts...)
@@ -679,6 +699,7 @@ func (e *csiSnapshotExposer) createBackupPod(
679699
Resources: resources,
680700
},
681701
},
702+
PriorityClassName: priorityClassName,
682703
ServiceAccountName: podInfo.serviceAccount,
683704
TerminationGracePeriodSeconds: &gracePeriod,
684705
Volumes: volumes,

0 commit comments

Comments
 (0)