summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/buildtool/serve_api/serve_service/just_serve.proto35
-rw-r--r--src/buildtool/serve_api/serve_service/target.cpp109
-rw-r--r--src/buildtool/serve_api/serve_service/target.hpp23
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*/,