k8s.io/kubernetes@v1.29.3/test/e2e/storage/testsuites/fsgroupchangepolicy.go (about) 1 /* 2 Copyright 2020 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 testsuites 18 19 import ( 20 "context" 21 "fmt" 22 "strconv" 23 24 "github.com/onsi/ginkgo/v2" 25 v1 "k8s.io/api/core/v1" 26 errors "k8s.io/apimachinery/pkg/util/errors" 27 "k8s.io/kubernetes/test/e2e/framework" 28 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 29 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 30 e2evolume "k8s.io/kubernetes/test/e2e/framework/volume" 31 storageframework "k8s.io/kubernetes/test/e2e/storage/framework" 32 storageutils "k8s.io/kubernetes/test/e2e/storage/utils" 33 admissionapi "k8s.io/pod-security-admission/api" 34 utilpointer "k8s.io/utils/pointer" 35 ) 36 37 const ( 38 rootDir = "/mnt/volume1" 39 rootDirFile = "file1" 40 rootDirFilePath = rootDir + "/" + rootDirFile 41 subdir = "/mnt/volume1/subdir" 42 subDirFile = "file2" 43 subDirFilePath = subdir + "/" + subDirFile 44 ) 45 46 type fsGroupChangePolicyTestSuite struct { 47 tsInfo storageframework.TestSuiteInfo 48 } 49 50 var _ storageframework.TestSuite = &fsGroupChangePolicyTestSuite{} 51 52 // InitCustomFsGroupChangePolicyTestSuite returns fsGroupChangePolicyTestSuite that implements TestSuite interface 53 func InitCustomFsGroupChangePolicyTestSuite(patterns []storageframework.TestPattern) storageframework.TestSuite { 54 return &fsGroupChangePolicyTestSuite{ 55 tsInfo: storageframework.TestSuiteInfo{ 56 Name: "fsgroupchangepolicy", 57 TestPatterns: patterns, 58 SupportedSizeRange: e2evolume.SizeRange{ 59 Min: "1Mi", 60 }, 61 }, 62 } 63 } 64 65 // InitFsGroupChangePolicyTestSuite returns fsGroupChangePolicyTestSuite that implements TestSuite interface 66 func InitFsGroupChangePolicyTestSuite() storageframework.TestSuite { 67 patterns := []storageframework.TestPattern{ 68 storageframework.DefaultFsDynamicPV, 69 } 70 return InitCustomFsGroupChangePolicyTestSuite(patterns) 71 } 72 73 func (s *fsGroupChangePolicyTestSuite) GetTestSuiteInfo() storageframework.TestSuiteInfo { 74 return s.tsInfo 75 } 76 77 func (s *fsGroupChangePolicyTestSuite) SkipUnsupportedTests(driver storageframework.TestDriver, pattern storageframework.TestPattern) { 78 skipVolTypePatterns(pattern, driver, storageframework.NewVolTypeMap(storageframework.CSIInlineVolume, storageframework.GenericEphemeralVolume)) 79 dInfo := driver.GetDriverInfo() 80 if !dInfo.Capabilities[storageframework.CapFsGroup] { 81 e2eskipper.Skipf("Driver %q does not support FsGroup - skipping", dInfo.Name) 82 } 83 84 if pattern.VolMode == v1.PersistentVolumeBlock { 85 e2eskipper.Skipf("Test does not support non-filesystem volume mode - skipping") 86 } 87 88 if pattern.VolType != storageframework.DynamicPV { 89 e2eskipper.Skipf("Suite %q does not support %v", s.tsInfo.Name, pattern.VolType) 90 } 91 92 _, ok := driver.(storageframework.DynamicPVTestDriver) 93 if !ok { 94 e2eskipper.Skipf("Driver %s doesn't support %v -- skipping", dInfo.Name, pattern.VolType) 95 } 96 } 97 98 func (s *fsGroupChangePolicyTestSuite) DefineTests(driver storageframework.TestDriver, pattern storageframework.TestPattern) { 99 type local struct { 100 config *storageframework.PerTestConfig 101 driver storageframework.TestDriver 102 resource *storageframework.VolumeResource 103 } 104 var l local 105 106 // Beware that it also registers an AfterEach which renders f unusable. Any code using 107 // f must run inside an It or Context callback. 108 f := framework.NewFrameworkWithCustomTimeouts("fsgroupchangepolicy", storageframework.GetDriverTimeouts(driver)) 109 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 110 111 init := func(ctx context.Context) { 112 e2eskipper.SkipIfNodeOSDistroIs("windows") 113 l = local{} 114 l.driver = driver 115 l.config = driver.PrepareTest(ctx, f) 116 testVolumeSizeRange := s.GetTestSuiteInfo().SupportedSizeRange 117 l.resource = storageframework.CreateVolumeResource(ctx, l.driver, l.config, pattern, testVolumeSizeRange) 118 } 119 120 cleanup := func(ctx context.Context) { 121 var errs []error 122 if l.resource != nil { 123 if err := l.resource.CleanupResource(ctx); err != nil { 124 errs = append(errs, err) 125 } 126 l.resource = nil 127 } 128 129 framework.ExpectNoError(errors.NewAggregate(errs), "while cleanup resource") 130 } 131 132 tests := []struct { 133 name string // Test case name 134 podfsGroupChangePolicy string // 'Always' or 'OnRootMismatch' 135 initialPodFsGroup int // FsGroup of the initial pod 136 changedRootDirFileOwnership int // Change the ownership of the file in the root directory (/mnt/volume1/file1), as part of the initial pod 137 changedSubDirFileOwnership int // Change the ownership of the file in the sub directory (/mnt/volume1/subdir/file2), as part of the initial pod 138 secondPodFsGroup int // FsGroup of the second pod 139 finalExpectedRootDirFileOwnership int // Final expected ownership of the file in the root directory (/mnt/volume1/file1), as part of the second pod 140 finalExpectedSubDirFileOwnership int // Final expected ownership of the file in the sub directory (/mnt/volume1/subdir/file2), as part of the second pod 141 // Whether the test can run for drivers that support volumeMountGroup capability. 142 // For CSI drivers that support volumeMountGroup: 143 // * OnRootMismatch policy is not supported. 144 // * It may not be possible to chgrp after mounting a volume. 145 supportsVolumeMountGroup bool 146 }{ 147 // Test cases for 'Always' policy 148 { 149 name: "pod created with an initial fsgroup, new pod fsgroup applied to volume contents", 150 podfsGroupChangePolicy: "Always", 151 initialPodFsGroup: 1000, 152 secondPodFsGroup: 2000, 153 finalExpectedRootDirFileOwnership: 2000, 154 finalExpectedSubDirFileOwnership: 2000, 155 supportsVolumeMountGroup: true, 156 }, 157 { 158 name: "pod created with an initial fsgroup, volume contents ownership changed via chgrp in first pod, new pod with same fsgroup applied to the volume contents", 159 podfsGroupChangePolicy: "Always", 160 initialPodFsGroup: 1000, 161 changedRootDirFileOwnership: 2000, 162 changedSubDirFileOwnership: 3000, 163 secondPodFsGroup: 1000, 164 finalExpectedRootDirFileOwnership: 1000, 165 finalExpectedSubDirFileOwnership: 1000, 166 }, 167 { 168 name: "pod created with an initial fsgroup, volume contents ownership changed via chgrp in first pod, new pod with different fsgroup applied to the volume contents", 169 podfsGroupChangePolicy: "Always", 170 initialPodFsGroup: 1000, 171 changedRootDirFileOwnership: 2000, 172 changedSubDirFileOwnership: 3000, 173 secondPodFsGroup: 4000, 174 finalExpectedRootDirFileOwnership: 4000, 175 finalExpectedSubDirFileOwnership: 4000, 176 }, 177 // Test cases for 'OnRootMismatch' policy 178 { 179 name: "pod created with an initial fsgroup, new pod fsgroup applied to volume contents", 180 podfsGroupChangePolicy: "OnRootMismatch", 181 initialPodFsGroup: 1000, 182 secondPodFsGroup: 2000, 183 finalExpectedRootDirFileOwnership: 2000, 184 finalExpectedSubDirFileOwnership: 2000, 185 }, 186 { 187 name: "pod created with an initial fsgroup, volume contents ownership changed via chgrp in first pod, new pod with same fsgroup skips ownership changes to the volume contents", 188 podfsGroupChangePolicy: "OnRootMismatch", 189 initialPodFsGroup: 1000, 190 changedRootDirFileOwnership: 2000, 191 changedSubDirFileOwnership: 3000, 192 secondPodFsGroup: 1000, 193 finalExpectedRootDirFileOwnership: 2000, 194 finalExpectedSubDirFileOwnership: 3000, 195 }, 196 { 197 name: "pod created with an initial fsgroup, volume contents ownership changed via chgrp in first pod, new pod with different fsgroup applied to the volume contents", 198 podfsGroupChangePolicy: "OnRootMismatch", 199 initialPodFsGroup: 1000, 200 changedRootDirFileOwnership: 2000, 201 changedSubDirFileOwnership: 3000, 202 secondPodFsGroup: 4000, 203 finalExpectedRootDirFileOwnership: 4000, 204 finalExpectedSubDirFileOwnership: 4000, 205 }, 206 } 207 208 for _, t := range tests { 209 test := t 210 testCaseName := fmt.Sprintf("(%s)[LinuxOnly], %s", test.podfsGroupChangePolicy, test.name) 211 ginkgo.It(testCaseName, func(ctx context.Context) { 212 dInfo := driver.GetDriverInfo() 213 policy := v1.PodFSGroupChangePolicy(test.podfsGroupChangePolicy) 214 215 if dInfo.Capabilities[storageframework.CapVolumeMountGroup] && 216 !test.supportsVolumeMountGroup { 217 e2eskipper.Skipf("Driver %q supports VolumeMountGroup, which is incompatible with this test - skipping", dInfo.Name) 218 } 219 220 init(ctx) 221 ginkgo.DeferCleanup(cleanup) 222 podConfig := e2epod.Config{ 223 NS: f.Namespace.Name, 224 NodeSelection: l.config.ClientNodeSelection, 225 PVCs: []*v1.PersistentVolumeClaim{l.resource.Pvc}, 226 FsGroup: utilpointer.Int64Ptr(int64(test.initialPodFsGroup)), 227 PodFSGroupChangePolicy: &policy, 228 } 229 // Create initial pod and create files in root and sub-directory and verify ownership. 230 pod := createPodAndVerifyContentGid(ctx, l.config.Framework, &podConfig, true /* createInitialFiles */, "" /* expectedRootDirFileOwnership */, "" /* expectedSubDirFileOwnership */) 231 232 // Change the ownership of files in the initial pod. 233 if test.changedRootDirFileOwnership != 0 { 234 ginkgo.By(fmt.Sprintf("Changing the root directory file ownership to %s", strconv.Itoa(test.changedRootDirFileOwnership))) 235 storageutils.ChangeFilePathGidInPod(f, rootDirFilePath, strconv.Itoa(test.changedRootDirFileOwnership), pod) 236 } 237 238 if test.changedSubDirFileOwnership != 0 { 239 ginkgo.By(fmt.Sprintf("Changing the sub-directory file ownership to %s", strconv.Itoa(test.changedSubDirFileOwnership))) 240 storageutils.ChangeFilePathGidInPod(f, subDirFilePath, strconv.Itoa(test.changedSubDirFileOwnership), pod) 241 } 242 243 ginkgo.By(fmt.Sprintf("Deleting Pod %s/%s", pod.Namespace, pod.Name)) 244 framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, f.ClientSet, pod)) 245 246 // Create a second pod with existing volume and verify the contents ownership. 247 podConfig.FsGroup = utilpointer.Int64Ptr(int64(test.secondPodFsGroup)) 248 pod = createPodAndVerifyContentGid(ctx, l.config.Framework, &podConfig, false /* createInitialFiles */, strconv.Itoa(test.finalExpectedRootDirFileOwnership), strconv.Itoa(test.finalExpectedSubDirFileOwnership)) 249 ginkgo.By(fmt.Sprintf("Deleting Pod %s/%s", pod.Namespace, pod.Name)) 250 framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, f.ClientSet, pod)) 251 }) 252 } 253 } 254 255 func createPodAndVerifyContentGid(ctx context.Context, f *framework.Framework, podConfig *e2epod.Config, createInitialFiles bool, expectedRootDirFileOwnership, expectedSubDirFileOwnership string) *v1.Pod { 256 podFsGroup := strconv.FormatInt(*podConfig.FsGroup, 10) 257 ginkgo.By(fmt.Sprintf("Creating Pod in namespace %s with fsgroup %s", podConfig.NS, podFsGroup)) 258 pod, err := e2epod.CreateSecPodWithNodeSelection(ctx, f.ClientSet, podConfig, f.Timeouts.PodStart) 259 framework.ExpectNoError(err) 260 framework.Logf("Pod %s/%s started successfully", pod.Namespace, pod.Name) 261 262 if createInitialFiles { 263 ginkgo.By(fmt.Sprintf("Creating a sub-directory and file, and verifying their ownership is %s", podFsGroup)) 264 cmd := fmt.Sprintf("touch %s", rootDirFilePath) 265 var err error 266 _, _, err = e2evolume.PodExec(f, pod, cmd) 267 framework.ExpectNoError(err) 268 storageutils.VerifyFilePathGidInPod(f, rootDirFilePath, podFsGroup, pod) 269 270 cmd = fmt.Sprintf("mkdir %s", subdir) 271 _, _, err = e2evolume.PodExec(f, pod, cmd) 272 framework.ExpectNoError(err) 273 cmd = fmt.Sprintf("touch %s", subDirFilePath) 274 _, _, err = e2evolume.PodExec(f, pod, cmd) 275 framework.ExpectNoError(err) 276 storageutils.VerifyFilePathGidInPod(f, subDirFilePath, podFsGroup, pod) 277 return pod 278 } 279 280 // Verify existing contents of the volume 281 ginkgo.By(fmt.Sprintf("Verifying the ownership of root directory file is %s", expectedRootDirFileOwnership)) 282 storageutils.VerifyFilePathGidInPod(f, rootDirFilePath, expectedRootDirFileOwnership, pod) 283 ginkgo.By(fmt.Sprintf("Verifying the ownership of sub directory file is %s", expectedSubDirFileOwnership)) 284 storageutils.VerifyFilePathGidInPod(f, subDirFilePath, expectedSubDirFileOwnership, pod) 285 return pod 286 }