Skip to content

Commit

Permalink
o/devicestate: handle components during an offline remodel
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewphelpsj committed Jan 31, 2025
1 parent d1da6a8 commit 37565a9
Show file tree
Hide file tree
Showing 2 changed files with 997 additions and 27 deletions.
101 changes: 74 additions & 27 deletions overlord/devicestate/devicestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,11 +427,10 @@ type modelSnapsForRemodel struct {
}

type remodeler struct {
newModel *asserts.Model
offline bool
localSnaps map[string]snapstate.PathSnap

// TODO:COMPS: keep track of local components here
newModel *asserts.Model
offline bool
localSnaps map[string]snapstate.PathSnap
localComponents map[string]snapstate.PathComponent

vsets *snapasserts.ValidationSets
tracker *snap.SelfContainedSetPrereqTracker
Expand Down Expand Up @@ -620,7 +619,7 @@ func (r *remodeler) maybeInstallOrUpdate(ctx context.Context, st *state.State, r

return remodelChannelSwitch, []*state.TaskSet{ts}, nil
case needsComponentChanges:
tss, err := r.installComponents(ctx, st, currentInfo, requiredComponents)
tss, err := r.installComponents(ctx, st, currentInfo, rt, requiredComponents)
if err != nil {
return 0, nil, err
}
Expand Down Expand Up @@ -719,8 +718,15 @@ func (r *remodeler) installGoal(sn remodelSnapTarget, components []string) (snap
return nil, fmt.Errorf("no snap file provided for %q", sn.name)
}

if len(components) != 0 {
return nil, errors.New("internal error: offline remodel with components not supported yet")
comps := make([]snapstate.PathComponent, 0, len(components))
for _, c := range components {
cref := naming.NewComponentRef(sn.name, c)
lc, ok := r.localComponents[cref.String()]
if !ok {
return nil, fmt.Errorf("cannot find locally provided component: %q", cref)
}

comps = append(comps, lc)
}

opts := snapstate.RevisionOptions{
Expand All @@ -729,9 +735,10 @@ func (r *remodeler) installGoal(sn remodelSnapTarget, components []string) (snap
}

return snapstatePathInstallGoal(snapstate.PathSnap{
Path: ls.Path,
SideInfo: ls.SideInfo,
RevOpts: opts,
Path: ls.Path,
SideInfo: ls.SideInfo,
RevOpts: opts,
Components: comps,
}), nil
}

Expand Down Expand Up @@ -801,8 +808,20 @@ func (r *remodeler) updateGoal(st *state.State, sn remodelSnapTarget, components
return g, nil
}

if len(components) != 0 {
return nil, errors.New("internal error: offline remodel with components not supported yet")
// we assume that all of the component revisions are valid with the
// given snap revision. the code in daemon that calls Remodel verifies
// this against the assertions db, and the task handlers in snapstate
// also double check this while installing the snap/components.
comps := make([]snapstate.PathComponent, 0, len(components))
for _, c := range components {
cref := naming.NewComponentRef(sn.name, c)

lc, ok := r.localComponents[cref.String()]
if !ok {
return nil, fmt.Errorf("cannot find locally provided component: %q", cref)
}

comps = append(comps, lc)
}

opts := snapstate.RevisionOptions{
Expand All @@ -814,9 +833,10 @@ func (r *remodeler) updateGoal(st *state.State, sn remodelSnapTarget, components
// snapstate for by-path installs (why don't we?)

return snapstatePathUpdateGoal(snapstate.PathSnap{
Path: ls.Path,
SideInfo: ls.SideInfo,
RevOpts: opts,
Path: ls.Path,
SideInfo: ls.SideInfo,
RevOpts: opts,
Components: comps,
}), nil
}

Expand All @@ -833,11 +853,33 @@ func (r *remodeler) updateGoal(st *state.State, sn remodelSnapTarget, components
}), nil
}

func (r *remodeler) installComponents(ctx context.Context, st *state.State, info *snap.Info, components []string) ([]*state.TaskSet, error) {
func (r *remodeler) installComponents(ctx context.Context, st *state.State, info *snap.Info, rt remodelSnapTarget, components []string) ([]*state.TaskSet, error) {
r.tracker.Add(info)

if r.offline {
return nil, errors.New("internal error: offline remodel with components not supported yet")
var tss []*state.TaskSet
for _, c := range components {
ref := naming.NewComponentRef(rt.name, c)

lc, ok := r.localComponents[ref.String()]
if !ok {
return nil, fmt.Errorf("cannot find locally provided component: %q", ref)
}

ts, err := snapstateInstallComponentPath(st, lc.SideInfo, info, lc.Path, snapstate.Options{
DeviceCtx: r.deviceCtx,
FromChange: r.fromChange,
PrereqTracker: r.tracker,
})
if err != nil {
return nil, err
}

Check warning on line 876 in overlord/devicestate/devicestate.go

View check run for this annotation

Codecov / codecov/patch

overlord/devicestate/devicestate.go#L875-L876

Added lines #L875 - L876 were not covered by tests
tss = append(tss, ts)

// TODO: verify against validation sets, since we don't do that in
// snapstate for by-path installs (why don't we?)
}
return tss, nil
}

return snapstateInstallComponents(ctx, st, components, info, r.vsets, snapstate.Options{
Expand Down Expand Up @@ -1037,13 +1079,14 @@ func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Mo
// provided. We check this flag whenever a snap installation/update is
// found needed for the remodel.
rm := remodeler{
newModel: new,
offline: opts.Offline,
vsets: vsets,
tracker: snap.NewSelfContainedSetPrereqTracker(),
deviceCtx: deviceCtx,
fromChange: fromChange,
localSnaps: make(map[string]snapstate.PathSnap, len(opts.LocalSnaps)),
newModel: new,
offline: opts.Offline,
vsets: vsets,
tracker: snap.NewSelfContainedSetPrereqTracker(),
deviceCtx: deviceCtx,
fromChange: fromChange,
localSnaps: make(map[string]snapstate.PathSnap, len(opts.LocalSnaps)),
localComponents: make(map[string]snapstate.PathComponent, len(opts.LocalComponents)),
}

for _, ls := range opts.LocalSnaps {
Expand All @@ -1053,6 +1096,10 @@ func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Mo
}
}

for _, lc := range opts.LocalComponents {
rm.localComponents[lc.SideInfo.Component.String()] = lc
}

// First handle snapd as a special case
tss, err := remodelSnapdSnapTasks(ctx, st, rm)
if err != nil {
Expand Down Expand Up @@ -1401,8 +1448,8 @@ func Remodel(st *state.State, new *asserts.Model, opts RemodelOptions) (*state.C
return nil, fmt.Errorf("cannot remodel until fully seeded")
}

if !opts.Offline && len(opts.LocalSnaps) > 0 {
return nil, errors.New("cannot do an online remodel with provided local snaps")
if !opts.Offline && (len(opts.LocalSnaps) > 0 || len(opts.LocalComponents) > 0) {
return nil, errors.New("cannot do an online remodel with provided local snaps or components")
}

Check warning on line 1453 in overlord/devicestate/devicestate.go

View check run for this annotation

Codecov / codecov/patch

overlord/devicestate/devicestate.go#L1452-L1453

Added lines #L1452 - L1453 were not covered by tests

for _, ls := range opts.LocalSnaps {
Expand Down
Loading

0 comments on commit 37565a9

Please sign in to comment.