k8s.io/kubernetes@v1.29.3/test/integration/apiserver/flowcontrol/concurrency_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 "context" 21 "fmt" 22 "io" 23 "strings" 24 "sync" 25 "testing" 26 "time" 27 28 "github.com/prometheus/common/expfmt" 29 "github.com/prometheus/common/model" 30 31 flowcontrol "k8s.io/api/flowcontrol/v1" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/util/wait" 34 clientset "k8s.io/client-go/kubernetes" 35 "k8s.io/client-go/rest" 36 "k8s.io/kubernetes/cmd/kube-apiserver/app/options" 37 "k8s.io/kubernetes/test/integration/framework" 38 "k8s.io/kubernetes/test/utils/ktesting" 39 "k8s.io/utils/ptr" 40 ) 41 42 const ( 43 nominalConcurrencyMetricsName = "apiserver_flowcontrol_nominal_limit_seats" 44 dispatchedRequestCountMetricsName = "apiserver_flowcontrol_dispatched_requests_total" 45 rejectedRequestCountMetricsName = "apiserver_flowcontrol_rejected_requests_total" 46 labelPriorityLevel = "priority_level" 47 timeout = time.Second * 10 48 ) 49 50 func setup(t testing.TB, maxReadonlyRequestsInFlight, maxMutatingRequestsInFlight int) (context.Context, *rest.Config, framework.TearDownFunc) { 51 _, ctx := ktesting.NewTestContext(t) 52 ctx, cancel := context.WithCancel(ctx) 53 54 _, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{ 55 ModifyServerRunOptions: func(opts *options.ServerRunOptions) { 56 // Ensure all clients are allowed to send requests. 57 opts.Authorization.Modes = []string{"AlwaysAllow"} 58 opts.GenericServerRunOptions.MaxRequestsInFlight = maxReadonlyRequestsInFlight 59 opts.GenericServerRunOptions.MaxMutatingRequestsInFlight = maxMutatingRequestsInFlight 60 }, 61 }) 62 63 newTeardown := func() { 64 cancel() 65 tearDownFn() 66 } 67 return ctx, kubeConfig, newTeardown 68 } 69 70 func TestPriorityLevelIsolation(t *testing.T) { 71 ctx, kubeConfig, closeFn := setup(t, 1, 1) 72 defer closeFn() 73 74 loopbackClient := clientset.NewForConfigOrDie(kubeConfig) 75 noxu1Client := getClientFor(kubeConfig, "noxu1") 76 noxu2Client := getClientFor(kubeConfig, "noxu2") 77 78 queueLength := 50 79 concurrencyShares := 1 80 81 priorityLevelNoxu1, _, err := createPriorityLevelAndBindingFlowSchemaForUser( 82 loopbackClient, "noxu1", concurrencyShares, queueLength) 83 if err != nil { 84 t.Error(err) 85 } 86 priorityLevelNoxu2, _, err := createPriorityLevelAndBindingFlowSchemaForUser( 87 loopbackClient, "noxu2", concurrencyShares, queueLength) 88 if err != nil { 89 t.Error(err) 90 } 91 92 nominalConcurrency, err := getNominalConcurrencyOfPriorityLevel(loopbackClient) 93 if err != nil { 94 t.Error(err) 95 } 96 97 if 1 != nominalConcurrency[priorityLevelNoxu1.Name] { 98 t.Errorf("unexpected shared concurrency %v instead of %v", nominalConcurrency[priorityLevelNoxu1.Name], 1) 99 } 100 if 1 != nominalConcurrency[priorityLevelNoxu2.Name] { 101 t.Errorf("unexpected shared concurrency %v instead of %v", nominalConcurrency[priorityLevelNoxu2.Name], 1) 102 } 103 104 stopCh := make(chan struct{}) 105 wg := sync.WaitGroup{} 106 defer func() { 107 close(stopCh) 108 wg.Wait() 109 }() 110 111 // "elephant" 112 wg.Add(concurrencyShares + queueLength) 113 streamRequests(concurrencyShares+queueLength, func() { 114 _, err := noxu1Client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) 115 if err != nil { 116 t.Error(err) 117 } 118 }, &wg, stopCh) 119 // "mouse" 120 wg.Add(3) 121 streamRequests(3, func() { 122 _, err := noxu2Client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) 123 if err != nil { 124 t.Error(err) 125 } 126 }, &wg, stopCh) 127 128 time.Sleep(time.Second * 10) // running in background for a while 129 130 allDispatchedReqCounts, rejectedReqCounts, err := getRequestCountOfPriorityLevel(loopbackClient) 131 if err != nil { 132 t.Error(err) 133 } 134 135 noxu1RequestCount := allDispatchedReqCounts[priorityLevelNoxu1.Name] 136 noxu2RequestCount := allDispatchedReqCounts[priorityLevelNoxu2.Name] 137 138 if rejectedReqCounts[priorityLevelNoxu1.Name] > 0 { 139 t.Errorf(`%v requests from the "elephant" stream were rejected unexpectedly`, rejectedReqCounts[priorityLevelNoxu2.Name]) 140 } 141 if rejectedReqCounts[priorityLevelNoxu2.Name] > 0 { 142 t.Errorf(`%v requests from the "mouse" stream were rejected unexpectedly`, rejectedReqCounts[priorityLevelNoxu2.Name]) 143 } 144 145 // Theoretically, the actual expected value of request counts upon the two priority-level should be 146 // the equal. We're deliberately lax to make flakes super rare. 147 if (noxu1RequestCount/2) > noxu2RequestCount || (noxu2RequestCount/2) > noxu1RequestCount { 148 t.Errorf("imbalanced requests made by noxu1/2: (%d:%d)", noxu1RequestCount, noxu2RequestCount) 149 } 150 } 151 152 func getClientFor(loopbackConfig *rest.Config, username string) clientset.Interface { 153 config := rest.CopyConfig(loopbackConfig) 154 config.Impersonate = rest.ImpersonationConfig{ 155 UserName: username, 156 } 157 return clientset.NewForConfigOrDie(config) 158 } 159 160 func getMetrics(c clientset.Interface) (string, error) { 161 resp, err := c.CoreV1(). 162 RESTClient(). 163 Get(). 164 RequestURI("/metrics"). 165 DoRaw(context.Background()) 166 if err != nil { 167 return "", err 168 } 169 return string(resp), err 170 } 171 172 func getNominalConcurrencyOfPriorityLevel(c clientset.Interface) (map[string]int, error) { 173 resp, err := getMetrics(c) 174 if err != nil { 175 return nil, err 176 } 177 178 dec := expfmt.NewDecoder(strings.NewReader(string(resp)), expfmt.FmtText) 179 decoder := expfmt.SampleDecoder{ 180 Dec: dec, 181 Opts: &expfmt.DecodeOptions{}, 182 } 183 184 concurrency := make(map[string]int) 185 for { 186 var v model.Vector 187 if err := decoder.Decode(&v); err != nil { 188 if err == io.EOF { 189 // Expected loop termination condition. 190 return concurrency, nil 191 } 192 return nil, fmt.Errorf("failed decoding metrics: %v", err) 193 } 194 for _, metric := range v { 195 switch name := string(metric.Metric[model.MetricNameLabel]); name { 196 case nominalConcurrencyMetricsName: 197 concurrency[string(metric.Metric[labelPriorityLevel])] = int(metric.Value) 198 } 199 } 200 } 201 } 202 203 func getRequestCountOfPriorityLevel(c clientset.Interface) (map[string]int, map[string]int, error) { 204 resp, err := getMetrics(c) 205 if err != nil { 206 return nil, nil, err 207 } 208 209 dec := expfmt.NewDecoder(strings.NewReader(string(resp)), expfmt.FmtText) 210 decoder := expfmt.SampleDecoder{ 211 Dec: dec, 212 Opts: &expfmt.DecodeOptions{}, 213 } 214 215 allReqCounts := make(map[string]int) 216 rejectReqCounts := make(map[string]int) 217 for { 218 var v model.Vector 219 if err := decoder.Decode(&v); err != nil { 220 if err == io.EOF { 221 // Expected loop termination condition. 222 return allReqCounts, rejectReqCounts, nil 223 } 224 return nil, nil, fmt.Errorf("failed decoding metrics: %v", err) 225 } 226 for _, metric := range v { 227 switch name := string(metric.Metric[model.MetricNameLabel]); name { 228 case dispatchedRequestCountMetricsName: 229 allReqCounts[string(metric.Metric[labelPriorityLevel])] = int(metric.Value) 230 case rejectedRequestCountMetricsName: 231 rejectReqCounts[string(metric.Metric[labelPriorityLevel])] = int(metric.Value) 232 } 233 } 234 } 235 } 236 237 func createPriorityLevelAndBindingFlowSchemaForUser(c clientset.Interface, username string, concurrencyShares, queuelength int) (*flowcontrol.PriorityLevelConfiguration, *flowcontrol.FlowSchema, error) { 238 i0 := int32(0) 239 pl, err := c.FlowcontrolV1().PriorityLevelConfigurations().Create(context.Background(), &flowcontrol.PriorityLevelConfiguration{ 240 ObjectMeta: metav1.ObjectMeta{ 241 Name: username, 242 }, 243 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 244 Type: flowcontrol.PriorityLevelEnablementLimited, 245 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 246 NominalConcurrencyShares: ptr.To(int32(concurrencyShares)), 247 BorrowingLimitPercent: &i0, 248 LimitResponse: flowcontrol.LimitResponse{ 249 Type: flowcontrol.LimitResponseTypeQueue, 250 Queuing: &flowcontrol.QueuingConfiguration{ 251 Queues: 100, 252 HandSize: 1, 253 QueueLengthLimit: int32(queuelength), 254 }, 255 }, 256 }, 257 }, 258 }, metav1.CreateOptions{}) 259 if err != nil { 260 return nil, nil, err 261 } 262 fs, err := c.FlowcontrolV1().FlowSchemas().Create(context.TODO(), &flowcontrol.FlowSchema{ 263 ObjectMeta: metav1.ObjectMeta{ 264 Name: username, 265 }, 266 Spec: flowcontrol.FlowSchemaSpec{ 267 DistinguisherMethod: &flowcontrol.FlowDistinguisherMethod{ 268 Type: flowcontrol.FlowDistinguisherMethodByUserType, 269 }, 270 MatchingPrecedence: 1000, 271 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 272 Name: username, 273 }, 274 Rules: []flowcontrol.PolicyRulesWithSubjects{ 275 { 276 ResourceRules: []flowcontrol.ResourcePolicyRule{ 277 { 278 Verbs: []string{flowcontrol.VerbAll}, 279 APIGroups: []string{flowcontrol.APIGroupAll}, 280 Resources: []string{flowcontrol.ResourceAll}, 281 Namespaces: []string{flowcontrol.NamespaceEvery}, 282 ClusterScope: true, 283 }, 284 }, 285 Subjects: []flowcontrol.Subject{ 286 { 287 Kind: flowcontrol.SubjectKindUser, 288 User: &flowcontrol.UserSubject{ 289 Name: username, 290 }, 291 }, 292 }, 293 }, 294 }, 295 }, 296 }, metav1.CreateOptions{}) 297 if err != nil { 298 return nil, nil, err 299 } 300 301 return pl, fs, wait.Poll(time.Second, timeout, func() (bool, error) { 302 fs, err := c.FlowcontrolV1().FlowSchemas().Get(context.TODO(), username, metav1.GetOptions{}) 303 if err != nil { 304 return false, err 305 } 306 for _, condition := range fs.Status.Conditions { 307 if condition.Type == flowcontrol.FlowSchemaConditionDangling { 308 if condition.Status == flowcontrol.ConditionFalse { 309 return true, nil 310 } 311 } 312 } 313 return false, nil 314 }) 315 } 316 317 func streamRequests(parallel int, request func(), wg *sync.WaitGroup, stopCh <-chan struct{}) { 318 for i := 0; i < parallel; i++ { 319 go func() { 320 defer wg.Done() 321 for { 322 select { 323 case <-stopCh: 324 return 325 default: 326 request() 327 } 328 } 329 }() 330 } 331 }