vsphere_volume_perf.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package vsphere
  14. import (
  15. "context"
  16. "fmt"
  17. "time"
  18. "github.com/onsi/ginkgo"
  19. "github.com/onsi/gomega"
  20. v1 "k8s.io/api/core/v1"
  21. storagev1 "k8s.io/api/storage/v1"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. clientset "k8s.io/client-go/kubernetes"
  24. "k8s.io/kubernetes/test/e2e/framework"
  25. e2enode "k8s.io/kubernetes/test/e2e/framework/node"
  26. e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
  27. e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
  28. e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
  29. "k8s.io/kubernetes/test/e2e/storage/utils"
  30. )
  31. /* This test calculates latency numbers for volume lifecycle operations
  32. 1. Create 4 type of storage classes
  33. 2. Read the total number of volumes to be created and volumes per pod
  34. 3. Create total PVCs (number of volumes)
  35. 4. Create Pods with attached volumes per pod
  36. 5. Verify access to the volumes
  37. 6. Delete pods and wait for volumes to detach
  38. 7. Delete the PVCs
  39. */
  40. const (
  41. SCSIUnitsAvailablePerNode = 55
  42. CreateOp = "CreateOp"
  43. AttachOp = "AttachOp"
  44. DetachOp = "DetachOp"
  45. DeleteOp = "DeleteOp"
  46. )
  47. var _ = utils.SIGDescribe("vcp-performance [Feature:vsphere]", func() {
  48. f := framework.NewDefaultFramework("vcp-performance")
  49. var (
  50. client clientset.Interface
  51. namespace string
  52. nodeSelectorList []*NodeSelector
  53. policyName string
  54. datastoreName string
  55. volumeCount int
  56. volumesPerPod int
  57. iterations int
  58. )
  59. ginkgo.BeforeEach(func() {
  60. e2eskipper.SkipUnlessProviderIs("vsphere")
  61. Bootstrap(f)
  62. client = f.ClientSet
  63. namespace = f.Namespace.Name
  64. // Read the environment variables
  65. volumeCount = GetAndExpectIntEnvVar(VCPPerfVolumeCount)
  66. volumesPerPod = GetAndExpectIntEnvVar(VCPPerfVolumesPerPod)
  67. iterations = GetAndExpectIntEnvVar(VCPPerfIterations)
  68. policyName = GetAndExpectStringEnvVar(SPBMPolicyName)
  69. datastoreName = GetAndExpectStringEnvVar(StorageClassDatastoreName)
  70. nodes, err := e2enode.GetReadySchedulableNodes(client)
  71. framework.ExpectNoError(err)
  72. gomega.Expect(len(nodes.Items)).To(gomega.BeNumerically(">=", 1), "Requires at least %d nodes (not %d)", 2, len(nodes.Items))
  73. 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))
  74. gomega.Expect(volumeCount).To(gomega.BeNumerically("<=", SCSIUnitsAvailablePerNode*len(nodes.Items)), msg)
  75. msg = fmt.Sprintf("Cannot attach %d volumes per pod. Maximum volumes that can be attached per pod is %d", volumesPerPod, SCSIUnitsAvailablePerNode)
  76. gomega.Expect(volumesPerPod).To(gomega.BeNumerically("<=", SCSIUnitsAvailablePerNode), msg)
  77. nodeSelectorList = createNodeLabels(client, namespace, nodes)
  78. })
  79. ginkgo.It("vcp performance tests", func() {
  80. scList := getTestStorageClasses(client, policyName, datastoreName)
  81. defer func(scList []*storagev1.StorageClass) {
  82. for _, sc := range scList {
  83. client.StorageV1().StorageClasses().Delete(context.TODO(), sc.Name, nil)
  84. }
  85. }(scList)
  86. sumLatency := make(map[string]float64)
  87. for i := 0; i < iterations; i++ {
  88. latency := invokeVolumeLifeCyclePerformance(f, client, namespace, scList, volumesPerPod, volumeCount, nodeSelectorList)
  89. for key, val := range latency {
  90. sumLatency[key] += val
  91. }
  92. }
  93. iterations64 := float64(iterations)
  94. framework.Logf("Average latency for below operations")
  95. framework.Logf("Creating %d PVCs and waiting for bound phase: %v seconds", volumeCount, sumLatency[CreateOp]/iterations64)
  96. framework.Logf("Creating %v Pod: %v seconds", volumeCount/volumesPerPod, sumLatency[AttachOp]/iterations64)
  97. framework.Logf("Deleting %v Pod and waiting for disk to be detached: %v seconds", volumeCount/volumesPerPod, sumLatency[DetachOp]/iterations64)
  98. framework.Logf("Deleting %v PVCs: %v seconds", volumeCount, sumLatency[DeleteOp]/iterations64)
  99. })
  100. })
  101. func getTestStorageClasses(client clientset.Interface, policyName, datastoreName string) []*storagev1.StorageClass {
  102. const (
  103. storageclass1 = "sc-default"
  104. storageclass2 = "sc-vsan"
  105. storageclass3 = "sc-spbm"
  106. storageclass4 = "sc-user-specified-ds"
  107. )
  108. scNames := []string{storageclass1, storageclass2, storageclass3, storageclass4}
  109. scArrays := make([]*storagev1.StorageClass, len(scNames))
  110. for index, scname := range scNames {
  111. // Create vSphere Storage Class
  112. ginkgo.By(fmt.Sprintf("Creating Storage Class : %v", scname))
  113. var sc *storagev1.StorageClass
  114. var err error
  115. switch scname {
  116. case storageclass1:
  117. sc, err = client.StorageV1().StorageClasses().Create(context.TODO(), getVSphereStorageClassSpec(storageclass1, nil, nil, ""), metav1.CreateOptions{})
  118. case storageclass2:
  119. var scVSanParameters map[string]string
  120. scVSanParameters = make(map[string]string)
  121. scVSanParameters[PolicyHostFailuresToTolerate] = "1"
  122. sc, err = client.StorageV1().StorageClasses().Create(context.TODO(), getVSphereStorageClassSpec(storageclass2, scVSanParameters, nil, ""), metav1.CreateOptions{})
  123. case storageclass3:
  124. var scSPBMPolicyParameters map[string]string
  125. scSPBMPolicyParameters = make(map[string]string)
  126. scSPBMPolicyParameters[SpbmStoragePolicy] = policyName
  127. sc, err = client.StorageV1().StorageClasses().Create(context.TODO(), getVSphereStorageClassSpec(storageclass3, scSPBMPolicyParameters, nil, ""), metav1.CreateOptions{})
  128. case storageclass4:
  129. var scWithDSParameters map[string]string
  130. scWithDSParameters = make(map[string]string)
  131. scWithDSParameters[Datastore] = datastoreName
  132. scWithDatastoreSpec := getVSphereStorageClassSpec(storageclass4, scWithDSParameters, nil, "")
  133. sc, err = client.StorageV1().StorageClasses().Create(context.TODO(), scWithDatastoreSpec, metav1.CreateOptions{})
  134. }
  135. gomega.Expect(sc).NotTo(gomega.BeNil())
  136. framework.ExpectNoError(err)
  137. scArrays[index] = sc
  138. }
  139. return scArrays
  140. }
  141. // invokeVolumeLifeCyclePerformance peforms full volume life cycle management and records latency for each operation
  142. func invokeVolumeLifeCyclePerformance(f *framework.Framework, client clientset.Interface, namespace string, sc []*storagev1.StorageClass, volumesPerPod int, volumeCount int, nodeSelectorList []*NodeSelector) (latency map[string]float64) {
  143. var (
  144. totalpvclaims [][]*v1.PersistentVolumeClaim
  145. totalpvs [][]*v1.PersistentVolume
  146. totalpods []*v1.Pod
  147. )
  148. nodeVolumeMap := make(map[string][]string)
  149. latency = make(map[string]float64)
  150. numPods := volumeCount / volumesPerPod
  151. ginkgo.By(fmt.Sprintf("Creating %d PVCs", volumeCount))
  152. start := time.Now()
  153. for i := 0; i < numPods; i++ {
  154. var pvclaims []*v1.PersistentVolumeClaim
  155. for j := 0; j < volumesPerPod; j++ {
  156. currsc := sc[((i*numPods)+j)%len(sc)]
  157. pvclaim, err := e2epv.CreatePVC(client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", currsc))
  158. framework.ExpectNoError(err)
  159. pvclaims = append(pvclaims, pvclaim)
  160. }
  161. totalpvclaims = append(totalpvclaims, pvclaims)
  162. }
  163. for _, pvclaims := range totalpvclaims {
  164. persistentvolumes, err := e2epv.WaitForPVClaimBoundPhase(client, pvclaims, framework.ClaimProvisionTimeout)
  165. framework.ExpectNoError(err)
  166. totalpvs = append(totalpvs, persistentvolumes)
  167. }
  168. elapsed := time.Since(start)
  169. latency[CreateOp] = elapsed.Seconds()
  170. ginkgo.By("Creating pod to attach PVs to the node")
  171. start = time.Now()
  172. for i, pvclaims := range totalpvclaims {
  173. nodeSelector := nodeSelectorList[i%len(nodeSelectorList)]
  174. pod, err := e2epod.CreatePod(client, namespace, map[string]string{nodeSelector.labelKey: nodeSelector.labelValue}, pvclaims, false, "")
  175. framework.ExpectNoError(err)
  176. totalpods = append(totalpods, pod)
  177. defer e2epod.DeletePodWithWait(client, pod)
  178. }
  179. elapsed = time.Since(start)
  180. latency[AttachOp] = elapsed.Seconds()
  181. for i, pod := range totalpods {
  182. verifyVSphereVolumesAccessible(client, pod, totalpvs[i])
  183. }
  184. ginkgo.By("Deleting pods")
  185. start = time.Now()
  186. for _, pod := range totalpods {
  187. err := e2epod.DeletePodWithWait(client, pod)
  188. framework.ExpectNoError(err)
  189. }
  190. elapsed = time.Since(start)
  191. latency[DetachOp] = elapsed.Seconds()
  192. for i, pod := range totalpods {
  193. for _, pv := range totalpvs[i] {
  194. nodeVolumeMap[pod.Spec.NodeName] = append(nodeVolumeMap[pod.Spec.NodeName], pv.Spec.VsphereVolume.VolumePath)
  195. }
  196. }
  197. err := waitForVSphereDisksToDetach(nodeVolumeMap)
  198. framework.ExpectNoError(err)
  199. ginkgo.By("Deleting the PVCs")
  200. start = time.Now()
  201. for _, pvclaims := range totalpvclaims {
  202. for _, pvc := range pvclaims {
  203. err = e2epv.DeletePersistentVolumeClaim(client, pvc.Name, namespace)
  204. framework.ExpectNoError(err)
  205. }
  206. }
  207. elapsed = time.Since(start)
  208. latency[DeleteOp] = elapsed.Seconds()
  209. return latency
  210. }