Skip to content

Commit

Permalink
Merge pull request #281 from foundriesio/install-modes
Browse files Browse the repository at this point in the history
api: POC: add installation modes
  • Loading branch information
mike-sul authored Nov 21, 2023
2 parents 2afacef + 21f25da commit e5106f2
Show file tree
Hide file tree
Showing 14 changed files with 189 additions and 62 deletions.
23 changes: 21 additions & 2 deletions include/aktualizr-lite/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class InstallResult {
Ok = 0,
OkBootFwNeedsCompletion,
NeedsCompletion,
AppsNeedCompletion,
BootFwNeedsCompletion,
Failed,
DownloadFailed,
Expand All @@ -116,7 +117,8 @@ class InstallResult {

// NOLINTNEXTLINE(hicpp-explicit-conversions,google-explicit-constructor)
operator bool() const {
return status == Status::Ok || status == Status::OkBootFwNeedsCompletion || status == Status::NeedsCompletion;
return status == Status::Ok || status == Status::OkBootFwNeedsCompletion || status == Status::NeedsCompletion ||
status == Status::AppsNeedCompletion;
}
};

Expand All @@ -143,6 +145,23 @@ class DownloadResult {
std::ostream &operator<<(std::ostream &os, const InstallResult &res);
std::ostream &operator<<(std::ostream &os, const DownloadResult &res);

/**
* The installation mode to be applied. Specified during InstallContext context initialization.
*/
enum class InstallMode {
/**
* A default install mode. Both Target's components ostree and Apps are fetched and installed
* within InstallContext::Install() call.
*/
All = 0,
/**
* Fetch both ostree and Apps, but only install ostree if it has been updated.
* The fetched Apps are installed and started during the finalization phase,
* which is executed by the AkliteClient::CompleteInstallation() call.
*/
OstreeOnly
};

class InstallContext {
public:
InstallContext(const InstallContext &) = delete;
Expand Down Expand Up @@ -306,7 +325,7 @@ class AkliteClient {
* Create an InstallContext object to help drive an update.
*/
std::unique_ptr<InstallContext> Installer(const TufTarget &t, std::string reason = "",
std::string correlation_id = "") const;
std::string correlation_id = "", InstallMode = InstallMode::All) const;

/**
* @brief Complete a pending installation
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ set(HEADERS helpers.h
yaml2json.h
target.h
downloader.h
installer.h
exec.h
cli/cli.h
../include/aktualizr-lite/api.h)
Expand Down
20 changes: 13 additions & 7 deletions src/api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -205,17 +205,22 @@ std::string AkliteClient::GetDeviceID() const { return client_->getDeviceID(); }

class LiteInstall : public InstallContext {
public:
LiteInstall(std::shared_ptr<LiteClient> client, std::unique_ptr<Uptane::Target> t, std::string& reason)
: client_(std::move(client)), target_(std::move(t)), reason_(reason) {}
LiteInstall(std::shared_ptr<LiteClient> client, std::unique_ptr<Uptane::Target> t, std::string& reason,
InstallMode install_mode = InstallMode::All)
: client_(std::move(client)), target_(std::move(t)), reason_(reason), mode_{install_mode} {}

InstallResult Install() override {
client_->logTarget("Installing: ", *target_);

auto rc = client_->install(*target_);
auto rc = client_->install(*target_, mode_);
auto status = InstallResult::Status::Failed;
if (rc == data::ResultCode::Numeric::kNeedCompletion) {
if (client_->isPendingTarget(*target_)) {
status = InstallResult::Status::NeedsCompletion;
if (client_->getCurrent().sha256Hash() == target_->sha256Hash()) {
status = InstallResult::Status::AppsNeedCompletion;
} else {
status = InstallResult::Status::NeedsCompletion;
}
} else {
// If the install returns `kNeedCompletion` and the target being installed is not pending,
// then it means that the previous boot fw update requires reboot prior to running the new target update
Expand Down Expand Up @@ -287,6 +292,7 @@ class LiteInstall : public InstallContext {
std::shared_ptr<LiteClient> client_;
std::unique_ptr<Uptane::Target> target_;
std::string reason_;
InstallMode mode_;
};

bool AkliteClient::IsInstallationInProgress() const { return client_->getPendingTarget().IsValid(); }
Expand All @@ -301,14 +307,14 @@ std::unique_ptr<InstallContext> AkliteClient::CheckAppsInSync() const {
auto correlation_id = target->custom_version() + "-" + boost::uuids::to_string(tmp);
target->setCorrelationId(correlation_id);
std::string reason = "Sync active target apps";
installer = std::make_unique<LiteInstall>(client_, std::move(target), reason);
installer = std::make_unique<LiteInstall>(client_, std::move(target), reason, InstallMode::All);
}
client_->setAppsNotChecked();
return installer;
}

std::unique_ptr<InstallContext> AkliteClient::Installer(const TufTarget& t, std::string reason,
std::string correlation_id) const {
std::string correlation_id, InstallMode install_mode) const {
if (read_only_) {
throw std::runtime_error("Can't perform this operation from read-only mode");
}
Expand Down Expand Up @@ -341,7 +347,7 @@ std::unique_ptr<InstallContext> AkliteClient::Installer(const TufTarget& t, std:
throw std::runtime_error("Correlation ID's must be less than 64 bytes");
}
target->setCorrelationId(correlation_id);
return std::make_unique<LiteInstall>(client_, std::move(target), reason);
return std::make_unique<LiteInstall>(client_, std::move(target), reason, install_mode);
}

InstallResult AkliteClient::CompleteInstallation() {
Expand Down
14 changes: 12 additions & 2 deletions src/cli/cli.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,21 @@ static const std::unordered_map<InstallResult::Status, StatusCode> i2s = {
{InstallResult::Status::Ok, StatusCode::Ok},
{InstallResult::Status::OkBootFwNeedsCompletion, StatusCode::OkNeedsRebootForBootFw},
{InstallResult::Status::NeedsCompletion, StatusCode::InstallNeedsReboot},
{InstallResult::Status::AppsNeedCompletion, StatusCode::InstallAppsNeedFinalization},
{InstallResult::Status::BootFwNeedsCompletion, StatusCode::InstallNeedsRebootForBootFw},
{InstallResult::Status::DownloadFailed, StatusCode::InstallAppPullFailure},
};

StatusCode Install(AkliteClient &client, int version, const std::string &target_name) {
StatusCode Install(AkliteClient &client, int version, const std::string &target_name, const std::string &install_mode) {
const static std::unordered_map<std::string, InstallMode> str2Mode = {{"delay-app-install", InstallMode::OstreeOnly}};
InstallMode mode{InstallMode::All};
if (!install_mode.empty()) {
if (str2Mode.count(install_mode) == 0) {
LOG_WARNING << "Unsupported installation mode: " << install_mode << "; falling back to the default install mode";
} else {
mode = str2Mode.at(install_mode);
}
}
// Check if the device is in a correct state to start a new update
if (client.IsInstallationInProgress()) {
LOG_ERROR << "Cannot start Target installation since there is ongoing installation; target: "
Expand Down Expand Up @@ -75,7 +85,7 @@ StatusCode Install(AkliteClient &client, int version, const std::string &target_
LOG_INFO << "To New Target: " << target.Name();
}

const auto installer = client.Installer(target);
const auto installer = client.Installer(target, "", "", mode);
if (installer == nullptr) {
LOG_ERROR << "Unexpected error: installer couldn't find Target in the DB; try again later";
return StatusCode::UnknownError;
Expand Down
4 changes: 3 additions & 1 deletion src/cli/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ enum class StatusCode {
InstallAppPullFailure = 80,
InstallNeedsRebootForBootFw = 90,
InstallNeedsReboot = 100,
InstallAppsNeedFinalization = 105,
InstallRollbackOk = 110,
InstallRollbackNeedsReboot = 120,
InstallRollbackFailed = 130,
};

StatusCode Install(AkliteClient &client, int version = -1, const std::string &target_name = "");
StatusCode Install(AkliteClient &client, int version = -1, const std::string &target_name = "",
const std::string &install_mode = "");
StatusCode CompleteInstall(AkliteClient &client);

} // namespace cli
Expand Down
13 changes: 12 additions & 1 deletion src/composeappmanager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,13 @@ TargetStatus ComposeAppManager::verifyTarget(const Uptane::Target& target) const
return ostree_target_status;
}

data::InstallationResult ComposeAppManager::Install(const TufTarget& target, InstallMode mode) {
if (mode == InstallMode::OstreeOnly) {
return RootfsTreeManager::Install(target, mode);
}
return install(Target::fromTufTarget(target));
}

data::InstallationResult ComposeAppManager::install(const Uptane::Target& target) const {
// Stopping disabled apps before creating or starting new apps
// because they may interfere with each other (e.g., using the same port).
Expand Down Expand Up @@ -432,7 +439,11 @@ data::InstallationResult ComposeAppManager::finalizeInstall(const Uptane::Target
// Stop disabled Apps before creating or starting new Apps since they may interfere with each other (e.g. the same
// port is used).
stopDisabledComposeApps(target);
LOG_INFO << "Starting Apps after successful boot on a new version of OSTree-based sysroot...";
if (ir.description != "Already booted on the required version") {
LOG_INFO << "Starting Apps after successful boot on a new version of OSTree-based sysroot...";
} else {
LOG_INFO << "Installing and starting Apps...";
}
// "finalize" (run) Apps that were pulled and created before reboot
for (const auto& app_pair : getApps(target)) {
const AppEngine::Result run_res = app_engine_->run({app_pair.first, app_pair.second});
Expand Down
1 change: 1 addition & 0 deletions src/composeappmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ComposeAppManager : public RootfsTreeManager {

std::string name() const override { return Name; }
DownloadResultWithStat Download(const TufTarget& target) override;
data::InstallationResult Install(const TufTarget& target, InstallMode mode) override;
bool fetchTarget(const Uptane::Target& target, Uptane::Fetcher& fetcher, const KeyManager& keys,
const FetcherProgressCb& progress_cb, const api::FlowControlToken* token) override;

Expand Down
22 changes: 22 additions & 0 deletions src/installer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef AKTUALIZR_LITE_INSTALLER_H_
#define AKTUALIZR_LITE_INSTALLER_H_

#include "aktualizr-lite/api.h"
#include "libaktualizr/types.h"

class Installer {
public:
// TODO: use the API's return type - InstallResult
virtual data::InstallationResult Install(const TufTarget& target, InstallMode mode) = 0;

virtual ~Installer() = default;
Installer(const Installer&) = delete;
Installer(const Installer&&) = delete;
Installer& operator=(const Installer&) = delete;
Installer& operator=(const Installer&&) = delete;

protected:
explicit Installer() = default;
};

#endif // AKTUALIZR_LITE_DOWNLOADER_H_
28 changes: 20 additions & 8 deletions src/liteclient.cc
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ LiteClient::LiteClient(Config& config_in, const AppEngine::Ptr& app_engine, cons
if (!downloader_) {
throw std::runtime_error("Invalid package manager: cannot cast to Downloader type");
}
installer_ = std::dynamic_pointer_cast<Installer>(package_manager_);
if (!downloader_) {
throw std::runtime_error("Invalid package manager: cannot cast to Installer type");
}
sysroot_ = ostree_sysroot;
}

Expand Down Expand Up @@ -474,10 +478,10 @@ void LiteClient::writeCurrentTarget(const Uptane::Target& t) const {
Utils::writeFile(config.storage.path / "current-target", ss.str());
}

data::InstallationResult LiteClient::installPackage(const Uptane::Target& target) {
data::InstallationResult LiteClient::installPackage(const Uptane::Target& target, InstallMode install_mode) {
LOG_INFO << "Installing package using " << package_manager_->name() << " package manager";
try {
return package_manager_->install(target);
return installer_->Install(Target::toTufTarget(target), install_mode);
} catch (std::exception& ex) {
return data::InstallationResult(data::ResultCode::Numeric::kInstallFailed, ex.what());
}
Expand Down Expand Up @@ -639,9 +643,9 @@ DownloadResultWithStat LiteClient::download(const Uptane::Target& target, const
return download_result;
}

data::ResultCode::Numeric LiteClient::install(const Uptane::Target& target) {
data::ResultCode::Numeric LiteClient::install(const Uptane::Target& target, InstallMode install_mode) {
notifyInstallStarted(target);
auto iresult = installPackage(target);
auto iresult = installPackage(target, install_mode);
if (iresult.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
LOG_INFO << "Update complete. Please reboot the device to activate";
is_reboot_required_ = (config.pacman.booted == BootedType::kBooted);
Expand All @@ -655,10 +659,18 @@ data::ResultCode::Numeric LiteClient::install(const Uptane::Target& target) {
storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kPending);
}
} else if (iresult.result_code.num_code == data::ResultCode::Numeric::kOk) {
LOG_INFO << "Update complete. No reboot needed";
storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kCurrent);
writeCurrentTarget(target);
updateRequestHeaders();
if (install_mode == InstallMode::OstreeOnly) {
// This is the case when the new Target updates just Apps and the ostree only mode is set.
// It means that the Apps update is downloaded but not installed.
LOG_INFO << "Apps have been downloaded. Run finalize to install and run them";
storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kPending);
iresult = {data::ResultCode::Numeric::kNeedCompletion, "finalization must be called to install and start Apps"};
} else {
LOG_INFO << "Update complete. No reboot needed";
storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kCurrent);
writeCurrentTarget(target);
updateRequestHeaders();
}
} else if (iresult.result_code.num_code == data::ResultCode::Numeric::kDownloadFailed) {
LOG_INFO << "Apps installation failed while the install process was trying to load App images to docker store,"
" will try the install again at the next update cycle.";
Expand Down
6 changes: 4 additions & 2 deletions src/liteclient.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class ReportEvent;
class ReportQueue;
class DownloadResult;
class Downloader;
class Installer;

class LiteClient {
public:
Expand All @@ -43,7 +44,7 @@ class LiteClient {
bool finalizeInstall(data::InstallationResult* ir = nullptr);
Uptane::Target getRollbackTarget();
DownloadResultWithStat download(const Uptane::Target& target, const std::string& reason);
data::ResultCode::Numeric install(const Uptane::Target& target);
data::ResultCode::Numeric install(const Uptane::Target& target, InstallMode install_mode = InstallMode::All);
void notifyInstallFinished(const Uptane::Target& t, data::InstallationResult& ir);
std::pair<bool, std::string> isRebootRequired() const {
return {is_reboot_required_, config.bootloader.reboot_command};
Expand Down Expand Up @@ -91,7 +92,7 @@ class LiteClient {
void notifyInstallStarted(const Uptane::Target& t);
void writeCurrentTarget(const Uptane::Target& t) const;

data::InstallationResult installPackage(const Uptane::Target& target);
data::InstallationResult installPackage(const Uptane::Target& target, InstallMode install_mode = InstallMode::All);
DownloadResultWithStat downloadImage(const Uptane::Target& target, const api::FlowControlToken* token = nullptr);
static void add_apps_header(std::vector<std::string>& headers, PackageConfig& config);
data::InstallationResult finalizePendingUpdate(boost::optional<Uptane::Target>& target);
Expand All @@ -113,6 +114,7 @@ class LiteClient {
std::vector<Uptane::Target> no_targets_;

std::shared_ptr<Downloader> downloader_;
std::shared_ptr<Installer> installer_;
Json::Value apps_state_;
const int report_queue_run_pause_s_{10};
const int report_queue_event_limit_{6};
Expand Down
43 changes: 6 additions & 37 deletions src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -222,42 +222,6 @@ static data::ResultCode::Numeric do_app_sync(LiteClient& client) {
return client.install(target);
}

static int update_main(LiteClient& client, const bpo::variables_map& variables_map) {
FileLock lock;
client.importRootMetaIfNeededAndPresent();
client.finalizeInstall();
Uptane::HardwareIdentifier hwid(client.config.provision.primary_ecu_hardware_id);

std::string version("latest");

if (variables_map.count("update-name") > 0) {
version = variables_map["update-name"].as<std::string>();
}

// This is only available if -DALLOW_MANUAL_ROLLBACK is set in the CLI args below.
if (variables_map.count("clear-installed-versions") > 0) {
LOG_WARNING << "Clearing installed version history!!!";
client.storage->clearInstalledVersions();
}

LOG_INFO << "Finding " << version << " to update to...";
auto find_target_res = find_target(client, hwid, client.tags, version);
if (find_target_res.first) {
std::string reason = "Manual update to " + version;
data::ResultCode::Numeric rc;
DownloadResultWithStat dr;
std::string cor_id;
std::tie(rc, dr, cor_id) = do_update(client, *find_target_res.second, reason);

return (rc == data::ResultCode::Numeric::kNeedCompletion || rc == data::ResultCode::Numeric::kOk) ? EXIT_SUCCESS
: EXIT_FAILURE;
} else {
LOG_INFO << "No Target found to update to; hw ID: " << hwid.ToString()
<< "; tags: " << boost::algorithm::join(client.tags, ",");
return EXIT_SUCCESS;
}
}

static int daemon_main(LiteClient& client, const bpo::variables_map& variables_map) {
FileLock lock;
if (client.config.uptane.repo_server.empty()) {
Expand Down Expand Up @@ -471,11 +435,15 @@ static int cli_install(LiteClient& client, const bpo::variables_map& params) {
target_name = version_str;
}
}
std::string install_mode;
if (params.count("install-mode") > 0) {
install_mode = params.at("install-mode").as<std::string>();
}

std::shared_ptr<LiteClient> client_ptr{&client, [](LiteClient* /*unused*/) {}};
AkliteClient akclient{client_ptr, false, true};

return static_cast<int>(cli::Install(akclient, version, target_name));
return static_cast<int>(cli::Install(akclient, version, target_name, install_mode));
}

static int cli_complete_install(LiteClient& client, const bpo::variables_map& params) {
Expand Down Expand Up @@ -534,6 +502,7 @@ bpo::variables_map parse_options(int argc, char** argv) {
("ostree-server", bpo::value<std::string>(), "URL of the Ostree repository")
("primary-ecu-hardware-id", bpo::value<std::string>(), "hardware ID of primary ecu")
("update-name", bpo::value<std::string>(), "optional name of the update when running \"update\". default=latest")
("install-mode", bpo::value<std::string>(), "Optional install mode. Supported modes: [delay-app-install]. By default both ostree and apps are installed before reboot")
#ifdef ALLOW_MANUAL_ROLLBACK
("clear-installed-versions", "DANGER - clear the history of installed updates before applying the given update. This is handy when doing test/debug and you need to rollback to an old version manually.")
#endif
Expand Down
4 changes: 4 additions & 0 deletions src/rootfstreemanager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ void RootfsTreeManager::installNotify(const Uptane::Target& target) {
OstreeManager::installNotify(target);
}

data::InstallationResult RootfsTreeManager::Install(const TufTarget& target, InstallMode /*mode*/) {
return RootfsTreeManager::install(Target::fromTufTarget(target));
}

data::InstallationResult RootfsTreeManager::install(const Uptane::Target& target) const {
data::InstallationResult res;
Uptane::Target current = OstreeManager::getCurrent();
Expand Down
Loading

0 comments on commit e5106f2

Please sign in to comment.