gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/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) TestHookTaskHandlesHookErrorAndIgnoresIt(c *C) {
   440  	// tell the mock handler to return 'true' from its Error() handler,
   441  	// indicating to the hookmgr to ignore the original hook error.
   442  	s.mockHandler.IgnoreOriginalErr = true
   443  
   444  	// Simulate hook error
   445  	cmd := testutil.MockCommand(
   446  		c, "snap", ">&2 echo 'hook failed at user request'; exit 1")
   447  	defer cmd.Restore()
   448  
   449  	s.se.Ensure()
   450  	s.se.Wait()
   451  
   452  	s.state.Lock()
   453  	defer s.state.Unlock()
   454  
   455  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   456  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   457  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   458  
   459  	c.Check(s.task.Kind(), Equals, "run-hook")
   460  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   461  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   462  
   463  	c.Check(s.manager.NumRunningHooks(), Equals, 0)
   464  }
   465  
   466  func (s *hookManagerSuite) TestHookTaskEnforcesTimeout(c *C) {
   467  	var hooksup hookstate.HookSetup
   468  
   469  	s.state.Lock()
   470  	s.task.Get("hook-setup", &hooksup)
   471  	hooksup.Timeout = time.Duration(200 * time.Millisecond)
   472  	s.task.Set("hook-setup", &hooksup)
   473  	s.state.Unlock()
   474  
   475  	// Force the snap command to hang
   476  	cmd := testutil.MockCommand(c, "snap", "while true; do sleep 1; done")
   477  	defer cmd.Restore()
   478  
   479  	s.se.Ensure()
   480  	s.se.Wait()
   481  
   482  	s.state.Lock()
   483  	defer s.state.Unlock()
   484  
   485  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   486  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   487  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   488  	c.Check(s.mockHandler.Err, ErrorMatches, `.*exceeded maximum runtime of 200ms.*`)
   489  
   490  	c.Check(s.task.Kind(), Equals, "run-hook")
   491  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   492  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   493  	checkTaskLogContains(c, s.task, `.*exceeded maximum runtime of 200ms`)
   494  }
   495  
   496  func (s *hookManagerSuite) TestHookTaskEnforcesDefaultTimeout(c *C) {
   497  	restore := hookstate.MockDefaultHookTimeout(150 * time.Millisecond)
   498  	defer restore()
   499  
   500  	// Force the snap command to hang
   501  	cmd := testutil.MockCommand(c, "snap", "while true; do sleep 1; done")
   502  	defer cmd.Restore()
   503  
   504  	s.se.Ensure()
   505  	s.se.Wait()
   506  
   507  	s.state.Lock()
   508  	defer s.state.Unlock()
   509  
   510  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   511  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   512  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   513  	c.Check(s.mockHandler.Err, ErrorMatches, `.*exceeded maximum runtime of 150ms.*`)
   514  
   515  	c.Check(s.task.Kind(), Equals, "run-hook")
   516  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   517  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   518  	checkTaskLogContains(c, s.task, `.*exceeded maximum runtime of 150ms`)
   519  }
   520  
   521  func (s *hookManagerSuite) TestHookTaskEnforcedTimeoutWithIgnoreError(c *C) {
   522  	var hooksup hookstate.HookSetup
   523  
   524  	s.state.Lock()
   525  	s.task.Get("hook-setup", &hooksup)
   526  	hooksup.Timeout = time.Duration(200 * time.Millisecond)
   527  	hooksup.IgnoreError = true
   528  	s.task.Set("hook-setup", &hooksup)
   529  	s.state.Unlock()
   530  
   531  	// Force the snap command to hang
   532  	cmd := testutil.MockCommand(c, "snap", "while true; do sleep 1; done")
   533  	defer cmd.Restore()
   534  
   535  	s.se.Ensure()
   536  	s.se.Wait()
   537  
   538  	s.state.Lock()
   539  	defer s.state.Unlock()
   540  
   541  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   542  	c.Check(s.mockHandler.DoneCalled, Equals, true)
   543  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   544  	c.Check(s.mockHandler.Err, IsNil)
   545  
   546  	c.Check(s.task.Kind(), Equals, "run-hook")
   547  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   548  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   549  	checkTaskLogContains(c, s.task, `.*ignoring failure in hook.*exceeded maximum runtime of 200ms`)
   550  }
   551  
   552  func (s *hookManagerSuite) TestHookTaskCanKillHook(c *C) {
   553  	// Force the snap command to hang
   554  	cmd := testutil.MockCommand(c, "snap", "while true; do sleep 1; done")
   555  	defer cmd.Restore()
   556  
   557  	s.se.Ensure()
   558  
   559  	// Abort the change, which should kill the hanging hook, and
   560  	// wait for the task to complete.
   561  	s.state.Lock()
   562  	s.change.Abort()
   563  	s.state.Unlock()
   564  	s.se.Ensure()
   565  	s.se.Wait()
   566  
   567  	s.state.Lock()
   568  	defer s.state.Unlock()
   569  
   570  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   571  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   572  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   573  	c.Check(s.mockHandler.Err, ErrorMatches, "<aborted>")
   574  
   575  	c.Check(s.task.Kind(), Equals, "run-hook")
   576  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   577  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   578  	checkTaskLogContains(c, s.task, `run hook "[^"]*": <aborted>`)
   579  
   580  	c.Check(s.manager.NumRunningHooks(), Equals, 0)
   581  }
   582  
   583  func (s *hookManagerSuite) TestHookTaskCorrectlyIncludesContext(c *C) {
   584  	// Force the snap command to exit with a failure and print to stderr so we
   585  	// can catch and verify it.
   586  	cmd := testutil.MockCommand(
   587  		c, "snap", ">&2 echo \"SNAP_COOKIE=$SNAP_COOKIE\"; exit 1")
   588  	defer cmd.Restore()
   589  
   590  	s.se.Ensure()
   591  	s.se.Wait()
   592  
   593  	s.state.Lock()
   594  	defer s.state.Unlock()
   595  
   596  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   597  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   598  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   599  
   600  	c.Check(s.task.Kind(), Equals, "run-hook")
   601  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   602  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   603  	checkTaskLogContains(c, s.task, `.*SNAP_COOKIE=\S+`)
   604  }
   605  
   606  func (s *hookManagerSuite) TestHookTaskHandlerBeforeError(c *C) {
   607  	s.mockHandler.BeforeError = true
   608  
   609  	s.se.Ensure()
   610  	s.se.Wait()
   611  
   612  	s.state.Lock()
   613  	defer s.state.Unlock()
   614  
   615  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   616  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   617  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   618  
   619  	c.Check(s.task.Kind(), Equals, "run-hook")
   620  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   621  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   622  	checkTaskLogContains(c, s.task, `.*Before failed at user request.*`)
   623  }
   624  
   625  func (s *hookManagerSuite) TestHookTaskHandlerDoneError(c *C) {
   626  	s.mockHandler.DoneError = true
   627  
   628  	s.se.Ensure()
   629  	s.se.Wait()
   630  
   631  	s.state.Lock()
   632  	defer s.state.Unlock()
   633  
   634  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   635  	c.Check(s.mockHandler.DoneCalled, Equals, true)
   636  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   637  
   638  	c.Check(s.task.Kind(), Equals, "run-hook")
   639  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   640  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   641  	checkTaskLogContains(c, s.task, `.*Done failed at user request.*`)
   642  }
   643  
   644  func (s *hookManagerSuite) TestHookTaskHandlerErrorError(c *C) {
   645  	s.mockHandler.ErrorError = true
   646  
   647  	// Force the snap command to simply exit 1, so the handler Error() runs
   648  	cmd := testutil.MockCommand(c, "snap", "exit 1")
   649  	defer cmd.Restore()
   650  
   651  	s.se.Ensure()
   652  	s.se.Wait()
   653  
   654  	s.state.Lock()
   655  	defer s.state.Unlock()
   656  
   657  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   658  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   659  	c.Check(s.mockHandler.ErrorCalled, Equals, true)
   660  
   661  	c.Check(s.task.Kind(), Equals, "run-hook")
   662  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   663  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   664  	checkTaskLogContains(c, s.task, `.*Error failed at user request.*`)
   665  }
   666  
   667  func (s *hookManagerSuite) TestHookUndoRunsOnError(c *C) {
   668  	handler := hooktest.NewMockHandler()
   669  	undoHandler := hooktest.NewMockHandler()
   670  
   671  	s.manager.Register(regexp.MustCompile("^do-something$"), func(context *hookstate.Context) hookstate.Handler {
   672  		return handler
   673  	})
   674  	s.manager.Register(regexp.MustCompile("^undo-something$"), func(context *hookstate.Context) hookstate.Handler {
   675  		return undoHandler
   676  	})
   677  
   678  	hooksup := &hookstate.HookSetup{
   679  		Snap:     "test-snap",
   680  		Hook:     "do-something",
   681  		Revision: snap.R(1),
   682  	}
   683  	undohooksup := &hookstate.HookSetup{
   684  		Snap:     "test-snap",
   685  		Hook:     "undo-something",
   686  		Revision: snap.R(1),
   687  	}
   688  
   689  	// use unknown hook to fail the change
   690  	failinghooksup := &hookstate.HookSetup{
   691  		Snap:     "test-snap",
   692  		Hook:     "unknown-hook",
   693  		Revision: snap.R(1),
   694  	}
   695  
   696  	initialContext := map[string]interface{}{}
   697  
   698  	s.state.Lock()
   699  	task := hookstate.HookTaskWithUndo(s.state, "test summary", hooksup, undohooksup, initialContext)
   700  	c.Assert(task, NotNil)
   701  	failtask := hookstate.HookTask(s.state, "test summary", failinghooksup, initialContext)
   702  	failtask.WaitFor(task)
   703  
   704  	change := s.state.NewChange("kind", "summary")
   705  	change.AddTask(task)
   706  	change.AddTask(failtask)
   707  	s.state.Unlock()
   708  
   709  	s.settle(c)
   710  
   711  	s.state.Lock()
   712  	defer s.state.Unlock()
   713  
   714  	c.Check(handler.BeforeCalled, Equals, true)
   715  	c.Check(handler.DoneCalled, Equals, true)
   716  	c.Check(handler.ErrorCalled, Equals, false)
   717  
   718  	c.Check(undoHandler.BeforeCalled, Equals, true)
   719  	c.Check(undoHandler.DoneCalled, Equals, true)
   720  	c.Check(undoHandler.ErrorCalled, Equals, false)
   721  
   722  	c.Check(task.Status(), Equals, state.UndoneStatus)
   723  	c.Check(change.Status(), Equals, state.ErrorStatus)
   724  
   725  	c.Check(s.manager.NumRunningHooks(), Equals, 0)
   726  }
   727  
   728  func (s *hookManagerSuite) TestHookWithoutHandlerIsError(c *C) {
   729  	hooksup := &hookstate.HookSetup{
   730  		Snap:     "test-snap",
   731  		Hook:     "prepare-device",
   732  		Revision: snap.R(1),
   733  	}
   734  	s.state.Lock()
   735  	s.task.Set("hook-setup", hooksup)
   736  	s.state.Unlock()
   737  
   738  	s.se.Ensure()
   739  	s.se.Wait()
   740  
   741  	s.state.Lock()
   742  	defer s.state.Unlock()
   743  
   744  	c.Check(s.task.Kind(), Equals, "run-hook")
   745  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   746  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   747  	checkTaskLogContains(c, s.task, `.*no registered handlers for hook "prepare-device".*`)
   748  }
   749  
   750  func (s *hookManagerSuite) TestHookWithMultipleHandlersIsError(c *C) {
   751  	// Register multiple times for this hook
   752  	s.manager.Register(regexp.MustCompile("configure"), func(context *hookstate.Context) hookstate.Handler {
   753  		return hooktest.NewMockHandler()
   754  	})
   755  
   756  	s.se.Ensure()
   757  	s.se.Wait()
   758  
   759  	s.state.Lock()
   760  	defer s.state.Unlock()
   761  
   762  	c.Check(s.task.Kind(), Equals, "run-hook")
   763  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   764  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   765  
   766  	checkTaskLogContains(c, s.task, `.*2 handlers registered for hook "configure".*`)
   767  }
   768  
   769  func (s *hookManagerSuite) TestHookWithoutHookIsError(c *C) {
   770  	hooksup := &hookstate.HookSetup{
   771  		Snap: "test-snap",
   772  		Hook: "missing-hook",
   773  	}
   774  	s.state.Lock()
   775  	s.task.Set("hook-setup", hooksup)
   776  	s.state.Unlock()
   777  
   778  	s.se.Ensure()
   779  	s.se.Wait()
   780  
   781  	s.state.Lock()
   782  	defer s.state.Unlock()
   783  
   784  	c.Check(s.task.Kind(), Equals, "run-hook")
   785  	c.Check(s.task.Status(), Equals, state.ErrorStatus)
   786  	c.Check(s.change.Status(), Equals, state.ErrorStatus)
   787  	checkTaskLogContains(c, s.task, `.*snap "test-snap" has no "missing-hook" hook`)
   788  }
   789  
   790  func (s *hookManagerSuite) TestHookWithoutHookOptional(c *C) {
   791  	s.manager.Register(regexp.MustCompile("missing-hook"), func(context *hookstate.Context) hookstate.Handler {
   792  		return s.mockHandler
   793  	})
   794  
   795  	hooksup := &hookstate.HookSetup{
   796  		Snap:     "test-snap",
   797  		Hook:     "missing-hook",
   798  		Optional: true,
   799  	}
   800  	s.state.Lock()
   801  	s.task.Set("hook-setup", hooksup)
   802  	s.state.Unlock()
   803  
   804  	s.se.Ensure()
   805  	s.se.Wait()
   806  
   807  	c.Check(s.mockHandler.BeforeCalled, Equals, false)
   808  	c.Check(s.mockHandler.DoneCalled, Equals, false)
   809  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   810  
   811  	c.Check(s.command.Calls(), IsNil)
   812  
   813  	s.state.Lock()
   814  	defer s.state.Unlock()
   815  
   816  	c.Check(s.task.Kind(), Equals, "run-hook")
   817  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   818  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   819  
   820  	c.Logf("Task log:\n%s\n", s.task.Log())
   821  }
   822  
   823  func (s *hookManagerSuite) TestHookWithoutHookAlways(c *C) {
   824  	s.manager.Register(regexp.MustCompile("missing-hook"), func(context *hookstate.Context) hookstate.Handler {
   825  		return s.mockHandler
   826  	})
   827  
   828  	hooksup := &hookstate.HookSetup{
   829  		Snap:     "test-snap",
   830  		Hook:     "missing-hook",
   831  		Optional: true,
   832  		Always:   true,
   833  	}
   834  	s.state.Lock()
   835  	s.task.Set("hook-setup", hooksup)
   836  	s.state.Unlock()
   837  
   838  	s.se.Ensure()
   839  	s.se.Wait()
   840  
   841  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
   842  	c.Check(s.mockHandler.DoneCalled, Equals, true)
   843  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
   844  
   845  	c.Check(s.command.Calls(), IsNil)
   846  
   847  	s.state.Lock()
   848  	defer s.state.Unlock()
   849  
   850  	c.Check(s.task.Kind(), Equals, "run-hook")
   851  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   852  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   853  
   854  	c.Logf("Task log:\n%s\n", s.task.Log())
   855  }
   856  
   857  func (s *hookManagerSuite) TestOptionalHookWithMissingHandler(c *C) {
   858  	hooksup := &hookstate.HookSetup{
   859  		Snap:     "test-snap",
   860  		Hook:     "missing-hook-and-no-handler",
   861  		Optional: true,
   862  	}
   863  	s.state.Lock()
   864  	s.task.Set("hook-setup", hooksup)
   865  	s.state.Unlock()
   866  
   867  	s.se.Ensure()
   868  	s.se.Wait()
   869  
   870  	c.Check(s.command.Calls(), IsNil)
   871  
   872  	s.state.Lock()
   873  	defer s.state.Unlock()
   874  
   875  	c.Check(s.task.Kind(), Equals, "run-hook")
   876  	c.Check(s.task.Status(), Equals, state.DoneStatus)
   877  	c.Check(s.change.Status(), Equals, state.DoneStatus)
   878  
   879  	c.Logf("Task log:\n%s\n", s.task.Log())
   880  }
   881  
   882  func checkTaskLogContains(c *C, task *state.Task, pattern string) {
   883  	exp := regexp.MustCompile(pattern)
   884  	found := false
   885  	for _, message := range task.Log() {
   886  		if exp.MatchString(message) {
   887  			found = true
   888  		}
   889  	}
   890  
   891  	c.Check(found, Equals, true, Commentf("Expected to find regex %q in task log: %v", pattern, task.Log()))
   892  }
   893  
   894  func (s *hookManagerSuite) TestHookTaskRunsRightSnapCmd(c *C) {
   895  	coreSnapCmdPath := filepath.Join(dirs.SnapMountDir, "core/12/usr/bin/snap")
   896  	cmd := testutil.MockCommand(c, coreSnapCmdPath, "")
   897  	defer cmd.Restore()
   898  
   899  	r := hookstate.MockReadlink(func(p string) (string, error) {
   900  		c.Assert(p, Equals, "/proc/self/exe")
   901  		return filepath.Join(dirs.SnapMountDir, "core/12/usr/lib/snapd/snapd"), nil
   902  	})
   903  	defer r()
   904  
   905  	s.se.Ensure()
   906  	s.se.Wait()
   907  
   908  	s.state.Lock()
   909  	defer s.state.Unlock()
   910  
   911  	c.Assert(s.context, NotNil, Commentf("Expected handler generator to be called with a valid context"))
   912  	c.Check(cmd.Calls(), DeepEquals, [][]string{{
   913  		"snap", "run", "--hook", "configure", "-r", "1", "test-snap",
   914  	}})
   915  
   916  }
   917  
   918  func (s *hookManagerSuite) TestHookTaskHandlerReportsErrorIfRequested(c *C) {
   919  	s.state.Lock()
   920  	var hooksup hookstate.HookSetup
   921  	s.task.Get("hook-setup", &hooksup)
   922  	hooksup.TrackError = true
   923  	s.task.Set("hook-setup", &hooksup)
   924  	s.state.Unlock()
   925  
   926  	errtrackerCalled := false
   927  	hookstate.MockErrtrackerReport(func(snap, errmsg, dupSig string, extra map[string]string) (string, error) {
   928  		c.Check(snap, Equals, "test-snap")
   929  		c.Check(errmsg, Equals, "hook configure in snap \"test-snap\" failed: hook failed at user request")
   930  		c.Check(dupSig, Equals, "hook:test-snap:configure:exit status 1\nhook failed at user request\n")
   931  
   932  		errtrackerCalled = true
   933  		return "some-oopsid", nil
   934  	})
   935  
   936  	// Force the snap command to exit 1, and print something to stderr
   937  	cmd := testutil.MockCommand(
   938  		c, "snap", ">&2 echo 'hook failed at user request'; exit 1")
   939  	defer cmd.Restore()
   940  
   941  	s.se.Ensure()
   942  	s.se.Wait()
   943  
   944  	s.state.Lock()
   945  	defer s.state.Unlock()
   946  
   947  	c.Check(errtrackerCalled, Equals, true)
   948  }
   949  
   950  func (s *hookManagerSuite) TestHookTaskHandlerReportsErrorDisabled(c *C) {
   951  	s.state.Lock()
   952  	var hooksup hookstate.HookSetup
   953  	s.task.Get("hook-setup", &hooksup)
   954  	hooksup.TrackError = true
   955  	s.task.Set("hook-setup", &hooksup)
   956  
   957  	tr := config.NewTransaction(s.state)
   958  	tr.Set("core", "problem-reports.disabled", true)
   959  	tr.Commit()
   960  	s.state.Unlock()
   961  
   962  	hookstate.MockErrtrackerReport(func(snap, errmsg, dupSig string, extra map[string]string) (string, error) {
   963  		c.Fatalf("no error reports should be generated")
   964  		return "", nil
   965  	})
   966  
   967  	// Force the snap command to exit 1, and print something to stderr
   968  	cmd := testutil.MockCommand(
   969  		c, "snap", ">&2 echo 'hook failed at user request'; exit 1")
   970  	defer cmd.Restore()
   971  
   972  	s.se.Ensure()
   973  	s.se.Wait()
   974  
   975  	s.state.Lock()
   976  	defer s.state.Unlock()
   977  }
   978  
   979  func (s *hookManagerSuite) TestHookTasksForSameSnapAreSerialized(c *C) {
   980  	var Executing int32
   981  	var TotalExecutions int32
   982  
   983  	s.mockHandler.BeforeCallback = func() {
   984  		executing := atomic.AddInt32(&Executing, 1)
   985  		if executing != 1 {
   986  			panic(fmt.Sprintf("More than one handler executed: %d", executing))
   987  		}
   988  	}
   989  
   990  	s.mockHandler.DoneCallback = func() {
   991  		executing := atomic.AddInt32(&Executing, -1)
   992  		if executing != 0 {
   993  			panic(fmt.Sprintf("More than one handler executed: %d", executing))
   994  		}
   995  		atomic.AddInt32(&TotalExecutions, 1)
   996  	}
   997  
   998  	hooksup := &hookstate.HookSetup{
   999  		Snap:     "test-snap",
  1000  		Hook:     "configure",
  1001  		Revision: snap.R(1),
  1002  	}
  1003  
  1004  	s.state.Lock()
  1005  
  1006  	var tasks []*state.Task
  1007  	for i := 0; i < 20; i++ {
  1008  		task := hookstate.HookTask(s.state, "test summary", hooksup, nil)
  1009  		c.Assert(s.task, NotNil)
  1010  		change := s.state.NewChange("kind", "summary")
  1011  		change.AddTask(task)
  1012  		tasks = append(tasks, task)
  1013  	}
  1014  	s.state.Unlock()
  1015  
  1016  	s.settle(c)
  1017  
  1018  	s.state.Lock()
  1019  	defer s.state.Unlock()
  1020  
  1021  	c.Check(s.task.Kind(), Equals, "run-hook")
  1022  	c.Check(s.task.Status(), Equals, state.DoneStatus)
  1023  	c.Check(s.change.Status(), Equals, state.DoneStatus)
  1024  
  1025  	for i := 0; i < len(tasks); i++ {
  1026  		c.Check(tasks[i].Kind(), Equals, "run-hook")
  1027  		c.Check(tasks[i].Status(), Equals, state.DoneStatus)
  1028  	}
  1029  	c.Assert(atomic.LoadInt32(&TotalExecutions), Equals, int32(1+len(tasks)))
  1030  	c.Assert(atomic.LoadInt32(&Executing), Equals, int32(0))
  1031  }
  1032  
  1033  type MockConcurrentHandler struct {
  1034  	onDone func()
  1035  }
  1036  
  1037  func (h *MockConcurrentHandler) Before() error {
  1038  	return nil
  1039  }
  1040  
  1041  func (h *MockConcurrentHandler) Done() error {
  1042  	h.onDone()
  1043  	return nil
  1044  }
  1045  
  1046  func (h *MockConcurrentHandler) Error(err error) (bool, error) {
  1047  	return false, nil
  1048  }
  1049  
  1050  func NewMockConcurrentHandler(onDone func()) *MockConcurrentHandler {
  1051  	return &MockConcurrentHandler{onDone: onDone}
  1052  }
  1053  
  1054  func (s *hookManagerSuite) TestHookTasksForDifferentSnapsRunConcurrently(c *C) {
  1055  	hooksup1 := &hookstate.HookSetup{
  1056  		Snap:     "test-snap-1",
  1057  		Hook:     "prepare-device",
  1058  		Revision: snap.R(1),
  1059  	}
  1060  	hooksup2 := &hookstate.HookSetup{
  1061  		Snap:     "test-snap-2",
  1062  		Hook:     "prepare-device",
  1063  		Revision: snap.R(1),
  1064  	}
  1065  
  1066  	s.state.Lock()
  1067  
  1068  	sideInfo := &snap.SideInfo{RealName: "test-snap-1", SnapID: "some-snap-id1", Revision: snap.R(1)}
  1069  	info := snaptest.MockSnap(c, snapYaml1, sideInfo)
  1070  	c.Assert(info.Hooks, HasLen, 1)
  1071  	snapstate.Set(s.state, "test-snap-1", &snapstate.SnapState{
  1072  		Active:   true,
  1073  		Sequence: []*snap.SideInfo{sideInfo},
  1074  		Current:  snap.R(1),
  1075  	})
  1076  
  1077  	sideInfo = &snap.SideInfo{RealName: "test-snap-2", SnapID: "some-snap-id2", Revision: snap.R(1)}
  1078  	snaptest.MockSnap(c, snapYaml2, sideInfo)
  1079  	snapstate.Set(s.state, "test-snap-2", &snapstate.SnapState{
  1080  		Active:   true,
  1081  		Sequence: []*snap.SideInfo{sideInfo},
  1082  		Current:  snap.R(1),
  1083  	})
  1084  
  1085  	var testSnap1HookCalls, testSnap2HookCalls int
  1086  	ch := make(chan struct{})
  1087  	mockHandler1 := NewMockConcurrentHandler(func() {
  1088  		ch <- struct{}{}
  1089  		testSnap1HookCalls++
  1090  	})
  1091  	mockHandler2 := NewMockConcurrentHandler(func() {
  1092  		<-ch
  1093  		testSnap2HookCalls++
  1094  	})
  1095  	s.manager.Register(regexp.MustCompile("prepare-device"), func(context *hookstate.Context) hookstate.Handler {
  1096  		if context.InstanceName() == "test-snap-1" {
  1097  			return mockHandler1
  1098  		}
  1099  		if context.InstanceName() == "test-snap-2" {
  1100  			return mockHandler2
  1101  		}
  1102  		c.Fatalf("unknown snap: %s", context.InstanceName())
  1103  		return nil
  1104  	})
  1105  
  1106  	task1 := hookstate.HookTask(s.state, "test summary", hooksup1, nil)
  1107  	c.Assert(task1, NotNil)
  1108  	change1 := s.state.NewChange("kind", "summary")
  1109  	change1.AddTask(task1)
  1110  
  1111  	task2 := hookstate.HookTask(s.state, "test summary", hooksup2, nil)
  1112  	c.Assert(task2, NotNil)
  1113  	change2 := s.state.NewChange("kind", "summary")
  1114  	change2.AddTask(task2)
  1115  
  1116  	s.state.Unlock()
  1117  
  1118  	s.settle(c)
  1119  
  1120  	s.state.Lock()
  1121  	defer s.state.Unlock()
  1122  
  1123  	c.Check(task1.Status(), Equals, state.DoneStatus)
  1124  	c.Check(change1.Status(), Equals, state.DoneStatus)
  1125  	c.Check(task2.Status(), Equals, state.DoneStatus)
  1126  	c.Check(change2.Status(), Equals, state.DoneStatus)
  1127  	c.Assert(testSnap1HookCalls, Equals, 1)
  1128  	c.Assert(testSnap2HookCalls, Equals, 1)
  1129  }
  1130  
  1131  func (s *hookManagerSuite) TestCompatForConfigureSnapd(c *C) {
  1132  	st := s.state
  1133  
  1134  	st.Lock()
  1135  	defer st.Unlock()
  1136  
  1137  	task := st.NewTask("configure-snapd", "Snapd between 2.29 and 2.30 in edge insertd those tasks")
  1138  	chg := st.NewChange("configure", "configure snapd")
  1139  	chg.AddTask(task)
  1140  
  1141  	st.Unlock()
  1142  	s.se.Ensure()
  1143  	s.se.Wait()
  1144  	st.Lock()
  1145  
  1146  	c.Check(chg.Status(), Equals, state.DoneStatus)
  1147  	c.Check(task.Status(), Equals, state.DoneStatus)
  1148  }
  1149  
  1150  func (s *hookManagerSuite) TestGracefullyWaitRunningHooksTimeout(c *C) {
  1151  	restore := hookstate.MockDefaultHookTimeout(100 * time.Millisecond)
  1152  	defer restore()
  1153  
  1154  	// this works even if test-snap is not present
  1155  	s.state.Lock()
  1156  	snapstate.Set(s.state, "test-snap", nil)
  1157  	s.state.Unlock()
  1158  
  1159  	quit := make(chan struct{})
  1160  	defer func() {
  1161  		quit <- struct{}{}
  1162  	}()
  1163  	didRun := make(chan bool)
  1164  	s.mockHandler.BeforeCallback = func() {
  1165  		c.Check(s.manager.NumRunningHooks(), Equals, 1)
  1166  		go func() {
  1167  			didRun <- s.manager.GracefullyWaitRunningHooks()
  1168  		}()
  1169  	}
  1170  
  1171  	s.manager.RegisterHijack("configure", "test-snap", func(ctx *hookstate.Context) error {
  1172  		<-quit
  1173  		return nil
  1174  	})
  1175  
  1176  	s.se.Ensure()
  1177  	select {
  1178  	case noPending := <-didRun:
  1179  		c.Check(noPending, Equals, false)
  1180  	case <-time.After(2 * time.Second):
  1181  		c.Fatal("timeout should have expired")
  1182  	}
  1183  }
  1184  
  1185  func (s *hookManagerSuite) TestSnapstateOpConflict(c *C) {
  1186  	s.state.Lock()
  1187  	defer s.state.Unlock()
  1188  	_, err := snapstate.Disable(s.state, "test-snap")
  1189  	c.Assert(err, ErrorMatches, `snap "test-snap" has "kind" change in progress`)
  1190  }
  1191  
  1192  func (s *hookManagerSuite) TestHookHijackingNoConflict(c *C) {
  1193  	s.state.Lock()
  1194  	defer s.state.Unlock()
  1195  
  1196  	s.manager.RegisterHijack("configure", "test-snap", func(ctx *hookstate.Context) error {
  1197  		return nil
  1198  	})
  1199  
  1200  	// no conflict on hijacked hooks
  1201  	_, err := snapstate.Disable(s.state, "test-snap")
  1202  	c.Assert(err, IsNil)
  1203  }
  1204  
  1205  func (s *hookManagerSuite) TestEphemeralRunHook(c *C) {
  1206  	contextData := map[string]interface{}{
  1207  		"key":  "value",
  1208  		"key2": "value2",
  1209  	}
  1210  	s.testEphemeralRunHook(c, contextData)
  1211  }
  1212  
  1213  func (s *hookManagerSuite) TestEphemeralRunHookNoContextData(c *C) {
  1214  	var contextData map[string]interface{} = nil
  1215  	s.testEphemeralRunHook(c, contextData)
  1216  }
  1217  
  1218  func (s *hookManagerSuite) testEphemeralRunHook(c *C, contextData map[string]interface{}) {
  1219  	var hookInvokeCalled []string
  1220  	hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
  1221  		c.Check(ctx.HookName(), Equals, "configure")
  1222  		hookInvokeCalled = append(hookInvokeCalled, ctx.HookName())
  1223  
  1224  		// check that context data was set correctly
  1225  		var s string
  1226  		ctx.Lock()
  1227  		defer ctx.Unlock()
  1228  		for k, v := range contextData {
  1229  			ctx.Get(k, &s)
  1230  			c.Check(s, Equals, v)
  1231  		}
  1232  		ctx.Set("key-set-from-hook", "value-set-from-hook")
  1233  
  1234  		return []byte("some output"), nil
  1235  	}
  1236  	restore := hookstate.MockRunHook(hookInvoke)
  1237  	defer restore()
  1238  
  1239  	hooksup := &hookstate.HookSetup{
  1240  		Snap:     "test-snap",
  1241  		Revision: snap.R(1),
  1242  		Hook:     "configure",
  1243  	}
  1244  	context, err := s.manager.EphemeralRunHook(context.Background(), hooksup, contextData)
  1245  	c.Assert(err, IsNil)
  1246  	c.Check(hookInvokeCalled, DeepEquals, []string{"configure"})
  1247  
  1248  	var value string
  1249  	context.Lock()
  1250  	context.Get("key-set-from-hook", &value)
  1251  	context.Unlock()
  1252  	c.Check(value, Equals, "value-set-from-hook")
  1253  }
  1254  
  1255  func (s *hookManagerSuite) TestEphemeralRunHookNoSnap(c *C) {
  1256  	hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
  1257  		c.Fatalf("hook should not be invoked in this test")
  1258  		return nil, nil
  1259  	}
  1260  	restore := hookstate.MockRunHook(hookInvoke)
  1261  	defer restore()
  1262  
  1263  	hooksup := &hookstate.HookSetup{
  1264  		Snap:     "not-installed-snap",
  1265  		Revision: snap.R(1),
  1266  		Hook:     "configure",
  1267  	}
  1268  	contextData := map[string]interface{}{
  1269  		"key": "value",
  1270  	}
  1271  	_, err := s.manager.EphemeralRunHook(context.Background(), hooksup, contextData)
  1272  	c.Assert(err, ErrorMatches, `cannot run ephemeral hook "configure" for snap "not-installed-snap": no state entry for key`)
  1273  }
  1274  
  1275  func (s *hookManagerSuite) TestEphemeralRunHookContextCanCancel(c *C) {
  1276  	tombDying := 0
  1277  	hookRunning := make(chan struct{})
  1278  
  1279  	hookInvoke := func(_ *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
  1280  		close(hookRunning)
  1281  
  1282  		select {
  1283  		case <-tomb.Dying():
  1284  			tombDying++
  1285  		case <-time.After(10 * time.Second):
  1286  			c.Fatalf("hook not canceled after 10s")
  1287  		}
  1288  		return nil, nil
  1289  	}
  1290  	restore := hookstate.MockRunHook(hookInvoke)
  1291  	defer restore()
  1292  
  1293  	hooksup := &hookstate.HookSetup{
  1294  		Snap:     "test-snap",
  1295  		Revision: snap.R(1),
  1296  		Hook:     "configure",
  1297  	}
  1298  
  1299  	ctx, cancelFunc := context.WithCancel(context.Background())
  1300  	go func() {
  1301  		<-hookRunning
  1302  		cancelFunc()
  1303  	}()
  1304  	_, err := s.manager.EphemeralRunHook(ctx, hooksup, nil)
  1305  	c.Assert(err, IsNil)
  1306  	c.Check(tombDying, Equals, 1)
  1307  }
  1308  
  1309  type parallelInstancesHookManagerSuite struct {
  1310  	baseHookManagerSuite
  1311  }
  1312  
  1313  var _ = Suite(&parallelInstancesHookManagerSuite{})
  1314  
  1315  func (s *parallelInstancesHookManagerSuite) SetUpTest(c *C) {
  1316  	s.commonSetUpTest(c)
  1317  	s.setUpSnap(c, "test-snap_instance", snapYaml)
  1318  }
  1319  
  1320  func (s *parallelInstancesHookManagerSuite) TearDownTest(c *C) {
  1321  	s.commonTearDownTest(c)
  1322  }
  1323  
  1324  func (s *parallelInstancesHookManagerSuite) TestHookTaskEnsureHookRan(c *C) {
  1325  	didRun := make(chan bool)
  1326  	s.mockHandler.BeforeCallback = func() {
  1327  		c.Check(s.manager.NumRunningHooks(), Equals, 1)
  1328  		go func() {
  1329  			didRun <- s.manager.GracefullyWaitRunningHooks()
  1330  		}()
  1331  	}
  1332  	s.se.Ensure()
  1333  	select {
  1334  	case ok := <-didRun:
  1335  		c.Check(ok, Equals, true)
  1336  	case <-time.After(5 * time.Second):
  1337  		c.Fatal("hook run should have been done by now")
  1338  	}
  1339  	s.se.Wait()
  1340  
  1341  	s.state.Lock()
  1342  	defer s.state.Unlock()
  1343  
  1344  	c.Check(s.context.InstanceName(), Equals, "test-snap_instance")
  1345  	c.Check(s.context.SnapRevision(), Equals, snap.R(1))
  1346  	c.Check(s.context.HookName(), Equals, "configure")
  1347  
  1348  	c.Check(s.command.Calls(), DeepEquals, [][]string{{
  1349  		"snap", "run", "--hook", "configure", "-r", "1", "test-snap_instance",
  1350  	}})
  1351  
  1352  	c.Check(s.mockHandler.BeforeCalled, Equals, true)
  1353  	c.Check(s.mockHandler.DoneCalled, Equals, true)
  1354  	c.Check(s.mockHandler.ErrorCalled, Equals, false)
  1355  
  1356  	c.Check(s.task.Kind(), Equals, "run-hook")
  1357  	c.Check(s.task.Status(), Equals, state.DoneStatus)
  1358  	c.Check(s.change.Status(), Equals, state.DoneStatus)
  1359  
  1360  	c.Check(s.manager.NumRunningHooks(), Equals, 0)
  1361  }