k8s.io/kubernetes@v1.29.3/test/e2e/storage/vsphere/vsphere_volume_perf.go (about)

     1  /*
     2  Copyright 2017 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 vsphere
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/onsi/ginkgo/v2"
    25  	"github.com/onsi/gomega"
    26  	v1 "k8s.io/api/core/v1"
    27  	storagev1 "k8s.io/api/storage/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	clientset "k8s.io/client-go/kubernetes"
    30  	"k8s.io/kubernetes/test/e2e/feature"
    31  	"k8s.io/kubernetes/test/e2e/framework"
    32  	e2enode "k8s.io/kubernetes/test/e2e/framework/node"
    33  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    34  	e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
    35  	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
    36  	"k8s.io/kubernetes/test/e2e/storage/utils"
    37  	admissionapi "k8s.io/pod-security-admission/api"
    38  )
    39  
    40  /*
    41  	This test calculates latency numbers for volume lifecycle operations
    42  
    43  1. Create 4 type of storage classes
    44  2. Read the total number of volumes to be created and volumes per pod
    45  3. Create total PVCs (number of volumes)
    46  4. Create Pods with attached volumes per pod
    47  5. Verify access to the volumes
    48  6. Delete pods and wait for volumes to detach
    49  7. Delete the PVCs
    50  */
    51  const (
    52  	SCSIUnitsAvailablePerNode = 55
    53  	CreateOp                  = "CreateOp"
    54  	AttachOp                  = "AttachOp"
    55  	DetachOp                  = "DetachOp"
    56  	DeleteOp                  = "DeleteOp"
    57  )
    58  
    59  var _ = utils.SIGDescribe("vcp-performance", feature.Vsphere, func() {
    60  	f := framework.NewDefaultFramework("vcp-performance")
    61  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    62  
    63  	var (
    64  		client           clientset.Interface
    65  		namespace        string
    66  		nodeSelectorList []*NodeSelector
    67  		policyName       string
    68  		datastoreName    string
    69  		volumeCount      int
    70  		volumesPerPod    int
    71  		iterations       int
    72  	)
    73  
    74  	ginkgo.BeforeEach(func(ctx context.Context) {
    75  		e2eskipper.SkipUnlessProviderIs("vsphere")
    76  		Bootstrap(f)
    77  		client = f.ClientSet
    78  		namespace = f.Namespace.Name
    79  
    80  		// Read the environment variables
    81  		volumeCount = GetAndExpectIntEnvVar(VCPPerfVolumeCount)
    82  		volumesPerPod = GetAndExpectIntEnvVar(VCPPerfVolumesPerPod)
    83  		iterations = GetAndExpectIntEnvVar(VCPPerfIterations)
    84  
    85  		policyName = GetAndExpectStringEnvVar(SPBMPolicyName)
    86  		datastoreName = GetAndExpectStringEnvVar(StorageClassDatastoreName)
    87  
    88  		nodes, err := e2enode.GetReadySchedulableNodes(ctx, client)
    89  		framework.ExpectNoError(err)
    90  		gomega.Expect(nodes.Items).ToNot(gomega.BeEmpty(), "Requires at least one ready node")
    91  
    92  		msg := fmt.Sprintf("Cannot attach %d volumes to %d nodes. Maximum volumes that can be attached on %d nodes is %d", volumeCount, len(nodes.Items), len(nodes.Items), SCSIUnitsAvailablePerNode*len(nodes.Items))
    93  		gomega.Expect(volumeCount).To(gomega.BeNumerically("<=", SCSIUnitsAvailablePerNode*len(nodes.Items)), msg)
    94  
    95  		msg = fmt.Sprintf("Cannot attach %d volumes per pod. Maximum volumes that can be attached per pod is %d", volumesPerPod, SCSIUnitsAvailablePerNode)
    96  		gomega.Expect(volumesPerPod).To(gomega.BeNumerically("<=", SCSIUnitsAvailablePerNode), msg)
    97  
    98  		nodeSelectorList = createNodeLabels(client, namespace, nodes)
    99  	})
   100  
   101  	ginkgo.It("vcp performance tests", func(ctx context.Context) {
   102  		scList := getTestStorageClasses(ctx, client, policyName, datastoreName)
   103  		for _, sc := range scList {
   104  			ginkgo.DeferCleanup(framework.IgnoreNotFound(client.StorageV1().StorageClasses().Delete), sc.Name, metav1.DeleteOptions{})
   105  		}
   106  
   107  		sumLatency := make(map[string]float64)
   108  		for i := 0; i < iterations; i++ {
   109  			latency := invokeVolumeLifeCyclePerformance(ctx, f, client, namespace, scList, volumesPerPod, volumeCount, nodeSelectorList)
   110  			for key, val := range latency {
   111  				sumLatency[key] += val
   112  			}
   113  		}
   114  
   115  		iterations64 := float64(iterations)
   116  		framework.Logf("Average latency for below operations")
   117  		framework.Logf("Creating %d PVCs and waiting for bound phase: %v seconds", volumeCount, sumLatency[CreateOp]/iterations64)
   118  		framework.Logf("Creating %v Pod: %v seconds", volumeCount/volumesPerPod, sumLatency[AttachOp]/iterations64)
   119  		framework.Logf("Deleting %v Pod and waiting for disk to be detached: %v seconds", volumeCount/volumesPerPod, sumLatency[DetachOp]/iterations64)
   120  		framework.Logf("Deleting %v PVCs: %v seconds", volumeCount, sumLatency[DeleteOp]/iterations64)
   121  
   122  	})
   123  })
   124  
   125  func getTestStorageClasses(ctx context.Context, client clientset.Interface, policyName, datastoreName string) []*storagev1.StorageClass {
   126  	const (
   127  		storageclass1 = "sc-default"
   128  		storageclass2 = "sc-vsan"
   129  		storageclass3 = "sc-spbm"
   130  		storageclass4 = "sc-user-specified-ds"
   131  	)
   132  	scNames := []string{storageclass1, storageclass2, storageclass3, storageclass4}
   133  	scArrays := make([]*storagev1.StorageClass, len(scNames))
   134  	for index, scname := range scNames {
   135  		// Create vSphere Storage Class
   136  		ginkgo.By(fmt.Sprintf("Creating Storage Class : %v", scname))
   137  		var sc *storagev1.StorageClass
   138  		var err error
   139  		switch scname {
   140  		case storageclass1:
   141  			sc, err = client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec(storageclass1, nil, nil, ""), metav1.CreateOptions{})
   142  		case storageclass2:
   143  			var scVSanParameters map[string]string
   144  			scVSanParameters = make(map[string]string)
   145  			scVSanParameters[PolicyHostFailuresToTolerate] = "1"
   146  			sc, err = client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec(storageclass2, scVSanParameters, nil, ""), metav1.CreateOptions{})
   147  		case storageclass3:
   148  			var scSPBMPolicyParameters map[string]string
   149  			scSPBMPolicyParameters = make(map[string]string)
   150  			scSPBMPolicyParameters[SpbmStoragePolicy] = policyName
   151  			sc, err = client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec(storageclass3, scSPBMPolicyParameters, nil, ""), metav1.CreateOptions{})
   152  		case storageclass4:
   153  			var scWithDSParameters map[string]string
   154  			scWithDSParameters = make(map[string]string)
   155  			scWithDSParameters[Datastore] = datastoreName
   156  			scWithDatastoreSpec := getVSphereStorageClassSpec(storageclass4, scWithDSParameters, nil, "")
   157  			sc, err = client.StorageV1().StorageClasses().Create(ctx, scWithDatastoreSpec, metav1.CreateOptions{})
   158  		}
   159  		gomega.Expect(sc).NotTo(gomega.BeNil())
   160  		framework.ExpectNoError(err)
   161  		scArrays[index] = sc
   162  	}
   163  	return scArrays
   164  }
   165  
   166  // invokeVolumeLifeCyclePerformance peforms full volume life cycle management and records latency for each operation
   167  func invokeVolumeLifeCyclePerformance(ctx context.Context, f *framework.Framework, client clientset.Interface, namespace string, sc []*storagev1.StorageClass, volumesPerPod int, volumeCount int, nodeSelectorList []*NodeSelector) (latency map[string]float64) {
   168  	var (
   169  		totalpvclaims [][]*v1.PersistentVolumeClaim
   170  		totalpvs      [][]*v1.PersistentVolume
   171  		totalpods     []*v1.Pod
   172  	)
   173  	nodeVolumeMap := make(map[string][]string)
   174  	latency = make(map[string]float64)
   175  	numPods := volumeCount / volumesPerPod
   176  
   177  	ginkgo.By(fmt.Sprintf("Creating %d PVCs", volumeCount))
   178  	start := time.Now()
   179  	for i := 0; i < numPods; i++ {
   180  		var pvclaims []*v1.PersistentVolumeClaim
   181  		for j := 0; j < volumesPerPod; j++ {
   182  			currsc := sc[((i*numPods)+j)%len(sc)]
   183  			pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", currsc))
   184  			framework.ExpectNoError(err)
   185  			pvclaims = append(pvclaims, pvclaim)
   186  		}
   187  		totalpvclaims = append(totalpvclaims, pvclaims)
   188  	}
   189  	for _, pvclaims := range totalpvclaims {
   190  		persistentvolumes, err := e2epv.WaitForPVClaimBoundPhase(ctx, client, pvclaims, f.Timeouts.ClaimProvision)
   191  		framework.ExpectNoError(err)
   192  		totalpvs = append(totalpvs, persistentvolumes)
   193  	}
   194  	elapsed := time.Since(start)
   195  	latency[CreateOp] = elapsed.Seconds()
   196  
   197  	ginkgo.By("Creating pod to attach PVs to the node")
   198  	start = time.Now()
   199  	for i, pvclaims := range totalpvclaims {
   200  		nodeSelector := nodeSelectorList[i%len(nodeSelectorList)]
   201  		pod, err := e2epod.CreatePod(ctx, client, namespace, map[string]string{nodeSelector.labelKey: nodeSelector.labelValue}, pvclaims, f.NamespacePodSecurityLevel, "")
   202  		framework.ExpectNoError(err)
   203  		totalpods = append(totalpods, pod)
   204  
   205  		ginkgo.DeferCleanup(e2epod.DeletePodWithWait, client, pod)
   206  	}
   207  	elapsed = time.Since(start)
   208  	latency[AttachOp] = elapsed.Seconds()
   209  
   210  	for i, pod := range totalpods {
   211  		verifyVSphereVolumesAccessible(ctx, client, pod, totalpvs[i])
   212  	}
   213  
   214  	ginkgo.By("Deleting pods")
   215  	start = time.Now()
   216  	for _, pod := range totalpods {
   217  		err := e2epod.DeletePodWithWait(ctx, client, pod)
   218  		framework.ExpectNoError(err)
   219  	}
   220  	elapsed = time.Since(start)
   221  	latency[DetachOp] = elapsed.Seconds()
   222  
   223  	for i, pod := range totalpods {
   224  		for _, pv := range totalpvs[i] {
   225  			nodeVolumeMap[pod.Spec.NodeName] = append(nodeVolumeMap[pod.Spec.NodeName], pv.Spec.VsphereVolume.VolumePath)
   226  		}
   227  	}
   228  
   229  	err := waitForVSphereDisksToDetach(ctx, nodeVolumeMap)
   230  	framework.ExpectNoError(err)
   231  
   232  	ginkgo.By("Deleting the PVCs")
   233  	start = time.Now()
   234  	for _, pvclaims := range totalpvclaims {
   235  		for _, pvc := range pvclaims {
   236  			err = e2epv.DeletePersistentVolumeClaim(ctx, client, pvc.Name, namespace)
   237  			framework.ExpectNoError(err)
   238  		}
   239  	}
   240  	elapsed = time.Since(start)
   241  	latency[DeleteOp] = elapsed.Seconds()
   242  
   243  	return latency
   244  }