blob: 8131edf0a7a0a8a0507c88c0bb61a1cc296182c2 [file] [log] [blame]
#include "install/installer.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <filesystem> // NOLINT
#include <fstream>
#include <ios>
#include <string>
#include <system_error> // NOLINT
#include <vector>
#include "absl/cleanup/cleanup.h"
#include "absl/log/log.h"
#include "absl/strings/substitute.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "nlohmann/json.hpp"
namespace bloom_install {
using ::bloom_install::internal::StatusReporter;
// LINT.IfChange
constexpr char kStatusKeyStatus[] = "status";
constexpr char kStatusKeyPercentComplete[] = "percent_complete";
constexpr char kStatusKeyMessage[] = "message";
constexpr char kStatusKeyStartTime[] = "start_time";
constexpr char kStatusKeyLastUpdateTime[] = "last_update_time";
// LINT.ThenChange(//depot/google3/third_party/milotic/external/cc/tlbmc/redfish/routes/task_service.cc)
ProgressLogger::ProgressLogger(const std::string& log_file_path)
: log_file_path_(log_file_path),
log_file_(log_file_path_, std::ios_base::app),
is_healthy_(log_file_.is_open()) {}
bool ProgressLogger::IsHealthy() const { return is_healthy_; }
void ProgressLogger::Log(State state, absl::Duration elapsed_time,
const std::string& message, LogCode code,
int retries) {
if (!is_healthy_) {
return;
}
nlohmann::json log_entry;
log_entry["state"] = StateToString(state);
log_entry["time"] = absl::ToDoubleSeconds(elapsed_time);
log_entry["message"] = message;
log_entry["code"] = LogCodeToString(code);
if (state == State::kFailureRetry) {
log_entry["retries"] = retries;
}
log_file_ << log_entry.dump() << "\n";
log_file_.flush();
}
namespace internal {
StatusReporter StatusReporter::Create(const std::string& status_path,
int total_packages) {
return StatusReporter(status_path, total_packages);
}
StatusReporter::StatusReporter(const std::string& status_path,
int total_packages)
: status_path_(status_path), total_packages_(std::max(total_packages, 1)) {}
void StatusReporter::Start(const std::string& message) {
status_json_[kStatusKeyStatus] = "New";
status_json_[kStatusKeyPercentComplete] = 0;
status_json_[kStatusKeyMessage] = message;
const std::string now_str = absl::FormatTime(absl::Now());
status_json_[kStatusKeyStartTime] = now_str;
status_json_[kStatusKeyLastUpdateTime] = now_str;
WriteStatus();
}
void StatusReporter::RecordSuccess(const std::string& message) {
if (packages_completed_ < total_packages_) {
packages_completed_++;
}
UpdateProgress(message);
}
void StatusReporter::UpdateProgress(const std::string& message) {
status_json_[kStatusKeyPercentComplete] =
std::round((packages_completed_ * 100.0) / total_packages_);
status_json_[kStatusKeyMessage] = message;
status_json_[kStatusKeyLastUpdateTime] = absl::FormatTime(absl::Now());
WriteStatus();
}
void StatusReporter::Complete(bool success, const std::string& message) {
status_json_[kStatusKeyStatus] = success ? "Completed" : "Exception";
if (success) {
status_json_[kStatusKeyPercentComplete] = 100;
}
status_json_[kStatusKeyMessage] = message;
status_json_[kStatusKeyLastUpdateTime] = absl::FormatTime(absl::Now());
WriteStatus();
}
void StatusReporter::WriteStatus() {
const std::string temp_path = status_path_ + ".tmp";
{
std::ofstream temp_file(temp_path);
if (!temp_file.is_open()) {
return;
}
temp_file << status_json_.dump(4);
}
std::error_code ec;
if (std::filesystem::rename(temp_path, status_path_, ec); ec) {
std::filesystem::remove(temp_path);
}
}
} // namespace internal
Installer::Installer(const InstallerOptions& options)
: options_(options),
logger_(options.log_path),
status_reporter_(StatusReporter::Create(
options.status_path, static_cast<int>(options.packages.size()))) {
if (options.packages.empty()) {
LOG(WARNING) << "No packages to install. Post install or activation "
"check will succeed with no-op.";
}
}
bool Installer::PostInstall() {
absl::Time total_start_time = absl::Now();
if (options_.packages.empty()) {
const std::string complete_msg =
"No packages to install. Process complete.";
logger_.Log(State::kSuccess, absl::Now() - total_start_time, complete_msg,
LogCode::kInstallComplete);
return true;
}
const std::string start_msg = "Starting installation process.";
logger_.Log(State::kStarting, absl::ZeroDuration(), start_msg,
LogCode::kInitStart);
status_reporter_.Start(start_msg);
int packages_completed_for_logging = 0;
for (const auto& package : options_.packages) {
if (!InstallPackage(package)) {
const std::string failure_msg =
absl::Substitute("Installation failed on package: $0", package.name);
absl::Duration elapsed = absl::Now() - total_start_time;
logger_.Log(
State::kFailureFatal, elapsed,
absl::Substitute("Installation halted due to failure in package: $0",
package.name),
LogCode::kInstallHalt);
status_reporter_.Complete(false, failure_msg);
return false;
}
// Persist the activation check script to the activation check directory.
std::filesystem::path activation_check_script_path =
options_.activation_check_dir;
activation_check_script_path /= package.name;
std::error_code ec;
if (std::filesystem::create_directories(activation_check_script_path, ec);
ec) {
const std::string failure_msg = absl::Substitute(
"Could not create activation check script directory: $0 with error: "
"$1",
activation_check_script_path.c_str(), ec.message());
logger_.Log(State::kFailureFatal, absl::Now() - total_start_time,
failure_msg, LogCode::kInstallHalt);
status_reporter_.Complete(false, failure_msg);
return false;
}
activation_check_script_path /= "activation_check.sh";
ec.clear();
if (std::filesystem::copy(
package.activation_script_path, activation_check_script_path,
std::filesystem::copy_options::overwrite_existing, ec);
ec) {
const std::string failure_msg = absl::Substitute(
"Could not copy activation check script: $0 to: $1 with error: $2",
package.activation_script_path, activation_check_script_path.c_str(),
ec.message());
logger_.Log(State::kFailureFatal, absl::Now() - total_start_time,
failure_msg, LogCode::kInstallHalt);
status_reporter_.Complete(false, failure_msg);
return false;
}
packages_completed_for_logging++;
const std::string progress_msg = absl::Substitute(
"Successfully completed package $0 ($1 of $2).", package.name,
packages_completed_for_logging, options_.packages.size());
status_reporter_.RecordSuccess(progress_msg);
}
const std::string success_msg = "All packages installed successfully.";
absl::Duration elapsed = absl::Now() - total_start_time;
logger_.Log(State::kSuccess, elapsed, success_msg, LogCode::kInstallComplete);
status_reporter_.Complete(true, success_msg);
return true;
}
bool Installer::InstallPackage(const Package& package) {
absl::Time check_start_time = absl::Now();
logger_.Log(
State::kCheckingActivation, absl::ZeroDuration(),
absl::Substitute("Checking activation for package: $0", package.name),
LogCode::kPkgActivationCheckStart);
int activation_return_code =
std::system(package.activation_script_path.c_str());
absl::Duration check_elapsed = absl::Now() - check_start_time;
if (activation_return_code == 0) {
logger_.Log(
State::kSkipped, check_elapsed,
absl::Substitute("Package already active, skipping installation: $0",
package.name),
LogCode::kPkgActivationSkip);
return true;
}
logger_.Log(State::kInstalling, absl::ZeroDuration(),
absl::Substitute(
"Package not active, proceeding with installation for: $0",
package.name),
LogCode::kPkgInstallProceed);
for (int attempt = 0; attempt <= options_.max_retries; ++attempt) {
absl::Time attempt_start_time = absl::Now();
logger_.Log(
State::kInstalling, absl::ZeroDuration(),
absl::Substitute("Attempting to install package: $0", package.name),
LogCode::kPkgInstallStart);
int return_code = std::system(package.install_script_path.c_str());
absl::Duration elapsed = absl::Now() - attempt_start_time;
if (return_code == 0) {
logger_.Log(
State::kSuccess, elapsed,
absl::Substitute("Successfully installed package: $0", package.name),
LogCode::kPkgInstallSuccess);
return true;
}
if (attempt < options_.max_retries) {
logger_.Log(State::kFailureRetry, elapsed,
absl::Substitute("Failed to install package: $0. Retrying.",
package.name),
LogCode::kPkgInstallFailRetry, attempt + 1);
absl::SleepFor(absl::Seconds(2));
} else {
logger_.Log(
State::kFailureFatal, elapsed,
absl::Substitute("Fatal error installing package: $0", package.name),
LogCode::kPkgInstallFailFatal);
return false;
}
}
return false;
}
void Installer::ActivationCheck() {
for (const auto& package : options_.packages) {
// Ignore the return code of the activation script so that all packages are
// checked.
std::system(package.activation_script_path.c_str());
}
}
int ActivationMain(const std::string& activation_check_dir) {
std::vector<Package> packages;
std::error_code ec;
for (const auto& entry :
std::filesystem::directory_iterator(activation_check_dir, ec)) {
if (!ec && entry.is_directory() &&
std::filesystem::exists(entry.path() / "activation_check.sh")) {
Package package;
package.name = entry.path().filename().string();
package.activation_script_path = entry.path() / "activation_check.sh";
packages.push_back(package);
}
}
InstallerOptions options;
options.packages = packages;
Installer installer(options);
installer.ActivationCheck();
LOG(INFO) << "Activation check completed successfully.";
return 0;
}
std::vector<Package> ParsePostInstallPackages(
const std::string& tar_file_path) {
std::vector<Package> packages;
std::filesystem::path untar_dir = tar_file_path;
untar_dir.remove_filename();
untar_dir /= "firmware_bundle";
LOG(INFO) << "Firmware bundle directory: " << untar_dir;
std::error_code ec;
for (const auto& entry : std::filesystem::directory_iterator(untar_dir, ec)) {
if (!ec && entry.is_directory() &&
std::filesystem::exists(entry.path() / "post_install.sh")) {
LOG(INFO) << "Found package: " << entry.path().filename().string();
Package package;
package.name = entry.path().filename().string();
package.activation_script_path = entry.path() / "activation_check.sh";
package.install_script_path = entry.path() / "post_install.sh";
packages.push_back(package);
}
}
return packages;
}
int PostInstallMain(const std::string& inventory_dir,
const std::string& install_lock_file_path,
const std::string& bundle_tar_file_path,
const std::string& activation_check_dir) {
std::vector<Package> packages =
ParsePostInstallPackages(bundle_tar_file_path);
InstallerOptions options;
// Check if an installation is already running.
std::filesystem::path install_lock_file = install_lock_file_path;
if (std::error_code ec; std::filesystem::exists(install_lock_file, ec)) {
LOG(ERROR) << "Installation is already in progress. Lock file found: "
<< install_lock_file;
return 1;
}
std::error_code ec;
if (std::filesystem::create_directories(inventory_dir, ec); ec) {
LOG(ERROR) << "Error: Could not create installation directory: "
<< inventory_dir << " with error: " << ec.message();
return 1;
}
ec.clear();
if (std::filesystem::create_directories(install_lock_file.parent_path(), ec);
ec) {
LOG(ERROR) << "Error: Could not create installation lock file directory: "
<< install_lock_file.parent_path()
<< " with error: " << ec.message();
return 1;
}
// Create the installation lock file to signal that an install is starting.
std::ofstream lock_file_stream(install_lock_file);
if (!lock_file_stream) {
LOG(ERROR) << "Error: Could not create lock file: " << install_lock_file
<< " with error: " << lock_file_stream.rdstate();
return 1;
}
absl::Cleanup lock_file_cleanup = [install_lock_file] {
std::error_code ec;
if (std::filesystem::remove(install_lock_file, ec); ec) {
LOG(ERROR) << "Error: Could not remove lock file: " << install_lock_file;
}
LOG(INFO) << "Successfully removed lock file: " << install_lock_file;
};
LOG(INFO) << "Successfully created lock file: " << install_lock_file;
// Now run install
std::filesystem::path bundle_tar_file = bundle_tar_file_path;
std::vector<Package> packages_to_install =
ParsePostInstallPackages(bundle_tar_file);
options.packages = packages_to_install;
options.max_retries = 2;
std::filesystem::path log_path = inventory_dir;
log_path.remove_filename();
log_path /= "progress_internal.log";
options.log_path = log_path.c_str();
options.activation_check_dir = activation_check_dir;
Installer installer(options);
LOG(INFO) << "Starting installation process...";
if (!installer.PostInstall()) {
LOG(ERROR) << "Post install failed. Check log at " << log_path
<< " for details.";
return 1;
}
LOG(INFO) << "Post install completed successfully.";
return 0;
}
} // namespace bloom_install