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