diff --git a/changelogs/unreleased/1745-prydonius b/changelogs/unreleased/1745-prydonius new file mode 100644 index 000000000..aa4ab49a5 --- /dev/null +++ b/changelogs/unreleased/1745-prydonius @@ -0,0 +1 @@ +support setting CPU/memory requests with unbounded limits using velero install diff --git a/pkg/cmd/cli/install/install.go b/pkg/cmd/cli/install/install.go index 8a0129029..190dde93b 100644 --- a/pkg/cmd/cli/install/install.go +++ b/pkg/cmd/cli/install/install.go @@ -72,14 +72,14 @@ func (o *InstallOptions) BindFlags(flags *pflag.FlagSet) { flags.StringVar(&o.Image, "image", o.Image, "image to use for the Velero and restic server pods. Optional.") flags.StringVar(&o.Prefix, "prefix", o.Prefix, "prefix under which all Velero data should be stored within the bucket. Optional.") flags.Var(&o.PodAnnotations, "pod-annotations", "annotations to add to the Velero and restic pods. Optional. Format is key1=value1,key2=value2") - flags.StringVar(&o.VeleroPodCPURequest, "velero-pod-cpu-request", o.VeleroPodCPURequest, "CPU request for Velero pod. Optional.") - flags.StringVar(&o.VeleroPodMemRequest, "velero-pod-mem-request", o.VeleroPodMemRequest, "memory request for Velero pod. Optional.") - flags.StringVar(&o.VeleroPodCPULimit, "velero-pod-cpu-limit", o.VeleroPodCPULimit, "CPU limit for Velero pod. Optional.") - flags.StringVar(&o.VeleroPodMemLimit, "velero-pod-mem-limit", o.VeleroPodMemLimit, "memory limit for Velero pod. Optional.") - flags.StringVar(&o.ResticPodCPURequest, "restic-pod-cpu-request", o.ResticPodCPURequest, "CPU request for restic pods. Optional.") - flags.StringVar(&o.ResticPodMemRequest, "restic-pod-mem-request", o.ResticPodMemRequest, "memory request for restic pods. Optional.") - flags.StringVar(&o.ResticPodCPULimit, "restic-pod-cpu-limit", o.ResticPodCPULimit, "CPU limit for restic pods. Optional.") - flags.StringVar(&o.ResticPodMemLimit, "restic-pod-mem-limit", o.ResticPodMemLimit, "memory limit for restic pods. Optional.") + flags.StringVar(&o.VeleroPodCPURequest, "velero-pod-cpu-request", o.VeleroPodCPURequest, `CPU request for Velero pod. A value of "0" is treated as unbounded. Optional.`) + flags.StringVar(&o.VeleroPodMemRequest, "velero-pod-mem-request", o.VeleroPodMemRequest, `memory request for Velero pod. A value of "0" is treated as unbounded. Optional.`) + flags.StringVar(&o.VeleroPodCPULimit, "velero-pod-cpu-limit", o.VeleroPodCPULimit, `CPU limit for Velero pod. A value of "0" is treated as unbounded. Optional.`) + flags.StringVar(&o.VeleroPodMemLimit, "velero-pod-mem-limit", o.VeleroPodMemLimit, `memory limit for Velero pod. A value of "0" is treated as unbounded. Optional.`) + flags.StringVar(&o.ResticPodCPURequest, "restic-pod-cpu-request", o.ResticPodCPURequest, `CPU request for restic pod. A value of "0" is treated as unbounded. Optional.`) + flags.StringVar(&o.ResticPodMemRequest, "restic-pod-mem-request", o.ResticPodMemRequest, `memory request for restic pod. A value of "0" is treated as unbounded. Optional.`) + flags.StringVar(&o.ResticPodCPULimit, "restic-pod-cpu-limit", o.ResticPodCPULimit, `CPU limit for restic pod. A value of "0" is treated as unbounded. Optional.`) + flags.StringVar(&o.ResticPodMemLimit, "restic-pod-mem-limit", o.ResticPodMemLimit, `memory limit for restic pod. A value of "0" is treated as unbounded. Optional.`) flags.Var(&o.BackupStorageConfig, "backup-location-config", "configuration to use for the backup storage location. Format is key1=value1,key2=value2") flags.Var(&o.VolumeSnapshotConfig, "snapshot-location-config", "configuration to use for the volume snapshot location. Format is key1=value1,key2=value2") flags.BoolVar(&o.UseVolumeSnapshots, "use-volume-snapshots", o.UseVolumeSnapshots, "whether or not to create snapshot location automatically. Set to false if you do not plan to create volume snapshots via a storage provider.") diff --git a/pkg/util/kube/resource_requirements.go b/pkg/util/kube/resource_requirements.go index a1c1f23c0..a1974516c 100644 --- a/pkg/util/kube/resource_requirements.go +++ b/pkg/util/kube/resource_requirements.go @@ -26,7 +26,10 @@ import ( // values and returns a ResourceRequirements struct to be used in a Container. // An error is returned if we cannot parse the request/limit. func ParseResourceRequirements(cpuRequest, memRequest, cpuLimit, memLimit string) (corev1.ResourceRequirements, error) { - var resources corev1.ResourceRequirements + resources := corev1.ResourceRequirements{ + Requests: corev1.ResourceList{}, + Limits: corev1.ResourceList{}, + } parsedCPURequest, err := resource.ParseQuantity(cpuRequest) if err != nil { @@ -48,21 +51,29 @@ func ParseResourceRequirements(cpuRequest, memRequest, cpuLimit, memLimit string return resources, errors.Wrapf(err, `couldn't parse memory limit "%s"`, memLimit) } - if parsedCPURequest.Cmp(parsedCPULimit) > 0 { + // A quantity of 0 is treated as unbounded + unbounded := resource.MustParse("0") + + if parsedCPULimit != unbounded && parsedCPURequest.Cmp(parsedCPULimit) > 0 { return resources, errors.WithStack(errors.Errorf(`CPU request "%s" must be less than or equal to CPU limit "%s"`, cpuRequest, cpuLimit)) } - if parsedMemRequest.Cmp(parsedMemLimit) > 0 { + if parsedMemLimit != unbounded && parsedMemRequest.Cmp(parsedMemLimit) > 0 { return resources, errors.WithStack(errors.Errorf(`Memory request "%s" must be less than or equal to Memory limit "%s"`, memRequest, memLimit)) } - resources.Requests = corev1.ResourceList{ - corev1.ResourceCPU: parsedCPURequest, - corev1.ResourceMemory: parsedMemRequest, + // Only set resources if they are not unbounded + if parsedCPURequest != unbounded { + resources.Requests[corev1.ResourceCPU] = parsedCPURequest } - resources.Limits = corev1.ResourceList{ - corev1.ResourceCPU: parsedCPULimit, - corev1.ResourceMemory: parsedMemLimit, + if parsedMemRequest != unbounded { + resources.Requests[corev1.ResourceMemory] = parsedMemRequest + } + if parsedCPULimit != unbounded { + resources.Limits[corev1.ResourceCPU] = parsedCPULimit + } + if parsedMemLimit != unbounded { + resources.Limits[corev1.ResourceMemory] = parsedMemLimit } return resources, nil diff --git a/pkg/util/kube/resource_requirements_test.go b/pkg/util/kube/resource_requirements_test.go index efd151ca9..68bdfc497 100644 --- a/pkg/util/kube/resource_requirements_test.go +++ b/pkg/util/kube/resource_requirements_test.go @@ -32,15 +32,44 @@ func TestParseResourceRequirements(t *testing.T) { memLimit string } tests := []struct { - name string - args args - wantErr bool + name string + args args + wantErr bool + expected *corev1.ResourceRequirements }{ - {"unbounded quantities", args{"0", "0", "0", "0"}, false}, - {"valid quantities", args{"100m", "128Mi", "200m", "256Mi"}, false}, - {"invalid quantity", args{"100m", "invalid", "200m", "256Mi"}, true}, - {"CPU request greater than limit", args{"300m", "128Mi", "200m", "256Mi"}, true}, - {"memory request greater than limit", args{"100m", "512Mi", "200m", "256Mi"}, true}, + {"unbounded quantities", args{"0", "0", "0", "0"}, false, &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{}, + Limits: corev1.ResourceList{}, + }}, + {"valid quantities", args{"100m", "128Mi", "200m", "256Mi"}, false, nil}, + {"CPU request with unbounded limit", args{"100m", "128Mi", "0", "256Mi"}, false, &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, + }}, + {"Mem request with unbounded limit", args{"100m", "128Mi", "200m", "0"}, false, &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("200m"), + }, + }}, + {"CPU/Mem requests with unbounded limits", args{"100m", "128Mi", "0", "0"}, false, &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: corev1.ResourceList{}, + }}, + {"invalid quantity", args{"100m", "invalid", "200m", "256Mi"}, true, nil}, + {"CPU request greater than limit", args{"300m", "128Mi", "200m", "256Mi"}, true, nil}, + {"memory request greater than limit", args{"100m", "512Mi", "200m", "256Mi"}, true, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -49,17 +78,24 @@ func TestParseResourceRequirements(t *testing.T) { assert.Error(t, err) return } + assert.NoError(t, err) - expected := corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse(tt.args.cpuRequest), - corev1.ResourceMemory: resource.MustParse(tt.args.memRequest), - }, - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse(tt.args.cpuLimit), - corev1.ResourceMemory: resource.MustParse(tt.args.memLimit), - }, + var expected corev1.ResourceRequirements + if tt.expected == nil { + expected = corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(tt.args.cpuRequest), + corev1.ResourceMemory: resource.MustParse(tt.args.memRequest), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(tt.args.cpuLimit), + corev1.ResourceMemory: resource.MustParse(tt.args.memLimit), + }, + } + } else { + expected = *tt.expected } + assert.Equal(t, expected, got) }) } diff --git a/site/docs/master/restic.md b/site/docs/master/restic.md index bef6d89dc..a000df191 100644 --- a/site/docs/master/restic.md +++ b/site/docs/master/restic.md @@ -235,19 +235,19 @@ data: image: myregistry.io/my-custom-helper-image[:OPTIONAL_TAG] # "cpuRequest" sets the request.cpu value on the restic init containers during restore. - # If not set, it will default to "100m". + # If not set, it will default to "100m". A value of "0" is treated as unbounded. cpuRequest: 200m # "memRequest" sets the request.memory value on the restic init containers during restore. - # If not set, it will default to "128Mi". + # If not set, it will default to "128Mi". A value of "0" is treated as unbounded. memRequest: 128Mi # "cpuLimit" sets the request.cpu value on the restic init containers during restore. - # If not set, it will default to "100m". + # If not set, it will default to "100m". A value of "0" is treated as unbounded. cpuLimit: 200m # "memLimit" sets the request.memory value on the restic init containers during restore. - # If not set, it will default to "128Mi". + # If not set, it will default to "128Mi". A value of "0" is treated as unbounded. memLimit: 128Mi