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 }