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

compose: Add --ex-lockfile and --ex-write-lockfile-to #1745

Closed
wants to merge 1 commit into from

Conversation

r4f4
Copy link
Contributor

@r4f4 r4f4 commented Jan 29, 2019

This is a skeleton of the solution for issue #1670.

The writing the lockfile part works but reading hasn't been tested yet.
I'm sending this PR so that we can start a discussion on my approach and I'll change things accordingly.

What's known to be missing:

  • Using the repodata checksum when reading the lock file
  • Make write-lockfile behave like coreos-assembler fetch. Right now the file is generated during the compose process.
  • How to properly pass the pkg NEVRAs to the depsolver.
  • Relevant tests: might involve creating a cached repo with fake/empty pkgs to make it deterministic.

@rh-atomic-bot
Copy link

Can one of the admins verify this patch?
I understand the following commands:

  • bot, add author to whitelist
  • bot, test pull request
  • bot, test pull request once

@r4f4
Copy link
Contributor Author

r4f4 commented Feb 1, 2019

Ok, so after some fixups, here is the current state. I generated a version lockfile a week ago.
Today I run a compose tree using that lock file. This is the output (with a print I added to src/libpriv/rpmostree-core.c:1926):

# ./rpm-ostree compose tree --workdir /tmp/workdir-2/ --repo /tmp/buildrepo --write-lockfile /tmp/version.lock --lockfile versions_lock.yaml /tmp/composedata/fedora-base.json
RPM-OSTree Version: 2019.1
No previous commit for fedora/stable/x86_64/base
Enabled rpm-md repositories: updates fedora
Updating metadata for 'updates'... done
rpm-md repo 'updates'; generated: 2019-02-01T01:33:06Z
Updating metadata for 'fedora'... done
rpm-md repo 'fedora'; generated: 2018-04-25T04:27:32Z
Importing rpm-md... done
Adding chrony-3.4-1.fc28.x86_64 to the goal
Adding container-selinux-2:2.79-1.git6f01752.fc28.noarch to the goal
Adding docker-2:1.13.1-61.git9cb56fd.fc28.x86_64 to the goal
Adding efibootmgr-16-2.fc28.x86_64 to the goal
Adding fedora-release-atomichost-28-6.noarch to the goal
Adding grub2 to the goal
Adding grub2-efi to the goal
Adding iputils-20161105-9.fc28.x86_64 to the goal
Adding kernel-4.19.16-200.fc28.x86_64 to the goal
Adding nss-altfiles-2.18.1-10.fc27.x86_64 to the goal
Adding ostree-2018.9-1.fc28.x86_64 to the goal
Adding ostree-grub2-2018.9-1.fc28.x86_64 to the goal
Adding selinux-policy-targeted-3.14.1-50.fc28.noarch to the goal
Adding shim to the goal
Adding systemd-238-10.git438ac26.fc28.x86_64 to the goal
Adding tuned-2.10.0-4.fc28.noarch to the goal
error: Packages not found: docker-2:1.13.1-61.git9cb56fd.fc28.x86_64, kernel-4.19.16-200.fc28.x86_64

# dnf repoquery docker
Last metadata expiration check: 0:39:58 ago on Fri Feb  1 22:04:03 2019.
docker-2:1.13.1-62.git9cb56fd.fc29.x86_64
docker-2:1.13.1-63.git1185cfd.fc29.x86_64

So it's trying to install a version that's not available anymore in the repos. Which is a good sign here, showing that the lockfile is working :)

@jlebon jlebon added the WIP label Mar 1, 2019
@rh-atomic-bot
Copy link

☔ The latest upstream changes (presumably 6b2ac58) made this pull request unmergeable. Please resolve the merge conflicts.

@r4f4
Copy link
Contributor Author

r4f4 commented May 21, 2019

Rebased on top of master, fixed a memory leak, fixed a double unref and squashed commits.
What would you guys like to see here before an initial review is made?

rust/src/lockfile.rs Outdated Show resolved Hide resolved
rust/src/lockfile.rs Outdated Show resolved Hide resolved
rust/src/lockfile.rs Outdated Show resolved Hide resolved
@@ -1954,7 +1956,7 @@ rpmostree_context_prepare (RpmOstreeContext *self,
g_autoptr(GPtrArray) missing_pkgs = NULL;
for (char **it = pkgnames; it && *it; it++)
{
const char *pkgname = *it;
const char *pkgname = g_hash_table_lookup (self->vlockmap, *it) ?: *it;
Copy link
Member

Choose a reason for hiding this comment

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

Heh, this part is surprisingly simple.

Copy link
Member

@jlebon jlebon left a comment

Choose a reason for hiding this comment

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

Regarding the lockfile spec itself, I'm not sure if it's worth breaking it down per-repo. (In fact, we're not actually verifying this part when reading it in anyway, right?). In pipeline contexts and even pungi, it's not uncommon to have custom/temporary repos for the build. It also makes it harder when overriding lockfile items, which is something we'll need for FCOS.

rust/src/lockfile.rs Outdated Show resolved Hide resolved
src/app/rpmostree-compose-builtin-tree.c Outdated Show resolved Hide resolved
src/app/rpmostree-compose-builtin-tree.c Outdated Show resolved Hide resolved
rust/src/lockfile.rs Outdated Show resolved Hide resolved
src/app/rpmostree-composeutil.c Outdated Show resolved Hide resolved
src/app/rpmostree-composeutil.c Outdated Show resolved Hide resolved
src/libpriv/rpmostree-core.c Outdated Show resolved Hide resolved
@r4f4
Copy link
Contributor Author

r4f4 commented May 30, 2019

Update PR with the suggestions. Dropping WIP.

@r4f4 r4f4 changed the title WIP: version lock for packages in the treefile Version lock for packages in the treefile May 30, 2019
src/app/rpmostree-compose-builtin-tree.c Outdated Show resolved Hide resolved
src/app/rpmostree-compose-builtin-tree.c Outdated Show resolved Hide resolved
src/app/rpmostree-composeutil.c Show resolved Hide resolved
src/app/rpmostree-composeutil.c Outdated Show resolved Hide resolved
src/app/rpmostree-composeutil.c Outdated Show resolved Hide resolved
return NULL;
}

g_autoptr(GVariant) value = g_variant_lookup_value (jsonmetav, "packages", G_VARIANT_TYPE ("av"));
Copy link
Member

Choose a reason for hiding this comment

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

a(ss)? That should simplify the loop below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried, it doesn't work. The parsed json is like this

Read: {'packages': <[<[<'acl-2.2.53-2.fc29.x86_64'>, <'sha256:0a7446de7de962fbbfdb204da0a7aef70cc8bd4ee4b2c03b0d730bf62b9a2223'>]>, <[<'atomic-registries-1.22.1-27.gitb507039.fc29.x86_64'>, <'sha256:19fdd5bcb79bc84c4c3d17d63d2ee39173d3d2504ea11ed9f0b7d0d0cf86550c'>]>, <[<'audit-libs-3.0-0.7.20190326git03e7489.fc29.x86_64'>, <'sha256:a907544ffac01209ce49026a0ebedef58d62639aa9d234537577c72d458cc850'>]>, <[<'basesystem-11-6.fc29.noarch'>, <'sha256:e4b86fe4f119873e8705dae47163f4afb6ccf8885426e04d960ff18eed7320bb'>]>, <[<'bash-4.4.23-6.fc29.x86_64'>, <'sha256:1899be2cb4a4793d6a6c62285a6b77f5c22eb5534e76d94d2a5d421cf2aa59ad'>]>,

as you can see, everything is wrapped inside a GVariant. Unless I'm missing something.

Copy link
Member

Choose a reason for hiding this comment

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

Ahh hmm, yeah I see what you mean. Seems like that's just how json_gvariant_deserialize works. Which makes sense, since it can't know that the JSON is following some particular scheme, so it just wraps all leaf nodes in the JSON into a GVariant. Yuck. So, I do think that parsing with serde would make this much nicer and give us scheme checking as well, but happy to stick with this for now!

tests/compose-tests/test-lockfile-write.sh Outdated Show resolved Hide resolved
tests/compose-tests/test-lockfile-write.sh Outdated Show resolved Hide resolved
{
/* The manifest might specify not only package names (foo-1.x) but also
* something-that-foo-provides */
g_autoptr(GPtrArray) alts = rpmostree_get_matching_packages (sack, name);
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I know I suggested this, but I'm not completely satisfied either with how "indirect" it all seems. One root issue here of course is #731; libsolv simply doesn't make it easy to provide resolution information. (It's not entirely its fault either; depsolving is a hard problem that isn't exactly disposed to provide clear cut answers to these sorts of questions).

Anyway, good to roll with this for now! Though it might need more thinking and tweaks in the future.

Copy link
Member

Choose a reason for hiding this comment

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

Just couldn't help thinking about this a bit more :)

ISTM like in the lockfile case, maybe we do want to require manifests to not use "provides" but instead purely pkgnames. So e.g. when using --lockfile or --write-lockfile-to, we would only search by pkgname rather than the default dnf_context_install() fuzzy logic and error out if the pkgname isn't found. That would really simplify the semantics of the lockfile.

But yeah, I'm happy getting this in as is for now, and then tweak it in follow-ups!

Copy link
Member

Choose a reason for hiding this comment

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

The tiny downside of course is that it requires slightly more churn on treefile writers to react to things like Obsoletes, package splits, etc... Doesn't seem like an unreasonable burden though.

Copy link
Member

Choose a reason for hiding this comment

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

Follow-up to this in #1849.

@r4f4
Copy link
Contributor Author

r4f4 commented May 31, 2019

Updated with requested changes.

@jlebon jlebon removed the WIP label Jun 3, 2019
Copy link
Member

@jlebon jlebon left a comment

Choose a reason for hiding this comment

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

Awesome, this looks good to me now as a first cut! Final tidbit, can you squash the two commits together and change the commit message title to something like: compose: Add --lockfile and --write-lockfile-to?

return NULL;
}

g_autoptr(GVariant) value = g_variant_lookup_value (jsonmetav, "packages", G_VARIANT_TYPE ("av"));
Copy link
Member

Choose a reason for hiding this comment

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

Ahh hmm, yeah I see what you mean. Seems like that's just how json_gvariant_deserialize works. Which makes sense, since it can't know that the JSON is following some particular scheme, so it just wraps all leaf nodes in the JSON into a GVariant. Yuck. So, I do think that parsing with serde would make this much nicer and give us scheme checking as well, but happy to stick with this for now!

@cgwalters
Copy link
Member

So, I do think that parsing with serde would make this much nicer and give us scheme checking as well, but happy to stick with this for now!

Heh, we're giving him conflicting advice. I feel like what we really need to do is get over the hump and try to have Rust driving things and that would naturally lead to things like this. Possibly even to the point of having the CLI entrypoint be Rust.

@jlebon
Copy link
Member

jlebon commented Jun 3, 2019

Heh, we're giving him conflicting advice.

Yeah, all I mean is that rather than reserializing and deserializing to pass to the C side as the first approach did, we could just pass e.g. two GStrvs (one for pkgnames and another for checksums). It'd be cleaner than mucking with JSON-GLib and give us type checking as well. That said, as I mentioned earlier, I'm totally cool with how it is right now too and doing this later on. 🕙

Possibly even to the point of having the CLI entrypoint be Rust.

Hehe, I was actually toying with this when I started hacking on ex history; trying to make the whole invocation essentially stay on the Rust side. Rewriting the CLI isn't the most fun thing to do though.

@jlebon
Copy link
Member

jlebon commented Jun 3, 2019

bot, add author to whitelist

@jlebon
Copy link
Member

jlebon commented Jun 3, 2019

Awesome, thanks so much for working on this and putting up with all the reviews! 🎉
@rh-atomic-bot r+ 2e237c3

(BTW, just realized this patch was never tested in CI. I've enabled it now, so if it bounces it should be easier to iterate until we go green.)

rh-atomic-bot pushed a commit that referenced this pull request Jun 3, 2019
Fixes #1670

This patch introduces a new `compose tree
--write-lockfile-to=manifest.lock` argument and a new `compose tree
--lockfile=manifest.lock` to read it back for subsequent invocations.

Signed-off-by: Rafael Fonseca <r4f4rfs@gmail.com>

Closes: #1745
Approved by: jlebon
@rh-atomic-bot
Copy link

⌛ Testing commit 2e237c3 with merge 8ff1c33...

@jlebon jlebon changed the title Version lock for packages in the treefile compose: Add --lockfile and --write-lockfile-to Jun 3, 2019
@jlebon
Copy link
Member

jlebon commented Jun 3, 2019

Also just noticed I messed up during my recommendation for the commit message and gave you the switch names without the ex- prefix. Not a big deal, though if it bounces, we can fix it up!

@rh-atomic-bot
Copy link

💔 Test failed - status-atomicjenkins

@jlebon
Copy link
Member

jlebon commented Jun 4, 2019

Hmm, looks like test-ucontainer.sh is failing with:

Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.Qe7wab
Enabled rpm-md repositories: test-repo
rpm-md repo 'test-repo' (cached); generated: 2019-06-03T22:20:59Z

(/var/tmp/checkout/.libs/lt-rpm-ostree ex container assemble:29578): GLib-CRITICAL **: 22:20:59.411: g_hash_table_lookup: assertion 'hash_table != NULL' failed
/var/tmp/checkout/tests/check/test-ucontainer.sh: line 48: 29578 Trace/breakpoint trap   (core dumped) rpm-ostree ex container assemble foo.conf

for (char **it = pkgnames; it && *it; it++)
{
const char *pkgname = *it;
g_autoptr(GError) local_error = NULL;
g_autoptr(DnfPackage) pkg = rpmostree_get_locked_package (sack, self->vlockmap, pkgname);
Copy link
Member

Choose a reason for hiding this comment

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

Either should conditionalize this bit on self->vlockmap != NULL here or you can also check it in rpmostree_get_locked_package.

@jlebon jlebon changed the title compose: Add --lockfile and --write-lockfile-to compose: Add --ex-lockfile and --ex-write-lockfile-to Jun 4, 2019
Fixes coreos#1670

This patch introduces a new `compose tree
--ex-write-lockfile-to=manifest.lock` argument and a new `compose tree
--ex-lockfile=manifest.lock` to read it back for subsequent invocations.

Signed-off-by: Rafael Fonseca <r4f4rfs@gmail.com>
@jlebon
Copy link
Member

jlebon commented Jun 4, 2019

@rh-atomic-bot r+ cd3e6c6

@rh-atomic-bot
Copy link

⌛ Testing commit cd3e6c6 with merge 79dfcea...

@rh-atomic-bot
Copy link

☀️ Test successful - status-atomicjenkins
Approved by: jlebon
Pushing 79dfcea to master...

@cgwalters
Copy link
Member

Awesome!

So an interesting next step here I think would be to change the FCOS pipeline to make use of this?

@dustymabe
Copy link
Member

So an interesting next step here I think would be to change the FCOS pipeline to make use of this?

right - we're setting up mechanical refs to print out lockfiles and then we'll create new lockfiles (full control of content) to use as input for our dev/prod refs

@dustymabe
Copy link
Member

nice work @r4f4

jlebon added a commit to jlebon/rpm-ostree that referenced this pull request Jun 5, 2019
One problem with how we use lockfiles right now is that we don't enforce
them for dependencies. That is, if `foo` requires `bar`, but only `foo`
is in the manifest, then while `foo` will be locked`, `bar` will never
be checked against the lockfile because it was never explicitly
requested.

Higher-level though, I don't like how indirect the locking here feels.
See some comments about that in:

coreos#1745 (comment)
coreos#1745 (comment)

Essentially, the manifest is an input file of patterns, and all we
really know from the lockfile output is that the set of packages in
there satisfies this input in some way. But:

1. there are multiple ways to satisfy the same input (hence why hints
   like `SOLVER_FAVOR` exist)
2. the solution is dependent on how the solver is implemented (i.e.
   different libsolv versions might yield different solutions)
3. the solution is dependent on flags fed to the solver (i.e. different
   libdnf versions might yield different solutions)

So any attempt at cross-checking between the input file and the lockfile
is going to be very hard. Using a stricter mode as I suggested in coreos#1745
of only allowing pure pkgnames or NEVRAs would help, but it wouldn't
address the dependency issue. (Though I'm still thinking about possibly
doing this anyway.)

The solution I propose here is instead to take the nuclear approach: we
completely exclude from the sack all packages of the same name as
packages in our lockfiles, but which do not match the NEVRA. Therefore,
any possible solution has to also satisfy our lockfile (or error out).
jlebon added a commit to jlebon/rpm-ostree that referenced this pull request Jun 5, 2019
One problem with how we use lockfiles right now is that we don't enforce
them for dependencies. That is, if `foo` requires `bar`, but only `foo`
is in the manifest, then while `foo` will be locked, `bar` will never
be checked against the lockfile because it was never explicitly
requested.

Higher-level though, I don't like how indirect the locking here feels.
See some comments about that in:

coreos#1745 (comment)
coreos#1745 (comment)

Essentially, the manifest is an input file of patterns, and all we
really know from the lockfile output is that the set of packages in
there satisfies this input in some way. But:

1. there are multiple ways to satisfy the same input (hence why hints
   like `SOLVER_FAVOR` exist)
2. the solution is dependent on how the solver is implemented (i.e.
   different libsolv versions might yield different solutions)
3. the solution is dependent on flags fed to the solver (i.e. different
   libdnf versions might yield different solutions)

So any attempt at cross-checking between the input file and the lockfile
is going to be very hard. Using a stricter mode as I suggested in coreos#1745
of only allowing pure pkgnames or NEVRAs would help, but it wouldn't
address the dependency issue. (Though I'm still thinking about possibly
doing this anyway.)

The solution I propose here is instead to take the nuclear approach: we
completely exclude from the sack all packages of the same name as
packages in our lockfiles, but which do not match the NEVRA. Therefore,
any possible solution has to also satisfy our lockfile (or error out).
@r4f4 r4f4 deleted the versionlock branch June 6, 2019 09:01
rh-atomic-bot pushed a commit that referenced this pull request Jun 6, 2019
One problem with how we use lockfiles right now is that we don't enforce
them for dependencies. That is, if `foo` requires `bar`, but only `foo`
is in the manifest, then while `foo` will be locked, `bar` will never
be checked against the lockfile because it was never explicitly
requested.

Higher-level though, I don't like how indirect the locking here feels.
See some comments about that in:

#1745 (comment)
#1745 (comment)

Essentially, the manifest is an input file of patterns, and all we
really know from the lockfile output is that the set of packages in
there satisfies this input in some way. But:

1. there are multiple ways to satisfy the same input (hence why hints
   like `SOLVER_FAVOR` exist)
2. the solution is dependent on how the solver is implemented (i.e.
   different libsolv versions might yield different solutions)
3. the solution is dependent on flags fed to the solver (i.e. different
   libdnf versions might yield different solutions)

So any attempt at cross-checking between the input file and the lockfile
is going to be very hard. Using a stricter mode as I suggested in #1745
of only allowing pure pkgnames or NEVRAs would help, but it wouldn't
address the dependency issue. (Though I'm still thinking about possibly
doing this anyway.)

The solution I propose here is instead to take the nuclear approach: we
completely exclude from the sack all packages of the same name as
packages in our lockfiles, but which do not match the NEVRA. Therefore,
any possible solution has to also satisfy our lockfile (or error out).

Closes: #1849
Approved by: cgwalters
rh-atomic-bot pushed a commit that referenced this pull request Jun 6, 2019
One problem with how we use lockfiles right now is that we don't enforce
them for dependencies. That is, if `foo` requires `bar`, but only `foo`
is in the manifest, then while `foo` will be locked, `bar` will never
be checked against the lockfile because it was never explicitly
requested.

Higher-level though, I don't like how indirect the locking here feels.
See some comments about that in:

#1745 (comment)
#1745 (comment)

Essentially, the manifest is an input file of patterns, and all we
really know from the lockfile output is that the set of packages in
there satisfies this input in some way. But:

1. there are multiple ways to satisfy the same input (hence why hints
   like `SOLVER_FAVOR` exist)
2. the solution is dependent on how the solver is implemented (i.e.
   different libsolv versions might yield different solutions)
3. the solution is dependent on flags fed to the solver (i.e. different
   libdnf versions might yield different solutions)

So any attempt at cross-checking between the input file and the lockfile
is going to be very hard. Using a stricter mode as I suggested in #1745
of only allowing pure pkgnames or NEVRAs would help, but it wouldn't
address the dependency issue. (Though I'm still thinking about possibly
doing this anyway.)

The solution I propose here is instead to take the nuclear approach: we
completely exclude from the sack all packages of the same name as
packages in our lockfiles, but which do not match the NEVRA. Therefore,
any possible solution has to also satisfy our lockfile (or error out).

Closes: #1849
Approved by: cgwalters
rh-atomic-bot pushed a commit that referenced this pull request Jun 6, 2019
One problem with how we use lockfiles right now is that we don't enforce
them for dependencies. That is, if `foo` requires `bar`, but only `foo`
is in the manifest, then while `foo` will be locked, `bar` will never
be checked against the lockfile because it was never explicitly
requested.

Higher-level though, I don't like how indirect the locking here feels.
See some comments about that in:

#1745 (comment)
#1745 (comment)

Essentially, the manifest is an input file of patterns, and all we
really know from the lockfile output is that the set of packages in
there satisfies this input in some way. But:

1. there are multiple ways to satisfy the same input (hence why hints
   like `SOLVER_FAVOR` exist)
2. the solution is dependent on how the solver is implemented (i.e.
   different libsolv versions might yield different solutions)
3. the solution is dependent on flags fed to the solver (i.e. different
   libdnf versions might yield different solutions)

So any attempt at cross-checking between the input file and the lockfile
is going to be very hard. Using a stricter mode as I suggested in #1745
of only allowing pure pkgnames or NEVRAs would help, but it wouldn't
address the dependency issue. (Though I'm still thinking about possibly
doing this anyway.)

The solution I propose here is instead to take the nuclear approach: we
completely exclude from the sack all packages of the same name as
packages in our lockfiles, but which do not match the NEVRA. Therefore,
any possible solution has to also satisfy our lockfile (or error out).

Closes: #1849
Approved by: cgwalters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants