| // Copyright 2019 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| |
| import 'package:github/github.dart'; |
| import 'package:meta/meta.dart'; |
| |
| import '../../ci_yaml.dart'; |
| import '../foundation/providers.dart'; |
| import '../foundation/typedefs.dart'; |
| import '../foundation/utils.dart'; |
| import '../model/appengine/commit.dart'; |
| import '../model/appengine/task.dart'; |
| import '../request_handling/api_request_handler.dart'; |
| import '../request_handling/authentication.dart'; |
| import '../request_handling/body.dart'; |
| import '../service/buildbucket.dart'; |
| import '../service/config.dart'; |
| import '../service/datastore.dart'; |
| import '../service/logging.dart'; |
| import '../service/luci.dart'; |
| import '../service/luci_build_service.dart'; |
| import '../service/scheduler.dart'; |
| |
| @immutable |
| class RefreshChromebotStatus extends ApiRequestHandler<Body> { |
| const RefreshChromebotStatus( |
| Config config, |
| AuthenticationProvider authenticationProvider, |
| this.luciBuildService, { |
| required this.scheduler, |
| @visibleForTesting LuciServiceProvider? luciServiceProvider, |
| @visibleForTesting DatastoreServiceProvider? datastoreProvider, |
| @visibleForTesting this.branchHttpClientProvider = Providers.freshHttpClient, |
| @visibleForTesting this.gitHubBackoffCalculator = twoSecondLinearBackoff, |
| }) : luciServiceProvider = luciServiceProvider ?? _createLuciService, |
| datastoreProvider = datastoreProvider ?? DatastoreService.defaultProvider, |
| super(config: config, authenticationProvider: authenticationProvider); |
| |
| final LuciBuildService luciBuildService; |
| final LuciServiceProvider luciServiceProvider; |
| final DatastoreServiceProvider datastoreProvider; |
| final HttpClientProvider branchHttpClientProvider; |
| final GitHubBackoffCalculator gitHubBackoffCalculator; |
| final Scheduler scheduler; |
| |
| static const String kRepoParam = 'repo'; |
| |
| static LuciService _createLuciService(ApiRequestHandler<dynamic> handler) { |
| return LuciService( |
| buildBucketClient: BuildBucketClient(), |
| config: handler.config, |
| clientContext: handler.authContext!.clientContext, |
| ); |
| } |
| |
| @override |
| Future<Body> get() async { |
| final String repoName = request!.uri.queryParameters[kRepoParam] ?? Config.flutterSlug.name; |
| final RepositorySlug slug = RepositorySlug('flutter', repoName); |
| final LuciService luciService = luciServiceProvider(this); |
| final DatastoreService datastore = datastoreProvider(config.db); |
| final Commit latestCommit = await datastore.queryRecentCommits(limit: 1, slug: slug).single; |
| final CiYaml ciYaml = await scheduler.getCiYaml(latestCommit); |
| final List<LuciBuilder> postsubmitBuilders = await scheduler.getPostSubmitBuilders(ciYaml); |
| final Map<BranchLuciBuilder, Map<String, List<LuciTask>>> luciTasks = await luciService.getBranchRecentTasks( |
| builders: postsubmitBuilders, |
| requireTaskName: true, |
| ); |
| log.fine('${luciTasks.keys.length} builders retrieved'); |
| |
| for (BranchLuciBuilder branchLuciBuilder in luciTasks.keys) { |
| await runTransactionWithRetries(() async { |
| await _updateStatus( |
| branchLuciBuilder.luciBuilder!, |
| branchLuciBuilder.branch, |
| datastore, |
| luciTasks[branchLuciBuilder], |
| slug, |
| ); |
| }); |
| } |
| return Body.empty; |
| } |
| |
| /// Update chromebot tasks statuses in datastore for [builder], |
| /// based on latest [luciTasks] statuses. |
| Future<void> _updateStatus( |
| LuciBuilder builder, |
| String? branch, |
| DatastoreService datastore, |
| Map<String, List<LuciTask>>? luciTasksMap, |
| RepositorySlug slug, |
| ) async { |
| final List<FullTask> datastoreTasks = await datastore |
| .queryRecentTasks( |
| taskName: builder.taskName, |
| branch: branch, |
| slug: slug, |
| ) |
| .toList(); |
| |
| /// Update [devicelabTask] when first [luciTask] run finishes. There may be |
| /// reruns for the same commit and same builder. Update [devicelabTask] |
| /// [builderNumberList] when luci rerun happens, and update [devicelabTask] |
| /// status when the status of latest luci run changes. |
| for (FullTask datastoreTask in datastoreTasks) { |
| final String commitSha = datastoreTask.commit.sha!; |
| if (!luciTasksMap!.containsKey(commitSha)) { |
| continue; |
| } |
| final List<LuciTask> luciTasks = luciTasksMap[commitSha]!; |
| final String buildNumberList = |
| luciTasks.reversed.map((LuciTask luciTask) => luciTask.buildNumber.toString()).toList().join(','); |
| final LuciTask latestLuciTask = luciTasks.first; |
| if (buildNumberList != datastoreTask.task.buildNumberList || latestLuciTask.status != datastoreTask.task.status) { |
| final Task update = datastoreTask.task; |
| update.status = latestLuciTask.status; |
| |
| final CiYaml ciYaml = await scheduler.getCiYaml(datastoreTask.commit); |
| final Target target = |
| ciYaml.postsubmitTargets.singleWhere((Target target) => target.value.name == datastoreTask.task.name); |
| |
| await luciBuildService.checkRerunBuilder( |
| commit: datastoreTask.commit, |
| target: target, |
| task: update, |
| datastore: datastore, |
| ); |
| |
| update.luciBucket = builder.flaky ?? false ? 'luci.flutter.staging' : 'luci.flutter.prod'; |
| update.buildNumberList = buildNumberList; |
| |
| await datastore.insert(<Task>[update]); |
| |
| // Save luci task record to BigQuery only when task finishes. |
| if (update.status == Task.statusFailed || update.status == Task.statusSucceeded) { |
| await _insertBigquery(update); |
| } |
| } |
| } |
| } |
| |
| Future<void> _insertBigquery(Task task) async { |
| const String bigqueryTableName = 'Task'; |
| final Map<String, dynamic> bigqueryData = <String, dynamic>{ |
| 'ID': task.commitKey?.id, |
| 'CreateTimestamp': task.createTimestamp, |
| 'StartTimestamp': task.startTimestamp, |
| 'EndTimestamp': task.endTimestamp, |
| 'Name': task.name, |
| 'Attempts': task.attempts, |
| 'IsFlaky': task.isFlaky, |
| 'TimeoutInMinutes': task.timeoutInMinutes ?? 0, |
| 'RequiredCapabilities': task.requiredCapabilities ?? <String>[], |
| 'ReservedForAgentID': task.reservedForAgentId, |
| 'StageName': task.stageName ?? 'unknown', |
| 'Status': task.status, |
| }; |
| await insertBigquery(bigqueryTableName, bigqueryData, await config.createTabledataResourceApi()); |
| } |
| } |