Skip to content

Commit

Permalink
rootfsmanager: Make use of the new storage usage func
Browse files Browse the repository at this point in the history
- Utilize the new storage usage functionality in `rootfsmanager`.
- Remove the previously used storage usage functionality.
- Use the path to the sysroot's ostree repo as the path to get storage
  usage statistic for.

Signed-off-by: Mike Sul <[email protected]>
  • Loading branch information
mike-sul committed Sep 2, 2023
1 parent 65b6501 commit 8b51296
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 79 deletions.
90 changes: 25 additions & 65 deletions src/rootfstreemanager.cc
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
#include "rootfstreemanager.h"

#include <fcntl.h>
#include <sys/statvfs.h>
#include <cstdio>

#include <boost/algorithm/hex.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>

#include "crypto/crypto.h"
#include "http/httpclient.h"
#include "ostree/repo.h"
#include "storage/invstorage.h"
#include "storage/stat.h"
#include "target.h"

RootfsTreeManager::Config::Config(const PackageConfig& pconfig) {
Expand Down Expand Up @@ -57,31 +53,33 @@ DownloadResult RootfsTreeManager::Download(const TufTarget& target) {
setRemote(remote.name, remote.baseUrl, remote.keys);
}

storage::Volume::UsageInfo pre_pull_usage_info{
storage::Volume::getUsageInfo(sysroot_->repoPath(), sysroot_->reservedStorageSpacePercentageDelta(),
OSTree::Sysroot::Config::ReservedStorageSpacePercentageDeltaParamName)};
if (!pre_pull_usage_info.isOk()) {
LOG_ERROR << "Failed to obtain storage usage statistic: " << pre_pull_usage_info.err;
}
DeltaStat delta_stat{};
if (getDeltaStatIfAvailable(target, remote, delta_stat)) {
LOG_INFO << "Found and pulled delta stats, checking if update can fit on a disk...";
try {
UpdateStat update_stat{};
bool ok{canDeltaFitOnDisk(delta_stat, update_stat)};
const auto stat_msg{boost::format{"required %u, available %u out of %u(%u%% of the volume capacity %u)"} %
update_stat.deltaSize % update_stat.available % update_stat.maxAvailable %
update_stat.highWatermark % update_stat.storageCapacity};

if (!ok) {
return {DownloadResult::Status::DownloadFailed_NoSpace,
"Insufficient storage available; err: " + stat_msg.str(), sysroot_->path()};
if (pre_pull_usage_info.isOk()) {
LOG_INFO << "Checking if update can fit on a disk...";
if (pre_pull_usage_info.available.first < delta_stat.uncompressedSize) {
return {
DownloadResult::Status::DownloadFailed_NoSpace,
"Insufficient storage available; " + pre_pull_usage_info.withRequired(delta_stat.uncompressedSize).str(),
sysroot_->repoPath()};
}
LOG_INFO << "Fetching static delta; " + stat_msg.str();
} catch (const std::exception& exc) {
LOG_ERROR << "Failed to check if the static delta can fit on a disk, skipping the update size check...; err: "
<< exc.what();
LOG_INFO << "Fetching ostree commit " + target.Sha256Hash() + " from " + remote.baseUrl;
LOG_INFO << "Sufficient free storage available; "
<< pre_pull_usage_info.withRequired(delta_stat.uncompressedSize);
} else {
LOG_INFO << "No storage usage statistic is available, skipping the update size check; "
<< pre_pull_usage_info.withRequired(delta_stat.uncompressedSize);
}
} else {
LOG_INFO << "No static delta or static delta stats are found, skipping the update size check...";
LOG_INFO << "Fetching ostree commit " + target.Sha256Hash() + " from " + remote.baseUrl;
LOG_INFO << "No static delta stats are found, skipping the update size check";
}

LOG_INFO << "Fetching ostree commit " + target.Sha256Hash() + " from " + remote.baseUrl;
pull_err = OstreeManager::pull(config.sysroot, remote.baseUrl, keys_, Target::fromTufTarget(target), nullptr,
prog_cb, remote.isRemoteSet ? nullptr : remote.name.c_str(), remote.headers);
if (pull_err.isSuccess()) {
Expand All @@ -98,9 +96,8 @@ DownloadResult RootfsTreeManager::Download(const TufTarget& target) {
// not enough storage space in the case of a static delta pull (pulling the delta parts/files)
(pull_err.description.find("Delta requires") != std::string::npos &&
pull_err.description.find("free space, but only") != std::string::npos)) {
res = {DownloadResult::Status::DownloadFailed_NoSpace,
"Insufficient storage available; path: " + config.sysroot.string() + "; err: " + pull_err.description,
sysroot_->path()};
res = {DownloadResult::Status::DownloadFailed_NoSpace, "Insufficient storage available; " + pull_err.description,
sysroot_->repoPath()};
break;
}
error_desc += pull_err.description + "\n";
Expand Down Expand Up @@ -301,37 +298,17 @@ bool RootfsTreeManager::getDeltaStatIfAvailable(const TufTarget& target, const R
}
LOG_INFO << "File with static delta stats has been downloaded, parsing it...";
if (!findDeltaStatForUpdate(delta_stats_json, getCurrentHash(), target.Sha256Hash(), delta_stat)) {
LOG_ERROR << "No delta stat found between " << getCurrentHash() << " and " << target.Sha256Hash();
LOG_ERROR << "No stat found for delta between " << getCurrentHash() << " and " << target.Sha256Hash();
return false;
}
LOG_INFO << "Found stat for delta between " << getCurrentHash() << " and " << target.Sha256Hash();
return true;
} catch (const std::exception& exc) {
LOG_ERROR << "Error occurred while getting static delta stats: " << exc.what();
}
return false;
}

bool RootfsTreeManager::canDeltaFitOnDisk(const DeltaStat& delta_stat, UpdateStat& update_stat) const {
StorageStat storage{};
getStorageStat(sysroot_->repoPath(), storage);
const auto highWatermark{100 - sysroot_->reservedStorageSpacePercentageDelta()};

const uint64_t max_blocks_available = std::floor(storage.blockNumb * (static_cast<double>(highWatermark) / 100));
const uint64_t blocks_in_use = storage.blockNumb - storage.freeBlockNumb;
const uint64_t max_blocks_available_for_update =
(max_blocks_available > blocks_in_use) ? (max_blocks_available - blocks_in_use) : 0;
const uint64_t blocks_required_by_delta = (delta_stat.uncompressedSize / storage.blockSize) +
((delta_stat.uncompressedSize % storage.blockSize != 0) ? 1 : 0);

update_stat.storageCapacity = storage.blockSize * storage.blockNumb;
update_stat.highWatermark = highWatermark;
update_stat.maxAvailable = max_blocks_available * storage.blockSize;
update_stat.available = max_blocks_available_for_update * storage.blockSize;
update_stat.deltaSize = delta_stat.uncompressedSize;

return blocks_required_by_delta <= max_blocks_available_for_update;
}

bool RootfsTreeManager::getDeltaStatsRef(const Json::Value& json, DeltaStatsRef& ref) {
if (!json.isMember("delta-stats")) {
return false;
Expand Down Expand Up @@ -416,20 +393,3 @@ bool RootfsTreeManager::findDeltaStatForUpdate(const Json::Value& delta_stats, c
found_delta_stat = {found_delta["size"].asUInt64(), found_delta["u_size"].asUInt64()};
return true;
}

void RootfsTreeManager::getStorageStat(const std::string& path, StorageStat& stat_out) {
int fd{-1};
if ((fd = open(path.c_str(), O_DIRECTORY | O_RDONLY)) == -1) {
throw std::runtime_error(std::string("Failed to obtain a sysroot directory file descriptor; path: ") + path +
", err: " + std::strerror(errno));
}
struct statvfs fs_stat {};
if (-1 == fstatvfs(fd, &fs_stat)) {
throw std::runtime_error(std::string("Failed to obtain statistic about the sysroot directory; path: ") + path +
", err: " + std::strerror(errno));
}

stat_out.freeBlockNumb = (getuid() != 0 ? fs_stat.f_bavail : fs_stat.f_bfree);
stat_out.blockNumb = fs_stat.f_blocks;
stat_out.blockSize = fs_stat.f_bsize; // f_frsize == f_bsize on the linux-based systems
}
9 changes: 0 additions & 9 deletions src/rootfstreemanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,6 @@ class RootfsTreeManager : public OstreeManager, public Downloader {
uint64_t freeBlockNumb;
uint64_t blockNumb;
};
struct UpdateStat {
uint64_t storageCapacity;
unsigned int highWatermark;
uint64_t maxAvailable;
uint64_t available;
uint64_t deltaSize;
};

std::string getCurrentHash() const override {
return sysroot_->getDeploymentHash(OSTree::Sysroot::Deployment::kCurrent);
Expand All @@ -77,13 +70,11 @@ class RootfsTreeManager : public OstreeManager, public Downloader {
void setRemote(const std::string& name, const std::string& url, const boost::optional<const KeyManager*>& keys);
data::InstallationResult verifyBootloaderUpdate(const Uptane::Target& target) const;
bool getDeltaStatIfAvailable(const TufTarget& target, const Remote& remote, DeltaStat& delta_stat) const;
bool canDeltaFitOnDisk(const DeltaStat& delta_stat, UpdateStat& update_stat) const;

static bool getDeltaStatsRef(const Json::Value& json, DeltaStatsRef& ref);
static Json::Value downloadDeltaStats(const DeltaStatsRef& ref, const Remote& remote);
static bool findDeltaStatForUpdate(const Json::Value& delta_stats, const std::string& from, const std::string& to,
DeltaStat& found_delta_stat);
static void getStorageStat(const std::string& path, StorageStat& stat_out);

const KeyManager& keys_;
std::shared_ptr<OSTree::Sysroot> sysroot_;
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/liteclienttest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ class ClientTest :virtual public ::testing::Test {
ASSERT_EQ(download_result.status, expected_download_result.status);
ASSERT_TRUE(download_result.description.find(expected_download_result.description) != std::string::npos) << "Actuall error message: " << download_result.description;
if (expected_download_result.noSpace()) {
ASSERT_EQ(download_result.destination_path, sys_repo_.getPath());
ASSERT_EQ(download_result.destination_path, sys_repo_.getRepo().getPath());
}
if (download_result) {
ASSERT_EQ(client.install(to), expected_install_code);
Expand Down
6 changes: 2 additions & 4 deletions tests/nospace_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -307,15 +307,13 @@ TEST_F(NoSpaceTest, OstreeUpdateNoSpaceIfStaticDeltaStats) {
ASSERT_TRUE(targetsMatch(client->getCurrent(), getInitialTarget()));
}
{
// not enough free blocks taking into account the default reserved 5%,
// (15 - 11 = 4) - 4% of blocks will be free after the update, we need 5%
// required 11%, free 15%, reserved 5% (default) -> available 10% < 11%
SetFreeBlockNumb(15, 100);
update(*client, getInitialTarget(), new_target, data::ResultCode::Numeric::kDownloadFailed,
{DownloadResult::Status::DownloadFailed_NoSpace, "Insufficient storage available"});
const auto events{device_gateway_.getEvents()};
const std::string event_err_msg{events[events.size() - 1]["event"]["details"].asString()};
ASSERT_TRUE(std::string::npos !=
event_err_msg.find("available 40960 out of 389120(95% of the volume capacity 409600)"))
ASSERT_TRUE(std::string::npos != event_err_msg.find("required: 42397B 11%, available: 40960B 10%"))
<< event_err_msg;
ASSERT_TRUE(targetsMatch(client->getCurrent(), getInitialTarget()));
}
Expand Down

0 comments on commit 8b51296

Please sign in to comment.