Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native AppStream support #87

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions accepted/00101-native-appstreams.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
- Feature Name: Native AppStream support
- Start Date: 2024-02-19

# Summary
[summary]: #summary

This RFC proposes native support for AppStreams repositories in Uyuni.

# Motivation
[motivation]: #motivation

Following the integration of [modularity and modular repositories](https://docs.fedoraproject.org/en-US/modularity/) in RHEL and its derivatives, Uyuni initially implemented modularity through content lifecycle management and the [introduction of AppStream filters](https://github.com/uyuni-project/uyuni-rfc/blob/master/accepted/00064-modular-repos-with-clm.md). These filters effectively remove modularity features from a repository, enabling consumption through the Uyuni UI. However, this approach introduced complexity and limited functionality, prompting the need for a more comprehensive solution.

The goal is to seamlessly support modularity in its native form within uyuni, ensuring a user-friendly and intuitive experience across all workflows involving modular repositories.

# Detailed design
[design]: #detailed-design

## Overview

The design encompasses core functionality, new UI operations, modifications to the existing CLM feature, and testing infrastructure.

## The core design

The core design involves three key components for data collection and filtering of packages for client consuption:

1. **Repo-sync**: A standalone Python module that extracts module metadata from source channels using `dnf`'s `libmodulemd` library during repo-sync, serving as the source of truth for the module information. As the data gathered during this process is critical to the feature, it must be executed as a single unit with the repo-sync ([See the PoC](https://github.com/cbbayburt/uyuni/commit/ed9391e8c6e0a66d1dd7cb0f3501332b0884f2f3)).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand on "as a single unit"? That sounds a bit like a transaction (succeed together or fail together), which we don't have in reposync.

The PoC script, as far as I can tell, works on already synced, imported, and linked packages. Is that correct? What are the downsides of doing it this way vs. doing it at a different time in the process?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new data we collect at this step is critical for modular repositories to work. If we somehow miss this data, Uyuni won't know if a package is really modular or not, and will treat it as a regular package. As a result, Uyuni will suggest wrong updates to existing packages (the initial problem we have with modularity).

Because of this, we have to make sure that module metadata is properly processed when syncing a repository. If not, the reposync for this channel should fail completely.

How do we currently make sure that the repos don't end up in an invalid state during reposync?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we currently make sure that the repos don't end up in an invalid state during reposync?

It seems we do it using proper exception handling and DB transactions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the module.yaml should be parsed first. I think during package import we call multiple times "commit" to solve problems with dependencies between packages which gets imported in parallel.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to do it in the end because we need package IDs for the relation.


2. **Package profile update**: A Salt state that retrieves current module information from clients, storing the data in the database. The state calls `dnf module` commands to retrieve the list of enabled module streams together with their *name*, *stream*, *version*, *context*, and *arch* values ([see the PoC](https://github.com/cbbayburt/uyuni/commit/2c788f3144f5bfe8ddd904045e0a757a7a432923)).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can, I'm not fully informed on this topic. So please excuse my ignorance.

Could you clarify how we currently installing/removing/updating packages—are we using yum or dnf ?

If we're using yum, will we stick with it for package operations, or are we considering transitioning to DNF?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nowadays we're using the terms yum and dnf interchangibly, but since RH8, yum is just a wrapper around dnf. We still have a lot of mentions of yum in our python modules, but Salt is actually using dnf.

Copy link
Contributor

@admd admd Mar 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you! So it's safe to say that for RHEL and clones, only DNF will be used at the end. Thank you Can.


3. **Package cache**: Incorporates data from repo-sync and package profile update into existing queries, ensuring proper filtering of non-enabled modular packages. The existing tables, queries and stored procedures will be updated with additional joins and proper indexing to ensure minimum performance impact.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a 4th point needed. You need to generate new metadata for the Channels including new compiled modules.yaml from the database informations.

A prototype showcasing the core functionality can be found [here](https://github.com/cbbayburt/uyuni/tree/native_appstreams_poc).

### ER diagram

The following figure shows the proposed ER diagram for the core design.

![ER diagram](images/00101-native-appstreams-er-diagram.png)

## New UI operations

The proposed UI includes a new page under the `System > Software` tab, visible only for clients with modular channels assigned. It displays available modules and their streams, allowing users to select and apply module changes to clients.

The main workflow is as follows:

1. Select an assigned modular channel.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be part of the change channels? Why we need a new place to define the assigned channels to a minions?
We should allow change to any, and auto-detect if the channels have modules enabled, should the "alternative" path

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every modular repository has its own set of modules and theoretically, a system can have more than one modular channel assigned. In such cases, the user must first pick a channel to see its modules.

But after writing this, I think we don't need this step. Instead, we can put together all the modules from all the assigned modular channels and present them in a single list.

I'll update this part accordingly.

2. Select enabled modules and their streams.
3. *Optional:* Run `mgr-libmod` for dependency resolution with real-time feedback.
Copy link
Contributor

@admd admd Mar 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I lack knowledge here so another question. What kind of dependency issue would you expect? Do streams depend on other streams or are they self-contained?

Ideally, we should have mechanism in place, so user doesn't get into bad state. Just like we do with required/recommended channels. Select all the needed stuff automagically when user select a module stream with option that experienced user can de-select.

But if that's asking for too much, this optional would be super helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a module may depend on other modules and they must be enabled together. If we miss this, dnf would complain about the missing dependencies. Other than that, mgr-libmod also checks for conflicting streams. If, for example the user tries to enable two streams of the same module, mgr-libmod would complain about this too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, in real world, dependency relations so far only exist in RH8. RH9 has a lot less modules than 8, and we haven't seen any dependent modules in 9 yet. RH8 is kind of an edge case of modularity 😂

4. Schedule an Uyuni action to apply the selection on the client.
5. The action calls `dnf` with the selection of module streams on the client via Salt.
6. On job return, update the selection of modules in the database.

To allow working on multiple clients at once, SSM will be used. In the SSM, the clients in the set will be first grouped by the modular channels they are assigned to. Then the user will select a group and makes a selection of modules that applies to the whole group.

## Content lifecycle management

The current CLM approach will be replaced with regular *ALLOW/DENY* logic in AppStream filters. During build, module filters will be translated to package filters that apply to all packages of the module. A module will only be included in the target repository if all its packages are visible.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means we would not be able to filter packages and is version inside a module? Would not make sense to have the module available if at least one package from a module is present?
This looks to be to restrictive to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In most cases, a module has multiple packages packed together to form an application. For example:

Packages in the Postgresql module:

  • pg_repack
  • pgaudit
  • postgres-decoderbufs
  • postgresql
  • postgresql-contrib
  • postgresql-docs
  • postgresql-plperl
  • postgresql-plpython3
  • postgresql-server
  • ...

so it usually makes no sense to have a module enabled if not all the packages are there.

OTOH, different versions of a module are separate module entries, so we can still implement filters to selectively filter different versions (using the module versions notation).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of scenarios where some packages in the module could be optional, and the user decided to exclude them in a filter. In this scenario, the module would never get available. Do you think a scenario like this one makes sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's done by "Profiles" in AppStreams. In the example above, postgresql module usually has at least 2 profiles, one named client and one named server. Depending on which profile is activated, the list of actual installed packages changes. But this needs careful consideration because the user might want to enable different profiles for different clients from the same repository.

So maybe the best way is to give the user the freedom indeed. If dnf fails because of some missing package, then it's up to them to fix it in CLM.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible problem: what happens if a customer want to allow only security updates and apply a filter like this?
Such an update do not match all all packages in a steam. We need to check what prevents to remove the whole stream incl. the security update.

Copy link
Contributor Author

@cbbayburt cbbayburt Mar 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also need to check how DNF behaves if some packages are missing in a module. Our strategy is to clone DNF's behavior as much as possible.


During repository metadata generation, `libmodulemd` will parse and index the modular metadata from the source channels and generate the modular metadata for the target repository following the rules enforced by the attached filters.

If all the modules are denied as a result of the enabled filters, no module metadata will be generated in the target repository.

## Testing

A "dummy" modular repository will be built in OBS for comprehensive testing in the CI environment.

Below is an example of a minimal test repository:

![Test repository](images/00101-native-appstreams-test-repo.png)

# Unresolved questions
[unresolved]: #unresolved-questions

- Final UI design considerations
- Whether the Salt module for profile update should be native or packaged with Uyuni
- Migration plan for the typical user
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I know this is in the unresolved questions section, are we roughly sure we can migrate everything we currently support in a reasonable way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new approach is fundamentally different so we won't be able to offer an automated migration at all. So what I mean here is the migration of the customer's workflow.

We just need to make sure that the customers have a smooth way of switching without any disruptions with proper documentation. But in the end, the change is really just getting rid of many extra steps to achieve the same goal, so I'm sure the customers would be happy to go through the labor of migration.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I acknowledge that customers may welcome the transition to the new approach, we cannot compel them to do so by breaking stuff; it should be done at their convenience. In other words, we must ensure that their existing clm project setups remain functional.

Moreover, if it helps, we could consider applying this change solely to new CLM projects, potentially easing concerns regarding existing setups.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, it's a bit tricky because we need to replace the old AppStream filters with the new functionality, but at least we can implement it so their existing projects won't be affected until they want to rebuild them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to implement it in a way that allows uses to continue using the flatten method and this one if they want?
Some customers may be familiar with the old one and may want to continue using it. Abid do you think such a case could exist?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flattening is only possible with the old-style AppStream filters. If we include both old and the new filters, it would be too confusing for users, and also they will keep using the old way for a longer time. That means more L3s and harder maintenance for us so I wouldn't recommend it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, and sounds good to me. 👍

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.