k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/cm/dra/claiminfo.go (about)

     1  /*
     2  Copyright 2022 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 dra
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  
    23  	resourcev1alpha2 "k8s.io/api/resource/v1alpha2"
    24  	"k8s.io/apimachinery/pkg/types"
    25  	"k8s.io/apimachinery/pkg/util/sets"
    26  	"k8s.io/kubernetes/pkg/kubelet/cm/dra/state"
    27  	"k8s.io/kubernetes/pkg/kubelet/cm/util/cdi"
    28  	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
    29  )
    30  
    31  // ClaimInfo holds information required
    32  // to prepare and unprepare a resource claim.
    33  // +k8s:deepcopy-gen=true
    34  type ClaimInfo struct {
    35  	state.ClaimInfoState
    36  	// annotations is a mapping of container annotations per DRA plugin associated with
    37  	// a prepared resource
    38  	annotations map[string][]kubecontainer.Annotation
    39  	prepared    bool
    40  }
    41  
    42  // claimInfoCache is a cache of processed resource claims keyed by namespace/claimname.
    43  type claimInfoCache struct {
    44  	sync.RWMutex
    45  	state     state.CheckpointState
    46  	claimInfo map[string]*ClaimInfo
    47  }
    48  
    49  // newClaimInfoFromClaim creates a new claim info from a resource claim.
    50  func newClaimInfoFromClaim(claim *resourcev1alpha2.ResourceClaim) *ClaimInfo {
    51  	// Grab the allocation.resourceHandles. If there are no
    52  	// allocation.resourceHandles, create a single resourceHandle with no
    53  	// content. This will trigger processing of this claim by a single
    54  	// kubelet plugin whose name matches resourceClaim.Status.DriverName.
    55  	resourceHandles := claim.Status.Allocation.ResourceHandles
    56  	if len(resourceHandles) == 0 {
    57  		resourceHandles = make([]resourcev1alpha2.ResourceHandle, 1)
    58  	}
    59  	claimInfoState := state.ClaimInfoState{
    60  		DriverName:      claim.Status.DriverName,
    61  		ClassName:       claim.Spec.ResourceClassName,
    62  		ClaimUID:        claim.UID,
    63  		ClaimName:       claim.Name,
    64  		Namespace:       claim.Namespace,
    65  		PodUIDs:         sets.New[string](),
    66  		ResourceHandles: resourceHandles,
    67  		CDIDevices:      make(map[string][]string),
    68  	}
    69  	info := &ClaimInfo{
    70  		ClaimInfoState: claimInfoState,
    71  		annotations:    make(map[string][]kubecontainer.Annotation),
    72  		prepared:       false,
    73  	}
    74  	return info
    75  }
    76  
    77  // newClaimInfoFromClaim creates a new claim info from a checkpointed claim info state object.
    78  func newClaimInfoFromState(state *state.ClaimInfoState) *ClaimInfo {
    79  	info := &ClaimInfo{
    80  		ClaimInfoState: *state.DeepCopy(),
    81  		annotations:    make(map[string][]kubecontainer.Annotation),
    82  		prepared:       false,
    83  	}
    84  	for pluginName, devices := range info.CDIDevices {
    85  		annotations, _ := cdi.GenerateAnnotations(info.ClaimUID, info.DriverName, devices)
    86  		info.annotations[pluginName] = append(info.annotations[pluginName], annotations...)
    87  	}
    88  	return info
    89  }
    90  
    91  // setCDIDevices adds a set of CDI devices to the claim info.
    92  func (info *ClaimInfo) setCDIDevices(pluginName string, cdiDevices []string) error {
    93  	// NOTE: Passing CDI device names as annotations is a temporary solution
    94  	// It will be removed after all runtimes are updated
    95  	// to get CDI device names from the ContainerConfig.CDIDevices field
    96  	annotations, err := cdi.GenerateAnnotations(info.ClaimUID, info.DriverName, cdiDevices)
    97  	if err != nil {
    98  		return fmt.Errorf("failed to generate container annotations, err: %+v", err)
    99  	}
   100  
   101  	if info.CDIDevices == nil {
   102  		info.CDIDevices = make(map[string][]string)
   103  	}
   104  
   105  	if info.annotations == nil {
   106  		info.annotations = make(map[string][]kubecontainer.Annotation)
   107  	}
   108  
   109  	info.CDIDevices[pluginName] = cdiDevices
   110  	info.annotations[pluginName] = annotations
   111  
   112  	return nil
   113  }
   114  
   115  // annotationsAsList returns container annotations as a single list.
   116  func (info *ClaimInfo) annotationsAsList() []kubecontainer.Annotation {
   117  	var lst []kubecontainer.Annotation
   118  	for _, v := range info.annotations {
   119  		lst = append(lst, v...)
   120  	}
   121  	return lst
   122  }
   123  
   124  // cdiDevicesAsList returns a list of CDIDevices from the provided claim info.
   125  func (info *ClaimInfo) cdiDevicesAsList() []kubecontainer.CDIDevice {
   126  	var cdiDevices []kubecontainer.CDIDevice
   127  	for _, devices := range info.CDIDevices {
   128  		for _, device := range devices {
   129  			cdiDevices = append(cdiDevices, kubecontainer.CDIDevice{Name: device})
   130  		}
   131  	}
   132  	return cdiDevices
   133  }
   134  
   135  // addPodReference adds a pod reference to the claim info.
   136  func (info *ClaimInfo) addPodReference(podUID types.UID) {
   137  	info.PodUIDs.Insert(string(podUID))
   138  }
   139  
   140  // hasPodReference checks if a pod reference exists in the claim info.
   141  func (info *ClaimInfo) hasPodReference(podUID types.UID) bool {
   142  	return info.PodUIDs.Has(string(podUID))
   143  }
   144  
   145  // deletePodReference deletes a pod reference from the claim info.
   146  func (info *ClaimInfo) deletePodReference(podUID types.UID) {
   147  	info.PodUIDs.Delete(string(podUID))
   148  }
   149  
   150  // setPrepared marks the claim info as prepared.
   151  func (info *ClaimInfo) setPrepared() {
   152  	info.prepared = true
   153  }
   154  
   155  // isPrepared checks if claim info is prepared or not.
   156  func (info *ClaimInfo) isPrepared() bool {
   157  	return info.prepared
   158  }
   159  
   160  // newClaimInfoCache creates a new claim info cache object, pre-populated from a checkpoint (if present).
   161  func newClaimInfoCache(stateDir, checkpointName string) (*claimInfoCache, error) {
   162  	stateImpl, err := state.NewCheckpointState(stateDir, checkpointName)
   163  	if err != nil {
   164  		return nil, fmt.Errorf("could not initialize checkpoint manager, please drain node and remove dra state file, err: %+v", err)
   165  	}
   166  
   167  	curState, err := stateImpl.GetOrCreate()
   168  	if err != nil {
   169  		return nil, fmt.Errorf("error calling GetOrCreate() on checkpoint state: %v", err)
   170  	}
   171  
   172  	cache := &claimInfoCache{
   173  		state:     stateImpl,
   174  		claimInfo: make(map[string]*ClaimInfo),
   175  	}
   176  
   177  	for _, entry := range curState {
   178  		info := newClaimInfoFromState(&entry)
   179  		cache.claimInfo[info.Namespace+"/"+info.ClaimName] = info
   180  	}
   181  
   182  	return cache, nil
   183  }
   184  
   185  // withLock runs a function while holding the claimInfoCache lock.
   186  func (cache *claimInfoCache) withLock(f func() error) error {
   187  	cache.Lock()
   188  	defer cache.Unlock()
   189  	return f()
   190  }
   191  
   192  // withRLock runs a function while holding the claimInfoCache rlock.
   193  func (cache *claimInfoCache) withRLock(f func() error) error {
   194  	cache.RLock()
   195  	defer cache.RUnlock()
   196  	return f()
   197  }
   198  
   199  // add adds a new claim info object into the claim info cache.
   200  func (cache *claimInfoCache) add(info *ClaimInfo) *ClaimInfo {
   201  	cache.claimInfo[info.Namespace+"/"+info.ClaimName] = info
   202  	return info
   203  }
   204  
   205  // contains checks to see if a specific claim info object is already in the cache.
   206  func (cache *claimInfoCache) contains(claimName, namespace string) bool {
   207  	_, exists := cache.claimInfo[namespace+"/"+claimName]
   208  	return exists
   209  }
   210  
   211  // get gets a specific claim info object from the cache.
   212  func (cache *claimInfoCache) get(claimName, namespace string) (*ClaimInfo, bool) {
   213  	info, exists := cache.claimInfo[namespace+"/"+claimName]
   214  	return info, exists
   215  }
   216  
   217  // delete deletes a specific claim info object from the cache.
   218  func (cache *claimInfoCache) delete(claimName, namespace string) {
   219  	delete(cache.claimInfo, namespace+"/"+claimName)
   220  }
   221  
   222  // hasPodReference checks if there is at least one claim
   223  // that is referenced by the pod with the given UID
   224  // This function is used indirectly by the status manager
   225  // to check if pod can enter termination status
   226  func (cache *claimInfoCache) hasPodReference(UID types.UID) bool {
   227  	for _, claimInfo := range cache.claimInfo {
   228  		if claimInfo.hasPodReference(UID) {
   229  			return true
   230  		}
   231  	}
   232  	return false
   233  }
   234  
   235  // syncToCheckpoint syncs the full claim info cache state to a checkpoint.
   236  func (cache *claimInfoCache) syncToCheckpoint() error {
   237  	claimInfoStateList := make(state.ClaimInfoStateList, 0, len(cache.claimInfo))
   238  	for _, infoClaim := range cache.claimInfo {
   239  		claimInfoStateList = append(claimInfoStateList, infoClaim.ClaimInfoState)
   240  	}
   241  	return cache.state.Store(claimInfoStateList)
   242  }