github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/wrappers/services_gen_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 "os" 25 "os/exec" 26 "strings" 27 "time" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/release" 33 "github.com/snapcore/snapd/snap" 34 "github.com/snapcore/snapd/snap/snaptest" 35 "github.com/snapcore/snapd/testutil" 36 "github.com/snapcore/snapd/timeout" 37 "github.com/snapcore/snapd/timeutil" 38 "github.com/snapcore/snapd/wrappers" 39 ) 40 41 type servicesWrapperGenSuite struct { 42 testutil.BaseTest 43 } 44 45 var _ = Suite(&servicesWrapperGenSuite{}) 46 47 const expectedServiceFmt = `[Unit] 48 # Auto-generated, DO NOT EDIT 49 Description=Service for snap application snap.app 50 Requires=%s-snap-44.mount 51 Wants=network.target 52 After=%s-snap-44.mount network.target snapd.apparmor.service 53 X-Snappy=yes 54 55 [Service] 56 EnvironmentFile=-/etc/environment 57 ExecStart=/usr/bin/snap run snap.app 58 SyslogIdentifier=snap.app 59 Restart=%s 60 WorkingDirectory=/var/snap/snap/44 61 ExecStop=/usr/bin/snap run --command=stop snap.app 62 ExecReload=/usr/bin/snap run --command=reload snap.app 63 ExecStopPost=/usr/bin/snap run --command=post-stop snap.app 64 TimeoutStopSec=10 65 Type=%s 66 %s` 67 68 const expectedInstallSection = ` 69 [Install] 70 WantedBy=multi-user.target 71 ` 72 73 const expectedUserServiceFmt = `[Unit] 74 # Auto-generated, DO NOT EDIT 75 Description=Service for snap application snap.app 76 X-Snappy=yes 77 78 [Service] 79 EnvironmentFile=-/etc/environment 80 ExecStart=/usr/bin/snap run snap.app 81 SyslogIdentifier=snap.app 82 Restart=%s 83 WorkingDirectory=/var/snap/snap/44 84 ExecStop=/usr/bin/snap run --command=stop snap.app 85 ExecReload=/usr/bin/snap run --command=reload snap.app 86 ExecStopPost=/usr/bin/snap run --command=post-stop snap.app 87 TimeoutStopSec=10 88 Type=%s 89 90 [Install] 91 WantedBy=default.target 92 ` 93 94 var ( 95 mountUnitPrefix = strings.Replace(dirs.SnapMountDir[1:], "/", "-", -1) 96 ) 97 98 var ( 99 expectedAppService = fmt.Sprintf(expectedServiceFmt, mountUnitPrefix, mountUnitPrefix, "on-failure", "simple", expectedInstallSection) 100 expectedDbusService = fmt.Sprintf(expectedServiceFmt, mountUnitPrefix, mountUnitPrefix, "on-failure", "dbus\nBusName=foo.bar.baz", "") 101 expectedOneshotService = fmt.Sprintf(expectedServiceFmt, mountUnitPrefix, mountUnitPrefix, "no", "oneshot\nRemainAfterExit=yes", expectedInstallSection) 102 expectedUserAppService = fmt.Sprintf(expectedUserServiceFmt, "on-failure", "simple") 103 ) 104 105 var ( 106 expectedServiceWrapperFmt = `[Unit] 107 # Auto-generated, DO NOT EDIT 108 Description=Service for snap application xkcd-webserver.xkcd-webserver 109 Requires=%s-xkcd\x2dwebserver-44.mount 110 Wants=network.target 111 After=%s-xkcd\x2dwebserver-44.mount network.target snapd.apparmor.service 112 X-Snappy=yes 113 114 [Service] 115 EnvironmentFile=-/etc/environment 116 ExecStart=/usr/bin/snap run xkcd-webserver 117 SyslogIdentifier=xkcd-webserver.xkcd-webserver 118 Restart=on-failure 119 WorkingDirectory=/var/snap/xkcd-webserver/44 120 ExecStop=/usr/bin/snap run --command=stop xkcd-webserver 121 ExecReload=/usr/bin/snap run --command=reload xkcd-webserver 122 ExecStopPost=/usr/bin/snap run --command=post-stop xkcd-webserver 123 TimeoutStopSec=30 124 Type=%s 125 %s` 126 expectedTypeForkingWrapper = fmt.Sprintf(expectedServiceWrapperFmt, mountUnitPrefix, mountUnitPrefix, "forking", expectedInstallSection) 127 ) 128 129 func (s *servicesWrapperGenSuite) SetUpTest(c *C) { 130 s.BaseTest.SetUpTest(c) 131 s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 132 } 133 134 func (s *servicesWrapperGenSuite) TearDownTest(c *C) { 135 s.BaseTest.TearDownTest(c) 136 } 137 138 func (s *servicesWrapperGenSuite) TestGenerateSnapServiceFileOnClassic(c *C) { 139 yamlText := ` 140 name: snap 141 version: 1.0 142 apps: 143 app: 144 command: bin/start 145 stop-command: bin/stop 146 reload-command: bin/reload 147 post-stop-command: bin/stop --post 148 stop-timeout: 10s 149 daemon: simple 150 ` 151 info, err := snap.InfoFromSnapYaml([]byte(yamlText)) 152 c.Assert(err, IsNil) 153 info.Revision = snap.R(44) 154 app := info.Apps["app"] 155 156 generatedWrapper, err := wrappers.GenerateSnapServiceFile(app, nil) 157 c.Assert(err, IsNil) 158 c.Check(string(generatedWrapper), Equals, expectedAppService) 159 } 160 161 func (s *servicesWrapperGenSuite) TestGenerateSnapServiceOnCore(c *C) { 162 defer func() { dirs.SetRootDir("/") }() 163 164 expectedAppServiceOnCore := `[Unit] 165 # Auto-generated, DO NOT EDIT 166 Description=Service for snap application foo.app 167 Requires=snap-foo-44.mount 168 Wants=network.target 169 After=snap-foo-44.mount network.target snapd.apparmor.service 170 X-Snappy=yes 171 172 [Service] 173 EnvironmentFile=-/etc/environment 174 ExecStart=/usr/bin/snap run foo.app 175 SyslogIdentifier=foo.app 176 Restart=on-failure 177 WorkingDirectory=/var/snap/foo/44 178 TimeoutStopSec=30 179 Type=simple 180 181 [Install] 182 WantedBy=multi-user.target 183 ` 184 185 yamlText := ` 186 name: foo 187 version: 1.0 188 apps: 189 app: 190 command: bin/start 191 daemon: simple 192 ` 193 info, err := snap.InfoFromSnapYaml([]byte(yamlText)) 194 c.Assert(err, IsNil) 195 info.Revision = snap.R(44) 196 app := info.Apps["app"] 197 198 // we are on core 199 restore := release.MockOnClassic(false) 200 defer restore() 201 restore = release.MockReleaseInfo(&release.OS{ID: "ubuntu-core"}) 202 defer restore() 203 dirs.SetRootDir("/") 204 205 opts := wrappers.AddSnapServicesOptions{ 206 RequireMountedSnapdSnap: false, 207 } 208 generatedWrapper, err := wrappers.GenerateSnapServiceFile(app, &opts) 209 c.Assert(err, IsNil) 210 c.Check(string(generatedWrapper), Equals, expectedAppServiceOnCore) 211 212 // now with additional dependency on tooling 213 opts = wrappers.AddSnapServicesOptions{ 214 RequireMountedSnapdSnap: true, 215 } 216 generatedWrapper, err = wrappers.GenerateSnapServiceFile(app, &opts) 217 c.Assert(err, IsNil) 218 // we gain additional Requires= & After= on usr-lib-snapd.mount 219 expectedAppServiceOnCoreWithSnapd := `[Unit] 220 # Auto-generated, DO NOT EDIT 221 Description=Service for snap application foo.app 222 Requires=snap-foo-44.mount 223 Wants=network.target 224 After=snap-foo-44.mount network.target snapd.apparmor.service 225 Wants=usr-lib-snapd.mount 226 After=usr-lib-snapd.mount 227 X-Snappy=yes 228 229 [Service] 230 EnvironmentFile=-/etc/environment 231 ExecStart=/usr/bin/snap run foo.app 232 SyslogIdentifier=foo.app 233 Restart=on-failure 234 WorkingDirectory=/var/snap/foo/44 235 TimeoutStopSec=30 236 Type=simple 237 238 [Install] 239 WantedBy=multi-user.target 240 ` 241 242 c.Check(string(generatedWrapper), Equals, expectedAppServiceOnCoreWithSnapd) 243 } 244 245 func (s *servicesWrapperGenSuite) TestGenerateSnapServiceFileWithStartTimeout(c *C) { 246 yamlText := ` 247 name: snap 248 version: 1.0 249 apps: 250 app: 251 command: bin/start 252 start-timeout: 10m 253 daemon: simple 254 ` 255 info, err := snap.InfoFromSnapYaml([]byte(yamlText)) 256 c.Assert(err, IsNil) 257 info.Revision = snap.R(44) 258 app := info.Apps["app"] 259 260 generatedWrapper, err := wrappers.GenerateSnapServiceFile(app, nil) 261 c.Assert(err, IsNil) 262 c.Check(string(generatedWrapper), testutil.Contains, "\nTimeoutStartSec=600\n") 263 } 264 265 func (s *servicesWrapperGenSuite) TestGenerateSnapServiceFileRestart(c *C) { 266 yamlTextTemplate := ` 267 name: snap 268 apps: 269 app: 270 daemon: simple 271 restart-condition: %s 272 ` 273 for name, cond := range snap.RestartMap { 274 yamlText := fmt.Sprintf(yamlTextTemplate, cond) 275 276 info, err := snap.InfoFromSnapYaml([]byte(yamlText)) 277 c.Assert(err, IsNil) 278 info.Revision = snap.R(44) 279 app := info.Apps["app"] 280 281 generatedWrapper, err := wrappers.GenerateSnapServiceFile(app, nil) 282 c.Assert(err, IsNil) 283 wrapperText := string(generatedWrapper) 284 if cond == snap.RestartNever { 285 c.Check(wrapperText, Matches, 286 `(?ms).*^Restart=no$.*`, Commentf(name)) 287 } else { 288 c.Check(wrapperText, Matches, 289 `(?ms).*^Restart=`+name+`$.*`, Commentf(name)) 290 } 291 } 292 } 293 294 func (s *servicesWrapperGenSuite) TestGenerateSnapServiceFileTypeForking(c *C) { 295 service := &snap.AppInfo{ 296 Snap: &snap.Info{ 297 SuggestedName: "xkcd-webserver", 298 Version: "0.3.4", 299 SideInfo: snap.SideInfo{Revision: snap.R(44)}, 300 }, 301 Name: "xkcd-webserver", 302 Command: "bin/foo start", 303 StopCommand: "bin/foo stop", 304 ReloadCommand: "bin/foo reload", 305 PostStopCommand: "bin/foo post-stop", 306 StopTimeout: timeout.DefaultTimeout, 307 Daemon: "forking", 308 DaemonScope: snap.SystemDaemon, 309 } 310 311 generatedWrapper, err := wrappers.GenerateSnapServiceFile(service, nil) 312 c.Assert(err, IsNil) 313 c.Assert(string(generatedWrapper), Equals, expectedTypeForkingWrapper) 314 } 315 316 func (s *servicesWrapperGenSuite) TestGenerateSnapServiceFileIllegalChars(c *C) { 317 service := &snap.AppInfo{ 318 Snap: &snap.Info{ 319 SuggestedName: "xkcd-webserver", 320 Version: "0.3.4", 321 SideInfo: snap.SideInfo{Revision: snap.R(44)}, 322 }, 323 Name: "xkcd-webserver", 324 Command: "bin/foo start\n", 325 StopCommand: "bin/foo stop", 326 ReloadCommand: "bin/foo reload", 327 PostStopCommand: "bin/foo post-stop", 328 StopTimeout: timeout.DefaultTimeout, 329 Daemon: "simple", 330 DaemonScope: snap.SystemDaemon, 331 } 332 333 _, err := wrappers.GenerateSnapServiceFile(service, nil) 334 c.Assert(err, NotNil) 335 } 336 337 func (s *servicesWrapperGenSuite) TestGenServiceFileWithBusName(c *C) { 338 yamlText := ` 339 name: snap 340 version: 1.0 341 slots: 342 dbus-slot: 343 interface: dbus 344 bus: system 345 name: org.example.Foo 346 apps: 347 app: 348 command: bin/start 349 stop-command: bin/stop 350 reload-command: bin/reload 351 post-stop-command: bin/stop --post 352 stop-timeout: 10s 353 bus-name: foo.bar.baz 354 daemon: dbus 355 activates-on: [dbus-slot] 356 ` 357 358 info, err := snap.InfoFromSnapYaml([]byte(yamlText)) 359 c.Assert(err, IsNil) 360 info.Revision = snap.R(44) 361 app := info.Apps["app"] 362 363 generatedWrapper, err := wrappers.GenerateSnapServiceFile(app, nil) 364 c.Assert(err, IsNil) 365 366 c.Assert(string(generatedWrapper), Equals, expectedDbusService) 367 } 368 369 func (s *servicesWrapperGenSuite) TestGenServiceFileWithBusNameOnly(c *C) { 370 371 yamlText := ` 372 name: snap 373 version: 1.0 374 apps: 375 app: 376 command: bin/start 377 stop-command: bin/stop 378 reload-command: bin/reload 379 post-stop-command: bin/stop --post 380 stop-timeout: 10s 381 bus-name: foo.bar.baz 382 daemon: dbus 383 ` 384 385 info, err := snap.InfoFromSnapYaml([]byte(yamlText)) 386 c.Assert(err, IsNil) 387 info.Revision = snap.R(44) 388 app := info.Apps["app"] 389 390 generatedWrapper, err := wrappers.GenerateSnapServiceFile(app, nil) 391 c.Assert(err, IsNil) 392 393 expectedDbusService := fmt.Sprintf(expectedServiceFmt, mountUnitPrefix, mountUnitPrefix, "on-failure", "dbus\nBusName=foo.bar.baz", expectedInstallSection) 394 c.Assert(string(generatedWrapper), Equals, expectedDbusService) 395 } 396 397 func (s *servicesWrapperGenSuite) TestGenServiceFileWithBusNameFromSlot(c *C) { 398 399 yamlText := ` 400 name: snap 401 version: 1.0 402 slots: 403 dbus-slot1: 404 interface: dbus 405 bus: system 406 name: org.example.Foo 407 dbus-slot2: 408 interface: dbus 409 bus: system 410 name: foo.bar.baz 411 apps: 412 app: 413 command: bin/start 414 stop-command: bin/stop 415 reload-command: bin/reload 416 post-stop-command: bin/stop --post 417 stop-timeout: 10s 418 daemon: dbus 419 activates-on: [dbus-slot1, dbus-slot2] 420 ` 421 422 info, err := snap.InfoFromSnapYaml([]byte(yamlText)) 423 c.Assert(err, IsNil) 424 info.Revision = snap.R(44) 425 app := info.Apps["app"] 426 427 generatedWrapper, err := wrappers.GenerateSnapServiceFile(app, nil) 428 c.Assert(err, IsNil) 429 430 // Bus name defaults to the name from the last slot the daemon 431 // activates on. 432 c.Assert(string(generatedWrapper), Equals, expectedDbusService) 433 } 434 435 func (s *servicesWrapperGenSuite) TestGenOneshotServiceFile(c *C) { 436 437 info := snaptest.MockInfo(c, ` 438 name: snap 439 version: 1.0 440 apps: 441 app: 442 command: bin/start 443 stop-command: bin/stop 444 reload-command: bin/reload 445 post-stop-command: bin/stop --post 446 stop-timeout: 10s 447 daemon: oneshot 448 `, &snap.SideInfo{Revision: snap.R(44)}) 449 450 app := info.Apps["app"] 451 452 generatedWrapper, err := wrappers.GenerateSnapServiceFile(app, nil) 453 c.Assert(err, IsNil) 454 455 c.Assert(string(generatedWrapper), Equals, expectedOneshotService) 456 } 457 458 func (s *servicesWrapperGenSuite) TestGenerateSnapUserServiceFile(c *C) { 459 yamlText := ` 460 name: snap 461 version: 1.0 462 apps: 463 app: 464 command: bin/start 465 stop-command: bin/stop 466 reload-command: bin/reload 467 post-stop-command: bin/stop --post 468 stop-timeout: 10s 469 daemon: simple 470 daemon-scope: user 471 ` 472 info, err := snap.InfoFromSnapYaml([]byte(yamlText)) 473 c.Assert(err, IsNil) 474 info.Revision = snap.R(44) 475 app := info.Apps["app"] 476 477 generatedWrapper, err := wrappers.GenerateSnapServiceFile(app, nil) 478 c.Assert(err, IsNil) 479 c.Check(string(generatedWrapper), Equals, expectedUserAppService) 480 } 481 482 func (s *servicesWrapperGenSuite) TestGenerateSnapServiceWithSockets(c *C) { 483 const sock1ExpectedFmt = `[Unit] 484 # Auto-generated, DO NOT EDIT 485 Description=Socket sock1 for snap application some-snap.app 486 Requires=%s-some\x2dsnap-44.mount 487 After=%s-some\x2dsnap-44.mount 488 X-Snappy=yes 489 490 [Socket] 491 Service=snap.some-snap.app.service 492 FileDescriptorName=sock1 493 ListenStream=%s/sock1.socket 494 SocketMode=0666 495 496 [Install] 497 WantedBy=sockets.target 498 ` 499 const sock2ExpectedFmt = `[Unit] 500 # Auto-generated, DO NOT EDIT 501 Description=Socket sock2 for snap application some-snap.app 502 Requires=%s-some\x2dsnap-44.mount 503 After=%s-some\x2dsnap-44.mount 504 X-Snappy=yes 505 506 [Socket] 507 Service=snap.some-snap.app.service 508 FileDescriptorName=sock2 509 ListenStream=%s/sock2.socket 510 511 [Install] 512 WantedBy=sockets.target 513 ` 514 515 si := &snap.Info{ 516 SuggestedName: "some-snap", 517 Version: "1.0", 518 SideInfo: snap.SideInfo{Revision: snap.R(44)}, 519 } 520 service := &snap.AppInfo{ 521 Snap: si, 522 Name: "app", 523 Command: "bin/foo start", 524 Daemon: "simple", 525 DaemonScope: snap.SystemDaemon, 526 Plugs: map[string]*snap.PlugInfo{"network-bind": {Interface: "network-bind"}}, 527 Sockets: map[string]*snap.SocketInfo{ 528 "sock1": { 529 Name: "sock1", 530 ListenStream: "$SNAP_DATA/sock1.socket", 531 SocketMode: 0666, 532 }, 533 "sock2": { 534 Name: "sock2", 535 ListenStream: "$SNAP_DATA/sock2.socket", 536 }, 537 }, 538 } 539 service.Sockets["sock1"].App = service 540 service.Sockets["sock2"].App = service 541 542 sock1Expected := fmt.Sprintf(sock1ExpectedFmt, mountUnitPrefix, mountUnitPrefix, si.DataDir()) 543 sock2Expected := fmt.Sprintf(sock2ExpectedFmt, mountUnitPrefix, mountUnitPrefix, si.DataDir()) 544 545 generatedWrapper, err := wrappers.GenerateSnapServiceFile(service, nil) 546 c.Assert(err, IsNil) 547 c.Assert(strings.Contains(string(generatedWrapper), "[Install]"), Equals, false) 548 c.Assert(strings.Contains(string(generatedWrapper), "WantedBy=multi-user.target"), Equals, false) 549 550 generatedSockets, err := wrappers.GenerateSnapSocketFiles(service) 551 c.Assert(err, IsNil) 552 c.Assert(generatedSockets, HasLen, 2) 553 c.Assert(generatedSockets, DeepEquals, map[string][]byte{ 554 "sock1": []byte(sock1Expected), 555 "sock2": []byte(sock2Expected), 556 }) 557 } 558 559 func (s *servicesWrapperGenSuite) TestServiceAfterBefore(c *C) { 560 const expectedServiceFmt = `[Unit] 561 # Auto-generated, DO NOT EDIT 562 Description=Service for snap application snap.app 563 Requires=%s-snap-44.mount 564 Wants=network.target 565 After=%s-snap-44.mount network.target %s snapd.apparmor.service 566 Before=%s 567 X-Snappy=yes 568 569 [Service] 570 EnvironmentFile=-/etc/environment 571 ExecStart=/usr/bin/snap run snap.app 572 SyslogIdentifier=snap.app 573 Restart=%s 574 WorkingDirectory=/var/snap/snap/44 575 TimeoutStopSec=30 576 Type=%s 577 578 [Install] 579 WantedBy=multi-user.target 580 ` 581 582 service := &snap.AppInfo{ 583 Snap: &snap.Info{ 584 SuggestedName: "snap", 585 Version: "0.3.4", 586 SideInfo: snap.SideInfo{Revision: snap.R(44)}, 587 Apps: map[string]*snap.AppInfo{ 588 "foo": { 589 Name: "foo", 590 Snap: &snap.Info{SuggestedName: "snap"}, 591 Daemon: "forking", 592 DaemonScope: snap.SystemDaemon, 593 }, 594 "bar": { 595 Name: "bar", 596 Snap: &snap.Info{SuggestedName: "snap"}, 597 Daemon: "forking", 598 DaemonScope: snap.SystemDaemon, 599 }, 600 "zed": { 601 Name: "zed", 602 Snap: &snap.Info{SuggestedName: "snap"}, 603 Daemon: "forking", 604 DaemonScope: snap.SystemDaemon, 605 }, 606 "baz": { 607 Name: "baz", 608 Snap: &snap.Info{SuggestedName: "snap"}, 609 Daemon: "forking", 610 DaemonScope: snap.SystemDaemon, 611 }, 612 }, 613 }, 614 Name: "app", 615 Command: "bin/foo start", 616 Daemon: "simple", 617 DaemonScope: snap.SystemDaemon, 618 StopTimeout: timeout.DefaultTimeout, 619 } 620 621 for _, tc := range []struct { 622 after []string 623 before []string 624 generatedAfter string 625 generatedBefore string 626 }{{ 627 after: []string{"bar", "zed"}, 628 generatedAfter: "snap.snap.bar.service snap.snap.zed.service", 629 before: []string{"foo", "baz"}, 630 generatedBefore: "snap.snap.foo.service snap.snap.baz.service", 631 }, { 632 after: []string{"bar"}, 633 generatedAfter: "snap.snap.bar.service", 634 before: []string{"foo"}, 635 generatedBefore: "snap.snap.foo.service", 636 }, 637 } { 638 c.Logf("tc: %v", tc) 639 service.After = tc.after 640 service.Before = tc.before 641 generatedWrapper, err := wrappers.GenerateSnapServiceFile(service, nil) 642 c.Assert(err, IsNil) 643 644 expectedService := fmt.Sprintf(expectedServiceFmt, mountUnitPrefix, mountUnitPrefix, 645 tc.generatedAfter, tc.generatedBefore, "on-failure", "simple") 646 c.Assert(string(generatedWrapper), Equals, expectedService) 647 } 648 } 649 650 func (s *servicesWrapperGenSuite) TestServiceTimerUnit(c *C) { 651 const expectedServiceFmt = `[Unit] 652 # Auto-generated, DO NOT EDIT 653 Description=Timer app for snap application snap.app 654 Requires=%s-snap-44.mount 655 After=%s-snap-44.mount 656 X-Snappy=yes 657 658 [Timer] 659 Unit=snap.snap.app.service 660 OnCalendar=*-*-* 10:00 661 OnCalendar=*-*-* 11:00 662 663 [Install] 664 WantedBy=timers.target 665 ` 666 667 expectedService := fmt.Sprintf(expectedServiceFmt, mountUnitPrefix, mountUnitPrefix) 668 service := &snap.AppInfo{ 669 Snap: &snap.Info{ 670 SuggestedName: "snap", 671 Version: "0.3.4", 672 SideInfo: snap.SideInfo{Revision: snap.R(44)}, 673 }, 674 Name: "app", 675 Command: "bin/foo start", 676 Daemon: "simple", 677 DaemonScope: snap.SystemDaemon, 678 StopTimeout: timeout.DefaultTimeout, 679 Timer: &snap.TimerInfo{ 680 Timer: "10:00-12:00/2", 681 }, 682 } 683 service.Timer.App = service 684 685 generatedWrapper, err := wrappers.GenerateSnapTimerFile(service) 686 c.Assert(err, IsNil) 687 688 c.Logf("timer: \n%v\n", string(generatedWrapper)) 689 c.Assert(string(generatedWrapper), Equals, expectedService) 690 } 691 692 func (s *servicesWrapperGenSuite) TestServiceTimerUnitBadTimer(c *C) { 693 service := &snap.AppInfo{ 694 Snap: &snap.Info{ 695 SuggestedName: "snap", 696 Version: "0.3.4", 697 SideInfo: snap.SideInfo{Revision: snap.R(44)}, 698 }, 699 Name: "app", 700 Command: "bin/foo start", 701 Daemon: "simple", 702 DaemonScope: snap.SystemDaemon, 703 StopTimeout: timeout.DefaultTimeout, 704 Timer: &snap.TimerInfo{ 705 Timer: "bad-timer", 706 }, 707 } 708 service.Timer.App = service 709 710 generatedWrapper, err := wrappers.GenerateSnapTimerFile(service) 711 c.Assert(err, ErrorMatches, `cannot parse "bad-timer": "bad" is not a valid weekday`) 712 c.Assert(generatedWrapper, IsNil) 713 } 714 715 func (s *servicesWrapperGenSuite) TestServiceTimerServiceUnit(c *C) { 716 const expectedServiceFmt = `[Unit] 717 # Auto-generated, DO NOT EDIT 718 Description=Service for snap application snap.app 719 Requires=%s-snap-44.mount 720 Wants=network.target 721 After=%s-snap-44.mount network.target snapd.apparmor.service 722 X-Snappy=yes 723 724 [Service] 725 EnvironmentFile=-/etc/environment 726 ExecStart=/usr/bin/snap run --timer="10:00-12:00,,mon,23:00~01:00/2" snap.app 727 SyslogIdentifier=snap.app 728 Restart=%s 729 WorkingDirectory=/var/snap/snap/44 730 TimeoutStopSec=30 731 Type=%s 732 ` 733 734 expectedService := fmt.Sprintf(expectedServiceFmt, mountUnitPrefix, mountUnitPrefix, "on-failure", "simple") 735 service := &snap.AppInfo{ 736 Snap: &snap.Info{ 737 SuggestedName: "snap", 738 Version: "0.3.4", 739 SideInfo: snap.SideInfo{Revision: snap.R(44)}, 740 }, 741 Name: "app", 742 Command: "bin/foo start", 743 Daemon: "simple", 744 DaemonScope: snap.SystemDaemon, 745 StopTimeout: timeout.DefaultTimeout, 746 Timer: &snap.TimerInfo{ 747 Timer: "10:00-12:00,,mon,23:00~01:00/2", 748 }, 749 } 750 751 generatedWrapper, err := wrappers.GenerateSnapServiceFile(service, nil) 752 c.Assert(err, IsNil) 753 754 c.Logf("service: \n%v\n", string(generatedWrapper)) 755 c.Assert(string(generatedWrapper), Equals, expectedService) 756 } 757 758 func (s *servicesWrapperGenSuite) TestTimerGenerateSchedules(c *C) { 759 systemdAnalyzePath, _ := exec.LookPath("systemd-analyze") 760 if systemdAnalyzePath != "" { 761 // systemd-analyze is in the path, but it will fail if the 762 // daemon is not running (as it happens in LP builds) and writes 763 // the following to stderr: 764 // Failed to create bus connection: No such file or directory 765 cmd := exec.Command(systemdAnalyzePath, "calendar", "12:00") 766 err := cmd.Run() 767 if err != nil { 768 // turns out it's not usable, disable extra verification 769 fmt.Fprintln(os.Stderr, `WARNING: systemd-analyze not usable, cannot validate a known schedule "12:00"`) 770 systemdAnalyzePath = "" 771 } 772 } 773 774 if systemdAnalyzePath == "" { 775 fmt.Fprintln(os.Stderr, "WARNING: generated schedules will not be validated by systemd-analyze") 776 } 777 778 for _, t := range []struct { 779 in string 780 expected []string 781 randomized bool 782 }{{ 783 in: "9:00-11:00,,20:00-22:00", 784 expected: []string{"*-*-* 09:00", "*-*-* 20:00"}, 785 }, { 786 in: "9:00-11:00/2,,20:00", 787 expected: []string{"*-*-* 09:00", "*-*-* 10:00", "*-*-* 20:00"}, 788 }, { 789 in: "9:00~11:00/2,,20:00", 790 expected: []string{`\*-\*-\* 09:[0-5][0-9]`, `\*-\*-\* 10:[0-5][0-9]`, `\*-\*-\* 20:00`}, 791 randomized: true, 792 }, { 793 in: "mon,10:00,,fri,15:00", 794 expected: []string{"Mon *-*-* 10:00", "Fri *-*-* 15:00"}, 795 }, { 796 in: "mon-fri,10:00-11:00", 797 expected: []string{"Mon,Tue,Wed,Thu,Fri *-*-* 10:00"}, 798 }, { 799 in: "fri-mon,10:00-11:00", 800 expected: []string{"Fri,Sat,Sun,Mon *-*-* 10:00"}, 801 }, { 802 in: "mon5,10:00", 803 expected: []string{"Mon *-*-22,23,24,25,26,27,28,29,30,31 10:00"}, 804 }, { 805 in: "mon2,10:00", 806 expected: []string{"Mon *-*-8,9,10,11,12,13,14 10:00"}, 807 }, { 808 in: "mon2,mon1,10:00", 809 expected: []string{"Mon *-*-8,9,10,11,12,13,14 10:00", "Mon *-*-1,2,3,4,5,6,7 10:00"}, 810 }, { 811 // (deprecated syntax, reduced to mon1-mon) 812 // NOTE: non-representable, assumes that service runner does the 813 // filtering of when to run the timer 814 in: "mon1-mon3,10:00", 815 expected: []string{"*-*-8,9,10,11,12,13,14 10:00", "*-*-1,2,3,4,5,6,7 10:00"}, 816 }, { 817 in: "mon,10:00~12:00,,fri,15:00", 818 expected: []string{`Mon \*-\*-\* 1[01]:[0-5][0-9]`, `Fri \*-\*-\* 15:00`}, 819 randomized: true, 820 }, { 821 in: "23:00~24:00/4", 822 expected: []string{`\*-\*-\* 23:[01][0-9]`, `\*-\*-\* 23:[12][0-9]`, `\*-\*-\* 23:[34][0-9]`, `*-*-* 23:[45][0-9]`}, 823 randomized: true, 824 }, { 825 in: "23:00~01:00/4", 826 expected: []string{`\*-\*-\* 23:[0-2][0-9]`, `\*-\*-\* 23:[3-5][0-9]`, `\*-\*-\* 00:[0-2][0-9]`, `\*-\*-\* 00:[3-5][0-9]`}, 827 randomized: true, 828 }, { 829 in: "23:00-01:00/4", 830 expected: []string{`*-*-* 23:00`, `*-*-* 23:30`, `*-*-* 00:00`, `*-*-* 00:30`}, 831 }, { 832 in: "24:00", 833 expected: []string{`*-*-* 00:00`}, 834 }, { 835 // NOTE: non-representable, assumes that service runner does the 836 // filtering of when to run the timer 837 in: "fri-mon1,10:00", 838 expected: []string{"*-*-22,23,24,25,26,27,28,29,30,31 10:00", "*-*-1,2,3,4,5,6,7 10:00"}, 839 }, { 840 // NOTE: non-representable, assumes that service runner does the 841 // filtering of when to run the timer 842 in: "mon5-fri,10:00", 843 expected: []string{"*-*-29,30,31 10:00", "*-*-1,2,3,4,5,6,7 10:00", "*-*-22,23,24,25,26,27,28 10:00"}, 844 }, { 845 // NOTE: non-representable, assumes that service runner does the 846 // filtering of when to run the timer 847 in: "mon4-fri,10:00", 848 expected: []string{"*-*-29,30,31 10:00", "*-*-1,2,3,4,5,6,7 10:00", "*-*-22,23,24,25,26,27,28 10:00"}, 849 }, { 850 // NOTE: non-representable, assumes that service runner does the 851 // filtering of when to run the timer 852 in: "mon-fri2,10:00", 853 expected: []string{"*-*-1,2,3,4,5,6,7 10:00", "*-*-8,9,10,11,12,13,14 10:00"}, 854 }, { 855 // NOTE: non-representable, assumes that service runner does the 856 // filtering of when to run the timer 857 in: "mon-fri5,10:00", 858 expected: []string{"*-*-29,30,31 10:00", "*-*-22,23,24,25,26,27,28 10:00"}, 859 }, { 860 // NOTE: non-representable, assumes that service runner does the 861 // filtering of when to run the timer 862 in: "mon1-mon,10:00", 863 expected: []string{"*-*-8,9,10,11,12,13,14 10:00", "*-*-1,2,3,4,5,6,7 10:00"}, 864 }, { 865 in: "mon", 866 expected: []string{"Mon *-*-*"}, 867 }, { 868 in: "mon,fri", 869 expected: []string{"Mon *-*-*", "Fri *-*-*"}, 870 }, { 871 in: "mon2,mon1", 872 expected: []string{"Mon *-*-8,9,10,11,12,13,14", "Mon *-*-1,2,3,4,5,6,7"}, 873 }} { 874 c.Logf("trying %+v", t) 875 876 schedule, err := timeutil.ParseSchedule(t.in) 877 c.Check(err, IsNil) 878 879 timer := wrappers.GenerateOnCalendarSchedules(schedule) 880 c.Check(timer, Not(IsNil)) 881 if !t.randomized { 882 c.Check(timer, DeepEquals, t.expected) 883 } else { 884 c.Assert(timer, HasLen, len(t.expected)) 885 for i := range timer { 886 c.Check(timer[i], Matches, t.expected[i]) 887 } 888 } 889 890 if systemdAnalyzePath != "" { 891 cmd := exec.Command(systemdAnalyzePath, append([]string{"calendar"}, timer...)...) 892 out, err := cmd.CombinedOutput() 893 c.Check(err, IsNil, Commentf("systemd-analyze failed with output:\n%s", string(out))) 894 } 895 } 896 } 897 898 func (s *servicesWrapperGenSuite) TestKillModeSig(c *C) { 899 for _, rm := range []string{"sigterm", "sighup", "sigusr1", "sigusr2"} { 900 service := &snap.AppInfo{ 901 Snap: &snap.Info{ 902 SuggestedName: "snap", 903 Version: "0.3.4", 904 SideInfo: snap.SideInfo{Revision: snap.R(44)}, 905 }, 906 Name: "app", 907 Command: "bin/foo start", 908 Daemon: "simple", 909 DaemonScope: snap.SystemDaemon, 910 StopMode: snap.StopModeType(rm), 911 } 912 913 generatedWrapper, err := wrappers.GenerateSnapServiceFile(service, nil) 914 c.Assert(err, IsNil) 915 916 c.Check(string(generatedWrapper), Equals, fmt.Sprintf(`[Unit] 917 # Auto-generated, DO NOT EDIT 918 Description=Service for snap application snap.app 919 Requires=%s-snap-44.mount 920 Wants=network.target 921 After=%s-snap-44.mount network.target snapd.apparmor.service 922 X-Snappy=yes 923 924 [Service] 925 EnvironmentFile=-/etc/environment 926 ExecStart=/usr/bin/snap run snap.app 927 SyslogIdentifier=snap.app 928 Restart=on-failure 929 WorkingDirectory=/var/snap/snap/44 930 TimeoutStopSec=30 931 Type=simple 932 KillMode=process 933 KillSignal=%s 934 935 [Install] 936 WantedBy=multi-user.target 937 `, mountUnitPrefix, mountUnitPrefix, strings.ToUpper(rm))) 938 } 939 } 940 941 func (s *servicesWrapperGenSuite) TestRestartDelay(c *C) { 942 service := &snap.AppInfo{ 943 Snap: &snap.Info{ 944 SuggestedName: "snap", 945 Version: "0.3.4", 946 SideInfo: snap.SideInfo{Revision: snap.R(44)}, 947 }, 948 Name: "app", 949 Command: "bin/foo start", 950 Daemon: "simple", 951 DaemonScope: snap.SystemDaemon, 952 RestartDelay: timeout.Timeout(20 * time.Second), 953 } 954 955 generatedWrapper, err := wrappers.GenerateSnapServiceFile(service, nil) 956 c.Assert(err, IsNil) 957 958 c.Check(string(generatedWrapper), Equals, fmt.Sprintf(`[Unit] 959 # Auto-generated, DO NOT EDIT 960 Description=Service for snap application snap.app 961 Requires=%s-snap-44.mount 962 Wants=network.target 963 After=%s-snap-44.mount network.target snapd.apparmor.service 964 X-Snappy=yes 965 966 [Service] 967 EnvironmentFile=-/etc/environment 968 ExecStart=/usr/bin/snap run snap.app 969 SyslogIdentifier=snap.app 970 Restart=on-failure 971 RestartSec=20 972 WorkingDirectory=/var/snap/snap/44 973 TimeoutStopSec=30 974 Type=simple 975 976 [Install] 977 WantedBy=multi-user.target 978 `, mountUnitPrefix, mountUnitPrefix)) 979 } 980 981 func (s *servicesWrapperGenSuite) TestVitalityScore(c *C) { 982 service := &snap.AppInfo{ 983 Snap: &snap.Info{ 984 SuggestedName: "snap", 985 Version: "0.3.4", 986 SideInfo: snap.SideInfo{Revision: snap.R(44)}, 987 }, 988 Name: "app", 989 Command: "bin/foo start", 990 Daemon: "simple", 991 DaemonScope: snap.SystemDaemon, 992 RestartDelay: timeout.Timeout(20 * time.Second), 993 } 994 995 opts := &wrappers.AddSnapServicesOptions{VitalityRank: 1} 996 generatedWrapper, err := wrappers.GenerateSnapServiceFile(service, opts) 997 c.Assert(err, IsNil) 998 999 c.Check(string(generatedWrapper), Equals, fmt.Sprintf(`[Unit] 1000 # Auto-generated, DO NOT EDIT 1001 Description=Service for snap application snap.app 1002 Requires=%s-snap-44.mount 1003 Wants=network.target 1004 After=%s-snap-44.mount network.target snapd.apparmor.service 1005 X-Snappy=yes 1006 1007 [Service] 1008 EnvironmentFile=-/etc/environment 1009 ExecStart=/usr/bin/snap run snap.app 1010 SyslogIdentifier=snap.app 1011 Restart=on-failure 1012 RestartSec=20 1013 WorkingDirectory=/var/snap/snap/44 1014 TimeoutStopSec=30 1015 Type=simple 1016 OOMScoreAdjust=-899 1017 1018 [Install] 1019 WantedBy=multi-user.target 1020 `, mountUnitPrefix, mountUnitPrefix)) 1021 }