k8s.io/kubernetes@v1.29.3/pkg/kubelet/kubelet_volumes_linux_test.go (about) 1 //go:build linux 2 // +build linux 3 4 /* 5 Copyright 2018 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package kubelet 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 "testing" 27 28 v1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 31 "k8s.io/apimachinery/pkg/types" 32 _ "k8s.io/kubernetes/pkg/apis/core/install" 33 "k8s.io/mount-utils" 34 ) 35 36 func validateDirExists(dir string) error { 37 _, err := os.ReadDir(dir) 38 if err != nil { 39 return err 40 } 41 return nil 42 } 43 44 func validateDirNotExists(dir string) error { 45 _, err := os.ReadDir(dir) 46 if os.IsNotExist(err) { 47 return nil 48 } 49 if err != nil { 50 return err 51 } 52 return fmt.Errorf("dir %q still exists", dir) 53 } 54 55 func TestCleanupOrphanedPodDirs(t *testing.T) { 56 if testing.Short() { 57 t.Skip("skipping test in short mode.") 58 } 59 60 testCases := map[string]struct { 61 pods []*v1.Pod 62 prepareFunc func(kubelet *Kubelet) error 63 validateFunc func(kubelet *Kubelet) error 64 expectErr bool 65 }{ 66 "nothing-to-do": {}, 67 "pods-dir-not-found": { 68 prepareFunc: func(kubelet *Kubelet) error { 69 return os.Remove(kubelet.getPodsDir()) 70 }, 71 expectErr: true, 72 }, 73 "pod-doesnot-exist-novolume": { 74 prepareFunc: func(kubelet *Kubelet) error { 75 podDir := kubelet.getPodDir("pod1uid") 76 return os.MkdirAll(filepath.Join(podDir, "not/a/volume"), 0750) 77 }, 78 validateFunc: func(kubelet *Kubelet) error { 79 podDir := kubelet.getPodDir("pod1uid") 80 return validateDirNotExists(filepath.Join(podDir, "not")) 81 }, 82 }, 83 "pod-exists-with-volume": { 84 pods: []*v1.Pod{ 85 { 86 ObjectMeta: metav1.ObjectMeta{ 87 Name: "pod1", 88 UID: "pod1uid", 89 }, 90 }, 91 }, 92 prepareFunc: func(kubelet *Kubelet) error { 93 podDir := kubelet.getPodDir("pod1uid") 94 return os.MkdirAll(filepath.Join(podDir, "volumes/plugin/name"), 0750) 95 }, 96 validateFunc: func(kubelet *Kubelet) error { 97 podDir := kubelet.getPodDir("pod1uid") 98 return validateDirExists(filepath.Join(podDir, "volumes/plugin/name")) 99 }, 100 }, 101 "pod-doesnot-exist-with-volume": { 102 prepareFunc: func(kubelet *Kubelet) error { 103 podDir := kubelet.getPodDir("pod1uid") 104 return os.MkdirAll(filepath.Join(podDir, "volumes/plugin/name"), 0750) 105 }, 106 validateFunc: func(kubelet *Kubelet) error { 107 podDir := kubelet.getPodDir("pod1uid") 108 return validateDirNotExists(podDir) 109 }, 110 }, 111 "pod-doesnot-exist-with-volume-subdir": { 112 prepareFunc: func(kubelet *Kubelet) error { 113 podDir := kubelet.getPodDir("pod1uid") 114 return os.MkdirAll(filepath.Join(podDir, "volumes/plugin/name/subdir"), 0750) 115 }, 116 validateFunc: func(kubelet *Kubelet) error { 117 podDir := kubelet.getPodDir("pod1uid") 118 return validateDirNotExists(filepath.Join(podDir, "volumes")) 119 }, 120 }, 121 "pod-doesnot-exist-with-subpath": { 122 prepareFunc: func(kubelet *Kubelet) error { 123 podDir := kubelet.getPodDir("pod1uid") 124 return os.MkdirAll(filepath.Join(podDir, "volume-subpaths/volume/container/index"), 0750) 125 }, 126 validateFunc: func(kubelet *Kubelet) error { 127 podDir := kubelet.getPodDir("pod1uid") 128 return validateDirNotExists(podDir) 129 }, 130 }, 131 "pod-doesnot-exist-with-subpath-top": { 132 prepareFunc: func(kubelet *Kubelet) error { 133 podDir := kubelet.getPodDir("pod1uid") 134 return os.MkdirAll(filepath.Join(podDir, "volume-subpaths"), 0750) 135 }, 136 validateFunc: func(kubelet *Kubelet) error { 137 podDir := kubelet.getPodDir("pod1uid") 138 return validateDirNotExists(podDir) 139 }, 140 }, 141 "pod-doesnot-exists-with-populated-volume": { 142 prepareFunc: func(kubelet *Kubelet) error { 143 podDir := kubelet.getPodDir("pod1uid") 144 volumePath := filepath.Join(podDir, "volumes/plugin/name") 145 if err := os.MkdirAll(volumePath, 0750); err != nil { 146 return err 147 } 148 return os.WriteFile(filepath.Join(volumePath, "test.txt"), []byte("test1"), 0640) 149 }, 150 validateFunc: func(kubelet *Kubelet) error { 151 podDir := kubelet.getPodDir("pod1uid") 152 return validateDirExists(filepath.Join(podDir, "volumes/plugin/name")) 153 }, 154 }, 155 "pod-doesnot-exists-with-populated-subpath": { 156 prepareFunc: func(kubelet *Kubelet) error { 157 podDir := kubelet.getPodDir("pod1uid") 158 subPath := filepath.Join(podDir, "volume-subpaths/volume/container/index") 159 if err := os.MkdirAll(subPath, 0750); err != nil { 160 return err 161 } 162 return os.WriteFile(filepath.Join(subPath, "test.txt"), []byte("test1"), 0640) 163 }, 164 validateFunc: func(kubelet *Kubelet) error { 165 podDir := kubelet.getPodDir("pod1uid") 166 return validateDirExists(filepath.Join(podDir, "volume-subpaths/volume/container/index")) 167 }, 168 }, 169 // TODO: test volume in volume-manager 170 } 171 172 for name, tc := range testCases { 173 t.Run(name, func(t *testing.T) { 174 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 175 defer testKubelet.Cleanup() 176 kubelet := testKubelet.kubelet 177 178 if tc.prepareFunc != nil { 179 if err := tc.prepareFunc(kubelet); err != nil { 180 t.Fatalf("%s failed preparation: %v", name, err) 181 } 182 } 183 184 err := kubelet.cleanupOrphanedPodDirs(tc.pods, nil) 185 if tc.expectErr && err == nil { 186 t.Errorf("%s failed: expected error, got success", name) 187 } 188 if !tc.expectErr && err != nil { 189 t.Errorf("%s failed: got error %v", name, err) 190 } 191 192 if tc.validateFunc != nil { 193 if err := tc.validateFunc(kubelet); err != nil { 194 t.Errorf("%s failed validation: %v", name, err) 195 } 196 } 197 198 }) 199 } 200 } 201 202 func TestPodVolumesExistWithMount(t *testing.T) { 203 poduid := types.UID("poduid") 204 testCases := map[string]struct { 205 prepareFunc func(kubelet *Kubelet) error 206 expected bool 207 }{ 208 "noncsivolume-dir-not-exist": { 209 prepareFunc: func(kubelet *Kubelet) error { 210 return nil 211 }, 212 expected: false, 213 }, 214 "noncsivolume-dir-exist-noplugins": { 215 prepareFunc: func(kubelet *Kubelet) error { 216 podDir := kubelet.getPodDir(poduid) 217 return os.MkdirAll(filepath.Join(podDir, "volumes/"), 0750) 218 }, 219 expected: false, 220 }, 221 "noncsivolume-dir-exist-nomount": { 222 prepareFunc: func(kubelet *Kubelet) error { 223 podDir := kubelet.getPodDir(poduid) 224 return os.MkdirAll(filepath.Join(podDir, "volumes/plugin/name"), 0750) 225 }, 226 expected: false, 227 }, 228 "noncsivolume-dir-exist-with-mount": { 229 prepareFunc: func(kubelet *Kubelet) error { 230 podDir := kubelet.getPodDir(poduid) 231 volumePath := filepath.Join(podDir, "volumes/plugin/name") 232 if err := os.MkdirAll(volumePath, 0750); err != nil { 233 return err 234 } 235 fm := mount.NewFakeMounter( 236 []mount.MountPoint{ 237 {Device: "/dev/sdb", Path: volumePath}, 238 }) 239 kubelet.mounter = fm 240 return nil 241 }, 242 expected: true, 243 }, 244 "noncsivolume-dir-exist-nomount-withcsimountpath": { 245 prepareFunc: func(kubelet *Kubelet) error { 246 podDir := kubelet.getPodDir(poduid) 247 volumePath := filepath.Join(podDir, "volumes/plugin/name/mount") 248 if err := os.MkdirAll(volumePath, 0750); err != nil { 249 return err 250 } 251 fm := mount.NewFakeMounter( 252 []mount.MountPoint{ 253 {Device: "/dev/sdb", Path: volumePath}, 254 }) 255 kubelet.mounter = fm 256 return nil 257 }, 258 expected: false, 259 }, 260 "csivolume-dir-exist-nomount": { 261 prepareFunc: func(kubelet *Kubelet) error { 262 podDir := kubelet.getPodDir(poduid) 263 volumePath := filepath.Join(podDir, "volumes/kubernetes.io~csi/name") 264 return os.MkdirAll(volumePath, 0750) 265 }, 266 expected: false, 267 }, 268 "csivolume-dir-exist-mount-nocsimountpath": { 269 prepareFunc: func(kubelet *Kubelet) error { 270 podDir := kubelet.getPodDir(poduid) 271 volumePath := filepath.Join(podDir, "volumes/kubernetes.io~csi/name/mount") 272 return os.MkdirAll(volumePath, 0750) 273 }, 274 expected: false, 275 }, 276 "csivolume-dir-exist-withcsimountpath": { 277 prepareFunc: func(kubelet *Kubelet) error { 278 podDir := kubelet.getPodDir(poduid) 279 volumePath := filepath.Join(podDir, "volumes/kubernetes.io~csi/name/mount") 280 if err := os.MkdirAll(volumePath, 0750); err != nil { 281 return err 282 } 283 fm := mount.NewFakeMounter( 284 []mount.MountPoint{ 285 {Device: "/dev/sdb", Path: volumePath}, 286 }) 287 kubelet.mounter = fm 288 return nil 289 }, 290 expected: true, 291 }, 292 } 293 294 for name, tc := range testCases { 295 t.Run(name, func(t *testing.T) { 296 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 297 defer testKubelet.Cleanup() 298 kubelet := testKubelet.kubelet 299 300 if tc.prepareFunc != nil { 301 if err := tc.prepareFunc(kubelet); err != nil { 302 t.Fatalf("%s failed preparation: %v", name, err) 303 } 304 } 305 306 exist := kubelet.podVolumesExist(poduid) 307 if tc.expected != exist { 308 t.Errorf("%s failed: expected %t, got %t", name, tc.expected, exist) 309 } 310 }) 311 } 312 }