snapstate: support for pre-refresh hook #4281

Merged
merged 26 commits into from Nov 29, 2017

Conversation

Projects
None yet
5 participants
Contributor

stolowski commented Nov 23, 2017

This branch completes the support for refresh hooks by providing pre-refresh hook that is run during refresh before switching to the new revision (as opposed to post-refresh hook).

Note: Depends on #4259.

zyga approved these changes Nov 23, 2017

LGTM but important question about services and hooks in this context.

+ return HookTask(st, summary, hooksup, nil)
+}
+
+func SetupPreRefreshHook(st *state.State, snapName string) *state.Task {
@zyga

zyga Nov 23, 2017

Contributor

As a general comment it'd be nice if we had a no-op thing here when the snap in question doesn't have a particular hook.

@stolowski

stolowski Nov 23, 2017

Contributor

That applies to the hook machinery, although IMHO it's useful that we have the task logged and visible even if there is no hook ("Run foo hook of bar snap if present" - that's what we see in the tasks). In any case it's a material for separate discussion if we want that.

@pedronis

pedronis Nov 23, 2017

Contributor

in general we might not even know if the hook is there (because of implicit hooks) when we setup the tasks in a change

overlord/snapstate/snapstate.go
+ }
+
+ if runRefreshHooks {
+ preRefreshHook := SetupPreRefreshHook(st, snapsup.Name())
@zyga

zyga Nov 23, 2017

Contributor

Can the refresh hook restart the services because it runs after they are stopped and can poke their sockets and what not?

@stolowski

stolowski Nov 23, 2017

Contributor

Yes, it can, there is nothing that prevents it currently. I see how this can be an issue that leads to a failure if service is started for the old revision of the snap in pre-refresh. We can easily prevent this and error out in snapctl based on hook name, if that's what we want.

@niemeyer

niemeyer Nov 27, 2017

Contributor

Why are we running this after the services are stopped? Part of the reason of running this before it stops is precisely so that we have the data and the old environment at hand. People will need the service running if we intend them to be able to dump data before the refresh, for example.

overlord/state/taskrunner.go
@@ -336,7 +336,15 @@ func (r *TaskRunner) Ensure() {
}
continue
}
+
+ if mustWait(t) {
@zyga

zyga Nov 23, 2017

Contributor

This is from another PR, right?

@stolowski

stolowski Nov 23, 2017

Contributor

Right.

codecov-io commented Nov 23, 2017

Codecov Report

Merging #4281 into master will decrease coverage by 0.01%.
The diff coverage is 59.25%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4281      +/-   ##
==========================================
- Coverage   76.19%   76.18%   -0.02%     
==========================================
  Files         445      445              
  Lines       38738    38759      +21     
==========================================
+ Hits        29516    29528      +12     
- Misses       7207     7216       +9     
  Partials     2015     2015
Impacted Files Coverage Δ
snap/hooktypes.go 100% <ø> (ø) ⬆️
overlord/hookstate/hooks.go 16.12% <18.18%> (+0.44%) ⬆️
overlord/snapstate/snapstate.go 81.77% <87.5%> (-0.02%) ⬇️
overlord/ifacestate/helpers.go 60.26% <0%> (+0.66%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5d384d5...44d370a. Read the comment docs.

@zyga zyga added the Blocked label Nov 23, 2017

Contributor

zyga commented Nov 23, 2017

I'm marking this as blocked as the prerequisite PR is still in a state where changes are requested.

@pedronis pedronis removed the Blocked label Nov 24, 2017

Some questions about the details there:

overlord/snapstate/snapstate.go
+ }
+
+ if runRefreshHooks {
+ preRefreshHook := SetupPreRefreshHook(st, snapsup.Name())
@zyga

zyga Nov 23, 2017

Contributor

Can the refresh hook restart the services because it runs after they are stopped and can poke their sockets and what not?

@stolowski

stolowski Nov 23, 2017

Contributor

Yes, it can, there is nothing that prevents it currently. I see how this can be an issue that leads to a failure if service is started for the old revision of the snap in pre-refresh. We can easily prevent this and error out in snapctl based on hook name, if that's what we want.

@niemeyer

niemeyer Nov 27, 2017

Contributor

Why are we running this after the services are stopped? Part of the reason of running this before it stops is precisely so that we have the data and the old environment at hand. People will need the service running if we intend them to be able to dump data before the refresh, for example.

overlord/snapstate/snapstate.go
+ if snapst.Active {
@niemeyer

niemeyer Nov 27, 2017

Contributor

Also, this sounds suspect. Does it mean we're running the hook even when the snap is not active? In which context would this make sense, or otherwise in which ways will it create problems?

@stolowski

stolowski Nov 27, 2017

Contributor

You're right about running the hook before stopping rather than after. I missed this aspect as I considered pre- and post- refresh hooks to be about running before and after switching active revision. Will fix.

@pedronis pedronis added this to the 2.30 milestone Nov 27, 2017

Contributor

stolowski commented Nov 28, 2017

Fixed per latest agreements.

lgtm

Two trivials and LGTM. Thanks!

overlord/snapstate/snapstate.go
@@ -61,6 +61,10 @@ func needsMaybeCore(typ snap.Type) int {
}
func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int) (*state.TaskSet, error) {
+ if snapst.IsInstalled() && !snapst.Active {
+ return nil, fmt.Errorf("internal error: cannot update a disabled snap")
@niemeyer

niemeyer Nov 29, 2017

Contributor

There's no reason for this to be an internal error, as this is a real situation that can happen by the user manipulating the snap state in sane ways. We just want to make sure that case is forbidden here.

overlord/snapstate/snapstate.go
@@ -122,14 +126,23 @@ func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int
prev = checkAsserts
}
+ // run refresh hooks when updating existing snap, otherwise run install
+ runRefreshHooks := (snapst.IsInstalled() && !snapsup.Flags.Revert)
@niemeyer

niemeyer Nov 29, 2017

Contributor

This should be moved right above the condition that uses it below. Otherwise this is lost between code that is completely unrelated to it.

couple small comments

overlord/snapstate/snapstate.go
@@ -61,6 +61,10 @@ func needsMaybeCore(typ snap.Type) int {
}
func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int) (*state.TaskSet, error) {
+ if snapst.IsInstalled() && !snapst.Active {
+ return nil, fmt.Errorf("cannot update a disabled snap %q", snapsup.Name())
@pedronis

pedronis Nov 29, 2017

Contributor

article "a" is not needed if we have the name

overlord/snapstate/snapstate.go
@@ -122,14 +126,23 @@ func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int
prev = checkAsserts
}
+ runRefreshHooks := (snapst.IsInstalled() && !snapsup.Flags.Revert)
@pedronis

pedronis Nov 29, 2017

Contributor

I think Gustavo meant the flag should also be computed near first use

@pedronis pedronis merged commit 804514c into snapcore:master Nov 29, 2017

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment