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