diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/buildtool/serve_api/serve_service/just_serve.proto | 35 | ||||
-rw-r--r-- | src/buildtool/serve_api/serve_service/target.cpp | 109 | ||||
-rw-r--r-- | src/buildtool/serve_api/serve_service/target.hpp | 23 |
3 files changed, 141 insertions, 26 deletions
diff --git a/src/buildtool/serve_api/serve_service/just_serve.proto b/src/buildtool/serve_api/serve_service/just_serve.proto index 198daa9d..c874e4fe 100644 --- a/src/buildtool/serve_api/serve_service/just_serve.proto +++ b/src/buildtool/serve_api/serve_service/just_serve.proto @@ -361,9 +361,13 @@ message ServeTargetRequest { message ServeTargetResponse { // Digest of the blob with the JSON object containing the target-cache value - // The implementation must guarantee that all the referenced objects are - // present in the remote CAS. + // on success. The implementation must guarantee that all the referenced + // objects are present in the remote CAS. build.bazel.remote.execution.v2.Digest target_value = 1; + + // Digest of the blob containing a log report relevant to the user, e.g., + // the error report for a failed analysis or build of a known target. + build.bazel.remote.execution.v2.Digest log = 2; } message ServeTargetVariablesRequest { @@ -399,16 +403,31 @@ message ServeTargetDescriptionResponse { } service Target { - // Given a target-level caching key, returns the computed value. In doing so, - // it can build on the associated end-point passing the + // Given a target-level caching key, returns the computed value. In doing + // so, it can build on the associated endpoint passing the // RemoteExecutionProperties contained in the ServeTargetRequest. + // The execution backend description, the resulting target cache value, + // and all other artifacts referenced therein MUST be made available in + // the CAS of the associated remote execution endpoint. // - // If the status has a code different from `OK`, the response MUST not be used. + // A failure to analyse or build a known target (i.e., a target for which + // we have all the needed information available) should NOT be reported as + // an error. Instead, the failure log should be uploaded as a blob to the + // CAS of the associated remote execution endpoint and its digest provided + // to the client in the response field `log`. In this case, the field + // `target_value` MUST not be set. + // + // If the status has a code different from `OK` or `NOT_FOUND`, the + // response MUST not be used. // // Errors: - // * `FAILED_PRECONDITION`: Failed to find required information in the CAS or - // the target cache key is malformed. - // * `UNAVAILABLE`: Could not communicate with the remote execution endpoint. + // * `NOT_FOUND`: Unknown target or missing needed local information. + // This should only be used for non-fatal failures. + // * `FAILED_PRECONDITION`: Required entries missing in the remote + // execution endpoint. + // * `UNAVAILABLE`: Could not communicate with the remote execution + // endpoint. + // * `INVALID_ARGUMENT`: The client provided invalid arguments in request. // * `INTERNAL`: Internally, something is very broken. rpc ServeTarget(ServeTargetRequest) returns (ServeTargetResponse) {} diff --git a/src/buildtool/serve_api/serve_service/target.cpp b/src/buildtool/serve_api/serve_service/target.cpp index 9cb0f384..67102c49 100644 --- a/src/buildtool/serve_api/serve_service/target.cpp +++ b/src/buildtool/serve_api/serve_service/target.cpp @@ -26,6 +26,7 @@ #include "src/buildtool/file_system/object_type.hpp" #include "src/buildtool/graph_traverser/graph_traverser.hpp" #include "src/buildtool/logging/log_level.hpp" +#include "src/buildtool/logging/log_sink_file.hpp" #include "src/buildtool/main/analyse.hpp" #include "src/buildtool/main/build_utils.hpp" #include "src/buildtool/multithreading/task_system.hpp" @@ -253,7 +254,7 @@ auto TargetService::ServeTarget( target_cache_key_digest.hash(), target_description_dict.ToJson().dump()); logger_->Emit(LogLevel::Error, msg); - return ::grpc::Status{::grpc::StatusCode::FAILED_PRECONDITION, msg}; + return ::grpc::Status{::grpc::StatusCode::NOT_FOUND, msg}; } std::string error_msg{}; // buffer to store various error messages @@ -276,8 +277,7 @@ auto TargetService::ServeTarget( if (!check_key("repo_key") or !check_key("target_name") or !check_key("effective_config")) { - return ::grpc::Status{::grpc::StatusCode::FAILED_PRECONDITION, - error_msg}; + return ::grpc::Status{::grpc::StatusCode::NOT_FOUND, error_msg}; } // get repository config blob path @@ -290,7 +290,7 @@ auto TargetService::ServeTarget( target_cache_key_digest.hash(), repo_key.ToJson().dump()); logger_->Emit(LogLevel::Error, msg); - return ::grpc::Status{::grpc::StatusCode::FAILED_PRECONDITION, msg}; + return ::grpc::Status{::grpc::StatusCode::NOT_FOUND, msg}; } ArtifactDigest repo_key_dgst{repo_key->String(), 0, /*is_tree=*/false}; if (!local_api_->IsAvailable(repo_key_dgst) and @@ -399,6 +399,16 @@ auto TargetService::ServeTarget( Statistics stats{}; Progress progress{}; + // setup logging for analysis and build; write into a temporary file + auto tmp_dir = StorageConfig::CreateTypedTmpDir("serve-target"); + if (!tmp_dir) { + auto msg = std::string("Could not create TmpDir"); + logger_->Emit(LogLevel::Error, msg); + return ::grpc::Status{::grpc::StatusCode::INTERNAL, msg}; + } + std::filesystem::path tmp_log{tmp_dir->GetPath() / "log"}; + Logger logger{"serve-target", {LogSinkFile::CreateFactory(tmp_log)}}; + // analyse the configured target auto result = AnalyseTarget(configured_target, &result_map, @@ -406,13 +416,37 @@ auto TargetService::ServeTarget( *tc, &stats, RemoteServeConfig::Jobs(), - std::nullopt /*request_action_input*/); + std::nullopt /*request_action_input*/, + &logger); if (not result) { + // report failure locally, to keep track of it... auto msg = fmt::format("Failed to analyse target {}", configured_target.target.ToString()); - logger_->Emit(LogLevel::Error, msg); - return ::grpc::Status{::grpc::StatusCode::FAILED_PRECONDITION, msg}; + logger_->Emit(LogLevel::Warning, msg); + // ...but try to give the client the proper log + auto const& cas = Storage::Instance().CAS(); + auto digest = cas.StoreBlob(tmp_log, /*is_executable=*/false); + if (not digest) { + auto msg = std::string( + "Failed to store log of failed analysis to local CAS"); + logger_->Emit(LogLevel::Error, msg); + return ::grpc::Status{::grpc::StatusCode::INTERNAL, msg}; + } + // upload log blob to remote + if (!local_api_->RetrieveToCas( + {Artifact::ObjectInfo{.digest = ArtifactDigest{*digest}, + .type = ObjectType::File}}, + &*remote_api_)) { + auto msg = fmt::format( + "Failed to upload to remote CAS the failed analysis log {}", + digest->hash()); + logger_->Emit(LogLevel::Error, msg); + return ::grpc::Status{::grpc::StatusCode::UNAVAILABLE, msg}; + } + // set response with log digest + response->mutable_log()->CopyFrom(*digest); + return ::grpc::Status::OK; } logger_->Emit(LogLevel::Info, "Analysed target {}", result->id.ToString()); @@ -421,7 +455,7 @@ auto TargetService::ServeTarget( // get the result map outputs auto const& [actions, blobs, trees] = - result_map.ToResult(&stats, &progress); + result_map.ToResult(&stats, &progress, &logger); // collect cache targets and artifacts for target-level caching auto const cache_targets = result_map.CacheTargets(); @@ -451,17 +485,41 @@ auto TargetService::ServeTarget( std::move(dispatch_list), &stats, &progress, - ProgressReporter::Reporter(&stats, &progress)}; + ProgressReporter::Reporter(&stats, &progress, &logger), + &logger}; // perform build auto build_result = traverser.BuildAndStage( artifacts, runfiles, actions, blobs, trees, std::move(cache_artifacts)); if (not build_result) { + // report failure locally, to keep track of it... auto msg = fmt::format("Build for target {} failed", configured_target.target.ToString()); - logger_->Emit(LogLevel::Error, msg); - return ::grpc::Status{::grpc::StatusCode::FAILED_PRECONDITION, msg}; + logger_->Emit(LogLevel::Warning, msg); + // ...but try to give the client the proper log + auto const& cas = Storage::Instance().CAS(); + auto digest = cas.StoreBlob(tmp_log, /*is_executable=*/false); + if (not digest) { + auto msg = + std::string("Failed to store log of failed build to local CAS"); + logger_->Emit(LogLevel::Error, msg); + return ::grpc::Status{::grpc::StatusCode::INTERNAL, msg}; + } + // upload log blob to remote + if (!local_api_->RetrieveToCas( + {Artifact::ObjectInfo{.digest = ArtifactDigest{*digest}, + .type = ObjectType::File}}, + &*remote_api_)) { + auto msg = fmt::format( + "Failed to upload to remote CAS the failed build log {}", + digest->hash()); + logger_->Emit(LogLevel::Error, msg); + return ::grpc::Status{::grpc::StatusCode::UNAVAILABLE, msg}; + } + // set response with log digest + response->mutable_log()->CopyFrom(*digest); + return ::grpc::Status::OK; } WriteTargetCacheEntries(cache_targets, @@ -470,14 +528,39 @@ auto TargetService::ServeTarget( traverser.GetLocalApi(), traverser.GetRemoteApi(), RemoteServeConfig::TCStrategy(), - *tc); + *tc, + &logger, + true /*strict_logging*/); if (build_result->failed_artifacts) { + // report failure locally, to keep track of it... auto msg = fmt::format("Build result for target {} contains failed artifacts ", configured_target.target.ToString()); logger_->Emit(LogLevel::Warning, msg); - return ::grpc::Status{::grpc::StatusCode::FAILED_PRECONDITION, msg}; + // ...but try to give the client the proper log + auto const& cas = Storage::Instance().CAS(); + auto digest = cas.StoreBlob(tmp_log, /*is_executable=*/false); + if (not digest) { + auto msg = std::string( + "Failed to store log of failed artifacts to local CAS"); + logger_->Emit(LogLevel::Error, msg); + return ::grpc::Status{::grpc::StatusCode::INTERNAL, msg}; + } + // upload log blob to remote + if (!local_api_->RetrieveToCas( + {Artifact::ObjectInfo{.digest = ArtifactDigest{*digest}, + .type = ObjectType::File}}, + &*remote_api_)) { + auto msg = fmt::format( + "Failed to upload to remote CAS the failed artifacts log {}", + digest->hash()); + logger_->Emit(LogLevel::Error, msg); + return ::grpc::Status{::grpc::StatusCode::UNAVAILABLE, msg}; + } + // set response with log digest + response->mutable_log()->CopyFrom(*digest); + return ::grpc::Status::OK; } // now that the target cache key is in, make sure remote CAS has all diff --git a/src/buildtool/serve_api/serve_service/target.hpp b/src/buildtool/serve_api/serve_service/target.hpp index df42a87b..09b8657b 100644 --- a/src/buildtool/serve_api/serve_service/target.hpp +++ b/src/buildtool/serve_api/serve_service/target.hpp @@ -37,17 +37,30 @@ class TargetService final : public justbuild::just_serve::Target::Service { public: // Given a target-level caching key, returns the computed value. In doing - // so, it can build on the associated end-point passing the + // so, it can build on the associated endpoint passing the // RemoteExecutionProperties contained in the ServeTargetRequest. + // The execution backend description, the resulting target cache value, + // and all other artifacts referenced therein MUST be made available in + // the CAS of the associated remote execution endpoint. // - // If the status has a code different from `OK`, the response MUST not be - // used. + // A failure to analyse or build a known target (i.e., a target for which + // we have all the needed information available) should NOT be reported as + // an error. Instead, the failure log should be uploaded as a blob to the + // CAS of the associated remote execution endpoint and its digest provided + // to the client in the response field `log`. In this case, the field + // `target_value` MUST not be set. + // + // If the status has a code different from `OK` or `NOT_FOUND`, the + // response MUST not be used. // // Errors: - // * `FAILED_PRECONDITION`: Failed to find required information in the CAS - // or the target cache key is malformed. + // * `NOT_FOUND`: Unknown target or missing needed local information. + // This should only be used for non-fatal failures. + // * `FAILED_PRECONDITION`: Required entries missing in the remote + // execution endpoint. // * `UNAVAILABLE`: Could not communicate with the remote execution // endpoint. + // * `INVALID_ARGUMENT`: The client provided invalid arguments in request. // * `INTERNAL`: Internally, something is very broken. auto ServeTarget( ::grpc::ServerContext* /*context*/, |