Skip to content

Commit

Permalink
Use ignore_installed option in transaction replay
Browse files Browse the repository at this point in the history
It adds checks for expected packages in transaction replay producing
either a warning or an error if something is missing.

This matches the dnf4 implementation.
  • Loading branch information
kontura committed May 22, 2024
1 parent ad30b66 commit 2cd537d
Showing 1 changed file with 154 additions and 1 deletion.
155 changes: 154 additions & 1 deletion libdnf5/base/goal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,7 @@ GoalProblem Goal::Impl::add_replay_to_goal(
const transaction::TransactionReplay & replay,
GoalJobSettings settings,
std::filesystem::path replay_location) {
auto ret = GoalProblem::NO_PROBLEM;
bool skip_unavailable = settings.resolve_skip_unavailable(base->get_config());

std::unordered_set<std::string> rpm_nevra_cache;
Expand All @@ -661,6 +662,17 @@ GoalProblem Goal::Impl::add_replay_to_goal(
rpm_nevra_cache.insert(package_replay.nevra);
libdnf5::GoalJobSettings settings_per_package = settings;
settings_per_package.set_clean_requirements_on_remove(libdnf5::GoalSetting::SET_FALSE);
const auto nevras = rpm::Nevra::parse(package_replay.nevra, {rpm::Nevra::Form::NEVRA});
libdnf_assert(
nevras.size() > 0, "Cannot parse rpm nevra \"{}\" while replaying transaction.", package_replay.nevra);

rpm::PackageQuery query_installed_na(base);
query_installed_na.filter_name(nevras[0].get_name());
query_installed_na.filter_arch(nevras[0].get_arch());
query_installed_na.filter_installed();
auto query_installed = query_installed_na;
query_installed.filter_nevra(package_replay.nevra);

if (!package_replay.repo_id.empty()) {
repo::RepoQuery enabled_repos(base);
enabled_repos.filter_enabled(true);
Expand All @@ -679,6 +691,54 @@ GoalProblem Goal::Impl::add_replay_to_goal(
if (package_replay.action == transaction::TransactionItemAction::UPGRADE ||
package_replay.action == transaction::TransactionItemAction::INSTALL ||
package_replay.action == transaction::TransactionItemAction::DOWNGRADE) {
// In order to properly report an error when another version of a package with action INSTALL is already
// installed we have to verify several conditions.
// - There is another versions installed for this package (name-arch).
// - The package isn't installonly.
// - The transaction doesn't contain an outbound action for this name-arch.
// This could happend during transaction reverting because upgrade/downgrade/reinstall (and obsoleting) actions are reverted as a REMOVE.
// For example upgrade transaction: [a-2 Upgrade, a-1 Replaced] is reverted to [a-2 Remove, a-1 Install].
// It is because we don't store the "replaces" relationship in history DB (there is a table `item_replaced_by`, but it is not populated
// and it doesn't seem worth it to populate it because of this use case) so we don't know which action to pick. We could try to guess
// based on the transaction packages but this approach seems easier.
if (package_replay.action == transaction::TransactionItemAction::INSTALL) {
bool has_counterpart_action = false;
for (const auto & installed_na : query_installed_na) {
has_counterpart_action |=
std::find_if(replay.packages.begin(), replay.packages.end(), [&installed_na](const auto & r) {
return r.nevra == installed_na.get_nevra() && transaction_item_action_is_outbound(r.action);
}) != replay.packages.end();
if (has_counterpart_action) {
break;
}
}
if (!has_counterpart_action) {
auto is_installonly = query_installed_na;
is_installonly.filter_installonly();

// the package is not installonly
if (!query_installed_na.empty() && is_installonly.empty()) {
auto problem = query_installed.empty() ? GoalProblem::INSTALLED_IN_DIFFERENT_VERSION
: GoalProblem::ALREADY_INSTALLED;
auto log_level = libdnf5::Logger::Level::WARNING;
if (!settings.get_ignore_installed()) {
log_level = libdnf5::Logger::Level::ERROR;
ret = problem;
}

transaction.p_impl->add_resolve_log(
GoalAction::REPLAY,
problem,
settings,
libdnf5::transaction::TransactionItemType::PACKAGE,
package_replay.nevra,
{transaction_item_action_to_string(package_replay.action)},
log_level);
continue;
}
}
}

if (local_pkg) {
add_rpm_ids(GoalAction::INSTALL, *local_pkg, settings_per_package);
} else {
Expand All @@ -693,13 +753,38 @@ GoalProblem Goal::Impl::add_replay_to_goal(
}
transaction.p_impl->rpm_reason_overrides[package_replay.nevra] = package_replay.reason;
} else if (package_replay.action == transaction::TransactionItemAction::REMOVE) {
if (query_installed.empty()) {
auto problem = transaction.p_impl->report_not_found(
GoalAction::REPLAY,
package_replay.nevra,
settings,
settings.get_ignore_installed() ? libdnf5::Logger::Level::WARNING : libdnf5::Logger::Level::ERROR,
{transaction_item_action_to_string(package_replay.action)});
if (!settings.get_ignore_installed()) {
ret |= problem;
}
continue;
}

if (local_pkg) {
add_rpm_ids(GoalAction::REMOVE, *local_pkg, settings_per_package);
} else {
rpm_specs.emplace_back(GoalAction::REMOVE, package_replay.nevra, settings_per_package);
}
transaction.p_impl->rpm_reason_overrides[package_replay.nevra] = package_replay.reason;
} else if (package_replay.action == transaction::TransactionItemAction::REPLACED) {
if (query_installed.empty()) {
auto problem = transaction.p_impl->report_not_found(
GoalAction::REPLAY,
package_replay.nevra,
settings,
settings.get_ignore_installed() ? libdnf5::Logger::Level::WARNING : libdnf5::Logger::Level::ERROR,
{transaction_item_action_to_string(package_replay.action)});
if (!settings.get_ignore_installed()) {
ret |= problem;
}
continue;
}
// Removing the original versions (the reverse part of an action like e.g. Upgrade) is more robust,
// but we can't do it if skip_unavailable is set because if the inbound action is skipped we would
// simply remove the package.
Expand Down Expand Up @@ -739,13 +824,47 @@ GoalProblem Goal::Impl::add_replay_to_goal(
base->get_repo_sack()->add_stored_transaction_comps(replay_location / group_replay.group_path);
}

comps::GroupQuery group_query_installed(base);
group_query_installed.filter_groupid(group_replay.group_id);
group_query_installed.filter_installed(true);

if (group_replay.action == transaction::TransactionItemAction::INSTALL) {
group_specs.emplace_back(
GoalAction::INSTALL, group_replay.reason, group_replay.group_id, settings_per_group);
} else if (group_replay.action == transaction::TransactionItemAction::UPGRADE) {
if (group_query_installed.empty()) {
auto log_level = libdnf5::Logger::Level::WARNING;
if (!settings.get_ignore_installed()) {
log_level = libdnf5::Logger::Level::ERROR;
ret = GoalProblem::NOT_INSTALLED;
}
transaction.p_impl->add_resolve_log(
GoalAction::REPLAY,
GoalProblem::NOT_INSTALLED,
settings,
libdnf5::transaction::TransactionItemType::GROUP,
group_replay.group_id,
{transaction_item_action_to_string(group_replay.action)},
log_level);
}
group_specs.emplace_back(
GoalAction::UPGRADE, group_replay.reason, group_replay.group_id, settings_per_group);
} else if (group_replay.action == transaction::TransactionItemAction::REMOVE) {
if (group_query_installed.empty()) {
auto log_level = libdnf5::Logger::Level::WARNING;
if (!settings.get_ignore_installed()) {
log_level = libdnf5::Logger::Level::ERROR;
ret = GoalProblem::NOT_INSTALLED;
}
transaction.p_impl->add_resolve_log(
GoalAction::REPLAY,
GoalProblem::NOT_INSTALLED,
settings,
libdnf5::transaction::TransactionItemType::GROUP,
group_replay.group_id,
{transaction_item_action_to_string(group_replay.action)},
log_level);
}
group_specs.emplace_back(
GoalAction::REMOVE, group_replay.reason, group_replay.group_id, settings_per_group);
} else {
Expand All @@ -769,6 +888,10 @@ GoalProblem Goal::Impl::add_replay_to_goal(
}
}

comps::EnvironmentQuery env_query_installed(base);
env_query_installed.filter_environmentid(env_replay.environment_id);
env_query_installed.filter_installed(true);

if (!env_replay.environment_path.empty()) {
base->get_repo_sack()->add_stored_transaction_comps(replay_location / env_replay.environment_path);
}
Expand All @@ -779,12 +902,42 @@ GoalProblem Goal::Impl::add_replay_to_goal(
env_replay.environment_id,
settings_per_environment);
} else if (env_replay.action == transaction::TransactionItemAction::UPGRADE) {
if (env_query_installed.empty()) {
auto log_level = libdnf5::Logger::Level::WARNING;
if (!settings.get_ignore_installed()) {
log_level = libdnf5::Logger::Level::ERROR;
ret = GoalProblem::NOT_INSTALLED;
}
transaction.p_impl->add_resolve_log(
GoalAction::REPLAY,
GoalProblem::NOT_INSTALLED,
settings,
libdnf5::transaction::TransactionItemType::ENVIRONMENT,
env_replay.environment_id,
{transaction_item_action_to_string(env_replay.action)},
log_level);
}
group_specs.emplace_back(
GoalAction::UPGRADE,
transaction::TransactionItemReason::USER,
env_replay.environment_id,
settings_per_environment);
} else if (env_replay.action == transaction::TransactionItemAction::REMOVE) {
if (env_query_installed.empty()) {
auto log_level = libdnf5::Logger::Level::WARNING;
if (!settings.get_ignore_installed()) {
log_level = libdnf5::Logger::Level::ERROR;
ret = GoalProblem::NOT_INSTALLED;
}
transaction.p_impl->add_resolve_log(
GoalAction::REPLAY,
GoalProblem::NOT_INSTALLED,
settings,
libdnf5::transaction::TransactionItemType::ENVIRONMENT,
env_replay.environment_id,
{transaction_item_action_to_string(env_replay.action)},
log_level);
}
group_specs.emplace_back(
GoalAction::REMOVE,
transaction::TransactionItemReason::USER,
Expand All @@ -796,7 +949,7 @@ GoalProblem Goal::Impl::add_replay_to_goal(
}
}

return GoalProblem::NO_PROBLEM;
return ret;
}

GoalProblem Goal::Impl::resolve_group_specs(std::vector<GroupSpec> & specs, base::Transaction & transaction) {
Expand Down

0 comments on commit 2cd537d

Please sign in to comment.