github.com/jingruilea/kubeedge@v1.2.0-beta.0.0.20200410162146-4bb8902b3879/edge/pkg/edged/volume/csi/nodeinfomanager/nodeinfomanager.go (about)

     1  /*
     2  Copyright 2018 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  @CHANGELOG
    17  KubeEdge Authors: To create mini-kubelet for edge deployment scenario,
    18  this file is derived from kubernetes v1.15.3,
    19  and the full file path is k8s.io/kubernetes/pkg/volume/csi/nodeinfomanager/nodeinfomanager.go
    20  and make some modifications including:
    21  1. remove some Unnecessary in nodeUpdateFunc.
    22  2. replace PatchNodeStatus with self-defined Update.
    23  */
    24  
    25  // Package nodeinfomanager includes internal functions used to add/delete labels to
    26  // kubernetes nodes for corresponding CSI drivers
    27  package nodeinfomanager
    28  
    29  import (
    30  	"encoding/json"
    31  	goerrors "errors"
    32  	"fmt"
    33  	"strings"
    34  	"time"
    35  
    36  	v1 "k8s.io/api/core/v1"
    37  	storagev1beta1 "k8s.io/api/storage/v1beta1"
    38  	"k8s.io/apimachinery/pkg/api/errors"
    39  	"k8s.io/apimachinery/pkg/api/resource"
    40  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    41  	"k8s.io/apimachinery/pkg/types"
    42  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    43  	"k8s.io/apimachinery/pkg/util/sets"
    44  	"k8s.io/apimachinery/pkg/util/wait"
    45  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    46  	clientset "k8s.io/client-go/kubernetes"
    47  	"k8s.io/klog"
    48  	"k8s.io/kubernetes/pkg/features"
    49  	"k8s.io/kubernetes/pkg/volume"
    50  	"k8s.io/kubernetes/pkg/volume/util"
    51  )
    52  
    53  const (
    54  	// Name of node annotation that contains JSON map of driver names to node
    55  	annotationKeyNodeID = "csi.volume.kubernetes.io/nodeid"
    56  )
    57  
    58  var (
    59  	nodeKind      = v1.SchemeGroupVersion.WithKind("Node")
    60  	updateBackoff = wait.Backoff{
    61  		Steps:    4,
    62  		Duration: 10 * time.Millisecond,
    63  		Factor:   5.0,
    64  		Jitter:   0.1,
    65  	}
    66  )
    67  
    68  // nodeInfoManager contains necessary common dependencies to update node info on both
    69  // the Node and CSINode objects.
    70  type nodeInfoManager struct {
    71  	nodeName        types.NodeName
    72  	volumeHost      volume.VolumeHost
    73  	migratedPlugins map[string](func() bool)
    74  }
    75  
    76  // If no updates is needed, the function must return the same Node object as the input.
    77  type nodeUpdateFunc func(*v1.Node) (newNode *v1.Node, updated bool, err error)
    78  
    79  // Interface implements an interface for managing labels of a node
    80  type Interface interface {
    81  	CreateCSINode() (*storagev1beta1.CSINode, error)
    82  
    83  	// Updates or Creates the CSINode object with annotations for CSI Migration
    84  	InitializeCSINodeWithAnnotation() error
    85  
    86  	// Record in the cluster the given node information from the CSI driver with the given name.
    87  	// Concurrent calls to InstallCSIDriver() is allowed, but they should not be intertwined with calls
    88  	// to other methods in this interface.
    89  	InstallCSIDriver(driverName string, driverNodeID string, maxVolumeLimit int64, topology map[string]string) error
    90  
    91  	// Remove in the cluster node information from the CSI driver with the given name.
    92  	// Concurrent calls to UninstallCSIDriver() is allowed, but they should not be intertwined with calls
    93  	// to other methods in this interface.
    94  	UninstallCSIDriver(driverName string) error
    95  }
    96  
    97  // NewNodeInfoManager initializes nodeInfoManager
    98  func NewNodeInfoManager(
    99  	nodeName types.NodeName,
   100  	volumeHost volume.VolumeHost,
   101  	migratedPlugins map[string](func() bool)) Interface {
   102  	return &nodeInfoManager{
   103  		nodeName:        nodeName,
   104  		volumeHost:      volumeHost,
   105  		migratedPlugins: migratedPlugins,
   106  	}
   107  }
   108  
   109  // InstallCSIDriver updates the node ID annotation in the Node object and CSIDrivers field in the
   110  // CSINode object. If the CSINode object doesn't yet exist, it will be created.
   111  // If multiple calls to InstallCSIDriver() are made in parallel, some calls might receive Node or
   112  // CSINode update conflicts, which causes the function to retry the corresponding update.
   113  func (nim *nodeInfoManager) InstallCSIDriver(driverName string, driverNodeID string, maxAttachLimit int64, topology map[string]string) error {
   114  	if driverNodeID == "" {
   115  		return fmt.Errorf("error adding CSI driver node info: driverNodeID must not be empty")
   116  	}
   117  
   118  	nodeUpdateFuncs := []nodeUpdateFunc{
   119  		updateNodeIDInNode(driverName, driverNodeID),
   120  	}
   121  
   122  	err := nim.updateNode(nodeUpdateFuncs...)
   123  	if err != nil {
   124  		return fmt.Errorf("error updating Node object with CSI driver node info: %v", err)
   125  	}
   126  	return nil
   127  }
   128  
   129  // UninstallCSIDriver removes the node ID annotation from the Node object and CSIDrivers field from the
   130  // CSINode object. If the CSINOdeInfo object contains no CSIDrivers, it will be deleted.
   131  // If multiple calls to UninstallCSIDriver() are made in parallel, some calls might receive Node or
   132  // CSINode update conflicts, which causes the function to retry the corresponding update.
   133  func (nim *nodeInfoManager) UninstallCSIDriver(driverName string) error {
   134  	if utilfeature.DefaultFeatureGate.Enabled(features.CSINodeInfo) {
   135  		err := nim.uninstallDriverFromCSINode(driverName)
   136  		if err != nil {
   137  			return fmt.Errorf("error uninstalling CSI driver from CSINode object %v", err)
   138  		}
   139  	}
   140  
   141  	err := nim.updateNode(
   142  		removeMaxAttachLimit(driverName),
   143  		removeNodeIDFromNode(driverName),
   144  	)
   145  	if err != nil {
   146  		return fmt.Errorf("error removing CSI driver node info from Node object %v", err)
   147  	}
   148  	return nil
   149  }
   150  
   151  func (nim *nodeInfoManager) updateNode(updateFuncs ...nodeUpdateFunc) error {
   152  	var updateErrs []error
   153  	err := wait.ExponentialBackoff(updateBackoff, func() (bool, error) {
   154  		if err := nim.tryUpdateNode(updateFuncs...); err != nil {
   155  			updateErrs = append(updateErrs, err)
   156  			return false, nil
   157  		}
   158  		return true, nil
   159  	})
   160  	if err != nil {
   161  		return fmt.Errorf("error updating node: %v; caused by: %v", err, utilerrors.NewAggregate(updateErrs))
   162  	}
   163  	return nil
   164  }
   165  
   166  // updateNode repeatedly attempts to update the corresponding node object
   167  // which is modified by applying the given update functions sequentially.
   168  // Because updateFuncs are applied sequentially, later updateFuncs should take into account
   169  // the effects of previous updateFuncs to avoid potential conflicts. For example, if multiple
   170  // functions update the same field, updates in the last function are persisted.
   171  func (nim *nodeInfoManager) tryUpdateNode(updateFuncs ...nodeUpdateFunc) error {
   172  	// Retrieve the latest version of Node before attempting update, so that
   173  	// existing changes are not overwritten.
   174  
   175  	kubeClient := nim.volumeHost.GetKubeClient()
   176  	if kubeClient == nil {
   177  		return fmt.Errorf("error getting kube client")
   178  	}
   179  
   180  	nodeClient := kubeClient.CoreV1().Nodes()
   181  	originalNode, err := nodeClient.Get(string(nim.nodeName), metav1.GetOptions{})
   182  	if err != nil {
   183  		return err
   184  	}
   185  	node := originalNode.DeepCopy()
   186  
   187  	needUpdate := false
   188  	for _, update := range updateFuncs {
   189  		newNode, updated, err := update(node)
   190  		if err != nil {
   191  			return err
   192  		}
   193  		node = newNode
   194  		needUpdate = needUpdate || updated
   195  	}
   196  
   197  	if needUpdate {
   198  		// PatchNodeStatus can update both node's status and labels or annotations
   199  		// Updating status by directly updating node does not work
   200  		_, updateErr := nodeClient.Update(node)
   201  		return updateErr
   202  	}
   203  
   204  	return nil
   205  }
   206  
   207  // Guarantees the map is non-nil if no error is returned.
   208  func buildNodeIDMapFromAnnotation(node *v1.Node) (map[string]string, error) {
   209  	var previousAnnotationValue string
   210  	if node.ObjectMeta.Annotations != nil {
   211  		previousAnnotationValue =
   212  			node.ObjectMeta.Annotations[annotationKeyNodeID]
   213  	}
   214  
   215  	var existingDriverMap map[string]string
   216  	if previousAnnotationValue != "" {
   217  		// Parse previousAnnotationValue as JSON
   218  		if err := json.Unmarshal([]byte(previousAnnotationValue), &existingDriverMap); err != nil {
   219  			return nil, fmt.Errorf(
   220  				"failed to parse node's %q annotation value (%q) err=%v",
   221  				annotationKeyNodeID,
   222  				previousAnnotationValue,
   223  				err)
   224  		}
   225  	}
   226  
   227  	if existingDriverMap == nil {
   228  		return make(map[string]string), nil
   229  	}
   230  	return existingDriverMap, nil
   231  }
   232  
   233  // updateNodeIDInNode returns a function that updates a Node object with the given
   234  // Node ID information.
   235  func updateNodeIDInNode(
   236  	csiDriverName string,
   237  	csiDriverNodeID string) nodeUpdateFunc {
   238  	return func(node *v1.Node) (*v1.Node, bool, error) {
   239  		existingDriverMap, err := buildNodeIDMapFromAnnotation(node)
   240  		if err != nil {
   241  			return nil, false, err
   242  		}
   243  
   244  		if val, ok := existingDriverMap[csiDriverName]; ok {
   245  			if val == csiDriverNodeID {
   246  				// Value already exists in node annotation, nothing more to do
   247  				return node, false, nil
   248  			}
   249  		}
   250  
   251  		// Add/update annotation value
   252  		existingDriverMap[csiDriverName] = csiDriverNodeID
   253  		jsonObj, err := json.Marshal(existingDriverMap)
   254  		if err != nil {
   255  			return nil, false, fmt.Errorf(
   256  				"error while marshalling node ID map updated with driverName=%q, nodeID=%q: %v",
   257  				csiDriverName,
   258  				csiDriverNodeID,
   259  				err)
   260  		}
   261  
   262  		if node.ObjectMeta.Annotations == nil {
   263  			node.ObjectMeta.Annotations = make(map[string]string)
   264  		}
   265  		node.ObjectMeta.Annotations[annotationKeyNodeID] = string(jsonObj)
   266  
   267  		node.ObjectMeta.Annotations["volumes.kubernetes.io/controller-managed-attach-detach"] = "true"
   268  
   269  		return node, true, nil
   270  	}
   271  }
   272  
   273  // removeNodeIDFromNode returns a function that removes node ID information matching the given
   274  // driver name from a Node object.
   275  func removeNodeIDFromNode(csiDriverName string) nodeUpdateFunc {
   276  	return func(node *v1.Node) (*v1.Node, bool, error) {
   277  		var previousAnnotationValue string
   278  		if node.ObjectMeta.Annotations != nil {
   279  			previousAnnotationValue =
   280  				node.ObjectMeta.Annotations[annotationKeyNodeID]
   281  		}
   282  
   283  		if previousAnnotationValue == "" {
   284  			return node, false, nil
   285  		}
   286  
   287  		// Parse previousAnnotationValue as JSON
   288  		existingDriverMap := map[string]string{}
   289  		if err := json.Unmarshal([]byte(previousAnnotationValue), &existingDriverMap); err != nil {
   290  			return nil, false, fmt.Errorf(
   291  				"failed to parse node's %q annotation value (%q) err=%v",
   292  				annotationKeyNodeID,
   293  				previousAnnotationValue,
   294  				err)
   295  		}
   296  
   297  		if _, ok := existingDriverMap[csiDriverName]; !ok {
   298  			// Value is already missing in node annotation, nothing more to do
   299  			return node, false, nil
   300  		}
   301  
   302  		// Delete annotation value
   303  		delete(existingDriverMap, csiDriverName)
   304  		if len(existingDriverMap) == 0 {
   305  			delete(node.ObjectMeta.Annotations, annotationKeyNodeID)
   306  		} else {
   307  			jsonObj, err := json.Marshal(existingDriverMap)
   308  			if err != nil {
   309  				return nil, false, fmt.Errorf(
   310  					"failed while trying to remove key %q from node %q annotation. Existing data: %v",
   311  					csiDriverName,
   312  					annotationKeyNodeID,
   313  					previousAnnotationValue)
   314  			}
   315  
   316  			node.ObjectMeta.Annotations[annotationKeyNodeID] = string(jsonObj)
   317  		}
   318  
   319  		return node, true, nil
   320  	}
   321  }
   322  
   323  // updateTopologyLabels returns a function that updates labels of a Node object with the given
   324  // topology information.
   325  func updateTopologyLabels(topology map[string]string) nodeUpdateFunc {
   326  	return func(node *v1.Node) (*v1.Node, bool, error) {
   327  		if topology == nil || len(topology) == 0 {
   328  			return node, false, nil
   329  		}
   330  
   331  		for k, v := range topology {
   332  			if curVal, exists := node.Labels[k]; exists && curVal != v {
   333  				return nil, false, fmt.Errorf("detected topology value collision: driver reported %q:%q but existing label is %q:%q", k, v, k, curVal)
   334  			}
   335  		}
   336  
   337  		if node.Labels == nil {
   338  			node.Labels = make(map[string]string)
   339  		}
   340  		for k, v := range topology {
   341  			node.Labels[k] = v
   342  		}
   343  		return node, true, nil
   344  	}
   345  }
   346  
   347  func (nim *nodeInfoManager) updateCSINode(
   348  	driverName string,
   349  	driverNodeID string,
   350  	topology map[string]string) error {
   351  
   352  	csiKubeClient := nim.volumeHost.GetKubeClient()
   353  	if csiKubeClient == nil {
   354  		return fmt.Errorf("error getting CSI client")
   355  	}
   356  
   357  	var updateErrs []error
   358  	err := wait.ExponentialBackoff(updateBackoff, func() (bool, error) {
   359  		if err := nim.tryUpdateCSINode(csiKubeClient, driverName, driverNodeID, topology); err != nil {
   360  			updateErrs = append(updateErrs, err)
   361  			return false, nil
   362  		}
   363  		return true, nil
   364  	})
   365  	if err != nil {
   366  		return fmt.Errorf("error updating CSINode: %v; caused by: %v", err, utilerrors.NewAggregate(updateErrs))
   367  	}
   368  	return nil
   369  }
   370  
   371  func (nim *nodeInfoManager) tryUpdateCSINode(
   372  	csiKubeClient clientset.Interface,
   373  	driverName string,
   374  	driverNodeID string,
   375  	topology map[string]string) error {
   376  
   377  	nodeInfo, err := csiKubeClient.StorageV1beta1().CSINodes().Get(string(nim.nodeName), metav1.GetOptions{})
   378  	if nodeInfo == nil || errors.IsNotFound(err) {
   379  		nodeInfo, err = nim.CreateCSINode()
   380  	}
   381  	if err != nil {
   382  		return err
   383  	}
   384  
   385  	return nim.installDriverToCSINode(nodeInfo, driverName, driverNodeID, topology)
   386  }
   387  
   388  func (nim *nodeInfoManager) InitializeCSINodeWithAnnotation() error {
   389  	csiKubeClient := nim.volumeHost.GetKubeClient()
   390  	if csiKubeClient == nil {
   391  		return goerrors.New("error getting CSI client")
   392  	}
   393  
   394  	var updateErrs []error
   395  	err := wait.ExponentialBackoff(updateBackoff, func() (bool, error) {
   396  		if err := nim.tryInitializeCSINodeWithAnnotation(csiKubeClient); err != nil {
   397  			updateErrs = append(updateErrs, err)
   398  			return false, nil
   399  		}
   400  		return true, nil
   401  	})
   402  	if err != nil {
   403  		return fmt.Errorf("error updating CSINode annotation: %v; caused by: %v", err, utilerrors.NewAggregate(updateErrs))
   404  	}
   405  
   406  	return nil
   407  }
   408  
   409  func (nim *nodeInfoManager) tryInitializeCSINodeWithAnnotation(csiKubeClient clientset.Interface) error {
   410  	nodeInfo, err := csiKubeClient.StorageV1beta1().CSINodes().Get(string(nim.nodeName), metav1.GetOptions{})
   411  	if nodeInfo == nil || errors.IsNotFound(err) {
   412  		// CreateCSINode will set the annotation
   413  		_, err = nim.CreateCSINode()
   414  		return err
   415  	} else if err != nil {
   416  		return err
   417  	}
   418  
   419  	annotationModified := setMigrationAnnotation(nim.migratedPlugins, nodeInfo)
   420  
   421  	if annotationModified {
   422  		_, err := csiKubeClient.StorageV1beta1().CSINodes().Update(nodeInfo)
   423  		return err
   424  	}
   425  	return nil
   426  
   427  }
   428  
   429  func (nim *nodeInfoManager) CreateCSINode() (*storagev1beta1.CSINode, error) {
   430  
   431  	kubeClient := nim.volumeHost.GetKubeClient()
   432  	if kubeClient == nil {
   433  		return nil, fmt.Errorf("error getting kube client")
   434  	}
   435  
   436  	csiKubeClient := nim.volumeHost.GetKubeClient()
   437  	if csiKubeClient == nil {
   438  		return nil, fmt.Errorf("error getting CSI client")
   439  	}
   440  
   441  	node, err := kubeClient.CoreV1().Nodes().Get(string(nim.nodeName), metav1.GetOptions{})
   442  	if err != nil {
   443  		return nil, err
   444  	}
   445  
   446  	nodeInfo := &storagev1beta1.CSINode{
   447  		ObjectMeta: metav1.ObjectMeta{
   448  			Name: string(nim.nodeName),
   449  			OwnerReferences: []metav1.OwnerReference{
   450  				{
   451  					APIVersion: nodeKind.Version,
   452  					Kind:       nodeKind.Kind,
   453  					Name:       node.Name,
   454  					UID:        node.UID,
   455  				},
   456  			},
   457  		},
   458  		Spec: storagev1beta1.CSINodeSpec{
   459  			Drivers: []storagev1beta1.CSINodeDriver{},
   460  		},
   461  	}
   462  
   463  	setMigrationAnnotation(nim.migratedPlugins, nodeInfo)
   464  
   465  	return csiKubeClient.StorageV1beta1().CSINodes().Create(nodeInfo)
   466  }
   467  
   468  func setMigrationAnnotation(migratedPlugins map[string](func() bool), nodeInfo *storagev1beta1.CSINode) (modified bool) {
   469  	if migratedPlugins == nil {
   470  		return false
   471  	}
   472  
   473  	nodeInfoAnnotations := nodeInfo.GetAnnotations()
   474  	if nodeInfoAnnotations == nil {
   475  		nodeInfoAnnotations = map[string]string{}
   476  	}
   477  
   478  	var oldAnnotationSet sets.String
   479  	mpa := nodeInfoAnnotations[v1.MigratedPluginsAnnotationKey]
   480  	tok := strings.Split(mpa, ",")
   481  	if len(mpa) == 0 {
   482  		oldAnnotationSet = sets.NewString()
   483  	} else {
   484  		oldAnnotationSet = sets.NewString(tok...)
   485  	}
   486  
   487  	newAnnotationSet := sets.NewString()
   488  	for pluginName, migratedFunc := range migratedPlugins {
   489  		if migratedFunc() {
   490  			newAnnotationSet.Insert(pluginName)
   491  		}
   492  	}
   493  
   494  	if oldAnnotationSet.Equal(newAnnotationSet) {
   495  		return false
   496  	}
   497  
   498  	nas := strings.Join(newAnnotationSet.List(), ",")
   499  	if len(nas) != 0 {
   500  		nodeInfoAnnotations[v1.MigratedPluginsAnnotationKey] = nas
   501  	} else {
   502  		delete(nodeInfoAnnotations, v1.MigratedPluginsAnnotationKey)
   503  	}
   504  
   505  	nodeInfo.Annotations = nodeInfoAnnotations
   506  	return true
   507  }
   508  
   509  func (nim *nodeInfoManager) installDriverToCSINode(
   510  	nodeInfo *storagev1beta1.CSINode,
   511  	driverName string,
   512  	driverNodeID string,
   513  	topology map[string]string) error {
   514  
   515  	csiKubeClient := nim.volumeHost.GetKubeClient()
   516  	if csiKubeClient == nil {
   517  		return fmt.Errorf("error getting CSI client")
   518  	}
   519  
   520  	topologyKeys := make(sets.String)
   521  	for k := range topology {
   522  		topologyKeys.Insert(k)
   523  	}
   524  
   525  	specModified := true
   526  	// Clone driver list, omitting the driver that matches the given driverName
   527  	newDriverSpecs := []storagev1beta1.CSINodeDriver{}
   528  	for _, driverInfoSpec := range nodeInfo.Spec.Drivers {
   529  		if driverInfoSpec.Name == driverName {
   530  			if driverInfoSpec.NodeID == driverNodeID &&
   531  				sets.NewString(driverInfoSpec.TopologyKeys...).Equal(topologyKeys) {
   532  				specModified = false
   533  			}
   534  		} else {
   535  			// Omit driverInfoSpec matching given driverName
   536  			newDriverSpecs = append(newDriverSpecs, driverInfoSpec)
   537  		}
   538  	}
   539  
   540  	annotationModified := setMigrationAnnotation(nim.migratedPlugins, nodeInfo)
   541  
   542  	if !specModified && !annotationModified {
   543  		return nil
   544  	}
   545  
   546  	// Append new driver
   547  	driverSpec := storagev1beta1.CSINodeDriver{
   548  		Name:         driverName,
   549  		NodeID:       driverNodeID,
   550  		TopologyKeys: topologyKeys.List(),
   551  	}
   552  
   553  	newDriverSpecs = append(newDriverSpecs, driverSpec)
   554  	nodeInfo.Spec.Drivers = newDriverSpecs
   555  
   556  	_, err := csiKubeClient.StorageV1beta1().CSINodes().Update(nodeInfo)
   557  	return err
   558  }
   559  
   560  func (nim *nodeInfoManager) uninstallDriverFromCSINode(
   561  	csiDriverName string) error {
   562  
   563  	csiKubeClient := nim.volumeHost.GetKubeClient()
   564  	if csiKubeClient == nil {
   565  		return fmt.Errorf("error getting CSI client")
   566  	}
   567  
   568  	var updateErrs []error
   569  	err := wait.ExponentialBackoff(updateBackoff, func() (bool, error) {
   570  		if err := nim.tryUninstallDriverFromCSINode(csiKubeClient, csiDriverName); err != nil {
   571  			updateErrs = append(updateErrs, err)
   572  			return false, nil
   573  		}
   574  		return true, nil
   575  	})
   576  	if err != nil {
   577  		return fmt.Errorf("error updating CSINode: %v; caused by: %v", err, utilerrors.NewAggregate(updateErrs))
   578  	}
   579  	return nil
   580  }
   581  
   582  func (nim *nodeInfoManager) tryUninstallDriverFromCSINode(
   583  	csiKubeClient clientset.Interface,
   584  	csiDriverName string) error {
   585  
   586  	nodeInfoClient := csiKubeClient.StorageV1beta1().CSINodes()
   587  	nodeInfo, err := nodeInfoClient.Get(string(nim.nodeName), metav1.GetOptions{})
   588  	if err != nil && errors.IsNotFound(err) {
   589  		return nil
   590  	} else if err != nil {
   591  		return err
   592  	}
   593  
   594  	hasModified := false
   595  	// Uninstall CSINodeDriver with name csiDriverName
   596  	drivers := nodeInfo.Spec.Drivers[:0]
   597  	for _, driver := range nodeInfo.Spec.Drivers {
   598  		if driver.Name != csiDriverName {
   599  			drivers = append(drivers, driver)
   600  		} else {
   601  			// Found a driver with name csiDriverName
   602  			// Set hasModified to true because it will be removed
   603  			hasModified = true
   604  		}
   605  	}
   606  
   607  	if !hasModified {
   608  		// No changes, don't update
   609  		return nil
   610  	}
   611  	nodeInfo.Spec.Drivers = drivers
   612  
   613  	_, err = nodeInfoClient.Update(nodeInfo)
   614  
   615  	return err // do not wrap error
   616  
   617  }
   618  
   619  func updateMaxAttachLimit(driverName string, maxLimit int64) nodeUpdateFunc {
   620  	return func(node *v1.Node) (*v1.Node, bool, error) {
   621  		if maxLimit <= 0 {
   622  			klog.V(4).Infof("skipping adding attach limit for %s", driverName)
   623  			return node, false, nil
   624  		}
   625  
   626  		if node.Status.Capacity == nil {
   627  			node.Status.Capacity = v1.ResourceList{}
   628  		}
   629  		if node.Status.Allocatable == nil {
   630  			node.Status.Allocatable = v1.ResourceList{}
   631  		}
   632  		limitKeyName := util.GetCSIAttachLimitKey(driverName)
   633  		node.Status.Capacity[v1.ResourceName(limitKeyName)] = *resource.NewQuantity(maxLimit, resource.DecimalSI)
   634  		node.Status.Allocatable[v1.ResourceName(limitKeyName)] = *resource.NewQuantity(maxLimit, resource.DecimalSI)
   635  
   636  		return node, true, nil
   637  	}
   638  }
   639  
   640  func removeMaxAttachLimit(driverName string) nodeUpdateFunc {
   641  	return func(node *v1.Node) (*v1.Node, bool, error) {
   642  		limitKey := v1.ResourceName(util.GetCSIAttachLimitKey(driverName))
   643  
   644  		capacityExists := false
   645  		if node.Status.Capacity != nil {
   646  			_, capacityExists = node.Status.Capacity[limitKey]
   647  		}
   648  
   649  		allocatableExists := false
   650  		if node.Status.Allocatable != nil {
   651  			_, allocatableExists = node.Status.Allocatable[limitKey]
   652  		}
   653  
   654  		if !capacityExists && !allocatableExists {
   655  			return node, false, nil
   656  		}
   657  
   658  		delete(node.Status.Capacity, limitKey)
   659  		if len(node.Status.Capacity) == 0 {
   660  			node.Status.Capacity = nil
   661  		}
   662  
   663  		delete(node.Status.Allocatable, limitKey)
   664  		if len(node.Status.Allocatable) == 0 {
   665  			node.Status.Allocatable = nil
   666  		}
   667  
   668  		return node, true, nil
   669  	}
   670  }