github.com/rigado/snapd@v2.42.5-go-mod+incompatible/wrappers/services_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-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 wrappers_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "path/filepath" 26 "regexp" 27 "sort" 28 "strings" 29 "time" 30 31 . "gopkg.in/check.v1" 32 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/osutil" 35 "github.com/snapcore/snapd/progress" 36 "github.com/snapcore/snapd/snap" 37 "github.com/snapcore/snapd/snap/snaptest" 38 "github.com/snapcore/snapd/strutil" 39 "github.com/snapcore/snapd/systemd" 40 "github.com/snapcore/snapd/testutil" 41 "github.com/snapcore/snapd/timings" 42 "github.com/snapcore/snapd/wrappers" 43 ) 44 45 type servicesTestSuite struct { 46 tempdir string 47 48 sysdLog [][]string 49 50 systemctlRestorer, delaysRestorer func() 51 52 perfTimings timings.Measurer 53 } 54 55 var _ = Suite(&servicesTestSuite{}) 56 57 func (s *servicesTestSuite) SetUpTest(c *C) { 58 s.tempdir = c.MkDir() 59 s.sysdLog = nil 60 dirs.SetRootDir(s.tempdir) 61 62 s.systemctlRestorer = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 63 s.sysdLog = append(s.sysdLog, cmd) 64 return []byte("ActiveState=inactive\n"), nil 65 }) 66 s.delaysRestorer = systemd.MockStopDelays(time.Millisecond, 25*time.Second) 67 s.perfTimings = timings.New(nil) 68 69 } 70 71 func (s *servicesTestSuite) TearDownTest(c *C) { 72 dirs.SetRootDir("") 73 s.systemctlRestorer() 74 s.delaysRestorer() 75 } 76 77 func (s *servicesTestSuite) TestAddSnapServicesAndRemove(c *C) { 78 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 79 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 80 81 err := wrappers.AddSnapServices(info, nil) 82 c.Assert(err, IsNil) 83 c.Check(s.sysdLog, DeepEquals, [][]string{ 84 {"--root", dirs.GlobalRootDir, "enable", filepath.Base(svcFile)}, 85 {"daemon-reload"}, 86 }) 87 88 content, err := ioutil.ReadFile(svcFile) 89 c.Assert(err, IsNil) 90 91 verbs := []string{"Start", "Stop", "StopPost"} 92 cmds := []string{"", " --command=stop", " --command=post-stop"} 93 for i := range verbs { 94 expected := fmt.Sprintf("Exec%s=/usr/bin/snap run%s hello-snap.svc1", verbs[i], cmds[i]) 95 c.Check(string(content), Matches, "(?ms).*^"+regexp.QuoteMeta(expected)) // check.v1 adds ^ and $ around the regexp provided 96 } 97 98 s.sysdLog = nil 99 err = wrappers.StopServices(info.Services(), "", progress.Null, s.perfTimings) 100 c.Assert(err, IsNil) 101 c.Assert(s.sysdLog, HasLen, 2) 102 c.Check(s.sysdLog, DeepEquals, [][]string{ 103 {"stop", filepath.Base(svcFile)}, 104 {"show", "--property=ActiveState", "snap.hello-snap.svc1.service"}, 105 }) 106 107 s.sysdLog = nil 108 err = wrappers.RemoveSnapServices(info, progress.Null) 109 c.Assert(err, IsNil) 110 c.Check(osutil.FileExists(svcFile), Equals, false) 111 c.Assert(s.sysdLog, HasLen, 2) 112 c.Check(s.sysdLog[0], DeepEquals, []string{"--root", dirs.GlobalRootDir, "disable", filepath.Base(svcFile)}) 113 c.Check(s.sysdLog[1], DeepEquals, []string{"daemon-reload"}) 114 } 115 116 var snapdYaml = `name: snapd 117 version: 1.0 118 type: snapd 119 ` 120 121 func (s *servicesTestSuite) TestRemoveSnapWithSocketsRemovesSocketsService(c *C) { 122 info := snaptest.MockSnap(c, packageHello+` 123 svc1: 124 daemon: simple 125 plugs: [network-bind] 126 sockets: 127 sock1: 128 listen-stream: $SNAP_DATA/sock1.socket 129 socket-mode: 0666 130 sock2: 131 listen-stream: $SNAP_COMMON/sock2.socket 132 `, &snap.SideInfo{Revision: snap.R(12)}) 133 134 err := wrappers.AddSnapServices(info, nil) 135 c.Assert(err, IsNil) 136 137 err = wrappers.StopServices(info.Services(), "", &progress.Null, s.perfTimings) 138 c.Assert(err, IsNil) 139 140 err = wrappers.RemoveSnapServices(info, &progress.Null) 141 c.Assert(err, IsNil) 142 143 app := info.Apps["svc1"] 144 c.Assert(app.Sockets, HasLen, 2) 145 for _, socket := range app.Sockets { 146 c.Check(osutil.FileExists(socket.File()), Equals, false) 147 } 148 } 149 150 func (s *servicesTestSuite) TestRemoveSnapPackageFallbackToKill(c *C) { 151 restore := wrappers.MockKillWait(time.Millisecond) 152 defer restore() 153 154 var sysdLog [][]string 155 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 156 // filter out the "systemctl show" that 157 // StopServices generates 158 if cmd[0] != "show" { 159 sysdLog = append(sysdLog, cmd) 160 } 161 return []byte("ActiveState=active\n"), nil 162 }) 163 defer r() 164 165 info := snaptest.MockSnap(c, `name: wat 166 version: 42 167 apps: 168 wat: 169 command: wat 170 stop-timeout: 20ms 171 daemon: forking 172 `, &snap.SideInfo{Revision: snap.R(11)}) 173 174 err := wrappers.AddSnapServices(info, nil) 175 c.Assert(err, IsNil) 176 177 sysdLog = nil 178 179 svcFName := "snap.wat.wat.service" 180 181 err = wrappers.StopServices(info.Services(), "", progress.Null, s.perfTimings) 182 c.Assert(err, IsNil) 183 184 c.Check(sysdLog, DeepEquals, [][]string{ 185 {"stop", svcFName}, 186 // check kill invocations 187 {"kill", svcFName, "-s", "TERM", "--kill-who=all"}, 188 {"kill", svcFName, "-s", "KILL", "--kill-who=all"}, 189 }) 190 } 191 192 func (s *servicesTestSuite) TestServicesEnableState(c *C) { 193 info := snaptest.MockSnap(c, packageHello+` 194 svc2: 195 command: bin/hello 196 daemon: forking 197 `, &snap.SideInfo{Revision: snap.R(12)}) 198 svc1File := "snap.hello-snap.svc1.service" 199 svc2File := "snap.hello-snap.svc2.service" 200 201 s.systemctlRestorer() 202 r := testutil.MockCommand(c, "systemctl", `#!/bin/sh 203 if [ "$1" = "--root" ]; then 204 # shifting by 2 also drops the temp dir arg to --root 205 shift 2 206 fi 207 208 case "$1" in 209 is-enabled) 210 case "$2" in 211 "snap.hello-snap.svc1.service") 212 echo "disabled" 213 exit 1 214 ;; 215 "snap.hello-snap.svc2.service") 216 echo "enabled" 217 exit 0 218 ;; 219 *) 220 echo "unexpected service $*" 221 exit 2 222 ;; 223 esac 224 ;; 225 *) 226 echo "unexpected op $*" 227 exit 2 228 esac 229 230 exit 1 231 `) 232 defer r.Restore() 233 234 states, err := wrappers.ServicesEnableState(info, progress.Null) 235 c.Assert(err, IsNil) 236 237 c.Assert(states, DeepEquals, map[string]bool{ 238 "svc1": false, 239 "svc2": true, 240 }) 241 242 // the calls could be out of order in the list, since iterating over a map 243 // is non-deterministic, so manually check each call 244 c.Assert(r.Calls(), HasLen, 2) 245 for _, call := range r.Calls() { 246 c.Assert(call, HasLen, 5) 247 c.Assert(call[:4], DeepEquals, []string{"systemctl", "--root", s.tempdir, "is-enabled"}) 248 switch call[4] { 249 case svc1File, svc2File: 250 default: 251 c.Errorf("unknown service for systemctl call: %s", call[4]) 252 } 253 } 254 } 255 256 func (s *servicesTestSuite) TestServicesEnableStateFail(c *C) { 257 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 258 svc1File := "snap.hello-snap.svc1.service" 259 260 s.systemctlRestorer() 261 r := testutil.MockCommand(c, "systemctl", `#!/bin/sh 262 if [ "$1" = "--root" ]; then 263 # shifting by 2 also drops the temp dir arg to --root 264 shift 2 265 fi 266 267 case "$1" in 268 is-enabled) 269 case "$2" in 270 "snap.hello-snap.svc1.service") 271 echo "whoops" 272 exit 1 273 ;; 274 *) 275 echo "unexpected service $*" 276 exit 2 277 ;; 278 esac 279 ;; 280 *) 281 echo "unexpected op $*" 282 exit 2 283 esac 284 285 exit 1 286 `) 287 defer r.Restore() 288 289 _, err := wrappers.ServicesEnableState(info, progress.Null) 290 c.Assert(err, ErrorMatches, ".*is-enabled snap.hello-snap.svc1.service\\] failed with exit status 1: whoops\n.*") 291 292 c.Assert(r.Calls(), DeepEquals, [][]string{ 293 {"systemctl", "--root", s.tempdir, "is-enabled", svc1File}, 294 }) 295 } 296 297 func (s *servicesTestSuite) TestStopServicesWithSockets(c *C) { 298 var sysdLog []string 299 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 300 if cmd[0] == "stop" { 301 sysdLog = append(sysdLog, cmd[1]) 302 } 303 return []byte("ActiveState=inactive\n"), nil 304 }) 305 defer r() 306 307 info := snaptest.MockSnap(c, packageHello+` 308 svc1: 309 daemon: simple 310 plugs: [network-bind] 311 sockets: 312 sock1: 313 listen-stream: $SNAP_COMMON/sock1.socket 314 socket-mode: 0666 315 sock2: 316 listen-stream: $SNAP_DATA/sock2.socket 317 `, &snap.SideInfo{Revision: snap.R(12)}) 318 319 err := wrappers.AddSnapServices(info, nil) 320 c.Assert(err, IsNil) 321 322 sysdLog = nil 323 324 err = wrappers.StopServices(info.Services(), "", &progress.Null, s.perfTimings) 325 c.Assert(err, IsNil) 326 327 sort.Strings(sysdLog) 328 c.Check(sysdLog, DeepEquals, []string{ 329 "snap.hello-snap.svc1.service", "snap.hello-snap.svc1.sock1.socket", "snap.hello-snap.svc1.sock2.socket"}) 330 } 331 332 func (s *servicesTestSuite) TestStartServices(c *C) { 333 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 334 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 335 336 err := wrappers.StartServices(info.Services(), nil, s.perfTimings) 337 c.Assert(err, IsNil) 338 339 c.Assert(s.sysdLog, DeepEquals, [][]string{ 340 {"--root", s.tempdir, "is-enabled", filepath.Base(svcFile)}, 341 {"start", filepath.Base(svcFile)}, 342 }) 343 } 344 345 func (s *servicesTestSuite) TestNoStartDisabledServices(c *C) { 346 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 347 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 348 349 s.systemctlRestorer() 350 r := testutil.MockCommand(c, "systemctl", `#!/bin/sh 351 if [ "$1" = "--root" ]; then 352 shift 2 353 fi 354 355 case "$1" in 356 is-enabled) 357 if [ "$2" = "snap.hello-snap.svc1.service" ]; then 358 echo "disabled" 359 exit 1 360 else 361 echo "unexpected call $*" 362 exit 2 363 fi 364 ;; 365 *) 366 echo "unexpected call $*" 367 exit 2 368 esac 369 `) 370 defer r.Restore() 371 372 err := wrappers.StartServices(info.Services(), nil, s.perfTimings) 373 c.Assert(err, IsNil) 374 375 c.Assert(r.Calls(), DeepEquals, [][]string{ 376 {"systemctl", "--root", s.tempdir, "is-enabled", filepath.Base(svcFile)}, 377 }) 378 } 379 380 func (s *servicesTestSuite) TestAddSnapMultiServicesFailCreateCleanup(c *C) { 381 // sanity check: there are no service files 382 svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service")) 383 c.Check(svcFiles, HasLen, 0) 384 385 info := snaptest.MockSnap(c, packageHello+` 386 svc2: 387 daemon: potato 388 `, &snap.SideInfo{Revision: snap.R(12)}) 389 390 err := wrappers.AddSnapServices(info, nil) 391 c.Assert(err, ErrorMatches, ".*potato.*") 392 393 // the services are cleaned up 394 svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service")) 395 c.Check(svcFiles, HasLen, 0) 396 397 // *either* the first service failed validation, and nothing 398 // was done, *or* the second one failed, and the first one was 399 // enabled before the second failed, and disabled after. 400 if len(s.sysdLog) > 0 { 401 // the second service failed validation 402 c.Check(s.sysdLog, DeepEquals, [][]string{ 403 {"--root", dirs.GlobalRootDir, "enable", "snap.hello-snap.svc1.service"}, 404 {"--root", dirs.GlobalRootDir, "disable", "snap.hello-snap.svc1.service"}, 405 {"daemon-reload"}, 406 }) 407 } 408 } 409 410 func (s *servicesTestSuite) TestAddSnapMultiServicesFailEnableCleanup(c *C) { 411 var sysdLog [][]string 412 svc1Name := "snap.hello-snap.svc1.service" 413 svc2Name := "snap.hello-snap.svc2.service" 414 numEnables := 0 415 416 // sanity check: there are no service files 417 svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service")) 418 c.Check(svcFiles, HasLen, 0) 419 420 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 421 sysdLog = append(sysdLog, cmd) 422 sdcmd := cmd[0] 423 if len(cmd) >= 2 { 424 sdcmd = cmd[len(cmd)-2] 425 } 426 switch sdcmd { 427 case "enable": 428 numEnables++ 429 switch numEnables { 430 case 1: 431 if cmd[len(cmd)-1] == svc2Name { 432 // the services are being iterated in the "wrong" order 433 svc1Name, svc2Name = svc2Name, svc1Name 434 } 435 return nil, nil 436 case 2: 437 return nil, fmt.Errorf("failed") 438 default: 439 panic("expected no more than 2 enables") 440 } 441 case "disable", "daemon-reload": 442 return nil, nil 443 default: 444 panic("unexpected systemctl command " + sdcmd) 445 } 446 }) 447 defer r() 448 449 info := snaptest.MockSnap(c, packageHello+` 450 svc2: 451 command: bin/hello 452 daemon: simple 453 `, &snap.SideInfo{Revision: snap.R(12)}) 454 455 err := wrappers.AddSnapServices(info, nil) 456 c.Assert(err, ErrorMatches, "failed") 457 458 // the services are cleaned up 459 svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service")) 460 c.Check(svcFiles, HasLen, 0) 461 c.Check(sysdLog, DeepEquals, [][]string{ 462 {"--root", dirs.GlobalRootDir, "enable", svc1Name}, 463 {"--root", dirs.GlobalRootDir, "enable", svc2Name}, // this one fails 464 {"--root", dirs.GlobalRootDir, "disable", svc1Name}, 465 {"daemon-reload"}, 466 }) 467 } 468 469 func (s *servicesTestSuite) TestAddSnapMultiServicesStartFailOnSystemdReloadCleanup(c *C) { 470 // this test might be overdoing it (it's mostly covering the same ground as the previous one), but ... :-) 471 var sysdLog [][]string 472 svc1Name := "snap.hello-snap.svc1.service" 473 svc2Name := "snap.hello-snap.svc2.service" 474 475 // sanity check: there are no service files 476 svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service")) 477 c.Check(svcFiles, HasLen, 0) 478 479 first := true 480 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 481 sysdLog = append(sysdLog, cmd) 482 if len(cmd) < 2 { 483 return nil, fmt.Errorf("failed") 484 } 485 if first { 486 first = false 487 if cmd[len(cmd)-1] == svc2Name { 488 // the services are being iterated in the "wrong" order 489 svc1Name, svc2Name = svc2Name, svc1Name 490 } 491 } 492 return nil, nil 493 494 }) 495 defer r() 496 497 info := snaptest.MockSnap(c, packageHello+` 498 svc2: 499 command: bin/hello 500 daemon: simple 501 `, &snap.SideInfo{Revision: snap.R(12)}) 502 503 err := wrappers.AddSnapServices(info, progress.Null) 504 c.Assert(err, ErrorMatches, "failed") 505 506 // the services are cleaned up 507 svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service")) 508 c.Check(svcFiles, HasLen, 0) 509 c.Check(sysdLog, DeepEquals, [][]string{ 510 {"--root", dirs.GlobalRootDir, "enable", svc1Name}, 511 {"--root", dirs.GlobalRootDir, "enable", svc2Name}, 512 {"daemon-reload"}, // this one fails 513 {"--root", dirs.GlobalRootDir, "disable", svc1Name}, 514 {"--root", dirs.GlobalRootDir, "disable", svc2Name}, 515 {"daemon-reload"}, // so does this one :-) 516 }) 517 } 518 519 func (s *servicesTestSuite) TestAddSnapSocketFiles(c *C) { 520 info := snaptest.MockSnap(c, packageHello+` 521 svc1: 522 daemon: simple 523 plugs: [network-bind] 524 sockets: 525 sock1: 526 listen-stream: $SNAP_COMMON/sock1.socket 527 socket-mode: 0666 528 sock2: 529 listen-stream: $SNAP_DATA/sock2.socket 530 sock3: 531 listen-stream: $XDG_RUNTIME_DIR/sock3.socket 532 533 `, &snap.SideInfo{Revision: snap.R(12)}) 534 535 sock1File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.sock1.socket") 536 sock2File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.sock2.socket") 537 sock3File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.sock3.socket") 538 539 err := wrappers.AddSnapServices(info, nil) 540 c.Assert(err, IsNil) 541 542 expected := fmt.Sprintf( 543 `[Socket] 544 Service=snap.hello-snap.svc1.service 545 FileDescriptorName=sock1 546 ListenStream=%s 547 SocketMode=0666 548 549 `, filepath.Join(s.tempdir, "/var/snap/hello-snap/common/sock1.socket")) 550 c.Check(sock1File, testutil.FileContains, expected) 551 552 expected = fmt.Sprintf( 553 `[Socket] 554 Service=snap.hello-snap.svc1.service 555 FileDescriptorName=sock2 556 ListenStream=%s 557 558 `, filepath.Join(s.tempdir, "/var/snap/hello-snap/12/sock2.socket")) 559 c.Check(sock2File, testutil.FileContains, expected) 560 561 expected = fmt.Sprintf( 562 `[Socket] 563 Service=snap.hello-snap.svc1.service 564 FileDescriptorName=sock3 565 ListenStream=%s 566 567 `, filepath.Join(s.tempdir, "/run/user/0/snap.hello-snap/sock3.socket")) 568 c.Check(sock3File, testutil.FileContains, expected) 569 } 570 571 func (s *servicesTestSuite) TestStartSnapMultiServicesFailStartCleanup(c *C) { 572 var sysdLog [][]string 573 svc1Name := "snap.hello-snap.svc1.service" 574 svc2Name := "snap.hello-snap.svc2.service" 575 576 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 577 sysdLog = append(sysdLog, cmd) 578 if len(cmd) >= 2 && cmd[0] == "start" { 579 name := cmd[len(cmd)-1] 580 if name == svc2Name { 581 return nil, fmt.Errorf("failed") 582 } 583 } 584 return []byte("ActiveState=inactive\n"), nil 585 }) 586 defer r() 587 588 info := snaptest.MockSnap(c, packageHello+` 589 svc2: 590 command: bin/hello 591 daemon: simple 592 `, &snap.SideInfo{Revision: snap.R(12)}) 593 594 svcs := info.Services() 595 c.Assert(svcs, HasLen, 2) 596 if svcs[0].Name == "svc2" { 597 svcs[0], svcs[1] = svcs[1], svcs[0] 598 } 599 err := wrappers.StartServices(svcs, nil, s.perfTimings) 600 c.Assert(err, ErrorMatches, "failed") 601 c.Assert(sysdLog, HasLen, 8, Commentf("len: %v calls: %v", len(sysdLog), sysdLog)) 602 c.Check(sysdLog, DeepEquals, [][]string{ 603 {"--root", s.tempdir, "is-enabled", svc1Name}, 604 {"--root", s.tempdir, "is-enabled", svc2Name}, 605 {"start", svc1Name}, 606 {"start", svc2Name}, // one of the services fails 607 {"stop", svc2Name}, 608 {"show", "--property=ActiveState", svc2Name}, 609 {"stop", svc1Name}, 610 {"show", "--property=ActiveState", svc1Name}, 611 }, Commentf("calls: %v", sysdLog)) 612 } 613 614 func (s *servicesTestSuite) TestStartSnapMultiServicesFailStartCleanupWithSockets(c *C) { 615 var sysdLog [][]string 616 svc1Name := "snap.hello-snap.svc1.service" 617 svc2Name := "snap.hello-snap.svc2.service" 618 svc2SocketName := "snap.hello-snap.svc2.sock1.socket" 619 svc3Name := "snap.hello-snap.svc3.service" 620 svc3SocketName := "snap.hello-snap.svc3.sock1.socket" 621 622 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 623 sysdLog = append(sysdLog, cmd) 624 c.Logf("call: %v", cmd) 625 if len(cmd) >= 2 && cmd[0] == "start" && cmd[1] == svc3SocketName { 626 // svc2 socket fails 627 return nil, fmt.Errorf("failed") 628 } 629 return []byte("ActiveState=inactive\n"), nil 630 }) 631 defer r() 632 633 info := snaptest.MockSnap(c, packageHello+` 634 svc2: 635 command: bin/hello 636 daemon: simple 637 sockets: 638 sock1: 639 listen-stream: $SNAP_COMMON/sock1.socket 640 socket-mode: 0666 641 svc3: 642 command: bin/hello 643 daemon: simple 644 sockets: 645 sock1: 646 listen-stream: $SNAP_COMMON/sock1.socket 647 socket-mode: 0666 648 `, &snap.SideInfo{Revision: snap.R(12)}) 649 650 // ensure desired order 651 apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"], info.Apps["svc3"]} 652 653 err := wrappers.StartServices(apps, nil, s.perfTimings) 654 c.Assert(err, ErrorMatches, "failed") 655 c.Logf("sysdlog: %v", sysdLog) 656 c.Assert(sysdLog, HasLen, 17, Commentf("len: %v calls: %v", len(sysdLog), sysdLog)) 657 c.Check(sysdLog, DeepEquals, [][]string{ 658 {"--root", s.tempdir, "is-enabled", svc1Name}, 659 {"--root", s.tempdir, "enable", svc2SocketName}, 660 {"start", svc2SocketName}, 661 {"--root", s.tempdir, "enable", svc3SocketName}, 662 {"start", svc3SocketName}, // start failed, what follows is the cleanup 663 {"stop", svc3SocketName}, 664 {"show", "--property=ActiveState", svc3SocketName}, 665 {"stop", svc3Name}, 666 {"show", "--property=ActiveState", svc3Name}, 667 {"--root", s.tempdir, "disable", svc3SocketName}, 668 {"stop", svc2SocketName}, 669 {"show", "--property=ActiveState", svc2SocketName}, 670 {"stop", svc2Name}, 671 {"show", "--property=ActiveState", svc2Name}, 672 {"--root", s.tempdir, "disable", svc2SocketName}, 673 {"stop", svc1Name}, 674 {"show", "--property=ActiveState", svc1Name}, 675 }, Commentf("calls: %v", sysdLog)) 676 } 677 678 func (s *servicesTestSuite) TestStartSnapServicesKeepsOrder(c *C) { 679 var sysdLog [][]string 680 svc1Name := "snap.services-snap.svc1.service" 681 svc2Name := "snap.services-snap.svc2.service" 682 svc3Name := "snap.services-snap.svc3.service" 683 684 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 685 sysdLog = append(sysdLog, cmd) 686 return []byte("ActiveState=inactive\n"), nil 687 }) 688 defer r() 689 690 info := snaptest.MockSnap(c, `name: services-snap 691 apps: 692 svc1: 693 daemon: simple 694 before: [svc3] 695 svc2: 696 daemon: simple 697 after: [svc1] 698 svc3: 699 daemon: simple 700 before: [svc2] 701 `, &snap.SideInfo{Revision: snap.R(12)}) 702 703 svcs := info.Services() 704 c.Assert(svcs, HasLen, 3) 705 706 sorted, err := snap.SortServices(svcs) 707 c.Assert(err, IsNil) 708 709 err = wrappers.StartServices(sorted, nil, s.perfTimings) 710 c.Assert(err, IsNil) 711 c.Assert(sysdLog, HasLen, 6, Commentf("len: %v calls: %v", len(sysdLog), sysdLog)) 712 c.Check(sysdLog, DeepEquals, [][]string{ 713 {"--root", s.tempdir, "is-enabled", svc1Name}, 714 {"--root", s.tempdir, "is-enabled", svc3Name}, 715 {"--root", s.tempdir, "is-enabled", svc2Name}, 716 {"start", svc1Name}, 717 {"start", svc3Name}, 718 {"start", svc2Name}, 719 }, Commentf("calls: %v", sysdLog)) 720 721 // change the order 722 sorted[1], sorted[0] = sorted[0], sorted[1] 723 724 // we should observe the calls done in the same order as services 725 err = wrappers.StartServices(sorted, nil, s.perfTimings) 726 c.Assert(err, IsNil) 727 c.Assert(sysdLog, HasLen, 12, Commentf("len: %v calls: %v", len(sysdLog), sysdLog)) 728 c.Check(sysdLog[6:], DeepEquals, [][]string{ 729 {"--root", s.tempdir, "is-enabled", svc3Name}, 730 {"--root", s.tempdir, "is-enabled", svc1Name}, 731 {"--root", s.tempdir, "is-enabled", svc2Name}, 732 {"start", svc3Name}, 733 {"start", svc1Name}, 734 {"start", svc2Name}, 735 }, Commentf("calls: %v", sysdLog)) 736 } 737 738 func (s *servicesTestSuite) TestServiceAfterBefore(c *C) { 739 snapYaml := packageHello + ` 740 svc2: 741 daemon: forking 742 after: [svc1] 743 svc3: 744 daemon: forking 745 before: [svc4] 746 after: [svc2] 747 svc4: 748 daemon: forking 749 after: 750 - svc1 751 - svc2 752 - svc3 753 ` 754 info := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{Revision: snap.R(12)}) 755 756 checks := []struct { 757 file string 758 kind string 759 matches []string 760 }{{ 761 file: filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service"), 762 kind: "After", 763 matches: []string{info.Apps["svc1"].ServiceName()}, 764 }, { 765 file: filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc3.service"), 766 kind: "After", 767 matches: []string{info.Apps["svc2"].ServiceName()}, 768 }, { 769 file: filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc3.service"), 770 kind: "Before", 771 matches: []string{info.Apps["svc4"].ServiceName()}, 772 }, { 773 file: filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc4.service"), 774 kind: "After", 775 matches: []string{ 776 info.Apps["svc1"].ServiceName(), 777 info.Apps["svc2"].ServiceName(), 778 info.Apps["svc3"].ServiceName(), 779 }, 780 }} 781 782 err := wrappers.AddSnapServices(info, nil) 783 c.Assert(err, IsNil) 784 785 for _, check := range checks { 786 content, err := ioutil.ReadFile(check.file) 787 c.Assert(err, IsNil) 788 789 for _, m := range check.matches { 790 c.Check(string(content), Matches, 791 // match: 792 // ... 793 // After=other.mount some.target foo.service bar.service 794 // Before=foo.service bar.service 795 // ... 796 // but not: 797 // Foo=something After=foo.service Bar=something else 798 // or: 799 // After=foo.service 800 // bar.service 801 // or: 802 // After= foo.service bar.service 803 "(?ms).*^(?U)"+check.kind+"=.*\\s?"+regexp.QuoteMeta(m)+"\\s?[^=]*$") 804 } 805 } 806 807 } 808 809 func (s *servicesTestSuite) TestServiceWatchdog(c *C) { 810 snapYaml := packageHello + ` 811 svc2: 812 daemon: forking 813 watchdog-timeout: 12s 814 svc3: 815 daemon: forking 816 watchdog-timeout: 0s 817 svc4: 818 daemon: forking 819 ` 820 info := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{Revision: snap.R(12)}) 821 822 err := wrappers.AddSnapServices(info, nil) 823 c.Assert(err, IsNil) 824 825 content, err := ioutil.ReadFile(filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service")) 826 c.Assert(err, IsNil) 827 c.Check(strings.Contains(string(content), "\nWatchdogSec=12\n"), Equals, true) 828 829 noWatchdog := []string{ 830 filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc3.service"), 831 filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc4.service"), 832 } 833 for _, svcPath := range noWatchdog { 834 content, err := ioutil.ReadFile(svcPath) 835 c.Assert(err, IsNil) 836 c.Check(strings.Contains(string(content), "WatchdogSec="), Equals, false) 837 } 838 } 839 840 func (s *servicesTestSuite) TestStopServiceEndure(c *C) { 841 const surviveYaml = `name: survive-snap 842 version: 1.0 843 apps: 844 survivor: 845 command: bin/survivor 846 refresh-mode: endure 847 daemon: simple 848 ` 849 info := snaptest.MockSnap(c, surviveYaml, &snap.SideInfo{Revision: snap.R(1)}) 850 survivorFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.survive-snap.survivor.service") 851 852 err := wrappers.AddSnapServices(info, nil) 853 c.Assert(err, IsNil) 854 c.Check(s.sysdLog, DeepEquals, [][]string{ 855 {"--root", dirs.GlobalRootDir, "enable", filepath.Base(survivorFile)}, 856 {"daemon-reload"}, 857 }) 858 859 s.sysdLog = nil 860 err = wrappers.StopServices(info.Services(), snap.StopReasonRefresh, progress.Null, s.perfTimings) 861 c.Assert(err, IsNil) 862 c.Assert(s.sysdLog, HasLen, 0) 863 864 s.sysdLog = nil 865 err = wrappers.StopServices(info.Services(), snap.StopReasonRemove, progress.Null, s.perfTimings) 866 c.Assert(err, IsNil) 867 c.Check(s.sysdLog, DeepEquals, [][]string{ 868 {"stop", filepath.Base(survivorFile)}, 869 {"show", "--property=ActiveState", "snap.survive-snap.survivor.service"}, 870 }) 871 872 } 873 874 func (s *servicesTestSuite) TestStopServiceSigs(c *C) { 875 r := wrappers.MockKillWait(1 * time.Millisecond) 876 defer r() 877 878 survivorFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.survive-snap.srv.service") 879 for _, t := range []struct { 880 mode string 881 expectedSig string 882 expectedWho string 883 }{ 884 {mode: "sigterm", expectedSig: "TERM", expectedWho: "main"}, 885 {mode: "sigterm-all", expectedSig: "TERM", expectedWho: "all"}, 886 {mode: "sighup", expectedSig: "HUP", expectedWho: "main"}, 887 {mode: "sighup-all", expectedSig: "HUP", expectedWho: "all"}, 888 {mode: "sigusr1", expectedSig: "USR1", expectedWho: "main"}, 889 {mode: "sigusr1-all", expectedSig: "USR1", expectedWho: "all"}, 890 {mode: "sigusr2", expectedSig: "USR2", expectedWho: "main"}, 891 {mode: "sigusr2-all", expectedSig: "USR2", expectedWho: "all"}, 892 } { 893 surviveYaml := fmt.Sprintf(`name: survive-snap 894 version: 1.0 895 apps: 896 srv: 897 command: bin/survivor 898 stop-mode: %s 899 daemon: simple 900 `, t.mode) 901 info := snaptest.MockSnap(c, surviveYaml, &snap.SideInfo{Revision: snap.R(1)}) 902 903 s.sysdLog = nil 904 err := wrappers.AddSnapServices(info, nil) 905 c.Assert(err, IsNil) 906 c.Check(s.sysdLog, DeepEquals, [][]string{ 907 {"--root", dirs.GlobalRootDir, "enable", filepath.Base(survivorFile)}, 908 {"daemon-reload"}, 909 }) 910 911 s.sysdLog = nil 912 err = wrappers.StopServices(info.Services(), snap.StopReasonRefresh, progress.Null, s.perfTimings) 913 c.Assert(err, IsNil) 914 c.Check(s.sysdLog, DeepEquals, [][]string{ 915 {"stop", filepath.Base(survivorFile)}, 916 {"show", "--property=ActiveState", "snap.survive-snap.srv.service"}, 917 }, Commentf("failure in %s", t.mode)) 918 919 s.sysdLog = nil 920 err = wrappers.StopServices(info.Services(), snap.StopReasonRemove, progress.Null, s.perfTimings) 921 c.Assert(err, IsNil) 922 switch t.expectedWho { 923 case "all": 924 c.Check(s.sysdLog, DeepEquals, [][]string{ 925 {"stop", filepath.Base(survivorFile)}, 926 {"show", "--property=ActiveState", "snap.survive-snap.srv.service"}, 927 }) 928 case "main": 929 c.Check(s.sysdLog, DeepEquals, [][]string{ 930 {"stop", filepath.Base(survivorFile)}, 931 {"show", "--property=ActiveState", "snap.survive-snap.srv.service"}, 932 {"kill", filepath.Base(survivorFile), "-s", "TERM", "--kill-who=all"}, 933 {"kill", filepath.Base(survivorFile), "-s", "KILL", "--kill-who=all"}, 934 }) 935 default: 936 panic("not reached") 937 } 938 } 939 940 } 941 942 func (s *servicesTestSuite) TestStartSnapTimerEnableStart(c *C) { 943 svc1Name := "snap.hello-snap.svc1.service" 944 // svc2Name := "snap.hello-snap.svc2.service" 945 svc2Timer := "snap.hello-snap.svc2.timer" 946 947 info := snaptest.MockSnap(c, packageHello+` 948 svc2: 949 command: bin/hello 950 daemon: simple 951 timer: 10:00-12:00 952 `, &snap.SideInfo{Revision: snap.R(12)}) 953 954 // fix the apps order to make the test stable 955 apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"]} 956 err := wrappers.StartServices(apps, nil, s.perfTimings) 957 c.Assert(err, IsNil) 958 c.Assert(s.sysdLog, HasLen, 4, Commentf("len: %v calls: %v", len(s.sysdLog), s.sysdLog)) 959 c.Check(s.sysdLog, DeepEquals, [][]string{ 960 {"--root", dirs.GlobalRootDir, "is-enabled", svc1Name}, 961 {"--root", dirs.GlobalRootDir, "enable", svc2Timer}, 962 {"start", svc2Timer}, 963 {"start", svc1Name}, 964 }, Commentf("calls: %v", s.sysdLog)) 965 } 966 967 func (s *servicesTestSuite) TestStartSnapTimerCleanup(c *C) { 968 var sysdLog [][]string 969 svc1Name := "snap.hello-snap.svc1.service" 970 svc2Name := "snap.hello-snap.svc2.service" 971 svc2Timer := "snap.hello-snap.svc2.timer" 972 973 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 974 sysdLog = append(sysdLog, cmd) 975 if len(cmd) >= 2 && cmd[0] == "start" && cmd[1] == svc2Timer { 976 return nil, fmt.Errorf("failed") 977 } 978 return []byte("ActiveState=inactive\n"), nil 979 }) 980 defer r() 981 982 info := snaptest.MockSnap(c, packageHello+` 983 svc2: 984 command: bin/hello 985 daemon: simple 986 timer: 10:00-12:00 987 `, &snap.SideInfo{Revision: snap.R(12)}) 988 989 // fix the apps order to make the test stable 990 apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"]} 991 err := wrappers.StartServices(apps, nil, s.perfTimings) 992 c.Assert(err, ErrorMatches, "failed") 993 c.Assert(sysdLog, HasLen, 10, Commentf("len: %v calls: %v", len(sysdLog), sysdLog)) 994 c.Check(sysdLog, DeepEquals, [][]string{ 995 {"--root", dirs.GlobalRootDir, "is-enabled", svc1Name}, 996 {"--root", dirs.GlobalRootDir, "enable", svc2Timer}, 997 {"start", svc2Timer}, // this call fails 998 {"stop", svc2Timer}, 999 {"show", "--property=ActiveState", svc2Timer}, 1000 {"stop", svc2Name}, 1001 {"show", "--property=ActiveState", svc2Name}, 1002 {"--root", dirs.GlobalRootDir, "disable", svc2Timer}, 1003 {"stop", svc1Name}, 1004 {"show", "--property=ActiveState", svc1Name}, 1005 }, Commentf("calls: %v", sysdLog)) 1006 } 1007 1008 func (s *servicesTestSuite) TestAddRemoveSnapWithTimersAddsRemovesTimerFiles(c *C) { 1009 info := snaptest.MockSnap(c, packageHello+` 1010 svc2: 1011 command: bin/hello 1012 daemon: simple 1013 timer: 10:00-12:00 1014 `, &snap.SideInfo{Revision: snap.R(12)}) 1015 1016 err := wrappers.AddSnapServices(info, nil) 1017 c.Assert(err, IsNil) 1018 1019 app := info.Apps["svc2"] 1020 c.Assert(app.Timer, NotNil) 1021 1022 c.Check(osutil.FileExists(app.Timer.File()), Equals, true) 1023 c.Check(osutil.FileExists(app.ServiceFile()), Equals, true) 1024 1025 err = wrappers.StopServices(info.Services(), "", &progress.Null, s.perfTimings) 1026 c.Assert(err, IsNil) 1027 1028 err = wrappers.RemoveSnapServices(info, &progress.Null) 1029 c.Assert(err, IsNil) 1030 1031 c.Check(osutil.FileExists(app.Timer.File()), Equals, false) 1032 c.Check(osutil.FileExists(app.ServiceFile()), Equals, false) 1033 } 1034 1035 func (s *servicesTestSuite) TestFailedAddSnapCleansUp(c *C) { 1036 info := snaptest.MockSnap(c, packageHello+` 1037 svc2: 1038 command: bin/hello 1039 daemon: simple 1040 timer: 10:00-12:00 1041 svc3: 1042 command: bin/hello 1043 daemon: simple 1044 plugs: [network-bind] 1045 sockets: 1046 sock1: 1047 listen-stream: $SNAP_COMMON/sock1.socket 1048 socket-mode: 0666 1049 `, &snap.SideInfo{Revision: snap.R(12)}) 1050 1051 calls := 0 1052 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 1053 if len(cmd) == 1 && cmd[0] == "daemon-reload" && calls == 0 { 1054 // only fail the first systemd daemon-reload call, the 1055 // second one is at the end of cleanup 1056 calls += 1 1057 return nil, fmt.Errorf("failed") 1058 } 1059 return []byte("ActiveState=inactive\n"), nil 1060 }) 1061 defer r() 1062 1063 err := wrappers.AddSnapServices(info, &progress.Null) 1064 c.Assert(err, NotNil) 1065 1066 c.Logf("services dir: %v", dirs.SnapServicesDir) 1067 matches, err := filepath.Glob(dirs.SnapServicesDir + "/*") 1068 c.Assert(err, IsNil) 1069 c.Assert(matches, HasLen, 0, Commentf("the following autogenerated files were left behind: %v", matches)) 1070 } 1071 1072 func (s *servicesTestSuite) TestAddServicesDidReload(c *C) { 1073 const base = `name: hello-snap 1074 version: 1.10 1075 summary: hello 1076 description: Hello... 1077 apps: 1078 ` 1079 onlyServices := snaptest.MockSnap(c, base+` 1080 svc1: 1081 command: bin/hello 1082 daemon: simple 1083 `, &snap.SideInfo{Revision: snap.R(12)}) 1084 1085 onlySockets := snaptest.MockSnap(c, base+` 1086 svc1: 1087 command: bin/hello 1088 daemon: simple 1089 plugs: [network-bind] 1090 sockets: 1091 sock1: 1092 listen-stream: $SNAP_COMMON/sock1.socket 1093 socket-mode: 0666 1094 `, &snap.SideInfo{Revision: snap.R(12)}) 1095 1096 onlyTimers := snaptest.MockSnap(c, base+` 1097 svc1: 1098 command: bin/hello 1099 daemon: oneshot 1100 timer: 10:00-12:00 1101 `, &snap.SideInfo{Revision: snap.R(12)}) 1102 1103 for i, info := range []*snap.Info{onlyServices, onlySockets, onlyTimers} { 1104 s.sysdLog = nil 1105 err := wrappers.AddSnapServices(info, &progress.Null) 1106 c.Assert(err, IsNil) 1107 reloads := 0 1108 c.Logf("calls: %v", s.sysdLog) 1109 for _, call := range s.sysdLog { 1110 if strutil.ListContains(call, "daemon-reload") { 1111 reloads += 1 1112 } 1113 } 1114 c.Check(reloads >= 1, Equals, true, Commentf("test-case %v did not reload services as expected", i)) 1115 } 1116 } 1117 1118 func (s *servicesTestSuite) TestSnapServicesActivation(c *C) { 1119 const snapYaml = `name: hello-snap 1120 version: 1.10 1121 summary: hello 1122 description: Hello... 1123 apps: 1124 svc1: 1125 command: bin/hello 1126 daemon: simple 1127 plugs: [network-bind] 1128 sockets: 1129 sock1: 1130 listen-stream: $SNAP_COMMON/sock1.socket 1131 socket-mode: 0666 1132 svc2: 1133 command: bin/hello 1134 daemon: oneshot 1135 timer: 10:00-12:00 1136 svc3: 1137 command: bin/hello 1138 daemon: simple 1139 ` 1140 1141 svc3Name := "snap.hello-snap.svc3.service" 1142 1143 info := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{Revision: snap.R(12)}) 1144 1145 // fix the apps order to make the test stable 1146 err := wrappers.AddSnapServices(info, nil) 1147 c.Assert(err, IsNil) 1148 c.Assert(s.sysdLog, HasLen, 2, Commentf("len: %v calls: %v", len(s.sysdLog), s.sysdLog)) 1149 c.Check(s.sysdLog, DeepEquals, [][]string{ 1150 // only svc3 gets started during boot 1151 {"--root", dirs.GlobalRootDir, "enable", svc3Name}, 1152 {"daemon-reload"}, 1153 }, Commentf("calls: %v", s.sysdLog)) 1154 } 1155 1156 func (s *servicesTestSuite) TestServiceRestartDelay(c *C) { 1157 snapYaml := packageHello + ` 1158 svc2: 1159 daemon: forking 1160 restart-delay: 12s 1161 svc3: 1162 daemon: forking 1163 ` 1164 info := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{Revision: snap.R(12)}) 1165 1166 err := wrappers.AddSnapServices(info, nil) 1167 c.Assert(err, IsNil) 1168 1169 content, err := ioutil.ReadFile(filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service")) 1170 c.Assert(err, IsNil) 1171 c.Check(strings.Contains(string(content), "\nRestartSec=12\n"), Equals, true) 1172 1173 content, err = ioutil.ReadFile(filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc3.service")) 1174 c.Assert(err, IsNil) 1175 c.Check(strings.Contains(string(content), "RestartSec="), Equals, false) 1176 }