...

Source file src/k8s.io/kubernetes/pkg/scheduler/framework/plugins/dynamicresources/structured/namedresources/namedresourcesmodel.go

Documentation: k8s.io/kubernetes/pkg/scheduler/framework/plugins/dynamicresources/structured/namedresources

     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  }
   154  

View as plain text