Add more error checking and log output to fetch_cvd
Includes:
- Reporting Build API error messages through to the user
- Enabling verbose debugging for curl internals
- Validating credentials were actually received with
"-credential_source=gce".
Bug: 137304531
Test: Run with some invalid flags.
Change-Id: Ic5483ff5a119e89b3d2072aff0b6cdadc2b4be46
diff --git a/host/commands/fetcher/build_api.cc b/host/commands/fetcher/build_api.cc
index 177c565..8dbf62b 100644
--- a/host/commands/fetcher/build_api.cc
+++ b/host/commands/fetcher/build_api.cc
@@ -52,6 +52,10 @@
crc32 = json_artifact["crc32"].asUInt();
}
+std::ostream& operator<<(std::ostream& out, const DeviceBuild& build) {
+ return out << "(id=\"" << build.id << "\", target=\"" << build.target << "\")";
+}
+
BuildApi::BuildApi(std::unique_ptr<CredentialSource> credential_source)
: credential_source(std::move(credential_source)) {}
@@ -68,8 +72,13 @@
std::string url = BUILD_API + "/builds?branch=" + branch
+ "&buildType=submitted&maxResults=1&successful=true&target=" + target;
auto response = curl.DownloadToJson(url, Headers());
- if (response["builds"].size() != 1) {
- LOG(WARNING) << "invalid number of builds\n";
+ CHECK(!response.isMember("error")) << "Error fetching the latest build of \""
+ << target << "\" on \"" << branch << "\". Response was " << response;
+
+ if (!response.isMember("builds") || response["builds"].size() != 1) {
+ LOG(WARNING) << "expected to receive 1 build for \"" << target << "\" on \""
+ << branch << "\", but received " << response["builds"].size()
+ << ". Full response was " << response;
return "";
}
return response["builds"][0]["buildId"].asString();
@@ -78,6 +87,9 @@
std::string BuildApi::BuildStatus(const DeviceBuild& build) {
std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target;
auto response_json = curl.DownloadToJson(url, Headers());
+ CHECK(!response_json.isMember("error")) << "Error fetching the status of "
+ << "build " << build << ". Response was " << response_json;
+
return response_json["buildAttemptStatus"].asString();
}
@@ -85,6 +97,9 @@
std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target
+ "/attempts/latest/artifacts?maxResults=1000";
auto artifacts_json = curl.DownloadToJson(url, Headers());
+ CHECK(!artifacts_json.isMember("error")) << "Error fetching the artifacts of "
+ << build << ". Response was " << artifacts_json;
+
std::vector<Artifact> artifacts;
for (const auto& artifact_json : artifacts_json["artifacts"]) {
artifacts.emplace_back(artifact_json);
@@ -119,24 +134,21 @@
if (branch_latest_build_id != "") {
LOG(INFO) << "The latest good build on branch \"" << branch_or_id
<< "\"with build target \"" << build_target
- << "is \"" << branch_latest_build_id << "\"";
+ << "\" is \"" << branch_latest_build_id << "\"";
build_id = branch_latest_build_id;
}
DeviceBuild proposed_build = DeviceBuild(build_id, build_target);
std::string status = build_api->BuildStatus(proposed_build);
if (status == "") {
- LOG(FATAL) << '"' << build_id << "\" with build target \""
- << build_target << "\" is not a valid branch or build id.";
+ LOG(FATAL) << proposed_build << " is not a valid branch or build id.";
}
- LOG(INFO) << "Status for build " << build_id << "/" << build_target
- << " is " << status;
+ LOG(INFO) << "Status for build " << proposed_build << " is " << status;
while (retry_period != std::chrono::seconds::zero() && !StatusIsTerminal(status)) {
LOG(INFO) << "Status is \"" << status << "\". Waiting for " << retry_period.count()
<< " seconds.";
std::this_thread::sleep_for(retry_period);
status = build_api->BuildStatus(proposed_build);
}
- LOG(INFO) << "Status for build " << build_id << "/" << build_target
- << " is " << status;
+ LOG(INFO) << "Status for build " << proposed_build << " is " << status;
return proposed_build;
}
diff --git a/host/commands/fetcher/build_api.h b/host/commands/fetcher/build_api.h
index 36aa81f..bb89e81 100644
--- a/host/commands/fetcher/build_api.h
+++ b/host/commands/fetcher/build_api.h
@@ -18,6 +18,7 @@
#include <chrono>
#include <functional>
#include <memory>
+#include <ostream>
#include <string>
#include "credential_source.h"
@@ -55,6 +56,8 @@
std::string target;
};
+std::ostream& operator<<(std::ostream&, const DeviceBuild&);
+
class BuildApi {
CurlWrapper curl;
std::unique_ptr<CredentialSource> credential_source;
diff --git a/host/commands/fetcher/credential_source.cc b/host/commands/fetcher/credential_source.cc
index 579272f..89d6b14 100644
--- a/host/commands/fetcher/credential_source.cc
+++ b/host/commands/fetcher/credential_source.cc
@@ -15,6 +15,8 @@
#include "credential_source.h"
+#include <glog/logging.h>
+
namespace {
std::chrono::steady_clock::duration REFRESH_WINDOW =
@@ -39,6 +41,16 @@
void GceMetadataCredentialSource::RefreshCredential() {
Json::Value credential_json =
curl.DownloadToJson(REFRESH_URL, {"Metadata-Flavor: Google"});
+
+ CHECK(!credential_json.isMember("error")) << "Error fetching credentials. " <<
+ "Response was " << credential_json;
+ bool has_access_token = credential_json.isMember("access_token");
+ bool has_expires_in = credential_json.isMember("expires_in");
+ if (!has_access_token || !has_expires_in) {
+ LOG(FATAL) << "GCE credential was missing access_token or expires_in. "
+ << "Full response was " << credential_json << "";
+ }
+
expiration = std::chrono::steady_clock::now()
+ std::chrono::seconds(credential_json["expires_in"].asInt());
latest_credential = credential_json["access_token"].asString();
diff --git a/host/commands/fetcher/curl_wrapper.cc b/host/commands/fetcher/curl_wrapper.cc
index 93dca5e..472963a 100644
--- a/host/commands/fetcher/curl_wrapper.cc
+++ b/host/commands/fetcher/curl_wrapper.cc
@@ -78,6 +78,9 @@
curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers);
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+ char error_buf[CURL_ERROR_SIZE];
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buf);
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
FILE* file = fopen(path.c_str(), "w");
if (!file) {
LOG(ERROR) << "could not open file " << path;
@@ -89,8 +92,11 @@
curl_slist_free_all(curl_headers);
}
fclose(file);
- if(res != CURLE_OK) {
- LOG(ERROR) << "curl_easy_perform() failed: " << curl_easy_strerror(res);
+ if (res != CURLE_OK) {
+ LOG(ERROR) << "curl_easy_perform() failed. "
+ << "Code was \"" << res << "\". "
+ << "Strerror was \"" << curl_easy_strerror(res) << "\". "
+ << "Error buffer was \"" << error_buf << "\".";
return false;
}
return true;
@@ -115,12 +121,18 @@
std::stringstream data;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, file_write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
+ char error_buf[CURL_ERROR_SIZE];
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buf);
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
CURLcode res = curl_easy_perform(curl);
if (curl_headers) {
curl_slist_free_all(curl_headers);
}
- if(res != CURLE_OK) {
- LOG(ERROR) << "curl_easy_perform() failed: " << curl_easy_strerror(res);
+ if (res != CURLE_OK) {
+ LOG(ERROR) << "curl_easy_perform() failed. "
+ << "Code was \"" << res << "\". "
+ << "Strerror was \"" << curl_easy_strerror(res) << "\". "
+ << "Error buffer was \"" << error_buf << "\".";
return "";
}
return data.str();
@@ -137,6 +149,8 @@
Json::Value json;
if (!reader.parse(contents, json)) {
LOG(ERROR) << "Could not parse json: " << reader.getFormattedErrorMessages();
+ json["error"] = "Failed to parse json.";
+ json["response"] = contents;
}
return json;
}
diff --git a/host/commands/fetcher/fetch_cvd.cc b/host/commands/fetcher/fetch_cvd.cc
index cf6c4b6..021bdca 100644
--- a/host/commands/fetcher/fetch_cvd.cc
+++ b/host/commands/fetcher/fetch_cvd.cc
@@ -74,7 +74,11 @@
return false;
}
std::string local_path = target_directory + "/" + img_zip_name;
- build_api->ArtifactToFile(build, img_zip_name, local_path);
+ if (!build_api->ArtifactToFile(build, img_zip_name, local_path)) {
+ LOG(ERROR) << "Unable to download " << build << ":" << img_zip_name << " to "
+ << local_path;
+ return false;
+ }
auto could_extract = ExtractImages(local_path, target_directory, images);
if (!could_extract) {
@@ -104,7 +108,13 @@
return false;
}
std::string local_path = target_directory + "/" + HOST_TOOLS;
- build_api->ArtifactToFile(build, HOST_TOOLS, local_path);
+
+ if (!build_api->ArtifactToFile(build, HOST_TOOLS, local_path)) {
+ LOG(ERROR) << "Unable to download " << build << ":" << HOST_TOOLS << " to "
+ << local_path;
+ return false;
+ }
+
if (cvd::execute({"/bin/tar", "xvf", local_path, "-C", target_directory}) != 0) {
LOG(FATAL) << "Could not extract " << local_path;
return false;
@@ -161,12 +171,10 @@
retry_period);
if (!download_host_package(&build_api, default_build, target_dir)) {
- LOG(FATAL) << "Could not download host package with target "
- << default_build.target << " and build id " << default_build.id;
+ LOG(FATAL) << "Could not download host package for " << default_build;
}
if (!download_images(&build_api, default_build, target_dir)) {
- LOG(FATAL) << "Could not download images with target "
- << default_build.target << " and build id " << default_build.id;
+ LOG(FATAL) << "Could not download images for " << default_build;
}
desparse(target_dir + "/userdata.img");
@@ -178,8 +186,7 @@
if (!download_images(&build_api, system_build, target_dir,
{"system.img"})) {
- LOG(FATAL) << "Could not download system image at target "
- << system_build.target << " and build id " << system_build.id;
+ LOG(FATAL) << "Could not download system image for " << system_build;
}
}
@@ -187,7 +194,10 @@
DeviceBuild kernel_build = ArgumentToBuild(&build_api, FLAGS_kernel_build,
"kernel", retry_period);
- build_api.ArtifactToFile(kernel_build, "bzImage", target_dir + "/kernel");
+ if (!build_api.ArtifactToFile(kernel_build, "bzImage", target_dir + "/kernel")) {
+ LOG(FATAL) << "Could not download " << kernel_build << ":bzImage to "
+ << target_dir + "/kernel";
+ }
}
}
curl_global_cleanup();