k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/auth/authorizer/node/node_authorizer_test.go (about) 1 /* 2 Copyright 2017 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 node 18 19 import ( 20 "context" 21 "fmt" 22 "math/rand" 23 "os" 24 "runtime" 25 "runtime/pprof" 26 "sync/atomic" 27 "testing" 28 "time" 29 30 corev1 "k8s.io/api/core/v1" 31 resourcev1alpha2 "k8s.io/api/resource/v1alpha2" 32 storagev1 "k8s.io/api/storage/v1" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apiserver/pkg/authentication/user" 35 "k8s.io/apiserver/pkg/authorization/authorizer" 36 utilfeature "k8s.io/apiserver/pkg/util/feature" 37 "k8s.io/component-base/featuregate" 38 "k8s.io/kubernetes/pkg/auth/nodeidentifier" 39 "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy" 40 ) 41 42 func TestAuthorizer(t *testing.T) { 43 g := NewGraph() 44 45 opts := &sampleDataOpts{ 46 nodes: 2, 47 namespaces: 2, 48 podsPerNode: 2, 49 attachmentsPerNode: 1, 50 sharedConfigMapsPerPod: 0, 51 uniqueConfigMapsPerPod: 1, 52 sharedSecretsPerPod: 1, 53 uniqueSecretsPerPod: 1, 54 sharedPVCsPerPod: 0, 55 uniquePVCsPerPod: 1, 56 uniqueResourceClaimsPerPod: 1, 57 uniqueResourceClaimTemplatesPerPod: 1, 58 uniqueResourceClaimTemplatesWithClaimPerPod: 1, 59 nodeResourceCapacitiesPerNode: 2, 60 } 61 nodes, pods, pvs, attachments, slices := generate(opts) 62 populate(g, nodes, pods, pvs, attachments, slices) 63 64 identifier := nodeidentifier.NewDefaultNodeIdentifier() 65 authz := NewAuthorizer(g, identifier, bootstrappolicy.NodeRules()) 66 67 node0 := &user.DefaultInfo{Name: "system:node:node0", Groups: []string{"system:nodes"}} 68 69 tests := []struct { 70 name string 71 attrs authorizer.AttributesRecord 72 expect authorizer.Decision 73 features featuregate.FeatureGate 74 }{ 75 { 76 name: "allowed configmap", 77 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node0", Namespace: "ns0"}, 78 expect: authorizer.DecisionAllow, 79 }, 80 { 81 name: "allowed secret via pod", 82 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: "ns0"}, 83 expect: authorizer.DecisionAllow, 84 }, 85 { 86 name: "list allowed secret via pod", 87 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "list", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: "ns0"}, 88 expect: authorizer.DecisionAllow, 89 }, 90 { 91 name: "watch allowed secret via pod", 92 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "watch", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: "ns0"}, 93 expect: authorizer.DecisionAllow, 94 }, 95 { 96 name: "disallowed list many secrets", 97 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "list", Resource: "secrets", Name: "", Namespace: "ns0"}, 98 expect: authorizer.DecisionNoOpinion, 99 }, 100 { 101 name: "disallowed watch many secrets", 102 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "watch", Resource: "secrets", Name: "", Namespace: "ns0"}, 103 expect: authorizer.DecisionNoOpinion, 104 }, 105 { 106 name: "disallowed list secrets from all namespaces with name", 107 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "list", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: ""}, 108 expect: authorizer.DecisionNoOpinion, 109 }, 110 { 111 name: "allowed shared secret via pod", 112 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-shared", Namespace: "ns0"}, 113 expect: authorizer.DecisionAllow, 114 }, 115 { 116 name: "allowed shared secret via pvc", 117 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node0-ns0", Namespace: "ns0"}, 118 expect: authorizer.DecisionAllow, 119 }, 120 { 121 name: "allowed pvc", 122 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node0", Namespace: "ns0"}, 123 expect: authorizer.DecisionAllow, 124 }, 125 { 126 name: "allowed resource claim", 127 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceclaims", APIGroup: "resource.k8s.io", Name: "claim0-pod0-node0-ns0", Namespace: "ns0"}, 128 expect: authorizer.DecisionAllow, 129 }, 130 { 131 name: "allowed resource claim with template", 132 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceclaims", APIGroup: "resource.k8s.io", Name: "generated-claim-pod0-node0-ns0-0", Namespace: "ns0"}, 133 expect: authorizer.DecisionAllow, 134 }, 135 { 136 name: "allowed pv", 137 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node0-ns0", Namespace: ""}, 138 expect: authorizer.DecisionAllow, 139 }, 140 { 141 name: "disallowed configmap", 142 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node1", Namespace: "ns0"}, 143 expect: authorizer.DecisionNoOpinion, 144 }, 145 { 146 name: "disallowed secret via pod", 147 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node1", Namespace: "ns0"}, 148 expect: authorizer.DecisionNoOpinion, 149 }, 150 { 151 name: "disallowed shared secret via pvc", 152 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node1-ns0", Namespace: "ns0"}, 153 expect: authorizer.DecisionNoOpinion, 154 }, 155 { 156 name: "disallowed pvc", 157 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"}, 158 expect: authorizer.DecisionNoOpinion, 159 }, 160 { 161 name: "disallowed resource claim, other node", 162 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceclaims", APIGroup: "resource.k8s.io", Name: "claim0-pod0-node1-ns0", Namespace: "ns0"}, 163 expect: authorizer.DecisionNoOpinion, 164 }, 165 { 166 name: "disallowed resource claim with template", 167 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceclaims", APIGroup: "resource.k8s.io", Name: "pod0-node1-claimtemplate0", Namespace: "ns0"}, 168 expect: authorizer.DecisionNoOpinion, 169 }, 170 { 171 name: "disallowed pv", 172 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""}, 173 expect: authorizer.DecisionNoOpinion, 174 }, 175 { 176 name: "allowed attachment", 177 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "volumeattachments", APIGroup: "storage.k8s.io", Name: "attachment0-node0"}, 178 expect: authorizer.DecisionAllow, 179 }, 180 { 181 name: "allowed svcacct token create", 182 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node0", Namespace: "ns0"}, 183 expect: authorizer.DecisionAllow, 184 }, 185 { 186 name: "disallowed svcacct token create - serviceaccount not attached to node", 187 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node1", Namespace: "ns0"}, 188 expect: authorizer.DecisionNoOpinion, 189 }, 190 { 191 name: "disallowed svcacct token create - no subresource", 192 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Name: "svcacct0-node0", Namespace: "ns0"}, 193 expect: authorizer.DecisionNoOpinion, 194 }, 195 { 196 name: "disallowed svcacct token create - non create", 197 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node0", Namespace: "ns0"}, 198 expect: authorizer.DecisionNoOpinion, 199 }, 200 { 201 name: "disallowed get lease in namespace other than kube-node-lease - feature enabled", 202 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node0", Namespace: "foo"}, 203 expect: authorizer.DecisionNoOpinion, 204 }, 205 { 206 name: "disallowed create lease in namespace other than kube-node-lease - feature enabled", 207 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node0", Namespace: "foo"}, 208 expect: authorizer.DecisionNoOpinion, 209 }, 210 { 211 name: "disallowed update lease in namespace other than kube-node-lease - feature enabled", 212 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node0", Namespace: "foo"}, 213 expect: authorizer.DecisionNoOpinion, 214 }, 215 { 216 name: "disallowed patch lease in namespace other than kube-node-lease - feature enabled", 217 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "patch", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node0", Namespace: "foo"}, 218 expect: authorizer.DecisionNoOpinion, 219 }, 220 { 221 name: "disallowed delete lease in namespace other than kube-node-lease - feature enabled", 222 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "delete", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node0", Namespace: "foo"}, 223 expect: authorizer.DecisionNoOpinion, 224 }, 225 { 226 name: "disallowed get another node's lease - feature enabled", 227 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node1", Namespace: corev1.NamespaceNodeLease}, 228 expect: authorizer.DecisionNoOpinion, 229 }, 230 { 231 name: "disallowed update another node's lease - feature enabled", 232 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node1", Namespace: corev1.NamespaceNodeLease}, 233 expect: authorizer.DecisionNoOpinion, 234 }, 235 { 236 name: "disallowed patch another node's lease - feature enabled", 237 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "patch", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node1", Namespace: corev1.NamespaceNodeLease}, 238 expect: authorizer.DecisionNoOpinion, 239 }, 240 { 241 name: "disallowed delete another node's lease - feature enabled", 242 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "delete", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node1", Namespace: corev1.NamespaceNodeLease}, 243 expect: authorizer.DecisionNoOpinion, 244 }, 245 { 246 name: "disallowed list node leases - feature enabled", 247 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "list", Resource: "leases", APIGroup: "coordination.k8s.io", Namespace: corev1.NamespaceNodeLease}, 248 expect: authorizer.DecisionNoOpinion, 249 }, 250 { 251 name: "disallowed watch node leases - feature enabled", 252 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "watch", Resource: "leases", APIGroup: "coordination.k8s.io", Namespace: corev1.NamespaceNodeLease}, 253 expect: authorizer.DecisionNoOpinion, 254 }, 255 { 256 name: "allowed get node lease - feature enabled", 257 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node0", Namespace: corev1.NamespaceNodeLease}, 258 expect: authorizer.DecisionAllow, 259 }, 260 { 261 name: "allowed create node lease - feature enabled", 262 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node0", Namespace: corev1.NamespaceNodeLease}, 263 expect: authorizer.DecisionAllow, 264 }, 265 { 266 name: "allowed update node lease - feature enabled", 267 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node0", Namespace: corev1.NamespaceNodeLease}, 268 expect: authorizer.DecisionAllow, 269 }, 270 { 271 name: "allowed patch node lease - feature enabled", 272 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "patch", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node0", Namespace: corev1.NamespaceNodeLease}, 273 expect: authorizer.DecisionAllow, 274 }, 275 { 276 name: "allowed delete node lease - feature enabled", 277 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "delete", Resource: "leases", APIGroup: "coordination.k8s.io", Name: "node0", Namespace: corev1.NamespaceNodeLease}, 278 expect: authorizer.DecisionAllow, 279 }, 280 // CSINode 281 { 282 name: "disallowed CSINode with subresource - feature enabled", 283 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "csinodes", Subresource: "csiDrivers", APIGroup: "storage.k8s.io", Name: "node0"}, 284 expect: authorizer.DecisionNoOpinion, 285 }, 286 { 287 name: "disallowed get another node's CSINode", 288 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "csinodes", APIGroup: "storage.k8s.io", Name: "node1"}, 289 expect: authorizer.DecisionNoOpinion, 290 }, 291 { 292 name: "disallowed update another node's CSINode", 293 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "csinodes", APIGroup: "storage.k8s.io", Name: "node1"}, 294 expect: authorizer.DecisionNoOpinion, 295 }, 296 { 297 name: "disallowed patch another node's CSINode", 298 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "patch", Resource: "csinodes", APIGroup: "storage.k8s.io", Name: "node1"}, 299 expect: authorizer.DecisionNoOpinion, 300 }, 301 { 302 name: "disallowed delete another node's CSINode", 303 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "delete", Resource: "csinodes", APIGroup: "storage.k8s.io", Name: "node1"}, 304 expect: authorizer.DecisionNoOpinion, 305 }, 306 { 307 name: "disallowed list CSINodes", 308 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "list", Resource: "csinodes", APIGroup: "storage.k8s.io"}, 309 expect: authorizer.DecisionNoOpinion, 310 }, 311 { 312 name: "disallowed watch CSINodes", 313 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "watch", Resource: "csinodes", APIGroup: "storage.k8s.io"}, 314 expect: authorizer.DecisionNoOpinion, 315 }, 316 { 317 name: "allowed get CSINode", 318 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "csinodes", APIGroup: "storage.k8s.io", Name: "node0"}, 319 expect: authorizer.DecisionAllow, 320 }, 321 { 322 name: "allowed create CSINode", 323 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "csinodes", APIGroup: "storage.k8s.io", Name: "node0"}, 324 expect: authorizer.DecisionAllow, 325 }, 326 { 327 name: "allowed update CSINode", 328 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "csinodes", APIGroup: "storage.k8s.io", Name: "node0"}, 329 expect: authorizer.DecisionAllow, 330 }, 331 { 332 name: "allowed patch CSINode", 333 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "patch", Resource: "csinodes", APIGroup: "storage.k8s.io", Name: "node0"}, 334 expect: authorizer.DecisionAllow, 335 }, 336 { 337 name: "allowed delete CSINode", 338 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "delete", Resource: "csinodes", APIGroup: "storage.k8s.io", Name: "node0"}, 339 expect: authorizer.DecisionAllow, 340 }, 341 // ResourceSlice 342 { 343 name: "disallowed ResourceSlice with subresource", 344 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceslices", Subresource: "status", APIGroup: "resource.k8s.io", Name: "slice0-node0"}, 345 expect: authorizer.DecisionNoOpinion, 346 }, 347 { 348 name: "disallowed get another node's ResourceSlice", 349 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node1"}, 350 expect: authorizer.DecisionNoOpinion, 351 }, 352 { 353 name: "disallowed update another node's ResourceSlice", 354 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node1"}, 355 expect: authorizer.DecisionNoOpinion, 356 }, 357 { 358 name: "disallowed patch another node's ResourceSlice", 359 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "patch", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node1"}, 360 expect: authorizer.DecisionNoOpinion, 361 }, 362 { 363 name: "disallowed delete another node's ResourceSlice", 364 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "delete", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node1"}, 365 expect: authorizer.DecisionNoOpinion, 366 }, 367 { 368 name: "allowed list ResourceSlices", 369 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "list", Resource: "resourceslices", APIGroup: "resource.k8s.io"}, 370 expect: authorizer.DecisionAllow, 371 }, 372 { 373 name: "allowed watch ResourceSlices", 374 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "watch", Resource: "resourceslices", APIGroup: "resource.k8s.io"}, 375 expect: authorizer.DecisionAllow, 376 }, 377 { 378 name: "allowed get ResourceSlice", 379 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"}, 380 expect: authorizer.DecisionAllow, 381 }, 382 { 383 name: "allowed create ResourceSlice", 384 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"}, 385 expect: authorizer.DecisionAllow, 386 }, 387 { 388 name: "allowed update ResourceSlice", 389 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"}, 390 expect: authorizer.DecisionAllow, 391 }, 392 { 393 name: "allowed patch ResourceSlice", 394 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "patch", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"}, 395 expect: authorizer.DecisionAllow, 396 }, 397 { 398 name: "allowed delete ResourceSlice", 399 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "delete", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"}, 400 expect: authorizer.DecisionAllow, 401 }, 402 } 403 404 for _, tc := range tests { 405 t.Run(tc.name, func(t *testing.T) { 406 if tc.features == nil { 407 authz.features = utilfeature.DefaultFeatureGate 408 } else { 409 authz.features = tc.features 410 } 411 decision, _, _ := authz.Authorize(context.Background(), tc.attrs) 412 if decision != tc.expect { 413 t.Errorf("expected %v, got %v", tc.expect, decision) 414 } 415 }) 416 } 417 } 418 419 func TestAuthorizerSharedResources(t *testing.T) { 420 g := NewGraph() 421 g.destinationEdgeThreshold = 1 422 identifier := nodeidentifier.NewDefaultNodeIdentifier() 423 authz := NewAuthorizer(g, identifier, bootstrappolicy.NodeRules()) 424 425 node1 := &user.DefaultInfo{Name: "system:node:node1", Groups: []string{"system:nodes"}} 426 node2 := &user.DefaultInfo{Name: "system:node:node2", Groups: []string{"system:nodes"}} 427 node3 := &user.DefaultInfo{Name: "system:node:node3", Groups: []string{"system:nodes"}} 428 429 g.AddPod(&corev1.Pod{ 430 ObjectMeta: metav1.ObjectMeta{Name: "pod1-node1", Namespace: "ns1"}, 431 Spec: corev1.PodSpec{ 432 NodeName: "node1", 433 Volumes: []corev1.Volume{ 434 {VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "node1-only"}}}, 435 {VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "node1-node2-only"}}}, 436 {VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "shared-all"}}}, 437 }, 438 }, 439 }) 440 g.AddPod(&corev1.Pod{ 441 ObjectMeta: metav1.ObjectMeta{Name: "pod2-node2", Namespace: "ns1"}, 442 Spec: corev1.PodSpec{ 443 NodeName: "node2", 444 Volumes: []corev1.Volume{ 445 {VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "node1-node2-only"}}}, 446 {VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "shared-all"}}}, 447 }, 448 }, 449 }) 450 451 pod3 := &corev1.Pod{ 452 ObjectMeta: metav1.ObjectMeta{Name: "pod3-node3", Namespace: "ns1"}, 453 Spec: corev1.PodSpec{ 454 NodeName: "node3", 455 Volumes: []corev1.Volume{ 456 {VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: "shared-all"}}}, 457 }, 458 }, 459 } 460 g.AddPod(pod3) 461 462 testcases := []struct { 463 User user.Info 464 Secret string 465 ConfigMap string 466 Decision authorizer.Decision 467 }{ 468 {User: node1, Decision: authorizer.DecisionAllow, Secret: "node1-only"}, 469 {User: node1, Decision: authorizer.DecisionAllow, Secret: "node1-node2-only"}, 470 {User: node1, Decision: authorizer.DecisionAllow, Secret: "shared-all"}, 471 472 {User: node2, Decision: authorizer.DecisionNoOpinion, Secret: "node1-only"}, 473 {User: node2, Decision: authorizer.DecisionAllow, Secret: "node1-node2-only"}, 474 {User: node2, Decision: authorizer.DecisionAllow, Secret: "shared-all"}, 475 476 {User: node3, Decision: authorizer.DecisionNoOpinion, Secret: "node1-only"}, 477 {User: node3, Decision: authorizer.DecisionNoOpinion, Secret: "node1-node2-only"}, 478 {User: node3, Decision: authorizer.DecisionAllow, Secret: "shared-all"}, 479 } 480 481 for i, tc := range testcases { 482 var ( 483 decision authorizer.Decision 484 err error 485 ) 486 487 if len(tc.Secret) > 0 { 488 decision, _, err = authz.Authorize(context.Background(), authorizer.AttributesRecord{User: tc.User, ResourceRequest: true, Verb: "get", Resource: "secrets", Namespace: "ns1", Name: tc.Secret}) 489 if err != nil { 490 t.Errorf("%d: unexpected error: %v", i, err) 491 continue 492 } 493 } else if len(tc.ConfigMap) > 0 { 494 decision, _, err = authz.Authorize(context.Background(), authorizer.AttributesRecord{User: tc.User, ResourceRequest: true, Verb: "get", Resource: "configmaps", Namespace: "ns1", Name: tc.ConfigMap}) 495 if err != nil { 496 t.Errorf("%d: unexpected error: %v", i, err) 497 continue 498 } 499 } else { 500 t.Fatalf("test case must include a request for a Secret or ConfigMap") 501 } 502 503 if decision != tc.Decision { 504 t.Errorf("%d: expected %v, got %v", i, tc.Decision, decision) 505 } 506 } 507 508 { 509 node3SharedSecretGet := authorizer.AttributesRecord{User: node3, ResourceRequest: true, Verb: "get", Resource: "secrets", Namespace: "ns1", Name: "shared-all"} 510 511 decision, _, err := authz.Authorize(context.Background(), node3SharedSecretGet) 512 if err != nil { 513 t.Errorf("unexpected error: %v", err) 514 } 515 if decision != authorizer.DecisionAllow { 516 t.Error("expected allowed") 517 } 518 519 // should trigger recalculation of the shared secret index 520 pod3.Spec.Volumes = nil 521 g.AddPod(pod3) 522 523 decision, _, err = authz.Authorize(context.Background(), node3SharedSecretGet) 524 if err != nil { 525 t.Errorf("unexpected error: %v", err) 526 } 527 if decision == authorizer.DecisionAllow { 528 t.Errorf("unexpectedly allowed") 529 } 530 } 531 } 532 533 type sampleDataOpts struct { 534 nodes int 535 namespaces int 536 podsPerNode int 537 538 attachmentsPerNode int 539 540 // sharedConfigMapsPerNamespaces defines number of shared configmaps in a given 541 // namespace. Each pod then mounts a random set of size `sharedConfigMapsPerPod` 542 // from that set. sharedConfigMapsPerPod is used if greater. 543 sharedConfigMapsPerNamespace int 544 // sharedSecretsPerNamespaces defines number of shared secrets in a given 545 // namespace. Each pod then mounts a random set of size `sharedSecretsPerPod` 546 // from that set. sharedSecretsPerPod is used if greater. 547 sharedSecretsPerNamespace int 548 // sharedPVCsPerNamespaces defines number of shared pvcs in a given 549 // namespace. Each pod then mounts a random set of size `sharedPVCsPerPod` 550 // from that set. sharedPVCsPerPod is used if greater. 551 sharedPVCsPerNamespace int 552 553 sharedConfigMapsPerPod int 554 sharedSecretsPerPod int 555 sharedPVCsPerPod int 556 557 uniqueSecretsPerPod int 558 uniqueConfigMapsPerPod int 559 uniquePVCsPerPod int 560 uniqueResourceClaimsPerPod int 561 uniqueResourceClaimTemplatesPerPod int 562 uniqueResourceClaimTemplatesWithClaimPerPod int 563 564 nodeResourceCapacitiesPerNode int 565 } 566 567 func BenchmarkPopulationAllocation(b *testing.B) { 568 opts := &sampleDataOpts{ 569 nodes: 500, 570 namespaces: 200, 571 podsPerNode: 200, 572 attachmentsPerNode: 20, 573 sharedConfigMapsPerPod: 0, 574 uniqueConfigMapsPerPod: 1, 575 sharedSecretsPerPod: 1, 576 uniqueSecretsPerPod: 1, 577 sharedPVCsPerPod: 0, 578 uniquePVCsPerPod: 1, 579 } 580 581 nodes, pods, pvs, attachments, slices := generate(opts) 582 b.ResetTimer() 583 584 for i := 0; i < b.N; i++ { 585 g := NewGraph() 586 populate(g, nodes, pods, pvs, attachments, slices) 587 } 588 } 589 590 func BenchmarkPopulationRetention(b *testing.B) { 591 592 // Run with: 593 // go test ./plugin/pkg/auth/authorizer/node -benchmem -bench . -run None -v -o node.test -timeout 300m 594 595 // Evaluate retained memory with: 596 // go tool pprof --inuse_space node.test plugin/pkg/auth/authorizer/node/BenchmarkPopulationRetention.profile 597 // list populate 598 599 opts := &sampleDataOpts{ 600 nodes: 500, 601 namespaces: 200, 602 podsPerNode: 200, 603 attachmentsPerNode: 20, 604 sharedConfigMapsPerPod: 0, 605 uniqueConfigMapsPerPod: 1, 606 sharedSecretsPerPod: 1, 607 uniqueSecretsPerPod: 1, 608 sharedPVCsPerPod: 0, 609 uniquePVCsPerPod: 1, 610 } 611 612 nodes, pods, pvs, attachments, slices := generate(opts) 613 // Garbage collect before the first iteration 614 runtime.GC() 615 b.ResetTimer() 616 617 for i := 0; i < b.N; i++ { 618 g := NewGraph() 619 populate(g, nodes, pods, pvs, attachments, slices) 620 621 if i == 0 { 622 f, _ := os.Create("BenchmarkPopulationRetention.profile") 623 runtime.GC() 624 pprof.WriteHeapProfile(f) 625 f.Close() 626 // reference the graph to keep it from getting garbage collected 627 _ = fmt.Sprintf("%T\n", g) 628 } 629 } 630 } 631 632 func BenchmarkWriteIndexMaintenance(b *testing.B) { 633 634 // Run with: 635 // go test ./plugin/pkg/auth/authorizer/node -benchmem -bench BenchmarkWriteIndexMaintenance -run None 636 637 opts := &sampleDataOpts{ 638 // simulate high replication in a small number of namespaces: 639 nodes: 5000, 640 namespaces: 1, 641 podsPerNode: 1, 642 attachmentsPerNode: 20, 643 sharedConfigMapsPerPod: 0, 644 uniqueConfigMapsPerPod: 1, 645 sharedSecretsPerPod: 1, 646 uniqueSecretsPerPod: 1, 647 sharedPVCsPerPod: 0, 648 uniquePVCsPerPod: 1, 649 } 650 nodes, pods, pvs, attachments, slices := generate(opts) 651 g := NewGraph() 652 populate(g, nodes, pods, pvs, attachments, slices) 653 // Garbage collect before the first iteration 654 runtime.GC() 655 b.ResetTimer() 656 657 b.SetParallelism(100) 658 b.RunParallel(func(pb *testing.PB) { 659 for pb.Next() { 660 g.AddPod(pods[0]) 661 } 662 }) 663 } 664 665 func BenchmarkAuthorization(b *testing.B) { 666 g := NewGraph() 667 668 opts := &sampleDataOpts{ 669 // To simulate high replication in a small number of namespaces: 670 // nodes: 5000, 671 // namespaces: 10, 672 // podsPerNode: 10, 673 nodes: 500, 674 namespaces: 200, 675 podsPerNode: 200, 676 attachmentsPerNode: 20, 677 sharedConfigMapsPerPod: 0, 678 uniqueConfigMapsPerPod: 1, 679 sharedSecretsPerPod: 1, 680 uniqueSecretsPerPod: 1, 681 sharedPVCsPerPod: 0, 682 uniquePVCsPerPod: 1, 683 } 684 nodes, pods, pvs, attachments, slices := generate(opts) 685 populate(g, nodes, pods, pvs, attachments, slices) 686 687 identifier := nodeidentifier.NewDefaultNodeIdentifier() 688 authz := NewAuthorizer(g, identifier, bootstrappolicy.NodeRules()) 689 690 node0 := &user.DefaultInfo{Name: "system:node:node0", Groups: []string{"system:nodes"}} 691 692 tests := []struct { 693 name string 694 attrs authorizer.AttributesRecord 695 expect authorizer.Decision 696 features featuregate.FeatureGate 697 }{ 698 { 699 name: "allowed configmap", 700 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node0", Namespace: "ns0"}, 701 expect: authorizer.DecisionAllow, 702 }, 703 { 704 name: "allowed secret via pod", 705 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: "ns0"}, 706 expect: authorizer.DecisionAllow, 707 }, 708 { 709 name: "allowed shared secret via pod", 710 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-shared", Namespace: "ns0"}, 711 expect: authorizer.DecisionAllow, 712 }, 713 { 714 name: "disallowed configmap", 715 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node1", Namespace: "ns0"}, 716 expect: authorizer.DecisionNoOpinion, 717 }, 718 { 719 name: "disallowed secret via pod", 720 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node1", Namespace: "ns0"}, 721 expect: authorizer.DecisionNoOpinion, 722 }, 723 { 724 name: "disallowed shared secret via pvc", 725 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node1-ns0", Namespace: "ns0"}, 726 expect: authorizer.DecisionNoOpinion, 727 }, 728 { 729 name: "disallowed pvc", 730 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"}, 731 expect: authorizer.DecisionNoOpinion, 732 }, 733 { 734 name: "disallowed pv", 735 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""}, 736 expect: authorizer.DecisionNoOpinion, 737 }, 738 { 739 name: "disallowed attachment - no relationship", 740 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "volumeattachments", APIGroup: "storage.k8s.io", Name: "attachment0-node1"}, 741 expect: authorizer.DecisionNoOpinion, 742 }, 743 { 744 name: "allowed attachment", 745 attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "volumeattachments", APIGroup: "storage.k8s.io", Name: "attachment0-node0"}, 746 expect: authorizer.DecisionAllow, 747 }, 748 } 749 750 podToAdd, _ := generatePod("testwrite", "ns0", "node0", "default", opts) 751 752 b.ResetTimer() 753 for _, testWriteContention := range []bool{false, true} { 754 755 shouldWrite := int32(1) 756 writes := int64(0) 757 _1ms := int64(0) 758 _10ms := int64(0) 759 _25ms := int64(0) 760 _50ms := int64(0) 761 _100ms := int64(0) 762 _250ms := int64(0) 763 _500ms := int64(0) 764 _1000ms := int64(0) 765 _1s := int64(0) 766 767 contentionPrefix := "" 768 if testWriteContention { 769 contentionPrefix = "contentious " 770 // Start a writer pushing graph modifications 100x a second 771 go func() { 772 for shouldWrite == 1 { 773 go func() { 774 start := time.Now() 775 authz.graph.AddPod(podToAdd) 776 diff := time.Since(start) 777 atomic.AddInt64(&writes, 1) 778 switch { 779 case diff < time.Millisecond: 780 atomic.AddInt64(&_1ms, 1) 781 case diff < 10*time.Millisecond: 782 atomic.AddInt64(&_10ms, 1) 783 case diff < 25*time.Millisecond: 784 atomic.AddInt64(&_25ms, 1) 785 case diff < 50*time.Millisecond: 786 atomic.AddInt64(&_50ms, 1) 787 case diff < 100*time.Millisecond: 788 atomic.AddInt64(&_100ms, 1) 789 case diff < 250*time.Millisecond: 790 atomic.AddInt64(&_250ms, 1) 791 case diff < 500*time.Millisecond: 792 atomic.AddInt64(&_500ms, 1) 793 case diff < 1000*time.Millisecond: 794 atomic.AddInt64(&_1000ms, 1) 795 default: 796 atomic.AddInt64(&_1s, 1) 797 } 798 }() 799 time.Sleep(10 * time.Millisecond) 800 } 801 }() 802 } 803 804 for _, tc := range tests { 805 if tc.features == nil { 806 authz.features = utilfeature.DefaultFeatureGate 807 } else { 808 authz.features = tc.features 809 } 810 b.Run(contentionPrefix+tc.name, func(b *testing.B) { 811 // Run authorization checks in parallel 812 b.SetParallelism(5000) 813 b.RunParallel(func(pb *testing.PB) { 814 for pb.Next() { 815 decision, _, _ := authz.Authorize(context.Background(), tc.attrs) 816 if decision != tc.expect { 817 b.Errorf("expected %v, got %v", tc.expect, decision) 818 } 819 } 820 }) 821 }) 822 } 823 824 atomic.StoreInt32(&shouldWrite, 0) 825 if testWriteContention { 826 b.Logf("graph modifications during contention test: %d", writes) 827 b.Logf("<1ms=%d, <10ms=%d, <25ms=%d, <50ms=%d, <100ms=%d, <250ms=%d, <500ms=%d, <1000ms=%d, >1000ms=%d", _1ms, _10ms, _25ms, _50ms, _100ms, _250ms, _500ms, _1000ms, _1s) 828 } else { 829 b.Logf("graph modifications during non-contention test: %d", writes) 830 } 831 } 832 } 833 834 func populate(graph *Graph, nodes []*corev1.Node, pods []*corev1.Pod, pvs []*corev1.PersistentVolume, attachments []*storagev1.VolumeAttachment, slices []*resourcev1alpha2.ResourceSlice) { 835 p := &graphPopulator{} 836 p.graph = graph 837 for _, pod := range pods { 838 p.addPod(pod) 839 } 840 for _, pv := range pvs { 841 p.addPV(pv) 842 } 843 for _, attachment := range attachments { 844 p.addVolumeAttachment(attachment) 845 } 846 for _, slice := range slices { 847 p.addResourceSlice(slice) 848 } 849 } 850 851 func randomSubset(a, b int) []int { 852 if b < a { 853 b = a 854 } 855 return rand.Perm(b)[:a] 856 } 857 858 // generate creates sample pods and persistent volumes based on the provided options. 859 // the secret/configmap/pvc/node references in the pod and pv objects are named to indicate the connections between the objects. 860 // for example, secret0-pod0-node0 is a secret referenced by pod0 which is bound to node0. 861 // when populated into the graph, the node authorizer should allow node0 to access that secret, but not node1. 862 func generate(opts *sampleDataOpts) ([]*corev1.Node, []*corev1.Pod, []*corev1.PersistentVolume, []*storagev1.VolumeAttachment, []*resourcev1alpha2.ResourceSlice) { 863 nodes := make([]*corev1.Node, 0, opts.nodes) 864 pods := make([]*corev1.Pod, 0, opts.nodes*opts.podsPerNode) 865 pvs := make([]*corev1.PersistentVolume, 0, (opts.nodes*opts.podsPerNode*opts.uniquePVCsPerPod)+(opts.sharedPVCsPerPod*opts.namespaces)) 866 attachments := make([]*storagev1.VolumeAttachment, 0, opts.nodes*opts.attachmentsPerNode) 867 slices := make([]*resourcev1alpha2.ResourceSlice, 0, opts.nodes*opts.nodeResourceCapacitiesPerNode) 868 869 rand.Seed(12345) 870 871 for n := 0; n < opts.nodes; n++ { 872 nodeName := fmt.Sprintf("node%d", n) 873 for p := 0; p < opts.podsPerNode; p++ { 874 name := fmt.Sprintf("pod%d-%s", p, nodeName) 875 namespace := fmt.Sprintf("ns%d", p%opts.namespaces) 876 svcAccountName := fmt.Sprintf("svcacct%d-%s", p, nodeName) 877 878 pod, podPVs := generatePod(name, namespace, nodeName, svcAccountName, opts) 879 pods = append(pods, pod) 880 pvs = append(pvs, podPVs...) 881 } 882 for a := 0; a < opts.attachmentsPerNode; a++ { 883 attachment := &storagev1.VolumeAttachment{} 884 attachment.Name = fmt.Sprintf("attachment%d-%s", a, nodeName) 885 attachment.Spec.NodeName = nodeName 886 attachments = append(attachments, attachment) 887 } 888 889 nodes = append(nodes, &corev1.Node{ 890 ObjectMeta: metav1.ObjectMeta{Name: nodeName}, 891 Spec: corev1.NodeSpec{}, 892 }) 893 894 for p := 0; p <= opts.nodeResourceCapacitiesPerNode; p++ { 895 name := fmt.Sprintf("slice%d-%s", p, nodeName) 896 slice := &resourcev1alpha2.ResourceSlice{ 897 ObjectMeta: metav1.ObjectMeta{Name: name}, 898 NodeName: nodeName, 899 } 900 slices = append(slices, slice) 901 } 902 } 903 return nodes, pods, pvs, attachments, slices 904 } 905 906 func generatePod(name, namespace, nodeName, svcAccountName string, opts *sampleDataOpts) (*corev1.Pod, []*corev1.PersistentVolume) { 907 pvs := make([]*corev1.PersistentVolume, 0, opts.uniquePVCsPerPod+opts.sharedPVCsPerPod) 908 909 pod := &corev1.Pod{} 910 pod.Name = name 911 pod.Namespace = namespace 912 pod.Spec.NodeName = nodeName 913 pod.Spec.ServiceAccountName = svcAccountName 914 915 for i := 0; i < opts.uniqueSecretsPerPod; i++ { 916 pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{VolumeSource: corev1.VolumeSource{ 917 Secret: &corev1.SecretVolumeSource{SecretName: fmt.Sprintf("secret%d-%s", i, pod.Name)}, 918 }}) 919 } 920 // Choose shared secrets randomly from shared secrets in a namespace. 921 subset := randomSubset(opts.sharedSecretsPerPod, opts.sharedSecretsPerNamespace) 922 for _, i := range subset { 923 pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{VolumeSource: corev1.VolumeSource{ 924 Secret: &corev1.SecretVolumeSource{SecretName: fmt.Sprintf("secret%d-shared", i)}, 925 }}) 926 } 927 928 for i := 0; i < opts.uniqueConfigMapsPerPod; i++ { 929 pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{VolumeSource: corev1.VolumeSource{ 930 ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: fmt.Sprintf("configmap%d-%s", i, pod.Name)}}, 931 }}) 932 } 933 // Choose shared configmaps randomly from shared configmaps in a namespace. 934 subset = randomSubset(opts.sharedConfigMapsPerPod, opts.sharedConfigMapsPerNamespace) 935 for _, i := range subset { 936 pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{VolumeSource: corev1.VolumeSource{ 937 ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: fmt.Sprintf("configmap%d-shared", i)}}, 938 }}) 939 } 940 941 for i := 0; i < opts.uniquePVCsPerPod; i++ { 942 pv := &corev1.PersistentVolume{} 943 pv.Name = fmt.Sprintf("pv%d-%s-%s", i, pod.Name, pod.Namespace) 944 pv.Spec.FlexVolume = &corev1.FlexPersistentVolumeSource{SecretRef: &corev1.SecretReference{Name: fmt.Sprintf("secret-%s", pv.Name)}} 945 pv.Spec.ClaimRef = &corev1.ObjectReference{Name: fmt.Sprintf("pvc%d-%s", i, pod.Name), Namespace: pod.Namespace} 946 pvs = append(pvs, pv) 947 948 pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{VolumeSource: corev1.VolumeSource{ 949 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: pv.Spec.ClaimRef.Name}, 950 }}) 951 } 952 for i := 0; i < opts.uniqueResourceClaimsPerPod; i++ { 953 claimName := fmt.Sprintf("claim%d-%s-%s", i, pod.Name, pod.Namespace) 954 pod.Spec.ResourceClaims = append(pod.Spec.ResourceClaims, corev1.PodResourceClaim{ 955 Name: fmt.Sprintf("claim%d", i), 956 Source: corev1.ClaimSource{ 957 ResourceClaimName: &claimName, 958 }, 959 }) 960 } 961 for i := 0; i < opts.uniqueResourceClaimTemplatesPerPod; i++ { 962 claimTemplateName := fmt.Sprintf("claimtemplate%d-%s-%s", i, pod.Name, pod.Namespace) 963 podClaimName := fmt.Sprintf("claimtemplate%d", i) 964 pod.Spec.ResourceClaims = append(pod.Spec.ResourceClaims, corev1.PodResourceClaim{ 965 Name: podClaimName, 966 Source: corev1.ClaimSource{ 967 ResourceClaimTemplateName: &claimTemplateName, 968 }, 969 }) 970 } 971 for i := 0; i < opts.uniqueResourceClaimTemplatesWithClaimPerPod; i++ { 972 claimTemplateName := fmt.Sprintf("claimtemplate%d-%s-%s", i, pod.Name, pod.Namespace) 973 podClaimName := fmt.Sprintf("claimtemplate-with-claim%d", i) 974 claimName := fmt.Sprintf("generated-claim-%s-%s-%d", pod.Name, pod.Namespace, i) 975 pod.Spec.ResourceClaims = append(pod.Spec.ResourceClaims, corev1.PodResourceClaim{ 976 Name: podClaimName, 977 Source: corev1.ClaimSource{ 978 ResourceClaimTemplateName: &claimTemplateName, 979 }, 980 }) 981 pod.Status.ResourceClaimStatuses = append(pod.Status.ResourceClaimStatuses, corev1.PodResourceClaimStatus{ 982 Name: podClaimName, 983 ResourceClaimName: &claimName, 984 }) 985 } 986 // Choose shared pvcs randomly from shared pvcs in a namespace. 987 subset = randomSubset(opts.sharedPVCsPerPod, opts.sharedPVCsPerNamespace) 988 for _, i := range subset { 989 pv := &corev1.PersistentVolume{} 990 pv.Name = fmt.Sprintf("pv%d-shared-%s", i, pod.Namespace) 991 pv.Spec.FlexVolume = &corev1.FlexPersistentVolumeSource{SecretRef: &corev1.SecretReference{Name: fmt.Sprintf("secret-%s", pv.Name)}} 992 pv.Spec.ClaimRef = &corev1.ObjectReference{Name: fmt.Sprintf("pvc%d-shared", i), Namespace: pod.Namespace} 993 pvs = append(pvs, pv) 994 995 pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{VolumeSource: corev1.VolumeSource{ 996 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: pv.Spec.ClaimRef.Name}, 997 }}) 998 } 999 1000 return pod, pvs 1001 }