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  }