k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/dynamicresources/structuredparameters.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 dynamicresources 18 19 import ( 20 "context" 21 "fmt" 22 "sync" 23 24 v1 "k8s.io/api/core/v1" 25 resourcev1alpha2 "k8s.io/api/resource/v1alpha2" 26 "k8s.io/apimachinery/pkg/labels" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/klog/v2" 29 namedresourcesmodel "k8s.io/kubernetes/pkg/scheduler/framework/plugins/dynamicresources/structured/namedresources" 30 ) 31 32 // resources is a map "node name" -> "driver name" -> available and 33 // allocated resources per structured parameter model. 34 type resources map[string]map[string]ResourceModels 35 36 // ResourceModels may have more than one entry because it is valid for a driver to 37 // use more than one structured parameter model. 38 type ResourceModels struct { 39 NamedResources namedresourcesmodel.Model 40 } 41 42 // resourceSliceLister is the subset of resourcev1alpha2listers.ResourceSliceLister needed by 43 // newResourceModel. 44 type resourceSliceLister interface { 45 List(selector labels.Selector) (ret []*resourcev1alpha2.ResourceSlice, err error) 46 } 47 48 // assumeCacheLister is the subset of volumebinding.AssumeCache needed by newResourceModel. 49 type assumeCacheLister interface { 50 List(indexObj interface{}) []interface{} 51 } 52 53 // newResourceModel parses the available information about resources. Objects 54 // with an unknown structured parameter model silently ignored. An error gets 55 // logged later when parameters required for a pod depend on such an unknown 56 // model. 57 func newResourceModel(logger klog.Logger, resourceSliceLister resourceSliceLister, claimAssumeCache assumeCacheLister, inFlightAllocations *sync.Map) (resources, error) { 58 model := make(resources) 59 60 slices, err := resourceSliceLister.List(labels.Everything()) 61 if err != nil { 62 return nil, fmt.Errorf("list node resource slices: %w", err) 63 } 64 for _, slice := range slices { 65 if model[slice.NodeName] == nil { 66 model[slice.NodeName] = make(map[string]ResourceModels) 67 } 68 resource := model[slice.NodeName][slice.DriverName] 69 namedresourcesmodel.AddResources(&resource.NamedResources, slice.NamedResources) 70 model[slice.NodeName][slice.DriverName] = resource 71 } 72 73 objs := claimAssumeCache.List(nil) 74 for _, obj := range objs { 75 claim, ok := obj.(*resourcev1alpha2.ResourceClaim) 76 if !ok { 77 return nil, fmt.Errorf("got unexpected object of type %T from claim assume cache", obj) 78 } 79 if obj, ok := inFlightAllocations.Load(claim.UID); ok { 80 // If the allocation is in-flight, then we have to use the allocation 81 // from that claim. 82 claim = obj.(*resourcev1alpha2.ResourceClaim) 83 } 84 if claim.Status.Allocation == nil { 85 continue 86 } 87 for _, handle := range claim.Status.Allocation.ResourceHandles { 88 structured := handle.StructuredData 89 if structured == nil { 90 continue 91 } 92 if model[structured.NodeName] == nil { 93 model[structured.NodeName] = make(map[string]ResourceModels) 94 } 95 resource := model[structured.NodeName][handle.DriverName] 96 for _, result := range structured.Results { 97 // Call AddAllocation for each known model. Each call itself needs to check for nil. 98 namedresourcesmodel.AddAllocation(&resource.NamedResources, result.NamedResources) 99 } 100 } 101 } 102 103 return model, nil 104 } 105 106 func newClaimController(logger klog.Logger, class *resourcev1alpha2.ResourceClass, classParameters *resourcev1alpha2.ResourceClassParameters, claimParameters *resourcev1alpha2.ResourceClaimParameters) (*claimController, error) { 107 // Each node driver is separate from the others. Each driver may have 108 // multiple requests which need to be allocated together, so here 109 // we have to collect them per model. 110 type perDriverRequests struct { 111 parameters []runtime.RawExtension 112 requests []*resourcev1alpha2.NamedResourcesRequest 113 } 114 namedresourcesRequests := make(map[string]perDriverRequests) 115 for i, request := range claimParameters.DriverRequests { 116 driverName := request.DriverName 117 p := namedresourcesRequests[driverName] 118 for e, request := range request.Requests { 119 switch { 120 case request.ResourceRequestModel.NamedResources != nil: 121 p.parameters = append(p.parameters, request.VendorParameters) 122 p.requests = append(p.requests, request.ResourceRequestModel.NamedResources) 123 default: 124 return nil, fmt.Errorf("claim parameters %s: driverRequests[%d].requests[%d]: no supported structured parameters found", klog.KObj(claimParameters), i, e) 125 } 126 } 127 if len(p.requests) > 0 { 128 namedresourcesRequests[driverName] = p 129 } 130 } 131 132 c := &claimController{ 133 class: class, 134 classParameters: classParameters, 135 claimParameters: claimParameters, 136 namedresources: make(map[string]perDriverController, len(namedresourcesRequests)), 137 } 138 for driverName, perDriver := range namedresourcesRequests { 139 var filter *resourcev1alpha2.NamedResourcesFilter 140 for _, f := range classParameters.Filters { 141 if f.DriverName == driverName && f.ResourceFilterModel.NamedResources != nil { 142 filter = f.ResourceFilterModel.NamedResources 143 break 144 } 145 } 146 controller, err := namedresourcesmodel.NewClaimController(filter, perDriver.requests) 147 if err != nil { 148 return nil, fmt.Errorf("creating claim controller for named resources structured model: %w", err) 149 } 150 c.namedresources[driverName] = perDriverController{ 151 parameters: perDriver.parameters, 152 controller: controller, 153 } 154 } 155 return c, nil 156 } 157 158 // claimController currently wraps exactly one structured parameter model. 159 160 type claimController struct { 161 class *resourcev1alpha2.ResourceClass 162 classParameters *resourcev1alpha2.ResourceClassParameters 163 claimParameters *resourcev1alpha2.ResourceClaimParameters 164 namedresources map[string]perDriverController 165 } 166 167 type perDriverController struct { 168 parameters []runtime.RawExtension 169 controller *namedresourcesmodel.Controller 170 } 171 172 func (c claimController) nodeIsSuitable(ctx context.Context, nodeName string, resources resources) (bool, error) { 173 nodeResources := resources[nodeName] 174 for driverName, perDriver := range c.namedresources { 175 okay, err := perDriver.controller.NodeIsSuitable(ctx, nodeResources[driverName].NamedResources) 176 if err != nil { 177 // This is an error in the CEL expression which needs 178 // to be fixed. Better fail very visibly instead of 179 // ignoring the node. 180 return false, fmt.Errorf("checking node %q and resources of driver %q: %w", nodeName, driverName, err) 181 } 182 if !okay { 183 return false, nil 184 } 185 } 186 return true, nil 187 } 188 189 func (c claimController) allocate(ctx context.Context, nodeName string, resources resources) (string, *resourcev1alpha2.AllocationResult, error) { 190 allocation := &resourcev1alpha2.AllocationResult{ 191 Shareable: c.claimParameters.Shareable, 192 AvailableOnNodes: &v1.NodeSelector{ 193 NodeSelectorTerms: []v1.NodeSelectorTerm{ 194 { 195 MatchExpressions: []v1.NodeSelectorRequirement{ 196 {Key: "kubernetes.io/hostname", Operator: v1.NodeSelectorOpIn, Values: []string{nodeName}}, 197 }, 198 }, 199 }, 200 }, 201 } 202 203 nodeResources := resources[nodeName] 204 for driverName, perDriver := range c.namedresources { 205 // Must return one entry for each request. The entry may be nil. This way, 206 // the result can be correlated with the per-request parameters. 207 results, err := perDriver.controller.Allocate(ctx, nodeResources[driverName].NamedResources) 208 if err != nil { 209 return "", nil, fmt.Errorf("allocating via named resources structured model: %w", err) 210 } 211 handle := resourcev1alpha2.ResourceHandle{ 212 DriverName: driverName, 213 StructuredData: &resourcev1alpha2.StructuredResourceHandle{ 214 NodeName: nodeName, 215 }, 216 } 217 for i, result := range results { 218 if result == nil { 219 continue 220 } 221 handle.StructuredData.Results = append(handle.StructuredData.Results, 222 resourcev1alpha2.DriverAllocationResult{ 223 VendorRequestParameters: perDriver.parameters[i], 224 AllocationResultModel: resourcev1alpha2.AllocationResultModel{ 225 NamedResources: result, 226 }, 227 }, 228 ) 229 } 230 if c.classParameters != nil { 231 for _, p := range c.classParameters.VendorParameters { 232 if p.DriverName == driverName { 233 handle.StructuredData.VendorClassParameters = p.Parameters 234 break 235 } 236 } 237 } 238 for _, request := range c.claimParameters.DriverRequests { 239 if request.DriverName == driverName { 240 handle.StructuredData.VendorClaimParameters = request.VendorParameters 241 break 242 } 243 } 244 allocation.ResourceHandles = append(allocation.ResourceHandles, handle) 245 } 246 247 return c.class.DriverName, allocation, nil 248 }