Skip to content

Commit 959f3c8

Browse files
authored
add validation to resource requirement (#685)
* add validation for resource requirement Signed-off-by: Stephanie <[email protected]> * address review comment Signed-off-by: Stephanie <[email protected]>
1 parent dfec9a4 commit 959f3c8

File tree

4 files changed

+111
-0
lines changed

4 files changed

+111
-0
lines changed

pkg/validation/components.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,41 @@ func ValidateComponents(components []v1alpha2.Component) (returnedErr error) {
6060
returnedErr = multierror.Append(returnedErr, reservedEnvErr)
6161
}
6262
}
63+
var memoryLimit, cpuLimit, memoryRequest, cpuRequest resource.Quantity
64+
if component.Container.MemoryLimit != "" {
65+
memoryLimit, err = resource.ParseQuantity(component.Container.MemoryLimit)
66+
if err != nil {
67+
parseQuantityErr := &ParsingResourceRequirementError{resource: MemoryLimit, cmpName: component.Name, errMsg: err.Error()}
68+
returnedErr = multierror.Append(returnedErr, parseQuantityErr)
69+
}
70+
}
71+
if component.Container.CpuLimit != "" {
72+
cpuLimit, err = resource.ParseQuantity(component.Container.CpuLimit)
73+
if err != nil {
74+
parseQuantityErr := &ParsingResourceRequirementError{resource: CpuLimit, cmpName: component.Name, errMsg: err.Error()}
75+
returnedErr = multierror.Append(returnedErr, parseQuantityErr)
76+
}
77+
}
78+
if component.Container.MemoryRequest != "" {
79+
memoryRequest, err = resource.ParseQuantity(component.Container.MemoryRequest)
80+
if err != nil {
81+
parseQuantityErr := &ParsingResourceRequirementError{resource: MemoryRequest, cmpName: component.Name, errMsg: err.Error()}
82+
returnedErr = multierror.Append(returnedErr, parseQuantityErr)
83+
} else if !memoryLimit.IsZero() && memoryRequest.Cmp(memoryLimit) > 0 {
84+
invalidResourceRequest := &InvalidResourceRequestError{cmpName: component.Name, errMsg: fmt.Sprintf("memoryRequest is greater than memoryLimit.")}
85+
returnedErr = multierror.Append(returnedErr, invalidResourceRequest)
86+
}
87+
}
88+
if component.Container.CpuRequest != "" {
89+
cpuRequest, err = resource.ParseQuantity(component.Container.CpuRequest)
90+
if err != nil {
91+
parseQuantityErr := &ParsingResourceRequirementError{resource: CpuRequest, cmpName: component.Name, errMsg: err.Error()}
92+
returnedErr = multierror.Append(returnedErr, parseQuantityErr)
93+
} else if !cpuLimit.IsZero() && cpuRequest.Cmp(cpuLimit) > 0 {
94+
invalidResourceRequest := &InvalidResourceRequestError{cmpName: component.Name, errMsg: fmt.Sprintf("cpuRequest is greater than cpuLimit.")}
95+
returnedErr = multierror.Append(returnedErr, invalidResourceRequest)
96+
}
97+
}
6398

6499
// if annotation is not empty and dedicatedPod is false
65100
if component.Container.Annotation != nil && component.Container.DedicatedPod != nil && !(*component.Container.DedicatedPod) {

pkg/validation/components_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,24 @@ func generateDummyContainerComponent(name string, volMounts []v1alpha2.VolumeMou
3131
}}}
3232
}
3333

34+
// generateDummyContainerComponentWithResourceRequirement returns a dummy container component with resource requirement for testing
35+
func generateDummyContainerComponentWithResourceRequirement(name, memoryLimit, memoryRequest, cpuLimit, cpuRequest string) v1alpha2.Component {
36+
image := "docker.io/maven:latest"
37+
38+
return v1alpha2.Component{
39+
Name: name,
40+
ComponentUnion: v1alpha2.ComponentUnion{
41+
Container: &v1alpha2.ContainerComponent{
42+
Container: v1alpha2.Container{
43+
Image: image,
44+
MemoryLimit: memoryLimit,
45+
MemoryRequest: memoryRequest,
46+
CpuLimit: cpuLimit,
47+
CpuRequest: cpuRequest,
48+
},
49+
}}}
50+
}
51+
3452
// generateDummyVolumeComponent returns a dummy volume component for testing
3553
func generateDummyVolumeComponent(name, size string) v1alpha2.Component {
3654

@@ -236,6 +254,9 @@ func TestValidateComponents(t *testing.T) {
236254
pluginOverridesFromMainDevfile := attributes.Attributes{}.PutString(ImportSourceAttribute,
237255
"uri: http://127.0.0.1:8080").PutString(PluginOverrideAttribute, "main devfile")
238256
invalidURIErrWithImportAttributes := ".*invalid URI for request, imported from uri: http://127.0.0.1:8080, in plugin overrides from main devfile"
257+
invalidCpuRequest := ".*cpuRequest is greater than cpuLimit."
258+
invalidMemoryRequest := ".*memoryRequest is greater than memoryLimit."
259+
quantityParsingErr := "error parsing .* requirement for component.*"
239260

240261
tests := []struct {
241262
name string
@@ -310,6 +331,30 @@ func TestValidateComponents(t *testing.T) {
310331
generateDummyContainerComponent("name1", nil, []v1alpha2.Endpoint{endpointUrl18080, endpointUrl28080}, nil, v1alpha2.Annotation{}, false),
311332
},
312333
},
334+
{
335+
name: "Valid containers with valid resource requirement",
336+
components: []v1alpha2.Component{
337+
generateDummyContainerComponentWithResourceRequirement("name1", "1024Mi", "512Mi", "1024Mi", "512Mi"),
338+
generateDummyContainerComponentWithResourceRequirement("name2", "", "512Mi", "", "512Mi"),
339+
generateDummyContainerComponentWithResourceRequirement("name3", "1024Mi", "", "1024Mi", ""),
340+
generateDummyContainerComponentWithResourceRequirement("name4", "", "", "", ""),
341+
},
342+
},
343+
{
344+
name: "Invalid containers with resource limit smaller than resource requested",
345+
components: []v1alpha2.Component{
346+
generateDummyContainerComponentWithResourceRequirement("name1", "512Mi", "1024Mi", "", ""),
347+
generateDummyContainerComponentWithResourceRequirement("name2", "", "", "512Mi", "1024Mi"),
348+
},
349+
wantErr: []string{invalidMemoryRequest, invalidCpuRequest},
350+
},
351+
{
352+
name: "Invalid container with resource quantity parsing error",
353+
components: []v1alpha2.Component{
354+
generateDummyContainerComponentWithResourceRequirement("name1", "512invalid", "", "", ""),
355+
},
356+
wantErr: []string{quantityParsingErr},
357+
},
313358
{
314359
name: "Valid container with deployment, service and ingress annotations",
315360
components: []v1alpha2.Component{

pkg/validation/errors.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,36 @@ func (e *InvalidProjectCheckoutRemoteError) Error() string {
161161
return fmt.Sprintf("unable to find the checkout remote %s in the remotes for %s %s", e.checkoutRemote, e.objectType, e.objectName)
162162
}
163163

164+
type ResourceRequirementType string
165+
166+
const (
167+
MemoryLimit ResourceRequirementType = "memoryLimit"
168+
CpuLimit ResourceRequirementType = "cpuLimit"
169+
MemoryRequest ResourceRequirementType = "memoryRequest"
170+
CpuRequest ResourceRequirementType = "cpuRequest"
171+
)
172+
173+
//ParsingResourceRequirementError returns an error if failed to parse a resource requirement
174+
type ParsingResourceRequirementError struct {
175+
resource ResourceRequirementType
176+
cmpName string
177+
errMsg string
178+
}
179+
180+
func (e *ParsingResourceRequirementError) Error() string {
181+
return fmt.Sprintf("error parsing %s requirement for component %s: %s", e.resource, e.cmpName, e.errMsg)
182+
}
183+
184+
//InvalidResourceRequestError returns an error if resource limit < resource requested
185+
type InvalidResourceRequestError struct {
186+
cmpName string
187+
errMsg string
188+
}
189+
190+
func (e *InvalidResourceRequestError) Error() string {
191+
return fmt.Sprintf("invalid resource request for component %s: %s", e.cmpName, e.errMsg)
192+
}
193+
164194
type AnnotationType string
165195

166196
const (

pkg/validation/validation-rule.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Common rules for all components types:
3535
1. the container components must reference a valid volume component if it uses volume mounts, and the volume components are unique
3636
2. `PROJECT_SOURCE` or `PROJECTS_ROOT` are reserved environment variables defined under env, cannot be defined again in `env`
3737
3. the annotations should not have conflict values for same key, except deployment annotations and service annotations set for a container with `dedicatedPod=true`
38+
4. resource requirements, e.g. `cpuLimit`, `cpuRequest`, `memoryLimit`, `memoryRequest`, must be in valid quantity format; and the resource requested must be less than the resource limit (if specified).
3839

3940
#### Plugin Component
4041
- Commands in plugins components share the same commands validation rules as listed above. Validation occurs after overriding and merging, in flattened devfile

0 commit comments

Comments
 (0)