Skip to content

Commit 8447578

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

3 files changed

Lines changed: 102 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: 93 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -426,15 +426,93 @@ func isBroken(st *state.State, snapName string) (bool, error) {
426426
return false, nil
427427
}
428428

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

440518
var policyChecker interfaces.PolicyFunc
@@ -445,21 +523,22 @@ func (m *InterfaceManager) reloadConnections(snapName string) (reloadedConnectio
445523
if errors.Is(err, state.ErrNoState) {
446524
// everything else is a noop, as no model means no connections
447525
// to reload
448-
return nil, nil
526+
return nil, nil, nil
449527
} else if err != nil {
450-
return nil, err
528+
return nil, nil, err
451529
}
452530
autoChecker, err = newAutoConnectChecker(m.state, m.repo, deviceCtx)
453531
if err != nil {
454-
return nil, err
532+
return nil, nil, err
455533
}
456534

457535
connChecker, err = newConnectChecker(m.state, deviceCtx)
458536
if err != nil {
459-
return nil, err
537+
return nil, nil, err
460538
}
461539

462540
connStateChanged := false
541+
changedConns = make(map[string]*schema.ConnState)
463542

464543
var reloadedConnections []string
465544
ConnsLoop:
@@ -473,7 +552,7 @@ ConnsLoop:
473552
}
474553
connRef, err := interfaces.ParseConnRef(connId)
475554
if err != nil {
476-
return nil, err
555+
return nil, nil, err
477556
}
478557
// Apply filtering, this allows us to reload only a subset of
479558
// connections (and similarly, refresh the static attributes of only a
@@ -497,13 +576,14 @@ ConnsLoop:
497576
for _, snapName := range []string{connRef.PlugRef.Snap, connRef.SlotRef.Snap} {
498577
broken, err := isBroken(m.state, snapName)
499578
if err != nil {
500-
return nil, err
579+
return nil, nil, err
501580
}
502581
if broken {
503582
logger.Noticef("Snap %q is broken, ignored by reloadConnections", snapName)
504583
continue ConnsLoop
505584
}
506585
}
586+
changedConns[connId] = cloneConnState(connState)
507587
delete(conns, connId)
508588
connStateChanged = true
509589
}
@@ -540,11 +620,11 @@ ConnsLoop:
540620

541621
plugAppSet, err := interfaces.NewSnapAppSet(plugInfo.Snap, nil)
542622
if err != nil {
543-
return nil, err
623+
return nil, nil, err
544624
}
545625
slotAppSet, err := interfaces.NewSnapAppSet(slotInfo.Snap, nil)
546626
if err != nil {
547-
return nil, err
627+
return nil, nil, err
548628
}
549629

550630
cplug := interfaces.NewConnectedPlug(plugInfo, plugAppSet, newStaticPlugAttrs, connState.DynamicPlugAttrs)
@@ -568,6 +648,7 @@ ConnsLoop:
568648
reloadedConnections = append(reloadedConnections, connId)
569649

570650
if updateStaticAttrs {
651+
changedConns[connId] = cloneConnState(connState)
571652
connState.StaticPlugAttrs = staticPlugAttrs
572653
connState.StaticSlotAttrs = staticSlotAttrs
573654
connStateChanged = true
@@ -578,7 +659,7 @@ ConnsLoop:
578659
setConns(m.state, conns)
579660
}
580661

581-
return reloadedConnections, nil
662+
return reloadedConnections, changedConns, nil
582663
}
583664

584665
// 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)