gitee.com/mysnapcore/mysnapd@v0.1.0/wrappers/services_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2021 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package wrappers_test 21 22 import ( 23 "errors" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "regexp" 29 "runtime" 30 "sort" 31 "strings" 32 "time" 33 34 . "gopkg.in/check.v1" 35 36 "gitee.com/mysnapcore/mysnapd/dirs" 37 "gitee.com/mysnapcore/mysnapd/gadget/quantity" 38 39 // imported to ensure actual interfaces are defined (in production this is guaranteed by ifacestate) 40 _ "gitee.com/mysnapcore/mysnapd/interfaces/builtin" 41 "gitee.com/mysnapcore/mysnapd/osutil" 42 "gitee.com/mysnapcore/mysnapd/progress" 43 "gitee.com/mysnapcore/mysnapd/snap" 44 "gitee.com/mysnapcore/mysnapd/snap/quota" 45 "gitee.com/mysnapcore/mysnapd/snap/snaptest" 46 "gitee.com/mysnapcore/mysnapd/strutil" 47 "gitee.com/mysnapcore/mysnapd/systemd" 48 "gitee.com/mysnapcore/mysnapd/systemd/systemdtest" 49 "gitee.com/mysnapcore/mysnapd/testutil" 50 "gitee.com/mysnapcore/mysnapd/timings" 51 "gitee.com/mysnapcore/mysnapd/usersession/agent" 52 "gitee.com/mysnapcore/mysnapd/wrappers" 53 ) 54 55 type servicesTestSuite struct { 56 testutil.DBusTest 57 58 tempdir string 59 60 sysdLog [][]string 61 62 systemctlRestorer, delaysRestorer func() 63 64 perfTimings timings.Measurer 65 66 agent *agent.SessionAgent 67 } 68 69 var _ = Suite(&servicesTestSuite{}) 70 71 func (s *servicesTestSuite) SetUpTest(c *C) { 72 s.DBusTest.SetUpTest(c) 73 s.tempdir = c.MkDir() 74 s.sysdLog = nil 75 dirs.SetRootDir(s.tempdir) 76 77 s.systemctlRestorer = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 78 s.sysdLog = append(s.sysdLog, cmd) 79 return []byte("ActiveState=inactive\n"), nil 80 }) 81 s.delaysRestorer = systemd.MockStopDelays(2*time.Millisecond, 4*time.Millisecond) 82 s.perfTimings = timings.New(nil) 83 84 xdgRuntimeDir := fmt.Sprintf("%s/%d", dirs.XdgRuntimeDirBase, os.Getuid()) 85 err := os.MkdirAll(xdgRuntimeDir, 0700) 86 c.Assert(err, IsNil) 87 s.agent, err = agent.New() 88 c.Assert(err, IsNil) 89 s.agent.Start() 90 } 91 92 func (s *servicesTestSuite) TearDownTest(c *C) { 93 if s.agent != nil { 94 err := s.agent.Stop() 95 c.Check(err, IsNil) 96 } 97 s.systemctlRestorer() 98 s.delaysRestorer() 99 dirs.SetRootDir("") 100 s.DBusTest.TearDownTest(c) 101 } 102 103 func (s *servicesTestSuite) TestAddSnapServicesAndRemove(c *C) { 104 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 105 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 106 107 err := wrappers.AddSnapServices(info, nil, progress.Null) 108 c.Assert(err, IsNil) 109 c.Check(s.sysdLog, DeepEquals, [][]string{ 110 {"daemon-reload"}, 111 }) 112 113 s.sysdLog = nil 114 115 flags := &wrappers.StartServicesFlags{Enable: true} 116 err = wrappers.StartServices(info.Services(), nil, flags, progress.Null, s.perfTimings) 117 c.Assert(err, IsNil) 118 c.Check(s.sysdLog, DeepEquals, [][]string{ 119 {"--no-reload", "enable", filepath.Base(svcFile)}, 120 {"daemon-reload"}, 121 {"start", filepath.Base(svcFile)}, 122 }) 123 124 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 125 c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(`[Unit] 126 # Auto-generated, DO NOT EDIT 127 Description=Service for snap application hello-snap.svc1 128 Requires=%[1]s 129 Wants=network.target 130 After=%[1]s network.target snapd.apparmor.service 131 X-Snappy=yes 132 133 [Service] 134 EnvironmentFile=-/etc/environment 135 ExecStart=/usr/bin/snap run hello-snap.svc1 136 SyslogIdentifier=hello-snap.svc1 137 Restart=on-failure 138 WorkingDirectory=%[2]s/var/snap/hello-snap/12 139 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 140 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 141 TimeoutStopSec=30 142 Type=forking 143 144 [Install] 145 WantedBy=multi-user.target 146 `, 147 systemd.EscapeUnitNamePath(dir), 148 dirs.GlobalRootDir, 149 )) 150 151 s.sysdLog = nil 152 err = wrappers.StopServices(info.Services(), nil, "", progress.Null, s.perfTimings) 153 c.Assert(err, IsNil) 154 c.Assert(s.sysdLog, HasLen, 2) 155 c.Check(s.sysdLog, DeepEquals, [][]string{ 156 {"stop", filepath.Base(svcFile)}, 157 {"show", "--property=ActiveState", "snap.hello-snap.svc1.service"}, 158 }) 159 160 s.sysdLog = nil 161 err = wrappers.RemoveSnapServices(info, progress.Null) 162 c.Assert(err, IsNil) 163 c.Check(svcFile, testutil.FileAbsent) 164 c.Assert(s.sysdLog, DeepEquals, [][]string{ 165 {"--no-reload", "disable", filepath.Base(svcFile)}, 166 {"daemon-reload"}, 167 }) 168 } 169 func (s *servicesTestSuite) TestEnsureSnapServicesAdds(c *C) { 170 // map unit -> new 171 seen := make(map[string]bool) 172 cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) { 173 seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = old == "" 174 } 175 176 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 177 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 178 179 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 180 info: nil, 181 } 182 183 err := wrappers.EnsureSnapServices(m, nil, cb, progress.Null) 184 c.Assert(err, IsNil) 185 c.Check(s.sysdLog, DeepEquals, [][]string{ 186 {"daemon-reload"}, 187 }) 188 c.Check(seen, DeepEquals, map[string]bool{ 189 "hello-snap:svc1:service:svc1": true, 190 }) 191 192 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 193 c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(`[Unit] 194 # Auto-generated, DO NOT EDIT 195 Description=Service for snap application hello-snap.svc1 196 Requires=%[1]s 197 Wants=network.target 198 After=%[1]s network.target snapd.apparmor.service 199 X-Snappy=yes 200 201 [Service] 202 EnvironmentFile=-/etc/environment 203 ExecStart=/usr/bin/snap run hello-snap.svc1 204 SyslogIdentifier=hello-snap.svc1 205 Restart=on-failure 206 WorkingDirectory=%[2]s/var/snap/hello-snap/12 207 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 208 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 209 TimeoutStopSec=30 210 Type=forking 211 212 [Install] 213 WantedBy=multi-user.target 214 `, 215 systemd.EscapeUnitNamePath(dir), 216 dirs.GlobalRootDir, 217 )) 218 } 219 220 func (s *servicesTestSuite) TestEnsureSnapServicesWithQuotas(c *C) { 221 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 222 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 223 224 // set up arbitrary quotas for the group to test they get written correctly to the slice 225 resourceLimits := quota.NewResourcesBuilder(). 226 WithMemoryLimit(quantity.SizeGiB). 227 WithCPUCount(2). 228 WithCPUPercentage(50). 229 WithCPUSet([]int{0, 1}). 230 WithThreadLimit(32). 231 Build() 232 grp, err := quota.NewGroup("foogroup", resourceLimits) 233 c.Assert(err, IsNil) 234 235 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 236 info: {QuotaGroup: grp}, 237 } 238 239 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 240 svcContent := fmt.Sprintf(`[Unit] 241 # Auto-generated, DO NOT EDIT 242 Description=Service for snap application hello-snap.svc1 243 Requires=%[1]s 244 Wants=network.target 245 After=%[1]s network.target snapd.apparmor.service 246 X-Snappy=yes 247 248 [Service] 249 EnvironmentFile=-/etc/environment 250 ExecStart=/usr/bin/snap run hello-snap.svc1 251 SyslogIdentifier=hello-snap.svc1 252 Restart=on-failure 253 WorkingDirectory=%[2]s/var/snap/hello-snap/12 254 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 255 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 256 TimeoutStopSec=30 257 Type=forking 258 Slice=snap.foogroup.slice 259 260 [Install] 261 WantedBy=multi-user.target 262 `, 263 systemd.EscapeUnitNamePath(dir), 264 dirs.GlobalRootDir, 265 ) 266 267 sliceTempl := `[Unit] 268 Description=Slice for snap quota group %s 269 Before=slices.target 270 X-Snappy=yes 271 272 [Slice] 273 # Always enable cpu accounting, so the following cpu quota options have an effect 274 CPUAccounting=true 275 CPUQuota=%[2]d%% 276 AllowedCPUs=%[3]s 277 278 # Always enable memory accounting otherwise the MemoryMax setting does nothing. 279 MemoryAccounting=true 280 MemoryMax=%[4]d 281 # for compatibility with older versions of systemd 282 MemoryLimit=%[4]d 283 284 # Always enable task accounting in order to be able to count the processes/ 285 # threads, etc for a slice 286 TasksAccounting=true 287 TasksMax=%[5]d 288 ` 289 290 allowedCpusValue := strutil.IntsToCommaSeparated(resourceLimits.CPUSet.CPUs) 291 sliceContent := fmt.Sprintf(sliceTempl, grp.Name, 292 resourceLimits.CPU.Count*resourceLimits.CPU.Percentage, 293 allowedCpusValue, 294 resourceLimits.Memory.Limit, 295 resourceLimits.Threads.Limit) 296 297 exp := []changesObservation{ 298 { 299 snapName: "hello-snap", 300 unitType: "service", 301 name: "svc1", 302 old: "", 303 new: svcContent, 304 }, 305 { 306 grp: grp, 307 unitType: "slice", 308 new: sliceContent, 309 old: "", 310 name: "foogroup", 311 }, 312 } 313 r, observe := expChangeObserver(c, exp) 314 defer r() 315 316 err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null) 317 c.Assert(err, IsNil) 318 c.Check(s.sysdLog, DeepEquals, [][]string{ 319 {"daemon-reload"}, 320 }) 321 322 c.Assert(svcFile, testutil.FileEquals, svcContent) 323 } 324 325 func (s *servicesTestSuite) TestEnsureSnapServicesWithZeroCpuCountQuotas(c *C) { 326 // Kind of a special case, if the cpu count is zero it needs to automatically scale 327 // at the moment of writing the service file to the current number of cpu cores 328 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 329 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 330 331 // set up arbitrary quotas for the group to test they get written correctly to the slice 332 resourceLimits := quota.NewResourcesBuilder(). 333 WithCPUPercentage(50). 334 Build() 335 grp, err := quota.NewGroup("foogroup", resourceLimits) 336 c.Assert(err, IsNil) 337 338 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 339 info: {QuotaGroup: grp}, 340 } 341 342 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 343 svcContent := fmt.Sprintf(`[Unit] 344 # Auto-generated, DO NOT EDIT 345 Description=Service for snap application hello-snap.svc1 346 Requires=%[1]s 347 Wants=network.target 348 After=%[1]s network.target snapd.apparmor.service 349 X-Snappy=yes 350 351 [Service] 352 EnvironmentFile=-/etc/environment 353 ExecStart=/usr/bin/snap run hello-snap.svc1 354 SyslogIdentifier=hello-snap.svc1 355 Restart=on-failure 356 WorkingDirectory=%[2]s/var/snap/hello-snap/12 357 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 358 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 359 TimeoutStopSec=30 360 Type=forking 361 Slice=snap.foogroup.slice 362 363 [Install] 364 WantedBy=multi-user.target 365 `, 366 systemd.EscapeUnitNamePath(dir), 367 dirs.GlobalRootDir, 368 ) 369 370 sliceTempl := `[Unit] 371 Description=Slice for snap quota group %s 372 Before=slices.target 373 X-Snappy=yes 374 375 [Slice] 376 # Always enable cpu accounting, so the following cpu quota options have an effect 377 CPUAccounting=true 378 CPUQuota=%[2]d%% 379 380 # Always enable memory accounting otherwise the MemoryMax setting does nothing. 381 MemoryAccounting=true 382 # Always enable task accounting in order to be able to count the processes/ 383 # threads, etc for a slice 384 TasksAccounting=true 385 ` 386 // The reason we are not mocking the cpu count here is because we are relying 387 // on the real code to produce the slice file content, and it will always use 388 // the current number of cores, so we also use the current number of cores of 389 // the test runner here. 390 sliceContent := fmt.Sprintf(sliceTempl, grp.Name, 391 runtime.NumCPU()*resourceLimits.CPU.Percentage) 392 393 exp := []changesObservation{ 394 { 395 snapName: "hello-snap", 396 unitType: "service", 397 name: "svc1", 398 old: "", 399 new: svcContent, 400 }, 401 { 402 grp: grp, 403 unitType: "slice", 404 new: sliceContent, 405 old: "", 406 name: "foogroup", 407 }, 408 } 409 r, observe := expChangeObserver(c, exp) 410 defer r() 411 412 err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null) 413 c.Assert(err, IsNil) 414 c.Check(s.sysdLog, DeepEquals, [][]string{ 415 {"daemon-reload"}, 416 }) 417 418 c.Assert(svcFile, testutil.FileEquals, svcContent) 419 } 420 421 func (s *servicesTestSuite) TestEnsureSnapServicesWithZeroCpuCountAndCpuSetQuotas(c *C) { 422 // Another special case, if the cpu count is zero it needs to automatically scale as the 423 // previous test, but only up the maximum allowed provided in the cpu-set. So in this test 424 // we provide only 1 allowed CPU, which means that the percentage that will be written is 50% 425 // and not 200% or how many cores that runs this test! 426 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 427 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 428 429 // set up arbitrary quotas for the group to test they get written correctly to the slice 430 resourceLimits := quota.NewResourcesBuilder(). 431 WithCPUPercentage(50). 432 WithCPUSet([]int{0}). 433 Build() 434 grp, err := quota.NewGroup("foogroup", resourceLimits) 435 c.Assert(err, IsNil) 436 437 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 438 info: {QuotaGroup: grp}, 439 } 440 441 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 442 svcContent := fmt.Sprintf(`[Unit] 443 # Auto-generated, DO NOT EDIT 444 Description=Service for snap application hello-snap.svc1 445 Requires=%[1]s 446 Wants=network.target 447 After=%[1]s network.target snapd.apparmor.service 448 X-Snappy=yes 449 450 [Service] 451 EnvironmentFile=-/etc/environment 452 ExecStart=/usr/bin/snap run hello-snap.svc1 453 SyslogIdentifier=hello-snap.svc1 454 Restart=on-failure 455 WorkingDirectory=%[2]s/var/snap/hello-snap/12 456 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 457 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 458 TimeoutStopSec=30 459 Type=forking 460 Slice=snap.foogroup.slice 461 462 [Install] 463 WantedBy=multi-user.target 464 `, 465 systemd.EscapeUnitNamePath(dir), 466 dirs.GlobalRootDir, 467 ) 468 469 sliceTempl := `[Unit] 470 Description=Slice for snap quota group %s 471 Before=slices.target 472 X-Snappy=yes 473 474 [Slice] 475 # Always enable cpu accounting, so the following cpu quota options have an effect 476 CPUAccounting=true 477 CPUQuota=50%% 478 AllowedCPUs=0 479 480 # Always enable memory accounting otherwise the MemoryMax setting does nothing. 481 MemoryAccounting=true 482 # Always enable task accounting in order to be able to count the processes/ 483 # threads, etc for a slice 484 TasksAccounting=true 485 ` 486 487 sliceContent := fmt.Sprintf(sliceTempl, grp.Name) 488 489 exp := []changesObservation{ 490 { 491 snapName: "hello-snap", 492 unitType: "service", 493 name: "svc1", 494 old: "", 495 new: svcContent, 496 }, 497 { 498 grp: grp, 499 unitType: "slice", 500 new: sliceContent, 501 old: "", 502 name: "foogroup", 503 }, 504 } 505 r, observe := expChangeObserver(c, exp) 506 defer r() 507 508 err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null) 509 c.Assert(err, IsNil) 510 c.Check(s.sysdLog, DeepEquals, [][]string{ 511 {"daemon-reload"}, 512 }) 513 514 c.Assert(svcFile, testutil.FileEquals, svcContent) 515 } 516 517 func (s *servicesTestSuite) TestEnsureSnapServicesWithJournalNamespaceOnly(c *C) { 518 // Ensure that the journald.conf file is correctly written 519 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 520 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 521 522 // set up arbitrary quotas for the group to test they get written correctly to the slice 523 resourceLimits := quota.NewResourcesBuilder(). 524 WithJournalNamespace(). 525 Build() 526 grp, err := quota.NewGroup("foogroup", resourceLimits) 527 c.Assert(err, IsNil) 528 529 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 530 info: {QuotaGroup: grp}, 531 } 532 533 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 534 svcContent := fmt.Sprintf(`[Unit] 535 # Auto-generated, DO NOT EDIT 536 Description=Service for snap application hello-snap.svc1 537 Requires=%[1]s 538 Wants=network.target 539 After=%[1]s network.target snapd.apparmor.service 540 X-Snappy=yes 541 542 [Service] 543 EnvironmentFile=-/etc/environment 544 ExecStart=/usr/bin/snap run hello-snap.svc1 545 SyslogIdentifier=hello-snap.svc1 546 Restart=on-failure 547 WorkingDirectory=%[2]s/var/snap/hello-snap/12 548 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 549 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 550 TimeoutStopSec=30 551 Type=forking 552 Slice=snap.foogroup.slice 553 LogNamespace=snap-foogroup 554 555 [Install] 556 WantedBy=multi-user.target 557 `, 558 systemd.EscapeUnitNamePath(dir), 559 dirs.GlobalRootDir, 560 ) 561 jconfTempl := `# Journald configuration for snap quota group %s 562 [Journal] 563 Storage=auto 564 ` 565 566 jSvcContent := `[Service] 567 LogsDirectory= 568 ` 569 570 sliceTempl := `[Unit] 571 Description=Slice for snap quota group %s 572 Before=slices.target 573 X-Snappy=yes 574 575 [Slice] 576 # Always enable cpu accounting, so the following cpu quota options have an effect 577 CPUAccounting=true 578 579 # Always enable memory accounting otherwise the MemoryMax setting does nothing. 580 MemoryAccounting=true 581 # Always enable task accounting in order to be able to count the processes/ 582 # threads, etc for a slice 583 TasksAccounting=true 584 ` 585 586 jconfContent := fmt.Sprintf(jconfTempl, grp.Name) 587 sliceContent := fmt.Sprintf(sliceTempl, grp.Name) 588 589 exp := []changesObservation{ 590 { 591 grp: grp, 592 unitType: "journald", 593 new: jconfContent, 594 old: "", 595 name: "foogroup", 596 }, 597 { 598 grp: grp, 599 unitType: "service", 600 new: jSvcContent, 601 old: "", 602 name: "foogroup", 603 }, 604 { 605 snapName: "hello-snap", 606 unitType: "service", 607 name: "svc1", 608 old: "", 609 new: svcContent, 610 }, 611 { 612 grp: grp, 613 unitType: "slice", 614 new: sliceContent, 615 old: "", 616 name: "foogroup", 617 }, 618 } 619 r, observe := expChangeObserver(c, exp) 620 defer r() 621 622 err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null) 623 c.Assert(err, IsNil) 624 c.Check(s.sysdLog, DeepEquals, [][]string{ 625 {"daemon-reload"}, 626 }) 627 628 c.Assert(svcFile, testutil.FileEquals, svcContent) 629 } 630 631 func (s *servicesTestSuite) TestEnsureSnapServicesWithJournalQuotas(c *C) { 632 // Ensure that the journald.conf file is correctly written 633 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 634 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 635 636 // set up arbitrary quotas for the group to test they get written correctly to the slice 637 resourceLimits := quota.NewResourcesBuilder(). 638 WithJournalSize(10*quantity.SizeMiB). 639 WithJournalRate(15, 5*time.Second). 640 Build() 641 grp, err := quota.NewGroup("foogroup", resourceLimits) 642 c.Assert(err, IsNil) 643 644 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 645 info: {QuotaGroup: grp}, 646 } 647 648 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 649 svcContent := fmt.Sprintf(`[Unit] 650 # Auto-generated, DO NOT EDIT 651 Description=Service for snap application hello-snap.svc1 652 Requires=%[1]s 653 Wants=network.target 654 After=%[1]s network.target snapd.apparmor.service 655 X-Snappy=yes 656 657 [Service] 658 EnvironmentFile=-/etc/environment 659 ExecStart=/usr/bin/snap run hello-snap.svc1 660 SyslogIdentifier=hello-snap.svc1 661 Restart=on-failure 662 WorkingDirectory=%[2]s/var/snap/hello-snap/12 663 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 664 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 665 TimeoutStopSec=30 666 Type=forking 667 Slice=snap.foogroup.slice 668 LogNamespace=snap-foogroup 669 670 [Install] 671 WantedBy=multi-user.target 672 `, 673 systemd.EscapeUnitNamePath(dir), 674 dirs.GlobalRootDir, 675 ) 676 jconfTempl := `# Journald configuration for snap quota group %s 677 [Journal] 678 Storage=auto 679 SystemMaxUse=10485760 680 RuntimeMaxUse=10485760 681 RateLimitIntervalSec=5000000us 682 RateLimitBurst=15 683 ` 684 685 jSvcContent := `[Service] 686 LogsDirectory= 687 ` 688 689 sliceTempl := `[Unit] 690 Description=Slice for snap quota group %s 691 Before=slices.target 692 X-Snappy=yes 693 694 [Slice] 695 # Always enable cpu accounting, so the following cpu quota options have an effect 696 CPUAccounting=true 697 698 # Always enable memory accounting otherwise the MemoryMax setting does nothing. 699 MemoryAccounting=true 700 # Always enable task accounting in order to be able to count the processes/ 701 # threads, etc for a slice 702 TasksAccounting=true 703 ` 704 705 jconfContent := fmt.Sprintf(jconfTempl, grp.Name) 706 sliceContent := fmt.Sprintf(sliceTempl, grp.Name) 707 708 exp := []changesObservation{ 709 { 710 grp: grp, 711 unitType: "journald", 712 new: jconfContent, 713 old: "", 714 name: "foogroup", 715 }, 716 { 717 grp: grp, 718 unitType: "service", 719 new: jSvcContent, 720 old: "", 721 name: "foogroup", 722 }, 723 { 724 snapName: "hello-snap", 725 unitType: "service", 726 name: "svc1", 727 old: "", 728 new: svcContent, 729 }, 730 { 731 grp: grp, 732 unitType: "slice", 733 new: sliceContent, 734 old: "", 735 name: "foogroup", 736 }, 737 } 738 r, observe := expChangeObserver(c, exp) 739 defer r() 740 741 err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null) 742 c.Assert(err, IsNil) 743 c.Check(s.sysdLog, DeepEquals, [][]string{ 744 {"daemon-reload"}, 745 }) 746 747 c.Assert(svcFile, testutil.FileEquals, svcContent) 748 } 749 750 func (s *servicesTestSuite) TestEnsureSnapServicesWithJournalQuotaRateAsZero(c *C) { 751 // Ensure that the journald.conf file is correctly written 752 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 753 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 754 755 // set up arbitrary quotas for the group to test they get written correctly to the slice 756 resourceLimits := quota.NewResourcesBuilder(). 757 WithJournalRate(0, 0). 758 Build() 759 grp, err := quota.NewGroup("foogroup", resourceLimits) 760 c.Assert(err, IsNil) 761 762 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 763 info: {QuotaGroup: grp}, 764 } 765 766 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 767 svcContent := fmt.Sprintf(`[Unit] 768 # Auto-generated, DO NOT EDIT 769 Description=Service for snap application hello-snap.svc1 770 Requires=%[1]s 771 Wants=network.target 772 After=%[1]s network.target snapd.apparmor.service 773 X-Snappy=yes 774 775 [Service] 776 EnvironmentFile=-/etc/environment 777 ExecStart=/usr/bin/snap run hello-snap.svc1 778 SyslogIdentifier=hello-snap.svc1 779 Restart=on-failure 780 WorkingDirectory=%[2]s/var/snap/hello-snap/12 781 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 782 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 783 TimeoutStopSec=30 784 Type=forking 785 Slice=snap.foogroup.slice 786 LogNamespace=snap-foogroup 787 788 [Install] 789 WantedBy=multi-user.target 790 `, 791 systemd.EscapeUnitNamePath(dir), 792 dirs.GlobalRootDir, 793 ) 794 jconfTempl := `# Journald configuration for snap quota group %s 795 [Journal] 796 Storage=auto 797 RateLimitIntervalSec=0us 798 RateLimitBurst=0 799 ` 800 801 jSvcContent := `[Service] 802 LogsDirectory= 803 ` 804 805 sliceTempl := `[Unit] 806 Description=Slice for snap quota group %s 807 Before=slices.target 808 X-Snappy=yes 809 810 [Slice] 811 # Always enable cpu accounting, so the following cpu quota options have an effect 812 CPUAccounting=true 813 814 # Always enable memory accounting otherwise the MemoryMax setting does nothing. 815 MemoryAccounting=true 816 # Always enable task accounting in order to be able to count the processes/ 817 # threads, etc for a slice 818 TasksAccounting=true 819 ` 820 821 jconfContent := fmt.Sprintf(jconfTempl, grp.Name) 822 sliceContent := fmt.Sprintf(sliceTempl, grp.Name) 823 824 exp := []changesObservation{ 825 { 826 grp: grp, 827 unitType: "journald", 828 new: jconfContent, 829 old: "", 830 name: "foogroup", 831 }, 832 { 833 grp: grp, 834 unitType: "service", 835 new: jSvcContent, 836 old: "", 837 name: "foogroup", 838 }, 839 { 840 snapName: "hello-snap", 841 unitType: "service", 842 name: "svc1", 843 old: "", 844 new: svcContent, 845 }, 846 { 847 grp: grp, 848 unitType: "slice", 849 new: sliceContent, 850 old: "", 851 name: "foogroup", 852 }, 853 } 854 r, observe := expChangeObserver(c, exp) 855 defer r() 856 857 err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null) 858 c.Assert(err, IsNil) 859 c.Check(s.sysdLog, DeepEquals, [][]string{ 860 {"daemon-reload"}, 861 }) 862 863 c.Assert(svcFile, testutil.FileEquals, svcContent) 864 } 865 866 type changesObservation struct { 867 snapName string 868 grp *quota.Group 869 unitType string 870 name string 871 old string 872 new string 873 } 874 875 func expChangeObserver(c *C, exp []changesObservation) (restore func(), obs wrappers.ObserveChangeCallback) { 876 changesObserved := []changesObservation{} 877 f := func(app *snap.AppInfo, grp *quota.Group, unitType, name, old, new string) { 878 snapName := "" 879 if app != nil { 880 snapName = app.Snap.SnapName() 881 } 882 changesObserved = append(changesObserved, changesObservation{ 883 snapName: snapName, 884 grp: grp, 885 unitType: unitType, 886 name: name, 887 old: old, 888 new: new, 889 }) 890 } 891 892 r := func() { 893 // sort the changesObserved by snapName, with all services being 894 // observed first, then all groups secondly 895 groupObservations := make([]changesObservation, 0, len(changesObserved)) 896 svcObservations := make([]changesObservation, 0, len(changesObserved)) 897 898 for _, chg := range changesObserved { 899 if chg.unitType == "slice" { 900 groupObservations = append(groupObservations, chg) 901 } else { 902 svcObservations = append(svcObservations, chg) 903 } 904 } 905 // sort svcObservations, note we do not need to sort groups, since 906 // quota groups are iterated over in a stable sorted order via 907 // QuotaGroupSet.AllQuotaGroups 908 sort.SliceStable(svcObservations, func(i, j int) bool { 909 return svcObservations[i].snapName < svcObservations[j].snapName 910 }) 911 finalSortChangesObserved := make([]changesObservation, 0, len(changesObserved)) 912 finalSortChangesObserved = append(finalSortChangesObserved, svcObservations...) 913 finalSortChangesObserved = append(finalSortChangesObserved, groupObservations...) 914 c.Assert(finalSortChangesObserved, DeepEquals, exp) 915 } 916 917 return r, f 918 } 919 920 func (s *servicesTestSuite) TestEnsureSnapServicesRewritesQuotaSlices(c *C) { 921 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 922 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 923 924 memLimit1 := quantity.SizeGiB 925 memLimit2 := quantity.SizeGiB * 2 926 927 // write both the unit file and a slice with a different memory limit 928 // setting than will be provided below 929 sliceTempl := `[Unit] 930 Description=Slice for snap quota group %s 931 Before=slices.target 932 X-Snappy=yes 933 934 [Slice] 935 # Always enable cpu accounting, so the following cpu quota options have an effect 936 CPUAccounting=true 937 938 # Always enable memory accounting otherwise the MemoryMax setting does nothing. 939 MemoryAccounting=true 940 MemoryMax=%[2]s 941 # for compatibility with older versions of systemd 942 MemoryLimit=%[2]s 943 944 # Always enable task accounting in order to be able to count the processes/ 945 # threads, etc for a slice 946 TasksAccounting=true 947 ` 948 sliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup.slice") 949 950 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 951 svcContent := fmt.Sprintf(`[Unit] 952 # Auto-generated, DO NOT EDIT 953 Description=Service for snap application hello-snap.svc1 954 Requires=%[1]s 955 Wants=network.target 956 After=%[1]s network.target snapd.apparmor.service 957 X-Snappy=yes 958 959 [Service] 960 EnvironmentFile=-/etc/environment 961 ExecStart=/usr/bin/snap run hello-snap.svc1 962 SyslogIdentifier=hello-snap.svc1 963 Restart=on-failure 964 WorkingDirectory=%[2]s/var/snap/hello-snap/12 965 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 966 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 967 TimeoutStopSec=30 968 Type=forking 969 Slice=snap.foogroup.slice 970 971 [Install] 972 WantedBy=multi-user.target 973 `, 974 systemd.EscapeUnitNamePath(dir), 975 dirs.GlobalRootDir, 976 ) 977 978 err := os.MkdirAll(filepath.Dir(sliceFile), 0755) 979 c.Assert(err, IsNil) 980 981 oldContent := fmt.Sprintf(sliceTempl, "foogroup", memLimit1.String()) 982 err = ioutil.WriteFile(sliceFile, []byte(oldContent), 0644) 983 c.Assert(err, IsNil) 984 985 err = ioutil.WriteFile(svcFile, []byte(svcContent), 0644) 986 c.Assert(err, IsNil) 987 988 // use new memory limit 989 resourceLimits := quota.NewResourcesBuilder().WithMemoryLimit(memLimit2).Build() 990 grp, err := quota.NewGroup("foogroup", resourceLimits) 991 c.Assert(err, IsNil) 992 993 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 994 info: {QuotaGroup: grp}, 995 } 996 997 newContent := fmt.Sprintf(sliceTempl, "foogroup", memLimit2.String()) 998 exp := []changesObservation{ 999 { 1000 grp: grp, 1001 unitType: "slice", 1002 new: newContent, 1003 old: oldContent, 1004 name: "foogroup", 1005 }, 1006 } 1007 r, observe := expChangeObserver(c, exp) 1008 defer r() 1009 1010 err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null) 1011 c.Assert(err, IsNil) 1012 c.Check(s.sysdLog, DeepEquals, [][]string{ 1013 {"daemon-reload"}, 1014 }) 1015 1016 c.Assert(svcFile, testutil.FileEquals, svcContent) 1017 1018 c.Assert(sliceFile, testutil.FileEquals, newContent) 1019 1020 } 1021 1022 func (s *servicesTestSuite) TestEnsureSnapServicesDoesNotRewriteQuotaSlicesOnNoop(c *C) { 1023 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 1024 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 1025 1026 memLimit := quantity.SizeGiB 1027 taskLimit := 32 // arbitrarily chosen 1028 1029 // write both the unit file and a slice before running the ensure 1030 sliceTempl := `[Unit] 1031 Description=Slice for snap quota group %s 1032 Before=slices.target 1033 X-Snappy=yes 1034 1035 [Slice] 1036 # Always enable cpu accounting, so the following cpu quota options have an effect 1037 CPUAccounting=true 1038 1039 # Always enable memory accounting otherwise the MemoryMax setting does nothing. 1040 MemoryAccounting=true 1041 MemoryMax=%[2]s 1042 # for compatibility with older versions of systemd 1043 MemoryLimit=%[2]s 1044 1045 # Always enable task accounting in order to be able to count the processes/ 1046 # threads, etc for a slice 1047 TasksAccounting=true 1048 TasksMax=%[3]d 1049 ` 1050 sliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup.slice") 1051 1052 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 1053 svcContent := fmt.Sprintf(`[Unit] 1054 # Auto-generated, DO NOT EDIT 1055 Description=Service for snap application hello-snap.svc1 1056 Requires=%[1]s 1057 Wants=network.target 1058 After=%[1]s network.target snapd.apparmor.service 1059 X-Snappy=yes 1060 1061 [Service] 1062 EnvironmentFile=-/etc/environment 1063 ExecStart=/usr/bin/snap run hello-snap.svc1 1064 SyslogIdentifier=hello-snap.svc1 1065 Restart=on-failure 1066 WorkingDirectory=%[2]s/var/snap/hello-snap/12 1067 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 1068 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 1069 TimeoutStopSec=30 1070 Type=forking 1071 Slice=snap.foogroup.slice 1072 1073 [Install] 1074 WantedBy=multi-user.target 1075 `, 1076 systemd.EscapeUnitNamePath(dir), 1077 dirs.GlobalRootDir, 1078 ) 1079 1080 err := os.MkdirAll(filepath.Dir(sliceFile), 0755) 1081 c.Assert(err, IsNil) 1082 1083 err = ioutil.WriteFile(sliceFile, []byte(fmt.Sprintf(sliceTempl, "foogroup", memLimit.String(), taskLimit)), 0644) 1084 c.Assert(err, IsNil) 1085 1086 err = ioutil.WriteFile(svcFile, []byte(svcContent), 0644) 1087 c.Assert(err, IsNil) 1088 1089 resourceLimits := quota.NewResourcesBuilder().WithMemoryLimit(memLimit).WithThreadLimit(taskLimit).Build() 1090 grp, err := quota.NewGroup("foogroup", resourceLimits) 1091 c.Assert(err, IsNil) 1092 1093 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 1094 info: {QuotaGroup: grp}, 1095 } 1096 1097 observe := func(app *snap.AppInfo, grp *quota.Group, unitType, name, old, new string) { 1098 c.Error("unexpected call to observe function") 1099 } 1100 1101 err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null) 1102 c.Assert(err, IsNil) 1103 // no daemon restart since the files didn't change 1104 c.Check(s.sysdLog, HasLen, 0) 1105 1106 c.Assert(svcFile, testutil.FileEquals, svcContent) 1107 1108 c.Assert(sliceFile, testutil.FileEquals, fmt.Sprintf(sliceTempl, "foogroup", memLimit.String(), taskLimit)) 1109 } 1110 1111 func (s *servicesTestSuite) TestRemoveQuotaGroup(c *C) { 1112 // create the group 1113 resourceLimits := quota.NewResourcesBuilder().WithMemoryLimit(650 * quantity.SizeKiB).Build() 1114 grp, err := quota.NewGroup("foogroup", resourceLimits) 1115 c.Assert(err, IsNil) 1116 1117 sliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup.slice") 1118 c.Assert(sliceFile, testutil.FileAbsent) 1119 1120 // removing the group when the slice file doesn't exist is not an error 1121 err = wrappers.RemoveQuotaGroup(grp, progress.Null) 1122 c.Assert(err, IsNil) 1123 1124 c.Assert(s.sysdLog, HasLen, 0) 1125 1126 c.Assert(sliceFile, testutil.FileAbsent) 1127 1128 // now write slice file and ensure it is deleted 1129 sliceTempl := `[Unit] 1130 Description=Slice for snap quota group %s 1131 Before=slices.target 1132 X-Snappy=yes 1133 1134 [Slice] 1135 # Always enable cpu accounting, so the following cpu quota options have an effect 1136 CPUAccounting=true 1137 1138 # Always enable memory accounting otherwise the MemoryMax setting does nothing. 1139 MemoryAccounting=true 1140 MemoryMax=1024 1141 # for compatibility with older versions of systemd 1142 MemoryLimit=1024 1143 ` 1144 1145 err = os.MkdirAll(filepath.Dir(sliceFile), 0755) 1146 c.Assert(err, IsNil) 1147 1148 err = ioutil.WriteFile(sliceFile, []byte(fmt.Sprintf(sliceTempl, "foogroup")), 0644) 1149 c.Assert(err, IsNil) 1150 1151 // removing it deletes it 1152 err = wrappers.RemoveQuotaGroup(grp, progress.Null) 1153 c.Assert(err, IsNil) 1154 1155 c.Assert(s.sysdLog, DeepEquals, [][]string{ 1156 {"daemon-reload"}, 1157 }) 1158 1159 c.Assert(sliceFile, testutil.FileAbsent) 1160 } 1161 1162 func (s *servicesTestSuite) TestEnsureSnapServicesWithSubGroupQuotaGroupsForSnaps(c *C) { 1163 info1 := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 1164 info2 := snaptest.MockSnap(c, ` 1165 name: hello-other-snap 1166 version: 1.10 1167 summary: hello 1168 description: Hello... 1169 apps: 1170 hello: 1171 command: bin/hello 1172 world: 1173 command: bin/world 1174 completer: world-completer.sh 1175 svc1: 1176 command: bin/hello 1177 stop-command: bin/goodbye 1178 post-stop-command: bin/missya 1179 daemon: forking 1180 `, &snap.SideInfo{Revision: snap.R(12)}) 1181 svcFile1 := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 1182 svcFile2 := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-other-snap.svc1.service") 1183 1184 var err error 1185 resourceLimits := quota.NewResourcesBuilder(). 1186 WithMemoryLimit(quantity.SizeGiB). 1187 WithCPUCount(4). 1188 WithCPUPercentage(25). 1189 Build() 1190 // make a root quota group and add the first snap to it 1191 grp, err := quota.NewGroup("foogroup", resourceLimits) 1192 c.Assert(err, IsNil) 1193 1194 // the second group is a sub-group with the same limit, but is for the 1195 // second snap 1196 subgrp, err := grp.NewSubGroup("subgroup", resourceLimits) 1197 c.Assert(err, IsNil) 1198 1199 sliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup.slice") 1200 subSliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup-subgroup.slice") 1201 1202 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 1203 info1: {QuotaGroup: grp}, 1204 info2: {QuotaGroup: subgrp}, 1205 } 1206 1207 sliceTempl := `[Unit] 1208 Description=Slice for snap quota group %s 1209 Before=slices.target 1210 X-Snappy=yes 1211 1212 [Slice] 1213 # Always enable cpu accounting, so the following cpu quota options have an effect 1214 CPUAccounting=true 1215 CPUQuota=%[2]d%% 1216 1217 # Always enable memory accounting otherwise the MemoryMax setting does nothing. 1218 MemoryAccounting=true 1219 MemoryMax=%[3]d 1220 # for compatibility with older versions of systemd 1221 MemoryLimit=%[3]d 1222 1223 # Always enable task accounting in order to be able to count the processes/ 1224 # threads, etc for a slice 1225 TasksAccounting=true 1226 ` 1227 1228 sliceContent := fmt.Sprintf(sliceTempl, "foogroup", resourceLimits.CPU.Count*resourceLimits.CPU.Percentage, resourceLimits.Memory.Limit) 1229 subSliceContent := fmt.Sprintf(sliceTempl, "subgroup", resourceLimits.CPU.Count*resourceLimits.CPU.Percentage, resourceLimits.Memory.Limit) 1230 1231 svcTemplate := `[Unit] 1232 # Auto-generated, DO NOT EDIT 1233 Description=Service for snap application %[1]s.svc1 1234 Requires=%[2]s 1235 Wants=network.target 1236 After=%[2]s network.target snapd.apparmor.service 1237 X-Snappy=yes 1238 1239 [Service] 1240 EnvironmentFile=-/etc/environment 1241 ExecStart=/usr/bin/snap run %[1]s.svc1 1242 SyslogIdentifier=%[1]s.svc1 1243 Restart=on-failure 1244 WorkingDirectory=%[3]s/var/snap/%[1]s/12 1245 ExecStop=/usr/bin/snap run --command=stop %[1]s.svc1 1246 ExecStopPost=/usr/bin/snap run --command=post-stop %[1]s.svc1 1247 TimeoutStopSec=30 1248 Type=forking 1249 Slice=%[4]s 1250 1251 [Install] 1252 WantedBy=multi-user.target 1253 ` 1254 1255 dir1 := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 1256 dir2 := filepath.Join(dirs.SnapMountDir, "hello-other-snap", "12.mount") 1257 1258 helloSnapContent := fmt.Sprintf(svcTemplate, 1259 "hello-snap", 1260 systemd.EscapeUnitNamePath(dir1), 1261 dirs.GlobalRootDir, 1262 "snap.foogroup.slice", 1263 ) 1264 1265 helloOtherSnapContent := fmt.Sprintf(svcTemplate, 1266 "hello-other-snap", 1267 systemd.EscapeUnitNamePath(dir2), 1268 dirs.GlobalRootDir, 1269 "snap.foogroup-subgroup.slice", 1270 ) 1271 1272 exp := []changesObservation{ 1273 { 1274 snapName: "hello-other-snap", 1275 unitType: "service", 1276 name: "svc1", 1277 old: "", 1278 new: helloOtherSnapContent, 1279 }, 1280 { 1281 snapName: "hello-snap", 1282 unitType: "service", 1283 name: "svc1", 1284 old: "", 1285 new: helloSnapContent, 1286 }, 1287 { 1288 grp: grp, 1289 unitType: "slice", 1290 new: sliceContent, 1291 old: "", 1292 name: "foogroup", 1293 }, 1294 { 1295 grp: subgrp, 1296 unitType: "slice", 1297 new: subSliceContent, 1298 old: "", 1299 name: "subgroup", 1300 }, 1301 } 1302 r, observe := expChangeObserver(c, exp) 1303 defer r() 1304 1305 err = wrappers.EnsureSnapServices(m, nil, observe, progress.Null) 1306 c.Assert(err, IsNil) 1307 c.Check(s.sysdLog, DeepEquals, [][]string{ 1308 {"daemon-reload"}, 1309 }) 1310 1311 c.Assert(svcFile1, testutil.FileEquals, helloSnapContent) 1312 1313 c.Assert(svcFile2, testutil.FileEquals, helloOtherSnapContent) 1314 1315 // check that the slice units were also generated 1316 1317 c.Assert(sliceFile, testutil.FileEquals, sliceContent) 1318 c.Assert(subSliceFile, testutil.FileEquals, subSliceContent) 1319 } 1320 1321 func (s *servicesTestSuite) TestEnsureSnapServicesWithSubGroupQuotaGroupsGeneratesParentGroups(c *C) { 1322 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 1323 svcFile1 := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 1324 1325 var err error 1326 resourceLimits := quota.NewResourcesBuilder(). 1327 WithMemoryLimit(quantity.SizeGiB). 1328 WithCPUSet([]int{0}). 1329 Build() 1330 // make a root quota group without any snaps in it 1331 grp, err := quota.NewGroup("foogroup", resourceLimits) 1332 c.Assert(err, IsNil) 1333 1334 // the second group is a sub-group with the same limit, but it is the one 1335 // with the snap in it 1336 subgrp, err := grp.NewSubGroup("subgroup", resourceLimits) 1337 c.Assert(err, IsNil) 1338 1339 sliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup.slice") 1340 subSliceFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.foogroup-subgroup.slice") 1341 1342 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 1343 info: {QuotaGroup: subgrp}, 1344 } 1345 1346 err = wrappers.EnsureSnapServices(m, nil, nil, progress.Null) 1347 c.Assert(err, IsNil) 1348 c.Check(s.sysdLog, DeepEquals, [][]string{ 1349 {"daemon-reload"}, 1350 }) 1351 1352 svcTemplate := `[Unit] 1353 # Auto-generated, DO NOT EDIT 1354 Description=Service for snap application %[1]s.svc1 1355 Requires=%[2]s 1356 Wants=network.target 1357 After=%[2]s network.target snapd.apparmor.service 1358 X-Snappy=yes 1359 1360 [Service] 1361 EnvironmentFile=-/etc/environment 1362 ExecStart=/usr/bin/snap run %[1]s.svc1 1363 SyslogIdentifier=%[1]s.svc1 1364 Restart=on-failure 1365 WorkingDirectory=%[3]s/var/snap/%[1]s/12 1366 ExecStop=/usr/bin/snap run --command=stop %[1]s.svc1 1367 ExecStopPost=/usr/bin/snap run --command=post-stop %[1]s.svc1 1368 TimeoutStopSec=30 1369 Type=forking 1370 Slice=%[4]s 1371 1372 [Install] 1373 WantedBy=multi-user.target 1374 ` 1375 1376 dir1 := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 1377 1378 c.Assert(svcFile1, testutil.FileEquals, fmt.Sprintf(svcTemplate, 1379 "hello-snap", 1380 systemd.EscapeUnitNamePath(dir1), 1381 dirs.GlobalRootDir, 1382 "snap.foogroup-subgroup.slice", 1383 )) 1384 1385 // check that both the parent and sub-group slice units were generated 1386 templ := `[Unit] 1387 Description=Slice for snap quota group %s 1388 Before=slices.target 1389 X-Snappy=yes 1390 1391 [Slice] 1392 # Always enable cpu accounting, so the following cpu quota options have an effect 1393 CPUAccounting=true 1394 AllowedCPUs=%[2]s 1395 1396 # Always enable memory accounting otherwise the MemoryMax setting does nothing. 1397 MemoryAccounting=true 1398 MemoryMax=%[3]d 1399 # for compatibility with older versions of systemd 1400 MemoryLimit=%[3]d 1401 1402 # Always enable task accounting in order to be able to count the processes/ 1403 # threads, etc for a slice 1404 TasksAccounting=true 1405 ` 1406 1407 allowedCpusValue := strutil.IntsToCommaSeparated(resourceLimits.CPUSet.CPUs) 1408 c.Assert(sliceFile, testutil.FileEquals, fmt.Sprintf(templ, "foogroup", allowedCpusValue, resourceLimits.Memory.Limit)) 1409 c.Assert(subSliceFile, testutil.FileEquals, fmt.Sprintf(templ, "subgroup", allowedCpusValue, resourceLimits.Memory.Limit)) 1410 } 1411 1412 func (s *servicesTestSuite) TestEnsureSnapServiceEnsureError(c *C) { 1413 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 1414 svcFileDir := filepath.Join(s.tempdir, "/etc/systemd/system") 1415 1416 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 1417 info: nil, 1418 } 1419 1420 // make the directory where the service file is written not writable, this 1421 // will make EnsureFileState return an error 1422 err := os.MkdirAll(svcFileDir, 0755) 1423 c.Assert(err, IsNil) 1424 1425 err = os.Chmod(svcFileDir, 0644) 1426 c.Assert(err, IsNil) 1427 1428 err = wrappers.EnsureSnapServices(m, nil, nil, progress.Null) 1429 c.Assert(err, ErrorMatches, ".* permission denied") 1430 // we don't issue a daemon-reload since we didn't actually end up making any 1431 // changes (there was nothing to rollback to) 1432 c.Check(s.sysdLog, HasLen, 0) 1433 1434 // we didn't write any files 1435 c.Assert(filepath.Join(svcFileDir, "snap.hello-snap.svc1.service"), testutil.FileAbsent) 1436 } 1437 1438 func (s *servicesTestSuite) TestEnsureSnapServicesPreseedingHappy(c *C) { 1439 // map unit -> new 1440 seen := make(map[string]bool) 1441 cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) { 1442 seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = old == "" 1443 } 1444 1445 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 1446 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 1447 1448 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 1449 info: nil, 1450 } 1451 1452 // we provide globally applicable Preseeding option via 1453 // EnsureSnapServiceOptions 1454 globalOpts := &wrappers.EnsureSnapServicesOptions{ 1455 Preseeding: true, 1456 } 1457 err := wrappers.EnsureSnapServices(m, globalOpts, cb, progress.Null) 1458 c.Assert(err, IsNil) 1459 // no daemon-reload's since we are preseeding 1460 c.Check(s.sysdLog, HasLen, 0) 1461 c.Check(seen, DeepEquals, map[string]bool{ 1462 "hello-snap:svc1:service:svc1": true, 1463 }) 1464 1465 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 1466 c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(`[Unit] 1467 # Auto-generated, DO NOT EDIT 1468 Description=Service for snap application hello-snap.svc1 1469 Requires=%[1]s 1470 Wants=network.target 1471 After=%[1]s network.target snapd.apparmor.service 1472 X-Snappy=yes 1473 1474 [Service] 1475 EnvironmentFile=-/etc/environment 1476 ExecStart=/usr/bin/snap run hello-snap.svc1 1477 SyslogIdentifier=hello-snap.svc1 1478 Restart=on-failure 1479 WorkingDirectory=%[2]s/var/snap/hello-snap/12 1480 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 1481 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 1482 TimeoutStopSec=30 1483 Type=forking 1484 1485 [Install] 1486 WantedBy=multi-user.target 1487 `, 1488 systemd.EscapeUnitNamePath(dir), 1489 dirs.GlobalRootDir, 1490 )) 1491 } 1492 1493 func (s *servicesTestSuite) TestEnsureSnapServicesRequireMountedSnapdSnapOptionsHappy(c *C) { 1494 // map unit -> new 1495 seen := make(map[string]bool) 1496 cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) { 1497 seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = old == "" 1498 } 1499 1500 // use two snaps one with per-snap options and one without to demonstrate 1501 // that the global options apply to all snaps 1502 info1 := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 1503 info2 := snaptest.MockSnap(c, ` 1504 name: hello-other-snap 1505 version: 1.10 1506 summary: hello 1507 description: Hello... 1508 apps: 1509 hello: 1510 command: bin/hello 1511 world: 1512 command: bin/world 1513 completer: world-completer.sh 1514 svc1: 1515 command: bin/hello 1516 stop-command: bin/goodbye 1517 post-stop-command: bin/missya 1518 daemon: forking 1519 `, &snap.SideInfo{Revision: snap.R(12)}) 1520 svcFile1 := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 1521 svcFile2 := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-other-snap.svc1.service") 1522 1523 // some options per-snap 1524 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 1525 info1: {VitalityRank: 1}, 1526 info2: nil, 1527 } 1528 1529 // and also a global option that should propagate to unit generation too 1530 globalOpts := &wrappers.EnsureSnapServicesOptions{ 1531 RequireMountedSnapdSnap: true, 1532 } 1533 err := wrappers.EnsureSnapServices(m, globalOpts, cb, progress.Null) 1534 c.Assert(err, IsNil) 1535 // no daemon-reload's since we are preseeding 1536 c.Check(s.sysdLog, DeepEquals, [][]string{ 1537 {"daemon-reload"}, 1538 }) 1539 c.Check(seen, DeepEquals, map[string]bool{ 1540 "hello-snap:svc1:service:svc1": true, 1541 "hello-other-snap:svc1:service:svc1": true, 1542 }) 1543 1544 template := `[Unit] 1545 # Auto-generated, DO NOT EDIT 1546 Description=Service for snap application %[1]s.svc1 1547 Requires=%[2]s 1548 Wants=network.target 1549 After=%[2]s network.target snapd.apparmor.service 1550 Wants=usr-lib-snapd.mount 1551 After=usr-lib-snapd.mount 1552 X-Snappy=yes 1553 1554 [Service] 1555 EnvironmentFile=-/etc/environment 1556 ExecStart=/usr/bin/snap run %[1]s.svc1 1557 SyslogIdentifier=%[1]s.svc1 1558 Restart=on-failure 1559 WorkingDirectory=%[3]s/var/snap/%[1]s/12 1560 ExecStop=/usr/bin/snap run --command=stop %[1]s.svc1 1561 ExecStopPost=/usr/bin/snap run --command=post-stop %[1]s.svc1 1562 TimeoutStopSec=30 1563 Type=forking 1564 %[4]s 1565 [Install] 1566 WantedBy=multi-user.target 1567 ` 1568 1569 dir1 := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 1570 dir2 := filepath.Join(dirs.SnapMountDir, "hello-other-snap", "12.mount") 1571 1572 c.Assert(svcFile1, testutil.FileEquals, fmt.Sprintf(template, 1573 "hello-snap", 1574 systemd.EscapeUnitNamePath(dir1), 1575 dirs.GlobalRootDir, 1576 "OOMScoreAdjust=-899\n", // VitalityRank in effect 1577 )) 1578 1579 c.Assert(svcFile2, testutil.FileEquals, fmt.Sprintf(template, 1580 "hello-other-snap", 1581 systemd.EscapeUnitNamePath(dir2), 1582 dirs.GlobalRootDir, 1583 "", // no VitalityRank in effect 1584 )) 1585 } 1586 1587 func (s *servicesTestSuite) TestEnsureSnapServicesCallback(c *C) { 1588 // hava a 2nd new service definition 1589 info := snaptest.MockSnap(c, packageHello+` svc2: 1590 command: bin/hello 1591 stop-command: bin/goodbye 1592 post-stop-command: bin/missya 1593 daemon: forking 1594 `, &snap.SideInfo{Revision: snap.R(12)}) 1595 svc1File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 1596 svc2File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service") 1597 1598 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 1599 template := `[Unit] 1600 # Auto-generated, DO NOT EDIT 1601 Description=Service for snap application hello-snap.%[1]s 1602 Requires=%[2]s 1603 Wants=network.target 1604 After=%[2]s network.target snapd.apparmor.service 1605 X-Snappy=yes 1606 1607 [Service] 1608 EnvironmentFile=-/etc/environment 1609 ExecStart=/usr/bin/snap run hello-snap.%[1]s 1610 SyslogIdentifier=hello-snap.%[1]s 1611 Restart=on-failure 1612 WorkingDirectory=%[3]s/var/snap/hello-snap/12 1613 ExecStop=/usr/bin/snap run --command=stop hello-snap.%[1]s 1614 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.%[1]s 1615 TimeoutStopSec=30 1616 Type=forking 1617 %[4]s 1618 [Install] 1619 WantedBy=multi-user.target 1620 ` 1621 svc1Content := fmt.Sprintf(template, 1622 "svc1", 1623 systemd.EscapeUnitNamePath(dir), 1624 dirs.GlobalRootDir, 1625 "", 1626 ) 1627 1628 err := os.MkdirAll(filepath.Dir(svc1File), 0755) 1629 c.Assert(err, IsNil) 1630 err = ioutil.WriteFile(svc1File, []byte(svc1Content), 0644) 1631 c.Assert(err, IsNil) 1632 1633 // both will be written, one is new 1634 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 1635 info: {VitalityRank: 1}, 1636 } 1637 1638 seen := make(map[string][]string) 1639 cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) { 1640 seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = []string{old, new} 1641 } 1642 1643 err = wrappers.EnsureSnapServices(m, nil, cb, progress.Null) 1644 c.Assert(err, IsNil) 1645 c.Check(s.sysdLog, DeepEquals, [][]string{ 1646 {"daemon-reload"}, 1647 }) 1648 1649 // svc2 was written as expected 1650 svc2New := fmt.Sprintf(template, 1651 "svc2", 1652 systemd.EscapeUnitNamePath(dir), 1653 dirs.GlobalRootDir, 1654 "OOMScoreAdjust=-899\n", 1655 ) 1656 c.Assert(svc2File, testutil.FileEquals, svc2New) 1657 1658 // and svc1 was changed as well 1659 svc1New := fmt.Sprintf(template, 1660 "svc1", 1661 systemd.EscapeUnitNamePath(dir), 1662 dirs.GlobalRootDir, 1663 "OOMScoreAdjust=-899\n", 1664 ) 1665 c.Assert(svc1File, testutil.FileEquals, svc1New) 1666 1667 c.Check(seen, DeepEquals, map[string][]string{ 1668 "hello-snap:svc1:service:svc1": {svc1Content, svc1New}, 1669 "hello-snap:svc2:service:svc2": {"", svc2New}, 1670 }) 1671 } 1672 1673 func (s *servicesTestSuite) TestEnsureSnapServicesAddsNewSvc(c *C) { 1674 // map unit -> new 1675 seen := make(map[string]bool) 1676 cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) { 1677 seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = old == "" 1678 } 1679 1680 // test that with an existing service unit definition, it is not changed 1681 // but we do add the new one 1682 info := snaptest.MockSnap(c, packageHello+` svc2: 1683 command: bin/hello 1684 stop-command: bin/goodbye 1685 post-stop-command: bin/missya 1686 daemon: forking 1687 `, &snap.SideInfo{Revision: snap.R(12)}) 1688 svc1File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 1689 svc2File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service") 1690 1691 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 1692 template := `[Unit] 1693 # Auto-generated, DO NOT EDIT 1694 Description=Service for snap application hello-snap.%[1]s 1695 Requires=%[2]s 1696 Wants=network.target 1697 After=%[2]s network.target snapd.apparmor.service 1698 X-Snappy=yes 1699 1700 [Service] 1701 EnvironmentFile=-/etc/environment 1702 ExecStart=/usr/bin/snap run hello-snap.%[1]s 1703 SyslogIdentifier=hello-snap.%[1]s 1704 Restart=on-failure 1705 WorkingDirectory=%[3]s/var/snap/hello-snap/12 1706 ExecStop=/usr/bin/snap run --command=stop hello-snap.%[1]s 1707 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.%[1]s 1708 TimeoutStopSec=30 1709 Type=forking 1710 %[4]s 1711 [Install] 1712 WantedBy=multi-user.target 1713 ` 1714 svc1Content := fmt.Sprintf(template, 1715 "svc1", 1716 systemd.EscapeUnitNamePath(dir), 1717 dirs.GlobalRootDir, 1718 "", 1719 ) 1720 1721 err := os.MkdirAll(filepath.Dir(svc1File), 0755) 1722 c.Assert(err, IsNil) 1723 err = ioutil.WriteFile(svc1File, []byte(svc1Content), 0644) 1724 c.Assert(err, IsNil) 1725 1726 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 1727 info: nil, 1728 } 1729 1730 err = wrappers.EnsureSnapServices(m, nil, cb, progress.Null) 1731 c.Assert(err, IsNil) 1732 c.Check(s.sysdLog, DeepEquals, [][]string{ 1733 {"daemon-reload"}, 1734 }) 1735 // we only added svc2 1736 c.Check(seen, DeepEquals, map[string]bool{ 1737 "hello-snap:svc2:service:svc2": true, 1738 }) 1739 1740 // svc2 was written as expected 1741 c.Assert(svc2File, testutil.FileEquals, fmt.Sprintf(template, 1742 "svc2", 1743 systemd.EscapeUnitNamePath(dir), 1744 dirs.GlobalRootDir, 1745 "", 1746 )) 1747 1748 // and svc1 didn't change 1749 c.Assert(svc1File, testutil.FileEquals, fmt.Sprintf(template, 1750 "svc1", 1751 systemd.EscapeUnitNamePath(dir), 1752 dirs.GlobalRootDir, 1753 "", 1754 )) 1755 } 1756 1757 func (s *servicesTestSuite) TestEnsureSnapServicesNoChangeNoop(c *C) { 1758 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 1759 1760 // pretend we already have a unit file setup 1761 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 1762 1763 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 1764 template := `[Unit] 1765 # Auto-generated, DO NOT EDIT 1766 Description=Service for snap application hello-snap.svc1 1767 Requires=%[1]s 1768 Wants=network.target 1769 After=%[1]s network.target snapd.apparmor.service 1770 X-Snappy=yes 1771 1772 [Service] 1773 EnvironmentFile=-/etc/environment 1774 ExecStart=/usr/bin/snap run hello-snap.svc1 1775 SyslogIdentifier=hello-snap.svc1 1776 Restart=on-failure 1777 WorkingDirectory=%[2]s/var/snap/hello-snap/12 1778 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 1779 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 1780 TimeoutStopSec=30 1781 Type=forking 1782 %s 1783 [Install] 1784 WantedBy=multi-user.target 1785 ` 1786 origContent := fmt.Sprintf(template, 1787 systemd.EscapeUnitNamePath(dir), 1788 dirs.GlobalRootDir, 1789 "", 1790 ) 1791 1792 err := os.MkdirAll(filepath.Dir(svcFile), 0755) 1793 c.Assert(err, IsNil) 1794 err = ioutil.WriteFile(svcFile, []byte(origContent), 0644) 1795 c.Assert(err, IsNil) 1796 1797 // now ensuring with no options will not modify anything or trigger a 1798 // daemon-reload 1799 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 1800 info: nil, 1801 } 1802 1803 cbCalled := 0 1804 cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) { 1805 cbCalled++ 1806 } 1807 1808 err = wrappers.EnsureSnapServices(m, nil, cb, progress.Null) 1809 c.Assert(err, IsNil) 1810 c.Check(s.sysdLog, HasLen, 0) 1811 1812 // the file is not changed 1813 c.Assert(svcFile, testutil.FileEquals, origContent) 1814 1815 // callback is not called if no change 1816 c.Check(cbCalled, Equals, 0) 1817 } 1818 1819 func (s *servicesTestSuite) TestEnsureSnapServicesChanges(c *C) { 1820 // map unit -> new 1821 seen := make(map[string]bool) 1822 cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) { 1823 seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = old == "" 1824 } 1825 1826 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 1827 1828 // pretend we already have a unit file with no VitalityRank options set 1829 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 1830 1831 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 1832 template := `[Unit] 1833 # Auto-generated, DO NOT EDIT 1834 Description=Service for snap application hello-snap.svc1 1835 Requires=%[1]s 1836 Wants=network.target 1837 After=%[1]s network.target snapd.apparmor.service 1838 X-Snappy=yes 1839 1840 [Service] 1841 EnvironmentFile=-/etc/environment 1842 ExecStart=/usr/bin/snap run hello-snap.svc1 1843 SyslogIdentifier=hello-snap.svc1 1844 Restart=on-failure 1845 WorkingDirectory=%[2]s/var/snap/hello-snap/12 1846 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 1847 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 1848 TimeoutStopSec=30 1849 Type=forking 1850 %s 1851 [Install] 1852 WantedBy=multi-user.target 1853 ` 1854 origContent := fmt.Sprintf(template, 1855 systemd.EscapeUnitNamePath(dir), 1856 dirs.GlobalRootDir, 1857 "", 1858 ) 1859 1860 err := os.MkdirAll(filepath.Dir(svcFile), 0755) 1861 c.Assert(err, IsNil) 1862 err = ioutil.WriteFile(svcFile, []byte(origContent), 0644) 1863 c.Assert(err, IsNil) 1864 1865 // now ensuring with the VitalityRank set will modify the file 1866 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 1867 info: {VitalityRank: 1}, 1868 } 1869 1870 err = wrappers.EnsureSnapServices(m, nil, cb, progress.Null) 1871 c.Assert(err, IsNil) 1872 c.Check(s.sysdLog, DeepEquals, [][]string{ 1873 {"daemon-reload"}, 1874 }) 1875 1876 // only modified 1877 c.Check(seen, DeepEquals, map[string]bool{ 1878 "hello-snap:svc1:service:svc1": false, 1879 }) 1880 1881 // now the file has been modified to have OOMScoreAdjust set for it 1882 c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(template, 1883 systemd.EscapeUnitNamePath(dir), 1884 dirs.GlobalRootDir, 1885 "OOMScoreAdjust=-899\n", 1886 )) 1887 } 1888 1889 func (s *servicesTestSuite) TestEnsureSnapServicesRollsback(c *C) { 1890 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 1891 1892 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 1893 1894 // pretend we already have a unit file with no VitalityRank options set 1895 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 1896 template := `[Unit] 1897 # Auto-generated, DO NOT EDIT 1898 Description=Service for snap application hello-snap.svc1 1899 Requires=%[1]s 1900 Wants=network.target 1901 After=%[1]s network.target snapd.apparmor.service 1902 X-Snappy=yes 1903 1904 [Service] 1905 EnvironmentFile=-/etc/environment 1906 ExecStart=/usr/bin/snap run hello-snap.svc1 1907 SyslogIdentifier=hello-snap.svc1 1908 Restart=on-failure 1909 WorkingDirectory=%[2]s/var/snap/hello-snap/12 1910 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 1911 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 1912 TimeoutStopSec=30 1913 Type=forking 1914 %s 1915 [Install] 1916 WantedBy=multi-user.target 1917 ` 1918 origContent := fmt.Sprintf(template, 1919 systemd.EscapeUnitNamePath(dir), 1920 dirs.GlobalRootDir, 1921 "", 1922 ) 1923 1924 err := os.MkdirAll(filepath.Dir(svcFile), 0755) 1925 c.Assert(err, IsNil) 1926 err = ioutil.WriteFile(svcFile, []byte(origContent), 0644) 1927 c.Assert(err, IsNil) 1928 1929 // make systemctl fail the first time when we try to do a daemon-reload, 1930 // then the next time don't return an error 1931 systemctlCalls := 0 1932 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 1933 systemctlCalls++ 1934 switch systemctlCalls { 1935 case 1: 1936 // check that the file has been modified to have OOMScoreAdjust set 1937 // for it 1938 c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(template, 1939 systemd.EscapeUnitNamePath(dir), 1940 dirs.GlobalRootDir, 1941 "OOMScoreAdjust=-899\n", 1942 )) 1943 1944 // now return an error to trigger a rollback 1945 return nil, fmt.Errorf("oops") 1946 case 2: 1947 // check that the rollback happened to restore the original content 1948 c.Assert(svcFile, testutil.FileEquals, origContent) 1949 1950 return nil, nil 1951 default: 1952 c.Errorf("unexpected call (number %d) to systemctl: %+v", systemctlCalls, cmd) 1953 return nil, fmt.Errorf("broken test") 1954 } 1955 }) 1956 defer r() 1957 1958 // now ensuring with the VitalityRank set will modify the file 1959 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 1960 info: {VitalityRank: 1}, 1961 } 1962 1963 err = wrappers.EnsureSnapServices(m, nil, nil, progress.Null) 1964 c.Assert(err, ErrorMatches, "oops") 1965 c.Assert(systemctlCalls, Equals, 2) 1966 1967 // double-check that after the function is done, the file is back to what we 1968 // had before (this check duplicates the one in MockSystemctl but doesn't 1969 // hurt anything to do again) 1970 c.Assert(svcFile, testutil.FileEquals, origContent) 1971 } 1972 1973 func (s *servicesTestSuite) TestEnsureSnapServicesRemovesNewAddOnRollback(c *C) { 1974 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 1975 1976 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 1977 1978 // pretend we already have a unit file with no VitalityRank options set 1979 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 1980 template := `[Unit] 1981 # Auto-generated, DO NOT EDIT 1982 Description=Service for snap application hello-snap.svc1 1983 Requires=%[1]s 1984 Wants=network.target 1985 After=%[1]s network.target snapd.apparmor.service 1986 X-Snappy=yes 1987 1988 [Service] 1989 EnvironmentFile=-/etc/environment 1990 ExecStart=/usr/bin/snap run hello-snap.svc1 1991 SyslogIdentifier=hello-snap.svc1 1992 Restart=on-failure 1993 WorkingDirectory=%[2]s/var/snap/hello-snap/12 1994 ExecStop=/usr/bin/snap run --command=stop hello-snap.svc1 1995 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 1996 TimeoutStopSec=30 1997 Type=forking 1998 %s 1999 [Install] 2000 WantedBy=multi-user.target 2001 ` 2002 // make systemctl fail the first time when we try to do a daemon-reload, 2003 // then the next time don't return an error 2004 systemctlCalls := 0 2005 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 2006 systemctlCalls++ 2007 switch systemctlCalls { 2008 case 1: 2009 // double check that we wrote the new file here before calling 2010 // daemon reload 2011 c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(template, 2012 systemd.EscapeUnitNamePath(dir), 2013 dirs.GlobalRootDir, 2014 "", 2015 )) 2016 2017 // now return an error to trigger a rollback 2018 return nil, fmt.Errorf("oops") 2019 case 2: 2020 // after the rollback, check that the new file was deleted 2021 c.Assert(svcFile, testutil.FileAbsent) 2022 2023 return nil, nil 2024 default: 2025 c.Errorf("unexpected call (number %d) to systemctl: %+v", systemctlCalls, cmd) 2026 return nil, fmt.Errorf("broken test") 2027 } 2028 }) 2029 defer r() 2030 2031 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 2032 info: nil, 2033 } 2034 2035 err := wrappers.EnsureSnapServices(m, nil, nil, progress.Null) 2036 c.Assert(err, ErrorMatches, "oops") 2037 c.Assert(systemctlCalls, Equals, 2) 2038 2039 // double-check that after the function is done, the file is gone again 2040 c.Assert(svcFile, testutil.FileAbsent) 2041 } 2042 2043 func (s *servicesTestSuite) TestEnsureSnapServicesOnlyRemovesNewAddOnRollback(c *C) { 2044 info := snaptest.MockSnap(c, packageHello+` svc2: 2045 command: bin/hello 2046 stop-command: bin/goodbye 2047 post-stop-command: bin/missya 2048 daemon: forking 2049 `, &snap.SideInfo{Revision: snap.R(12)}) 2050 2051 // we won't delete existing files, but we will delete new files, so mock an 2052 // existing file to check that it doesn't get deleted 2053 svc1File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 2054 svc2File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service") 2055 2056 // pretend we already have a unit file with no VitalityRank options set 2057 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 2058 template := `[Unit] 2059 # Auto-generated, DO NOT EDIT 2060 Description=Service for snap application hello-snap.%[1]s 2061 Requires=%[2]s 2062 Wants=network.target 2063 After=%[2]s network.target snapd.apparmor.service 2064 X-Snappy=yes 2065 2066 [Service] 2067 EnvironmentFile=-/etc/environment 2068 ExecStart=/usr/bin/snap run hello-snap.%[1]s 2069 SyslogIdentifier=hello-snap.%[1]s 2070 Restart=on-failure 2071 WorkingDirectory=%[3]s/var/snap/hello-snap/12 2072 ExecStop=/usr/bin/snap run --command=stop hello-snap.%[1]s 2073 ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.%[1]s 2074 TimeoutStopSec=30 2075 Type=forking 2076 %[4]s 2077 [Install] 2078 WantedBy=multi-user.target 2079 ` 2080 2081 svc1Content := fmt.Sprintf(template, 2082 "svc1", 2083 systemd.EscapeUnitNamePath(dir), 2084 dirs.GlobalRootDir, 2085 "", 2086 ) 2087 svc2Content := fmt.Sprintf(template, 2088 "svc2", 2089 systemd.EscapeUnitNamePath(dir), 2090 dirs.GlobalRootDir, 2091 "", 2092 ) 2093 2094 err := os.MkdirAll(filepath.Dir(svc1File), 0755) 2095 c.Assert(err, IsNil) 2096 err = ioutil.WriteFile(svc1File, []byte(svc1Content), 0644) 2097 c.Assert(err, IsNil) 2098 2099 // make systemctl fail the first time when we try to do a daemon-reload, 2100 // then the next time don't return an error 2101 systemctlCalls := 0 2102 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 2103 systemctlCalls++ 2104 switch systemctlCalls { 2105 case 1: 2106 // double check that we wrote the new file here before calling 2107 // daemon reload 2108 c.Assert(svc2File, testutil.FileEquals, svc2Content) 2109 2110 // and the existing file is still the same 2111 c.Assert(svc1File, testutil.FileEquals, svc1Content) 2112 2113 // now return error to trigger a rollback 2114 return nil, fmt.Errorf("oops") 2115 case 2: 2116 // after the rollback, check that the new file was deleted 2117 c.Assert(svc2File, testutil.FileAbsent) 2118 2119 return nil, nil 2120 default: 2121 c.Errorf("unexpected call (number %d) to systemctl: %+v", systemctlCalls, cmd) 2122 return nil, fmt.Errorf("broken test") 2123 } 2124 }) 2125 defer r() 2126 2127 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 2128 info: nil, 2129 } 2130 2131 err = wrappers.EnsureSnapServices(m, nil, nil, progress.Null) 2132 c.Assert(err, ErrorMatches, "oops") 2133 c.Assert(systemctlCalls, Equals, 2) 2134 2135 // double-check that after the function, svc2 (the new one) is missing, but 2136 // svc1 is still the same 2137 c.Assert(svc2File, testutil.FileAbsent) 2138 c.Assert(svc1File, testutil.FileEquals, svc1Content) 2139 } 2140 2141 func (s *servicesTestSuite) TestEnsureSnapServicesSubunits(c *C) { 2142 // map unit -> new 2143 seen := make(map[string]bool) 2144 cb := func(app *snap.AppInfo, grp *quota.Group, unitType, name string, old, new string) { 2145 seen[fmt.Sprintf("%s:%s:%s:%s", app.Snap.InstanceName(), app.Name, unitType, name)] = old == "" 2146 } 2147 2148 info := snaptest.MockSnap(c, packageHello+` 2149 timer: 10:00-12:00 2150 `, &snap.SideInfo{Revision: snap.R(11)}) 2151 2152 m := map[*snap.Info]*wrappers.SnapServiceOptions{ 2153 info: nil, 2154 } 2155 err := wrappers.EnsureSnapServices(m, nil, cb, progress.Null) 2156 c.Assert(err, IsNil) 2157 2158 c.Check(seen, DeepEquals, map[string]bool{ 2159 "hello-snap:svc1:service:svc1": true, 2160 "hello-snap:svc1:timer:": true, 2161 }) 2162 // reset 2163 seen = make(map[string]bool) 2164 2165 // change vitality, timer, add socket 2166 info = snaptest.MockSnap(c, packageHello+` 2167 plugs: [network-bind] 2168 timer: 10:00-12:00,20:00-22:00 2169 sockets: 2170 sock1: 2171 listen-stream: $SNAP_DATA/sock1.socket 2172 `, &snap.SideInfo{Revision: snap.R(12)}) 2173 2174 m = map[*snap.Info]*wrappers.SnapServiceOptions{ 2175 info: {VitalityRank: 1}, 2176 } 2177 err = wrappers.EnsureSnapServices(m, nil, cb, progress.Null) 2178 c.Assert(err, IsNil) 2179 2180 c.Check(seen, DeepEquals, map[string]bool{ 2181 "hello-snap:svc1:service:svc1": false, 2182 "hello-snap:svc1:timer:": false, 2183 "hello-snap:svc1:socket:sock1": true, 2184 }) 2185 } 2186 2187 func (s *servicesTestSuite) TestAddSnapServicesWithInterfaceSnippets(c *C) { 2188 tt := []struct { 2189 comment string 2190 plugSnippet string 2191 }{ 2192 // just single bare interfaces with no attributes 2193 { 2194 "docker-support", 2195 ` 2196 plugs: 2197 - docker-support`, 2198 }, 2199 { 2200 "k8s-support", 2201 ` 2202 plugs: 2203 - kubernetes-support`, 2204 }, 2205 { 2206 "lxd-support", 2207 ` 2208 plugs: 2209 - lxd-support 2210 `, 2211 }, 2212 { 2213 "greengrass-support", 2214 ` 2215 plugs: 2216 - greengrass-support 2217 `, 2218 }, 2219 2220 // multiple interfaces that require Delegate=true, but only one is 2221 // generated 2222 2223 { 2224 "multiple interfaces that require Delegate=true", 2225 ` 2226 plugs: 2227 - docker-support 2228 - kubernetes-support`, 2229 }, 2230 2231 // interfaces with flavor attributes 2232 2233 { 2234 "k8s-support with kubelet", 2235 ` 2236 plugs: 2237 - kubelet 2238 plugs: 2239 kubelet: 2240 interface: kubernetes-support 2241 flavor: kubelet 2242 `, 2243 }, 2244 { 2245 "k8s-support with kubeproxy", 2246 ` 2247 plugs: 2248 - kubeproxy 2249 plugs: 2250 kubeproxy: 2251 interface: kubernetes-support 2252 flavor: kubeproxy 2253 `, 2254 }, 2255 { 2256 "greengrass-support with legacy-container flavor", 2257 ` 2258 plugs: 2259 - greengrass 2260 plugs: 2261 greengrass: 2262 interface: greengrass-support 2263 flavor: legacy-container 2264 `, 2265 }, 2266 } 2267 2268 for _, t := range tt { 2269 comment := Commentf(t.comment) 2270 info := snaptest.MockSnap(c, packageHelloNoSrv+` 2271 svc1: 2272 daemon: simple 2273 `+t.plugSnippet, 2274 &snap.SideInfo{Revision: snap.R(12)}) 2275 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 2276 2277 err := wrappers.AddSnapServices(info, nil, progress.Null) 2278 c.Assert(err, IsNil, comment) 2279 c.Check(s.sysdLog, DeepEquals, [][]string{ 2280 {"daemon-reload"}, 2281 }, comment) 2282 2283 dir := filepath.Join(dirs.SnapMountDir, "hello-snap", "12.mount") 2284 c.Assert(svcFile, testutil.FileEquals, fmt.Sprintf(`[Unit] 2285 # Auto-generated, DO NOT EDIT 2286 Description=Service for snap application hello-snap.svc1 2287 Requires=%[1]s 2288 Wants=network.target 2289 After=%[1]s network.target snapd.apparmor.service 2290 X-Snappy=yes 2291 2292 [Service] 2293 EnvironmentFile=-/etc/environment 2294 ExecStart=/usr/bin/snap run hello-snap.svc1 2295 SyslogIdentifier=hello-snap.svc1 2296 Restart=on-failure 2297 WorkingDirectory=%[2]s/var/snap/hello-snap/12 2298 TimeoutStopSec=30 2299 Type=simple 2300 Delegate=true 2301 2302 [Install] 2303 WantedBy=multi-user.target 2304 `, 2305 systemd.EscapeUnitNamePath(dir), 2306 dirs.GlobalRootDir, 2307 ), comment) 2308 2309 s.sysdLog = nil 2310 err = wrappers.StopServices(info.Services(), nil, "", progress.Null, s.perfTimings) 2311 c.Assert(err, IsNil, comment) 2312 c.Assert(s.sysdLog, HasLen, 2, comment) 2313 c.Check(s.sysdLog, DeepEquals, [][]string{ 2314 {"stop", filepath.Base(svcFile)}, 2315 {"show", "--property=ActiveState", "snap.hello-snap.svc1.service"}, 2316 }, comment) 2317 2318 s.sysdLog = nil 2319 err = wrappers.RemoveSnapServices(info, progress.Null) 2320 c.Assert(err, IsNil, comment) 2321 c.Check(osutil.FileExists(svcFile), Equals, false, comment) 2322 c.Assert(s.sysdLog, HasLen, 2, comment) 2323 c.Check(s.sysdLog, DeepEquals, [][]string{ 2324 {"--no-reload", "disable", filepath.Base(svcFile)}, 2325 {"daemon-reload"}, 2326 }, comment) 2327 2328 s.sysdLog = nil 2329 } 2330 } 2331 2332 func (s *servicesTestSuite) TestAddSnapServicesAndRemoveUserDaemons(c *C) { 2333 info := snaptest.MockSnap(c, packageHelloNoSrv+` 2334 svc1: 2335 daemon: simple 2336 daemon-scope: user 2337 `, &snap.SideInfo{Revision: snap.R(12)}) 2338 svcFile := filepath.Join(s.tempdir, "/etc/systemd/user/snap.hello-snap.svc1.service") 2339 2340 err := wrappers.AddSnapServices(info, nil, progress.Null) 2341 c.Assert(err, IsNil) 2342 c.Check(s.sysdLog, DeepEquals, [][]string{ 2343 {"--user", "daemon-reload"}, 2344 }) 2345 2346 expected := "ExecStart=/usr/bin/snap run hello-snap.svc1" 2347 c.Check(svcFile, testutil.FileMatches, "(?ms).*^"+regexp.QuoteMeta(expected)) // check.v1 adds ^ and $ around the regexp provided 2348 2349 s.sysdLog = nil 2350 err = wrappers.StopServices(info.Services(), nil, "", progress.Null, s.perfTimings) 2351 c.Assert(err, IsNil) 2352 c.Assert(s.sysdLog, HasLen, 2) 2353 c.Check(s.sysdLog, DeepEquals, [][]string{ 2354 {"--user", "stop", filepath.Base(svcFile)}, 2355 {"--user", "show", "--property=ActiveState", "snap.hello-snap.svc1.service"}, 2356 }) 2357 2358 s.sysdLog = nil 2359 err = wrappers.RemoveSnapServices(info, progress.Null) 2360 c.Assert(err, IsNil) 2361 c.Check(osutil.FileExists(svcFile), Equals, false) 2362 c.Assert(s.sysdLog, HasLen, 2) 2363 c.Check(s.sysdLog, DeepEquals, [][]string{ 2364 {"--user", "--global", "--no-reload", "disable", filepath.Base(svcFile)}, 2365 {"--user", "daemon-reload"}, 2366 }) 2367 } 2368 2369 var snapdYaml = `name: snapd 2370 version: 1.0 2371 type: snapd 2372 ` 2373 2374 func (s *servicesTestSuite) TestRemoveSnapWithSocketsRemovesSocketsService(c *C) { 2375 info := snaptest.MockSnap(c, packageHelloNoSrv+` 2376 svc1: 2377 daemon: simple 2378 plugs: [network-bind] 2379 sockets: 2380 sock1: 2381 listen-stream: $SNAP_DATA/sock1.socket 2382 socket-mode: 0666 2383 sock2: 2384 listen-stream: $SNAP_COMMON/sock2.socket 2385 `, &snap.SideInfo{Revision: snap.R(12)}) 2386 2387 err := wrappers.AddSnapServices(info, nil, progress.Null) 2388 c.Assert(err, IsNil) 2389 2390 err = wrappers.StopServices(info.Services(), nil, "", &progress.Null, s.perfTimings) 2391 c.Assert(err, IsNil) 2392 2393 err = wrappers.RemoveSnapServices(info, &progress.Null) 2394 c.Assert(err, IsNil) 2395 2396 app := info.Apps["svc1"] 2397 c.Assert(app.Sockets, HasLen, 2) 2398 for _, socket := range app.Sockets { 2399 c.Check(osutil.FileExists(socket.File()), Equals, false) 2400 } 2401 } 2402 2403 func (s *servicesTestSuite) TestRemoveSnapPackageFallbackToKill(c *C) { 2404 restore := wrappers.MockKillWait(time.Millisecond) 2405 defer restore() 2406 2407 var sysdLog [][]string 2408 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 2409 // filter out the "systemctl show" that 2410 // StopServices generates 2411 if cmd[0] != "show" { 2412 sysdLog = append(sysdLog, cmd) 2413 return nil, nil 2414 } 2415 return []byte("ActiveState=active\n"), errors.New("mock systemctl error") 2416 }) 2417 defer r() 2418 2419 info := snaptest.MockSnap(c, `name: wat 2420 version: 42 2421 apps: 2422 wat: 2423 command: wat 2424 stop-timeout: 20ms 2425 daemon: forking 2426 `, &snap.SideInfo{Revision: snap.R(11)}) 2427 2428 err := wrappers.AddSnapServices(info, nil, progress.Null) 2429 c.Assert(err, IsNil) 2430 2431 sysdLog = nil 2432 2433 svcFName := "snap.wat.wat.service" 2434 2435 err = wrappers.StopServices(info.Services(), nil, "", progress.Null, s.perfTimings) 2436 c.Assert(err, ErrorMatches, "mock systemctl error") 2437 2438 c.Check(sysdLog, DeepEquals, [][]string{ 2439 {"stop", svcFName}, 2440 }) 2441 } 2442 2443 func (s *servicesTestSuite) TestRemoveSnapPackageUserDaemonStopFailure(c *C) { 2444 var sysdLog [][]string 2445 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 2446 // filter out the "systemctl --user show" that 2447 // StopServices generates 2448 if cmd[0] == "--user" && cmd[1] != "show" { 2449 sysdLog = append(sysdLog, cmd) 2450 } 2451 if cmd[0] == "--user" && cmd[1] == "stop" { 2452 return nil, fmt.Errorf("user unit stop failed") 2453 } 2454 return []byte("ActiveState=active\n"), nil 2455 }) 2456 defer r() 2457 2458 info := snaptest.MockSnap(c, `name: wat 2459 version: 42 2460 apps: 2461 wat: 2462 command: wat 2463 stop-timeout: 20ms 2464 daemon: forking 2465 daemon-scope: user 2466 `, &snap.SideInfo{Revision: snap.R(11)}) 2467 2468 err := wrappers.AddSnapServices(info, nil, progress.Null) 2469 c.Assert(err, IsNil) 2470 2471 sysdLog = nil 2472 2473 svcFName := "snap.wat.wat.service" 2474 2475 err = wrappers.StopServices(info.Services(), nil, "", progress.Null, s.perfTimings) 2476 c.Check(err, ErrorMatches, "some user services failed to stop") 2477 c.Check(sysdLog, DeepEquals, [][]string{ 2478 {"--user", "stop", svcFName}, 2479 }) 2480 } 2481 2482 func (s *servicesTestSuite) TestServicesEnableState(c *C) { 2483 info := snaptest.MockSnap(c, packageHello+` 2484 svc2: 2485 command: bin/hello 2486 daemon: forking 2487 svc3: 2488 command: bin/hello 2489 daemon: simple 2490 daemon-scope: user 2491 `, &snap.SideInfo{Revision: snap.R(12)}) 2492 svc1File := "snap.hello-snap.svc1.service" 2493 svc2File := "snap.hello-snap.svc2.service" 2494 2495 s.systemctlRestorer() 2496 r := testutil.MockCommand(c, "systemctl", `#!/bin/sh 2497 if [ "$1" = "--root" ]; then 2498 # shifting by 2 also drops the temp dir arg to --root 2499 shift 2 2500 fi 2501 2502 case "$1" in 2503 is-enabled) 2504 case "$2" in 2505 "snap.hello-snap.svc1.service") 2506 echo "disabled" 2507 exit 1 2508 ;; 2509 "snap.hello-snap.svc2.service") 2510 echo "enabled" 2511 exit 0 2512 ;; 2513 *) 2514 echo "unexpected is-enabled of service $2" 2515 exit 2 2516 ;; 2517 esac 2518 ;; 2519 *) 2520 echo "unexpected op $*" 2521 exit 2 2522 esac 2523 2524 exit 1 2525 `) 2526 defer r.Restore() 2527 2528 states, err := wrappers.ServicesEnableState(info, progress.Null) 2529 c.Assert(err, IsNil) 2530 2531 c.Assert(states, DeepEquals, map[string]bool{ 2532 "svc1": false, 2533 "svc2": true, 2534 }) 2535 2536 // the calls could be out of order in the list, since iterating over a map 2537 // is non-deterministic, so manually check each call 2538 c.Assert(r.Calls(), HasLen, 2) 2539 for _, call := range r.Calls() { 2540 c.Assert(call, HasLen, 3) 2541 c.Assert(call[:2], DeepEquals, []string{"systemctl", "is-enabled"}) 2542 switch call[2] { 2543 case svc1File, svc2File: 2544 default: 2545 c.Errorf("unknown service for systemctl call: %s", call[2]) 2546 } 2547 } 2548 } 2549 2550 func (s *servicesTestSuite) TestServicesEnableStateFail(c *C) { 2551 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 2552 svc1File := "snap.hello-snap.svc1.service" 2553 2554 s.systemctlRestorer() 2555 r := testutil.MockCommand(c, "systemctl", `#!/bin/sh 2556 if [ "$1" = "--root" ]; then 2557 # shifting by 2 also drops the temp dir arg to --root 2558 shift 2 2559 fi 2560 2561 case "$1" in 2562 is-enabled) 2563 case "$2" in 2564 "snap.hello-snap.svc1.service") 2565 echo "whoops" 2566 exit 1 2567 ;; 2568 *) 2569 echo "unexpected is-enabled of service $2" 2570 exit 2 2571 ;; 2572 esac 2573 ;; 2574 *) 2575 echo "unexpected op $*" 2576 exit 2 2577 esac 2578 2579 exit 1 2580 `) 2581 defer r.Restore() 2582 2583 _, err := wrappers.ServicesEnableState(info, progress.Null) 2584 c.Assert(err, ErrorMatches, ".*is-enabled snap.hello-snap.svc1.service\\] failed with exit status 1: whoops\n.*") 2585 2586 c.Assert(r.Calls(), DeepEquals, [][]string{ 2587 {"systemctl", "is-enabled", svc1File}, 2588 }) 2589 } 2590 2591 func (s *servicesTestSuite) TestAddSnapServicesWithDisabledServices(c *C) { 2592 info := snaptest.MockSnap(c, packageHello+` 2593 svc2: 2594 command: bin/hello 2595 daemon: forking 2596 `, &snap.SideInfo{Revision: snap.R(12)}) 2597 2598 s.systemctlRestorer() 2599 r := testutil.MockCommand(c, "systemctl", `#!/bin/sh 2600 if [ "$1" = "--root" ]; then 2601 shift 2 2602 fi 2603 if [ "$1" = "--no-reload" ]; then 2604 shift 2605 fi 2606 2607 case "$1" in 2608 enable) 2609 case "$2" in 2610 "snap.hello-snap.svc1.service") 2611 echo "unexpected enable of disabled service $2" 2612 exit 1 2613 ;; 2614 "snap.hello-snap.svc2.service") 2615 exit 0 2616 ;; 2617 *) 2618 echo "unexpected enable of service $2" 2619 exit 1 2620 ;; 2621 esac 2622 ;; 2623 start) 2624 case "$2" in 2625 "snap.hello-snap.svc2.service") 2626 exit 0 2627 ;; 2628 *) 2629 echo "unexpected start of service $2" 2630 exit 1 2631 ;; 2632 esac 2633 ;; 2634 daemon-reload) 2635 exit 0 2636 ;; 2637 *) 2638 echo "unexpected op $*" 2639 exit 2 2640 esac 2641 exit 2 2642 `) 2643 defer r.Restore() 2644 2645 // svc1 will be disabled 2646 disabledSvcs := []string{"svc1"} 2647 2648 err := wrappers.AddSnapServices(info, nil, progress.Null) 2649 c.Assert(err, IsNil) 2650 2651 c.Assert(r.Calls(), DeepEquals, [][]string{ 2652 {"systemctl", "daemon-reload"}, 2653 }) 2654 2655 r.ForgetCalls() 2656 2657 flags := &wrappers.StartServicesFlags{Enable: true} 2658 err = wrappers.StartServices(info.Services(), disabledSvcs, flags, progress.Null, s.perfTimings) 2659 c.Assert(err, IsNil) 2660 2661 // only svc2 should be enabled 2662 c.Assert(r.Calls(), DeepEquals, [][]string{ 2663 {"systemctl", "--no-reload", "enable", "snap.hello-snap.svc2.service"}, 2664 {"systemctl", "daemon-reload"}, 2665 {"systemctl", "start", "snap.hello-snap.svc2.service"}, 2666 }) 2667 } 2668 2669 func (s *servicesTestSuite) TestAddSnapServicesWithPreseed(c *C) { 2670 opts := &wrappers.AddSnapServicesOptions{Preseeding: true} 2671 2672 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 2673 2674 s.systemctlRestorer() 2675 r := testutil.MockCommand(c, "systemctl", "exit 1") 2676 defer r.Restore() 2677 2678 err := wrappers.AddSnapServices(info, opts, progress.Null) 2679 c.Assert(err, IsNil) 2680 2681 // file was created 2682 svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.*.service")) 2683 c.Check(svcFiles, HasLen, 1) 2684 2685 // but systemctl was not called 2686 c.Assert(r.Calls(), HasLen, 0) 2687 } 2688 2689 func (s *servicesTestSuite) TestStopServicesWithSockets(c *C) { 2690 var sysServices, userServices []string 2691 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 2692 if cmd[0] == "stop" { 2693 sysServices = append(sysServices, cmd[1:]...) 2694 } else if cmd[0] == "--user" && cmd[1] == "stop" { 2695 userServices = append(userServices, cmd[2:]...) 2696 } 2697 return []byte("ActiveState=inactive\n"), nil 2698 }) 2699 defer r() 2700 2701 info := snaptest.MockSnap(c, packageHelloNoSrv+` 2702 svc1: 2703 daemon: simple 2704 plugs: [network-bind] 2705 sockets: 2706 sock1: 2707 listen-stream: $SNAP_COMMON/sock1.socket 2708 socket-mode: 0666 2709 sock2: 2710 listen-stream: $SNAP_DATA/sock2.socket 2711 svc2: 2712 daemon: simple 2713 daemon-scope: user 2714 plugs: [network-bind] 2715 sockets: 2716 sock1: 2717 listen-stream: $SNAP_USER_COMMON/sock1.socket 2718 socket-mode: 0666 2719 sock2: 2720 listen-stream: $SNAP_USER_DATA/sock2.socket 2721 `, &snap.SideInfo{Revision: snap.R(12)}) 2722 2723 err := wrappers.AddSnapServices(info, nil, progress.Null) 2724 c.Assert(err, IsNil) 2725 2726 sysServices = nil 2727 userServices = nil 2728 2729 err = wrappers.StopServices(info.Services(), nil, "", &progress.Null, s.perfTimings) 2730 c.Assert(err, IsNil) 2731 2732 sort.Strings(sysServices) 2733 c.Check(sysServices, DeepEquals, []string{ 2734 "snap.hello-snap.svc1.service", 2735 "snap.hello-snap.svc1.sock1.socket", "snap.hello-snap.svc1.sock2.socket", 2736 }) 2737 sort.Strings(userServices) 2738 c.Check(userServices, DeepEquals, []string{ 2739 "snap.hello-snap.svc2.service", 2740 "snap.hello-snap.svc2.sock1.socket", "snap.hello-snap.svc2.sock2.socket", 2741 }) 2742 } 2743 2744 func (s *servicesTestSuite) TestStartServices(c *C) { 2745 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 2746 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 2747 2748 flags := &wrappers.StartServicesFlags{Enable: true} 2749 err := wrappers.StartServices(info.Services(), nil, flags, &progress.Null, s.perfTimings) 2750 c.Assert(err, IsNil) 2751 2752 c.Check(s.sysdLog, DeepEquals, [][]string{ 2753 {"--no-reload", "enable", filepath.Base(svcFile)}, 2754 {"daemon-reload"}, 2755 {"start", filepath.Base(svcFile)}, 2756 }) 2757 } 2758 2759 func (s *servicesTestSuite) TestStartServicesNoEnable(c *C) { 2760 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 2761 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 2762 2763 flags := &wrappers.StartServicesFlags{Enable: false} 2764 err := wrappers.StartServices(info.Services(), nil, flags, &progress.Null, s.perfTimings) 2765 c.Assert(err, IsNil) 2766 2767 c.Check(s.sysdLog, DeepEquals, [][]string{ 2768 {"start", filepath.Base(svcFile)}, 2769 }) 2770 } 2771 2772 func (s *servicesTestSuite) TestStartServicesUserDaemons(c *C) { 2773 info := snaptest.MockSnap(c, packageHelloNoSrv+` 2774 svc1: 2775 daemon: simple 2776 daemon-scope: user 2777 `, &snap.SideInfo{Revision: snap.R(12)}) 2778 svcFile := filepath.Join(s.tempdir, "/etc/systemd/user/snap.hello-snap.svc1.service") 2779 2780 flags := &wrappers.StartServicesFlags{Enable: true} 2781 err := wrappers.StartServices(info.Services(), nil, flags, &progress.Null, s.perfTimings) 2782 c.Assert(err, IsNil) 2783 2784 c.Assert(s.sysdLog, DeepEquals, [][]string{ 2785 {"--user", "--global", "--no-reload", "enable", filepath.Base(svcFile)}, 2786 {"--user", "start", filepath.Base(svcFile)}, 2787 }) 2788 } 2789 2790 func (s *servicesTestSuite) TestStartServicesEnabledConditional(c *C) { 2791 info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) 2792 svcFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.service") 2793 2794 flags := &wrappers.StartServicesFlags{} 2795 c.Check(wrappers.StartServices(info.Services(), nil, flags, progress.Null, s.perfTimings), IsNil) 2796 c.Check(s.sysdLog, DeepEquals, [][]string{{"start", filepath.Base(svcFile)}}) 2797 } 2798 2799 func (s *servicesTestSuite) TestNoStartDisabledServices(c *C) { 2800 svc2Name := "snap.hello-snap.svc2.service" 2801 2802 info := snaptest.MockSnap(c, packageHello+` 2803 svc2: 2804 command: bin/hello 2805 daemon: simple 2806 `, &snap.SideInfo{Revision: snap.R(12)}) 2807 2808 s.systemctlRestorer() 2809 r := testutil.MockCommand(c, "systemctl", `#!/bin/sh 2810 if [ "$1" = "--root" ]; then 2811 shift 2 2812 fi 2813 if [ "$1" = "--no-reload" ]; then 2814 shift 2815 fi 2816 2817 case "$1" in 2818 start) 2819 if [ "$2" = "snap.hello-snap.svc2.service" ]; then 2820 exit 0 2821 fi 2822 echo "unexpected start of service $2" 2823 exit 1 2824 ;; 2825 enable) 2826 if [ "$2" = "snap.hello-snap.svc2.service" ]; then 2827 exit 0 2828 fi 2829 echo "unexpected enable of service $2" 2830 exit 1 2831 ;; 2832 daemon-reload) 2833 ;; 2834 *) 2835 echo "unexpected call $*" 2836 exit 2 2837 esac 2838 `) 2839 defer r.Restore() 2840 2841 flags := &wrappers.StartServicesFlags{Enable: true} 2842 err := wrappers.StartServices(info.Services(), []string{"svc1"}, flags, &progress.Null, s.perfTimings) 2843 c.Assert(err, IsNil) 2844 c.Assert(r.Calls(), DeepEquals, [][]string{ 2845 {"systemctl", "--no-reload", "enable", svc2Name}, 2846 {"systemctl", "daemon-reload"}, 2847 {"systemctl", "start", svc2Name}, 2848 }) 2849 } 2850 2851 func (s *servicesTestSuite) TestAddSnapMultiServicesFailCreateCleanup(c *C) { 2852 // validity check: there are no service files 2853 svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service")) 2854 c.Check(svcFiles, HasLen, 0) 2855 2856 info := snaptest.MockSnap(c, packageHello+` 2857 svc2: 2858 daemon: potato 2859 `, &snap.SideInfo{Revision: snap.R(12)}) 2860 2861 err := wrappers.AddSnapServices(info, nil, progress.Null) 2862 c.Assert(err, ErrorMatches, ".*potato.*") 2863 2864 // the services are cleaned up 2865 svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service")) 2866 c.Check(svcFiles, HasLen, 0) 2867 2868 // *either* the first service failed validation, and nothing 2869 // was done, *or* the second one failed, and the first one was 2870 // enabled before the second failed, and disabled after. 2871 if len(s.sysdLog) > 0 { 2872 // the second service failed validation 2873 c.Check(s.sysdLog, DeepEquals, [][]string{ 2874 {"daemon-reload"}, 2875 }) 2876 } 2877 } 2878 2879 func (s *servicesTestSuite) TestMultiServicesFailEnableCleanup(c *C) { 2880 var sysdLog [][]string 2881 svc1Name := "snap.hello-snap.svc1.service" 2882 svc2Name := "snap.hello-snap.svc2.service" 2883 numEnables := 0 2884 2885 // validity check: there are no service files 2886 svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service")) 2887 c.Check(svcFiles, HasLen, 0) 2888 2889 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 2890 c.Logf("cmd: %q", cmd) 2891 sysdLog = append(sysdLog, cmd) 2892 sdcmd := cmd[0] 2893 if sdcmd == "show" { 2894 return []byte("ActiveState=inactive"), nil 2895 } 2896 if strings.HasPrefix(sdcmd, "--") { 2897 c.Assert(len(sdcmd) >= 2, Equals, true) 2898 sdcmd = cmd[1] 2899 } 2900 switch sdcmd { 2901 case "enable": 2902 numEnables++ 2903 c.Assert(cmd, HasLen, 4) 2904 if cmd[2] == svc2Name { 2905 svc1Name, svc2Name = svc2Name, svc1Name 2906 } 2907 return nil, fmt.Errorf("failed") 2908 case "disable", "daemon-reload", "stop": 2909 return nil, nil 2910 default: 2911 panic(fmt.Sprintf("unexpected systemctl command %q", cmd)) 2912 } 2913 }) 2914 defer r() 2915 2916 info := snaptest.MockSnap(c, packageHello+` 2917 svc2: 2918 command: bin/hello 2919 daemon: simple 2920 `, &snap.SideInfo{Revision: snap.R(12)}) 2921 2922 err := wrappers.AddSnapServices(info, nil, progress.Null) 2923 c.Assert(err, IsNil) 2924 2925 svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service")) 2926 c.Check(svcFiles, HasLen, 2) 2927 2928 flags := &wrappers.StartServicesFlags{Enable: true} 2929 err = wrappers.StartServices(info.Services(), nil, flags, progress.Null, s.perfTimings) 2930 c.Assert(err, ErrorMatches, "failed") 2931 2932 c.Check(sysdLog, DeepEquals, [][]string{ 2933 {"daemon-reload"}, // from AddSnapServices 2934 {"--no-reload", "enable", svc1Name, svc2Name}, 2935 {"--no-reload", "disable", svc1Name, svc2Name}, 2936 {"daemon-reload"}, // cleanup 2937 }) 2938 } 2939 2940 func (s *servicesTestSuite) TestAddSnapMultiServicesStartFailOnSystemdReloadCleanup(c *C) { 2941 // this test might be overdoing it (it's mostly covering the same ground as the previous one), but ... :-) 2942 var sysdLog [][]string 2943 2944 // validity check: there are no service files 2945 svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service")) 2946 c.Check(svcFiles, HasLen, 0) 2947 2948 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 2949 sysdLog = append(sysdLog, cmd) 2950 if cmd[0] == "daemon-reload" { 2951 return nil, fmt.Errorf("failed") 2952 } 2953 c.Fatalf("unexpected systemctl call") 2954 return nil, nil 2955 2956 }) 2957 defer r() 2958 2959 info := snaptest.MockSnap(c, packageHello+` 2960 svc2: 2961 command: bin/hello 2962 daemon: simple 2963 `, &snap.SideInfo{Revision: snap.R(12)}) 2964 2965 err := wrappers.AddSnapServices(info, nil, progress.Null) 2966 c.Assert(err, ErrorMatches, "failed") 2967 2968 // the services are cleaned up 2969 svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "snap.hello-snap.*.service")) 2970 c.Check(svcFiles, HasLen, 0) 2971 c.Check(sysdLog, DeepEquals, [][]string{ 2972 {"daemon-reload"}, // this one fails 2973 {"daemon-reload"}, // reload as part of cleanup after removal 2974 }) 2975 } 2976 2977 func (s *servicesTestSuite) TestAddSnapMultiUserServicesFailEnableCleanup(c *C) { 2978 var sysdLog [][]string 2979 2980 // validity check: there are no service files 2981 svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapUserServicesDir, "snap.hello-snap.*.service")) 2982 c.Check(svcFiles, HasLen, 0) 2983 2984 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 2985 sysdLog = append(sysdLog, cmd) 2986 if len(cmd) >= 1 && cmd[0] == "--user" { 2987 cmd = cmd[1:] 2988 } 2989 if len(cmd) >= 1 && cmd[0] == "--global" { 2990 cmd = cmd[1:] 2991 } 2992 sdcmd := cmd[0] 2993 if len(cmd) >= 2 { 2994 sdcmd = cmd[len(cmd)-2] 2995 } 2996 switch sdcmd { 2997 case "daemon-reload": 2998 return nil, fmt.Errorf("failed") 2999 default: 3000 panic("unexpected systemctl command " + sdcmd) 3001 } 3002 }) 3003 defer r() 3004 3005 info := snaptest.MockSnap(c, packageHelloNoSrv+` 3006 svc1: 3007 command: bin/hello 3008 daemon: simple 3009 daemon-scope: user 3010 svc2: 3011 command: bin/hello 3012 daemon: simple 3013 daemon-scope: user 3014 `, &snap.SideInfo{Revision: snap.R(12)}) 3015 3016 err := wrappers.AddSnapServices(info, nil, progress.Null) 3017 c.Assert(err, ErrorMatches, "cannot reload daemon: failed") 3018 3019 // the services are cleaned up 3020 svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapUserServicesDir, "snap.hello-snap.*.service")) 3021 c.Check(svcFiles, HasLen, 0) 3022 c.Check(sysdLog, DeepEquals, [][]string{ 3023 {"--user", "daemon-reload"}, 3024 {"--user", "daemon-reload"}, 3025 }) 3026 } 3027 3028 func (s *servicesTestSuite) TestAddSnapMultiUserServicesStartFailOnSystemdReloadCleanup(c *C) { 3029 // this test might be overdoing it (it's mostly covering the same ground as the previous one), but ... :-) 3030 var sysdLog [][]string 3031 svc1Name := "snap.hello-snap.svc1.service" 3032 svc2Name := "snap.hello-snap.svc2.service" 3033 3034 // validity check: there are no service files 3035 svcFiles, _ := filepath.Glob(filepath.Join(dirs.SnapUserServicesDir, "snap.hello-snap.*.service")) 3036 c.Check(svcFiles, HasLen, 0) 3037 3038 first := true 3039 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 3040 sysdLog = append(sysdLog, cmd) 3041 if len(cmd) < 3 { 3042 return nil, fmt.Errorf("failed") 3043 } 3044 if first { 3045 first = false 3046 if cmd[len(cmd)-1] == svc2Name { 3047 // the services are being iterated in the "wrong" order 3048 svc1Name, svc2Name = svc2Name, svc1Name 3049 } 3050 } 3051 return nil, nil 3052 3053 }) 3054 defer r() 3055 3056 info := snaptest.MockSnap(c, packageHelloNoSrv+` 3057 svc1: 3058 command: bin/hello 3059 daemon: simple 3060 daemon-scope: user 3061 svc2: 3062 command: bin/hello 3063 daemon: simple 3064 daemon-scope: user 3065 `, &snap.SideInfo{Revision: snap.R(12)}) 3066 3067 err := wrappers.AddSnapServices(info, nil, progress.Null) 3068 c.Assert(err, ErrorMatches, "cannot reload daemon: failed") 3069 3070 // the services are cleaned up 3071 svcFiles, _ = filepath.Glob(filepath.Join(dirs.SnapUserServicesDir, "snap.hello-snap.*.service")) 3072 c.Check(svcFiles, HasLen, 0) 3073 c.Check(sysdLog, DeepEquals, [][]string{ 3074 {"--user", "daemon-reload"}, // this one fails 3075 {"--user", "daemon-reload"}, // so does this one :-) 3076 }) 3077 } 3078 3079 func (s *servicesTestSuite) TestAddSnapSocketFiles(c *C) { 3080 info := snaptest.MockSnap(c, packageHelloNoSrv+` 3081 svc1: 3082 daemon: simple 3083 plugs: [network-bind] 3084 sockets: 3085 sock1: 3086 listen-stream: $SNAP_COMMON/sock1.socket 3087 socket-mode: 0666 3088 sock2: 3089 listen-stream: $SNAP_DATA/sock2.socket 3090 sock3: 3091 listen-stream: $XDG_RUNTIME_DIR/sock3.socket 3092 3093 `, &snap.SideInfo{Revision: snap.R(12)}) 3094 3095 sock1File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.sock1.socket") 3096 sock2File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.sock2.socket") 3097 sock3File := filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc1.sock3.socket") 3098 3099 err := wrappers.AddSnapServices(info, nil, progress.Null) 3100 c.Assert(err, IsNil) 3101 3102 expected := fmt.Sprintf( 3103 `[Socket] 3104 Service=snap.hello-snap.svc1.service 3105 FileDescriptorName=sock1 3106 ListenStream=%s 3107 SocketMode=0666 3108 3109 `, filepath.Join(s.tempdir, "/var/snap/hello-snap/common/sock1.socket")) 3110 c.Check(sock1File, testutil.FileContains, expected) 3111 3112 expected = fmt.Sprintf( 3113 `[Socket] 3114 Service=snap.hello-snap.svc1.service 3115 FileDescriptorName=sock2 3116 ListenStream=%s 3117 3118 `, filepath.Join(s.tempdir, "/var/snap/hello-snap/12/sock2.socket")) 3119 c.Check(sock2File, testutil.FileContains, expected) 3120 3121 expected = fmt.Sprintf( 3122 `[Socket] 3123 Service=snap.hello-snap.svc1.service 3124 FileDescriptorName=sock3 3125 ListenStream=%s 3126 3127 `, filepath.Join(s.tempdir, "/run/user/0/snap.hello-snap/sock3.socket")) 3128 c.Check(sock3File, testutil.FileContains, expected) 3129 } 3130 3131 func (s *servicesTestSuite) TestAddSnapUserSocketFiles(c *C) { 3132 info := snaptest.MockSnap(c, packageHelloNoSrv+` 3133 svc1: 3134 daemon: simple 3135 daemon-scope: user 3136 plugs: [network-bind] 3137 sockets: 3138 sock1: 3139 listen-stream: $SNAP_USER_COMMON/sock1.socket 3140 socket-mode: 0666 3141 sock2: 3142 listen-stream: $SNAP_USER_DATA/sock2.socket 3143 sock3: 3144 listen-stream: $XDG_RUNTIME_DIR/sock3.socket 3145 `, &snap.SideInfo{Revision: snap.R(12)}) 3146 3147 sock1File := filepath.Join(s.tempdir, "/etc/systemd/user/snap.hello-snap.svc1.sock1.socket") 3148 sock2File := filepath.Join(s.tempdir, "/etc/systemd/user/snap.hello-snap.svc1.sock2.socket") 3149 sock3File := filepath.Join(s.tempdir, "/etc/systemd/user/snap.hello-snap.svc1.sock3.socket") 3150 3151 err := wrappers.AddSnapServices(info, nil, progress.Null) 3152 c.Assert(err, IsNil) 3153 3154 expected := `[Socket] 3155 Service=snap.hello-snap.svc1.service 3156 FileDescriptorName=sock1 3157 ListenStream=%h/snap/hello-snap/common/sock1.socket 3158 SocketMode=0666 3159 3160 ` 3161 c.Check(sock1File, testutil.FileContains, expected) 3162 3163 expected = `[Socket] 3164 Service=snap.hello-snap.svc1.service 3165 FileDescriptorName=sock2 3166 ListenStream=%h/snap/hello-snap/12/sock2.socket 3167 3168 ` 3169 c.Check(sock2File, testutil.FileContains, expected) 3170 3171 expected = `[Socket] 3172 Service=snap.hello-snap.svc1.service 3173 FileDescriptorName=sock3 3174 ListenStream=%t/snap.hello-snap/sock3.socket 3175 3176 ` 3177 c.Check(sock3File, testutil.FileContains, expected) 3178 } 3179 3180 func (s *servicesTestSuite) TestStartSnapMultiServicesFailStartCleanup(c *C) { 3181 var sysdLog [][]string 3182 svc1Name := "snap.hello-snap.svc1.service" 3183 svc2Name := "snap.hello-snap.svc2.service" 3184 3185 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 3186 sysdLog = append(sysdLog, cmd) 3187 if len(cmd) >= 2 && cmd[0] == "start" { 3188 name := cmd[len(cmd)-1] 3189 if name == svc2Name { 3190 return nil, fmt.Errorf("failed") 3191 } 3192 } 3193 return []byte("ActiveState=inactive\n"), nil 3194 }) 3195 defer r() 3196 3197 info := snaptest.MockSnap(c, packageHello+` 3198 svc2: 3199 command: bin/hello 3200 daemon: simple 3201 `, &snap.SideInfo{Revision: snap.R(12)}) 3202 3203 svcs := info.Services() 3204 c.Assert(svcs, HasLen, 2) 3205 if svcs[0].Name == "svc2" { 3206 svcs[0], svcs[1] = svcs[1], svcs[0] 3207 } 3208 3209 flags := &wrappers.StartServicesFlags{Enable: true} 3210 err := wrappers.StartServices(svcs, nil, flags, &progress.Null, s.perfTimings) 3211 c.Assert(err, ErrorMatches, "failed") 3212 c.Assert(sysdLog, HasLen, 10, Commentf("len: %v calls: %v", len(sysdLog), sysdLog)) 3213 c.Check(sysdLog, DeepEquals, [][]string{ 3214 {"--no-reload", "enable", svc1Name, svc2Name}, 3215 {"daemon-reload"}, 3216 {"start", svc1Name}, 3217 {"start", svc2Name}, // one of the services fails 3218 {"stop", svc2Name}, 3219 {"show", "--property=ActiveState", svc2Name}, 3220 {"stop", svc1Name}, 3221 {"show", "--property=ActiveState", svc1Name}, 3222 {"--no-reload", "disable", svc1Name, svc2Name}, 3223 {"daemon-reload"}, 3224 }, Commentf("calls: %v", sysdLog)) 3225 } 3226 3227 func (s *servicesTestSuite) TestStartSnapMultiServicesFailStartCleanupWithSockets(c *C) { 3228 var sysdLog [][]string 3229 svc1Name := "snap.hello-snap.svc1.service" 3230 svc2Name := "snap.hello-snap.svc2.service" 3231 svc2SocketName := "snap.hello-snap.svc2.sock1.socket" 3232 svc3Name := "snap.hello-snap.svc3.service" 3233 svc3SocketName := "snap.hello-snap.svc3.sock1.socket" 3234 3235 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 3236 sysdLog = append(sysdLog, cmd) 3237 c.Logf("call: %v", cmd) 3238 if len(cmd) >= 2 && cmd[0] == "start" && cmd[1] == svc3SocketName { 3239 // svc3 socket fails 3240 return nil, fmt.Errorf("failed") 3241 } 3242 return []byte("ActiveState=inactive\n"), nil 3243 }) 3244 defer r() 3245 3246 info := snaptest.MockSnap(c, packageHello+` 3247 svc2: 3248 command: bin/hello 3249 daemon: simple 3250 sockets: 3251 sock1: 3252 listen-stream: $SNAP_COMMON/sock1.socket 3253 socket-mode: 0666 3254 svc3: 3255 command: bin/hello 3256 daemon: simple 3257 sockets: 3258 sock1: 3259 listen-stream: $SNAP_COMMON/sock1.socket 3260 socket-mode: 0666 3261 `, &snap.SideInfo{Revision: snap.R(12)}) 3262 3263 // ensure desired order 3264 apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"], info.Apps["svc3"]} 3265 3266 flags := &wrappers.StartServicesFlags{Enable: true} 3267 err := wrappers.StartServices(apps, nil, flags, &progress.Null, s.perfTimings) 3268 c.Assert(err, ErrorMatches, "failed") 3269 c.Logf("sysdlog: %v", sysdLog) 3270 c.Assert(sysdLog, HasLen, 14, Commentf("len: %v calls: %v", len(sysdLog), sysdLog)) 3271 c.Check(sysdLog, DeepEquals, [][]string{ 3272 {"--no-reload", "enable", svc2SocketName, svc3SocketName, svc1Name}, 3273 {"daemon-reload"}, 3274 {"start", svc2SocketName}, 3275 {"start", svc3SocketName}, // start failed, what follows is the cleanup 3276 {"stop", svc3SocketName, svc3Name}, 3277 {"show", "--property=ActiveState", svc3SocketName}, 3278 {"show", "--property=ActiveState", svc3Name}, 3279 {"stop", svc2SocketName, svc2Name}, 3280 {"show", "--property=ActiveState", svc2SocketName}, 3281 {"show", "--property=ActiveState", svc2Name}, 3282 {"stop", svc1Name}, 3283 {"show", "--property=ActiveState", svc1Name}, 3284 {"--no-reload", "disable", svc2SocketName, svc3SocketName, svc1Name}, 3285 {"daemon-reload"}, 3286 }, Commentf("calls: %v", sysdLog)) 3287 } 3288 3289 func (s *servicesTestSuite) TestStartSnapMultiServicesFailStartNoEnableNoDisable(c *C) { 3290 // start services is called without the enable flag (eg. as during snap 3291 // start foo), in which case, the cleanup does not call disable (unless 3292 // there are timers and socket, in which the buggy behavior kicks in, 3293 // but only for those units) 3294 var sysdLog [][]string 3295 svc1Name := "snap.hello-snap.svc1.service" 3296 svc2Name := "snap.hello-snap.svc2.service" 3297 svc2SocketName := "snap.hello-snap.svc2.sock1.socket" 3298 svc3Name := "snap.hello-snap.svc3.service" 3299 svc3SocketName := "snap.hello-snap.svc3.sock1.socket" 3300 3301 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 3302 sysdLog = append(sysdLog, cmd) 3303 c.Logf("call: %v", cmd) 3304 if len(cmd) >= 2 && cmd[0] == "start" && cmd[1] == svc1Name { 3305 // svc1 fails 3306 return nil, fmt.Errorf("failed") 3307 } 3308 return []byte("ActiveState=inactive\n"), nil 3309 }) 3310 defer r() 3311 3312 info := snaptest.MockSnap(c, packageHello+` 3313 svc2: 3314 command: bin/hello 3315 daemon: simple 3316 sockets: 3317 sock1: 3318 listen-stream: $SNAP_COMMON/sock1.socket 3319 socket-mode: 0666 3320 svc3: 3321 command: bin/hello 3322 daemon: simple 3323 sockets: 3324 sock1: 3325 listen-stream: $SNAP_COMMON/sock1.socket 3326 socket-mode: 0666 3327 `, &snap.SideInfo{Revision: snap.R(12)}) 3328 3329 // ensure desired order 3330 apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"], info.Apps["svc3"]} 3331 3332 // no enable 3333 flags := &wrappers.StartServicesFlags{Enable: false} 3334 err := wrappers.StartServices(apps, nil, flags, &progress.Null, s.perfTimings) 3335 c.Assert(err, ErrorMatches, "failed") 3336 c.Logf("sysdlog: %v", sysdLog) 3337 c.Assert(sysdLog, HasLen, 15, Commentf("len: %v calls: %v", len(sysdLog), sysdLog)) 3338 c.Check(sysdLog, DeepEquals, [][]string{ 3339 {"--no-reload", "enable", svc2SocketName, svc3SocketName}, 3340 {"daemon-reload"}, 3341 {"start", svc2SocketName}, 3342 {"start", svc3SocketName}, 3343 {"start", svc1Name}, // start failed, what follows is the cleanup 3344 {"stop", svc3SocketName, svc3Name}, 3345 {"show", "--property=ActiveState", svc3SocketName}, 3346 {"show", "--property=ActiveState", svc3Name}, 3347 {"stop", svc2SocketName, svc2Name}, 3348 {"show", "--property=ActiveState", svc2SocketName}, 3349 {"show", "--property=ActiveState", svc2Name}, 3350 {"stop", svc1Name}, 3351 {"show", "--property=ActiveState", svc1Name}, 3352 {"--no-reload", "disable", svc2SocketName, svc3SocketName}, 3353 {"daemon-reload"}, 3354 }, Commentf("calls: %v", sysdLog)) 3355 } 3356 3357 func (s *servicesTestSuite) TestStartSnapMultiUserServicesFailStartCleanup(c *C) { 3358 var sysdLog [][]string 3359 svc1Name := "snap.hello-snap.svc1.service" 3360 svc2Name := "snap.hello-snap.svc2.service" 3361 3362 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 3363 sysdLog = append(sysdLog, cmd) 3364 if len(cmd) >= 3 && cmd[0] == "--user" && cmd[1] == "start" { 3365 name := cmd[len(cmd)-1] 3366 if name == svc2Name { 3367 return nil, fmt.Errorf("failed") 3368 } 3369 } 3370 return []byte("ActiveState=inactive\n"), nil 3371 }) 3372 defer r() 3373 3374 info := snaptest.MockSnap(c, packageHelloNoSrv+` 3375 svc1: 3376 command: bin/hello 3377 daemon: simple 3378 daemon-scope: user 3379 svc2: 3380 command: bin/hello 3381 daemon: simple 3382 daemon-scope: user 3383 `, &snap.SideInfo{Revision: snap.R(12)}) 3384 3385 svcs := info.Services() 3386 c.Assert(svcs, HasLen, 2) 3387 if svcs[0].Name == "svc2" { 3388 svcs[0], svcs[1] = svcs[1], svcs[0] 3389 } 3390 flags := &wrappers.StartServicesFlags{Enable: true} 3391 err := wrappers.StartServices(svcs, nil, flags, &progress.Null, s.perfTimings) 3392 c.Assert(err, ErrorMatches, "some user services failed to start") 3393 c.Assert(sysdLog, HasLen, 10, Commentf("len: %v calls: %v", len(sysdLog), sysdLog)) 3394 c.Check(sysdLog, DeepEquals, [][]string{ 3395 {"--user", "--global", "--no-reload", "enable", svc1Name, svc2Name}, 3396 {"--user", "start", svc1Name}, 3397 {"--user", "start", svc2Name}, // one of the services fails 3398 // session agent attempts to stop the non-failed services 3399 {"--user", "stop", svc1Name}, 3400 {"--user", "show", "--property=ActiveState", svc1Name}, 3401 // StartServices ensures everything is stopped 3402 {"--user", "stop", svc2Name}, 3403 {"--user", "show", "--property=ActiveState", svc2Name}, 3404 {"--user", "stop", svc1Name}, 3405 {"--user", "show", "--property=ActiveState", svc1Name}, 3406 {"--user", "--global", "--no-reload", "disable", svc1Name, svc2Name}, 3407 }, Commentf("calls: %v", sysdLog)) 3408 } 3409 3410 func (s *servicesTestSuite) TestStartSnapServicesKeepsOrder(c *C) { 3411 var sysdLog [][]string 3412 svc1Name := "snap.services-snap.svc1.service" 3413 svc2Name := "snap.services-snap.svc2.service" 3414 svc3Name := "snap.services-snap.svc3.service" 3415 3416 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 3417 sysdLog = append(sysdLog, cmd) 3418 return []byte("ActiveState=inactive\n"), nil 3419 }) 3420 defer r() 3421 3422 info := snaptest.MockSnap(c, `name: services-snap 3423 apps: 3424 svc1: 3425 daemon: simple 3426 before: [svc3] 3427 svc2: 3428 daemon: simple 3429 after: [svc1] 3430 svc3: 3431 daemon: simple 3432 before: [svc2] 3433 `, &snap.SideInfo{Revision: snap.R(12)}) 3434 3435 svcs := info.Services() 3436 c.Assert(svcs, HasLen, 3) 3437 3438 sorted, err := snap.SortServices(svcs) 3439 c.Assert(err, IsNil) 3440 3441 flags := &wrappers.StartServicesFlags{Enable: true} 3442 err = wrappers.StartServices(sorted, nil, flags, &progress.Null, s.perfTimings) 3443 c.Assert(err, IsNil) 3444 c.Assert(sysdLog, HasLen, 5, Commentf("len: %v calls: %v", len(sysdLog), sysdLog)) 3445 c.Check(sysdLog, DeepEquals, [][]string{ 3446 {"--no-reload", "enable", svc1Name, svc3Name, svc2Name}, 3447 {"daemon-reload"}, 3448 {"start", svc1Name}, 3449 {"start", svc3Name}, 3450 {"start", svc2Name}, 3451 }, Commentf("calls: %v", sysdLog)) 3452 3453 // change the order 3454 sorted[1], sorted[0] = sorted[0], sorted[1] 3455 3456 // we should observe the calls done in the same order as services 3457 err = wrappers.StartServices(sorted, nil, flags, &progress.Null, s.perfTimings) 3458 c.Assert(err, IsNil) 3459 c.Assert(sysdLog, HasLen, 10, Commentf("len: %v calls: %v", len(sysdLog), sysdLog)) 3460 c.Check(sysdLog[5:], DeepEquals, [][]string{ 3461 {"--no-reload", "enable", svc3Name, svc1Name, svc2Name}, 3462 {"daemon-reload"}, 3463 {"start", svc3Name}, 3464 {"start", svc1Name}, 3465 {"start", svc2Name}, 3466 }, Commentf("calls: %v", sysdLog)) 3467 } 3468 3469 func (s *servicesTestSuite) TestServiceAfterBefore(c *C) { 3470 snapYaml := packageHello + ` 3471 svc2: 3472 daemon: forking 3473 after: [svc1] 3474 svc3: 3475 daemon: forking 3476 before: [svc4] 3477 after: [svc2] 3478 svc4: 3479 daemon: forking 3480 after: 3481 - svc1 3482 - svc2 3483 - svc3 3484 ` 3485 info := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{Revision: snap.R(12)}) 3486 3487 checks := []struct { 3488 file string 3489 kind string 3490 matches []string 3491 }{{ 3492 file: filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service"), 3493 kind: "After", 3494 matches: []string{info.Apps["svc1"].ServiceName()}, 3495 }, { 3496 file: filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc3.service"), 3497 kind: "After", 3498 matches: []string{info.Apps["svc2"].ServiceName()}, 3499 }, { 3500 file: filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc3.service"), 3501 kind: "Before", 3502 matches: []string{info.Apps["svc4"].ServiceName()}, 3503 }, { 3504 file: filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc4.service"), 3505 kind: "After", 3506 matches: []string{ 3507 info.Apps["svc1"].ServiceName(), 3508 info.Apps["svc2"].ServiceName(), 3509 info.Apps["svc3"].ServiceName(), 3510 }, 3511 }} 3512 3513 err := wrappers.AddSnapServices(info, nil, progress.Null) 3514 c.Assert(err, IsNil) 3515 3516 for _, check := range checks { 3517 for _, m := range check.matches { 3518 c.Check(check.file, testutil.FileMatches, 3519 // match: 3520 // ... 3521 // After=other.mount some.target foo.service bar.service 3522 // Before=foo.service bar.service 3523 // ... 3524 // but not: 3525 // Foo=something After=foo.service Bar=something else 3526 // or: 3527 // After=foo.service 3528 // bar.service 3529 // or: 3530 // After= foo.service bar.service 3531 "(?ms).*^(?U)"+check.kind+"=.*\\s?"+regexp.QuoteMeta(m)+"\\s?[^=]*$") 3532 } 3533 } 3534 } 3535 3536 func (s *servicesTestSuite) TestServiceWatchdog(c *C) { 3537 snapYaml := packageHello + ` 3538 svc2: 3539 daemon: forking 3540 watchdog-timeout: 12s 3541 svc3: 3542 daemon: forking 3543 watchdog-timeout: 0s 3544 svc4: 3545 daemon: forking 3546 ` 3547 info := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{Revision: snap.R(12)}) 3548 3549 err := wrappers.AddSnapServices(info, nil, progress.Null) 3550 c.Assert(err, IsNil) 3551 3552 content, err := ioutil.ReadFile(filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service")) 3553 c.Assert(err, IsNil) 3554 c.Check(strings.Contains(string(content), "\nWatchdogSec=12\n"), Equals, true) 3555 3556 noWatchdog := []string{ 3557 filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc3.service"), 3558 filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc4.service"), 3559 } 3560 for _, svcPath := range noWatchdog { 3561 content, err := ioutil.ReadFile(svcPath) 3562 c.Assert(err, IsNil) 3563 c.Check(strings.Contains(string(content), "WatchdogSec="), Equals, false) 3564 } 3565 } 3566 3567 func (s *servicesTestSuite) TestStopServiceEndure(c *C) { 3568 const surviveYaml = `name: survive-snap 3569 version: 1.0 3570 apps: 3571 survivor: 3572 command: bin/survivor 3573 refresh-mode: endure 3574 daemon: simple 3575 ` 3576 info := snaptest.MockSnap(c, surviveYaml, &snap.SideInfo{Revision: snap.R(1)}) 3577 survivorFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.survive-snap.survivor.service") 3578 3579 err := wrappers.AddSnapServices(info, nil, progress.Null) 3580 c.Assert(err, IsNil) 3581 c.Check(s.sysdLog, DeepEquals, [][]string{ 3582 {"daemon-reload"}, 3583 }) 3584 s.sysdLog = nil 3585 3586 apps := []*snap.AppInfo{info.Apps["survivor"]} 3587 flags := &wrappers.StartServicesFlags{Enable: true} 3588 err = wrappers.StartServices(apps, nil, flags, progress.Null, s.perfTimings) 3589 c.Assert(err, IsNil) 3590 3591 c.Check(s.sysdLog, DeepEquals, [][]string{ 3592 {"--no-reload", "enable", filepath.Base(survivorFile)}, 3593 {"daemon-reload"}, 3594 {"start", filepath.Base(survivorFile)}, 3595 }) 3596 3597 s.sysdLog = nil 3598 err = wrappers.StopServices(info.Services(), nil, snap.StopReasonRefresh, progress.Null, s.perfTimings) 3599 c.Assert(err, IsNil) 3600 c.Assert(s.sysdLog, HasLen, 0) 3601 3602 s.sysdLog = nil 3603 err = wrappers.StopServices(info.Services(), nil, snap.StopReasonRemove, progress.Null, s.perfTimings) 3604 c.Assert(err, IsNil) 3605 c.Check(s.sysdLog, DeepEquals, [][]string{ 3606 {"stop", filepath.Base(survivorFile)}, 3607 {"show", "--property=ActiveState", "snap.survive-snap.survivor.service"}, 3608 }) 3609 3610 } 3611 3612 func (s *servicesTestSuite) TestStopServiceSigs(c *C) { 3613 r := wrappers.MockKillWait(1 * time.Millisecond) 3614 defer r() 3615 3616 survivorFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.survive-snap.srv.service") 3617 for _, t := range []struct { 3618 mode string 3619 expectedSig string 3620 expectedWho string 3621 }{ 3622 {mode: "sigterm", expectedSig: "TERM", expectedWho: "main"}, 3623 {mode: "sigterm-all", expectedSig: "TERM", expectedWho: "all"}, 3624 {mode: "sighup", expectedSig: "HUP", expectedWho: "main"}, 3625 {mode: "sighup-all", expectedSig: "HUP", expectedWho: "all"}, 3626 {mode: "sigusr1", expectedSig: "USR1", expectedWho: "main"}, 3627 {mode: "sigusr1-all", expectedSig: "USR1", expectedWho: "all"}, 3628 {mode: "sigusr2", expectedSig: "USR2", expectedWho: "main"}, 3629 {mode: "sigusr2-all", expectedSig: "USR2", expectedWho: "all"}, 3630 {mode: "sigint", expectedSig: "INT", expectedWho: "main"}, 3631 {mode: "sigint-all", expectedSig: "INT", expectedWho: "all"}, 3632 } { 3633 surviveYaml := fmt.Sprintf(`name: survive-snap 3634 version: 1.0 3635 apps: 3636 srv: 3637 command: bin/survivor 3638 stop-mode: %s 3639 daemon: simple 3640 `, t.mode) 3641 info := snaptest.MockSnap(c, surviveYaml, &snap.SideInfo{Revision: snap.R(1)}) 3642 3643 s.sysdLog = nil 3644 err := wrappers.AddSnapServices(info, nil, progress.Null) 3645 c.Assert(err, IsNil) 3646 3647 c.Check(s.sysdLog, DeepEquals, [][]string{ 3648 {"daemon-reload"}, 3649 }) 3650 s.sysdLog = nil 3651 3652 var apps []*snap.AppInfo 3653 for _, a := range info.Apps { 3654 apps = append(apps, a) 3655 } 3656 flags := &wrappers.StartServicesFlags{Enable: true} 3657 err = wrappers.StartServices(apps, nil, flags, progress.Null, s.perfTimings) 3658 c.Assert(err, IsNil) 3659 c.Check(s.sysdLog, DeepEquals, [][]string{ 3660 {"--no-reload", "enable", filepath.Base(survivorFile)}, 3661 {"daemon-reload"}, 3662 {"start", filepath.Base(survivorFile)}, 3663 }) 3664 3665 s.sysdLog = nil 3666 err = wrappers.StopServices(info.Services(), nil, snap.StopReasonRefresh, progress.Null, s.perfTimings) 3667 c.Assert(err, IsNil) 3668 c.Check(s.sysdLog, DeepEquals, [][]string{ 3669 {"stop", filepath.Base(survivorFile)}, 3670 {"show", "--property=ActiveState", "snap.survive-snap.srv.service"}, 3671 }, Commentf("failure in %s", t.mode)) 3672 3673 s.sysdLog = nil 3674 err = wrappers.StopServices(info.Services(), nil, snap.StopReasonRemove, progress.Null, s.perfTimings) 3675 c.Assert(err, IsNil) 3676 switch t.expectedWho { 3677 case "all": 3678 c.Check(s.sysdLog, DeepEquals, [][]string{ 3679 {"stop", filepath.Base(survivorFile)}, 3680 {"show", "--property=ActiveState", "snap.survive-snap.srv.service"}, 3681 }) 3682 case "main": 3683 c.Check(s.sysdLog, DeepEquals, [][]string{ 3684 {"stop", filepath.Base(survivorFile)}, 3685 {"show", "--property=ActiveState", "snap.survive-snap.srv.service"}, 3686 }) 3687 default: 3688 panic("not reached") 3689 } 3690 } 3691 3692 } 3693 3694 func (s *servicesTestSuite) TestStartSnapSocketEnableStart(c *C) { 3695 svc1Name := "snap.hello-snap.svc1.service" 3696 // svc2Name := "snap.hello-snap.svc2.service" 3697 svc2Sock := "snap.hello-snap.svc2.sock.socket" 3698 svc3Sock := "snap.hello-snap.svc3.sock.socket" 3699 3700 info := snaptest.MockSnap(c, packageHello+` 3701 svc2: 3702 command: bin/hello 3703 daemon: simple 3704 sockets: 3705 sock: 3706 listen-stream: $SNAP_COMMON/sock1.socket 3707 svc3: 3708 command: bin/hello 3709 daemon: simple 3710 daemon-scope: user 3711 sockets: 3712 sock: 3713 listen-stream: $SNAP_USER_COMMON/sock1.socket 3714 `, &snap.SideInfo{Revision: snap.R(12)}) 3715 3716 // fix the apps order to make the test stable 3717 apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"], info.Apps["svc3"]} 3718 flags := &wrappers.StartServicesFlags{Enable: true} 3719 err := wrappers.StartServices(apps, nil, flags, &progress.Null, s.perfTimings) 3720 c.Assert(err, IsNil) 3721 c.Assert(s.sysdLog, HasLen, 6, Commentf("len: %v calls: %v", len(s.sysdLog), s.sysdLog)) 3722 c.Check(s.sysdLog, DeepEquals, [][]string{ 3723 {"--no-reload", "enable", svc2Sock, svc1Name}, 3724 {"daemon-reload"}, 3725 {"--user", "--global", "--no-reload", "enable", svc3Sock}, 3726 {"start", svc2Sock}, 3727 {"start", svc1Name}, 3728 {"--user", "start", svc3Sock}, 3729 }, Commentf("calls: %v", s.sysdLog)) 3730 } 3731 3732 func (s *servicesTestSuite) TestStartSnapTimerEnableStart(c *C) { 3733 svc1Name := "snap.hello-snap.svc1.service" 3734 // svc2Name := "snap.hello-snap.svc2.service" 3735 svc2Timer := "snap.hello-snap.svc2.timer" 3736 svc3Timer := "snap.hello-snap.svc3.timer" 3737 3738 info := snaptest.MockSnap(c, packageHello+` 3739 svc2: 3740 command: bin/hello 3741 daemon: simple 3742 timer: 10:00-12:00 3743 svc3: 3744 command: bin/hello 3745 daemon: simple 3746 daemon-scope: user 3747 timer: 10:00-12:00 3748 `, &snap.SideInfo{Revision: snap.R(12)}) 3749 3750 // fix the apps order to make the test stable 3751 apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"], info.Apps["svc3"]} 3752 flags := &wrappers.StartServicesFlags{Enable: true} 3753 err := wrappers.StartServices(apps, nil, flags, &progress.Null, s.perfTimings) 3754 c.Assert(err, IsNil) 3755 c.Assert(s.sysdLog, HasLen, 6, Commentf("len: %v calls: %v", len(s.sysdLog), s.sysdLog)) 3756 c.Check(s.sysdLog, DeepEquals, [][]string{ 3757 {"--no-reload", "enable", svc2Timer, svc1Name}, 3758 {"daemon-reload"}, 3759 {"--user", "--global", "--no-reload", "enable", svc3Timer}, 3760 {"start", svc2Timer}, 3761 {"start", svc1Name}, 3762 {"--user", "start", svc3Timer}, 3763 }, Commentf("calls: %v", s.sysdLog)) 3764 } 3765 3766 func (s *servicesTestSuite) TestStartSnapTimerCleanup(c *C) { 3767 var sysdLog [][]string 3768 svc1Name := "snap.hello-snap.svc1.service" 3769 svc2Name := "snap.hello-snap.svc2.service" 3770 svc2Timer := "snap.hello-snap.svc2.timer" 3771 3772 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 3773 sysdLog = append(sysdLog, cmd) 3774 if len(cmd) >= 2 && cmd[0] == "start" && cmd[1] == svc2Timer { 3775 return nil, fmt.Errorf("failed") 3776 } 3777 return []byte("ActiveState=inactive\n"), nil 3778 }) 3779 defer r() 3780 3781 info := snaptest.MockSnap(c, packageHello+` 3782 svc2: 3783 command: bin/hello 3784 daemon: simple 3785 timer: 10:00-12:00 3786 `, &snap.SideInfo{Revision: snap.R(12)}) 3787 3788 // fix the apps order to make the test stable 3789 apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"]} 3790 flags := &wrappers.StartServicesFlags{Enable: true} 3791 err := wrappers.StartServices(apps, nil, flags, &progress.Null, s.perfTimings) 3792 c.Assert(err, ErrorMatches, "failed") 3793 c.Assert(sysdLog, HasLen, 10, Commentf("len: %v calls: %v", len(sysdLog), sysdLog)) 3794 c.Check(sysdLog, DeepEquals, [][]string{ 3795 {"--no-reload", "enable", svc2Timer, svc1Name}, 3796 {"daemon-reload"}, 3797 {"start", svc2Timer}, // this call fails 3798 {"stop", svc2Timer, svc2Name}, 3799 {"show", "--property=ActiveState", svc2Timer}, 3800 {"show", "--property=ActiveState", svc2Name}, 3801 {"stop", svc1Name}, 3802 {"show", "--property=ActiveState", svc1Name}, 3803 {"--no-reload", "disable", svc2Timer, svc1Name}, 3804 {"daemon-reload"}, 3805 }, Commentf("calls: %v", sysdLog)) 3806 } 3807 3808 func (s *servicesTestSuite) TestAddRemoveSnapWithTimersAddsRemovesTimerFiles(c *C) { 3809 info := snaptest.MockSnap(c, packageHello+` 3810 svc2: 3811 command: bin/hello 3812 daemon: simple 3813 timer: 10:00-12:00 3814 `, &snap.SideInfo{Revision: snap.R(12)}) 3815 3816 err := wrappers.AddSnapServices(info, nil, progress.Null) 3817 c.Assert(err, IsNil) 3818 3819 app := info.Apps["svc2"] 3820 c.Assert(app.Timer, NotNil) 3821 3822 c.Check(osutil.FileExists(app.Timer.File()), Equals, true) 3823 c.Check(osutil.FileExists(app.ServiceFile()), Equals, true) 3824 3825 err = wrappers.StopServices(info.Services(), nil, "", &progress.Null, s.perfTimings) 3826 c.Assert(err, IsNil) 3827 3828 err = wrappers.RemoveSnapServices(info, &progress.Null) 3829 c.Assert(err, IsNil) 3830 3831 c.Check(osutil.FileExists(app.Timer.File()), Equals, false) 3832 c.Check(osutil.FileExists(app.ServiceFile()), Equals, false) 3833 } 3834 3835 func (s *servicesTestSuite) TestFailedAddSnapCleansUp(c *C) { 3836 info := snaptest.MockSnap(c, packageHello+` 3837 svc2: 3838 command: bin/hello 3839 daemon: simple 3840 timer: 10:00-12:00 3841 svc3: 3842 command: bin/hello 3843 daemon: simple 3844 plugs: [network-bind] 3845 sockets: 3846 sock1: 3847 listen-stream: $SNAP_COMMON/sock1.socket 3848 socket-mode: 0666 3849 `, &snap.SideInfo{Revision: snap.R(12)}) 3850 3851 calls := 0 3852 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 3853 if len(cmd) == 1 && cmd[0] == "daemon-reload" && calls == 0 { 3854 // only fail the first systemd daemon-reload call, the 3855 // second one is at the end of cleanup 3856 calls += 1 3857 return nil, fmt.Errorf("failed") 3858 } 3859 return []byte("ActiveState=inactive\n"), nil 3860 }) 3861 defer r() 3862 3863 err := wrappers.AddSnapServices(info, nil, progress.Null) 3864 c.Assert(err, NotNil) 3865 3866 c.Logf("services dir: %v", dirs.SnapServicesDir) 3867 matches, err := filepath.Glob(dirs.SnapServicesDir + "/*") 3868 c.Assert(err, IsNil) 3869 c.Assert(matches, HasLen, 0, Commentf("the following autogenerated files were left behind: %v", matches)) 3870 } 3871 3872 func (s *servicesTestSuite) TestAddServicesDidReload(c *C) { 3873 const base = `name: hello-snap 3874 version: 1.10 3875 summary: hello 3876 description: Hello... 3877 apps: 3878 ` 3879 onlyServices := snaptest.MockSnap(c, base+` 3880 svc1: 3881 command: bin/hello 3882 daemon: simple 3883 `, &snap.SideInfo{Revision: snap.R(12)}) 3884 3885 onlySockets := snaptest.MockSnap(c, base+` 3886 svc1: 3887 command: bin/hello 3888 daemon: simple 3889 plugs: [network-bind] 3890 sockets: 3891 sock1: 3892 listen-stream: $SNAP_COMMON/sock1.socket 3893 socket-mode: 0666 3894 `, &snap.SideInfo{Revision: snap.R(12)}) 3895 3896 onlyTimers := snaptest.MockSnap(c, base+` 3897 svc1: 3898 command: bin/hello 3899 daemon: oneshot 3900 timer: 10:00-12:00 3901 `, &snap.SideInfo{Revision: snap.R(12)}) 3902 3903 for i, info := range []*snap.Info{onlyServices, onlySockets, onlyTimers} { 3904 s.sysdLog = nil 3905 err := wrappers.AddSnapServices(info, nil, progress.Null) 3906 c.Assert(err, IsNil) 3907 reloads := 0 3908 c.Logf("calls: %v", s.sysdLog) 3909 for _, call := range s.sysdLog { 3910 if strutil.ListContains(call, "daemon-reload") { 3911 reloads += 1 3912 } 3913 } 3914 c.Check(reloads >= 1, Equals, true, Commentf("test-case %v did not reload services as expected", i)) 3915 } 3916 } 3917 3918 func (s *servicesTestSuite) TestSnapServicesActivation(c *C) { 3919 const snapYaml = `name: hello-snap 3920 version: 1.10 3921 summary: hello 3922 description: Hello... 3923 apps: 3924 svc1: 3925 command: bin/hello 3926 daemon: simple 3927 plugs: [network-bind] 3928 sockets: 3929 sock1: 3930 listen-stream: $SNAP_COMMON/sock1.socket 3931 socket-mode: 0666 3932 svc2: 3933 command: bin/hello 3934 daemon: oneshot 3935 timer: 10:00-12:00 3936 svc3: 3937 command: bin/hello 3938 daemon: simple 3939 ` 3940 svc1Socket := "snap.hello-snap.svc1.sock1.socket" 3941 svc2Timer := "snap.hello-snap.svc2.timer" 3942 svc3Name := "snap.hello-snap.svc3.service" 3943 3944 info := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{Revision: snap.R(12)}) 3945 3946 // fix the apps order to make the test stable 3947 err := wrappers.AddSnapServices(info, nil, progress.Null) 3948 c.Assert(err, IsNil) 3949 c.Check(s.sysdLog, DeepEquals, [][]string{ 3950 {"daemon-reload"}, 3951 }) 3952 s.sysdLog = nil 3953 3954 apps := []*snap.AppInfo{info.Apps["svc1"], info.Apps["svc2"], info.Apps["svc3"]} 3955 flags := &wrappers.StartServicesFlags{Enable: true} 3956 err = wrappers.StartServices(apps, nil, flags, progress.Null, s.perfTimings) 3957 c.Assert(err, IsNil) 3958 3959 c.Assert(s.sysdLog, HasLen, 5, Commentf("len: %v calls: %v", len(s.sysdLog), s.sysdLog)) 3960 c.Check(s.sysdLog, DeepEquals, [][]string{ 3961 {"--no-reload", "enable", svc1Socket, svc2Timer, svc3Name}, 3962 {"daemon-reload"}, 3963 {"start", svc1Socket}, 3964 {"start", svc2Timer}, 3965 {"start", svc3Name}, 3966 }, Commentf("calls: %v", s.sysdLog)) 3967 } 3968 3969 func (s *servicesTestSuite) TestServiceRestartDelay(c *C) { 3970 snapYaml := packageHello + ` 3971 svc2: 3972 daemon: forking 3973 restart-delay: 12s 3974 svc3: 3975 daemon: forking 3976 ` 3977 info := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{Revision: snap.R(12)}) 3978 3979 err := wrappers.AddSnapServices(info, nil, progress.Null) 3980 c.Assert(err, IsNil) 3981 3982 content, err := ioutil.ReadFile(filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc2.service")) 3983 c.Assert(err, IsNil) 3984 c.Check(strings.Contains(string(content), "\nRestartSec=12\n"), Equals, true) 3985 3986 content, err = ioutil.ReadFile(filepath.Join(s.tempdir, "/etc/systemd/system/snap.hello-snap.svc3.service")) 3987 c.Assert(err, IsNil) 3988 c.Check(strings.Contains(string(content), "RestartSec="), Equals, false) 3989 } 3990 3991 func (s *servicesTestSuite) TestAddRemoveSnapServiceWithSnapd(c *C) { 3992 info := makeMockSnapdSnap(c) 3993 3994 err := wrappers.AddSnapServices(info, nil, progress.Null) 3995 c.Check(err, ErrorMatches, "internal error: adding explicit services for snapd snap is unexpected") 3996 3997 err = wrappers.RemoveSnapServices(info, progress.Null) 3998 c.Check(err, ErrorMatches, "internal error: removing explicit services for snapd snap is unexpected") 3999 } 4000 4001 func (s *servicesTestSuite) TestReloadOrRestart(c *C) { 4002 const surviveYaml = `name: test-snap 4003 version: 1.0 4004 apps: 4005 foo: 4006 command: bin/foo 4007 daemon: simple 4008 ` 4009 info := snaptest.MockSnap(c, surviveYaml, &snap.SideInfo{Revision: snap.R(1)}) 4010 srvFile := "snap.test-snap.foo.service" 4011 4012 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 4013 s.sysdLog = append(s.sysdLog, cmd) 4014 if out := systemdtest.HandleMockAllUnitsActiveOutput(cmd, nil); out != nil { 4015 return out, nil 4016 } 4017 return []byte("ActiveState=inactive\n"), nil 4018 }) 4019 defer r() 4020 4021 err := wrappers.AddSnapServices(info, nil, progress.Null) 4022 c.Assert(err, IsNil) 4023 4024 s.sysdLog = nil 4025 flags := &wrappers.RestartServicesFlags{Reload: true} 4026 c.Assert(wrappers.RestartServices(info.Services(), nil, flags, progress.Null, s.perfTimings), IsNil) 4027 c.Assert(err, IsNil) 4028 c.Check(s.sysdLog, DeepEquals, [][]string{ 4029 {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", srvFile}, 4030 {"reload-or-restart", srvFile}, 4031 }) 4032 4033 s.sysdLog = nil 4034 flags.Reload = false 4035 c.Assert(wrappers.RestartServices(info.Services(), nil, flags, progress.Null, s.perfTimings), IsNil) 4036 c.Check(s.sysdLog, DeepEquals, [][]string{ 4037 {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", srvFile}, 4038 {"stop", srvFile}, 4039 {"show", "--property=ActiveState", srvFile}, 4040 {"start", srvFile}, 4041 }) 4042 4043 s.sysdLog = nil 4044 c.Assert(wrappers.RestartServices(info.Services(), nil, nil, progress.Null, s.perfTimings), IsNil) 4045 c.Check(s.sysdLog, DeepEquals, [][]string{ 4046 {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", srvFile}, 4047 {"stop", srvFile}, 4048 {"show", "--property=ActiveState", srvFile}, 4049 {"start", srvFile}, 4050 }) 4051 } 4052 4053 func (s *servicesTestSuite) TestRestartInDifferentStates(c *C) { 4054 const manyServicesYaml = `name: test-snap 4055 version: 1.0 4056 apps: 4057 svc1: 4058 command: bin/foo 4059 daemon: simple 4060 svc2: 4061 command: bin/foo 4062 daemon: simple 4063 svc3: 4064 command: bin/foo 4065 daemon: simple 4066 svc4: 4067 command: bin/foo 4068 daemon: simple 4069 ` 4070 srvFile1 := "snap.test-snap.svc1.service" 4071 srvFile2 := "snap.test-snap.svc2.service" 4072 srvFile3 := "snap.test-snap.svc3.service" 4073 srvFile4 := "snap.test-snap.svc4.service" 4074 4075 info := snaptest.MockSnap(c, manyServicesYaml, &snap.SideInfo{Revision: snap.R(1)}) 4076 4077 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 4078 s.sysdLog = append(s.sysdLog, cmd) 4079 states := map[string]systemdtest.ServiceState{ 4080 srvFile1: {ActiveState: "active", UnitFileState: "enabled"}, 4081 srvFile2: {ActiveState: "inactive", UnitFileState: "enabled"}, 4082 srvFile3: {ActiveState: "active", UnitFileState: "disabled"}, 4083 srvFile4: {ActiveState: "inactive", UnitFileState: "disabled"}, 4084 } 4085 if out := systemdtest.HandleMockAllUnitsActiveOutput(cmd, states); out != nil { 4086 return out, nil 4087 } 4088 return []byte("ActiveState=inactive\n"), nil 4089 }) 4090 defer r() 4091 4092 err := wrappers.AddSnapServices(info, nil, progress.Null) 4093 c.Assert(err, IsNil) 4094 4095 s.sysdLog = nil 4096 services := info.Services() 4097 sort.Sort(snap.AppInfoBySnapApp(services)) 4098 c.Assert(wrappers.RestartServices(services, nil, nil, progress.Null, s.perfTimings), IsNil) 4099 c.Check(s.sysdLog, DeepEquals, [][]string{ 4100 {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", 4101 srvFile1, srvFile2, srvFile3, srvFile4}, 4102 {"stop", srvFile1}, 4103 {"show", "--property=ActiveState", srvFile1}, 4104 {"start", srvFile1}, 4105 {"stop", srvFile3}, 4106 {"show", "--property=ActiveState", srvFile3}, 4107 {"start", srvFile3}, 4108 }) 4109 4110 // Verify that explicitly mentioning a service causes it to restart, 4111 // regardless of its state 4112 s.sysdLog = nil 4113 c.Assert(wrappers.RestartServices(services, []string{srvFile2}, nil, progress.Null, s.perfTimings), IsNil) 4114 c.Check(s.sysdLog, DeepEquals, [][]string{ 4115 {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", 4116 srvFile1, srvFile2, srvFile3, srvFile4}, 4117 {"stop", srvFile1}, 4118 {"show", "--property=ActiveState", srvFile1}, 4119 {"start", srvFile1}, 4120 {"stop", srvFile2}, 4121 {"show", "--property=ActiveState", srvFile2}, 4122 {"start", srvFile2}, 4123 {"stop", srvFile3}, 4124 {"show", "--property=ActiveState", srvFile3}, 4125 {"start", srvFile3}, 4126 }) 4127 } 4128 4129 func (s *servicesTestSuite) TestStopAndDisableServices(c *C) { 4130 info := snaptest.MockSnap(c, packageHelloNoSrv+` 4131 svc1: 4132 daemon: simple 4133 `, &snap.SideInfo{Revision: snap.R(12)}) 4134 svcFile := "snap.hello-snap.svc1.service" 4135 4136 err := wrappers.AddSnapServices(info, nil, progress.Null) 4137 c.Assert(err, IsNil) 4138 4139 s.sysdLog = nil 4140 flags := &wrappers.StopServicesFlags{Disable: true} 4141 err = wrappers.StopServices(info.Services(), flags, "", progress.Null, s.perfTimings) 4142 c.Assert(err, IsNil) 4143 c.Check(s.sysdLog, DeepEquals, [][]string{ 4144 {"stop", svcFile}, 4145 {"show", "--property=ActiveState", svcFile}, 4146 {"--no-reload", "disable", svcFile}, 4147 {"daemon-reload"}, 4148 }) 4149 }