k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/volume/attachdetach/cache/desired_state_of_world.go (about)

     1  /*
     2  Copyright 2016 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  /*
    18  Package cache implements data structures used by the attach/detach controller
    19  to keep track of volumes, the nodes they are attached to, and the pods that
    20  reference them.
    21  */
    22  package cache
    23  
    24  import (
    25  	"fmt"
    26  	"sync"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	k8stypes "k8s.io/apimachinery/pkg/types"
    30  	"k8s.io/kubernetes/pkg/volume"
    31  	"k8s.io/kubernetes/pkg/volume/util"
    32  	"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
    33  	"k8s.io/kubernetes/pkg/volume/util/types"
    34  )
    35  
    36  // DesiredStateOfWorld defines a set of thread-safe operations supported on
    37  // the attach/detach controller's desired state of the world cache.
    38  // This cache contains nodes->volumes->pods where nodes are all the nodes
    39  // managed by the attach/detach controller, volumes are all the volumes that
    40  // should be attached to the specified node, and pods are the pods that
    41  // reference the volume and are scheduled to that node.
    42  // Note: This is distinct from the DesiredStateOfWorld implemented by the
    43  // kubelet volume manager. They both keep track of different objects. This
    44  // contains attach/detach controller specific state.
    45  type DesiredStateOfWorld interface {
    46  	// AddNode adds the given node to the list of nodes managed by the attach/
    47  	// detach controller.
    48  	// If the node already exists this is a no-op.
    49  	AddNode(nodeName k8stypes.NodeName)
    50  
    51  	// AddPod adds the given pod to the list of pods that reference the
    52  	// specified volume and is scheduled to the specified node.
    53  	// A unique volumeName is generated from the volumeSpec and returned on
    54  	// success.
    55  	// If the pod already exists under the specified volume, this is a no-op.
    56  	// If volumeSpec is not an attachable volume plugin, an error is returned.
    57  	// If no volume with the name volumeName exists in the list of volumes that
    58  	// should be attached to the specified node, the volume is implicitly added.
    59  	// If no node with the name nodeName exists in list of nodes managed by the
    60  	// attach/detach attached controller, an error is returned.
    61  	AddPod(podName types.UniquePodName, pod *v1.Pod, volumeSpec *volume.Spec, nodeName k8stypes.NodeName) (v1.UniqueVolumeName, error)
    62  
    63  	// DeleteNode removes the given node from the list of nodes managed by the
    64  	// attach/detach controller.
    65  	// If the node does not exist this is a no-op.
    66  	// If the node exists but has 1 or more child volumes, an error is returned.
    67  	DeleteNode(nodeName k8stypes.NodeName) error
    68  
    69  	// DeletePod removes the given pod from the list of pods that reference the
    70  	// specified volume and are scheduled to the specified node.
    71  	// If no pod exists in the list of pods that reference the specified volume
    72  	// and are scheduled to the specified node, this is a no-op.
    73  	// If a node with the name nodeName does not exist in the list of nodes
    74  	// managed by the attach/detach attached controller, this is a no-op.
    75  	// If no volume with the name volumeName exists in the list of managed
    76  	// volumes under the specified node, this is a no-op.
    77  	// If after deleting the pod, the specified volume contains no other child
    78  	// pods, the volume is also deleted.
    79  	DeletePod(podName types.UniquePodName, volumeName v1.UniqueVolumeName, nodeName k8stypes.NodeName)
    80  
    81  	// NodeExists returns true if the node with the specified name exists in
    82  	// the list of nodes managed by the attach/detach controller.
    83  	NodeExists(nodeName k8stypes.NodeName) bool
    84  
    85  	// VolumeExists returns true if the volume with the specified name exists
    86  	// in the list of volumes that should be attached to the specified node by
    87  	// the attach detach controller.
    88  	VolumeExists(volumeName v1.UniqueVolumeName, nodeName k8stypes.NodeName) bool
    89  
    90  	// GetVolumesToAttach generates and returns a list of volumes to attach
    91  	// and the nodes they should be attached to based on the current desired
    92  	// state of the world.
    93  	GetVolumesToAttach() []VolumeToAttach
    94  
    95  	// GetPodToAdd generates and returns a map of pods based on the current desired
    96  	// state of world
    97  	GetPodToAdd() map[types.UniquePodName]PodToAdd
    98  
    99  	// Mark multi-attach error as reported to prevent spamming multiple
   100  	// events for same error
   101  	SetMultiAttachError(v1.UniqueVolumeName, k8stypes.NodeName)
   102  
   103  	// GetPodsOnNodes returns list of pods ("namespace/name") that require
   104  	// given volume on given nodes.
   105  	GetVolumePodsOnNodes(nodes []k8stypes.NodeName, volumeName v1.UniqueVolumeName) []*v1.Pod
   106  }
   107  
   108  // VolumeToAttach represents a volume that should be attached to a node.
   109  type VolumeToAttach struct {
   110  	operationexecutor.VolumeToAttach
   111  }
   112  
   113  // PodToAdd represents a pod that references the underlying volume and is
   114  // scheduled to the underlying node.
   115  type PodToAdd struct {
   116  	// pod contains the api object of pod
   117  	Pod *v1.Pod
   118  
   119  	// volumeName contains the unique identifier for this volume.
   120  	VolumeName v1.UniqueVolumeName
   121  
   122  	// nodeName contains the name of this node.
   123  	NodeName k8stypes.NodeName
   124  }
   125  
   126  // NewDesiredStateOfWorld returns a new instance of DesiredStateOfWorld.
   127  func NewDesiredStateOfWorld(volumePluginMgr *volume.VolumePluginMgr) DesiredStateOfWorld {
   128  	return &desiredStateOfWorld{
   129  		nodesManaged:    make(map[k8stypes.NodeName]nodeManaged),
   130  		volumePluginMgr: volumePluginMgr,
   131  	}
   132  }
   133  
   134  type desiredStateOfWorld struct {
   135  	// nodesManaged is a map containing the set of nodes managed by the attach/
   136  	// detach controller. The key in this map is the name of the node and the
   137  	// value is a node object containing more information about the node.
   138  	nodesManaged map[k8stypes.NodeName]nodeManaged
   139  	// volumePluginMgr is the volume plugin manager used to create volume
   140  	// plugin objects.
   141  	volumePluginMgr *volume.VolumePluginMgr
   142  	sync.RWMutex
   143  }
   144  
   145  // nodeManaged represents a node that is being managed by the attach/detach
   146  // controller.
   147  type nodeManaged struct {
   148  	// nodeName contains the name of this node.
   149  	nodeName k8stypes.NodeName
   150  
   151  	// volumesToAttach is a map containing the set of volumes that should be
   152  	// attached to this node. The key in the map is the name of the volume and
   153  	// the value is a volumeToAttach object containing more information about the volume.
   154  	volumesToAttach map[v1.UniqueVolumeName]volumeToAttach
   155  }
   156  
   157  // The volumeToAttach object represents a volume that should be attached to a node.
   158  type volumeToAttach struct {
   159  	// multiAttachErrorReported indicates whether the multi-attach error has been reported for the given volume.
   160  	// It is used to prevent reporting the error from being reported more than once for a given volume.
   161  	multiAttachErrorReported bool
   162  
   163  	// volumeName contains the unique identifier for this volume.
   164  	volumeName v1.UniqueVolumeName
   165  
   166  	// spec is the volume spec containing the specification for this volume.
   167  	// Used to generate the volume plugin object, and passed to attach/detach
   168  	// methods.
   169  	spec *volume.Spec
   170  
   171  	// scheduledPods is a map containing the set of pods that reference this
   172  	// volume and are scheduled to the underlying node. The key in the map is
   173  	// the name of the pod and the value is a pod object containing more
   174  	// information about the pod.
   175  	scheduledPods map[types.UniquePodName]pod
   176  }
   177  
   178  // The pod represents a pod that references the underlying volume and is
   179  // scheduled to the underlying node.
   180  type pod struct {
   181  	// podName contains the unique identifier for this pod
   182  	podName types.UniquePodName
   183  
   184  	// pod object contains the api object of pod
   185  	podObj *v1.Pod
   186  }
   187  
   188  func (dsw *desiredStateOfWorld) AddNode(nodeName k8stypes.NodeName) {
   189  	dsw.Lock()
   190  	defer dsw.Unlock()
   191  
   192  	if _, nodeExists := dsw.nodesManaged[nodeName]; !nodeExists {
   193  		dsw.nodesManaged[nodeName] = nodeManaged{
   194  			nodeName:        nodeName,
   195  			volumesToAttach: make(map[v1.UniqueVolumeName]volumeToAttach),
   196  		}
   197  	}
   198  }
   199  
   200  func (dsw *desiredStateOfWorld) AddPod(
   201  	podName types.UniquePodName,
   202  	podToAdd *v1.Pod,
   203  	volumeSpec *volume.Spec,
   204  	nodeName k8stypes.NodeName) (v1.UniqueVolumeName, error) {
   205  	dsw.Lock()
   206  	defer dsw.Unlock()
   207  
   208  	nodeObj, nodeExists := dsw.nodesManaged[nodeName]
   209  	if !nodeExists {
   210  		return "", fmt.Errorf(
   211  			"no node with the name %q exists in the list of managed nodes",
   212  			nodeName)
   213  	}
   214  
   215  	attachableVolumePlugin, err := dsw.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec)
   216  	if err != nil || attachableVolumePlugin == nil {
   217  		if attachableVolumePlugin == nil {
   218  			err = fmt.Errorf("plugin do not support attachment")
   219  		}
   220  		return "", fmt.Errorf(
   221  			"failed to get AttachablePlugin from volumeSpec for volume %q err=%v",
   222  			volumeSpec.Name(),
   223  			err)
   224  	}
   225  
   226  	volumeName, err := util.GetUniqueVolumeNameFromSpec(
   227  		attachableVolumePlugin, volumeSpec)
   228  	if err != nil {
   229  		return "", fmt.Errorf(
   230  			"failed to get UniqueVolumeName from volumeSpec for plugin=%q and volume=%q err=%v",
   231  			attachableVolumePlugin.GetPluginName(),
   232  			volumeSpec.Name(),
   233  			err)
   234  	}
   235  
   236  	volumeObj, volumeExists := nodeObj.volumesToAttach[volumeName]
   237  	if !volumeExists {
   238  		volumeObj = volumeToAttach{
   239  			multiAttachErrorReported: false,
   240  			volumeName:               volumeName,
   241  			spec:                     volumeSpec,
   242  			scheduledPods:            make(map[types.UniquePodName]pod),
   243  		}
   244  		dsw.nodesManaged[nodeName].volumesToAttach[volumeName] = volumeObj
   245  	}
   246  	if _, podExists := volumeObj.scheduledPods[podName]; !podExists {
   247  		dsw.nodesManaged[nodeName].volumesToAttach[volumeName].scheduledPods[podName] =
   248  			pod{
   249  				podName: podName,
   250  				podObj:  podToAdd,
   251  			}
   252  	}
   253  
   254  	return volumeName, nil
   255  }
   256  
   257  func (dsw *desiredStateOfWorld) DeleteNode(nodeName k8stypes.NodeName) error {
   258  	dsw.Lock()
   259  	defer dsw.Unlock()
   260  
   261  	nodeObj, nodeExists := dsw.nodesManaged[nodeName]
   262  	if !nodeExists {
   263  		return nil
   264  	}
   265  
   266  	if len(nodeObj.volumesToAttach) > 0 {
   267  		return fmt.Errorf(
   268  			"failed to delete node %q from list of nodes managed by attach/detach controller--the node still contains %v volumes in its list of volumes to attach",
   269  			nodeName,
   270  			len(nodeObj.volumesToAttach))
   271  	}
   272  
   273  	delete(
   274  		dsw.nodesManaged,
   275  		nodeName)
   276  	return nil
   277  }
   278  
   279  func (dsw *desiredStateOfWorld) DeletePod(
   280  	podName types.UniquePodName,
   281  	volumeName v1.UniqueVolumeName,
   282  	nodeName k8stypes.NodeName) {
   283  	dsw.Lock()
   284  	defer dsw.Unlock()
   285  
   286  	nodeObj, nodeExists := dsw.nodesManaged[nodeName]
   287  	if !nodeExists {
   288  		return
   289  	}
   290  
   291  	volumeObj, volumeExists := nodeObj.volumesToAttach[volumeName]
   292  	if !volumeExists {
   293  		return
   294  	}
   295  	if _, podExists := volumeObj.scheduledPods[podName]; !podExists {
   296  		return
   297  	}
   298  
   299  	delete(
   300  		dsw.nodesManaged[nodeName].volumesToAttach[volumeName].scheduledPods,
   301  		podName)
   302  
   303  	if len(volumeObj.scheduledPods) == 0 {
   304  		delete(
   305  			dsw.nodesManaged[nodeName].volumesToAttach,
   306  			volumeName)
   307  	}
   308  }
   309  
   310  func (dsw *desiredStateOfWorld) NodeExists(nodeName k8stypes.NodeName) bool {
   311  	dsw.RLock()
   312  	defer dsw.RUnlock()
   313  
   314  	_, nodeExists := dsw.nodesManaged[nodeName]
   315  	return nodeExists
   316  }
   317  
   318  func (dsw *desiredStateOfWorld) VolumeExists(
   319  	volumeName v1.UniqueVolumeName, nodeName k8stypes.NodeName) bool {
   320  	dsw.RLock()
   321  	defer dsw.RUnlock()
   322  
   323  	nodeObj, nodeExists := dsw.nodesManaged[nodeName]
   324  	if nodeExists {
   325  		if _, volumeExists := nodeObj.volumesToAttach[volumeName]; volumeExists {
   326  			return true
   327  		}
   328  	}
   329  
   330  	return false
   331  }
   332  
   333  func (dsw *desiredStateOfWorld) SetMultiAttachError(
   334  	volumeName v1.UniqueVolumeName,
   335  	nodeName k8stypes.NodeName) {
   336  	dsw.Lock()
   337  	defer dsw.Unlock()
   338  
   339  	nodeObj, nodeExists := dsw.nodesManaged[nodeName]
   340  	if nodeExists {
   341  		if volumeObj, volumeExists := nodeObj.volumesToAttach[volumeName]; volumeExists {
   342  			volumeObj.multiAttachErrorReported = true
   343  			dsw.nodesManaged[nodeName].volumesToAttach[volumeName] = volumeObj
   344  		}
   345  	}
   346  }
   347  
   348  func (dsw *desiredStateOfWorld) GetVolumesToAttach() []VolumeToAttach {
   349  	dsw.RLock()
   350  	defer dsw.RUnlock()
   351  
   352  	volumesToAttach := make([]VolumeToAttach, 0 /* len */, len(dsw.nodesManaged) /* cap */)
   353  	for nodeName, nodeObj := range dsw.nodesManaged {
   354  		for volumeName, volumeObj := range nodeObj.volumesToAttach {
   355  			volumesToAttach = append(volumesToAttach,
   356  				VolumeToAttach{
   357  					VolumeToAttach: operationexecutor.VolumeToAttach{
   358  						MultiAttachErrorReported: volumeObj.multiAttachErrorReported,
   359  						VolumeName:               volumeName,
   360  						VolumeSpec:               volumeObj.spec,
   361  						NodeName:                 nodeName,
   362  						ScheduledPods:            getPodsFromMap(volumeObj.scheduledPods),
   363  					}})
   364  		}
   365  	}
   366  
   367  	return volumesToAttach
   368  }
   369  
   370  // Construct a list of v1.Pod objects from the given pod map
   371  func getPodsFromMap(podMap map[types.UniquePodName]pod) []*v1.Pod {
   372  	pods := make([]*v1.Pod, 0, len(podMap))
   373  	for _, pod := range podMap {
   374  		pods = append(pods, pod.podObj)
   375  	}
   376  	return pods
   377  }
   378  
   379  func (dsw *desiredStateOfWorld) GetPodToAdd() map[types.UniquePodName]PodToAdd {
   380  	dsw.RLock()
   381  	defer dsw.RUnlock()
   382  
   383  	pods := make(map[types.UniquePodName]PodToAdd)
   384  	for nodeName, nodeObj := range dsw.nodesManaged {
   385  		for volumeName, volumeObj := range nodeObj.volumesToAttach {
   386  			for podUID, pod := range volumeObj.scheduledPods {
   387  				pods[podUID] = PodToAdd{
   388  					Pod:        pod.podObj,
   389  					VolumeName: volumeName,
   390  					NodeName:   nodeName,
   391  				}
   392  			}
   393  		}
   394  	}
   395  	return pods
   396  }
   397  
   398  func (dsw *desiredStateOfWorld) GetVolumePodsOnNodes(nodes []k8stypes.NodeName, volumeName v1.UniqueVolumeName) []*v1.Pod {
   399  	dsw.RLock()
   400  	defer dsw.RUnlock()
   401  
   402  	pods := []*v1.Pod{}
   403  	for _, nodeName := range nodes {
   404  		node, ok := dsw.nodesManaged[nodeName]
   405  		if !ok {
   406  			continue
   407  		}
   408  		volume, ok := node.volumesToAttach[volumeName]
   409  		if !ok {
   410  			continue
   411  		}
   412  		for _, pod := range volume.scheduledPods {
   413  			pods = append(pods, pod.podObj)
   414  		}
   415  	}
   416  	return pods
   417  }