github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/overlord/hookstate/hookstate_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package hookstate_test
    21  
    22  import (
    23  	"context"
    24  	"encoding/json"
    25  	"fmt"
    26  	"path/filepath"
    27  	"regexp"
    28  	"sync/atomic"
    29  	"testing"
    30  	"time"
    31  
    32  	. "gopkg.in/check.v1"
    33  	"gopkg.in/tomb.v2"
    34  
    35  	"github.com/snapcore/snapd/dirs"
    36  	"github.com/snapcore/snapd/overlord"
    37  	"github.com/snapcore/snapd/overlord/configstate/config"
    38  	"github.com/snapcore/snapd/overlord/hookstate"
    39  	"github.com/snapcore/snapd/overlord/hookstate/hooktest"
    40  	"github.com/snapcore/snapd/overlord/snapstate"
    41  	"github.com/snapcore/snapd/overlord/state"
    42  	"github.com/snapcore/snapd/snap"
    43  	"github.com/snapcore/snapd/snap/snaptest"
    44  	"github.com/snapcore/snapd/testutil"
    45  )
    46  
    47  func TestHookManager(t *testing.T) { TestingT(t) }
    48  
    49  type baseHookManagerSuite struct {
    50  	testutil.BaseTest
    51  
    52  	o           *overlord.Overlord
    53  	state       *state.State
    54  	se          *overlord.StateEngine
    55  	manager     *hookstate.HookManager
    56  	context     *hookstate.Context
    57  	mockHandler *hooktest.MockHandler
    58  	task        *state.Task
    59  	change      *state.Change
    60  	command     *testutil.MockCmd
    61  }
    62  
    63  func (s *baseHookManagerSuite) commonSetUpTest(c *C) {
    64  	s.BaseTest.SetUpTest(c)
    65  
    66  	hooktype1 := snap.NewHookType(regexp.MustCompile("^do-something$"))
    67  	hooktype2 := snap.NewHookType(regexp.MustCompile("^undo-something$"))
    68  	s.AddCleanup(snap.MockAppendSupportedHookTypes([]*snap.HookType{hooktype1, hooktype2}))
    69  
    70  	dirs.SetRootDir(c.MkDir())
    71  	s.o = overlord.Mock()
    72  	s.state = s.o.State()
    73  	manager, err := hookstate.Manager(s.state, s.o.TaskRunner())
    74  	c.Assert(err, IsNil)
    75  	s.manager = manager
    76  	s.se = s.o.StateEngine()
    77  	s.o.AddManager(s.manager)
    78  	s.o.AddManager(s.o.TaskRunner())
    79  	c.Assert(s.o.StartUp(), IsNil)
    80  
    81  	s.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    82  
    83  	s.command = testutil.MockCommand(c, "snap", "")
    84  	s.AddCleanup(s.command.Restore)
    85  
    86  	s.context = nil
    87  	s.mockHandler = hooktest.NewMockHandler()
    88  	s.manager.Register(regexp.MustCompile("configure"), func(context *hookstate.Context) hookstate.Handler {
    89  		s.context = context
    90  		return s.mockHandler
    91  	})
    92  	s.AddCleanup(hookstate.MockErrtrackerReport(func(string, string, string, map[string]string) (string, error) {
    93  		return "", nil
    94  	}))
    95  }
    96  
    97  func (s *baseHookManagerSuite) commonTearDownTest(c *C) {
    98  	s.BaseTest.TearDownTest(c)
    99  
   100  	s.manager.StopHooks()
   101  	s.se.Stop()
   102  	dirs.SetRootDir("")
   103  }
   104  
   105  func (s *baseHookManagerSuite) setUpSnap(c *C, instanceName string, yaml string) {
   106  	hooksup := &hookstate.HookSetup{
   107  		Snap:     instanceName,
   108  		Hook:     "configure",
   109  		Revision: snap.R(1),
   110  	}
   111  
   112  	initialContext := map[string]interface{}{
   113  		"test-key": "test-value",
   114  	}
   115  
   116  	s.state.Lock()
   117  	defer s.state.Unlock()
   118  	s.task = hookstate.HookTask(s.state, "test summary", hooksup, initialContext)
   119  	c.Assert(s.task, NotNil, Commentf("Expected HookTask to return a task"))
   120  
   121  	s.change = s.state.NewChange("kind", "summary")
   122  	s.change.AddTask(s.task)
   123  
   124  	snapName, instanceKey := snap.SplitInstanceName(instanceName)
   125  
   126  	sideInfo := &snap.SideInfo{RealName: snapName, SnapID: "some-snap-id", Revision: snap.R(1)}
   127  	snaptest.MockSnapInstance(c, instanceName, yaml, sideInfo)
   128  	snapstate.Set(s.state, instanceName, &snapstate.SnapState{
   129  		Active:      true,
   130  		Sequence:    []*snap.SideInfo{sideInfo},
   131  		Current:     snap.R(1),
   132  		InstanceKey: instanceKey,
   133  	})
   134  }
   135  
   136  type hookManagerSuite struct {
   137  	baseHookManagerSuite
   138  }
   139  
   140  var _ = Suite(&hookManagerSuite{})
   141  
   142  var snapYaml = `
   143  name: test-snap
   144  version: 1.0
   145  hooks:
   146      configure:
   147      prepare-device:
   148      do-something:
   149      undo-something:
   150  `
   151  
   152  var snapYaml1 = `
   153  name: test-snap-1
   154  version: 1.0
   155  hooks:
   156      prepare-device:
   157  `
   158  
   159  var snapYaml2 = `
   160  name: test-snap-2
   161  version: 1.0
   162  hooks:
   163      prepare-device:
   164  `
   165  
   166  func (s *hookManagerSuite) SetUpTest(c *C) {
   167  	s.commonSetUpTest(c)
   168  
   169  	s.setUpSnap(c, "test-snap", snapYaml)
   170  }
   171  
   172  func (s *hookManagerSuite) TearDownTest(c *C) {
   173  	s.commonTearDownTest(c)
   174  }
   175  
   176  func (s *hookManagerSuite) settle(c *C) {
   177  	err := s.o.Settle(5 * time.Second)
   178  	c.Assert(err, IsNil)
   179  }
   180  
   181  func (s *hookManagerSuite) TestSmoke(c *C) {
   182  	s.se.Ensure()
   183  	s.se.Wait()
   184  }
   185  
   186  func (s *hookManagerSuite) TestHookSetupJsonMarshal(c *C) {
   187  	hookSetup := &hookstate.HookSetup{Snap: "snap-name", Revision: snap.R(1), Hook: "hook-name"}
   188  	out, err := json.Marshal(hookSetup)
   189  	c.Assert(err, IsNil)
   190  	c.Check(string(out), Equals, "{\"snap\":\"snap-name\",\"revision\":\"1\",\"hook\":\"hook-name\"}")
   191  }
   192  
   193  func (s *hookManagerSuite) TestHookSetupJsonUnmarshal(c *C) {
   194  	out, err := json.Marshal(hookstate.HookSetup{Snap: "snap-name", Revision: snap.R(1), Hook: "hook-name"})
   195  	c.Assert(err, IsNil)
   196  
   197  	var setup hookstate.HookSetup
   198  	err = json.Unmarshal(out, &setup)
   199  	c.Assert(err, IsNil)
   200  	c.Check(setup.Snap, Equals, "snap-name")
   201  	c.Check(setup.Revision, Equals, snap.R(1))
   202  	c.Check(setup.Hook, Equals, "hook-name")
   203  }
   204  
   205  func (s *hookManagerSuite) TestHookTask(c *C) {
   206  	s.state.Lock()
   207  	defer s.state.Unlock()
   208  
   209  	hooksup := &hookstate.HookSetup{
   210  		Snap:     "test-snap",
   211  		Hook:     "configure",
   212  		Revision: snap.R(1),
   213  	}
   214  
   215  	task := hookstate.HookTask(s.state, "test summary", hooksup, nil)
   216  	c.Check(task.Kind(), Equals, "run-hook")
   217  
   218  	var setup hookstate.HookSetup
   219  	err := task.Get("hook-setup", &setup)
   220  	c.Check(err, IsNil)
   221  	c.Check(setup.Snap, Equals, "test-snap")
   222  	c.Check(setup.Revision, Equals, snap.R(1))
   223  	c.Check(setup.Hook, Equals, "configure")
   224  }
   225  
   226  func (s *hookManagerSuite) TestHookTaskEnsure(c *C) {
   227  	didRun := make(chan bool)
   228  	s.mockHandler.BeforeCallback = func() {
   229  		c.Check(s.manager.NumRunningHooks(), Equals, 1)
   230  		go func() {
   231  			didRun <- s.manager.GracefullyWaitRunningHooks()
   232  		}()
   233  	}
   234  	s.se.Ensure()
   235  	select {
   236  	case ok := <-didRun:
   237  		c.Check(ok, Equals, true)
   238  	case <-time.After(5 * time.Second):
   239  		c.Fatal("hook run should have been done by now")
   240  	}
   241  	s.se.Wait()
   242  
   243  	s.state.Lock()
   244  	defer s.state.Unlock()
   245  
   246  	c.Assert(s.context, NotNil, Commentf("Expected handler generator to be called with a valid context"))
   247  	c.Check(s.context.InstanceName(), Equals, "test-snap")
   248  	c.Check(s.context.SnapRevision(), Equals, snap.R(1))
   249  	c.Check(s.context.HookName(), Equals, "configure")
   250  
   251  	c.Check(s.command.Calls(), DeepEquals, [][]string{{
   252  		"snap", "run", "--hook", "configure", "-r", "1", "test-snap",
   253  	}})
   254  
   255  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   256  	c.Check(s.mockHandler.DoneCalled, Equals, true)
   257  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   258  
   259  	c.Check(s.task.Kind(), Equals, "run-hook")
   260  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   261  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   262  
   263  	c.Check(s.manager.NumRunningHooks(), Equals, 0)
   264  }
   265  
   266  func (s *hookManagerSuite) TestHookTaskEnsureRestarting(c *C) {
   267  	// we do no start new hooks runs if we are restarting
   268  	s.state.RequestRestart(state.RestartDaemon)
   269  
   270  	s.se.Ensure()
   271  	s.se.Wait()
   272  
   273  	s.state.Lock()
   274  	defer s.state.Unlock()
   275  
   276  	c.Assert(s.context, IsNil)
   277  
   278  	c.Check(s.command.Calls(), HasLen, 0)
   279  
   280  	c.Check(s.mockHandler.BeforeCalled, Equals, false)
   281  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   282  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   283  
   284  	c.Check(s.task.Status(), Equals, state.DoingStatus)
   285  	c.Check(s.change.Status(), Equals, state.DoingStatus)
   286  
   287  	c.Check(s.manager.NumRunningHooks(), Equals, 0)
   288  }
   289  
   290  func (s *hookManagerSuite) TestHookSnapMissing(c *C) {
   291  	s.state.Lock()
   292  	snapstate.Set(s.state, "test-snap", nil)
   293  	s.state.Unlock()
   294  
   295  	s.se.Ensure()
   296  	s.se.Wait()
   297  
   298  	s.state.Lock()
   299  	defer s.state.Unlock()
   300  
   301  	c.Check(s.change.Err(), ErrorMatches, `(?s).*cannot find "test-snap" snap.*`)
   302  }
   303  
   304  func (s *hookManagerSuite) TestHookHijackingHappy(c *C) {
   305  	// this works even if test-snap is not present
   306  	s.state.Lock()
   307  	snapstate.Set(s.state, "test-snap", nil)
   308  	s.state.Unlock()
   309  
   310  	var hijackedContext *hookstate.Context
   311  	s.manager.RegisterHijack("configure", "test-snap", func(ctx *hookstate.Context) error {
   312  		hijackedContext = ctx
   313  		return nil
   314  	})
   315  
   316  	s.se.Ensure()
   317  	s.se.Wait()
   318  
   319  	s.state.Lock()
   320  	defer s.state.Unlock()
   321  
   322  	c.Check(hijackedContext, DeepEquals, s.context)
   323  	c.Check(s.command.Calls(), HasLen, 0)
   324  
   325  	c.Assert(s.context, NotNil)
   326  	c.Check(s.context.InstanceName(), Equals, "test-snap")
   327  	c.Check(s.context.SnapRevision(), Equals, snap.R(1))
   328  	c.Check(s.context.HookName(), Equals, "configure")
   329  
   330  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   331  	c.Check(s.mockHandler.DoneCalled, Equals, true)
   332  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   333  
   334  	c.Check(s.task.Kind(), Equals, "run-hook")
   335  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   336  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   337  }
   338  
   339  func (s *hookManagerSuite) TestHookHijackingUnHappy(c *C) {
   340  	s.manager.RegisterHijack("configure", "test-snap", func(ctx *hookstate.Context) error {
   341  		return fmt.Errorf("not-happy-at-all")
   342  	})
   343  
   344  	s.se.Ensure()
   345  	s.se.Wait()
   346  
   347  	s.state.Lock()
   348  	defer s.state.Unlock()
   349  
   350  	c.Check(s.command.Calls(), HasLen, 0)
   351  
   352  	c.Assert(s.context, NotNil)
   353  	c.Check(s.context.InstanceName(), Equals, "test-snap")
   354  	c.Check(s.context.SnapRevision(), Equals, snap.R(1))
   355  	c.Check(s.context.HookName(), Equals, "configure")
   356  
   357  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   358  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   359  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   360  
   361  	c.Check(s.task.Kind(), Equals, "run-hook")
   362  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   363  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   364  }
   365  
   366  func (s *hookManagerSuite) TestHookHijackingVeryUnHappy(c *C) {
   367  	f := func(ctx *hookstate.Context) error {
   368  		return nil
   369  	}
   370  	s.manager.RegisterHijack("configure", "test-snap", f)
   371  	c.Check(func() { s.manager.RegisterHijack("configure", "test-snap", f) }, PanicMatches, "hook configure for snap test-snap already hijacked")
   372  }
   373  
   374  func (s *hookManagerSuite) TestHookTaskInitializesContext(c *C) {
   375  	s.se.Ensure()
   376  	s.se.Wait()
   377  
   378  	var value string
   379  	c.Assert(s.context, NotNil, Commentf("Expected handler generator to be called with a valid context"))
   380  	s.context.Lock()
   381  	defer s.context.Unlock()
   382  	c.Check(s.context.Get("test-key", &value), IsNil, Commentf("Expected context to be initialized"))
   383  	c.Check(value, Equals, "test-value")
   384  }
   385  
   386  func (s *hookManagerSuite) TestHookTaskHandlesHookError(c *C) {
   387  	// Force the snap command to exit 1, and print something to stderr
   388  	cmd := testutil.MockCommand(
   389  		c, "snap", ">&2 echo 'hook failed at user request'; exit 1")
   390  	defer cmd.Restore()
   391  
   392  	s.se.Ensure()
   393  	s.se.Wait()
   394  
   395  	s.state.Lock()
   396  	defer s.state.Unlock()
   397  
   398  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   399  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   400  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   401  
   402  	c.Check(s.task.Kind(), Equals, "run-hook")
   403  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   404  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   405  	checkTaskLogContains(c, s.task, ".*failed at user request.*")
   406  
   407  	c.Check(s.manager.NumRunningHooks(), Equals, 0)
   408  }
   409  
   410  func (s *hookManagerSuite) TestHookTaskHandleIgnoreErrorWorks(c *C) {
   411  	s.state.Lock()
   412  	var hooksup hookstate.HookSetup
   413  	s.task.Get("hook-setup", &hooksup)
   414  	hooksup.IgnoreError = true
   415  	s.task.Set("hook-setup", &hooksup)
   416  	s.state.Unlock()
   417  
   418  	// Force the snap command to exit 1, and print something to stderr
   419  	cmd := testutil.MockCommand(
   420  		c, "snap", ">&2 echo 'hook failed at user request'; exit 1")
   421  	defer cmd.Restore()
   422  
   423  	s.se.Ensure()
   424  	s.se.Wait()
   425  
   426  	s.state.Lock()
   427  	defer s.state.Unlock()
   428  
   429  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   430  	c.Check(s.mockHandler.DoneCalled, Equals, true)
   431  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   432  
   433  	c.Check(s.task.Kind(), Equals, "run-hook")
   434  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   435  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   436  	checkTaskLogContains(c, s.task, ".*ignoring failure in hook.*")
   437  }
   438  
   439  func (s *hookManagerSuite) TestHookTaskEnforcesTimeout(c *C) {
   440  	var hooksup hookstate.HookSetup
   441  
   442  	s.state.Lock()
   443  	s.task.Get("hook-setup", &hooksup)
   444  	hooksup.Timeout = time.Duration(200 * time.Millisecond)
   445  	s.task.Set("hook-setup", &hooksup)
   446  	s.state.Unlock()
   447  
   448  	// Force the snap command to hang
   449  	cmd := testutil.MockCommand(c, "snap", "while true; do sleep 1; done")
   450  	defer cmd.Restore()
   451  
   452  	s.se.Ensure()
   453  	s.se.Wait()
   454  
   455  	s.state.Lock()
   456  	defer s.state.Unlock()
   457  
   458  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   459  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   460  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   461  	c.Check(s.mockHandler.Err, ErrorMatches, `.*exceeded maximum runtime of 200ms.*`)
   462  
   463  	c.Check(s.task.Kind(), Equals, "run-hook")
   464  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   465  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   466  	checkTaskLogContains(c, s.task, `.*exceeded maximum runtime of 200ms`)
   467  }
   468  
   469  func (s *hookManagerSuite) TestHookTaskEnforcesDefaultTimeout(c *C) {
   470  	restore := hookstate.MockDefaultHookTimeout(150 * time.Millisecond)
   471  	defer restore()
   472  
   473  	// Force the snap command to hang
   474  	cmd := testutil.MockCommand(c, "snap", "while true; do sleep 1; done")
   475  	defer cmd.Restore()
   476  
   477  	s.se.Ensure()
   478  	s.se.Wait()
   479  
   480  	s.state.Lock()
   481  	defer s.state.Unlock()
   482  
   483  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   484  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   485  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   486  	c.Check(s.mockHandler.Err, ErrorMatches, `.*exceeded maximum runtime of 150ms.*`)
   487  
   488  	c.Check(s.task.Kind(), Equals, "run-hook")
   489  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   490  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   491  	checkTaskLogContains(c, s.task, `.*exceeded maximum runtime of 150ms`)
   492  }
   493  
   494  func (s *hookManagerSuite) TestHookTaskEnforcedTimeoutWithIgnoreError(c *C) {
   495  	var hooksup hookstate.HookSetup
   496  
   497  	s.state.Lock()
   498  	s.task.Get("hook-setup", &hooksup)
   499  	hooksup.Timeout = time.Duration(200 * time.Millisecond)
   500  	hooksup.IgnoreError = true
   501  	s.task.Set("hook-setup", &hooksup)
   502  	s.state.Unlock()
   503  
   504  	// Force the snap command to hang
   505  	cmd := testutil.MockCommand(c, "snap", "while true; do sleep 1; done")
   506  	defer cmd.Restore()
   507  
   508  	s.se.Ensure()
   509  	s.se.Wait()
   510  
   511  	s.state.Lock()
   512  	defer s.state.Unlock()
   513  
   514  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   515  	c.Check(s.mockHandler.DoneCalled, Equals, true)
   516  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   517  	c.Check(s.mockHandler.Err, IsNil)
   518  
   519  	c.Check(s.task.Kind(), Equals, "run-hook")
   520  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   521  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   522  	checkTaskLogContains(c, s.task, `.*ignoring failure in hook.*exceeded maximum runtime of 200ms`)
   523  }
   524  
   525  func (s *hookManagerSuite) TestHookTaskCanKillHook(c *C) {
   526  	// Force the snap command to hang
   527  	cmd := testutil.MockCommand(c, "snap", "while true; do sleep 1; done")
   528  	defer cmd.Restore()
   529  
   530  	s.se.Ensure()
   531  
   532  	// Abort the change, which should kill the hanging hook, and
   533  	// wait for the task to complete.
   534  	s.state.Lock()
   535  	s.change.Abort()
   536  	s.state.Unlock()
   537  	s.se.Ensure()
   538  	s.se.Wait()
   539  
   540  	s.state.Lock()
   541  	defer s.state.Unlock()
   542  
   543  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   544  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   545  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   546  	c.Check(s.mockHandler.Err, ErrorMatches, "<aborted>")
   547  
   548  	c.Check(s.task.Kind(), Equals, "run-hook")
   549  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   550  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   551  	checkTaskLogContains(c, s.task, `run hook "[^"]*": <aborted>`)
   552  
   553  	c.Check(s.manager.NumRunningHooks(), Equals, 0)
   554  }
   555  
   556  func (s *hookManagerSuite) TestHookTaskCorrectlyIncludesContext(c *C) {
   557  	// Force the snap command to exit with a failure and print to stderr so we
   558  	// can catch and verify it.
   559  	cmd := testutil.MockCommand(
   560  		c, "snap", ">&2 echo \"SNAP_COOKIE=$SNAP_COOKIE\"; exit 1")
   561  	defer cmd.Restore()
   562  
   563  	s.se.Ensure()
   564  	s.se.Wait()
   565  
   566  	s.state.Lock()
   567  	defer s.state.Unlock()
   568  
   569  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   570  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   571  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   572  
   573  	c.Check(s.task.Kind(), Equals, "run-hook")
   574  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   575  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   576  	checkTaskLogContains(c, s.task, `.*SNAP_COOKIE=\S+`)
   577  }
   578  
   579  func (s *hookManagerSuite) TestHookTaskHandlerBeforeError(c *C) {
   580  	s.mockHandler.BeforeError = true
   581  
   582  	s.se.Ensure()
   583  	s.se.Wait()
   584  
   585  	s.state.Lock()
   586  	defer s.state.Unlock()
   587  
   588  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   589  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   590  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   591  
   592  	c.Check(s.task.Kind(), Equals, "run-hook")
   593  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   594  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   595  	checkTaskLogContains(c, s.task, `.*Before failed at user request.*`)
   596  }
   597  
   598  func (s *hookManagerSuite) TestHookTaskHandlerDoneError(c *C) {
   599  	s.mockHandler.DoneError = true
   600  
   601  	s.se.Ensure()
   602  	s.se.Wait()
   603  
   604  	s.state.Lock()
   605  	defer s.state.Unlock()
   606  
   607  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   608  	c.Check(s.mockHandler.DoneCalled, Equals, true)
   609  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   610  
   611  	c.Check(s.task.Kind(), Equals, "run-hook")
   612  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   613  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   614  	checkTaskLogContains(c, s.task, `.*Done failed at user request.*`)
   615  }
   616  
   617  func (s *hookManagerSuite) TestHookTaskHandlerErrorError(c *C) {
   618  	s.mockHandler.ErrorError = true
   619  
   620  	// Force the snap command to simply exit 1, so the handler Error() runs
   621  	cmd := testutil.MockCommand(c, "snap", "exit 1")
   622  	defer cmd.Restore()
   623  
   624  	s.se.Ensure()
   625  	s.se.Wait()
   626  
   627  	s.state.Lock()
   628  	defer s.state.Unlock()
   629  
   630  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   631  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   632  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   633  
   634  	c.Check(s.task.Kind(), Equals, "run-hook")
   635  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   636  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   637  	checkTaskLogContains(c, s.task, `.*Error failed at user request.*`)
   638  }
   639  
   640  func (s *hookManagerSuite) TestHookUndoRunsOnError(c *C) {
   641  	handler := hooktest.NewMockHandler()
   642  	undoHandler := hooktest.NewMockHandler()
   643  
   644  	s.manager.Register(regexp.MustCompile("^do-something$"), func(context *hookstate.Context) hookstate.Handler {
   645  		return handler
   646  	})
   647  	s.manager.Register(regexp.MustCompile("^undo-something$"), func(context *hookstate.Context) hookstate.Handler {
   648  		return undoHandler
   649  	})
   650  
   651  	hooksup := &hookstate.HookSetup{
   652  		Snap:     "test-snap",
   653  		Hook:     "do-something",
   654  		Revision: snap.R(1),
   655  	}
   656  	undohooksup := &hookstate.HookSetup{
   657  		Snap:     "test-snap",
   658  		Hook:     "undo-something",
   659  		Revision: snap.R(1),
   660  	}
   661  
   662  	// use unknown hook to fail the change
   663  	failinghooksup := &hookstate.HookSetup{
   664  		Snap:     "test-snap",
   665  		Hook:     "unknown-hook",
   666  		Revision: snap.R(1),
   667  	}
   668  
   669  	initialContext := map[string]interface{}{}
   670  
   671  	s.state.Lock()
   672  	task := hookstate.HookTaskWithUndo(s.state, "test summary", hooksup, undohooksup, initialContext)
   673  	c.Assert(task, NotNil)
   674  	failtask := hookstate.HookTask(s.state, "test summary", failinghooksup, initialContext)
   675  	failtask.WaitFor(task)
   676  
   677  	change := s.state.NewChange("kind", "summary")
   678  	change.AddTask(task)
   679  	change.AddTask(failtask)
   680  	s.state.Unlock()
   681  
   682  	s.settle(c)
   683  
   684  	s.state.Lock()
   685  	defer s.state.Unlock()
   686  
   687  	c.Check(handler.BeforeCalled, Equals, true)
   688  	c.Check(handler.DoneCalled, Equals, true)
   689  	c.Check(handler.ErrorCalled, Equals, false)
   690  
   691  	c.Check(undoHandler.BeforeCalled, Equals, true)
   692  	c.Check(undoHandler.DoneCalled, Equals, true)
   693  	c.Check(undoHandler.ErrorCalled, Equals, false)
   694  
   695  	c.Check(task.Status(), Equals, state.UndoneStatus)
   696  	c.Check(change.Status(), Equals, state.ErrorStatus)
   697  
   698  	c.Check(s.manager.NumRunningHooks(), Equals, 0)
   699  }
   700  
   701  func (s *hookManagerSuite) TestHookWithoutHandlerIsError(c *C) {
   702  	hooksup := &hookstate.HookSetup{
   703  		Snap:     "test-snap",
   704  		Hook:     "prepare-device",
   705  		Revision: snap.R(1),
   706  	}
   707  	s.state.Lock()
   708  	s.task.Set("hook-setup", hooksup)
   709  	s.state.Unlock()
   710  
   711  	s.se.Ensure()
   712  	s.se.Wait()
   713  
   714  	s.state.Lock()
   715  	defer s.state.Unlock()
   716  
   717  	c.Check(s.task.Kind(), Equals, "run-hook")
   718  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   719  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   720  	checkTaskLogContains(c, s.task, `.*no registered handlers for hook "prepare-device".*`)
   721  }
   722  
   723  func (s *hookManagerSuite) TestHookWithMultipleHandlersIsError(c *C) {
   724  	// Register multiple times for this hook
   725  	s.manager.Register(regexp.MustCompile("configure"), func(context *hookstate.Context) hookstate.Handler {
   726  		return hooktest.NewMockHandler()
   727  	})
   728  
   729  	s.se.Ensure()
   730  	s.se.Wait()
   731  
   732  	s.state.Lock()
   733  	defer s.state.Unlock()
   734  
   735  	c.Check(s.task.Kind(), Equals, "run-hook")
   736  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   737  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   738  
   739  	checkTaskLogContains(c, s.task, `.*2 handlers registered for hook "configure".*`)
   740  }
   741  
   742  func (s *hookManagerSuite) TestHookWithoutHookIsError(c *C) {
   743  	hooksup := &hookstate.HookSetup{
   744  		Snap: "test-snap",
   745  		Hook: "missing-hook",
   746  	}
   747  	s.state.Lock()
   748  	s.task.Set("hook-setup", hooksup)
   749  	s.state.Unlock()
   750  
   751  	s.se.Ensure()
   752  	s.se.Wait()
   753  
   754  	s.state.Lock()
   755  	defer s.state.Unlock()
   756  
   757  	c.Check(s.task.Kind(), Equals, "run-hook")
   758  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   759  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   760  	checkTaskLogContains(c, s.task, `.*snap "test-snap" has no "missing-hook" hook`)
   761  }
   762  
   763  func (s *hookManagerSuite) TestHookWithoutHookOptional(c *C) {
   764  	s.manager.Register(regexp.MustCompile("missing-hook"), func(context *hookstate.Context) hookstate.Handler {
   765  		return s.mockHandler
   766  	})
   767  
   768  	hooksup := &hookstate.HookSetup{
   769  		Snap:     "test-snap",
   770  		Hook:     "missing-hook",
   771  		Optional: true,
   772  	}
   773  	s.state.Lock()
   774  	s.task.Set("hook-setup", hooksup)
   775  	s.state.Unlock()
   776  
   777  	s.se.Ensure()
   778  	s.se.Wait()
   779  
   780  	c.Check(s.mockHandler.BeforeCalled, Equals, false)
   781  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   782  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   783  
   784  	c.Check(s.command.Calls(), IsNil)
   785  
   786  	s.state.Lock()
   787  	defer s.state.Unlock()
   788  
   789  	c.Check(s.task.Kind(), Equals, "run-hook")
   790  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   791  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   792  
   793  	c.Logf("Task log:\n%s\n", s.task.Log())
   794  }
   795  
   796  func (s *hookManagerSuite) TestHookWithoutHookAlways(c *C) {
   797  	s.manager.Register(regexp.MustCompile("missing-hook"), func(context *hookstate.Context) hookstate.Handler {
   798  		return s.mockHandler
   799  	})
   800  
   801  	hooksup := &hookstate.HookSetup{
   802  		Snap:     "test-snap",
   803  		Hook:     "missing-hook",
   804  		Optional: true,
   805  		Always:   true,
   806  	}
   807  	s.state.Lock()
   808  	s.task.Set("hook-setup", hooksup)
   809  	s.state.Unlock()
   810  
   811  	s.se.Ensure()
   812  	s.se.Wait()
   813  
   814  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   815  	c.Check(s.mockHandler.DoneCalled, Equals, true)
   816  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   817  
   818  	c.Check(s.command.Calls(), IsNil)
   819  
   820  	s.state.Lock()
   821  	defer s.state.Unlock()
   822  
   823  	c.Check(s.task.Kind(), Equals, "run-hook")
   824  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   825  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   826  
   827  	c.Logf("Task log:\n%s\n", s.task.Log())
   828  }
   829  
   830  func (s *hookManagerSuite) TestOptionalHookWithMissingHandler(c *C) {
   831  	hooksup := &hookstate.HookSetup{
   832  		Snap:     "test-snap",
   833  		Hook:     "missing-hook-and-no-handler",
   834  		Optional: true,
   835  	}
   836  	s.state.Lock()
   837  	s.task.Set("hook-setup", hooksup)
   838  	s.state.Unlock()
   839  
   840  	s.se.Ensure()
   841  	s.se.Wait()
   842  
   843  	c.Check(s.command.Calls(), IsNil)
   844  
   845  	s.state.Lock()
   846  	defer s.state.Unlock()
   847  
   848  	c.Check(s.task.Kind(), Equals, "run-hook")
   849  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   850  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   851  
   852  	c.Logf("Task log:\n%s\n", s.task.Log())
   853  }
   854  
   855  func checkTaskLogContains(c *C, task *state.Task, pattern string) {
   856  	exp := regexp.MustCompile(pattern)
   857  	found := false
   858  	for _, message := range task.Log() {
   859  		if exp.MatchString(message) {
   860  			found = true
   861  		}
   862  	}
   863  
   864  	c.Check(found, Equals, true, Commentf("Expected to find regex %q in task log: %v", pattern, task.Log()))
   865  }
   866  
   867  func (s *hookManagerSuite) TestHookTaskRunsRightSnapCmd(c *C) {
   868  	coreSnapCmdPath := filepath.Join(dirs.SnapMountDir, "core/12/usr/bin/snap")
   869  	cmd := testutil.MockCommand(c, coreSnapCmdPath, "")
   870  	defer cmd.Restore()
   871  
   872  	r := hookstate.MockReadlink(func(p string) (string, error) {
   873  		c.Assert(p, Equals, "/proc/self/exe")
   874  		return filepath.Join(dirs.SnapMountDir, "core/12/usr/lib/snapd/snapd"), nil
   875  	})
   876  	defer r()
   877  
   878  	s.se.Ensure()
   879  	s.se.Wait()
   880  
   881  	s.state.Lock()
   882  	defer s.state.Unlock()
   883  
   884  	c.Assert(s.context, NotNil, Commentf("Expected handler generator to be called with a valid context"))
   885  	c.Check(cmd.Calls(), DeepEquals, [][]string{{
   886  		"snap", "run", "--hook", "configure", "-r", "1", "test-snap",
   887  	}})
   888  
   889  }
   890  
   891  func (s *hookManagerSuite) TestHookTaskHandlerReportsErrorIfRequested(c *C) {
   892  	s.state.Lock()
   893  	var hooksup hookstate.HookSetup
   894  	s.task.Get("hook-setup", &hooksup)
   895  	hooksup.TrackError = true
   896  	s.task.Set("hook-setup", &hooksup)
   897  	s.state.Unlock()
   898  
   899  	errtrackerCalled := false
   900  	hookstate.MockErrtrackerReport(func(snap, errmsg, dupSig string, extra map[string]string) (string, error) {
   901  		c.Check(snap, Equals, "test-snap")
   902  		c.Check(errmsg, Equals, "hook configure in snap \"test-snap\" failed: hook failed at user request")
   903  		c.Check(dupSig, Equals, "hook:test-snap:configure:exit status 1\nhook failed at user request\n")
   904  
   905  		errtrackerCalled = true
   906  		return "some-oopsid", nil
   907  	})
   908  
   909  	// Force the snap command to exit 1, and print something to stderr
   910  	cmd := testutil.MockCommand(
   911  		c, "snap", ">&2 echo 'hook failed at user request'; exit 1")
   912  	defer cmd.Restore()
   913  
   914  	s.se.Ensure()
   915  	s.se.Wait()
   916  
   917  	s.state.Lock()
   918  	defer s.state.Unlock()
   919  
   920  	c.Check(errtrackerCalled, Equals, true)
   921  }
   922  
   923  func (s *hookManagerSuite) TestHookTaskHandlerReportsErrorDisabled(c *C) {
   924  	s.state.Lock()
   925  	var hooksup hookstate.HookSetup
   926  	s.task.Get("hook-setup", &hooksup)
   927  	hooksup.TrackError = true
   928  	s.task.Set("hook-setup", &hooksup)
   929  
   930  	tr := config.NewTransaction(s.state)
   931  	tr.Set("core", "problem-reports.disabled", true)
   932  	tr.Commit()
   933  	s.state.Unlock()
   934  
   935  	hookstate.MockErrtrackerReport(func(snap, errmsg, dupSig string, extra map[string]string) (string, error) {
   936  		c.Fatalf("no error reports should be generated")
   937  		return "", nil
   938  	})
   939  
   940  	// Force the snap command to exit 1, and print something to stderr
   941  	cmd := testutil.MockCommand(
   942  		c, "snap", ">&2 echo 'hook failed at user request'; exit 1")
   943  	defer cmd.Restore()
   944  
   945  	s.se.Ensure()
   946  	s.se.Wait()
   947  
   948  	s.state.Lock()
   949  	defer s.state.Unlock()
   950  }
   951  
   952  func (s *hookManagerSuite) TestHookTasksForSameSnapAreSerialized(c *C) {
   953  	var Executing int32
   954  	var TotalExecutions int32
   955  
   956  	s.mockHandler.BeforeCallback = func() {
   957  		executing := atomic.AddInt32(&Executing, 1)
   958  		if executing != 1 {
   959  			panic(fmt.Sprintf("More than one handler executed: %d", executing))
   960  		}
   961  	}
   962  
   963  	s.mockHandler.DoneCallback = func() {
   964  		executing := atomic.AddInt32(&Executing, -1)
   965  		if executing != 0 {
   966  			panic(fmt.Sprintf("More than one handler executed: %d", executing))
   967  		}
   968  		atomic.AddInt32(&TotalExecutions, 1)
   969  	}
   970  
   971  	hooksup := &hookstate.HookSetup{
   972  		Snap:     "test-snap",
   973  		Hook:     "configure",
   974  		Revision: snap.R(1),
   975  	}
   976  
   977  	s.state.Lock()
   978  
   979  	var tasks []*state.Task
   980  	for i := 0; i < 20; i++ {
   981  		task := hookstate.HookTask(s.state, "test summary", hooksup, nil)
   982  		c.Assert(s.task, NotNil)
   983  		change := s.state.NewChange("kind", "summary")
   984  		change.AddTask(task)
   985  		tasks = append(tasks, task)
   986  	}
   987  	s.state.Unlock()
   988  
   989  	s.settle(c)
   990  
   991  	s.state.Lock()
   992  	defer s.state.Unlock()
   993  
   994  	c.Check(s.task.Kind(), Equals, "run-hook")
   995  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   996  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   997  
   998  	for i := 0; i < len(tasks); i++ {
   999  		c.Check(tasks[i].Kind(), Equals, "run-hook")
  1000  		c.Check(tasks[i].Status(), Equals, state.DoneStatus)
  1001  	}
  1002  	c.Assert(atomic.LoadInt32(&TotalExecutions), Equals, int32(1+len(tasks)))
  1003  	c.Assert(atomic.LoadInt32(&Executing), Equals, int32(0))
  1004  }
  1005  
  1006  type MockConcurrentHandler struct {
  1007  	onDone func()
  1008  }
  1009  
  1010  func (h *MockConcurrentHandler) Before() error {
  1011  	return nil
  1012  }
  1013  
  1014  func (h *MockConcurrentHandler) Done() error {
  1015  	h.onDone()
  1016  	return nil
  1017  }
  1018  
  1019  func (h *MockConcurrentHandler) Error(err error) error {
  1020  	return nil
  1021  }
  1022  
  1023  func NewMockConcurrentHandler(onDone func()) *MockConcurrentHandler {
  1024  	return &MockConcurrentHandler{onDone: onDone}
  1025  }
  1026  
  1027  func (s *hookManagerSuite) TestHookTasksForDifferentSnapsRunConcurrently(c *C) {
  1028  	hooksup1 := &hookstate.HookSetup{
  1029  		Snap:     "test-snap-1",
  1030  		Hook:     "prepare-device",
  1031  		Revision: snap.R(1),
  1032  	}
  1033  	hooksup2 := &hookstate.HookSetup{
  1034  		Snap:     "test-snap-2",
  1035  		Hook:     "prepare-device",
  1036  		Revision: snap.R(1),
  1037  	}
  1038  
  1039  	s.state.Lock()
  1040  
  1041  	sideInfo := &snap.SideInfo{RealName: "test-snap-1", SnapID: "some-snap-id1", Revision: snap.R(1)}
  1042  	info := snaptest.MockSnap(c, snapYaml1, sideInfo)
  1043  	c.Assert(info.Hooks, HasLen, 1)
  1044  	snapstate.Set(s.state, "test-snap-1", &snapstate.SnapState{
  1045  		Active:   true,
  1046  		Sequence: []*snap.SideInfo{sideInfo},
  1047  		Current:  snap.R(1),
  1048  	})
  1049  
  1050  	sideInfo = &snap.SideInfo{RealName: "test-snap-2", SnapID: "some-snap-id2", Revision: snap.R(1)}
  1051  	snaptest.MockSnap(c, snapYaml2, sideInfo)
  1052  	snapstate.Set(s.state, "test-snap-2", &snapstate.SnapState{
  1053  		Active:   true,
  1054  		Sequence: []*snap.SideInfo{sideInfo},
  1055  		Current:  snap.R(1),
  1056  	})
  1057  
  1058  	var testSnap1HookCalls, testSnap2HookCalls int
  1059  	ch := make(chan struct{})
  1060  	mockHandler1 := NewMockConcurrentHandler(func() {
  1061  		ch <- struct{}{}
  1062  		testSnap1HookCalls++
  1063  	})
  1064  	mockHandler2 := NewMockConcurrentHandler(func() {
  1065  		<-ch
  1066  		testSnap2HookCalls++
  1067  	})
  1068  	s.manager.Register(regexp.MustCompile("prepare-device"), func(context *hookstate.Context) hookstate.Handler {
  1069  		if context.InstanceName() == "test-snap-1" {
  1070  			return mockHandler1
  1071  		}
  1072  		if context.InstanceName() == "test-snap-2" {
  1073  			return mockHandler2
  1074  		}
  1075  		c.Fatalf("unknown snap: %s", context.InstanceName())
  1076  		return nil
  1077  	})
  1078  
  1079  	task1 := hookstate.HookTask(s.state, "test summary", hooksup1, nil)
  1080  	c.Assert(task1, NotNil)
  1081  	change1 := s.state.NewChange("kind", "summary")
  1082  	change1.AddTask(task1)
  1083  
  1084  	task2 := hookstate.HookTask(s.state, "test summary", hooksup2, nil)
  1085  	c.Assert(task2, NotNil)
  1086  	change2 := s.state.NewChange("kind", "summary")
  1087  	change2.AddTask(task2)
  1088  
  1089  	s.state.Unlock()
  1090  
  1091  	s.settle(c)
  1092  
  1093  	s.state.Lock()
  1094  	defer s.state.Unlock()
  1095  
  1096  	c.Check(task1.Status(), Equals, state.DoneStatus)
  1097  	c.Check(change1.Status(), Equals, state.DoneStatus)
  1098  	c.Check(task2.Status(), Equals, state.DoneStatus)
  1099  	c.Check(change2.Status(), Equals, state.DoneStatus)
  1100  	c.Assert(testSnap1HookCalls, Equals, 1)
  1101  	c.Assert(testSnap2HookCalls, Equals, 1)
  1102  }
  1103  
  1104  func (s *hookManagerSuite) TestCompatForConfigureSnapd(c *C) {
  1105  	st := s.state
  1106  
  1107  	st.Lock()
  1108  	defer st.Unlock()
  1109  
  1110  	task := st.NewTask("configure-snapd", "Snapd between 2.29 and 2.30 in edge insertd those tasks")
  1111  	chg := st.NewChange("configure", "configure snapd")
  1112  	chg.AddTask(task)
  1113  
  1114  	st.Unlock()
  1115  	s.se.Ensure()
  1116  	s.se.Wait()
  1117  	st.Lock()
  1118  
  1119  	c.Check(chg.Status(), Equals, state.DoneStatus)
  1120  	c.Check(task.Status(), Equals, state.DoneStatus)
  1121  }
  1122  
  1123  func (s *hookManagerSuite) TestGracefullyWaitRunningHooksTimeout(c *C) {
  1124  	restore := hookstate.MockDefaultHookTimeout(100 * time.Millisecond)
  1125  	defer restore()
  1126  
  1127  	// this works even if test-snap is not present
  1128  	s.state.Lock()
  1129  	snapstate.Set(s.state, "test-snap", nil)
  1130  	s.state.Unlock()
  1131  
  1132  	quit := make(chan struct{})
  1133  	defer func() {
  1134  		quit <- struct{}{}
  1135  	}()
  1136  	didRun := make(chan bool)
  1137  	s.mockHandler.BeforeCallback = func() {
  1138  		c.Check(s.manager.NumRunningHooks(), Equals, 1)
  1139  		go func() {
  1140  			didRun <- s.manager.GracefullyWaitRunningHooks()
  1141  		}()
  1142  	}
  1143  
  1144  	s.manager.RegisterHijack("configure", "test-snap", func(ctx *hookstate.Context) error {
  1145  		<-quit
  1146  		return nil
  1147  	})
  1148  
  1149  	s.se.Ensure()
  1150  	select {
  1151  	case noPending := <-didRun:
  1152  		c.Check(noPending, Equals, false)
  1153  	case <-time.After(2 * time.Second):
  1154  		c.Fatal("timeout should have expired")
  1155  	}
  1156  }
  1157  
  1158  func (s *hookManagerSuite) TestSnapstateOpConflict(c *C) {
  1159  	s.state.Lock()
  1160  	defer s.state.Unlock()
  1161  	_, err := snapstate.Disable(s.state, "test-snap")
  1162  	c.Assert(err, ErrorMatches, `snap "test-snap" has "kind" change in progress`)
  1163  }
  1164  
  1165  func (s *hookManagerSuite) TestHookHijackingNoConflict(c *C) {
  1166  	s.state.Lock()
  1167  	defer s.state.Unlock()
  1168  
  1169  	s.manager.RegisterHijack("configure", "test-snap", func(ctx *hookstate.Context) error {
  1170  		return nil
  1171  	})
  1172  
  1173  	// no conflict on hijacked hooks
  1174  	_, err := snapstate.Disable(s.state, "test-snap")
  1175  	c.Assert(err, IsNil)
  1176  }
  1177  
  1178  func (s *hookManagerSuite) TestEphemeralRunHook(c *C) {
  1179  	contextData := map[string]interface{}{
  1180  		"key":  "value",
  1181  		"key2": "value2",
  1182  	}
  1183  	s.testEphemeralRunHook(c, contextData)
  1184  }
  1185  
  1186  func (s *hookManagerSuite) TestEphemeralRunHookNoContextData(c *C) {
  1187  	var contextData map[string]interface{} = nil
  1188  	s.testEphemeralRunHook(c, contextData)
  1189  }
  1190  
  1191  func (s *hookManagerSuite) testEphemeralRunHook(c *C, contextData map[string]interface{}) {
  1192  	var hookInvokeCalled []string
  1193  	hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
  1194  		c.Check(ctx.HookName(), Equals, "configure")
  1195  		hookInvokeCalled = append(hookInvokeCalled, ctx.HookName())
  1196  
  1197  		// check that context data was set correctly
  1198  		var s string
  1199  		ctx.Lock()
  1200  		defer ctx.Unlock()
  1201  		for k, v := range contextData {
  1202  			ctx.Get(k, &s)
  1203  			c.Check(s, Equals, v)
  1204  		}
  1205  		ctx.Set("key-set-from-hook", "value-set-from-hook")
  1206  
  1207  		return []byte("some output"), nil
  1208  	}
  1209  	restore := hookstate.MockRunHook(hookInvoke)
  1210  	defer restore()
  1211  
  1212  	hooksup := &hookstate.HookSetup{
  1213  		Snap:     "test-snap",
  1214  		Revision: snap.R(1),
  1215  		Hook:     "configure",
  1216  	}
  1217  	context, err := s.manager.EphemeralRunHook(context.Background(), hooksup, contextData)
  1218  	c.Assert(err, IsNil)
  1219  	c.Check(hookInvokeCalled, DeepEquals, []string{"configure"})
  1220  
  1221  	var value string
  1222  	context.Lock()
  1223  	context.Get("key-set-from-hook", &value)
  1224  	context.Unlock()
  1225  	c.Check(value, Equals, "value-set-from-hook")
  1226  }
  1227  
  1228  func (s *hookManagerSuite) TestEphemeralRunHookNoSnap(c *C) {
  1229  	hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
  1230  		c.Fatalf("hook should not be invoked in this test")
  1231  		return nil, nil
  1232  	}
  1233  	restore := hookstate.MockRunHook(hookInvoke)
  1234  	defer restore()
  1235  
  1236  	hooksup := &hookstate.HookSetup{
  1237  		Snap:     "not-installed-snap",
  1238  		Revision: snap.R(1),
  1239  		Hook:     "configure",
  1240  	}
  1241  	contextData := map[string]interface{}{
  1242  		"key": "value",
  1243  	}
  1244  	_, err := s.manager.EphemeralRunHook(context.Background(), hooksup, contextData)
  1245  	c.Assert(err, ErrorMatches, `cannot run ephemeral hook "configure" for snap "not-installed-snap": no state entry for key`)
  1246  }
  1247  
  1248  func (s *hookManagerSuite) TestEphemeralRunHookContextCanCancel(c *C) {
  1249  	tombDying := 0
  1250  	hookRunning := make(chan struct{})
  1251  
  1252  	hookInvoke := func(_ *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
  1253  		close(hookRunning)
  1254  
  1255  		select {
  1256  		case <-tomb.Dying():
  1257  			tombDying++
  1258  		case <-time.After(10 * time.Second):
  1259  			c.Fatalf("hook not canceled after 10s")
  1260  		}
  1261  		return nil, nil
  1262  	}
  1263  	restore := hookstate.MockRunHook(hookInvoke)
  1264  	defer restore()
  1265  
  1266  	hooksup := &hookstate.HookSetup{
  1267  		Snap:     "test-snap",
  1268  		Revision: snap.R(1),
  1269  		Hook:     "configure",
  1270  	}
  1271  
  1272  	ctx, cancelFunc := context.WithCancel(context.Background())
  1273  	go func() {
  1274  		<-hookRunning
  1275  		cancelFunc()
  1276  	}()
  1277  	_, err := s.manager.EphemeralRunHook(ctx, hooksup, nil)
  1278  	c.Assert(err, IsNil)
  1279  	c.Check(tombDying, Equals, 1)
  1280  }
  1281  
  1282  type parallelInstancesHookManagerSuite struct {
  1283  	baseHookManagerSuite
  1284  }
  1285  
  1286  var _ = Suite(&parallelInstancesHookManagerSuite{})
  1287  
  1288  func (s *parallelInstancesHookManagerSuite) SetUpTest(c *C) {
  1289  	s.commonSetUpTest(c)
  1290  	s.setUpSnap(c, "test-snap_instance", snapYaml)
  1291  }
  1292  
  1293  func (s *parallelInstancesHookManagerSuite) TearDownTest(c *C) {
  1294  	s.commonTearDownTest(c)
  1295  }
  1296  
  1297  func (s *parallelInstancesHookManagerSuite) TestHookTaskEnsureHookRan(c *C) {
  1298  	didRun := make(chan bool)
  1299  	s.mockHandler.BeforeCallback = func() {
  1300  		c.Check(s.manager.NumRunningHooks(), Equals, 1)
  1301  		go func() {
  1302  			didRun <- s.manager.GracefullyWaitRunningHooks()
  1303  		}()
  1304  	}
  1305  	s.se.Ensure()
  1306  	select {
  1307  	case ok := <-didRun:
  1308  		c.Check(ok, Equals, true)
  1309  	case <-time.After(5 * time.Second):
  1310  		c.Fatal("hook run should have been done by now")
  1311  	}
  1312  	s.se.Wait()
  1313  
  1314  	s.state.Lock()
  1315  	defer s.state.Unlock()
  1316  
  1317  	c.Check(s.context.InstanceName(), Equals, "test-snap_instance")
  1318  	c.Check(s.context.SnapRevision(), Equals, snap.R(1))
  1319  	c.Check(s.context.HookName(), Equals, "configure")
  1320  
  1321  	c.Check(s.command.Calls(), DeepEquals, [][]string{{
  1322  		"snap", "run", "--hook", "configure", "-r", "1", "test-snap_instance",
  1323  	}})
  1324  
  1325  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
  1326  	c.Check(s.mockHandler.DoneCalled, Equals, true)
  1327  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
  1328  
  1329  	c.Check(s.task.Kind(), Equals, "run-hook")
  1330  	c.Check(s.task.Status(), Equals, state.DoneStatus)
  1331  	c.Check(s.change.Status(), Equals, state.DoneStatus)
  1332  
  1333  	c.Check(s.manager.NumRunningHooks(), Equals, 0)
  1334  }