Skip to content

Commit 5a5154a

Browse files
committed
o/ifacestate: properly handle undo scenarios where auto-connections are dropped
1 parent e07c813 commit 5a5154a

3 files changed

Lines changed: 112 additions & 15 deletions

File tree

overlord/ifacestate/handlers.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,10 +369,13 @@ func (m *InterfaceManager) refreshAppSetConnections(task *state.Task, appSet *in
369369
task.Logf("%s", snap.BadInterfacesSummary(snapInfo))
370370
}
371371

372-
affectedConnections, err := m.reloadConnections(snapName)
372+
affectedConnections, changedConns, err := m.reloadConnections(snapName)
373373
if err != nil {
374374
return nil, nil, err
375375
}
376+
if err := saveChangedConnectionsForSetupProfilesRestore(task, snapName, changedConns); err != nil {
377+
return nil, nil, err
378+
}
376379
return disconnectedSnaps, affectedConnections, nil
377380
}
378381

@@ -696,6 +699,9 @@ func (m *InterfaceManager) undoSetupProfiles(task *state.Task, tomb *tomb.Tomb)
696699
if err := snapstateFinishRestart(task, snapsup, finishOpts); err != nil {
697700
return err
698701
}
702+
if err := restoreConnectionsForSetupProfiles(task); err != nil {
703+
return err
704+
}
699705

700706
// Get the name from SnapSetup and use it to find the current SideInfo
701707
// about the snap, if there is one.
@@ -2003,7 +2009,7 @@ func (m *InterfaceManager) transitionConnectionsCoreMigration(st *state.State, o
20032009
// on disk are rewritten. This is ok because core/ubuntu-core have
20042010
// exactly the same profiles and nothing in the generated policies
20052011
// has the core snap-name encoded.
2006-
if _, err := m.reloadConnections(newName); err != nil {
2012+
if _, _, err := m.reloadConnections(newName); err != nil {
20072013
return err
20082014
}
20092015

overlord/ifacestate/helpers.go

Lines changed: 103 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -426,15 +426,103 @@ func isBroken(st *state.State, snapName string) (bool, error) {
426426
return false, nil
427427
}
428428

429+
func cloneConnState(connState *schema.ConnState) *schema.ConnState {
430+
clone := *connState
431+
432+
cloneAttrs := func(attrs map[string]any) map[string]any {
433+
if attrs == nil {
434+
return nil
435+
}
436+
return utils.CopyAttributes(attrs)
437+
}
438+
439+
clone.StaticPlugAttrs = cloneAttrs(connState.StaticPlugAttrs)
440+
clone.DynamicPlugAttrs = cloneAttrs(connState.DynamicPlugAttrs)
441+
clone.StaticSlotAttrs = cloneAttrs(connState.StaticSlotAttrs)
442+
clone.DynamicSlotAttrs = cloneAttrs(connState.DynamicSlotAttrs)
443+
444+
return &clone
445+
}
446+
447+
// saveChangedConnectionsForSetupProfilesRestore records original connection
448+
// states for setup-profiles do tasks whose undo can restore them.
449+
func saveChangedConnectionsForSetupProfilesRestore(task *state.Task, instanceName string, changedConns map[string]*schema.ConnState) error {
450+
if len(changedConns) == 0 {
451+
return nil
452+
}
453+
454+
// undo setup-profiles also calls this code while rebuilding old profiles,
455+
// but restoration data should only come from the original do path
456+
if task.Status() == state.UndoingStatus {
457+
return nil
458+
}
459+
460+
// if this isn't the setup-profiles task that is going to handle the undo,
461+
// then we don't need to keep track of these on the task
462+
if !shouldUndoSetupProfiles(task, instanceName) {
463+
return nil
464+
}
465+
466+
var originalConns map[string]*schema.ConnState
467+
err := task.Get("original-connection-states", &originalConns)
468+
if err != nil && !errors.Is(err, state.ErrNoState) {
469+
return err
470+
}
471+
if originalConns == nil {
472+
originalConns = make(map[string]*schema.ConnState)
473+
}
474+
475+
for connID, connState := range changedConns {
476+
if originalConns[connID] != nil {
477+
// a setup-profiles task can be retried after saving original states
478+
// and unlocking for backend setup. keep the original snapshot.
479+
continue
480+
}
481+
originalConns[connID] = connState
482+
}
483+
484+
task.Set("original-connection-states", originalConns)
485+
486+
return nil
487+
}
488+
489+
// restoreConnectionsForSetupProfiles restores connection states saved on a
490+
// setup-profiles task.
491+
func restoreConnectionsForSetupProfiles(task *state.Task) error {
492+
var original map[string]*schema.ConnState
493+
err := task.Get("original-connection-states", &original)
494+
if errors.Is(err, state.ErrNoState) {
495+
return nil
496+
}
497+
if err != nil {
498+
return err
499+
}
500+
501+
st := task.State()
502+
503+
conns, err := getConns(st)
504+
if err != nil {
505+
return err
506+
}
507+
508+
for connID, connState := range original {
509+
conns[connID] = connState
510+
}
511+
setConns(st, conns)
512+
513+
return nil
514+
}
515+
429516
// reloadConnections reloads connections stored in the state in the repository.
430517
// Using non-empty snapName the operation can be scoped to connections
431518
// affecting a given snap.
432519
//
433-
// The return value is the list of affected snap names and their connection IDs.
434-
func (m *InterfaceManager) reloadConnections(snapName string) (reloadedConnectionIDs []string, err error) {
520+
// The return value is the list of affected snap names and their connection IDs,
521+
// plus the original connection states that were changed.
522+
func (m *InterfaceManager) reloadConnections(snapName string) (reloadedConnectionIDs []string, changedConns map[string]*schema.ConnState, err error) {
435523
conns, err := getConns(m.state)
436524
if err != nil {
437-
return nil, err
525+
return nil, nil, err
438526
}
439527

440528
var policyChecker interfaces.PolicyFunc
@@ -445,21 +533,22 @@ func (m *InterfaceManager) reloadConnections(snapName string) (reloadedConnectio
445533
if errors.Is(err, state.ErrNoState) {
446534
// everything else is a noop, as no model means no connections
447535
// to reload
448-
return nil, nil
536+
return nil, nil, nil
449537
} else if err != nil {
450-
return nil, err
538+
return nil, nil, err
451539
}
452540
autoChecker, err = newAutoConnectChecker(m.state, m.repo, deviceCtx)
453541
if err != nil {
454-
return nil, err
542+
return nil, nil, err
455543
}
456544

457545
connChecker, err = newConnectChecker(m.state, deviceCtx)
458546
if err != nil {
459-
return nil, err
547+
return nil, nil, err
460548
}
461549

462550
connStateChanged := false
551+
changedConns = make(map[string]*schema.ConnState)
463552

464553
var reloadedConnections []string
465554
ConnsLoop:
@@ -473,7 +562,7 @@ ConnsLoop:
473562
}
474563
connRef, err := interfaces.ParseConnRef(connId)
475564
if err != nil {
476-
return nil, err
565+
return nil, nil, err
477566
}
478567
// Apply filtering, this allows us to reload only a subset of
479568
// connections (and similarly, refresh the static attributes of only a
@@ -497,13 +586,14 @@ ConnsLoop:
497586
for _, snapName := range []string{connRef.PlugRef.Snap, connRef.SlotRef.Snap} {
498587
broken, err := isBroken(m.state, snapName)
499588
if err != nil {
500-
return nil, err
589+
return nil, nil, err
501590
}
502591
if broken {
503592
logger.Noticef("Snap %q is broken, ignored by reloadConnections", snapName)
504593
continue ConnsLoop
505594
}
506595
}
596+
changedConns[connId] = cloneConnState(connState)
507597
delete(conns, connId)
508598
connStateChanged = true
509599
}
@@ -540,11 +630,11 @@ ConnsLoop:
540630

541631
plugAppSet, err := interfaces.NewSnapAppSet(plugInfo.Snap, nil)
542632
if err != nil {
543-
return nil, err
633+
return nil, nil, err
544634
}
545635
slotAppSet, err := interfaces.NewSnapAppSet(slotInfo.Snap, nil)
546636
if err != nil {
547-
return nil, err
637+
return nil, nil, err
548638
}
549639

550640
cplug := interfaces.NewConnectedPlug(plugInfo, plugAppSet, newStaticPlugAttrs, connState.DynamicPlugAttrs)
@@ -568,6 +658,7 @@ ConnsLoop:
568658
reloadedConnections = append(reloadedConnections, connId)
569659

570660
if updateStaticAttrs {
661+
changedConns[connId] = cloneConnState(connState)
571662
connState.StaticPlugAttrs = staticPlugAttrs
572663
connState.StaticSlotAttrs = staticSlotAttrs
573664
connStateChanged = true
@@ -578,7 +669,7 @@ ConnsLoop:
578669
setConns(m.state, conns)
579670
}
580671

581-
return reloadedConnections, nil
672+
return reloadedConnections, changedConns, nil
582673
}
583674

584675
// removeConnections disconnects all connections of the snap in the repo. It should only be used if the snap

overlord/ifacestate/ifacemgr.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ func (m *InterfaceManager) StartUp() error {
206206
if err := removeStaleConnections(m.state); err != nil {
207207
return err
208208
}
209-
if _, err := m.reloadConnections(""); err != nil {
209+
if _, _, err := m.reloadConnections(""); err != nil {
210210
return err
211211
}
212212

0 commit comments

Comments
 (0)