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

o/devicestate: consider snapd snap when remodeling #13242

Merged

Conversation

alfonsosanchezbeato
Copy link
Member

Changes to snapd in a new model assertion (which can be only changes
in tracked channel at the moment) where not being applied, make sure
this does not happen anymore. Fixes LP#2035186.

@alfonsosanchezbeato alfonsosanchezbeato added the Run nested The PR also runs tests inluded in nested suite label Oct 2, 2023
@alfonsosanchezbeato alfonsosanchezbeato added this to the 2.61 milestone Oct 2, 2023
@codecov-commenter
Copy link

codecov-commenter commented Oct 2, 2023

Codecov Report

Merging #13242 (76e6425) into master (73c90a1) will decrease coverage by 0.01%.
Report is 54 commits behind head on master.
The diff coverage is 91.66%.

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

@@            Coverage Diff             @@
##           master   #13242      +/-   ##
==========================================
- Coverage   78.83%   78.83%   -0.01%     
==========================================
  Files        1016     1013       -3     
  Lines      126663   126392     -271     
==========================================
- Hits        99857    99636     -221     
+ Misses      20557    20527      -30     
+ Partials     6249     6229      -20     
Flag Coverage Δ
unittests 78.83% <91.66%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
aspects/schema.go 91.26% <100.00%> (+1.67%) ⬆️
bootloader/assets/grub_recovery_cfg_asset.go 100.00% <100.00%> (ø)
cmd/snap-failure/cmd_snapd.go 84.67% <ø> (ø)
cmd/snap-repair/runner.go 82.91% <100.00%> (+0.75%) ⬆️
cmd/snapd/main.go 34.69% <ø> (-1.31%) ⬇️
dirs/dirs.go 93.46% <ø> (-0.03%) ⬇️
interfaces/builtin/common.go 71.76% <ø> (ø)
interfaces/builtin/gpio_control.go 100.00% <ø> (ø)
interfaces/core.go 88.57% <ø> (ø)
interfaces/policy/basedeclaration.go 38.88% <ø> (ø)
... and 11 more

... and 17 files with indirect coverage changes

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

Copy link
Collaborator

@andrewphelpsj andrewphelpsj left a comment

Choose a reason for hiding this comment

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

Looks good! One quick question.

// new required snaps or a track for existing ones needs to be updated
for _, modelSnap := range new.Snaps() {
switch modelSnap.SnapType {
case "gadget", "kernel", "base", "core":
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would

if snapsAccountedFor[modelSnap.Name] {
	continue
}

work instead of specifically calling out out the already handled ones? It looks like new.Base(), which is used as a key in snapsAccountedFor earlier, returns something that may not be the name, so it might not. If it does, though, I think it'd be a bit better.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually this is an excellent suggestion, as now I notice that this could overlook non-rootfs bases (we can have UC22 but also a core20 snap installed).

Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

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

not sure it makes sense to treat snapd as we treat app snaps

Comment on lines 712 to 749
for _, modelSnap := range new.Snaps() {
if snapsAccountedFor[modelSnap.Name] {
continue
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we want to switch snapd track in the middle of other things, it needs to be treated as one of the essential snaps I think and processed first as we do during regular refreshes

Copy link
Collaborator

Choose a reason for hiding this comment

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

It'd be really cool if we could do the essential snaps in a loop, rather than having them all listed like they are now. Although some of the special cases make that difficult. Specifically, new.BaseSnap() can return nil on UC16, and only the gadget snap's task set gets passed to updateNeededSnapsFromTs.

Copy link
Member Author

Choose a reason for hiding this comment

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

I want ahead with this, hopefully it looks a bit better now.

@alfonsosanchezbeato
Copy link
Member Author

Thanks for the reviews, I've repushed now so snapd is considered jointly with the other essential snaps. To be clear, by looking at how are things implemented at the moment I don't think it will make a difference with the previous approach.

Copy link
Collaborator

@andrewphelpsj andrewphelpsj left a comment

Choose a reason for hiding this comment

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

Looks good! Just one comment

asserts/model.go Outdated
@@ -523,6 +523,14 @@ func (mod *Model) StorageSafety() StorageSafety {
return mod.storageSafety
}

// SnapdSnap returns the details of the snapd snap for this model.
func (mod *Model) SnapdSnap() *ModelSnap {
if mod.Base() == "" || mod.Base() == "core" {
Copy link
Collaborator

Choose a reason for hiding this comment

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

A comment explaining why these checks are made might be useful. Additionally, rather than directly accessing the slice of snaps, maybe store a pointer to the snap on *Model like the rest of the essential snaps. Although that really doesn't matter too much.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think core18 style classic models can have no base but a snapd snap?

Copy link
Member Author

Choose a reason for hiding this comment

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

But then how you would know whether to seed core or core18? I don't see examples of 18.04 classic models in https://github.com/snapcore/models/tree/master , do you know where could I find one?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the hybrid installer might have one, maybe some cloud images. We should have some example in our own unit test somewhere, though they test might be signing it on the fly.

Copy link
Collaborator

Choose a reason for hiding this comment

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

also on classic afair if there's a snapd it will be used in preference to core

Copy link
Member Author

Choose a reason for hiding this comment

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

I've changed the strategy and now I simply check the name of the first snap in the slice, to avoid this issues. The only example that I've seen of classic 18 model is snapd/tests/lib/assertions/developer1-my-classic-w-gadget-18.model, which has:

required-snaps:
  - core18

Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

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

question

asserts/model.go Outdated
@@ -523,6 +523,15 @@ func (mod *Model) StorageSafety() StorageSafety {
return mod.storageSafety
}

// SnapdSnap returns the details of the snapd snap for this model.
func (mod *Model) SnapdSnap() *ModelSnap {
Copy link
Collaborator

Choose a reason for hiding this comment

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

it's unclear to me if this is enough, snapd can be explicit or implicit in UC18 and UC20+ models, but seed code will pick what to use even if it's implicit:

https://github.com/snapcore/snapd/blob/2.60.4/seed/seed20.go#L699
https://github.com/snapcore/snapd/blob/2.60.4/seed/seed16.go#L321

and at least with UC20+ potentially moving from an implicit to and explict or the other way around snapd might involve a channel change but is not clear to me that this simple method plus the code in devicestate will cover those scenarios correctly?

Copy link
Member Author

Choose a reason for hiding this comment

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

I have rewritten the PR so this method is not needed anymore, accounting for cases when snapd information is implicit. In the end, modelSnaps type and other types reflect only the explicit information in the model assertion, and we are not changing that now, although I think it would be good to reflect also implicit information there in the future.

@@ -541,6 +536,10 @@ func remodelEssentialSnapTasks(ctx context.Context, st *state.State, pathSI *pat
}

if !changed {
addExistingSnapTasks := snapstate.LinkNewBaseOrKernel
Copy link
Collaborator

Choose a reason for hiding this comment

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

I like this change, but I think it'd be even more clear if addExistingSnapTasks was eliminated and the functions were called directly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Comment on lines 674 to 682
var pathSi *pathSideInfo
// Implicit new channel if snapd is not explicitly in the model
newSnapdChannel := "latest/stable"
essentialSnaps := newModel.EssentialSnaps()
if essentialSnaps[0].SnapType == "snapd" {
// snapd can be specified explicitly in the model (UC20+)
newSnapdChannel = essentialSnaps[0].DefaultChannel
}
pathSi = sideInfoAndPathFromID(localSnaps, paths, naming.WellKnownSnapID("snapd"))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
var pathSi *pathSideInfo
// Implicit new channel if snapd is not explicitly in the model
newSnapdChannel := "latest/stable"
essentialSnaps := newModel.EssentialSnaps()
if essentialSnaps[0].SnapType == "snapd" {
// snapd can be specified explicitly in the model (UC20+)
newSnapdChannel = essentialSnaps[0].DefaultChannel
}
pathSi = sideInfoAndPathFromID(localSnaps, paths, naming.WellKnownSnapID("snapd"))
// Implicit new channel if snapd is not explicitly in the model
newSnapdChannel := "latest/stable"
essentialSnaps := newModel.EssentialSnaps()
if essentialSnaps[0].SnapType == "snapd" {
// snapd can be specified explicitly in the model (UC20+)
newSnapdChannel = essentialSnaps[0].DefaultChannel
}
pathSi := sideInfoAndPathFromID(localSnaps, paths, naming.WellKnownSnapID("snapd"))

No need to declare pathSi early.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, done now

return nil, err
}
if ts != nil {
tss = append(tss, ts)
if modelSnap.SnapType == "gadget" {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you know why we have to have this special case? Is it because gadgets can have prerequisites, and other types cannot?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, that is the case. Bases cannot depend on other base, and kernels do not depend on a base, at least for the moment. And neither of them can have content providers I think.

Copy link
Collaborator

@andrewphelpsj andrewphelpsj left a comment

Choose a reason for hiding this comment

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

Updates look good!

Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

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

thanks, left a couple of comments on the new version

overlord/devicestate/devicestate.go Show resolved Hide resolved
Comment on lines 8442 to 8444
snapdPath, _ := s.makeStoreTestSnap(c, "name: snapd\ntype: snapd\nversion: 123", "1")
s.serveSnap(snapdPath, "2")

Copy link
Collaborator

Choose a reason for hiding this comment

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

why do we need this change? shouldn't we make it so that there's no channel change instead? the test doesn't seem to be about this anyway

Copy link
Member Author

Choose a reason for hiding this comment

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

Without adding this the test does not progress enough and the error:
unexpectedly empty response from the server (try again later)
is returned. snapd snap is set in the state for the test, so at some point the code is trying to contact the fakestore for information about it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

sorry, maybe I wasn't clear but the issue why you need this is that the new code thinks there's a channel change for snapd: this works too and is a bit cleaner I think:

diff --git a/overlord/managers_test.go b/overlord/managers_test.go
index f216eccaf5..b51b17af30 100644
--- a/overlord/managers_test.go
+++ b/overlord/managers_test.go
@@ -8460,8 +8460,6 @@ func (s *mgrsSuiteCore) TestRemodelUC20SnapWithPrereqsMissingDeps(c *C) {
 
        snapPath, _ := s.makeStoreTestSnap(c, prereqSnapYaml, "1")
        s.serveSnap(snapPath, "1")
-       snapdPath, _ := s.makeStoreTestSnap(c, "name: snapd\ntype: snapd\nversion: 123", "1")
-       s.serveSnap(snapdPath, "2")
 
        st := s.o.State()
        st.Lock()
@@ -8475,6 +8473,7 @@ func (s *mgrsSuiteCore) TestRemodelUC20SnapWithPrereqsMissingDeps(c *C) {
                },
                Current:  snap.R(1),
                SnapType: "snapd",
+               TrackingChannel: "latest/stable",
                Flags: snapstate.Flags{
                        Required: true,
                },

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, right, I've changed this now. Sorry, I should have read more carefully!

Copy link
Member Author

@alfonsosanchezbeato alfonsosanchezbeato left a comment

Choose a reason for hiding this comment

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

Thanks for the reviews, I've addressed the comments now.

@@ -541,6 +536,10 @@ func remodelEssentialSnapTasks(ctx context.Context, st *state.State, pathSI *pat
}

if !changed {
addExistingSnapTasks := snapstate.LinkNewBaseOrKernel
Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Comment on lines 674 to 682
var pathSi *pathSideInfo
// Implicit new channel if snapd is not explicitly in the model
newSnapdChannel := "latest/stable"
essentialSnaps := newModel.EssentialSnaps()
if essentialSnaps[0].SnapType == "snapd" {
// snapd can be specified explicitly in the model (UC20+)
newSnapdChannel = essentialSnaps[0].DefaultChannel
}
pathSi = sideInfoAndPathFromID(localSnaps, paths, naming.WellKnownSnapID("snapd"))
Copy link
Member Author

Choose a reason for hiding this comment

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

Right, done now

return nil, err
}
if ts != nil {
tss = append(tss, ts)
if modelSnap.SnapType == "gadget" {
Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, that is the case. Bases cannot depend on other base, and kernels do not depend on a base, at least for the moment. And neither of them can have content providers I think.

overlord/devicestate/devicestate.go Show resolved Hide resolved
Comment on lines 8442 to 8444
snapdPath, _ := s.makeStoreTestSnap(c, "name: snapd\ntype: snapd\nversion: 123", "1")
s.serveSnap(snapdPath, "2")

Copy link
Member Author

Choose a reason for hiding this comment

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

Without adding this the test does not progress enough and the error:
unexpectedly empty response from the server (try again later)
is returned. snapd snap is set in the state for the test, so at some point the code is trying to contact the fakestore for information about it.

Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

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

thank you, further comment about the managers_test.go change

Comment on lines 8442 to 8444
snapdPath, _ := s.makeStoreTestSnap(c, "name: snapd\ntype: snapd\nversion: 123", "1")
s.serveSnap(snapdPath, "2")

Copy link
Collaborator

Choose a reason for hiding this comment

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

sorry, maybe I wasn't clear but the issue why you need this is that the new code thinks there's a channel change for snapd: this works too and is a bit cleaner I think:

diff --git a/overlord/managers_test.go b/overlord/managers_test.go
index f216eccaf5..b51b17af30 100644
--- a/overlord/managers_test.go
+++ b/overlord/managers_test.go
@@ -8460,8 +8460,6 @@ func (s *mgrsSuiteCore) TestRemodelUC20SnapWithPrereqsMissingDeps(c *C) {
 
        snapPath, _ := s.makeStoreTestSnap(c, prereqSnapYaml, "1")
        s.serveSnap(snapPath, "1")
-       snapdPath, _ := s.makeStoreTestSnap(c, "name: snapd\ntype: snapd\nversion: 123", "1")
-       s.serveSnap(snapdPath, "2")
 
        st := s.o.State()
        st.Lock()
@@ -8475,6 +8473,7 @@ func (s *mgrsSuiteCore) TestRemodelUC20SnapWithPrereqsMissingDeps(c *C) {
                },
                Current:  snap.R(1),
                SnapType: "snapd",
+               TrackingChannel: "latest/stable",
                Flags: snapstate.Flags{
                        Required: true,
                },

Changes to snapd in a new model assertion (which can be only changes
in tracked channel at the moment) where not being applied, make sure
this does not happen anymore. Fixes LP#2035186.
@alfonsosanchezbeato
Copy link
Member Author

History rewritten and rebased to master.

@alfonsosanchezbeato alfonsosanchezbeato merged commit 5393602 into snapcore:master Oct 20, 2023
45 of 51 checks passed
@ernestl ernestl mentioned this pull request Nov 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cherry-picked Run nested The PR also runs tests inluded in nested suite
Projects
None yet
5 participants