-
Notifications
You must be signed in to change notification settings - Fork 680
o/ifacestate: properly handle undo scenarios where auto-connections are dropped #17018
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
Changes from 3 commits
e07c813
5a5154a
86bc62d
3faf92e
d0bd8c6
d56f83c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -369,10 +369,18 @@ func (m *InterfaceManager) refreshAppSetConnections(task *state.Task, appSet *in | |
| task.Logf("%s", snap.BadInterfacesSummary(snapInfo)) | ||
| } | ||
|
|
||
| affectedConnections, err := m.reloadConnections(snapName) | ||
| affectedConnections, changedConns, err := m.reloadConnections(snapName) | ||
| if err != nil { | ||
| return nil, nil, err | ||
| } | ||
|
|
||
| // if this task modified any connection states, we take a snapshot of the | ||
| // original connections so that we can restore them on the undo path, if | ||
| // needed | ||
| if err := snapshotChangedConnectionsForUndo(task, snapName, changedConns); err != nil { | ||
| return nil, nil, err | ||
| } | ||
|
Comment on lines
+383
to
+387
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know this is super convenient place to put this code, it creates some implicit behaviour of this function that is really not obvious, and should as a minimum be documented. Ideally I'd rather see this as a return value an handled on a task level instead, but I recognize the annoying thing about changing the call chain that also goes through
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a completely naive thought: is there any reason we don't just cache the all the refreshed snaps connections from "conns" state at the start of Do and restore this during undo?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't wrong, it would be much more explicit. The catch is we don't know which ones are relevant until quite late, so we'd need to collect all of the connections of a snap, along with information on how they were established. Not great but doable. For some snaps, like snapd there's quite a lot of data to be stored this way.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I pushed a commit renaming some things and adding some comments. I agree that making the task handler itself store this data on the task would be nice, but that would require a pretty large refactor I think?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's that large a refactor, but it's also something we can do at another stage. |
||
|
|
||
| return disconnectedSnaps, affectedConnections, nil | ||
| } | ||
|
|
||
|
|
@@ -696,6 +704,9 @@ func (m *InterfaceManager) undoSetupProfiles(task *state.Task, tomb *tomb.Tomb) | |
| if err := snapstateFinishRestart(task, snapsup, finishOpts); err != nil { | ||
| return err | ||
| } | ||
| if err := restoreConnectionsForSetupProfiles(task); err != nil { | ||
| return err | ||
| } | ||
|
Comment on lines
+716
to
+718
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should mention that it relies on the task variable being set by |
||
|
|
||
| // Get the name from SnapSetup and use it to find the current SideInfo | ||
| // about the snap, if there is one. | ||
|
|
@@ -2003,7 +2014,7 @@ func (m *InterfaceManager) transitionConnectionsCoreMigration(st *state.State, o | |
| // on disk are rewritten. This is ok because core/ubuntu-core have | ||
| // exactly the same profiles and nothing in the generated policies | ||
| // has the core snap-name encoded. | ||
| if _, err := m.reloadConnections(newName); err != nil { | ||
| if _, _, err := m.reloadConnections(newName); err != nil { | ||
| return err | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -426,15 +426,104 @@ func isBroken(st *state.State, snapName string) (bool, error) { | |
| return false, nil | ||
| } | ||
|
|
||
| func cloneConnState(connState *schema.ConnState) *schema.ConnState { | ||
|
andrewphelpsj marked this conversation as resolved.
|
||
| clone := *connState | ||
|
|
||
| cloneAttrs := func(attrs map[string]any) map[string]any { | ||
| if attrs == nil { | ||
| return nil | ||
| } | ||
| return utils.CopyAttributes(attrs) | ||
| } | ||
|
|
||
| clone.StaticPlugAttrs = cloneAttrs(connState.StaticPlugAttrs) | ||
| clone.DynamicPlugAttrs = cloneAttrs(connState.DynamicPlugAttrs) | ||
| clone.StaticSlotAttrs = cloneAttrs(connState.StaticSlotAttrs) | ||
| clone.DynamicSlotAttrs = cloneAttrs(connState.DynamicSlotAttrs) | ||
|
|
||
| return &clone | ||
| } | ||
|
|
||
| // snapshotModifiedConnectionsForUndo records a snapshot of the connection | ||
|
andrewphelpsj marked this conversation as resolved.
Outdated
|
||
| // states, prior to modification. This enables the undo of setup-profiles to | ||
| // restore them, if needed. | ||
| func snapshotChangedConnectionsForUndo(task *state.Task, instanceName string, changedConns map[string]*schema.ConnState) error { | ||
| if len(changedConns) == 0 { | ||
| return nil | ||
| } | ||
|
|
||
| // undo setup-profiles also calls this code while rebuilding old profiles, | ||
| // but restoration data should only come from the original do path | ||
| if task.Status() == state.UndoingStatus { | ||
|
andrewphelpsj marked this conversation as resolved.
Outdated
|
||
| return nil | ||
| } | ||
|
|
||
| // if this isn't the setup-profiles task that is going to handle the undo, | ||
| // then we don't need to keep track of these on the task | ||
| if !shouldUndoSetupProfiles(task, instanceName) { | ||
| return nil | ||
| } | ||
|
|
||
| var connectionSnapshot map[string]*schema.ConnState | ||
| err := task.Get("changed-connection-snapshot", &connectionSnapshot) | ||
| if err != nil && !errors.Is(err, state.ErrNoState) { | ||
| return err | ||
| } | ||
| if connectionSnapshot == nil { | ||
| connectionSnapshot = make(map[string]*schema.ConnState) | ||
| } | ||
|
|
||
| for connID, connState := range changedConns { | ||
| if connectionSnapshot[connID] != nil { | ||
| // a setup-profiles task can be retried after saving the connection | ||
| // states and unlocking for backend setup. keep the first snapshot. | ||
| continue | ||
| } | ||
| connectionSnapshot[connID] = connState | ||
| } | ||
|
|
||
| task.Set("changed-connection-snapshot", connectionSnapshot) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // restoreConnectionsForSetupProfiles restores connection states saved on a | ||
| // setup-profiles task. | ||
| func restoreConnectionsForSetupProfiles(task *state.Task) error { | ||
| var connectionSnapshot map[string]*schema.ConnState | ||
| err := task.Get("changed-connection-snapshot", &connectionSnapshot) | ||
| if errors.Is(err, state.ErrNoState) { | ||
| return nil | ||
| } | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| st := task.State() | ||
|
|
||
| conns, err := getConns(st) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| for connID, connState := range connectionSnapshot { | ||
| conns[connID] = connState | ||
| } | ||
| setConns(st, conns) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // reloadConnections reloads connections stored in the state in the repository. | ||
| // Using non-empty snapName the operation can be scoped to connections | ||
| // affecting a given snap. | ||
| // | ||
| // The return value is the list of affected snap names and their connection IDs. | ||
| func (m *InterfaceManager) reloadConnections(snapName string) (reloadedConnectionIDs []string, err error) { | ||
| // The return value is the list of affected snap names and their connection IDs, | ||
|
andrewphelpsj marked this conversation as resolved.
Outdated
|
||
| // plus the original connection states that were changed. | ||
| func (m *InterfaceManager) reloadConnections(snapName string) (reloadedConnectionIDs []string, changedConns map[string]*schema.ConnState, err error) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose changedConns includes also dropped connections?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it contains both deleted connections and connections whose static attributes were updated. |
||
| conns, err := getConns(m.state) | ||
| if err != nil { | ||
| return nil, err | ||
| return nil, nil, err | ||
| } | ||
|
|
||
| var policyChecker interfaces.PolicyFunc | ||
|
|
@@ -445,21 +534,22 @@ func (m *InterfaceManager) reloadConnections(snapName string) (reloadedConnectio | |
| if errors.Is(err, state.ErrNoState) { | ||
| // everything else is a noop, as no model means no connections | ||
| // to reload | ||
| return nil, nil | ||
| return nil, nil, nil | ||
| } else if err != nil { | ||
| return nil, err | ||
| return nil, nil, err | ||
| } | ||
| autoChecker, err = newAutoConnectChecker(m.state, m.repo, deviceCtx) | ||
| if err != nil { | ||
| return nil, err | ||
| return nil, nil, err | ||
| } | ||
|
|
||
| connChecker, err = newConnectChecker(m.state, deviceCtx) | ||
| if err != nil { | ||
| return nil, err | ||
| return nil, nil, err | ||
| } | ||
|
|
||
| connStateChanged := false | ||
| changedConns = make(map[string]*schema.ConnState) | ||
|
|
||
| var reloadedConnections []string | ||
| ConnsLoop: | ||
|
|
@@ -473,7 +563,7 @@ ConnsLoop: | |
| } | ||
| connRef, err := interfaces.ParseConnRef(connId) | ||
| if err != nil { | ||
| return nil, err | ||
| return nil, nil, err | ||
| } | ||
| // Apply filtering, this allows us to reload only a subset of | ||
| // connections (and similarly, refresh the static attributes of only a | ||
|
|
@@ -497,13 +587,14 @@ ConnsLoop: | |
| for _, snapName := range []string{connRef.PlugRef.Snap, connRef.SlotRef.Snap} { | ||
| broken, err := isBroken(m.state, snapName) | ||
| if err != nil { | ||
| return nil, err | ||
| return nil, nil, err | ||
| } | ||
| if broken { | ||
| logger.Noticef("Snap %q is broken, ignored by reloadConnections", snapName) | ||
| continue ConnsLoop | ||
| } | ||
| } | ||
| changedConns[connId] = cloneConnState(connState) | ||
| delete(conns, connId) | ||
| connStateChanged = true | ||
| } | ||
|
|
@@ -540,11 +631,11 @@ ConnsLoop: | |
|
|
||
| plugAppSet, err := interfaces.NewSnapAppSet(plugInfo.Snap, nil) | ||
| if err != nil { | ||
| return nil, err | ||
| return nil, nil, err | ||
| } | ||
| slotAppSet, err := interfaces.NewSnapAppSet(slotInfo.Snap, nil) | ||
| if err != nil { | ||
| return nil, err | ||
| return nil, nil, err | ||
| } | ||
|
|
||
| cplug := interfaces.NewConnectedPlug(plugInfo, plugAppSet, newStaticPlugAttrs, connState.DynamicPlugAttrs) | ||
|
|
@@ -568,6 +659,7 @@ ConnsLoop: | |
| reloadedConnections = append(reloadedConnections, connId) | ||
|
|
||
| if updateStaticAttrs { | ||
| changedConns[connId] = cloneConnState(connState) | ||
| connState.StaticPlugAttrs = staticPlugAttrs | ||
| connState.StaticSlotAttrs = staticSlotAttrs | ||
| connStateChanged = true | ||
|
|
@@ -578,7 +670,7 @@ ConnsLoop: | |
| setConns(m.state, conns) | ||
| } | ||
|
|
||
| return reloadedConnections, nil | ||
| return reloadedConnections, changedConns, nil | ||
| } | ||
|
|
||
| // removeConnections disconnects all connections of the snap in the repo. It should only be used if the snap | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.