|
| 1 | +package resources |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + |
| 6 | + corev1 "k8s.io/api/core/v1" |
| 7 | + "k8s.io/apimachinery/pkg/api/resource" |
| 8 | + |
| 9 | + dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" |
| 10 | +) |
| 11 | + |
| 12 | +// Takes a component and returns the resource requests and limits that it defines. |
| 13 | +// If a resource request or limit is not defined within the component, it will |
| 14 | +// not be populated in the corresponding corev1.ResourceList map. |
| 15 | +// Returns an error if a non-container component is passed to the function, or if an error |
| 16 | +// occurs while parsing a resource limit or request. |
| 17 | +func ParseResourcesFromComponent(component *dw.Component) (*corev1.ResourceRequirements, error) { |
| 18 | + if component.Container == nil { |
| 19 | + return nil, fmt.Errorf("attempted to parse resource requirements from a non-container component") |
| 20 | + } |
| 21 | + |
| 22 | + resources := &corev1.ResourceRequirements{ |
| 23 | + Limits: corev1.ResourceList{}, |
| 24 | + Requests: corev1.ResourceList{}, |
| 25 | + } |
| 26 | + |
| 27 | + memLimitStr := component.Container.MemoryLimit |
| 28 | + if memLimitStr != "" { |
| 29 | + memoryLimit, err := resource.ParseQuantity(memLimitStr) |
| 30 | + if err != nil { |
| 31 | + return nil, fmt.Errorf("failed to parse memory limit for container component %s: %w", component.Name, err) |
| 32 | + } |
| 33 | + resources.Limits[corev1.ResourceMemory] = memoryLimit |
| 34 | + } |
| 35 | + |
| 36 | + memRequestStr := component.Container.MemoryRequest |
| 37 | + if memRequestStr != "" { |
| 38 | + memoryRequest, err := resource.ParseQuantity(memRequestStr) |
| 39 | + if err != nil { |
| 40 | + return nil, fmt.Errorf("failed to parse memory request for container component %s: %w", component.Name, err) |
| 41 | + } |
| 42 | + resources.Requests[corev1.ResourceMemory] = memoryRequest |
| 43 | + } |
| 44 | + |
| 45 | + cpuLimitStr := component.Container.CpuLimit |
| 46 | + if cpuLimitStr != "" { |
| 47 | + cpuLimit, err := resource.ParseQuantity(cpuLimitStr) |
| 48 | + if err != nil { |
| 49 | + return nil, fmt.Errorf("failed to parse CPU limit for container component %s: %w", component.Name, err) |
| 50 | + } |
| 51 | + resources.Limits[corev1.ResourceCPU] = cpuLimit |
| 52 | + } |
| 53 | + |
| 54 | + cpuRequestStr := component.Container.CpuRequest |
| 55 | + if cpuRequestStr != "" { |
| 56 | + cpuRequest, err := resource.ParseQuantity(cpuRequestStr) |
| 57 | + if err != nil { |
| 58 | + return nil, fmt.Errorf("failed to parse CPU request for container component %s: %w", component.Name, err) |
| 59 | + } |
| 60 | + resources.Requests[corev1.ResourceCPU] = cpuRequest |
| 61 | + } |
| 62 | + |
| 63 | + return resources, nil |
| 64 | +} |
| 65 | + |
| 66 | +// Adds the resource limits and requests that are set in the component "toAdd" to "resources". |
| 67 | +// Returns an error if the resources defined in toAdd could not be parsed. |
| 68 | +// |
| 69 | +// Note: only resources that are set in the function argument "resources" will be summed |
| 70 | +// with the resources set in "toAdd". |
| 71 | +// For example, if "resources" does not set a CPU limit but "toAdd" does set a CPU limit, |
| 72 | +// the CPU limit of "resources" will remain unset. |
| 73 | +func AddResourceRequirements(resources *corev1.ResourceRequirements, toAdd *dw.Component) error { |
| 74 | + componentResources, err := ParseResourcesFromComponent(toAdd) |
| 75 | + if err != nil { |
| 76 | + return err |
| 77 | + } |
| 78 | + |
| 79 | + for resourceName, limit := range resources.Limits { |
| 80 | + if componentLimit, ok := componentResources.Limits[resourceName]; ok { |
| 81 | + limit.Add(componentLimit) |
| 82 | + resources.Limits[resourceName] = limit |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + for resourceName, request := range resources.Requests { |
| 87 | + if componentRequest, ok := componentResources.Requests[resourceName]; ok { |
| 88 | + request.Add(componentRequest) |
| 89 | + resources.Requests[resourceName] = request |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + return nil |
| 94 | +} |
| 95 | + |
| 96 | +// Applies the given resource limits and requirements that are non-zero to the container component. |
| 97 | +// If a resource limit or request has a value of zero, then the corresponding limit or request is not set |
| 98 | +// in the container component's resource requirements. |
| 99 | +func ApplyResourceRequirementsToComponent(container *dw.ContainerComponent, resources *corev1.ResourceRequirements) { |
| 100 | + memLimit := resources.Limits[corev1.ResourceMemory] |
| 101 | + if !memLimit.IsZero() { |
| 102 | + container.MemoryLimit = memLimit.String() |
| 103 | + } |
| 104 | + |
| 105 | + cpuLimit := resources.Limits[corev1.ResourceCPU] |
| 106 | + if !cpuLimit.IsZero() { |
| 107 | + container.CpuLimit = cpuLimit.String() |
| 108 | + } |
| 109 | + |
| 110 | + memRequest := resources.Requests[corev1.ResourceMemory] |
| 111 | + if !memRequest.IsZero() { |
| 112 | + container.MemoryRequest = memRequest.String() |
| 113 | + } |
| 114 | + |
| 115 | + cpuRequest := resources.Requests[corev1.ResourceCPU] |
| 116 | + if !cpuRequest.IsZero() { |
| 117 | + container.CpuRequest = cpuRequest.String() |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +// ProcessResources checks that specified resources are valid (e.g. requests are less than limits) and supports |
| 122 | +// un-setting resources that have default values by interpreting zero as "do not set" |
| 123 | +func ProcessResources(resources *corev1.ResourceRequirements) (*corev1.ResourceRequirements, error) { |
| 124 | + result := resources.DeepCopy() |
| 125 | + |
| 126 | + if result.Limits.Memory().IsZero() { |
| 127 | + delete(result.Limits, corev1.ResourceMemory) |
| 128 | + } |
| 129 | + if result.Limits.Cpu().IsZero() { |
| 130 | + delete(result.Limits, corev1.ResourceCPU) |
| 131 | + } |
| 132 | + if result.Requests.Memory().IsZero() { |
| 133 | + delete(result.Requests, corev1.ResourceMemory) |
| 134 | + } |
| 135 | + if result.Requests.Cpu().IsZero() { |
| 136 | + delete(result.Requests, corev1.ResourceCPU) |
| 137 | + } |
| 138 | + |
| 139 | + memLimit, hasMemLimit := result.Limits[corev1.ResourceMemory] |
| 140 | + memRequest, hasMemRequest := result.Requests[corev1.ResourceMemory] |
| 141 | + if hasMemLimit && hasMemRequest && memRequest.Cmp(memLimit) > 0 { |
| 142 | + return result, fmt.Errorf("project clone memory request (%s) must be less than limit (%s)", memRequest.String(), memLimit.String()) |
| 143 | + } |
| 144 | + |
| 145 | + cpuLimit, hasCPULimit := result.Limits[corev1.ResourceCPU] |
| 146 | + cpuRequest, hasCPURequest := result.Requests[corev1.ResourceCPU] |
| 147 | + if hasCPULimit && hasCPURequest && cpuRequest.Cmp(cpuLimit) > 0 { |
| 148 | + return result, fmt.Errorf("project clone CPU request (%s) must be less than limit (%s)", cpuRequest.String(), cpuLimit.String()) |
| 149 | + } |
| 150 | + |
| 151 | + if len(result.Limits) == 0 { |
| 152 | + result.Limits = nil |
| 153 | + } |
| 154 | + if len(result.Requests) == 0 { |
| 155 | + result.Requests = nil |
| 156 | + } |
| 157 | + |
| 158 | + return result, nil |
| 159 | +} |
0 commit comments