k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/dynamicresources/structured/namedresources/namedresourcesmodel.go (about) 1 /* 2 Copyright 2024 The Kubernetes Authors. 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 namedresources 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "slices" 24 25 resourceapi "k8s.io/api/resource/v1alpha2" 26 "k8s.io/apiserver/pkg/cel/environment" 27 "k8s.io/dynamic-resource-allocation/structured/namedresources/cel" 28 ) 29 30 // These types and fields are all exported to allow logging them with 31 // pretty-printed JSON. 32 33 type Model struct { 34 Instances []InstanceAllocation 35 } 36 37 type InstanceAllocation struct { 38 Allocated bool 39 Instance *resourceapi.NamedResourcesInstance 40 } 41 42 // AddResources must be called first to create entries for all existing 43 // resource instances. The resources parameter may be nil. 44 func AddResources(m *Model, resources *resourceapi.NamedResourcesResources) { 45 if resources == nil { 46 return 47 } 48 49 for i := range resources.Instances { 50 m.Instances = append(m.Instances, InstanceAllocation{Instance: &resources.Instances[i]}) 51 } 52 } 53 54 // AddAllocation may get called after AddResources to mark some resource 55 // instances as allocated. The result parameter may be nil. 56 func AddAllocation(m *Model, result *resourceapi.NamedResourcesAllocationResult) { 57 if result == nil { 58 return 59 } 60 for i := range m.Instances { 61 if m.Instances[i].Instance.Name == result.Name { 62 m.Instances[i].Allocated = true 63 break 64 } 65 } 66 } 67 68 func NewClaimController(filter *resourceapi.NamedResourcesFilter, requests []*resourceapi.NamedResourcesRequest) (*Controller, error) { 69 c := &Controller{} 70 if filter != nil { 71 compilation := cel.Compiler.CompileCELExpression(filter.Selector, environment.StoredExpressions) 72 if compilation.Error != nil { 73 // Shouldn't happen because of validation. 74 return nil, fmt.Errorf("compile class filter CEL expression: %w", compilation.Error) 75 } 76 c.filter = &compilation 77 } 78 for _, request := range requests { 79 compilation := cel.Compiler.CompileCELExpression(request.Selector, environment.StoredExpressions) 80 if compilation.Error != nil { 81 // Shouldn't happen because of validation. 82 return nil, fmt.Errorf("compile request CEL expression: %w", compilation.Error) 83 } 84 c.requests = append(c.requests, compilation) 85 } 86 return c, nil 87 } 88 89 type Controller struct { 90 filter *cel.CompilationResult 91 requests []cel.CompilationResult 92 } 93 94 func (c *Controller) NodeIsSuitable(ctx context.Context, model Model) (bool, error) { 95 indices, err := c.allocate(ctx, model) 96 return len(indices) == len(c.requests), err 97 } 98 99 func (c *Controller) Allocate(ctx context.Context, model Model) ([]*resourceapi.NamedResourcesAllocationResult, error) { 100 indices, err := c.allocate(ctx, model) 101 if err != nil { 102 return nil, err 103 } 104 if len(indices) != len(c.requests) { 105 return nil, errors.New("insufficient resources") 106 } 107 results := make([]*resourceapi.NamedResourcesAllocationResult, len(c.requests)) 108 for i := range c.requests { 109 results[i] = &resourceapi.NamedResourcesAllocationResult{Name: model.Instances[indices[i]].Instance.Name} 110 } 111 return results, nil 112 } 113 114 func (c *Controller) allocate(ctx context.Context, model Model) ([]int, error) { 115 // Shallow copy, we need to modify the allocated boolean. 116 instances := slices.Clone(model.Instances) 117 indices := make([]int, 0, len(c.requests)) 118 119 for _, request := range c.requests { 120 for i, instance := range instances { 121 if instance.Allocated { 122 continue 123 } 124 if c.filter != nil { 125 okay, err := c.filter.Evaluate(ctx, instance.Instance.Attributes) 126 if err != nil { 127 return nil, fmt.Errorf("evaluate filter CEL expression: %w", err) 128 } 129 if !okay { 130 continue 131 } 132 } 133 okay, err := request.Evaluate(ctx, instance.Instance.Attributes) 134 if err != nil { 135 return nil, fmt.Errorf("evaluate request CEL expression: %w", err) 136 } 137 if !okay { 138 continue 139 } 140 // Found a matching, unallocated instance. Let's use it. 141 // 142 // A more thorough search would include backtracking because 143 // allocating one "large" instances for a "small" request may 144 // make a following "large" request impossible to satisfy when 145 // only "small" instances are left. 146 instances[i].Allocated = true 147 indices = append(indices, i) 148 break 149 } 150 } 151 return indices, nil 152 153 }