volumes.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /*
  2. Copyright 2018 The Rook Authors. All rights reserved.
  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 test
  14. import (
  15. "fmt"
  16. "testing"
  17. "github.com/stretchr/testify/assert"
  18. v1 "k8s.io/api/core/v1"
  19. )
  20. /*
  21. * Simple Volume and VolumeMount Tests
  22. */
  23. // VolumeExists returns a descriptive error if the volume does not exist.
  24. func VolumeExists(volumeName string, volumes []v1.Volume) error {
  25. _, err := getVolume(volumeName, volumes)
  26. return err
  27. }
  28. // VolumeIsEmptyDir returns a descriptive error if the volume does not exist or is not an empty dir
  29. func VolumeIsEmptyDir(volumeName string, volumes []v1.Volume) error {
  30. volume, err := getVolume(volumeName, volumes)
  31. if err == nil && volume.VolumeSource.EmptyDir == nil {
  32. return fmt.Errorf("volume %s is not EmptyDir: %s", volumeName, HumanReadableVolume(volume))
  33. }
  34. if err == nil && volume.VolumeSource.HostPath != nil {
  35. return fmt.Errorf("volume %s is both EmptyDir and HostPath: %s", volumeName, HumanReadableVolume(volume))
  36. }
  37. return err
  38. }
  39. // VolumeIsHostPath returns a descriptive error if the volume does not exist, is not a HostPath
  40. // volume, or if the volume's path is not as expected.
  41. func VolumeIsHostPath(volumeName, path string, volumes []v1.Volume) error {
  42. volume, err := getVolume(volumeName, volumes)
  43. if err == nil && volume.VolumeSource.HostPath == nil {
  44. return fmt.Errorf("volume %s is not HostPath: %s", volumeName, HumanReadableVolume(volume))
  45. }
  46. if err == nil && volume.VolumeSource.HostPath.Path != path {
  47. return fmt.Errorf("volume %s is HostPath but has wrong path: %s", volumeName, HumanReadableVolume(volume))
  48. }
  49. if err == nil && volume.VolumeSource.EmptyDir != nil {
  50. return fmt.Errorf("volume %s is both HostPath and EmptyDir: %s", volumeName, HumanReadableVolume(volume))
  51. }
  52. return err
  53. }
  54. // VolumeMountExists returns a descriptive error if the volume mount does not exist.
  55. func VolumeMountExists(mountName string, mounts []v1.VolumeMount) error {
  56. _, err := getMount(mountName, mounts)
  57. return err
  58. }
  59. /*
  60. * Human-readable representations of Volumes and VolumeMounts
  61. */
  62. // HumanReadableVolumes returns a string representation of a list of Kubernetes volumes which is
  63. // more compact and human readable than the default string go prints.
  64. func HumanReadableVolumes(volumes []v1.Volume) string {
  65. stringVols := []string{}
  66. for _, volume := range volumes {
  67. localvolume := volume
  68. stringVols = append(stringVols, HumanReadableVolume(&localvolume))
  69. }
  70. return fmt.Sprintf("%v", stringVols)
  71. }
  72. // HumanReadableVolume returns a string representation of a Kubernetes volume which is more compact
  73. // and human readable than the default string go prints.
  74. func HumanReadableVolume(v *v1.Volume) string {
  75. var sourceString string
  76. if v.VolumeSource.EmptyDir != nil {
  77. sourceString = "EmptyDir"
  78. } else if v.VolumeSource.HostPath != nil {
  79. sourceString = v.VolumeSource.HostPath.Path
  80. } else {
  81. // If can't convert the vol source to something human readable, just output something useful
  82. sourceString = fmt.Sprintf("%v", v.VolumeSource)
  83. }
  84. return fmt.Sprintf("{%s : %s}", v.Name, sourceString)
  85. }
  86. // HumanReadableVolumeMounts returns a string representation of a list of Kubernetes volume mounts which
  87. // is more compact and human readable than the default string go prints.
  88. func HumanReadableVolumeMounts(mounts []v1.VolumeMount) string {
  89. stringMounts := []string{}
  90. for _, mount := range mounts {
  91. localmount := mount
  92. stringMounts = append(stringMounts, HumanReadableVolumeMount(&localmount))
  93. }
  94. return fmt.Sprintf("%v", stringMounts)
  95. }
  96. // HumanReadableVolumeMount returns a string representation of a Kubernetes volume mount which is more
  97. // compact and human readable than the default string go prints.
  98. func HumanReadableVolumeMount(m *v1.VolumeMount) string {
  99. return fmt.Sprintf("{%s : %s}", m.Name, m.MountPath)
  100. }
  101. /*
  102. * Fully-implemented test for matching Volumes and VolumeMounts
  103. */
  104. // VolumesSpec is a struct which includes a list of Kubernetes volumes as well as additional
  105. // metadata about the volume list for better identification during tests.
  106. type VolumesSpec struct {
  107. // Moniker is a name given to the list to help identify it
  108. Moniker string
  109. Volumes []v1.Volume
  110. }
  111. // MountsSpec is a struct which includes a list of Kubernetes volume mounts as well as additional
  112. // metadata about the volume mount list for better identification during tests.
  113. type MountsSpec struct {
  114. // Moniker is a name given to the list to help identify it
  115. Moniker string
  116. Mounts []v1.VolumeMount
  117. }
  118. // VolumesAndMountsTestDefinition defines which volumes and mounts to test and what those values
  119. // should be. The test is intended to be defined with VolumesSpec defined as a pod's volumes, and
  120. // the list of MountsSpec items defined as the volume mounts from every container in the pod.
  121. type VolumesAndMountsTestDefinition struct {
  122. VolumesSpec *VolumesSpec
  123. MountsSpecItems []*MountsSpec
  124. }
  125. // TestMountsMatchVolumes tests two things:
  126. // 1. That each volume mount in each every MountsSpec has a corresponding volume to source it in the VolumesSpec
  127. // 2. That there are no extraneous volumes defined in the VolumesSpec that do not have a
  128. // corresponding volume mount in any of the MountsSpec items
  129. func (d *VolumesAndMountsTestDefinition) TestMountsMatchVolumes(t *testing.T) {
  130. // Run a test for each MountsSpec item to verify that all the volume mounts within each item
  131. // have a corresponding volume in VolumesSpec to source it
  132. for _, mountsSpec := range d.MountsSpecItems {
  133. t.Run(
  134. fmt.Sprintf("%s mounts match volumes in %s", mountsSpec.Moniker, d.VolumesSpec.Moniker),
  135. func(t *testing.T) {
  136. for _, mount := range mountsSpec.Mounts {
  137. localmount := mount
  138. assert.Nil(t, VolumeExists(mount.Name, d.VolumesSpec.Volumes),
  139. "%s volume mount %s does not have a corresponding %s volume to source it: %s",
  140. mountsSpec.Moniker, HumanReadableVolumeMount(&localmount),
  141. d.VolumesSpec.Moniker, HumanReadableVolumes(d.VolumesSpec.Volumes))
  142. }
  143. },
  144. )
  145. }
  146. // Test that each volume in VolumesSpec has a usage in at least one of the volume mounts in at
  147. // least one of the MountsSpecs items
  148. t.Run(
  149. fmt.Sprintf("No extraneous %s volumes exist", d.VolumesSpec.Moniker),
  150. func(t *testing.T) {
  151. for _, volume := range d.VolumesSpec.Volumes {
  152. localvolume := volume
  153. assert.Nil(t, mountExistsInMountsSpecItems(volume.Name, d.MountsSpecItems),
  154. "%s volume %s is not used by any volume mount: %v",
  155. d.VolumesSpec.Moniker, HumanReadableVolume(&localvolume),
  156. humanReadableMountsSpecItems(d.MountsSpecItems))
  157. }
  158. },
  159. )
  160. }
  161. /*
  162. * Helpers
  163. */
  164. func mountExistsInMountsSpecItems(name string, mountsSpecItems []*MountsSpec) error {
  165. for _, mountsSpec := range mountsSpecItems {
  166. if err := VolumeMountExists(name, mountsSpec.Mounts); err == nil {
  167. return nil // successfully found that the mount exists at least once
  168. }
  169. }
  170. return fmt.Errorf("") // not in any mounts; calling func will output a better error
  171. }
  172. func humanReadableMountsSpecItems(mountsSpecItems []*MountsSpec) (humanReadable string) {
  173. for _, mountsSpec := range mountsSpecItems {
  174. humanReadable = fmt.Sprintf("%s%s - %s\n", humanReadable,
  175. mountsSpec.Moniker, HumanReadableVolumeMounts(mountsSpec.Mounts),
  176. )
  177. }
  178. return
  179. }
  180. const (
  181. volNotFound = "Volume Was Not Found by getVolume()"
  182. mntNotFound = "VolumeMount Was Not Found by getMount()"
  183. )
  184. func getVolume(volumeName string, volumes []v1.Volume) (*v1.Volume, error) {
  185. for _, volume := range volumes {
  186. if volume.Name == volumeName {
  187. return &volume, nil
  188. }
  189. }
  190. return &v1.Volume{Name: volNotFound},
  191. fmt.Errorf("volume %s does not exist in %s", volumeName, HumanReadableVolumes(volumes))
  192. }
  193. func getMount(mountName string, mounts []v1.VolumeMount) (*v1.VolumeMount, error) {
  194. for _, mount := range mounts {
  195. if mount.Name == mountName {
  196. return &mount, nil
  197. }
  198. }
  199. return &v1.VolumeMount{Name: mntNotFound},
  200. fmt.Errorf("volume mount %s does not exist in %s", mountName, HumanReadableVolumeMounts(mounts))
  201. }