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(¶llelInstancesHookManagerSuite{}) 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 }