k8s.io/kubernetes@v1.29.3/pkg/kubelet/userns/userns_manager_test.go (about) 1 /* 2 Copyright 2022 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 userns 18 19 import ( 20 "fmt" 21 "testing" 22 23 "github.com/stretchr/testify/assert" 24 "github.com/stretchr/testify/require" 25 v1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/types" 28 utilfeature "k8s.io/apiserver/pkg/util/feature" 29 featuregatetesting "k8s.io/component-base/featuregate/testing" 30 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 31 pkgfeatures "k8s.io/kubernetes/pkg/features" 32 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 33 ) 34 35 type testUserNsPodsManager struct { 36 podDir string 37 podList []types.UID 38 } 39 40 func (m *testUserNsPodsManager) GetPodDir(podUID types.UID) string { 41 if m.podDir == "" { 42 return "/tmp/non-existant-dir.This-is-not-used-in-tests" 43 } 44 return m.podDir 45 } 46 47 func (m *testUserNsPodsManager) ListPodsFromDisk() ([]types.UID, error) { 48 if len(m.podList) == 0 { 49 return nil, nil 50 } 51 return m.podList, nil 52 } 53 54 func TestUserNsManagerAllocate(t *testing.T) { 55 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)() 56 57 testUserNsPodsManager := &testUserNsPodsManager{} 58 m, err := MakeUserNsManager(testUserNsPodsManager) 59 require.NoError(t, err) 60 61 assert.Equal(t, true, m.isSet(0*65536), "m.isSet(0) should be true") 62 63 allocated, length, err := m.allocateOne("one") 64 assert.NoError(t, err) 65 assert.Equal(t, userNsLength, int(length), "m.isSet(%d).length=%v", allocated, length) 66 assert.Equal(t, true, m.isSet(allocated), "m.isSet(%d)", allocated) 67 68 allocated2, length2, err := m.allocateOne("two") 69 assert.NoError(t, err) 70 assert.NotEqual(t, allocated, allocated2, "allocated != allocated2") 71 assert.Equal(t, length, length2, "length == length2") 72 73 // verify that re-adding the same pod with the same settings won't fail 74 err = m.record("two", allocated2, length2) 75 assert.NoError(t, err) 76 // but it fails if anyting is different 77 err = m.record("two", allocated2+1, length2) 78 assert.Error(t, err) 79 80 m.Release("one") 81 m.Release("two") 82 assert.Equal(t, false, m.isSet(allocated), "m.isSet(%d)", allocated) 83 assert.Equal(t, false, m.isSet(allocated2), "m.nsSet(%d)", allocated2) 84 85 var allocs []uint32 86 for i := 0; i < 1000; i++ { 87 allocated, length, err = m.allocateOne(types.UID(fmt.Sprintf("%d", i))) 88 assert.Equal(t, userNsLength, int(length), "length is not the expected. iter: %v", i) 89 assert.NoError(t, err) 90 allocs = append(allocs, allocated) 91 } 92 for i, v := range allocs { 93 assert.Equal(t, true, m.isSet(v), "m.isSet(%d) should be true", v) 94 m.Release(types.UID(fmt.Sprintf("%d", i))) 95 assert.Equal(t, false, m.isSet(v), "m.isSet(%d) should be false", v) 96 97 err = m.record(types.UID(fmt.Sprintf("%d", i)), v, userNsLength) 98 assert.NoError(t, err) 99 m.Release(types.UID(fmt.Sprintf("%d", i))) 100 assert.Equal(t, false, m.isSet(v), "m.isSet(%d) should be false", v) 101 } 102 } 103 104 func TestUserNsManagerParseUserNsFile(t *testing.T) { 105 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)() 106 107 cases := []struct { 108 name string 109 file string 110 success bool 111 }{ 112 { 113 name: "basic", 114 file: `{ 115 "uidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ], 116 "gidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ] 117 }`, 118 success: true, 119 }, 120 { 121 name: "invalid length", 122 file: `{ 123 "uidMappings":[ { "hostId":131072, "containerId":0, "length":0 } ], 124 "gidMappings":[ { "hostId":131072, "containerId":0, "length":0 } ] 125 }`, 126 success: false, 127 }, 128 { 129 name: "wrong offset", 130 file: `{ 131 "uidMappings":[ {"hostId":131072, "containerId":0, "length":65536 } ], 132 "gidMappings":[ {"hostId":1, "containerId":0, "length":65536 } ] 133 }`, 134 success: false, 135 }, 136 { 137 name: "two GID mappings", 138 file: `{ 139 "uidMappings":[ { "hostId":131072, "containerId":0, "length":userNsLength } ], 140 "gidMappings":[ { "hostId":131072, "containerId":0, "length":userNsLength }, { "hostId":196608, "containerId":0, "length":65536 } ] 141 }`, 142 success: false, 143 }, 144 { 145 name: "two UID mappings", 146 file: `{ 147 "uidMappings":[ { "hostId":131072, "containerId":0, "length":65536 }, { "hostId":196608, "containerId":0, "length":65536 } ], 148 "gidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ] 149 }`, 150 success: false, 151 }, 152 { 153 name: "no root UID", 154 file: `{ 155 "uidMappings":[ { "hostId":131072, "containerId":1, "length":65536 } ], 156 "gidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ] 157 }`, 158 success: false, 159 }, 160 { 161 name: "no root GID", 162 file: `{ 163 "uidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ], 164 "gidMappings":[ { "hostId":131072, "containerId":1, "length":65536 } ] 165 }`, 166 success: false, 167 }, 168 } 169 170 testUserNsPodsManager := &testUserNsPodsManager{} 171 m, err := MakeUserNsManager(testUserNsPodsManager) 172 assert.NoError(t, err) 173 174 for _, tc := range cases { 175 t.Run(tc.name, func(t *testing.T) { 176 // We don't validate the result. It was parsed with the json parser, we trust that. 177 _, err = m.parseUserNsFileAndRecord(types.UID(tc.name), []byte(tc.file)) 178 if (tc.success && err == nil) || (!tc.success && err != nil) { 179 return 180 } 181 182 t.Errorf("expected success: %v but got error: %v", tc.success, err) 183 }) 184 } 185 } 186 187 func TestGetOrCreateUserNamespaceMappings(t *testing.T) { 188 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)() 189 190 trueVal := true 191 falseVal := false 192 193 cases := []struct { 194 name string 195 pod *v1.Pod 196 expMode runtimeapi.NamespaceMode 197 success bool 198 }{ 199 { 200 name: "no user namespace", 201 pod: &v1.Pod{}, 202 expMode: runtimeapi.NamespaceMode_NODE, 203 success: true, 204 }, 205 { 206 name: "opt-in to host user namespace", 207 pod: &v1.Pod{ 208 Spec: v1.PodSpec{ 209 HostUsers: &trueVal, 210 }, 211 }, 212 expMode: runtimeapi.NamespaceMode_NODE, 213 success: true, 214 }, 215 { 216 name: "user namespace", 217 pod: &v1.Pod{ 218 Spec: v1.PodSpec{ 219 HostUsers: &falseVal, 220 }, 221 }, 222 expMode: runtimeapi.NamespaceMode_POD, 223 success: true, 224 }, 225 } 226 227 for _, tc := range cases { 228 t.Run(tc.name, func(t *testing.T) { 229 // These tests will create the userns file, so use an existing podDir. 230 testUserNsPodsManager := &testUserNsPodsManager{podDir: t.TempDir()} 231 m, err := MakeUserNsManager(testUserNsPodsManager) 232 assert.NoError(t, err) 233 234 userns, err := m.GetOrCreateUserNamespaceMappings(tc.pod) 235 if (tc.success && err != nil) || (!tc.success && err == nil) { 236 t.Errorf("expected success: %v but got error: %v", tc.success, err) 237 } 238 239 if userns.GetMode() != tc.expMode { 240 t.Errorf("expected mode: %v but got: %v", tc.expMode, userns.GetMode()) 241 } 242 }) 243 } 244 } 245 246 func TestCleanupOrphanedPodUsernsAllocations(t *testing.T) { 247 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)() 248 249 cases := []struct { 250 name string 251 runningPods []*kubecontainer.Pod 252 pods []*v1.Pod 253 listPods []types.UID /* pods to list */ 254 podSetBeforeCleanup []types.UID /* pods to record before cleanup */ 255 podSetAfterCleanup []types.UID /* pods set expected after cleanup */ 256 podUnsetAfterCleanup []types.UID /* pods set expected after cleanup */ 257 }{ 258 { 259 name: "no stale pods", 260 listPods: []types.UID{"pod-1", "pod-2"}, 261 }, 262 { 263 name: "no stale pods set", 264 podSetBeforeCleanup: []types.UID{"pod-1", "pod-2"}, 265 listPods: []types.UID{"pod-1", "pod-2"}, 266 podUnsetAfterCleanup: []types.UID{"pod-1", "pod-2"}, 267 }, 268 { 269 name: "one running pod", 270 listPods: []types.UID{"pod-1", "pod-2"}, 271 podSetBeforeCleanup: []types.UID{"pod-1", "pod-2"}, 272 runningPods: []*kubecontainer.Pod{{ID: "pod-1"}}, 273 podSetAfterCleanup: []types.UID{"pod-1"}, 274 podUnsetAfterCleanup: []types.UID{"pod-2"}, 275 }, 276 { 277 name: "pod set before cleanup but not listed ==> unset", 278 podSetBeforeCleanup: []types.UID{"pod-1", "pod-2"}, 279 runningPods: []*kubecontainer.Pod{{ID: "pod-1"}}, 280 podUnsetAfterCleanup: []types.UID{"pod-1", "pod-2"}, 281 }, 282 { 283 name: "one pod", 284 listPods: []types.UID{"pod-1", "pod-2"}, 285 podSetBeforeCleanup: []types.UID{"pod-1", "pod-2"}, 286 pods: []*v1.Pod{{ObjectMeta: metav1.ObjectMeta{UID: "pod-1"}}}, 287 podSetAfterCleanup: []types.UID{"pod-1"}, 288 podUnsetAfterCleanup: []types.UID{"pod-2"}, 289 }, 290 { 291 name: "no listed pods ==> all unset", 292 podSetBeforeCleanup: []types.UID{"pod-1", "pod-2"}, 293 podUnsetAfterCleanup: []types.UID{"pod-1", "pod-2"}, 294 }, 295 } 296 297 for _, tc := range cases { 298 t.Run(tc.name, func(t *testing.T) { 299 testUserNsPodsManager := &testUserNsPodsManager{ 300 podList: tc.listPods, 301 } 302 m, err := MakeUserNsManager(testUserNsPodsManager) 303 require.NoError(t, err) 304 305 // Record the userns range as used 306 for i, pod := range tc.podSetBeforeCleanup { 307 err := m.record(pod, uint32((i+1)*65536), 65536) 308 require.NoError(t, err) 309 } 310 311 err = m.CleanupOrphanedPodUsernsAllocations(tc.pods, tc.runningPods) 312 require.NoError(t, err) 313 314 for _, pod := range tc.podSetAfterCleanup { 315 ok := m.podAllocated(pod) 316 assert.True(t, ok, "pod %q should be allocated", pod) 317 } 318 319 for _, pod := range tc.podUnsetAfterCleanup { 320 ok := m.podAllocated(pod) 321 assert.False(t, ok, "pod %q should not be allocated", pod) 322 } 323 }) 324 } 325 } 326 327 func TestAllocateMaxPods(t *testing.T) { 328 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)() 329 330 testUserNsPodsManager := &testUserNsPodsManager{} 331 m, err := MakeUserNsManager(testUserNsPodsManager) 332 require.NoError(t, err) 333 334 // The first maxPods allocations should succeed. 335 for i := 0; i < maxPods; i++ { 336 _, _, err = m.allocateOne(types.UID(fmt.Sprintf("%d", i))) 337 require.NoError(t, err) 338 } 339 340 // The next allocation should fail, hitting maxPods. 341 _, _, err = m.allocateOne(types.UID(fmt.Sprintf("%d", maxPods+1))) 342 assert.Error(t, err) 343 } 344 345 func TestRecordMaxPods(t *testing.T) { 346 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)() 347 348 testUserNsPodsManager := &testUserNsPodsManager{} 349 m, err := MakeUserNsManager(testUserNsPodsManager) 350 require.NoError(t, err) 351 352 // The first maxPods allocations should succeed. 353 for i := 0; i < maxPods; i++ { 354 err = m.record(types.UID(fmt.Sprintf("%d", i)), uint32((i+1)*65536), 65536) 355 require.NoError(t, err) 356 } 357 358 // The next allocation should fail, hitting maxPods. 359 err = m.record(types.UID(fmt.Sprintf("%d", maxPods+1)), uint32((maxPods+1)*65536), 65536) 360 assert.Error(t, err) 361 }