gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/servicestate/servicemgr_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 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 servicestate_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "time" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/asserts" 32 "github.com/snapcore/snapd/asserts/assertstest" 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/logger" 35 "github.com/snapcore/snapd/overlord" 36 "github.com/snapcore/snapd/overlord/configstate/config" 37 "github.com/snapcore/snapd/overlord/servicestate" 38 "github.com/snapcore/snapd/overlord/snapstate" 39 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 40 "github.com/snapcore/snapd/overlord/state" 41 "github.com/snapcore/snapd/snap" 42 "github.com/snapcore/snapd/snap/snaptest" 43 "github.com/snapcore/snapd/systemd" 44 "github.com/snapcore/snapd/testutil" 45 "github.com/snapcore/snapd/wrappers" 46 ) 47 48 type baseServiceMgrTestSuite struct { 49 testutil.BaseTest 50 51 mgr *servicestate.ServiceManager 52 53 o *overlord.Overlord 54 se *overlord.StateEngine 55 state *state.State 56 57 restartRequests []state.RestartType 58 restartObserve func() 59 60 uc18Model *asserts.Model 61 uc16Model *asserts.Model 62 63 testSnapState *snapstate.SnapState 64 testSnapSideInfo *snap.SideInfo 65 } 66 67 func (s *baseServiceMgrTestSuite) SetUpTest(c *C) { 68 s.BaseTest.SetUpTest(c) 69 70 dirs.SetRootDir(c.MkDir()) 71 s.AddCleanup(func() { dirs.SetRootDir("") }) 72 73 s.restartRequests = nil 74 75 s.restartObserve = nil 76 s.o = overlord.MockWithStateAndRestartHandler(nil, func(req state.RestartType) { 77 s.restartRequests = append(s.restartRequests, req) 78 if s.restartObserve != nil { 79 s.restartObserve() 80 } 81 }) 82 83 s.state = s.o.State() 84 s.state.Lock() 85 s.state.VerifyReboot("boot-id-0") 86 s.state.Unlock() 87 s.se = s.o.StateEngine() 88 89 s.mgr = servicestate.Manager(s.state, s.o.TaskRunner()) 90 s.o.AddManager(s.mgr) 91 s.o.AddManager(s.o.TaskRunner()) 92 93 err := s.o.StartUp() 94 c.Assert(err, IsNil) 95 96 // by default we are seeded 97 s.state.Lock() 98 s.state.Set("seeded", true) 99 s.state.Unlock() 100 101 s.uc18Model = assertstest.FakeAssertion(map[string]interface{}{ 102 "type": "model", 103 "authority-id": "canonical", 104 "series": "16", 105 "brand-id": "canonical", 106 "model": "pc", 107 "gadget": "pc", 108 "kernel": "kernel", 109 "architecture": "amd64", 110 "base": "core18", 111 }).(*asserts.Model) 112 113 s.uc16Model = assertstest.FakeAssertion(map[string]interface{}{ 114 "type": "model", 115 "authority-id": "canonical", 116 "series": "16", 117 "brand-id": "canonical", 118 "model": "pc", 119 "gadget": "pc", 120 "kernel": "kernel", 121 "architecture": "amd64", 122 // no base 123 }).(*asserts.Model) 124 125 // by default mock that we are uc18 126 s.AddCleanup(snapstatetest.MockDeviceModel(s.uc18Model)) 127 128 // setup a test-snap with a service that can be easily injected into 129 // snapstate to be setup as needed 130 s.testSnapSideInfo = &snap.SideInfo{RealName: "test-snap", Revision: snap.R(42)} 131 s.testSnapState = &snapstate.SnapState{ 132 Sequence: []*snap.SideInfo{s.testSnapSideInfo}, 133 Current: snap.R(42), 134 Active: true, 135 SnapType: "app", 136 } 137 } 138 139 type expectedSystemctl struct { 140 expArgs []string 141 output string 142 err error 143 } 144 145 type ensureSnapServiceSuite struct { 146 baseServiceMgrTestSuite 147 } 148 149 var ( 150 unitTempl = `[Unit] 151 # Auto-generated, DO NOT EDIT 152 Description=Service for snap application test-snap.svc1 153 Requires=%[1]s 154 Wants=network.target 155 After=%[1]s network.target snapd.apparmor.service 156 %[3]sX-Snappy=yes 157 158 [Service] 159 EnvironmentFile=-/etc/environment 160 ExecStart=/usr/bin/snap run test-snap.svc1 161 SyslogIdentifier=test-snap.svc1 162 Restart=on-failure 163 WorkingDirectory=%[2]s/var/snap/test-snap/42 164 TimeoutStopSec=30 165 Type=simple 166 %[4]s 167 [Install] 168 WantedBy=multi-user.target 169 ` 170 171 testYaml = `name: test-snap 172 version: v1 173 apps: 174 svc1: 175 command: bin.sh 176 daemon: simple 177 ` 178 testYaml2 = `name: test-snap2 179 version: v1 180 apps: 181 svc1: 182 command: bin.sh 183 daemon: simple 184 ` 185 186 systemdTimeFormat = "Mon 2006-01-02 15:04:05 MST" 187 ) 188 189 type unitOptions struct { 190 usrLibSnapdOrderVerb string 191 snapName string 192 snapRev string 193 oomScore string 194 } 195 196 func mkUnitFile(c *C, opts *unitOptions) string { 197 if opts == nil { 198 opts = &unitOptions{} 199 } 200 usrLibSnapdSnippet := "" 201 if opts.usrLibSnapdOrderVerb != "" { 202 usrLibSnapdSnippet = fmt.Sprintf(`%[1]s=usr-lib-snapd.mount 203 After=usr-lib-snapd.mount 204 `, 205 opts.usrLibSnapdOrderVerb) 206 } 207 oomScoreAdjust := "" 208 if opts.oomScore != "" { 209 oomScoreAdjust = fmt.Sprintf(`OOMScoreAdjust=%s 210 `, 211 opts.oomScore, 212 ) 213 } 214 215 return fmt.Sprintf(unitTempl, 216 systemd.EscapeUnitNamePath(filepath.Join(dirs.SnapMountDir, opts.snapName, opts.snapRev+".mount")), 217 dirs.GlobalRootDir, 218 usrLibSnapdSnippet, 219 oomScoreAdjust, 220 ) 221 } 222 223 var _ = Suite(&ensureSnapServiceSuite{}) 224 225 func (s *baseServiceMgrTestSuite) mockSystemctlCalls(c *C, expCalls []expectedSystemctl) (restore func()) { 226 allSystemctlCalls := [][]string{} 227 r := systemd.MockSystemctl(func(args ...string) ([]byte, error) { 228 systemctlCalls := len(allSystemctlCalls) 229 allSystemctlCalls = append(allSystemctlCalls, args) 230 if systemctlCalls < len(expCalls) { 231 res := expCalls[systemctlCalls] 232 c.Check(args, DeepEquals, res.expArgs) 233 return []byte(res.output), res.err 234 } 235 c.Errorf("unexpected and unhandled systemctl command: %+v", args) 236 return nil, fmt.Errorf("broken test") 237 }) 238 239 return func() { 240 r() 241 // double-check at the end of the test that we got as many systemctl calls 242 // as were mocked and that we didn't get less, then re-set it for the next 243 // test 244 expArgCalls := make([][]string, 0, len(expCalls)) 245 for _, call := range expCalls { 246 expArgCalls = append(expArgCalls, call.expArgs) 247 } 248 c.Assert(allSystemctlCalls, DeepEquals, expArgCalls) 249 } 250 } 251 252 func (s *ensureSnapServiceSuite) SetUpTest(c *C) { 253 s.baseServiceMgrTestSuite.SetUpTest(c) 254 } 255 256 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesNoSnapsDoesNothing(c *C) { 257 // don't mock any snaps in snapstate 258 err := s.mgr.Ensure() 259 c.Assert(err, IsNil) 260 261 // we didn't write any services 262 c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileAbsent) 263 264 // we did not request a restart 265 c.Assert(s.restartRequests, HasLen, 0) 266 } 267 268 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesNotSeeded(c *C) { 269 s.state.Lock() 270 // we are not seeded but we do have a service which needs to be generated 271 s.state.Set("seeded", false) 272 snapstate.Set(s.state, "test-snap", s.testSnapState) 273 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 274 s.state.Unlock() 275 276 err := s.mgr.Ensure() 277 c.Assert(err, IsNil) 278 279 // we didn't write any services 280 c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileAbsent) 281 282 // we did not request a restart 283 c.Assert(s.restartRequests, HasLen, 0) 284 } 285 286 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSimpleWritesServicesFilesUC16(c *C) { 287 s.state.Lock() 288 // there is a snap in snap state that needs a service generated for it 289 snapstate.Set(s.state, "test-snap", s.testSnapState) 290 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 291 // mock the device context as uc16 292 s.AddCleanup(snapstatetest.MockDeviceModel(s.uc16Model)) 293 294 s.state.Unlock() 295 296 // don't add a usr-lib-snapd.mount unit since we won't read it, since we are 297 // on uc16 298 299 // we will only trigger a daemon-reload once after generating the service 300 // file 301 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 302 { 303 expArgs: []string{"daemon-reload"}, 304 }, 305 }) 306 defer r() 307 308 err := s.mgr.Ensure() 309 c.Assert(err, IsNil) 310 311 // we wrote a service unit file 312 content := mkUnitFile(c, &unitOptions{ 313 snapName: "test-snap", 314 snapRev: "42", 315 }) 316 c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileEquals, content) 317 318 // we did not request a restart 319 c.Assert(s.restartRequests, HasLen, 0) 320 } 321 322 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSkipsSnapdSnap(c *C) { 323 s.state.Lock() 324 // add an unexpected snapd snap which has services in it, but we 325 // specifically skip the snapd snap when considering services to add since 326 // it is special 327 sideInfo := &snap.SideInfo{RealName: "snapd", Revision: snap.R(42)} 328 snapstate.Set(s.state, "snapd", &snapstate.SnapState{ 329 Sequence: []*snap.SideInfo{sideInfo}, 330 Current: snap.R(42), 331 Active: true, 332 SnapType: string(snap.TypeSnapd), 333 }) 334 snaptest.MockSnapCurrent(c, `name: snapd 335 type: snapd 336 version: v1 337 apps: 338 svc1: 339 command: bin.sh 340 daemon: simple 341 `, sideInfo) 342 343 s.state.Unlock() 344 345 // don't need to mock usr-lib-snapd.mount since we will skip before that 346 // with snapd as the only snap 347 348 err := s.mgr.Ensure() 349 c.Assert(err, IsNil) 350 351 // we didn't write a snap service file for snapd 352 c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.snapd.svc1.service"), testutil.FileAbsent) 353 354 // we did not request a restart 355 c.Assert(s.restartRequests, HasLen, 0) 356 } 357 358 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesUC18(c *C) { 359 s.state.Lock() 360 // there is a snap in snap state that needs a service generated for it 361 snapstate.Set(s.state, "test-snap", s.testSnapState) 362 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 363 364 s.state.Unlock() 365 366 // add the usr-lib-snapd.mount unit 367 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 368 c.Assert(err, IsNil) 369 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 370 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 371 c.Assert(err, IsNil) 372 373 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 374 { 375 expArgs: []string{"daemon-reload"}, 376 }, 377 }) 378 defer r() 379 380 err = s.mgr.Ensure() 381 c.Assert(err, IsNil) 382 383 // we wrote the service unit file 384 content := mkUnitFile(c, &unitOptions{ 385 usrLibSnapdOrderVerb: "Wants", 386 snapName: "test-snap", 387 snapRev: "42", 388 }) 389 c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileEquals, content) 390 391 // we did not request a restart 392 c.Assert(s.restartRequests, HasLen, 0) 393 } 394 395 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesVitalityRankUC18(c *C) { 396 s.state.Lock() 397 // there is a snap in snap state that needs a service generated for it 398 snapstate.Set(s.state, "test-snap", s.testSnapState) 399 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 400 401 // also set vitality-hint for this snap 402 t := config.NewTransaction(s.state) 403 err := t.Set("core", "resilience.vitality-hint", "bar,test-snap") 404 c.Assert(err, IsNil) 405 t.Commit() 406 407 s.state.Unlock() 408 409 // add the usr-lib-snapd.mount unit 410 err = os.MkdirAll(dirs.SnapServicesDir, 0755) 411 c.Assert(err, IsNil) 412 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 413 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 414 c.Assert(err, IsNil) 415 416 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 417 { 418 expArgs: []string{"daemon-reload"}, 419 }, 420 }) 421 defer r() 422 423 err = s.mgr.Ensure() 424 c.Assert(err, IsNil) 425 426 // we wrote the service unit file 427 content := mkUnitFile(c, &unitOptions{ 428 usrLibSnapdOrderVerb: "Wants", 429 snapName: "test-snap", 430 snapRev: "42", 431 oomScore: "-898", 432 }) 433 c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileEquals, content) 434 435 // we did not request a restart 436 c.Assert(s.restartRequests, HasLen, 0) 437 } 438 439 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndDoesNotRestartIfBootTimeAfterModTime(c *C) { 440 s.state.Lock() 441 // there is a snap in snap state that needs a service generated for it 442 snapstate.Set(s.state, "test-snap", s.testSnapState) 443 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 444 445 s.state.Unlock() 446 447 // add the usr-lib-snapd.mount unit 448 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 449 c.Assert(err, IsNil) 450 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 451 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 452 c.Assert(err, IsNil) 453 454 now := time.Now() 455 err = os.Chtimes(usrLibSnapdMountFile, now, now) 456 c.Assert(err, IsNil) 457 458 logbuf, r := logger.MockLogger() 459 defer r() 460 461 // TZ's are important for the boot time specifically, we need to output the 462 // UTC time from the uptime script below, otherwise using local time here 463 // but not elsewhere leads to errors 464 future := now.Add(30 * time.Minute).UTC() 465 466 // we won't try to start services if the current boot time is ahead of the 467 // modification time 468 469 // mock the uptime command 470 cmd := testutil.MockCommand(c, "uptime", fmt.Sprintf(` 471 #!/bin/sh 472 473 if [ "$TZ" != "UTC" ]; then 474 echo "unexpected TZ env value: $TZ (expected UTC)" 475 exit 1 476 fi 477 478 if [ "$*" != "-s" ]; then 479 echo "arguments $* were unexpected" 480 exit 1 481 fi 482 483 echo %[1]q 484 `, future.Format("2006-01-02 15:04:05"))) 485 defer cmd.Restore() 486 487 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 488 489 // add the initial state of the service file using Requires 490 requiresContent := mkUnitFile(c, &unitOptions{ 491 usrLibSnapdOrderVerb: "Requires", 492 snapName: "test-snap", 493 snapRev: "42", 494 }) 495 err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644) 496 c.Assert(err, IsNil) 497 498 r = s.mockSystemctlCalls(c, []expectedSystemctl{ 499 { 500 expArgs: []string{"daemon-reload"}, 501 }, 502 }) 503 defer r() 504 505 err = s.mgr.Ensure() 506 c.Assert(err, IsNil) 507 508 // we wrote the service unit file 509 content := mkUnitFile(c, &unitOptions{ 510 usrLibSnapdOrderVerb: "Wants", 511 snapName: "test-snap", 512 snapRev: "42", 513 }) 514 c.Assert(svcFile, testutil.FileEquals, content) 515 516 c.Assert(cmd.Calls(), DeepEquals, [][]string{ 517 {"uptime", "-s"}, 518 }) 519 520 c.Assert(logbuf.String(), Equals, "") 521 522 // we did not request a restart 523 c.Assert(s.restartRequests, HasLen, 0) 524 } 525 526 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndIgnoresBootTimeErrors(c *C) { 527 s.state.Lock() 528 // there is a snap in snap state that needs a service generated for it 529 snapstate.Set(s.state, "test-snap", s.testSnapState) 530 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 531 532 s.state.Unlock() 533 534 // add the usr-lib-snapd.mount unit 535 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 536 c.Assert(err, IsNil) 537 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 538 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 539 c.Assert(err, IsNil) 540 541 now := time.Now() 542 err = os.Chtimes(usrLibSnapdMountFile, now, now) 543 c.Assert(err, IsNil) 544 545 // if the boot time can't be determined, we log a message and continue on 546 // considering whether or not the service should be restarted based on when 547 // it exited 548 cmd := testutil.MockCommand(c, "uptime", ` 549 #!/bin/sh 550 echo "boot time broken" 551 exit 1 552 `) 553 defer cmd.Restore() 554 555 logbuf, r := logger.MockLogger() 556 defer r() 557 558 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 559 560 // add the initial state of the service file using Requires 561 requiresContent := mkUnitFile(c, &unitOptions{ 562 usrLibSnapdOrderVerb: "Requires", 563 snapName: "test-snap", 564 snapRev: "42", 565 }) 566 err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644) 567 c.Assert(err, IsNil) 568 569 r = s.mockSystemctlCalls(c, []expectedSystemctl{ 570 { 571 expArgs: []string{"daemon-reload"}, 572 }, 573 { 574 // usr-lib-snapd.mount has never been stopped though so we skip out 575 // anyways 576 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}, 577 output: "InactiveEnterTimestamp=", 578 }, 579 }) 580 defer r() 581 582 err = s.mgr.Ensure() 583 c.Assert(err, IsNil) 584 585 // we wrote the service unit file 586 content := mkUnitFile(c, &unitOptions{ 587 usrLibSnapdOrderVerb: "Wants", 588 snapName: "test-snap", 589 snapRev: "42", 590 }) 591 c.Assert(svcFile, testutil.FileEquals, content) 592 593 c.Assert(cmd.Calls(), DeepEquals, [][]string{ 594 {"uptime", "-s"}, 595 }) 596 597 c.Assert(logbuf.String(), Matches, ".*error getting boot time: boot time broken\n") 598 599 // we did not request a restart 600 c.Assert(s.restartRequests, HasLen, 0) 601 } 602 603 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndRestarts(c *C) { 604 s.state.Lock() 605 // there is a snap in snap state that needs a service generated for it 606 snapstate.Set(s.state, "test-snap", s.testSnapState) 607 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 608 609 s.state.Unlock() 610 611 // add the usr-lib-snapd.mount unit 612 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 613 c.Assert(err, IsNil) 614 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 615 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 616 c.Assert(err, IsNil) 617 618 now := time.Now() 619 err = os.Chtimes(usrLibSnapdMountFile, now, now) 620 c.Assert(err, IsNil) 621 622 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 623 624 // add the initial state of the service file using Requires 625 requiresContent := mkUnitFile(c, &unitOptions{ 626 usrLibSnapdOrderVerb: "Requires", 627 snapName: "test-snap", 628 snapRev: "42", 629 }) 630 err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644) 631 c.Assert(err, IsNil) 632 633 slightFuture := now.Add(30 * time.Minute).Format(systemdTimeFormat) 634 theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat) 635 636 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 637 { 638 expArgs: []string{"daemon-reload"}, 639 }, 640 { 641 // usr-lib-snapd.mount was stopped "far in the future" 642 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}, 643 output: fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture), 644 }, 645 { 646 // but the snap.test-snap.svc1 was stopped only slightly in the 647 // future (hence before the usr-lib-snapd.mount unit was stopped and 648 // after usr-lib-snapd.mount file was modified) 649 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}, 650 output: fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture), 651 }, 652 { 653 expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, 654 output: "enabled", 655 }, 656 { 657 expArgs: []string{"start", "snap.test-snap.svc1.service"}, 658 }, 659 }) 660 defer r() 661 662 err = s.mgr.Ensure() 663 c.Assert(err, IsNil) 664 665 // we wrote the service unit file 666 content := mkUnitFile(c, &unitOptions{ 667 usrLibSnapdOrderVerb: "Wants", 668 snapName: "test-snap", 669 snapRev: "42", 670 }) 671 c.Assert(svcFile, testutil.FileEquals, content) 672 673 // we did not request a restart 674 c.Assert(s.restartRequests, HasLen, 0) 675 } 676 677 type systemctlDisabledServiceError struct{} 678 679 func (s systemctlDisabledServiceError) Msg() []byte { return []byte("disabled") } 680 func (s systemctlDisabledServiceError) ExitCode() int { return 1 } 681 func (s systemctlDisabledServiceError) Error() string { return "disabled service" } 682 683 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesButDoesNotRestartDisabledServices(c *C) { 684 s.state.Lock() 685 // there is a snap in snap state that needs a service generated for it 686 snapstate.Set(s.state, "test-snap", s.testSnapState) 687 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 688 689 s.state.Unlock() 690 691 // add the usr-lib-snapd.mount unit 692 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 693 c.Assert(err, IsNil) 694 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 695 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 696 c.Assert(err, IsNil) 697 698 now := time.Now() 699 err = os.Chtimes(usrLibSnapdMountFile, now, now) 700 c.Assert(err, IsNil) 701 702 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 703 704 // add the initial state of the service file using Requires 705 requiresContent := mkUnitFile(c, &unitOptions{ 706 usrLibSnapdOrderVerb: "Requires", 707 snapName: "test-snap", 708 snapRev: "42", 709 }) 710 err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644) 711 c.Assert(err, IsNil) 712 713 slightFuture := now.Add(30 * time.Minute).Format(systemdTimeFormat) 714 theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat) 715 716 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 717 { 718 expArgs: []string{"daemon-reload"}, 719 }, 720 { 721 // usr-lib-snapd.mount was stopped "far in the future" 722 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}, 723 output: fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture), 724 }, 725 { 726 // but the snap.test-snap.svc1 was stopped only slightly in the 727 // future (hence before the usr-lib-snapd.mount unit was stopped and 728 // after usr-lib-snapd.mount file was modified) 729 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}, 730 output: fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture), 731 }, 732 // the service is disabled 733 { 734 expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, 735 output: "disabled", 736 err: systemctlDisabledServiceError{}, 737 }, 738 // then we don't restart the service even though it was killed 739 }) 740 defer r() 741 742 err = s.mgr.Ensure() 743 c.Assert(err, IsNil) 744 745 // we wrote the service unit file 746 content := mkUnitFile(c, &unitOptions{ 747 usrLibSnapdOrderVerb: "Wants", 748 snapName: "test-snap", 749 snapRev: "42", 750 }) 751 c.Assert(svcFile, testutil.FileEquals, content) 752 753 // we did not request a restart 754 c.Assert(s.restartRequests, HasLen, 0) 755 } 756 757 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesDoesNotRestartServicesKilledBeforeSnapdRefresh(c *C) { 758 s.state.Lock() 759 // there is a snap in snap state that needs a service generated for it 760 snapstate.Set(s.state, "test-snap", s.testSnapState) 761 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 762 763 s.state.Unlock() 764 765 // add the usr-lib-snapd.mount unit 766 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 767 c.Assert(err, IsNil) 768 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 769 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 770 c.Assert(err, IsNil) 771 772 now := time.Now() 773 err = os.Chtimes(usrLibSnapdMountFile, now, now) 774 c.Assert(err, IsNil) 775 776 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 777 778 // add the initial state of the service file using Requires 779 requiresContent := mkUnitFile(c, &unitOptions{ 780 usrLibSnapdOrderVerb: "Requires", 781 snapName: "test-snap", 782 snapRev: "42", 783 }) 784 err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644) 785 c.Assert(err, IsNil) 786 787 theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat) 788 thePast := now.Add(-30 * time.Minute).Format(systemdTimeFormat) 789 790 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 791 { 792 expArgs: []string{"daemon-reload"}, 793 }, 794 { 795 // usr-lib-snapd.mount was stopped "far in the future" 796 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}, 797 output: fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture), 798 }, 799 { 800 // but the snap.test-snap.svc1 was stopped before that, so it isn't 801 // restarted 802 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}, 803 output: fmt.Sprintf("InactiveEnterTimestamp=%s", thePast), 804 }, 805 }) 806 defer r() 807 808 err = s.mgr.Ensure() 809 c.Assert(err, IsNil) 810 811 // we wrote the service unit file 812 content := mkUnitFile(c, &unitOptions{ 813 usrLibSnapdOrderVerb: "Wants", 814 snapName: "test-snap", 815 snapRev: "42", 816 }) 817 c.Assert(svcFile, testutil.FileEquals, content) 818 819 // we did not request a restart 820 c.Assert(s.restartRequests, HasLen, 0) 821 } 822 823 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesDoesNotRestartServicesKilledAfterSnapdRefresh(c *C) { 824 s.state.Lock() 825 // there is a snap in snap state that needs a service generated for it 826 snapstate.Set(s.state, "test-snap", s.testSnapState) 827 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 828 829 s.state.Unlock() 830 831 // add the usr-lib-snapd.mount unit 832 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 833 c.Assert(err, IsNil) 834 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 835 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 836 c.Assert(err, IsNil) 837 838 now := time.Now() 839 err = os.Chtimes(usrLibSnapdMountFile, now, now) 840 c.Assert(err, IsNil) 841 842 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 843 844 // add the initial state of the service file using Requires 845 requiresContent := mkUnitFile(c, &unitOptions{ 846 usrLibSnapdOrderVerb: "Requires", 847 snapName: "test-snap", 848 snapRev: "42", 849 }) 850 err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644) 851 c.Assert(err, IsNil) 852 853 theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat) 854 thePast := now.Add(-30 * time.Minute).Format(systemdTimeFormat) 855 856 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 857 { 858 expArgs: []string{"daemon-reload"}, 859 }, 860 { 861 // usr-lib-snapd.mount was stopped in the past 862 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}, 863 output: fmt.Sprintf("InactiveEnterTimestamp=%s", thePast), 864 }, 865 { 866 // but the snap.test-snap.svc1 was stopped after that, so it isn't 867 // restarted 868 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}, 869 output: fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture), 870 }, 871 }) 872 defer r() 873 874 err = s.mgr.Ensure() 875 c.Assert(err, IsNil) 876 877 // we wrote the service unit file 878 content := mkUnitFile(c, &unitOptions{ 879 usrLibSnapdOrderVerb: "Wants", 880 snapName: "test-snap", 881 snapRev: "42", 882 }) 883 c.Assert(svcFile, testutil.FileEquals, content) 884 885 // we did not request a restart 886 c.Assert(s.restartRequests, HasLen, 0) 887 } 888 889 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSimpleRewritesServicesFilesAndRestartsTimePrecisionSilly(c *C) { 890 s.state.Lock() 891 // there is a snap in snap state that needs a service generated for it 892 snapstate.Set(s.state, "test-snap", s.testSnapState) 893 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 894 895 s.state.Unlock() 896 897 // add the usr-lib-snapd.mount unit 898 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 899 c.Assert(err, IsNil) 900 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 901 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 902 c.Assert(err, IsNil) 903 904 // this test is about the specific scenario we have now when using systemctl 905 // show --property where the time precision of the InactiveEnterTimestamp's 906 // is much lower than that of the modification file time, so we need to 907 // set the inactive enter time for both the usr-lib-snapd.mount and the snap 908 // service to be the same time, which is actually _in the past_ compared to 909 // the file modification time 910 911 // truncate the current time and add 500 milliseconds 912 t0 := time.Now().Truncate(time.Second).Add(500 * time.Millisecond) 913 err = os.Chtimes(usrLibSnapdMountFile, t0, t0) 914 c.Assert(err, IsNil) 915 916 // drop the milliseconds 917 t1 := t0.Truncate(time.Second) 918 t1Str := t1.Format(systemdTimeFormat) 919 920 // double check our math for the times is correct 921 c.Assert(t1.Before(t0), Equals, true) 922 c.Assert(t0.After(t1), Equals, true) 923 c.Assert(t1.Equal(t0), Equals, false) 924 925 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 926 927 // add the initial state of the service file using Requires 928 requiresContent := mkUnitFile(c, &unitOptions{ 929 usrLibSnapdOrderVerb: "Requires", 930 snapName: "test-snap", 931 snapRev: "42", 932 }) 933 err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644) 934 c.Assert(err, IsNil) 935 936 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 937 { 938 expArgs: []string{"daemon-reload"}, 939 }, 940 { 941 // usr-lib-snapd.mount was stopped "far in the future" 942 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}, 943 output: fmt.Sprintf("InactiveEnterTimestamp=%s", t1Str), 944 }, 945 { 946 // but the snap.test-snap.svc1 was stopped only slightly in the 947 // future 948 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}, 949 output: fmt.Sprintf("InactiveEnterTimestamp=%s", t1Str), 950 }, 951 { 952 expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, 953 output: "enabled", 954 }, 955 { 956 expArgs: []string{"start", "snap.test-snap.svc1.service"}, 957 }, 958 }) 959 defer r() 960 961 err = s.mgr.Ensure() 962 c.Assert(err, IsNil) 963 964 // the file was rewritten to use Wants instead now 965 wantsContent := mkUnitFile(c, &unitOptions{ 966 usrLibSnapdOrderVerb: "Wants", 967 snapName: "test-snap", 968 snapRev: "42", 969 }) 970 c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileEquals, wantsContent) 971 972 // we did not request a restart 973 c.Assert(s.restartRequests, HasLen, 0) 974 } 975 976 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSimpleRewritesServicesFilesAndRestartsTimePrecisionMoreSilly(c *C) { 977 s.state.Lock() 978 // there is a snap in snap state that needs a service generated for it 979 snapstate.Set(s.state, "test-snap", s.testSnapState) 980 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 981 982 s.state.Unlock() 983 984 // add the usr-lib-snapd.mount unit 985 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 986 c.Assert(err, IsNil) 987 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 988 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 989 c.Assert(err, IsNil) 990 991 // this test is like TestEnsureSnapServicesSimpleRewritesServicesFilesAndRestartsTimePrecisionSilly, 992 // but more extreme, in that we don't have precision problems of less than a 993 // second, we have some more critical error where the lower timestamp range 994 // is somehow way in the future and we want our system to act rationally and 995 // pick the upper time bound as the lower time bound when the initially 996 // identified lower time bound is nonsensical 997 998 // truncate the current time and add 500 minutes 999 now := time.Now().Truncate(time.Second) 1000 t0 := now.Add(500 * time.Minute) 1001 err = os.Chtimes(usrLibSnapdMountFile, t0, t0) 1002 c.Assert(err, IsNil) 1003 1004 t1 := now 1005 t1Str := t1.Format(systemdTimeFormat) 1006 1007 // double check our math for the times is correct 1008 c.Assert(t1.Before(t0), Equals, true) 1009 c.Assert(t0.After(t1), Equals, true) 1010 c.Assert(t1.Equal(t0), Equals, false) 1011 1012 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 1013 1014 // add the initial state of the service file using Requires 1015 requiresContent := mkUnitFile(c, &unitOptions{ 1016 usrLibSnapdOrderVerb: "Requires", 1017 snapName: "test-snap", 1018 snapRev: "42", 1019 }) 1020 err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644) 1021 c.Assert(err, IsNil) 1022 1023 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 1024 { 1025 expArgs: []string{"daemon-reload"}, 1026 }, 1027 { 1028 // usr-lib-snapd.mount was stopped "far in the future" 1029 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}, 1030 output: fmt.Sprintf("InactiveEnterTimestamp=%s", t1Str), 1031 }, 1032 { 1033 // but the snap.test-snap.svc1 was stopped only slightly in the 1034 // future 1035 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}, 1036 output: fmt.Sprintf("InactiveEnterTimestamp=%s", t1Str), 1037 }, 1038 { 1039 expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, 1040 output: "enabled", 1041 }, 1042 { 1043 expArgs: []string{"start", "snap.test-snap.svc1.service"}, 1044 }, 1045 }) 1046 defer r() 1047 1048 err = s.mgr.Ensure() 1049 c.Assert(err, IsNil) 1050 1051 // the file was rewritten to use Wants instead now 1052 wantsContent := mkUnitFile(c, &unitOptions{ 1053 usrLibSnapdOrderVerb: "Wants", 1054 snapName: "test-snap", 1055 snapRev: "42", 1056 }) 1057 c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileEquals, wantsContent) 1058 1059 // we did not request a restart 1060 c.Assert(s.restartRequests, HasLen, 0) 1061 } 1062 1063 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSimpleRewritesServicesFilesAndRestartsUC18(c *C) { 1064 s.state.Lock() 1065 // there is a snap in snap state that needs a service generated for it 1066 snapstate.Set(s.state, "test-snap", s.testSnapState) 1067 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 1068 1069 s.state.Unlock() 1070 1071 // add the usr-lib-snapd.mount unit 1072 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 1073 c.Assert(err, IsNil) 1074 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 1075 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 1076 c.Assert(err, IsNil) 1077 1078 now := time.Now() 1079 err = os.Chtimes(usrLibSnapdMountFile, now, now) 1080 c.Assert(err, IsNil) 1081 1082 slightFuture := now.Add(30 * time.Minute).Format(systemdTimeFormat) 1083 theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat) 1084 1085 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 1086 1087 // add the initial state of the service file using Requires 1088 requiresContent := mkUnitFile(c, &unitOptions{ 1089 usrLibSnapdOrderVerb: "Requires", 1090 snapName: "test-snap", 1091 snapRev: "42", 1092 }) 1093 err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644) 1094 c.Assert(err, IsNil) 1095 1096 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 1097 { 1098 expArgs: []string{"daemon-reload"}, 1099 }, 1100 { 1101 // usr-lib-snapd.mount was stopped "far in the future" 1102 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}, 1103 output: fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture), 1104 }, 1105 { 1106 // but the snap.test-snap.svc1 was stopped only slightly in the 1107 // future 1108 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}, 1109 output: fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture), 1110 }, 1111 { 1112 expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, 1113 output: "enabled", 1114 }, 1115 { 1116 expArgs: []string{"start", "snap.test-snap.svc1.service"}, 1117 }, 1118 }) 1119 defer r() 1120 1121 err = s.mgr.Ensure() 1122 c.Assert(err, IsNil) 1123 1124 // the file was rewritten to use Wants instead now 1125 wantsContent := mkUnitFile(c, &unitOptions{ 1126 usrLibSnapdOrderVerb: "Wants", 1127 snapName: "test-snap", 1128 snapRev: "42", 1129 }) 1130 c.Assert(filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service"), testutil.FileEquals, wantsContent) 1131 1132 // we did not request a restart 1133 c.Assert(s.restartRequests, HasLen, 0) 1134 } 1135 1136 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesNoChangeServiceFileDoesNothingUC18(c *C) { 1137 s.state.Lock() 1138 // there is a snap in snap state that needs a service generated for it 1139 snapstate.Set(s.state, "test-snap", s.testSnapState) 1140 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 1141 1142 s.state.Unlock() 1143 1144 // add the usr-lib-snapd.mount unit 1145 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 1146 c.Assert(err, IsNil) 1147 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 1148 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 1149 c.Assert(err, IsNil) 1150 1151 now := time.Now() 1152 err = os.Chtimes(usrLibSnapdMountFile, now, now) 1153 c.Assert(err, IsNil) 1154 1155 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 1156 1157 // add the initial state of the service file using Wants 1158 content := mkUnitFile(c, &unitOptions{ 1159 usrLibSnapdOrderVerb: "Wants", 1160 snapName: "test-snap", 1161 snapRev: "42", 1162 }) 1163 err = ioutil.WriteFile(svcFile, []byte(content), 0644) 1164 c.Assert(err, IsNil) 1165 1166 // we don't use systemctl at all because we didn't change anything 1167 // s.systemctlReturns = []expectedSystemctl{} 1168 1169 err = s.mgr.Ensure() 1170 c.Assert(err, IsNil) 1171 1172 // the file was not modified 1173 c.Assert(svcFile, testutil.FileEquals, content) 1174 1175 // we did not request a restart 1176 c.Assert(s.restartRequests, HasLen, 0) 1177 1178 } 1179 1180 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesDoesNotRestartServicesWhenUsrLibSnapdWasNeverInactive(c *C) { 1181 s.state.Lock() 1182 // there is a snap in snap state that needs a service generated for it 1183 snapstate.Set(s.state, "test-snap", s.testSnapState) 1184 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 1185 1186 s.state.Unlock() 1187 1188 // add the usr-lib-snapd.mount unit 1189 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 1190 c.Assert(err, IsNil) 1191 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 1192 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 1193 c.Assert(err, IsNil) 1194 1195 now := time.Now() 1196 os.Chtimes(usrLibSnapdMountFile, now, now) 1197 1198 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 1199 1200 // add the initial state of the service file using Requires 1201 requiresContent := mkUnitFile(c, &unitOptions{ 1202 usrLibSnapdOrderVerb: "Requires", 1203 snapName: "test-snap", 1204 snapRev: "42", 1205 }) 1206 err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644) 1207 c.Assert(err, IsNil) 1208 1209 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 1210 { 1211 expArgs: []string{"daemon-reload"}, 1212 }, 1213 { 1214 // usr-lib-snapd.mount has never been stopped this boot, thus has 1215 // always been active 1216 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}, 1217 output: "InactiveEnterTimestamp=", 1218 }, 1219 }) 1220 defer r() 1221 1222 err = s.mgr.Ensure() 1223 c.Assert(err, IsNil) 1224 1225 content := mkUnitFile(c, &unitOptions{ 1226 usrLibSnapdOrderVerb: "Wants", 1227 snapName: "test-snap", 1228 snapRev: "42", 1229 }) 1230 c.Assert(svcFile, testutil.FileEquals, content) 1231 1232 // we did not request a restart 1233 c.Assert(s.restartRequests, HasLen, 0) 1234 } 1235 1236 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndRestartsButThenFallsbackToReboot(c *C) { 1237 s.state.Lock() 1238 // there is a snap in snap state that needs a service generated for it 1239 snapstate.Set(s.state, "test-snap", s.testSnapState) 1240 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 1241 1242 s.state.Unlock() 1243 1244 // add the usr-lib-snapd.mount unit 1245 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 1246 c.Assert(err, IsNil) 1247 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 1248 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 1249 c.Assert(err, IsNil) 1250 1251 now := time.Now() 1252 err = os.Chtimes(usrLibSnapdMountFile, now, now) 1253 c.Assert(err, IsNil) 1254 1255 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 1256 1257 // add the initial state of the service file using Requires 1258 requiresContent := mkUnitFile(c, &unitOptions{ 1259 usrLibSnapdOrderVerb: "Requires", 1260 snapName: "test-snap", 1261 snapRev: "42", 1262 }) 1263 err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644) 1264 c.Assert(err, IsNil) 1265 1266 slightFuture := now.Add(30 * time.Minute).Format(systemdTimeFormat) 1267 theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat) 1268 1269 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 1270 { 1271 expArgs: []string{"daemon-reload"}, 1272 }, 1273 { 1274 // usr-lib-snapd.mount was stopped "far in the future" 1275 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}, 1276 output: fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture), 1277 }, 1278 { 1279 // but the snap.test-snap.svc1 was stopped only slightly in the 1280 // future (hence before the usr-lib-snapd.mount unit was stopped and 1281 // after usr-lib-snapd.mount file was modified) 1282 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}, 1283 output: fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture), 1284 }, 1285 { 1286 expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, 1287 output: "enabled", 1288 }, 1289 { 1290 expArgs: []string{"start", "snap.test-snap.svc1.service"}, 1291 err: fmt.Errorf("this service is having a bad day"), 1292 }, 1293 { 1294 expArgs: []string{"stop", "snap.test-snap.svc1.service"}, 1295 err: fmt.Errorf("this service is still having a bad day"), 1296 }, 1297 }) 1298 defer r() 1299 1300 err = s.mgr.Ensure() 1301 c.Assert(err, ErrorMatches, "error trying to restart killed services, immediately rebooting: this service is having a bad day") 1302 1303 // we did write the service unit file 1304 content := mkUnitFile(c, &unitOptions{ 1305 usrLibSnapdOrderVerb: "Wants", 1306 snapName: "test-snap", 1307 snapRev: "42", 1308 }) 1309 c.Assert(svcFile, testutil.FileEquals, content) 1310 1311 // we requested a restart 1312 c.Assert(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 1313 } 1314 1315 func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndTriesRestartButFailsButThenFallsbackToReboot(c *C) { 1316 s.state.Lock() 1317 // there is a snap in snap state that needs a service generated for it 1318 snapstate.Set(s.state, "test-snap", s.testSnapState) 1319 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 1320 1321 s.state.Unlock() 1322 1323 // add the usr-lib-snapd.mount unit 1324 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 1325 c.Assert(err, IsNil) 1326 usrLibSnapdMountFile := filepath.Join(dirs.SnapServicesDir, wrappers.SnapdToolingMountUnit) 1327 err = ioutil.WriteFile(usrLibSnapdMountFile, nil, 0644) 1328 c.Assert(err, IsNil) 1329 1330 now := time.Now() 1331 err = os.Chtimes(usrLibSnapdMountFile, now, now) 1332 c.Assert(err, IsNil) 1333 1334 svcFile := filepath.Join(dirs.GlobalRootDir, "/etc/systemd/system/snap.test-snap.svc1.service") 1335 1336 // add the initial state of the service file using Requires 1337 requiresContent := mkUnitFile(c, &unitOptions{ 1338 usrLibSnapdOrderVerb: "Requires", 1339 snapName: "test-snap", 1340 snapRev: "42", 1341 }) 1342 err = ioutil.WriteFile(svcFile, []byte(requiresContent), 0644) 1343 c.Assert(err, IsNil) 1344 1345 slightFuture := now.Add(30 * time.Minute).Format(systemdTimeFormat) 1346 theFuture := now.Add(1 * time.Hour).Format(systemdTimeFormat) 1347 1348 r := s.mockSystemctlCalls(c, []expectedSystemctl{ 1349 { 1350 expArgs: []string{"daemon-reload"}, 1351 }, 1352 { 1353 // usr-lib-snapd.mount was stopped "far in the future" 1354 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "usr-lib-snapd.mount"}, 1355 output: fmt.Sprintf("InactiveEnterTimestamp=%s", theFuture), 1356 }, 1357 { 1358 // but the snap.test-snap.svc1 was stopped only slightly in the 1359 // future (hence before the usr-lib-snapd.mount unit was stopped and 1360 // after usr-lib-snapd.mount file was modified) 1361 expArgs: []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}, 1362 output: fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture), 1363 }, 1364 { 1365 expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, 1366 err: fmt.Errorf("systemd is having a bad day"), 1367 }, 1368 }) 1369 defer r() 1370 1371 err = s.mgr.Ensure() 1372 c.Assert(err, ErrorMatches, "error trying to restart killed services, immediately rebooting: systemd is having a bad day") 1373 1374 // we did write the service unit file 1375 content := mkUnitFile(c, &unitOptions{ 1376 usrLibSnapdOrderVerb: "Wants", 1377 snapName: "test-snap", 1378 snapRev: "42", 1379 }) 1380 c.Assert(svcFile, testutil.FileEquals, content) 1381 1382 // we requested a restart 1383 c.Assert(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 1384 }