k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/util/operationexecutor/operation_executor_test.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  package operationexecutor
    18  
    19  import (
    20  	"fmt"
    21  	"strconv"
    22  	"testing"
    23  	"time"
    24  
    25  	"k8s.io/klog/v2"
    26  
    27  	v1 "k8s.io/api/core/v1"
    28  	"k8s.io/apimachinery/pkg/api/resource"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/apimachinery/pkg/util/uuid"
    32  	csitrans "k8s.io/csi-translation-lib"
    33  	"k8s.io/klog/v2/ktesting"
    34  	"k8s.io/kubernetes/pkg/volume"
    35  	"k8s.io/kubernetes/pkg/volume/util/hostutil"
    36  	volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
    37  )
    38  
    39  const (
    40  	numVolumesToMount                    = 2
    41  	numAttachableVolumesToUnmount        = 2
    42  	numNonAttachableVolumesToUnmount     = 2
    43  	numDevicesToUnmount                  = 2
    44  	numVolumesToAttach                   = 2
    45  	numVolumesToDetach                   = 2
    46  	numVolumesToVerifyAttached           = 2
    47  	numVolumesToVerifyControllerAttached = 2
    48  	numVolumesToMap                      = 2
    49  	numAttachableVolumesToUnmap          = 2
    50  	numNonAttachableVolumesToUnmap       = 2
    51  	numDevicesToUnmap                    = 2
    52  )
    53  
    54  var _ OperationGenerator = &fakeOperationGenerator{}
    55  
    56  func TestOperationExecutor_MountVolume_ConcurrentMountForNonAttachableAndNonDevicemountablePlugins(t *testing.T) {
    57  	// Arrange
    58  	ch, quit, oe := setup()
    59  	volumesToMount := make([]VolumeToMount, numVolumesToMount)
    60  	secretName := "secret-volume"
    61  	volumeName := v1.UniqueVolumeName(secretName)
    62  
    63  	// Act
    64  	for i := range volumesToMount {
    65  		podName := "pod-" + strconv.Itoa(i+1)
    66  		pod := getTestPodWithSecret(podName, secretName)
    67  		volumesToMount[i] = VolumeToMount{
    68  			Pod:                     pod,
    69  			VolumeName:              volumeName,
    70  			PluginIsAttachable:      false, // this field determines whether the plugin is attachable
    71  			PluginIsDeviceMountable: false, // this field determines whether the plugin is devicemountable
    72  			ReportedInUse:           true,
    73  		}
    74  		oe.MountVolume(0 /* waitForAttachTimeOut */, volumesToMount[i], nil /* actualStateOfWorldMounterUpdater */, false /* isRemount */)
    75  	}
    76  
    77  	// Assert
    78  	if !isOperationRunConcurrently(ch, quit, numVolumesToMount) {
    79  		t.Fatalf("Unable to start mount operations in Concurrent for non-attachable volumes")
    80  	}
    81  }
    82  
    83  func TestOperationExecutor_MountVolume_ConcurrentMountForAttachablePlugins(t *testing.T) {
    84  	t.Parallel()
    85  
    86  	// Arrange
    87  	ch, quit, oe := setup()
    88  	volumesToMount := make([]VolumeToMount, numVolumesToAttach)
    89  	pdName := "pd-volume"
    90  	volumeName := v1.UniqueVolumeName(pdName)
    91  	// Act
    92  	for i := range volumesToMount {
    93  		podName := "pod-" + strconv.Itoa(i+1)
    94  		pod := getTestPodWithGCEPD(podName, pdName)
    95  		volumesToMount[i] = VolumeToMount{
    96  			Pod:                pod,
    97  			VolumeName:         volumeName,
    98  			PluginIsAttachable: true, // this field determines whether the plugin is attachable
    99  			ReportedInUse:      true,
   100  		}
   101  		oe.MountVolume(0 /* waitForAttachTimeout */, volumesToMount[i], nil /* actualStateOfWorldMounterUpdater */, false /* isRemount */)
   102  	}
   103  
   104  	// Assert
   105  	if !isOperationRunSerially(ch, quit) {
   106  		t.Fatalf("Mount operations should not start concurrently for attachable volumes")
   107  	}
   108  }
   109  
   110  func TestOperationExecutor_MountVolume_ConcurrentMountForDeviceMountablePlugins(t *testing.T) {
   111  	t.Parallel()
   112  
   113  	// Arrange
   114  	ch, quit, oe := setup()
   115  	volumesToMount := make([]VolumeToMount, numVolumesToAttach)
   116  	pdName := "pd-volume"
   117  	volumeName := v1.UniqueVolumeName(pdName)
   118  	// Act
   119  	for i := range volumesToMount {
   120  		podName := "pod-" + strconv.Itoa(i+1)
   121  		pod := getTestPodWithGCEPD(podName, pdName)
   122  		volumesToMount[i] = VolumeToMount{
   123  			Pod:                     pod,
   124  			VolumeName:              volumeName,
   125  			PluginIsDeviceMountable: true, // this field determines whether the plugin is devicemountable
   126  			ReportedInUse:           true,
   127  		}
   128  		oe.MountVolume(0 /* waitForAttachTimeout */, volumesToMount[i], nil /* actualStateOfWorldMounterUpdater */, false /* isRemount */)
   129  	}
   130  
   131  	// Assert
   132  	if !isOperationRunSerially(ch, quit) {
   133  		t.Fatalf("Mount operations should not start concurrently for devicemountable volumes")
   134  	}
   135  }
   136  
   137  func TestOperationExecutor_UnmountVolume_ConcurrentUnmountForAllPlugins(t *testing.T) {
   138  	// Arrange
   139  	ch, quit, oe := setup()
   140  	volumesToUnmount := make([]MountedVolume, numAttachableVolumesToUnmount+numNonAttachableVolumesToUnmount)
   141  	pdName := "pd-volume"
   142  	secretName := "secret-volume"
   143  
   144  	// Act
   145  	for i := 0; i < numNonAttachableVolumesToUnmount+numAttachableVolumesToUnmount; i++ {
   146  		podName := "pod-" + strconv.Itoa(i+1)
   147  		if i < numNonAttachableVolumesToUnmount {
   148  			pod := getTestPodWithSecret(podName, secretName)
   149  			volumesToUnmount[i] = MountedVolume{
   150  				PodName:    volumetypes.UniquePodName(podName),
   151  				VolumeName: v1.UniqueVolumeName(secretName),
   152  				PodUID:     pod.UID,
   153  			}
   154  		} else {
   155  			pod := getTestPodWithGCEPD(podName, pdName)
   156  			volumesToUnmount[i] = MountedVolume{
   157  				PodName:    volumetypes.UniquePodName(podName),
   158  				VolumeName: v1.UniqueVolumeName(pdName),
   159  				PodUID:     pod.UID,
   160  			}
   161  		}
   162  		oe.UnmountVolume(volumesToUnmount[i], nil /* actualStateOfWorldMounterUpdater */, "" /*podsDir*/)
   163  	}
   164  
   165  	// Assert
   166  	if !isOperationRunConcurrently(ch, quit, numNonAttachableVolumesToUnmount+numAttachableVolumesToUnmount) {
   167  		t.Fatalf("Unable to start unmount operations concurrently for volume plugins")
   168  	}
   169  }
   170  
   171  func TestOperationExecutor_UnmountDeviceConcurrently(t *testing.T) {
   172  	t.Parallel()
   173  
   174  	// Arrange
   175  	ch, quit, oe := setup()
   176  	attachedVolumes := make([]AttachedVolume, numDevicesToUnmount)
   177  	pdName := "pd-volume"
   178  
   179  	// Act
   180  	for i := range attachedVolumes {
   181  		attachedVolumes[i] = AttachedVolume{
   182  			VolumeName: v1.UniqueVolumeName(pdName),
   183  			NodeName:   "node-name",
   184  		}
   185  		oe.UnmountDevice(attachedVolumes[i], nil /* actualStateOfWorldMounterUpdater */, nil /* mount.Interface */)
   186  	}
   187  
   188  	// Assert
   189  	if !isOperationRunSerially(ch, quit) {
   190  		t.Fatalf("Unmount device operations should not start concurrently")
   191  	}
   192  }
   193  
   194  func TestOperationExecutor_AttachSingleNodeVolumeConcurrentlyToSameNode(t *testing.T) {
   195  	t.Parallel()
   196  
   197  	// Arrange
   198  	ch, quit, oe := setup()
   199  	volumesToAttach := make([]VolumeToAttach, numVolumesToAttach)
   200  	pdName := "pd-volume"
   201  
   202  	// Act
   203  	for i := range volumesToAttach {
   204  		volumesToAttach[i] = VolumeToAttach{
   205  			VolumeName: v1.UniqueVolumeName(pdName),
   206  			NodeName:   "node",
   207  			VolumeSpec: &volume.Spec{
   208  				PersistentVolume: &v1.PersistentVolume{
   209  					Spec: v1.PersistentVolumeSpec{
   210  						AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
   211  					},
   212  				},
   213  			},
   214  		}
   215  		logger, _ := ktesting.NewTestContext(t)
   216  		oe.AttachVolume(logger, volumesToAttach[i], nil /* actualStateOfWorldAttacherUpdater */)
   217  	}
   218  
   219  	// Assert
   220  	if !isOperationRunSerially(ch, quit) {
   221  		t.Fatalf("Attach volume operations should not start concurrently")
   222  	}
   223  }
   224  
   225  func TestOperationExecutor_AttachMultiNodeVolumeConcurrentlyToSameNode(t *testing.T) {
   226  	t.Parallel()
   227  
   228  	// Arrange
   229  	ch, quit, oe := setup()
   230  	volumesToAttach := make([]VolumeToAttach, numVolumesToAttach)
   231  	pdName := "pd-volume"
   232  
   233  	// Act
   234  	for i := range volumesToAttach {
   235  		volumesToAttach[i] = VolumeToAttach{
   236  			VolumeName: v1.UniqueVolumeName(pdName),
   237  			NodeName:   "node",
   238  			VolumeSpec: &volume.Spec{
   239  				PersistentVolume: &v1.PersistentVolume{
   240  					Spec: v1.PersistentVolumeSpec{
   241  						AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany},
   242  					},
   243  				},
   244  			},
   245  		}
   246  		logger, _ := ktesting.NewTestContext(t)
   247  		oe.AttachVolume(logger, volumesToAttach[i], nil /* actualStateOfWorldAttacherUpdater */)
   248  	}
   249  
   250  	// Assert
   251  	if !isOperationRunSerially(ch, quit) {
   252  		t.Fatalf("Attach volume operations should not start concurrently")
   253  	}
   254  }
   255  
   256  func TestOperationExecutor_AttachSingleNodeVolumeConcurrentlyToDifferentNodes(t *testing.T) {
   257  	t.Parallel()
   258  
   259  	// Arrange
   260  	ch, quit, oe := setup()
   261  	volumesToAttach := make([]VolumeToAttach, numVolumesToAttach)
   262  	pdName := "pd-volume"
   263  
   264  	// Act
   265  	for i := range volumesToAttach {
   266  		volumesToAttach[i] = VolumeToAttach{
   267  			VolumeName: v1.UniqueVolumeName(pdName),
   268  			NodeName:   types.NodeName(fmt.Sprintf("node%d", i)),
   269  			VolumeSpec: &volume.Spec{
   270  				PersistentVolume: &v1.PersistentVolume{
   271  					Spec: v1.PersistentVolumeSpec{
   272  						AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
   273  					},
   274  				},
   275  			},
   276  		}
   277  		logger, _ := ktesting.NewTestContext(t)
   278  		oe.AttachVolume(logger, volumesToAttach[i], nil /* actualStateOfWorldAttacherUpdater */)
   279  	}
   280  
   281  	// Assert
   282  	if !isOperationRunSerially(ch, quit) {
   283  		t.Fatalf("Attach volume operations should not start concurrently")
   284  	}
   285  }
   286  
   287  func TestOperationExecutor_AttachMultiNodeVolumeConcurrentlyToDifferentNodes(t *testing.T) {
   288  	// Arrange
   289  	ch, quit, oe := setup()
   290  	volumesToAttach := make([]VolumeToAttach, numVolumesToAttach)
   291  	pdName := "pd-volume"
   292  
   293  	// Act
   294  	for i := range volumesToAttach {
   295  		volumesToAttach[i] = VolumeToAttach{
   296  			VolumeName: v1.UniqueVolumeName(pdName),
   297  			NodeName:   types.NodeName(fmt.Sprintf("node%d", i)),
   298  			VolumeSpec: &volume.Spec{
   299  				PersistentVolume: &v1.PersistentVolume{
   300  					Spec: v1.PersistentVolumeSpec{
   301  						AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany},
   302  					},
   303  				},
   304  			},
   305  		}
   306  		logger, _ := ktesting.NewTestContext(t)
   307  		oe.AttachVolume(logger, volumesToAttach[i], nil /* actualStateOfWorldAttacherUpdater */)
   308  	}
   309  
   310  	// Assert
   311  	if !isOperationRunConcurrently(ch, quit, numVolumesToAttach) {
   312  		t.Fatalf("Attach volume operations should not execute serially")
   313  	}
   314  }
   315  
   316  func TestOperationExecutor_DetachSingleNodeVolumeConcurrentlyFromSameNode(t *testing.T) {
   317  	t.Parallel()
   318  
   319  	// Arrange
   320  	ch, quit, oe := setup()
   321  	attachedVolumes := make([]AttachedVolume, numVolumesToDetach)
   322  	pdName := "pd-volume"
   323  
   324  	// Act
   325  	for i := range attachedVolumes {
   326  		attachedVolumes[i] = AttachedVolume{
   327  			VolumeName: v1.UniqueVolumeName(pdName),
   328  			NodeName:   "node",
   329  			VolumeSpec: &volume.Spec{
   330  				PersistentVolume: &v1.PersistentVolume{
   331  					Spec: v1.PersistentVolumeSpec{
   332  						AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
   333  					},
   334  				},
   335  			},
   336  		}
   337  		logger, _ := ktesting.NewTestContext(t)
   338  		oe.DetachVolume(logger, attachedVolumes[i], true /* verifySafeToDetach */, nil /* actualStateOfWorldAttacherUpdater */)
   339  	}
   340  
   341  	// Assert
   342  	if !isOperationRunSerially(ch, quit) {
   343  		t.Fatalf("DetachVolume operations should not run concurrently")
   344  	}
   345  }
   346  
   347  func TestOperationExecutor_DetachMultiNodeVolumeConcurrentlyFromSameNode(t *testing.T) {
   348  	t.Parallel()
   349  
   350  	// Arrange
   351  	ch, quit, oe := setup()
   352  	attachedVolumes := make([]AttachedVolume, numVolumesToDetach)
   353  	pdName := "pd-volume"
   354  
   355  	// Act
   356  	for i := range attachedVolumes {
   357  		attachedVolumes[i] = AttachedVolume{
   358  			VolumeName: v1.UniqueVolumeName(pdName),
   359  			NodeName:   "node",
   360  			VolumeSpec: &volume.Spec{
   361  				PersistentVolume: &v1.PersistentVolume{
   362  					Spec: v1.PersistentVolumeSpec{
   363  						AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany},
   364  					},
   365  				},
   366  			},
   367  		}
   368  		logger, _ := ktesting.NewTestContext(t)
   369  		oe.DetachVolume(logger, attachedVolumes[i], true /* verifySafeToDetach */, nil /* actualStateOfWorldAttacherUpdater */)
   370  	}
   371  
   372  	// Assert
   373  	if !isOperationRunSerially(ch, quit) {
   374  		t.Fatalf("DetachVolume operations should not run concurrently")
   375  	}
   376  }
   377  
   378  func TestOperationExecutor_DetachMultiNodeVolumeConcurrentlyFromDifferentNodes(t *testing.T) {
   379  	// Arrange
   380  	ch, quit, oe := setup()
   381  	attachedVolumes := make([]AttachedVolume, numVolumesToDetach)
   382  	pdName := "pd-volume"
   383  
   384  	// Act
   385  	for i := range attachedVolumes {
   386  		attachedVolumes[i] = AttachedVolume{
   387  			VolumeName: v1.UniqueVolumeName(pdName),
   388  			NodeName:   types.NodeName(fmt.Sprintf("node%d", i)),
   389  			VolumeSpec: &volume.Spec{
   390  				PersistentVolume: &v1.PersistentVolume{
   391  					Spec: v1.PersistentVolumeSpec{
   392  						AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany},
   393  					},
   394  				},
   395  			},
   396  		}
   397  		logger, _ := ktesting.NewTestContext(t)
   398  		oe.DetachVolume(logger, attachedVolumes[i], true /* verifySafeToDetach */, nil /* actualStateOfWorldAttacherUpdater */)
   399  	}
   400  
   401  	// Assert
   402  	if !isOperationRunConcurrently(ch, quit, numVolumesToDetach) {
   403  		t.Fatalf("Attach volume operations should not execute serially")
   404  	}
   405  }
   406  
   407  func TestOperationExecutor_VerifyVolumesAreAttachedConcurrentlyOnSameNode(t *testing.T) {
   408  	// Arrange
   409  	ch, quit, oe := setup()
   410  
   411  	// Act
   412  	for i := 0; i < numVolumesToVerifyAttached; i++ {
   413  		oe.VerifyVolumesAreAttachedPerNode(nil /* attachedVolumes */, "node-name", nil /* actualStateOfWorldAttacherUpdater */)
   414  	}
   415  
   416  	// Assert
   417  	if !isOperationRunConcurrently(ch, quit, numVolumesToVerifyAttached) {
   418  		t.Fatalf("VerifyVolumesAreAttached operation is not being run concurrently")
   419  	}
   420  }
   421  
   422  func TestOperationExecutor_VerifyVolumesAreAttachedConcurrentlyOnDifferentNodes(t *testing.T) {
   423  	// Arrange
   424  	ch, quit, oe := setup()
   425  
   426  	// Act
   427  	for i := 0; i < numVolumesToVerifyAttached; i++ {
   428  		oe.VerifyVolumesAreAttachedPerNode(
   429  			nil, /* attachedVolumes */
   430  			types.NodeName(fmt.Sprintf("node-name-%d", i)),
   431  			nil /* actualStateOfWorldAttacherUpdater */)
   432  	}
   433  
   434  	// Assert
   435  	if !isOperationRunConcurrently(ch, quit, numVolumesToVerifyAttached) {
   436  		t.Fatalf("VerifyVolumesAreAttached operation is not being run concurrently")
   437  	}
   438  }
   439  
   440  func TestOperationExecutor_VerifyControllerAttachedVolumeConcurrently(t *testing.T) {
   441  	t.Parallel()
   442  
   443  	// Arrange
   444  	ch, quit, oe := setup()
   445  	volumesToMount := make([]VolumeToMount, numVolumesToVerifyControllerAttached)
   446  	pdName := "pd-volume"
   447  
   448  	// Act
   449  	for i := range volumesToMount {
   450  		volumesToMount[i] = VolumeToMount{
   451  			VolumeName: v1.UniqueVolumeName(pdName),
   452  		}
   453  		logger, _ := ktesting.NewTestContext(t)
   454  		oe.VerifyControllerAttachedVolume(logger, volumesToMount[i], types.NodeName("node-name"), nil /* actualStateOfWorldMounterUpdater */)
   455  	}
   456  
   457  	// Assert
   458  	if !isOperationRunSerially(ch, quit) {
   459  		t.Fatalf("VerifyControllerAttachedVolume should not run concurrently")
   460  	}
   461  }
   462  
   463  func TestOperationExecutor_MountVolume_ConcurrentMountForNonAttachablePlugins_VolumeMode_Block(t *testing.T) {
   464  	// Arrange
   465  	ch, quit, oe := setup()
   466  	volumesToMount := make([]VolumeToMount, numVolumesToMap)
   467  	secretName := "secret-volume"
   468  	volumeName := v1.UniqueVolumeName(secretName)
   469  	volumeMode := v1.PersistentVolumeBlock
   470  	tmpSpec := &volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{VolumeMode: &volumeMode}}}
   471  
   472  	// Act
   473  	for i := range volumesToMount {
   474  		podName := "pod-" + strconv.Itoa(i+1)
   475  		pod := getTestPodWithSecret(podName, secretName)
   476  		volumesToMount[i] = VolumeToMount{
   477  			Pod:                pod,
   478  			VolumeName:         volumeName,
   479  			PluginIsAttachable: false, // this field determines whether the plugin is attachable
   480  			ReportedInUse:      true,
   481  			VolumeSpec:         tmpSpec,
   482  		}
   483  		oe.MountVolume(0 /* waitForAttachTimeOut */, volumesToMount[i], nil /* actualStateOfWorldMounterUpdater */, false)
   484  	}
   485  
   486  	// Assert
   487  	if !isOperationRunConcurrently(ch, quit, numVolumesToMap) {
   488  		t.Fatalf("Unable to start map operations in Concurrent for non-attachable volumes")
   489  	}
   490  }
   491  
   492  func TestOperationExecutor_MountVolume_ConcurrentMountForAttachablePlugins_VolumeMode_Block(t *testing.T) {
   493  	t.Parallel()
   494  
   495  	// Arrange
   496  	ch, quit, oe := setup()
   497  	volumesToMount := make([]VolumeToMount, numVolumesToAttach)
   498  	pdName := "pd-volume"
   499  	volumeName := v1.UniqueVolumeName(pdName)
   500  	volumeMode := v1.PersistentVolumeBlock
   501  	tmpSpec := &volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{VolumeMode: &volumeMode}}}
   502  
   503  	// Act
   504  	for i := range volumesToMount {
   505  		podName := "pod-" + strconv.Itoa(i+1)
   506  		pod := getTestPodWithGCEPD(podName, pdName)
   507  		volumesToMount[i] = VolumeToMount{
   508  			Pod:                pod,
   509  			VolumeName:         volumeName,
   510  			PluginIsAttachable: true, // this field determines whether the plugin is attachable
   511  			ReportedInUse:      true,
   512  			VolumeSpec:         tmpSpec,
   513  		}
   514  		oe.MountVolume(0 /* waitForAttachTimeout */, volumesToMount[i], nil /* actualStateOfWorldMounterUpdater */, false)
   515  	}
   516  
   517  	// Assert
   518  	if !isOperationRunSerially(ch, quit) {
   519  		t.Fatalf("Map operations should not start concurrently for attachable volumes")
   520  	}
   521  }
   522  
   523  func TestOperationExecutor_UnmountVolume_ConcurrentUnmountForAllPlugins_VolumeMode_Block(t *testing.T) {
   524  	// Arrange
   525  	ch, quit, oe := setup()
   526  	volumesToUnmount := make([]MountedVolume, numAttachableVolumesToUnmap+numNonAttachableVolumesToUnmap)
   527  	pdName := "pd-volume"
   528  	secretName := "secret-volume"
   529  	volumeMode := v1.PersistentVolumeBlock
   530  	tmpSpec := &volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{VolumeMode: &volumeMode}}}
   531  
   532  	// Act
   533  	for i := 0; i < numNonAttachableVolumesToUnmap+numAttachableVolumesToUnmap; i++ {
   534  		podName := "pod-" + strconv.Itoa(i+1)
   535  		if i < numNonAttachableVolumesToUnmap {
   536  			pod := getTestPodWithSecret(podName, secretName)
   537  			volumesToUnmount[i] = MountedVolume{
   538  				PodName:    volumetypes.UniquePodName(podName),
   539  				VolumeName: v1.UniqueVolumeName(secretName),
   540  				PodUID:     pod.UID,
   541  				VolumeSpec: tmpSpec,
   542  			}
   543  		} else {
   544  			pod := getTestPodWithGCEPD(podName, pdName)
   545  			volumesToUnmount[i] = MountedVolume{
   546  				PodName:    volumetypes.UniquePodName(podName),
   547  				VolumeName: v1.UniqueVolumeName(pdName),
   548  				PodUID:     pod.UID,
   549  				VolumeSpec: tmpSpec,
   550  			}
   551  		}
   552  		oe.UnmountVolume(volumesToUnmount[i], nil /* actualStateOfWorldMounterUpdater */, "" /* podsDir */)
   553  	}
   554  
   555  	// Assert
   556  	if !isOperationRunConcurrently(ch, quit, numNonAttachableVolumesToUnmap+numAttachableVolumesToUnmap) {
   557  		t.Fatalf("Unable to start unmap operations concurrently for volume plugins")
   558  	}
   559  }
   560  
   561  func TestOperationExecutor_UnmountDeviceConcurrently_VolumeMode_Block(t *testing.T) {
   562  	t.Parallel()
   563  
   564  	// Arrange
   565  	ch, quit, oe := setup()
   566  	attachedVolumes := make([]AttachedVolume, numDevicesToUnmap)
   567  	pdName := "pd-volume"
   568  	volumeMode := v1.PersistentVolumeBlock
   569  	tmpSpec := &volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{VolumeMode: &volumeMode}}}
   570  
   571  	// Act
   572  	for i := range attachedVolumes {
   573  		attachedVolumes[i] = AttachedVolume{
   574  			VolumeName: v1.UniqueVolumeName(pdName),
   575  			NodeName:   "node-name",
   576  			VolumeSpec: tmpSpec,
   577  		}
   578  		oe.UnmountDevice(attachedVolumes[i], nil /* actualStateOfWorldMounterUpdater */, nil /* mount.Interface */)
   579  	}
   580  
   581  	// Assert
   582  	if !isOperationRunSerially(ch, quit) {
   583  		t.Fatalf("Unmap device operations should not start concurrently")
   584  	}
   585  }
   586  
   587  type fakeOperationGenerator struct {
   588  	ch   chan interface{}
   589  	quit chan interface{}
   590  }
   591  
   592  func newFakeOperationGenerator(ch chan interface{}, quit chan interface{}) OperationGenerator {
   593  	return &fakeOperationGenerator{
   594  		ch:   ch,
   595  		quit: quit,
   596  	}
   597  }
   598  
   599  func (fopg *fakeOperationGenerator) GenerateMountVolumeFunc(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorldMounterUpdater ActualStateOfWorldMounterUpdater, isRemount bool) volumetypes.GeneratedOperations {
   600  	opFunc := func() volumetypes.OperationContext {
   601  		startOperationAndBlock(fopg.ch, fopg.quit)
   602  		return volumetypes.NewOperationContext(nil, nil, false)
   603  	}
   604  	return volumetypes.GeneratedOperations{
   605  		OperationFunc: opFunc,
   606  	}
   607  }
   608  func (fopg *fakeOperationGenerator) GenerateUnmountVolumeFunc(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, podsDir string) (volumetypes.GeneratedOperations, error) {
   609  	opFunc := func() volumetypes.OperationContext {
   610  		startOperationAndBlock(fopg.ch, fopg.quit)
   611  		return volumetypes.NewOperationContext(nil, nil, false)
   612  	}
   613  	return volumetypes.GeneratedOperations{
   614  		OperationFunc: opFunc,
   615  	}, nil
   616  }
   617  func (fopg *fakeOperationGenerator) GenerateAttachVolumeFunc(logger klog.Logger, volumeToAttach VolumeToAttach, actualStateOfWorld ActualStateOfWorldAttacherUpdater) volumetypes.GeneratedOperations {
   618  	opFunc := func() volumetypes.OperationContext {
   619  		startOperationAndBlock(fopg.ch, fopg.quit)
   620  		return volumetypes.NewOperationContext(nil, nil, false)
   621  	}
   622  	return volumetypes.GeneratedOperations{
   623  		OperationFunc: opFunc,
   624  	}
   625  }
   626  func (fopg *fakeOperationGenerator) GenerateDetachVolumeFunc(logger klog.Logger, volumeToDetach AttachedVolume, verifySafeToDetach bool, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) {
   627  	opFunc := func() volumetypes.OperationContext {
   628  		startOperationAndBlock(fopg.ch, fopg.quit)
   629  		return volumetypes.NewOperationContext(nil, nil, false)
   630  	}
   631  	return volumetypes.GeneratedOperations{
   632  		OperationFunc: opFunc,
   633  	}, nil
   634  }
   635  func (fopg *fakeOperationGenerator) GenerateVolumesAreAttachedFunc(attachedVolumes []AttachedVolume, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) {
   636  	opFunc := func() volumetypes.OperationContext {
   637  		startOperationAndBlock(fopg.ch, fopg.quit)
   638  		return volumetypes.NewOperationContext(nil, nil, false)
   639  	}
   640  	return volumetypes.GeneratedOperations{
   641  		OperationFunc: opFunc,
   642  	}, nil
   643  }
   644  func (fopg *fakeOperationGenerator) GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, hostutil hostutil.HostUtils) (volumetypes.GeneratedOperations, error) {
   645  	opFunc := func() volumetypes.OperationContext {
   646  		startOperationAndBlock(fopg.ch, fopg.quit)
   647  		return volumetypes.NewOperationContext(nil, nil, false)
   648  	}
   649  	return volumetypes.GeneratedOperations{
   650  		OperationFunc: opFunc,
   651  	}, nil
   652  }
   653  func (fopg *fakeOperationGenerator) GenerateVerifyControllerAttachedVolumeFunc(logger klog.Logger, volumeToMount VolumeToMount, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) {
   654  	opFunc := func() volumetypes.OperationContext {
   655  		startOperationAndBlock(fopg.ch, fopg.quit)
   656  		return volumetypes.NewOperationContext(nil, nil, false)
   657  	}
   658  	return volumetypes.GeneratedOperations{
   659  		OperationFunc: opFunc,
   660  	}, nil
   661  }
   662  
   663  func (fopg *fakeOperationGenerator) GenerateExpandVolumeFunc(pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) (volumetypes.GeneratedOperations, error) {
   664  	opFunc := func() volumetypes.OperationContext {
   665  		startOperationAndBlock(fopg.ch, fopg.quit)
   666  		return volumetypes.NewOperationContext(nil, nil, false)
   667  	}
   668  	return volumetypes.GeneratedOperations{
   669  		OperationFunc: opFunc,
   670  	}, nil
   671  }
   672  
   673  func (fopg *fakeOperationGenerator) GenerateExpandAndRecoverVolumeFunc(pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume, resizerName string) (volumetypes.GeneratedOperations, error) {
   674  	opFunc := func() volumetypes.OperationContext {
   675  		startOperationAndBlock(fopg.ch, fopg.quit)
   676  		return volumetypes.NewOperationContext(nil, nil, false)
   677  	}
   678  	return volumetypes.GeneratedOperations{
   679  		OperationFunc: opFunc,
   680  	}, nil
   681  }
   682  
   683  func (fopg *fakeOperationGenerator) GenerateExpandInUseVolumeFunc(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater, currentSize resource.Quantity) (volumetypes.GeneratedOperations, error) {
   684  	opFunc := func() volumetypes.OperationContext {
   685  		startOperationAndBlock(fopg.ch, fopg.quit)
   686  		return volumetypes.NewOperationContext(nil, nil, false)
   687  	}
   688  	return volumetypes.GeneratedOperations{
   689  		OperationFunc: opFunc,
   690  	}, nil
   691  }
   692  
   693  func (fopg *fakeOperationGenerator) GenerateMapVolumeFunc(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorldMounterUpdater ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) {
   694  	opFunc := func() volumetypes.OperationContext {
   695  		startOperationAndBlock(fopg.ch, fopg.quit)
   696  		return volumetypes.NewOperationContext(nil, nil, false)
   697  	}
   698  	return volumetypes.GeneratedOperations{
   699  		OperationFunc: opFunc,
   700  	}, nil
   701  }
   702  
   703  func (fopg *fakeOperationGenerator) GenerateUnmapVolumeFunc(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) {
   704  	opFunc := func() volumetypes.OperationContext {
   705  		startOperationAndBlock(fopg.ch, fopg.quit)
   706  		return volumetypes.NewOperationContext(nil, nil, false)
   707  	}
   708  	return volumetypes.GeneratedOperations{
   709  		OperationFunc: opFunc,
   710  	}, nil
   711  }
   712  
   713  func (fopg *fakeOperationGenerator) GenerateUnmapDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, hostutil hostutil.HostUtils) (volumetypes.GeneratedOperations, error) {
   714  	opFunc := func() volumetypes.OperationContext {
   715  		startOperationAndBlock(fopg.ch, fopg.quit)
   716  		return volumetypes.NewOperationContext(nil, nil, false)
   717  	}
   718  	return volumetypes.GeneratedOperations{
   719  		OperationFunc: opFunc,
   720  	}, nil
   721  }
   722  
   723  func (fopg *fakeOperationGenerator) GetVolumePluginMgr() *volume.VolumePluginMgr {
   724  	return nil
   725  }
   726  
   727  func (fopg *fakeOperationGenerator) GetCSITranslator() InTreeToCSITranslator {
   728  	return csitrans.New()
   729  }
   730  
   731  func getTestPodWithSecret(podName, secretName string) *v1.Pod {
   732  	return &v1.Pod{
   733  		ObjectMeta: metav1.ObjectMeta{
   734  			Name: podName,
   735  			UID:  types.UID(podName),
   736  		},
   737  		Spec: v1.PodSpec{
   738  			Volumes: []v1.Volume{
   739  				{
   740  					Name: secretName,
   741  					VolumeSource: v1.VolumeSource{
   742  						Secret: &v1.SecretVolumeSource{
   743  							SecretName: secretName,
   744  						},
   745  					},
   746  				},
   747  			},
   748  			Containers: []v1.Container{
   749  				{
   750  					Name:  "secret-volume-test",
   751  					Image: "registry.k8s.io/mounttest:0.8",
   752  					Args: []string{
   753  						"--file_content=/etc/secret-volume/data-1",
   754  						"--file_mode=/etc/secret-volume/data-1"},
   755  					VolumeMounts: []v1.VolumeMount{
   756  						{
   757  							Name:      secretName,
   758  							MountPath: "/data",
   759  						},
   760  					},
   761  				},
   762  			},
   763  			RestartPolicy: v1.RestartPolicyNever,
   764  		},
   765  	}
   766  }
   767  
   768  func getTestPodWithGCEPD(podName, pdName string) *v1.Pod {
   769  	return &v1.Pod{
   770  		ObjectMeta: metav1.ObjectMeta{
   771  			Name: podName,
   772  			UID:  types.UID(podName + string(uuid.NewUUID())),
   773  		},
   774  		Spec: v1.PodSpec{
   775  			Volumes: []v1.Volume{
   776  				{
   777  					Name: pdName,
   778  					VolumeSource: v1.VolumeSource{
   779  						GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
   780  							PDName:   pdName,
   781  							FSType:   "ext4",
   782  							ReadOnly: false,
   783  						},
   784  					},
   785  				},
   786  			},
   787  			Containers: []v1.Container{
   788  				{
   789  					Name:  "pd-volume-test",
   790  					Image: "registry.k8s.io/mounttest:0.8",
   791  					Args: []string{
   792  						"--file_content=/etc/pd-volume/data-1",
   793  					},
   794  					VolumeMounts: []v1.VolumeMount{
   795  						{
   796  							Name:      pdName,
   797  							MountPath: "/data",
   798  						},
   799  					},
   800  				},
   801  			},
   802  			RestartPolicy: v1.RestartPolicyNever,
   803  		},
   804  	}
   805  }
   806  
   807  func isOperationRunSerially(ch <-chan interface{}, quit chan<- interface{}) bool {
   808  	defer close(quit)
   809  	numOperationsStarted := 0
   810  loop:
   811  	for {
   812  		select {
   813  		case <-ch:
   814  			numOperationsStarted++
   815  			if numOperationsStarted > 1 {
   816  				return false
   817  			}
   818  		case <-time.After(5 * time.Second):
   819  			break loop
   820  		}
   821  	}
   822  	return true
   823  }
   824  
   825  func isOperationRunConcurrently(ch <-chan interface{}, quit chan<- interface{}, numOperationsToRun int) bool {
   826  	defer close(quit)
   827  	numOperationsStarted := 0
   828  loop:
   829  	for {
   830  		select {
   831  		case <-ch:
   832  			numOperationsStarted++
   833  			if numOperationsStarted == numOperationsToRun {
   834  				return true
   835  			}
   836  		case <-time.After(5 * time.Second):
   837  			break loop
   838  		}
   839  	}
   840  	return false
   841  }
   842  
   843  func setup() (chan interface{}, chan interface{}, OperationExecutor) {
   844  	ch, quit := make(chan interface{}), make(chan interface{})
   845  	return ch, quit, NewOperationExecutor(newFakeOperationGenerator(ch, quit))
   846  }
   847  
   848  // This function starts by writing to ch and blocks on the quit channel
   849  // until it is closed by the currently running test
   850  func startOperationAndBlock(ch chan<- interface{}, quit <-chan interface{}) {
   851  	ch <- nil
   852  	<-quit
   853  }