github.com/redhat-appstudio/e2e-tests@v0.0.0-20230619105049-9a422b2094d7/cmd/loadTests.go (about) 1 package cmd 2 3 import ( 4 "encoding/json" 5 "flag" 6 "fmt" 7 "net/http" 8 "os" 9 "runtime" 10 "strconv" 11 "sync" 12 "time" 13 14 "github.com/codeready-toolchain/toolchain-e2e/setup/auth" 15 "github.com/codeready-toolchain/toolchain-e2e/setup/metrics" 16 "github.com/codeready-toolchain/toolchain-e2e/setup/metrics/queries" 17 "github.com/codeready-toolchain/toolchain-e2e/setup/terminal" 18 "github.com/gosuri/uiprogress" 19 "github.com/gosuri/uitable/util/strutil" 20 "github.com/redhat-appstudio/e2e-tests/pkg/constants" 21 "github.com/redhat-appstudio/e2e-tests/pkg/framework" 22 "github.com/redhat-appstudio/e2e-tests/pkg/utils" 23 "github.com/spf13/cobra" 24 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" 25 "k8s.io/apimachinery/pkg/util/rand" 26 k8swait "k8s.io/apimachinery/pkg/util/wait" 27 "k8s.io/klog/v2" 28 "knative.dev/pkg/apis" 29 ) 30 31 var ( 32 componentRepoUrl string = "https://github.com/devfile-samples/devfile-sample-code-with-quarkus" 33 usernamePrefix string = "testuser" 34 numberOfUsers int 35 waitPipelines bool 36 verbose bool 37 token string 38 logConsole bool 39 failFast bool 40 disableMetrics bool 41 threadCount int 42 pipelineSkipInitialChecks bool 43 ) 44 45 var ( 46 UserCreationTimeMaxPerThread []time.Duration 47 ResourceCreationTimeMaxPerThread []time.Duration 48 PipelineRunSucceededTimeMaxPerThread []time.Duration 49 UserCreationTimeSumPerThread []time.Duration 50 ResourceCreationTimeSumPerThread []time.Duration 51 PipelineRunSucceededTimeSumPerThread []time.Duration 52 PipelineRunFailedTimeSumPerThread []time.Duration 53 SuccessfulUserCreationsPerThread []int64 54 SuccessfulResourceCreationsPerThread []int64 55 SuccessfulPipelineRunsPerThread []int64 56 FailedUserCreationsPerThread []int64 57 FailedResourceCreationsPerThread []int64 58 FailedPipelineRunsPerThread []int64 59 frameworkMap *sync.Map 60 userComponentMap *sync.Map 61 errorCountMap map[int]ErrorCount 62 errorMutex = &sync.Mutex{} 63 usersBarMutex = &sync.Mutex{} 64 resourcesBarMutex = &sync.Mutex{} 65 pipelinesBarMutex = &sync.Mutex{} 66 threadsWG *sync.WaitGroup 67 logData LogData 68 ) 69 70 type ErrorOccurrence struct { 71 ErrorCode int `json:"errorCode"` 72 Message string `json:"message"` 73 } 74 75 type ErrorCount struct { 76 ErrorCode int `json:"errorCode"` 77 Count int `json:"count"` 78 } 79 80 type LogData struct { 81 Timestamp string `json:"timestamp"` 82 EndTimestamp string `json:"endTimestamp"` 83 MachineName string `json:"machineName"` 84 BinaryDetails string `json:"binaryDetails"` 85 ComponentRepoUrl string `json:"componentRepoUrl"` 86 NumberOfThreads int `json:"threads"` 87 NumberOfUsersPerThread int `json:"usersPerThread"` 88 NumberOfUsers int `json:"totalUsers"` 89 PipelineSkipInitialChecks bool `json:"pipelineSkipInitialChecks"` 90 LoadTestCompletionStatus string `json:"status"` 91 AverageTimeToSpinUpUsers float64 `json:"createUserTimeAvg"` 92 MaxTimeToSpinUpUsers float64 `json:"createUserTimeMax"` 93 AverageTimeToCreateResources float64 `json:"createResourcesTimeAvg"` 94 MaxTimeToCreateResources float64 `json:"createResourcesTimeMax"` 95 AverageTimeToRunPipelineSucceeded float64 `json:"runPipelineSucceededTimeAvg"` 96 MaxTimeToRunPipelineSucceeded float64 `json:"runPipelineSucceededTimeMax"` 97 AverageTimeToRunPipelineFailed float64 `json:"runPipelineFailedTimeAvg"` 98 UserCreationFailureCount int64 `json:"createUserFailures"` 99 UserCreationFailureRate float64 `json:"createUserFailureRate"` 100 ResourceCreationFailureCount int64 `json:"createResourcesFailures"` 101 ResourceCreationFailureRate float64 `json:"createResourcesFailureRate"` 102 PipelineRunFailureCount int64 `json:"runPipelineFailures"` 103 PipelineRunFailureRate float64 `json:"runPipelineFailureRate"` 104 ErrorCounts []ErrorCount `json:"errorCounts"` 105 Errors []ErrorOccurrence `json:"errors"` 106 ErrorsTotal int `json:"errorsTotal"` 107 } 108 109 func createLogDataJSON(outputFile string, logDataInput LogData) error { 110 jsonData, err := json.MarshalIndent(logDataInput, "", " ") 111 if err != nil { 112 return fmt.Errorf("error marshalling JSON: %v", err) 113 } 114 115 err = os.WriteFile(outputFile, jsonData, 0644) 116 if err != nil { 117 return fmt.Errorf("error writing JSON file: %v", err) 118 } 119 120 return nil 121 } 122 123 var rootCmd = &cobra.Command{ 124 Use: "load-test", 125 Short: "Used to Generate Users and Run Load Tests on AppStudio.", 126 Long: `Used to Generate Users and Run Load Tests on AppStudio.`, 127 SilenceErrors: true, 128 SilenceUsage: false, 129 Args: cobra.NoArgs, 130 Run: setup, 131 } 132 133 func ExecuteLoadTest() { 134 err := rootCmd.Execute() 135 if err != nil { 136 os.Exit(1) 137 } 138 } 139 140 func init() { 141 rootCmd.Flags().StringVar(&componentRepoUrl, "component-repo", componentRepoUrl, "the component repo URL to be used") 142 rootCmd.Flags().StringVar(&usernamePrefix, "username", usernamePrefix, "the prefix used for usersignup names") 143 // TODO use a custom kubeconfig and introduce debug logging and trace 144 rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "if 'debug' traces should be displayed in the console") 145 rootCmd.Flags().IntVarP(&numberOfUsers, "users", "u", 5, "the number of user accounts to provision per thread") 146 rootCmd.Flags().BoolVarP(&waitPipelines, "waitpipelines", "w", false, "if you want to wait for pipelines to finish") 147 rootCmd.Flags().BoolVarP(&logConsole, "log-to-console", "l", false, "if you want to log to console in addition to the log file") 148 rootCmd.Flags().BoolVar(&failFast, "fail-fast", false, "if you want the test to fail fast at first failure") 149 rootCmd.Flags().BoolVar(&disableMetrics, "disable-metrics", false, "if you want to disable metrics gathering") 150 rootCmd.Flags().IntVarP(&threadCount, "threads", "t", 1, "number of concurrent threads to execute") 151 rootCmd.Flags().BoolVar(&pipelineSkipInitialChecks, "pipeline-skip-initial-checks", true, "if pipeline runs' initial checks are to be skipped") 152 } 153 154 func logError(errCode int, message string) { 155 msg := fmt.Sprintf("Error #%d: %s", errCode, message) 156 if failFast { 157 klog.Fatalln(msg) 158 } else { 159 klog.Errorln(msg) 160 } 161 errorMutex.Lock() 162 defer errorMutex.Unlock() 163 164 errorCount, ok := errorCountMap[errCode] 165 if ok { 166 errorCount.Count = errorCount.Count + 1 167 errorCountMap[errCode] = errorCount 168 } else { 169 errorCountMap[errCode] = ErrorCount{ 170 ErrorCode: errCode, 171 Count: 1, 172 } 173 } 174 175 errorOccurrence := ErrorOccurrence{ 176 ErrorCode: errCode, 177 Message: message, 178 } 179 logData.Errors = append(logData.Errors, errorOccurrence) 180 } 181 182 func setKlogFlag(fs flag.FlagSet, name string, value string) { 183 err := fs.Set(name, value) 184 if err != nil { 185 klog.Fatalf("Unable to set klog flag %s: %v", name, err) 186 } 187 } 188 189 func setup(cmd *cobra.Command, args []string) { 190 cmd.SilenceUsage = true 191 term := terminal.New(cmd.InOrStdin, cmd.OutOrStdout, verbose) 192 193 logFile, err := os.Create("load-tests.log") 194 if err != nil { 195 klog.Fatalf("Error creating log file: %v", err) 196 } 197 var fs flag.FlagSet 198 klog.InitFlags(&fs) 199 setKlogFlag(fs, "log_file", logFile.Name()) 200 setKlogFlag(fs, "logtostderr", "false") 201 setKlogFlag(fs, "alsologtostderr", strconv.FormatBool(logConsole)) 202 203 overallCount := numberOfUsers * threadCount 204 205 klog.Infof("Number of threads: %d", threadCount) 206 klog.Infof("Number of users per thread: %d", numberOfUsers) 207 klog.Infof("Number of users overall: %d", overallCount) 208 klog.Infof("Pipeline run initial checks skipped: %t", pipelineSkipInitialChecks) 209 210 klog.Infof("🕖 initializing...\n") 211 globalframework, err := framework.NewFramework("load-tests") 212 if err != nil { 213 klog.Fatalf("error creating client-go %v", err) 214 } 215 216 if len(token) == 0 { 217 token, err = auth.GetTokenFromOC() 218 if err != nil { 219 tokenRequestURI, err := auth.GetTokenRequestURI(globalframework.AsKubeAdmin.CommonController.KubeRest()) 220 if err != nil { 221 klog.Fatalf("a token is required to capture metrics, use oc login to log into the cluster: %v", err) 222 } 223 klog.Fatalf("a token is required to capture metrics, use oc login to log into the cluster. alternatively request a token and use the token flag: %v", tokenRequestURI) 224 } 225 } 226 227 var stopMetrics chan struct{} 228 var metricsInstance *metrics.Gatherer 229 if !disableMetrics { 230 metricsInstance = metrics.NewEmpty(term, globalframework.AsKubeAdmin.CommonController.KubeRest(), 10*time.Minute) 231 232 prometheusClient := metrics.GetPrometheusClient(term, globalframework.AsKubeAdmin.CommonController.KubeRest(), token) 233 234 metricsInstance.AddQueries( 235 queries.QueryClusterCPUUtilisation(prometheusClient), 236 queries.QueryClusterMemoryUtilisation(prometheusClient), 237 queries.QueryNodeMemoryUtilisation(prometheusClient), 238 queries.QueryEtcdMemoryUsage(prometheusClient), 239 queries.QueryWorkloadCPUUsage(prometheusClient, constants.OLMOperatorNamespace, constants.OLMOperatorWorkload), 240 queries.QueryWorkloadMemoryUsage(prometheusClient, constants.OLMOperatorNamespace, constants.OLMOperatorWorkload), 241 queries.QueryOpenshiftKubeAPIMemoryUtilisation(prometheusClient), 242 queries.QueryWorkloadCPUUsage(prometheusClient, constants.OSAPIServerNamespace, constants.OSAPIServerWorkload), 243 queries.QueryWorkloadMemoryUsage(prometheusClient, constants.OSAPIServerNamespace, constants.OSAPIServerWorkload), 244 queries.QueryWorkloadCPUUsage(prometheusClient, constants.HostOperatorNamespace, constants.HostOperatorWorkload), 245 queries.QueryWorkloadMemoryUsage(prometheusClient, constants.HostOperatorNamespace, constants.HostOperatorWorkload), 246 queries.QueryWorkloadCPUUsage(prometheusClient, constants.MemberOperatorNamespace, constants.MemberOperatorWorkload), 247 queries.QueryWorkloadMemoryUsage(prometheusClient, constants.MemberOperatorNamespace, constants.MemberOperatorWorkload), 248 queries.QueryWorkloadCPUUsage(prometheusClient, "application-service", "application-service-application-service-controller-manager"), 249 queries.QueryWorkloadMemoryUsage(prometheusClient, "application-service", "application-service-application-service-controller-manager"), 250 queries.QueryWorkloadCPUUsage(prometheusClient, "build-service", "build-service-controller-manager"), 251 queries.QueryWorkloadMemoryUsage(prometheusClient, "build-service", "build-service-controller-manager"), 252 ) 253 stopMetrics = metricsInstance.StartGathering() 254 255 klog.Infof("Sleeping till all metrics queries gets init") 256 time.Sleep(time.Second * 10) 257 } 258 259 machineName, err := os.Hostname() 260 if err != nil { 261 klog.Errorf("error getting hostname: %v\n", err) 262 return 263 } 264 265 goVersion := runtime.Version() 266 goOS := runtime.GOOS 267 goArch := runtime.GOARCH 268 binaryDetails := fmt.Sprintf("Built with %s for %s/%s", goVersion, goOS, goArch) 269 270 logData = LogData{ 271 Timestamp: time.Now().Format("2006-01-02T15:04:05Z07:00"), 272 MachineName: machineName, 273 BinaryDetails: binaryDetails, 274 ComponentRepoUrl: componentRepoUrl, 275 NumberOfThreads: threadCount, 276 NumberOfUsersPerThread: numberOfUsers, 277 NumberOfUsers: overallCount, 278 PipelineSkipInitialChecks: pipelineSkipInitialChecks, 279 Errors: []ErrorOccurrence{}, 280 ErrorCounts: []ErrorCount{}, 281 } 282 283 klog.Infof("🍿 provisioning users...\n") 284 285 uip := uiprogress.New() 286 uip.Start() 287 288 barLength := 60 289 290 AppStudioUsersBar := uip.AddBar(overallCount).AppendCompleted().PrependFunc(func(b *uiprogress.Bar) string { 291 return strutil.PadLeft(fmt.Sprintf("Creating AppStudio Users (%d/%d) [%d failed]", b.Current(), overallCount, sumFromArray(FailedUserCreationsPerThread)), barLength, ' ') 292 }) 293 294 ResourcesBar := uip.AddBar(overallCount).AppendCompleted().PrependFunc(func(b *uiprogress.Bar) string { 295 return strutil.PadLeft(fmt.Sprintf("Creating AppStudio User Resources (%d/%d) [%d failed]", b.Current(), overallCount, sumFromArray(FailedResourceCreationsPerThread)), barLength, ' ') 296 }) 297 298 PipelinesBar := uip.AddBar(overallCount).AppendCompleted().PrependFunc(func(b *uiprogress.Bar) string { 299 return strutil.PadLeft(fmt.Sprintf("Waiting for pipelines to finish (%d/%d) [%d failed]", b.Current(), overallCount, sumFromArray(FailedPipelineRunsPerThread)), barLength, ' ') 300 }) 301 302 UserCreationTimeMaxPerThread = make([]time.Duration, threadCount) 303 ResourceCreationTimeMaxPerThread = make([]time.Duration, threadCount) 304 PipelineRunSucceededTimeMaxPerThread = make([]time.Duration, threadCount) 305 UserCreationTimeSumPerThread = make([]time.Duration, threadCount) 306 ResourceCreationTimeSumPerThread = make([]time.Duration, threadCount) 307 PipelineRunSucceededTimeSumPerThread = make([]time.Duration, threadCount) 308 PipelineRunFailedTimeSumPerThread = make([]time.Duration, threadCount) 309 SuccessfulUserCreationsPerThread = make([]int64, threadCount) 310 SuccessfulResourceCreationsPerThread = make([]int64, threadCount) 311 SuccessfulPipelineRunsPerThread = make([]int64, threadCount) 312 FailedUserCreationsPerThread = make([]int64, threadCount) 313 FailedResourceCreationsPerThread = make([]int64, threadCount) 314 FailedPipelineRunsPerThread = make([]int64, threadCount) 315 frameworkMap = &sync.Map{} 316 userComponentMap = &sync.Map{} 317 errorCountMap = make(map[int]ErrorCount) 318 319 rand.Seed(time.Now().UnixNano()) 320 321 threadsWG = &sync.WaitGroup{} 322 threadsWG.Add(threadCount) 323 for thread := 0; thread < threadCount; thread++ { 324 go userJourneyThread(frameworkMap, threadsWG, thread, AppStudioUsersBar, ResourcesBar, PipelinesBar) 325 } 326 327 // Todo add cleanup functions that will delete user signups 328 329 threadsWG.Wait() 330 uip.Stop() 331 332 logData.EndTimestamp = time.Now().Format("2006-01-02T15:04:05Z07:00") 333 334 logData.LoadTestCompletionStatus = "Completed" 335 336 userCreationFailureCount := sumFromArray(FailedUserCreationsPerThread) 337 logData.UserCreationFailureCount = userCreationFailureCount 338 339 averageTimeToSpinUpUsers := float64(0) 340 userCreationSuccessCount := sumFromArray(SuccessfulUserCreationsPerThread) 341 if userCreationSuccessCount > 0 { 342 averageTimeToSpinUpUsers = sumDurationFromArray(UserCreationTimeSumPerThread).Seconds() / float64(userCreationSuccessCount) 343 } 344 logData.AverageTimeToSpinUpUsers = averageTimeToSpinUpUsers 345 346 logData.MaxTimeToSpinUpUsers = maxDurationFromArray(UserCreationTimeMaxPerThread).Seconds() 347 348 resourceCreationFailureCount := sumFromArray(FailedResourceCreationsPerThread) 349 logData.ResourceCreationFailureCount = resourceCreationFailureCount 350 351 averageTimeToCreateResources := float64(0) 352 resourceCreationSuccessCount := sumFromArray(SuccessfulResourceCreationsPerThread) 353 if resourceCreationSuccessCount > 0 { 354 averageTimeToCreateResources = sumDurationFromArray(ResourceCreationTimeSumPerThread).Seconds() / float64(resourceCreationSuccessCount) 355 } 356 logData.AverageTimeToCreateResources = averageTimeToCreateResources 357 358 logData.MaxTimeToCreateResources = maxDurationFromArray(ResourceCreationTimeMaxPerThread).Seconds() 359 360 pipelineRunFailureCount := sumFromArray(FailedPipelineRunsPerThread) 361 logData.PipelineRunFailureCount = pipelineRunFailureCount 362 363 averageTimeToRunPipelineSucceeded := float64(0) 364 pipelineRunSuccessCount := sumFromArray(SuccessfulPipelineRunsPerThread) 365 if pipelineRunSuccessCount > 0 { 366 averageTimeToRunPipelineSucceeded = sumDurationFromArray(PipelineRunSucceededTimeSumPerThread).Seconds() / float64(pipelineRunSuccessCount) 367 } 368 logData.AverageTimeToRunPipelineSucceeded = averageTimeToRunPipelineSucceeded 369 370 logData.MaxTimeToRunPipelineSucceeded = maxDurationFromArray(PipelineRunSucceededTimeMaxPerThread).Seconds() 371 372 averageTimeToRunPipelineFailed := float64(0) 373 if pipelineRunFailureCount > 0 { 374 averageTimeToRunPipelineFailed = sumDurationFromArray(PipelineRunFailedTimeSumPerThread).Seconds() / float64(pipelineRunFailureCount) 375 } 376 logData.AverageTimeToRunPipelineFailed = averageTimeToRunPipelineFailed 377 378 userCreationFailureRate := float64(userCreationFailureCount) / float64(overallCount) 379 logData.UserCreationFailureRate = userCreationFailureRate 380 381 resourceCreationFailureRate := float64(resourceCreationFailureCount) / float64(overallCount) 382 logData.ResourceCreationFailureRate = resourceCreationFailureRate 383 384 pipelineRunFailureRate := float64(pipelineRunFailureCount) / float64(overallCount) 385 logData.PipelineRunFailureRate = pipelineRunFailureRate 386 387 klog.Infof("🏁 Load Test Completed!") 388 klog.Infof("📈 Results 📉") 389 klog.Infof("Average Time to spin up users: %.2f s", averageTimeToSpinUpUsers) 390 klog.Infof("Maximal Time to spin up users: %.2f s", logData.MaxTimeToSpinUpUsers) 391 klog.Infof("Average Time to create Resources: %.2f s", averageTimeToCreateResources) 392 klog.Infof("Maximal Time to create Resources: %.2f s", logData.MaxTimeToCreateResources) 393 klog.Infof("Average Time to run Pipelines successfully: %.2f s", averageTimeToRunPipelineSucceeded) 394 klog.Infof("Maximal Time to run Pipelines successfully: %.2f s", logData.MaxTimeToRunPipelineSucceeded) 395 klog.Infof("Average Time to fail Pipelines: %.2f s", averageTimeToRunPipelineFailed) 396 klog.Infof("Number of times user creation failed: %d (%.2f %%)", userCreationFailureCount, userCreationFailureRate*100) 397 klog.Infof("Number of times resource creation failed: %d (%.2f %%)", resourceCreationFailureCount, resourceCreationFailureRate*100) 398 klog.Infof("Number of times pipeline run failed: %d (%.2f %%)", pipelineRunFailureCount, pipelineRunFailureRate*100) 399 klog.Infoln("Error summary:") 400 for _, errorCount := range errorCountMap { 401 klog.Infof("Number of error #%d occured: %d", errorCount.ErrorCode, errorCount.Count) 402 logData.ErrorCounts = append(logData.ErrorCounts, errorCount) 403 } 404 logData.ErrorsTotal = len(logData.Errors) 405 klog.Infof("Total number of errors occured: %d", logData.ErrorsTotal) 406 407 err = createLogDataJSON("load-tests.json", logData) 408 if err != nil { 409 klog.Errorf("error while marshalling JSON: %v\n", err) 410 } 411 412 klog.StopFlushDaemon() 413 klog.Flush() 414 if !disableMetrics { 415 defer close(stopMetrics) 416 metricsInstance.PrintResults() 417 } 418 } 419 420 func maxDurationFromArray(durations []time.Duration) time.Duration { 421 max := time.Duration(0) 422 for _, i := range durations { 423 if i > max { 424 max = i 425 } 426 } 427 return max 428 } 429 430 func sumDurationFromArray(durations []time.Duration) time.Duration { 431 sum := time.Duration(0) 432 for _, i := range durations { 433 sum += i 434 } 435 return sum 436 } 437 438 func sumFromArray(array []int64) int64 { 439 sum := int64(0) 440 for _, i := range array { 441 sum += i 442 } 443 return sum 444 } 445 446 func increaseBar(bar *uiprogress.Bar, mutex *sync.Mutex) { 447 mutex.Lock() 448 defer mutex.Unlock() 449 bar.Incr() 450 } 451 452 func componentForUser(username string) string { 453 val, ok := userComponentMap.Load(username) 454 if ok { 455 componentName, ok2 := val.(string) 456 if ok2 { 457 return componentName 458 } else { 459 klog.Errorf("Invalid type of map value: %+v", val) 460 } 461 } 462 return "" 463 } 464 465 func frameworkForUser(username string) *framework.Framework { 466 val, ok := frameworkMap.Load(username) 467 if ok { 468 framework, ok2 := val.(*framework.Framework) 469 if ok2 { 470 return framework 471 } else { 472 klog.Errorf("Invalid type of map value: %+v", val) 473 } 474 } 475 return nil 476 } 477 478 func tryNewFramework(username string, timeout time.Duration) (*framework.Framework, error) { 479 ch := make(chan *framework.Framework) 480 var fw *framework.Framework 481 var err error 482 go func() { 483 fw, err = framework.NewFrameworkWithTimeout(username, time.Minute*60) 484 ch <- fw 485 }() 486 487 var ret *framework.Framework 488 489 select { 490 case result := <-ch: 491 ret = result 492 case <-time.After(timeout): 493 ret = nil 494 err = fmt.Errorf("unable to create new framework for user %s within %v", username, timeout) 495 } 496 497 return ret, err 498 } 499 500 func userJourneyThread(frameworkMap *sync.Map, threadWaitGroup *sync.WaitGroup, threadIndex int, usersBar *uiprogress.Bar, resourcesBar *uiprogress.Bar, pipelinesBar *uiprogress.Bar) { 501 chUsers := make(chan string, numberOfUsers) 502 chPipelines := make(chan string, numberOfUsers) 503 504 defer threadWaitGroup.Done() 505 506 var wg *sync.WaitGroup = &sync.WaitGroup{} 507 508 if waitPipelines { 509 wg.Add(3) 510 } else { 511 wg.Add(2) 512 } 513 514 go func() { 515 defer wg.Done() 516 for userIndex := 1; userIndex <= numberOfUsers; userIndex++ { 517 startTime := time.Now() 518 username := fmt.Sprintf("%s-%04d", usernamePrefix, threadIndex*numberOfUsers+userIndex) 519 framework, err := tryNewFramework(username, 60*time.Minute) 520 if err != nil { 521 logError(1, fmt.Sprintf("Unable to provision user '%s': %v", username, err)) 522 FailedUserCreationsPerThread[threadIndex] += 1 523 increaseBar(usersBar, usersBarMutex) 524 continue 525 } else { 526 frameworkMap.Store(username, framework) 527 } 528 529 chUsers <- username 530 531 userCreationTime := time.Since(startTime) 532 UserCreationTimeSumPerThread[threadIndex] += userCreationTime 533 if userCreationTime > UserCreationTimeMaxPerThread[threadIndex] { 534 UserCreationTimeMaxPerThread[threadIndex] = userCreationTime 535 } 536 537 SuccessfulUserCreationsPerThread[threadIndex] += 1 538 increaseBar(usersBar, usersBarMutex) 539 } 540 close(chUsers) 541 }() 542 543 go func() { 544 defer wg.Done() 545 for username := range chUsers { 546 startTime := time.Now() 547 framework := frameworkForUser(username) 548 if framework == nil { 549 logError(2, fmt.Sprintf("Framework not found for username %s", username)) 550 increaseBar(resourcesBar, resourcesBarMutex) 551 continue 552 } 553 usernamespace := framework.UserNamespace 554 ApplicationName := fmt.Sprintf("%s-app", username) 555 app, err := framework.AsKubeDeveloper.HasController.CreateHasApplicationWithTimeout(ApplicationName, usernamespace, 60*time.Minute) 556 if err != nil { 557 logError(4, fmt.Sprintf("Unable to create the Application %s: %v", ApplicationName, err)) 558 FailedResourceCreationsPerThread[threadIndex] += 1 559 increaseBar(resourcesBar, resourcesBarMutex) 560 continue 561 } 562 gitopsRepoInterval := 5 * time.Second 563 gitopsRepoTimeout := 60 * time.Minute 564 repoUrl := utils.ObtainGitOpsRepositoryUrl(app.Status.Devfile) 565 if err := utils.WaitUntilWithInterval(func() (done bool, err error) { 566 resp, err := http.Get(repoUrl) 567 if err != nil { 568 return false, fmt.Errorf("unable to request gitops repo %s: %+v", repoUrl, err) 569 } 570 defer resp.Body.Close() 571 if resp.StatusCode == 404 { 572 return false, nil 573 } else if resp.StatusCode == 200 { 574 return true, nil 575 } else { 576 return false, fmt.Errorf("unexpected response code when requesting gitop repo %s: %v", repoUrl, err) 577 } 578 }, gitopsRepoInterval, gitopsRepoTimeout); err != nil { 579 logError(5, fmt.Sprintf("Unable to create application %s gitops repo within %v: %v", ApplicationName, gitopsRepoTimeout, err)) 580 FailedResourceCreationsPerThread[threadIndex] += 1 581 increaseBar(resourcesBar, resourcesBarMutex) 582 continue 583 } 584 585 ComponentDetectionQueryName := fmt.Sprintf("%s-cdq", username) 586 cdq, err := framework.AsKubeDeveloper.HasController.CreateComponentDetectionQueryWithTimeout(ComponentDetectionQueryName, usernamespace, componentRepoUrl, "", "", "", false, 60*time.Minute) 587 if err != nil { 588 logError(6, fmt.Sprintf("Unable to create ComponentDetectionQuery %s: %v", ComponentDetectionQueryName, err)) 589 FailedResourceCreationsPerThread[threadIndex] += 1 590 increaseBar(resourcesBar, resourcesBarMutex) 591 continue 592 } 593 if cdq.Name != ComponentDetectionQueryName { 594 logError(7, fmt.Sprintf("Actual cdq name (%s) does not match expected (%s): %v", cdq.Name, ComponentDetectionQueryName, err)) 595 FailedResourceCreationsPerThread[threadIndex] += 1 596 increaseBar(resourcesBar, resourcesBarMutex) 597 continue 598 } 599 if len(cdq.Status.ComponentDetected) > 1 { 600 logError(8, fmt.Sprintf("cdq (%s) detected more than 1 component", cdq.Name)) 601 FailedResourceCreationsPerThread[threadIndex] += 1 602 increaseBar(resourcesBar, resourcesBarMutex) 603 continue 604 } 605 606 for _, compStub := range cdq.Status.ComponentDetected { 607 component, err := framework.AsKubeDeveloper.HasController.CreateComponentFromStubSkipInitialChecks(compStub, usernamespace, "", "", ApplicationName, pipelineSkipInitialChecks) 608 609 if err != nil { 610 logError(9, fmt.Sprintf("Unable to create the Component %s: %v", compStub.ComponentStub.ComponentName, err)) 611 FailedResourceCreationsPerThread[threadIndex] += 1 612 increaseBar(resourcesBar, resourcesBarMutex) 613 continue 614 } 615 if component.Name != compStub.ComponentStub.ComponentName { 616 logError(10, fmt.Sprintf("Actual component name (%s) does not match expected (%s): %v", component.Name, compStub.ComponentStub.ComponentName, err)) 617 FailedResourceCreationsPerThread[threadIndex] += 1 618 increaseBar(resourcesBar, resourcesBarMutex) 619 continue 620 } 621 userComponentMap.Store(username, component.Name) 622 } 623 624 resourceCreationTime := time.Since(startTime) 625 ResourceCreationTimeSumPerThread[threadIndex] += resourceCreationTime 626 if resourceCreationTime > ResourceCreationTimeMaxPerThread[threadIndex] { 627 ResourceCreationTimeMaxPerThread[threadIndex] = resourceCreationTime 628 } 629 SuccessfulResourceCreationsPerThread[threadIndex] += 1 630 631 chPipelines <- username 632 increaseBar(resourcesBar, resourcesBarMutex) 633 } 634 close(chPipelines) 635 }() 636 637 if waitPipelines { 638 go func() { 639 defer wg.Done() 640 for username := range chPipelines { 641 framework := frameworkForUser(username) 642 if framework == nil { 643 logError(11, fmt.Sprintf("Framework not found for username %s", username)) 644 increaseBar(pipelinesBar, pipelinesBarMutex) 645 continue 646 } 647 usernamespace := framework.UserNamespace 648 componentName := componentForUser(username) 649 if componentName == "" { 650 logError(12, fmt.Sprintf("Component not found for username %s", username)) 651 increaseBar(pipelinesBar, pipelinesBarMutex) 652 continue 653 } 654 applicationName := fmt.Sprintf("%s-app", username) 655 pipelineCreatedRetryInterval := time.Second * 5 656 pipelineCreatedTimeout := time.Minute * 15 657 var pipelineRun *v1beta1.PipelineRun 658 err := k8swait.Poll(pipelineCreatedRetryInterval, pipelineCreatedTimeout, func() (done bool, err error) { 659 pipelineRun, err = framework.AsKubeDeveloper.HasController.GetComponentPipelineRun(componentName, applicationName, usernamespace, "") 660 if err != nil { 661 time.Sleep(time.Millisecond * time.Duration(rand.IntnRange(10, 200))) 662 return false, nil 663 } 664 return true, nil 665 }) 666 if err != nil { 667 logError(13, fmt.Sprintf("PipelineRun for %s/%s has not been created within %v: %v", applicationName, componentName, pipelineCreatedTimeout, err)) 668 FailedPipelineRunsPerThread[threadIndex] += 1 669 increaseBar(pipelinesBar, pipelinesBarMutex) 670 continue 671 } 672 pipelineRunRetryInterval := time.Second * 5 673 pipelineRunTimeout := time.Minute * 60 674 err = k8swait.Poll(pipelineRunRetryInterval, pipelineRunTimeout, func() (done bool, err error) { 675 pipelineRun, err = framework.AsKubeDeveloper.HasController.GetComponentPipelineRun(componentName, applicationName, usernamespace, "") 676 if err != nil { 677 time.Sleep(time.Millisecond * time.Duration(rand.IntnRange(10, 200))) 678 return false, nil 679 } 680 if pipelineRun.IsDone() { 681 succeededCondition := pipelineRun.Status.GetCondition(apis.ConditionSucceeded) 682 if succeededCondition.IsFalse() { 683 dur := pipelineRun.Status.CompletionTime.Sub(pipelineRun.CreationTimestamp.Time) 684 PipelineRunFailedTimeSumPerThread[threadIndex] += dur 685 logError(14, fmt.Sprintf("Pipeline run for %s/%s failed due to %v: %v", applicationName, componentName, succeededCondition.Reason, succeededCondition.Message)) 686 FailedPipelineRunsPerThread[threadIndex] += 1 687 } else { 688 dur := pipelineRun.Status.CompletionTime.Sub(pipelineRun.CreationTimestamp.Time) 689 PipelineRunSucceededTimeSumPerThread[threadIndex] += dur 690 if dur > PipelineRunSucceededTimeMaxPerThread[threadIndex] { 691 PipelineRunSucceededTimeMaxPerThread[threadIndex] = dur 692 } 693 SuccessfulPipelineRunsPerThread[threadIndex] += 1 694 } 695 increaseBar(pipelinesBar, pipelinesBarMutex) 696 } 697 return pipelineRun.IsDone(), nil 698 }) 699 if err != nil { 700 logError(15, fmt.Sprintf("Pipeline run for %s/%s failed to succeed within %v: %v", applicationName, componentName, pipelineRunTimeout, err)) 701 FailedPipelineRunsPerThread[threadIndex] += 1 702 increaseBar(pipelinesBar, pipelinesBarMutex) 703 continue 704 } 705 } 706 }() 707 } 708 wg.Wait() 709 }