Skip to content

Commit 35bc2c7

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

3 files changed

Lines changed: 108 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: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -426,15 +426,99 @@ 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 originalConns map[string]*schema.ConnState
493+
err := task.Get("original-connection-states", &originalConns)
494+
if errors.Is(err, state.ErrNoState) {
495+
return nil
496+
}
497+
if err != nil {
498+
return err
499+
}
500+
501+
conns, err := getConns(task.State())
502+
if err != nil {
503+
return err
504+
}
505+
for connID, connState := range originalConns {
506+
conns[connID] = cloneConnState(connState)
507+
}
508+
setConns(task.State(), conns)
509+
return nil
510+
}
511+
429512
// reloadConnections reloads connections stored in the state in the repository.
430513
// Using non-empty snapName the operation can be scoped to connections
431514
// affecting a given snap.
432515
//
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) {
516+
// The return value is the list of affected snap names and their connection IDs,
517+
// plus the original connection states that were changed.
518+
func (m *InterfaceManager) reloadConnections(snapName string) (reloadedConnectionIDs []string, changedConns map[string]*schema.ConnState, err error) {
435519
conns, err := getConns(m.state)
436520
if err != nil {
437-
return nil, err
521+
return nil, nil, err
438522
}
439523

440524
var policyChecker interfaces.PolicyFunc
@@ -445,21 +529,22 @@ func (m *InterfaceManager) reloadConnections(snapName string) (reloadedConnectio
445529
if errors.Is(err, state.ErrNoState) {
446530
// everything else is a noop, as no model means no connections
447531
// to reload
448-
return nil, nil
532+
return nil, nil, nil
449533
} else if err != nil {
450-
return nil, err
534+
return nil, nil, err
451535
}
452536
autoChecker, err = newAutoConnectChecker(m.state, m.repo, deviceCtx)
453537
if err != nil {
454-
return nil, err
538+
return nil, nil, err
455539
}
456540

457541
connChecker, err = newConnectChecker(m.state, deviceCtx)
458542
if err != nil {
459-
return nil, err
543+
return nil, nil, err
460544
}
461545

462546
connStateChanged := false
547+
changedConns = make(map[string]*schema.ConnState)
463548

464549
var reloadedConnections []string
465550
ConnsLoop:
@@ -473,7 +558,7 @@ ConnsLoop:
473558
}
474559
connRef, err := interfaces.ParseConnRef(connId)
475560
if err != nil {
476-
return nil, err
561+
return nil, nil, err
477562
}
478563
// Apply filtering, this allows us to reload only a subset of
479564
// connections (and similarly, refresh the static attributes of only a
@@ -497,13 +582,14 @@ ConnsLoop:
497582
for _, snapName := range []string{connRef.PlugRef.Snap, connRef.SlotRef.Snap} {
498583
broken, err := isBroken(m.state, snapName)
499584
if err != nil {
500-
return nil, err
585+
return nil, nil, err
501586
}
502587
if broken {
503588
logger.Noticef("Snap %q is broken, ignored by reloadConnections", snapName)
504589
continue ConnsLoop
505590
}
506591
}
592+
changedConns[connId] = cloneConnState(connState)
507593
delete(conns, connId)
508594
connStateChanged = true
509595
}
@@ -540,11 +626,11 @@ ConnsLoop:
540626

541627
plugAppSet, err := interfaces.NewSnapAppSet(plugInfo.Snap, nil)
542628
if err != nil {
543-
return nil, err
629+
return nil, nil, err
544630
}
545631
slotAppSet, err := interfaces.NewSnapAppSet(slotInfo.Snap, nil)
546632
if err != nil {
547-
return nil, err
633+
return nil, nil, err
548634
}
549635

550636
cplug := interfaces.NewConnectedPlug(plugInfo, plugAppSet, newStaticPlugAttrs, connState.DynamicPlugAttrs)
@@ -568,6 +654,7 @@ ConnsLoop:
568654
reloadedConnections = append(reloadedConnections, connId)
569655

570656
if updateStaticAttrs {
657+
changedConns[connId] = cloneConnState(connState)
571658
connState.StaticPlugAttrs = staticPlugAttrs
572659
connState.StaticSlotAttrs = staticSlotAttrs
573660
connStateChanged = true
@@ -578,7 +665,7 @@ ConnsLoop:
578665
setConns(m.state, conns)
579666
}
580667

581-
return reloadedConnections, nil
668+
return reloadedConnections, changedConns, nil
582669
}
583670

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