k8s.io/apiserver@v0.31.1/pkg/util/flowcontrol/gen_test.go (about) 1 /* 2 Copyright 2019 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 flowcontrol 18 19 import ( 20 "fmt" 21 "math/rand" 22 "sync/atomic" 23 "testing" 24 25 "k8s.io/utils/clock" 26 "k8s.io/utils/ptr" 27 28 flowcontrol "k8s.io/api/flowcontrol/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/util/sets" 31 fcboot "k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap" 32 "k8s.io/apiserver/pkg/authentication/user" 33 "k8s.io/apiserver/pkg/endpoints/request" 34 fq "k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing" 35 fqtesting "k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/testing" 36 fcfmt "k8s.io/apiserver/pkg/util/flowcontrol/format" 37 "k8s.io/apiserver/pkg/util/flowcontrol/metrics" 38 ) 39 40 var noRestraintQSF = fqtesting.NewNoRestraintFactory() 41 42 // genPL creates a valid PriorityLevelConfiguration with the given 43 // name and randomly generated spec. The given name must not be one 44 // of the mandatory ones. 45 func genPL(rng *rand.Rand, name string) *flowcontrol.PriorityLevelConfiguration { 46 plc := &flowcontrol.PriorityLevelConfiguration{ 47 ObjectMeta: metav1.ObjectMeta{Name: name}, 48 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 49 Type: flowcontrol.PriorityLevelEnablementLimited, 50 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 51 NominalConcurrencyShares: ptr.To(int32(rng.Int31n(100) + 1)), 52 LimitResponse: flowcontrol.LimitResponse{ 53 Type: flowcontrol.LimitResponseTypeReject}}}} 54 if rng.Float32() < 0.95 { 55 plc.Spec.Limited.LimitResponse.Type = flowcontrol.LimitResponseTypeQueue 56 hs := rng.Int31n(5) + 1 57 plc.Spec.Limited.LimitResponse.Queuing = &flowcontrol.QueuingConfiguration{ 58 Queues: hs + rng.Int31n(20), 59 HandSize: hs, 60 QueueLengthLimit: 5} 61 } 62 labelVals := []string{"test"} 63 _, err := queueSetCompleterForPL(noRestraintQSF, nil, plc, metrics.RatioedGaugeVecPhasedElementPair(metrics.PriorityLevelConcurrencyGaugeVec, 1, 1, labelVals), metrics.PriorityLevelExecutionSeatsGaugeVec.NewForLabelValuesSafe(0, 1, labelVals), fq.NewNamedIntegrator(clock.RealClock{}, name)) 64 if err != nil { 65 panic(err) 66 } 67 return plc 68 } 69 70 // A FlowSchema together with characteristics relevant to testing 71 type fsTestingRecord struct { 72 fs *flowcontrol.FlowSchema 73 // Does this reference an existing priority level? 74 wellFormed bool 75 matchesAllResourceRequests bool 76 matchesAllNonResourceRequests bool 77 // maps `matches bool` to `isResourceRequest bool` to digests 78 digests map[bool]map[bool][]RequestDigest 79 } 80 81 func (ftr *fsTestingRecord) addDigest(digest RequestDigest, matches bool) { 82 ftr.digests[matches][digest.RequestInfo.IsResourceRequest] = append(ftr.digests[matches][digest.RequestInfo.IsResourceRequest], digest) 83 } 84 85 func (ftr *fsTestingRecord) addDigests(digests []RequestDigest, matches bool) { 86 for _, digest := range digests { 87 ftr.addDigest(digest, matches) 88 } 89 } 90 91 var flowDistinguisherMethodTypes = sets.NewString( 92 string(flowcontrol.FlowDistinguisherMethodByUserType), 93 string(flowcontrol.FlowDistinguisherMethodByNamespaceType), 94 ) 95 96 var mandFTRExempt = &fsTestingRecord{ 97 fs: fcboot.MandatoryFlowSchemaExempt, 98 wellFormed: true, 99 digests: map[bool]map[bool][]RequestDigest{ 100 false: { 101 false: {{ 102 RequestInfo: &request.RequestInfo{ 103 IsResourceRequest: false, 104 Path: "/foo/bar", 105 Verb: "frobulate"}, 106 User: &user.DefaultInfo{ 107 Name: "nobody", 108 Groups: []string{user.AllAuthenticated, "nogroup"}, 109 }, 110 }}, 111 true: {{ 112 RequestInfo: &request.RequestInfo{ 113 IsResourceRequest: true, 114 Verb: "mandate", 115 APIGroup: "nogroup", 116 Namespace: "nospace", 117 Resource: "nons", 118 }, 119 User: &user.DefaultInfo{ 120 Name: "nobody", 121 Groups: []string{user.AllAuthenticated, "nogroup"}, 122 }, 123 }}, 124 }, 125 true: { 126 false: {{ 127 RequestInfo: &request.RequestInfo{ 128 IsResourceRequest: false, 129 Path: "/foo/bar", 130 Verb: "frobulate"}, 131 User: &user.DefaultInfo{ 132 Name: "nobody", 133 Groups: []string{user.AllAuthenticated, user.SystemPrivilegedGroup}, 134 }, 135 }}, 136 true: {{ 137 RequestInfo: &request.RequestInfo{ 138 IsResourceRequest: true, 139 Verb: "mandate", 140 APIGroup: "nogroup", 141 Namespace: "nospace", 142 Resource: "nons", 143 }, 144 User: &user.DefaultInfo{ 145 Name: "nobody", 146 Groups: []string{user.AllAuthenticated, user.SystemPrivilegedGroup}, 147 }, 148 }}, 149 }, 150 }, 151 } 152 153 var mandFTRCatchAll = &fsTestingRecord{ 154 fs: fcboot.MandatoryFlowSchemaCatchAll, 155 wellFormed: true, 156 digests: map[bool]map[bool][]RequestDigest{ 157 false: {}, 158 true: { 159 false: {{ 160 RequestInfo: &request.RequestInfo{ 161 IsResourceRequest: false, 162 Path: "/foo/bar", 163 Verb: "frobulate"}, 164 User: &user.DefaultInfo{ 165 Name: "nobody", 166 Groups: []string{user.AllAuthenticated, "nogroup"}, 167 }, 168 }}, 169 true: {{ 170 RequestInfo: &request.RequestInfo{ 171 IsResourceRequest: true, 172 Verb: "mandate", 173 APIGroup: "nogroup", 174 Namespace: "nospace", 175 Resource: "nons", 176 }, 177 User: &user.DefaultInfo{ 178 Name: "nobody", 179 Groups: []string{user.AllAuthenticated, "nogroup"}, 180 }, 181 }}, 182 }, 183 }, 184 } 185 186 // genFS creates a valid FlowSchema with the given name and randomly 187 // generated spec, along with characteristics relevant to testing. 188 // When all the FlowSchemas in a collection are generated with 189 // different names: (a) the matching digests match only the schema for 190 // which they were generated, and (b) the non-matching digests do not 191 // match any schema in the collection. The generated spec is 192 // relatively likely to be well formed but might not be. An ill 193 // formed spec references a priority level drawn from badPLNames. 194 // goodPLNames may be empty, but badPLNames may not. 195 func genFS(t *testing.T, rng *rand.Rand, name string, mayMatchClusterScope bool, goodPLNames, badPLNames sets.String) *fsTestingRecord { 196 fs := &flowcontrol.FlowSchema{ 197 ObjectMeta: metav1.ObjectMeta{Name: name}, 198 Spec: flowcontrol.FlowSchemaSpec{}} 199 // 5% chance of zero rules, otherwise draw from 1--6 biased low 200 nRules := (1 + rng.Intn(3)) * (1 + rng.Intn(2)) * ((19 + rng.Intn(20)) / 20) 201 ftr := &fsTestingRecord{fs: fs, 202 wellFormed: true, 203 matchesAllResourceRequests: nRules > 0 && rng.Float32() < 0.1, 204 matchesAllNonResourceRequests: nRules > 0 && rng.Float32() < 0.1, 205 digests: map[bool]map[bool][]RequestDigest{ 206 false: {false: {}, true: {}}, 207 true: {false: {}, true: {}}}, 208 } 209 dangleStatus := flowcontrol.ConditionFalse 210 if rng.Float32() < 0.9 && len(goodPLNames) > 0 { 211 fs.Spec.PriorityLevelConfiguration = flowcontrol.PriorityLevelConfigurationReference{Name: pickSetString(rng, goodPLNames)} 212 } else { 213 fs.Spec.PriorityLevelConfiguration = flowcontrol.PriorityLevelConfigurationReference{Name: pickSetString(rng, badPLNames)} 214 ftr.wellFormed = false 215 dangleStatus = flowcontrol.ConditionTrue 216 } 217 fs.Status.Conditions = []flowcontrol.FlowSchemaCondition{{ 218 Type: flowcontrol.FlowSchemaConditionDangling, 219 Status: dangleStatus}} 220 fs.Spec.MatchingPrecedence = rng.Int31n(9997) + 2 221 if rng.Float32() < 0.8 { 222 fdmt := flowcontrol.FlowDistinguisherMethodType(pickSetString(rng, flowDistinguisherMethodTypes)) 223 fs.Spec.DistinguisherMethod = &flowcontrol.FlowDistinguisherMethod{Type: fdmt} 224 } 225 fs.Spec.Rules = []flowcontrol.PolicyRulesWithSubjects{} 226 everyResourceMatcher := -1 227 if ftr.matchesAllResourceRequests { 228 if mayMatchClusterScope { 229 everyResourceMatcher = 0 230 } else { 231 everyResourceMatcher = rng.Intn(nRules) 232 } 233 } 234 everyNonResourceMatcher := -1 235 if ftr.matchesAllNonResourceRequests { 236 if mayMatchClusterScope { 237 everyNonResourceMatcher = 0 238 } else { 239 everyNonResourceMatcher = rng.Intn(nRules) 240 } 241 } 242 // Allow only one rule if mayMatchClusterScope because that breaks 243 // cross-rule exclusion. 244 for i := 0; i < nRules && (i == 0 || !mayMatchClusterScope); i++ { 245 rule, ruleMatchingRDigests, ruleMatchingNDigests, ruleSkippingRDigests, ruleSkippingNDigests := genPolicyRuleWithSubjects(t, rng, fmt.Sprintf("%s-%d", name, i+1), mayMatchClusterScope, ftr.matchesAllResourceRequests, ftr.matchesAllNonResourceRequests, i == everyResourceMatcher, i == everyNonResourceMatcher) 246 fs.Spec.Rules = append(fs.Spec.Rules, rule) 247 ftr.addDigests(ruleMatchingRDigests, true) 248 ftr.addDigests(ruleMatchingNDigests, true) 249 ftr.addDigests(ruleSkippingRDigests, false) 250 ftr.addDigests(ruleSkippingNDigests, false) 251 } 252 if nRules == 0 { 253 var skippingRDigests, skippingNDigests []RequestDigest 254 _, _, _, skippingRDigests, skippingNDigests = genPolicyRuleWithSubjects(t, rng, name+"-1", false, false, false, false, false) 255 ftr.addDigests(skippingRDigests, false) 256 ftr.addDigests(skippingNDigests, false) 257 } 258 if testDebugLogs { 259 t.Logf("Returning name=%s, plRef=%q, wellFormed=%v, matchesAllResourceRequests=%v, matchesAllNonResourceRequests=%v for mayMatchClusterScope=%v", fs.Name, fs.Spec.PriorityLevelConfiguration.Name, ftr.wellFormed, ftr.matchesAllResourceRequests, ftr.matchesAllNonResourceRequests, mayMatchClusterScope) 260 } 261 return ftr 262 } 263 264 var noextra = make(map[string][]string) 265 266 // Generate one valid PolicyRulesWithSubjects. Also returns: matching 267 // resource-style digests, matching non-resource-style digests, 268 // skipping (i.e., not matching the generated rule) resource-style 269 // digests, skipping non-resource-style digests. When a collection of 270 // rules is generated with unique prefixes, the skipping digests for 271 // each rule match no rules in the collection. The 272 // someMatchesAllResourceRequests and 273 // someMatchesAllNonResourceRequests parameters indicate whether any 274 // rule in the collection matches all of the relevant sort of request; 275 // these imply the respective returned slice of counterexamples will 276 // be empty. The matchAllResourceRequests and 277 // matchAllNonResourceRequests parameters indicate whether the 278 // generated rule should match all of the relevant sort. The 279 // cross-rule exclusion is based on using names that start with the 280 // given prefix --- which can not be done for the namespace of a 281 // cluster-scoped request. Thus, these are normally excluded. When 282 // mayMatchClusterScope==true the generated rule may be cluster-scoped 283 // and there is no promise of cross-rule exclusion. 284 func genPolicyRuleWithSubjects(t *testing.T, rng *rand.Rand, pfx string, mayMatchClusterScope, someMatchesAllResourceRequests, someMatchesAllNonResourceRequests, matchAllResourceRequests, matchAllNonResourceRequests bool) (flowcontrol.PolicyRulesWithSubjects, []RequestDigest, []RequestDigest, []RequestDigest, []RequestDigest) { 285 subjects := []flowcontrol.Subject{} 286 matchingUIs := []user.Info{} 287 skippingUIs := []user.Info{} 288 resourceRules := []flowcontrol.ResourcePolicyRule{} 289 nonResourceRules := []flowcontrol.NonResourcePolicyRule{} 290 matchingRRIs := []*request.RequestInfo{} 291 skippingRRIs := []*request.RequestInfo{} 292 matchingNRIs := []*request.RequestInfo{} 293 skippingNRIs := []*request.RequestInfo{} 294 nSubj := rng.Intn(4) 295 for i := 0; i < nSubj; i++ { 296 subject, smus, ssus := genSubject(rng, fmt.Sprintf("%s-%d", pfx, i+1)) 297 subjects = append(subjects, subject) 298 matchingUIs = append(matchingUIs, smus...) 299 skippingUIs = append(skippingUIs, ssus...) 300 } 301 if matchAllResourceRequests || matchAllNonResourceRequests { 302 switch rng.Intn(3) { 303 case 0: 304 subjects = append(subjects, mkUserSubject("*")) 305 case 1: 306 subjects = append(subjects, mkGroupSubject("*")) 307 default: 308 subjects = append(subjects, mkGroupSubject("system:authenticated"), mkGroupSubject("system:unauthenticated")) 309 } 310 matchingUIs = append(matchingUIs, skippingUIs...) 311 } 312 if someMatchesAllResourceRequests || someMatchesAllNonResourceRequests { 313 skippingUIs = []user.Info{} 314 } else if nSubj == 0 { 315 _, _, skippingUIs = genSubject(rng, pfx+"-o") 316 } 317 var nRR, nNRR int 318 for nRR+nNRR == 0 || matchAllResourceRequests && nRR == 0 || matchAllNonResourceRequests && nNRR == 0 { 319 nRR = rng.Intn(4) 320 nNRR = rng.Intn(4) 321 } 322 allResourceMatcher := -1 323 if matchAllResourceRequests { 324 allResourceMatcher = rng.Intn(nRR) 325 } 326 // Allow only one resource rule if mayMatchClusterScope because 327 // that breaks cross-rule exclusion. 328 for i := 0; i < nRR && (i == 0 || !mayMatchClusterScope); i++ { 329 rr, rmrs, rsrs := genResourceRule(rng, fmt.Sprintf("%s-%d", pfx, i+1), mayMatchClusterScope, i == allResourceMatcher, someMatchesAllResourceRequests) 330 resourceRules = append(resourceRules, rr) 331 matchingRRIs = append(matchingRRIs, rmrs...) 332 skippingRRIs = append(skippingRRIs, rsrs...) 333 } 334 if nRR == 0 { 335 _, _, skippingRRIs = genResourceRule(rng, pfx+"-o", mayMatchClusterScope, false, someMatchesAllResourceRequests) 336 } 337 allNonResourceMatcher := -1 338 if matchAllNonResourceRequests { 339 allNonResourceMatcher = rng.Intn(nNRR) 340 } 341 for i := 0; i < nNRR; i++ { 342 nrr, nmrs, nsrs := genNonResourceRule(rng, fmt.Sprintf("%s-%d", pfx, i+1), i == allNonResourceMatcher, someMatchesAllNonResourceRequests) 343 nonResourceRules = append(nonResourceRules, nrr) 344 matchingNRIs = append(matchingNRIs, nmrs...) 345 skippingNRIs = append(skippingNRIs, nsrs...) 346 } 347 if nRR == 0 { 348 _, _, skippingNRIs = genNonResourceRule(rng, pfx+"-o", false, someMatchesAllNonResourceRequests) 349 } 350 rule := flowcontrol.PolicyRulesWithSubjects{Subjects: subjects, ResourceRules: resourceRules, NonResourceRules: nonResourceRules} 351 if testDebugLogs { 352 t.Logf("For pfx=%s, mayMatchClusterScope=%v, someMatchesAllResourceRequests=%v, someMatchesAllNonResourceRequests=%v, marr=%v, manrr=%v: generated prws=%s, mu=%s, su=%s, mrr=%s, mnr=%s, srr=%s, snr=%s", pfx, mayMatchClusterScope, someMatchesAllResourceRequests, someMatchesAllNonResourceRequests, matchAllResourceRequests, matchAllNonResourceRequests, fcfmt.Fmt(rule), fcfmt.Fmt(matchingUIs), fcfmt.Fmt(skippingUIs), fcfmt.Fmt(matchingRRIs), fcfmt.Fmt(matchingNRIs), fcfmt.Fmt(skippingRRIs), fcfmt.Fmt(skippingNRIs)) 353 } 354 matchingRDigests := cross(matchingUIs, matchingRRIs) 355 skippingRDigests := append(append(cross(matchingUIs, skippingRRIs), 356 cross(skippingUIs, matchingRRIs)...), 357 cross(skippingUIs, skippingRRIs)...) 358 matchingNDigests := cross(matchingUIs, matchingNRIs) 359 skippingNDigests := append(append(cross(matchingUIs, skippingNRIs), 360 cross(skippingUIs, matchingNRIs)...), 361 cross(skippingUIs, skippingNRIs)...) 362 matchingRDigests = shuffleAndTakeDigests(t, rng, &rule, true, matchingRDigests, (1+rng.Intn(2))*(1+rng.Intn(2))) 363 skippingRDigests = shuffleAndTakeDigests(t, rng, &rule, false, skippingRDigests, (1+rng.Intn(2))*(1+rng.Intn(2))) 364 matchingNDigests = shuffleAndTakeDigests(t, rng, &rule, true, matchingNDigests, (1+rng.Intn(2))*(1+rng.Intn(2))) 365 skippingNDigests = shuffleAndTakeDigests(t, rng, &rule, false, skippingNDigests, (1+rng.Intn(2))*(1+rng.Intn(2))) 366 return rule, matchingRDigests, matchingNDigests, skippingRDigests, skippingNDigests 367 } 368 369 func cross(uis []user.Info, ris []*request.RequestInfo) []RequestDigest { 370 ans := make([]RequestDigest, 0, len(uis)*len(ris)) 371 for _, ui := range uis { 372 for _, ri := range ris { 373 ans = append(ans, RequestDigest{RequestInfo: ri, User: ui}) 374 } 375 } 376 return ans 377 } 378 379 func shuffleAndTakeDigests(t *testing.T, rng *rand.Rand, rule *flowcontrol.PolicyRulesWithSubjects, toMatch bool, digests []RequestDigest, n int) []RequestDigest { 380 ans := make([]RequestDigest, 0, n) 381 for len(ans) < n && len(digests) > 0 { 382 i := rng.Intn(len(digests)) 383 digest := digests[i] 384 ans = append(ans, digest) 385 digests[i] = digests[len(digests)-1] 386 digests = digests[:len(digests)-1] 387 if rule != nil { 388 thisMatches := matchesPolicyRule(digest, rule) 389 if toMatch { 390 if testDebugLogs { 391 t.Logf("Added matching digest %#+v", digest) 392 } 393 if !thisMatches { 394 t.Errorf("Fail in check: rule %s does not match digest %#+v", fcfmt.Fmt(rule), digest) 395 } 396 } else { 397 if testDebugLogs { 398 t.Logf("Added skipping digest %#+v", digest) 399 } 400 if thisMatches { 401 t.Errorf("Fail in check: rule %s matches digest %#+v", fcfmt.Fmt(rule), digest) 402 } 403 } 404 } 405 } 406 return ans 407 } 408 409 var uCounter uint32 = 1 410 411 func uniqify(in RequestDigest) RequestDigest { 412 u1 := in.User.(*user.DefaultInfo) 413 u2 := *u1 414 u2.Extra = map[string][]string{"u": {fmt.Sprintf("z%d", atomic.AddUint32(&uCounter, 1))}} 415 return RequestDigest{User: &u2, RequestInfo: in.RequestInfo} 416 } 417 418 // genSubject returns a randomly generated valid Subject that matches 419 // on some name(s) starting with the given prefix. The first returned 420 // list contains members that match the generated Subject and involve 421 // names that begin with the given prefix. The second returned list 422 // contains members that mismatch the generated Subject and involve 423 // names that begin with the given prefix. 424 func genSubject(rng *rand.Rand, pfx string) (flowcontrol.Subject, []user.Info, []user.Info) { 425 subject := flowcontrol.Subject{} 426 var matchingUIs, skippingUIs []user.Info 427 x := rng.Float32() 428 switch { 429 case x < 0.33: 430 subject.Kind = flowcontrol.SubjectKindUser 431 subject.User, matchingUIs, skippingUIs = genUser(rng, pfx) 432 case x < 0.67: 433 subject.Kind = flowcontrol.SubjectKindGroup 434 subject.Group, matchingUIs, skippingUIs = genGroup(rng, pfx) 435 default: 436 subject.Kind = flowcontrol.SubjectKindServiceAccount 437 subject.ServiceAccount, matchingUIs, skippingUIs = genServiceAccount(rng, pfx) 438 } 439 return subject, matchingUIs, skippingUIs 440 } 441 442 func genUser(rng *rand.Rand, pfx string) (*flowcontrol.UserSubject, []user.Info, []user.Info) { 443 mui := &user.DefaultInfo{ 444 Name: pfx + "-u", 445 UID: "good-id", 446 Groups: []string{pfx + "-g1", mg(rng), pfx + "-g2"}, 447 Extra: noextra} 448 skips := []user.Info{&user.DefaultInfo{ 449 Name: mui.Name + "x", 450 UID: mui.UID, 451 Groups: mui.Groups, 452 Extra: mui.Extra}} 453 return &flowcontrol.UserSubject{Name: mui.Name}, []user.Info{mui}, skips 454 } 455 456 var groupCover = []string{"system:authenticated", "system:unauthenticated"} 457 458 func mg(rng *rand.Rand) string { 459 return groupCover[rng.Intn(len(groupCover))] 460 } 461 462 func mkUserSubject(username string) flowcontrol.Subject { 463 return flowcontrol.Subject{ 464 Kind: flowcontrol.SubjectKindUser, 465 User: &flowcontrol.UserSubject{Name: username}, 466 } 467 } 468 469 func mkGroupSubject(group string) flowcontrol.Subject { 470 return flowcontrol.Subject{ 471 Kind: flowcontrol.SubjectKindGroup, 472 Group: &flowcontrol.GroupSubject{Name: group}, 473 } 474 } 475 476 func genGroup(rng *rand.Rand, pfx string) (*flowcontrol.GroupSubject, []user.Info, []user.Info) { 477 name := pfx + "-g" 478 ui := &user.DefaultInfo{ 479 Name: pfx + "-u", 480 UID: "good-id", 481 Groups: []string{name}, 482 Extra: noextra} 483 if rng.Intn(2) == 0 { 484 ui.Groups = append([]string{mg(rng)}, ui.Groups...) 485 } else { 486 ui.Groups = append(ui.Groups, mg(rng)) 487 } 488 if rng.Intn(3) == 0 { 489 ui.Groups = append([]string{pfx + "-h"}, ui.Groups...) 490 } 491 if rng.Intn(3) == 0 { 492 ui.Groups = append(ui.Groups, pfx+"-i") 493 } 494 skipper := &user.DefaultInfo{ 495 Name: pfx + "-u", 496 UID: "bad-id", 497 Groups: []string{pfx + "-j", mg(rng)}, 498 Extra: noextra} 499 if rng.Intn(2) == 0 { 500 skipper.Groups = append(skipper.Groups, pfx+"-k") 501 } 502 return &flowcontrol.GroupSubject{Name: name}, []user.Info{ui}, []user.Info{skipper} 503 } 504 505 func genServiceAccount(rng *rand.Rand, pfx string) (*flowcontrol.ServiceAccountSubject, []user.Info, []user.Info) { 506 ns := pfx + "-ns" 507 name := pfx + "-n" 508 mname := name 509 if rng.Float32() < 0.05 { 510 mname = "*" 511 } 512 mui := &user.DefaultInfo{ 513 Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, name), 514 UID: "good-id", 515 Groups: []string{pfx + "-g1", mg(rng), pfx + "-g2"}, 516 Extra: noextra} 517 var skips []user.Info 518 if mname == "*" || rng.Intn(2) == 0 { 519 skips = []user.Info{&user.DefaultInfo{ 520 Name: fmt.Sprintf("system:serviceaccount:%sx:%s", ns, name), 521 UID: "bad-id", 522 Groups: mui.Groups, 523 Extra: mui.Extra}} 524 } else { 525 skips = []user.Info{&user.DefaultInfo{ 526 Name: fmt.Sprintf("system:serviceaccount:%s:%sx", ns, name), 527 UID: "bad-id", 528 Groups: mui.Groups, 529 Extra: mui.Extra}} 530 } 531 return &flowcontrol.ServiceAccountSubject{Namespace: ns, Name: mname}, []user.Info{mui}, skips 532 } 533 534 // genResourceRule randomly generates a valid ResourcePolicyRule and lists 535 // of matching and non-matching `*request.RequestInfo`. 536 func genResourceRule(rng *rand.Rand, pfx string, mayMatchClusterScope, matchAllResources, someMatchesAllResources bool) (flowcontrol.ResourcePolicyRule, []*request.RequestInfo, []*request.RequestInfo) { 537 namespaces := []string{pfx + "-n1", pfx + "-n2", pfx + "-n3"} 538 rnamespaces := namespaces 539 if mayMatchClusterScope && rng.Float32() < 0.1 { 540 namespaces[0] = "" 541 rnamespaces = namespaces[1:] 542 } 543 rr := flowcontrol.ResourcePolicyRule{ 544 Verbs: []string{pfx + "-v1", pfx + "-v2", pfx + "-v3"}, 545 APIGroups: []string{pfx + ".g1", pfx + ".g2", pfx + ".g3"}, 546 Resources: []string{pfx + "-r1s", pfx + "-r2s", pfx + "-r3s"}, 547 ClusterScope: namespaces[0] == "", 548 Namespaces: rnamespaces} 549 matchingRIs := genRRIs(rng, 3, rr.Verbs, rr.APIGroups, rr.Resources, namespaces) 550 var skippingRIs []*request.RequestInfo 551 if !someMatchesAllResources { 552 skipNSs := []string{pfx + "-n4", pfx + "-n5", pfx + "-n6"} 553 if mayMatchClusterScope && rr.Namespaces[0] != "" && rng.Float32() < 0.1 { 554 skipNSs[0] = "" 555 } 556 skippingRIs = genRRIs(rng, 3, 557 []string{pfx + "-v4", pfx + "-v5", pfx + "-v6"}, 558 []string{pfx + ".g4", pfx + ".g5", pfx + ".g6"}, 559 []string{pfx + "-r4s", pfx + "-r5s", pfx + "-r6s"}, 560 skipNSs) 561 } 562 // choose a proper subset of fields to wildcard; only matters if not matching all 563 starMask := rng.Intn(15) 564 if matchAllResources || starMask&1 == 1 && rng.Float32() < 0.1 { 565 rr.Verbs = []string{flowcontrol.VerbAll} 566 } 567 if matchAllResources || starMask&2 == 2 && rng.Float32() < 0.1 { 568 rr.APIGroups = []string{flowcontrol.APIGroupAll} 569 } 570 if matchAllResources || starMask&4 == 4 && rng.Float32() < 0.1 { 571 rr.Resources = []string{flowcontrol.ResourceAll} 572 } 573 if matchAllResources || starMask&8 == 8 && rng.Float32() < 0.1 { 574 rr.ClusterScope = true 575 rr.Namespaces = []string{flowcontrol.NamespaceEvery} 576 } 577 return rr, matchingRIs, skippingRIs 578 } 579 580 func genRRIs(rng *rand.Rand, m int, verbs, apiGroups, resources, namespaces []string) []*request.RequestInfo { 581 nv := len(verbs) 582 ng := len(apiGroups) 583 nr := len(resources) 584 nn := len(namespaces) 585 coords := chooseInts(rng, nv*ng*nr*nn, m) 586 ans := make([]*request.RequestInfo, 0, m) 587 for _, coord := range coords { 588 ans = append(ans, &request.RequestInfo{ 589 IsResourceRequest: true, 590 Verb: verbs[coord%nv], 591 APIGroup: apiGroups[coord/nv%ng], 592 Resource: resources[coord/nv/ng%nr], 593 Namespace: namespaces[coord/nv/ng/nr]}) 594 } 595 return ans 596 } 597 598 func genNRRIs(rng *rand.Rand, m int, verbs, urls []string) []*request.RequestInfo { 599 nv := len(verbs) 600 nu := len(urls) 601 coords := chooseInts(rng, nv*nu, m) 602 ans := make([]*request.RequestInfo, 0, m) 603 for _, coord := range coords { 604 ri := &request.RequestInfo{ 605 IsResourceRequest: false, 606 Verb: verbs[coord%nv], 607 Path: urls[coord/nv]} 608 if rng.Intn(2) == 1 { 609 ri.Path = ri.Path + "/more" 610 } 611 ans = append(ans, ri) 612 } 613 return ans 614 } 615 616 func chooseInts(rng *rand.Rand, n, m int) []int { 617 ans := sets.NewInt() 618 for len(ans) < m { 619 i := rng.Intn(n) 620 if ans.Has(i) { 621 continue 622 } 623 ans.Insert(i) 624 } 625 return ans.List() 626 } 627 628 // genNonResourceRule returns a randomly generated valid 629 // NonResourcePolicyRule and lists of matching and non-matching 630 // `*request.RequestInfo`. 631 func genNonResourceRule(rng *rand.Rand, pfx string, matchAllNonResources, someMatchesAllNonResources bool) (flowcontrol.NonResourcePolicyRule, []*request.RequestInfo, []*request.RequestInfo) { 632 nrr := flowcontrol.NonResourcePolicyRule{ 633 Verbs: []string{pfx + "-v1", pfx + "-v2", pfx + "-v3"}, 634 NonResourceURLs: []string{"/" + pfx + "/g/p1", "/" + pfx + "/g/p2", "/" + pfx + "/g/p3"}, 635 } 636 matchingRIs := genNRRIs(rng, 3, nrr.Verbs, nrr.NonResourceURLs) 637 var skippingRIs []*request.RequestInfo 638 if !someMatchesAllNonResources { 639 skippingRIs = genNRRIs(rng, 3, 640 []string{pfx + "-v4", pfx + "-v5", pfx + "-v6"}, 641 []string{"/" + pfx + "/b/p1", "/" + pfx + "/b/p2", "/" + pfx + "/b/p3"}) 642 } 643 // choose a proper subset of fields to consider wildcarding; only matters if not matching all 644 starMask := rng.Intn(3) 645 if matchAllNonResources || starMask&1 == 1 && rng.Float32() < 0.1 { 646 nrr.Verbs = []string{flowcontrol.VerbAll} 647 } 648 if matchAllNonResources || starMask&2 == 2 && rng.Float32() < 0.1 { 649 nrr.NonResourceURLs = []string{"*"} 650 } else { 651 nrr.NonResourceURLs[rng.Intn(3)] = "/" + pfx + "/g/*" 652 nrr.NonResourceURLs[rng.Intn(3)] = "/" + pfx + "/g" 653 } 654 return nrr, matchingRIs, skippingRIs 655 } 656 657 func pickSetString(rng *rand.Rand, set sets.String) string { 658 i, n := 0, rng.Intn(len(set)) 659 for s := range set { 660 if i == n { 661 return s 662 } 663 i++ 664 } 665 panic("empty set") 666 }