github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/systemd/systemd_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2015 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 systemd_test 21 22 import ( 23 "bytes" 24 "fmt" 25 "io" 26 "io/ioutil" 27 "os" 28 "path/filepath" 29 "strconv" 30 "testing" 31 "time" 32 33 . "gopkg.in/check.v1" 34 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/osutil" 37 "github.com/snapcore/snapd/osutil/squashfs" 38 "github.com/snapcore/snapd/sandbox/selinux" 39 "github.com/snapcore/snapd/testutil" 40 41 . "github.com/snapcore/snapd/systemd" 42 ) 43 44 type testreporter struct { 45 msgs []string 46 } 47 48 func (tr *testreporter) Notify(msg string) { 49 tr.msgs = append(tr.msgs, msg) 50 } 51 52 // Hook up check.v1 into the "go test" runner 53 func Test(t *testing.T) { TestingT(t) } 54 55 // systemd's testsuite 56 type SystemdTestSuite struct { 57 i int 58 argses [][]string 59 errors []error 60 outs [][]byte 61 62 j int 63 jns []string 64 jsvcs [][]string 65 jouts [][]byte 66 jerrs []error 67 jfollows []bool 68 69 rep *testreporter 70 71 restoreSystemctl func() 72 restoreJournalctl func() 73 restoreSELinux func() 74 } 75 76 var _ = Suite(&SystemdTestSuite{}) 77 78 func (s *SystemdTestSuite) SetUpTest(c *C) { 79 dirs.SetRootDir(c.MkDir()) 80 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 81 c.Assert(err, IsNil) 82 c.Assert(os.MkdirAll(filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants"), 0755), IsNil) 83 84 // force UTC timezone, for reproducible timestamps 85 os.Setenv("TZ", "") 86 87 s.restoreSystemctl = MockSystemctl(s.myRun) 88 s.i = 0 89 s.argses = nil 90 s.errors = nil 91 s.outs = nil 92 93 s.restoreJournalctl = MockJournalctl(s.myJctl) 94 s.j = 0 95 s.jns = nil 96 s.jsvcs = nil 97 s.jouts = nil 98 s.jerrs = nil 99 s.jfollows = nil 100 101 s.rep = new(testreporter) 102 103 s.restoreSELinux = selinux.MockIsEnabled(func() (bool, error) { return false, nil }) 104 } 105 106 func (s *SystemdTestSuite) TearDownTest(c *C) { 107 s.restoreSystemctl() 108 s.restoreJournalctl() 109 s.restoreSELinux() 110 } 111 112 func (s *SystemdTestSuite) myRun(args ...string) (out []byte, err error) { 113 s.argses = append(s.argses, args) 114 if s.i < len(s.outs) { 115 out = s.outs[s.i] 116 } 117 if s.i < len(s.errors) { 118 err = s.errors[s.i] 119 } 120 s.i++ 121 return out, err 122 } 123 124 func (s *SystemdTestSuite) myJctl(svcs []string, n int, follow bool) (io.ReadCloser, error) { 125 var err error 126 var out []byte 127 128 s.jns = append(s.jns, strconv.Itoa(n)) 129 s.jsvcs = append(s.jsvcs, svcs) 130 s.jfollows = append(s.jfollows, follow) 131 132 if s.j < len(s.jouts) { 133 out = s.jouts[s.j] 134 } 135 if s.j < len(s.jerrs) { 136 err = s.jerrs[s.j] 137 } 138 s.j++ 139 140 if out == nil { 141 return nil, err 142 } 143 144 return ioutil.NopCloser(bytes.NewReader(out)), err 145 } 146 147 func (s *SystemdTestSuite) TestDaemonReload(c *C) { 148 err := New(SystemMode, s.rep).DaemonReload() 149 c.Assert(err, IsNil) 150 c.Assert(s.argses, DeepEquals, [][]string{{"daemon-reload"}}) 151 } 152 153 func (s *SystemdTestSuite) TestDaemonReexec(c *C) { 154 err := New(SystemMode, s.rep).DaemonReexec() 155 c.Assert(err, IsNil) 156 c.Assert(s.argses, DeepEquals, [][]string{{"daemon-reexec"}}) 157 } 158 159 func (s *SystemdTestSuite) TestStart(c *C) { 160 err := New(SystemMode, s.rep).Start("foo") 161 c.Assert(err, IsNil) 162 c.Check(s.argses, DeepEquals, [][]string{{"start", "foo"}}) 163 } 164 165 func (s *SystemdTestSuite) TestStartMany(c *C) { 166 err := New(SystemMode, s.rep).Start("foo", "bar", "baz") 167 c.Assert(err, IsNil) 168 c.Check(s.argses, DeepEquals, [][]string{{"start", "foo", "bar", "baz"}}) 169 } 170 171 func (s *SystemdTestSuite) TestStop(c *C) { 172 restore := MockStopDelays(time.Millisecond, 25*time.Second) 173 defer restore() 174 s.outs = [][]byte{ 175 nil, // for the "stop" itself 176 []byte("ActiveState=whatever\n"), 177 []byte("ActiveState=active\n"), 178 []byte("ActiveState=inactive\n"), 179 } 180 s.errors = []error{nil, nil, nil, nil, &Timeout{}} 181 err := New(SystemMode, s.rep).Stop("foo", 1*time.Second) 182 c.Assert(err, IsNil) 183 c.Assert(s.argses, HasLen, 4) 184 c.Check(s.argses[0], DeepEquals, []string{"stop", "foo"}) 185 c.Check(s.argses[1], DeepEquals, []string{"show", "--property=ActiveState", "foo"}) 186 c.Check(s.argses[1], DeepEquals, s.argses[2]) 187 c.Check(s.argses[1], DeepEquals, s.argses[3]) 188 } 189 190 func (s *SystemdTestSuite) TestStatus(c *C) { 191 s.outs = [][]byte{ 192 []byte(` 193 Type=simple 194 Id=foo.service 195 ActiveState=active 196 UnitFileState=enabled 197 198 Type=simple 199 Id=bar.service 200 ActiveState=reloading 201 UnitFileState=static 202 203 Type=potato 204 Id=baz.service 205 ActiveState=inactive 206 UnitFileState=disabled 207 `[1:]), 208 []byte(` 209 Id=some.timer 210 ActiveState=active 211 UnitFileState=enabled 212 213 Id=other.socket 214 ActiveState=active 215 UnitFileState=disabled 216 `[1:]), 217 } 218 s.errors = []error{nil} 219 out, err := New(SystemMode, s.rep).Status("foo.service", "bar.service", "baz.service", "some.timer", "other.socket") 220 c.Assert(err, IsNil) 221 c.Check(out, DeepEquals, []*UnitStatus{ 222 { 223 Daemon: "simple", 224 UnitName: "foo.service", 225 Active: true, 226 Enabled: true, 227 }, { 228 Daemon: "simple", 229 UnitName: "bar.service", 230 Active: true, 231 Enabled: true, 232 }, { 233 Daemon: "potato", 234 UnitName: "baz.service", 235 Active: false, 236 Enabled: false, 237 }, { 238 UnitName: "some.timer", 239 Active: true, 240 Enabled: true, 241 }, { 242 UnitName: "other.socket", 243 Active: true, 244 Enabled: false, 245 }, 246 }) 247 c.Check(s.rep.msgs, IsNil) 248 c.Assert(s.argses, DeepEquals, [][]string{ 249 {"show", "--property=Id,ActiveState,UnitFileState,Type", "foo.service", "bar.service", "baz.service"}, 250 {"show", "--property=Id,ActiveState,UnitFileState", "some.timer", "other.socket"}, 251 }) 252 } 253 254 func (s *SystemdTestSuite) TestStatusBadNumberOfValues(c *C) { 255 s.outs = [][]byte{ 256 []byte(` 257 Type=simple 258 Id=foo.service 259 ActiveState=active 260 UnitFileState=enabled 261 262 Type=simple 263 Id=foo.service 264 ActiveState=active 265 UnitFileState=enabled 266 `[1:]), 267 } 268 s.errors = []error{nil} 269 out, err := New(SystemMode, s.rep).Status("foo.service") 270 c.Check(err, ErrorMatches, "cannot get unit status: expected 1 results, got 2") 271 c.Check(out, IsNil) 272 c.Check(s.rep.msgs, IsNil) 273 } 274 275 func (s *SystemdTestSuite) TestStatusBadLine(c *C) { 276 s.outs = [][]byte{ 277 []byte(` 278 Type=simple 279 Id=foo.service 280 ActiveState=active 281 UnitFileState=enabled 282 Potatoes 283 `[1:]), 284 } 285 s.errors = []error{nil} 286 out, err := New(SystemMode, s.rep).Status("foo.service") 287 c.Assert(err, ErrorMatches, `.* bad line "Potatoes" .*`) 288 c.Check(out, IsNil) 289 } 290 291 func (s *SystemdTestSuite) TestStatusBadId(c *C) { 292 s.outs = [][]byte{ 293 []byte(` 294 Type=simple 295 Id=bar.service 296 ActiveState=active 297 UnitFileState=enabled 298 `[1:]), 299 } 300 s.errors = []error{nil} 301 out, err := New(SystemMode, s.rep).Status("foo.service") 302 c.Assert(err, ErrorMatches, `.* queried status of "foo.service" but got status of "bar.service"`) 303 c.Check(out, IsNil) 304 } 305 306 func (s *SystemdTestSuite) TestStatusBadField(c *C) { 307 s.outs = [][]byte{ 308 []byte(` 309 Type=simple 310 Id=foo.service 311 ActiveState=active 312 UnitFileState=enabled 313 Potatoes=false 314 `[1:]), 315 } 316 s.errors = []error{nil} 317 out, err := New(SystemMode, s.rep).Status("foo.service") 318 c.Assert(err, ErrorMatches, `.* unexpected field "Potatoes" .*`) 319 c.Check(out, IsNil) 320 } 321 322 func (s *SystemdTestSuite) TestStatusMissingRequiredFieldService(c *C) { 323 s.outs = [][]byte{ 324 []byte(` 325 Id=foo.service 326 ActiveState=active 327 `[1:]), 328 } 329 s.errors = []error{nil} 330 out, err := New(SystemMode, s.rep).Status("foo.service") 331 c.Assert(err, ErrorMatches, `.* missing UnitFileState, Type .*`) 332 c.Check(out, IsNil) 333 } 334 335 func (s *SystemdTestSuite) TestStatusMissingRequiredFieldTimer(c *C) { 336 s.outs = [][]byte{ 337 []byte(` 338 Id=foo.timer 339 ActiveState=active 340 `[1:]), 341 } 342 s.errors = []error{nil} 343 out, err := New(SystemMode, s.rep).Status("foo.timer") 344 c.Assert(err, ErrorMatches, `.* missing UnitFileState .*`) 345 c.Check(out, IsNil) 346 } 347 348 func (s *SystemdTestSuite) TestStatusDupeField(c *C) { 349 s.outs = [][]byte{ 350 []byte(` 351 Type=simple 352 Id=foo.service 353 ActiveState=active 354 ActiveState=active 355 UnitFileState=enabled 356 `[1:]), 357 } 358 s.errors = []error{nil} 359 out, err := New(SystemMode, s.rep).Status("foo.service") 360 c.Assert(err, ErrorMatches, `.* duplicate field "ActiveState" .*`) 361 c.Check(out, IsNil) 362 } 363 364 func (s *SystemdTestSuite) TestStatusEmptyField(c *C) { 365 s.outs = [][]byte{ 366 []byte(` 367 Type=simple 368 Id= 369 ActiveState=active 370 UnitFileState=enabled 371 `[1:]), 372 } 373 s.errors = []error{nil} 374 out, err := New(SystemMode, s.rep).Status("foo.service") 375 c.Assert(err, ErrorMatches, `.* empty field "Id" .*`) 376 c.Check(out, IsNil) 377 } 378 379 func (s *SystemdTestSuite) TestStopTimeout(c *C) { 380 restore := MockStopDelays(time.Millisecond, 25*time.Second) 381 defer restore() 382 err := New(SystemMode, s.rep).Stop("foo", 10*time.Millisecond) 383 c.Assert(err, FitsTypeOf, &Timeout{}) 384 c.Assert(len(s.rep.msgs) > 0, Equals, true) 385 c.Check(s.rep.msgs[0], Equals, "Waiting for foo to stop.") 386 } 387 388 func (s *SystemdTestSuite) TestDisable(c *C) { 389 err := New(SystemMode, s.rep).Disable("foo") 390 c.Assert(err, IsNil) 391 c.Check(s.argses, DeepEquals, [][]string{{"disable", "foo"}}) 392 } 393 394 func (s *SystemdTestSuite) TestUnderRootDisable(c *C) { 395 err := NewUnderRoot("xyzzy", SystemMode, s.rep).Disable("foo") 396 c.Assert(err, IsNil) 397 c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "disable", "foo"}}) 398 } 399 400 func (s *SystemdTestSuite) TestAvailable(c *C) { 401 err := Available() 402 c.Assert(err, IsNil) 403 c.Check(s.argses, DeepEquals, [][]string{{"--version"}}) 404 } 405 406 func (s *SystemdTestSuite) TestVersion(c *C) { 407 s.outs = [][]byte{ 408 []byte("systemd 223\n+PAM\n"), 409 []byte("systemd 245 (245.4-4ubuntu3)\n+PAM +AUDIT +SELINUX +IMA\n"), 410 // error cases 411 []byte("foo 223\n+PAM\n"), 412 []byte(""), 413 []byte("systemd abc\n+PAM\n"), 414 } 415 416 v, err := Version() 417 c.Assert(err, IsNil) 418 c.Check(v, Equals, 223) 419 420 v, err = Version() 421 c.Assert(err, IsNil) 422 c.Check(v, Equals, 245) 423 424 _, err = Version() 425 c.Assert(err, ErrorMatches, `cannot parse systemd version: expected "systemd", got "foo"`) 426 427 _, err = Version() 428 c.Assert(err, ErrorMatches, `cannot read systemd version: <nil>`) 429 430 _, err = Version() 431 c.Assert(err, ErrorMatches, `cannot convert systemd version to number: abc`) 432 433 c.Check(s.argses, DeepEquals, [][]string{ 434 {"--version"}, 435 {"--version"}, 436 {"--version"}, 437 {"--version"}, 438 {"--version"}, 439 }) 440 } 441 442 func (s *SystemdTestSuite) TestEnable(c *C) { 443 err := New(SystemMode, s.rep).Enable("foo") 444 c.Assert(err, IsNil) 445 c.Check(s.argses, DeepEquals, [][]string{{"enable", "foo"}}) 446 } 447 448 func (s *SystemdTestSuite) TestEnableUnderRoot(c *C) { 449 err := NewUnderRoot("xyzzy", SystemMode, s.rep).Enable("foo") 450 c.Assert(err, IsNil) 451 c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "enable", "foo"}}) 452 } 453 454 func (s *SystemdTestSuite) TestMask(c *C) { 455 err := New(SystemMode, s.rep).Mask("foo") 456 c.Assert(err, IsNil) 457 c.Check(s.argses, DeepEquals, [][]string{{"mask", "foo"}}) 458 } 459 460 func (s *SystemdTestSuite) TestMaskUnderRoot(c *C) { 461 err := NewUnderRoot("xyzzy", SystemMode, s.rep).Mask("foo") 462 c.Assert(err, IsNil) 463 c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "mask", "foo"}}) 464 } 465 466 func (s *SystemdTestSuite) TestUnmask(c *C) { 467 err := New(SystemMode, s.rep).Unmask("foo") 468 c.Assert(err, IsNil) 469 c.Check(s.argses, DeepEquals, [][]string{{"unmask", "foo"}}) 470 } 471 472 func (s *SystemdTestSuite) TestUnmaskUnderRoot(c *C) { 473 err := NewUnderRoot("xyzzy", SystemMode, s.rep).Unmask("foo") 474 c.Assert(err, IsNil) 475 c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "unmask", "foo"}}) 476 } 477 478 func (s *SystemdTestSuite) TestRestart(c *C) { 479 restore := MockStopDelays(time.Millisecond, 25*time.Second) 480 defer restore() 481 s.outs = [][]byte{ 482 nil, // for the "stop" itself 483 []byte("ActiveState=inactive\n"), 484 nil, // for the "start" 485 } 486 s.errors = []error{nil, nil, nil, nil, &Timeout{}} 487 err := New(SystemMode, s.rep).Restart("foo", 100*time.Millisecond) 488 c.Assert(err, IsNil) 489 c.Check(s.argses, HasLen, 3) 490 c.Check(s.argses[0], DeepEquals, []string{"stop", "foo"}) 491 c.Check(s.argses[1], DeepEquals, []string{"show", "--property=ActiveState", "foo"}) 492 c.Check(s.argses[2], DeepEquals, []string{"start", "foo"}) 493 } 494 495 func (s *SystemdTestSuite) TestKill(c *C) { 496 c.Assert(New(SystemMode, s.rep).Kill("foo", "HUP", ""), IsNil) 497 c.Check(s.argses, DeepEquals, [][]string{{"kill", "foo", "-s", "HUP", "--kill-who=all"}}) 498 } 499 500 func (s *SystemdTestSuite) TestIsTimeout(c *C) { 501 c.Check(IsTimeout(os.ErrInvalid), Equals, false) 502 c.Check(IsTimeout(&Timeout{}), Equals, true) 503 } 504 505 func (s *SystemdTestSuite) TestLogErrJctl(c *C) { 506 s.jerrs = []error{&Timeout{}} 507 508 reader, err := New(SystemMode, s.rep).LogReader([]string{"foo"}, 24, false) 509 c.Check(err, NotNil) 510 c.Check(reader, IsNil) 511 c.Check(s.jns, DeepEquals, []string{"24"}) 512 c.Check(s.jsvcs, DeepEquals, [][]string{{"foo"}}) 513 c.Check(s.jfollows, DeepEquals, []bool{false}) 514 c.Check(s.j, Equals, 1) 515 } 516 517 func (s *SystemdTestSuite) TestLogs(c *C) { 518 expected := `{"a": 1} 519 {"a": 2} 520 ` 521 s.jouts = [][]byte{[]byte(expected)} 522 523 reader, err := New(SystemMode, s.rep).LogReader([]string{"foo"}, 24, false) 524 c.Check(err, IsNil) 525 logs, err := ioutil.ReadAll(reader) 526 c.Assert(err, IsNil) 527 c.Check(string(logs), Equals, expected) 528 c.Check(s.jns, DeepEquals, []string{"24"}) 529 c.Check(s.jsvcs, DeepEquals, [][]string{{"foo"}}) 530 c.Check(s.jfollows, DeepEquals, []bool{false}) 531 c.Check(s.j, Equals, 1) 532 } 533 534 func (s *SystemdTestSuite) TestLogPID(c *C) { 535 c.Check(Log{}.PID(), Equals, "-") 536 c.Check(Log{"_PID": "99"}.PID(), Equals, "99") 537 c.Check(Log{"SYSLOG_PID": "99"}.PID(), Equals, "99") 538 // things starting with underscore are "trusted", so we trust 539 // them more than the user-settable ones: 540 c.Check(Log{"_PID": "42", "SYSLOG_PID": "99"}.PID(), Equals, "42") 541 } 542 543 func (s *SystemdTestSuite) TestTime(c *C) { 544 t, err := Log{}.Time() 545 c.Check(t.IsZero(), Equals, true) 546 c.Check(err, ErrorMatches, "no timestamp") 547 548 t, err = Log{"__REALTIME_TIMESTAMP": "what"}.Time() 549 c.Check(t.IsZero(), Equals, true) 550 c.Check(err, ErrorMatches, `timestamp not a decimal number: "what"`) 551 552 t, err = Log{"__REALTIME_TIMESTAMP": "0"}.Time() 553 c.Check(err, IsNil) 554 c.Check(t.String(), Equals, "1970-01-01 00:00:00 +0000 UTC") 555 556 t, err = Log{"__REALTIME_TIMESTAMP": "42"}.Time() 557 c.Check(err, IsNil) 558 c.Check(t.String(), Equals, "1970-01-01 00:00:00.000042 +0000 UTC") 559 } 560 561 func (s *SystemdTestSuite) TestMountUnitPath(c *C) { 562 c.Assert(MountUnitPath("/apps/hello/1.1"), Equals, filepath.Join(dirs.SnapServicesDir, "apps-hello-1.1.mount")) 563 } 564 565 func makeMockFile(c *C, path string) { 566 err := os.MkdirAll(filepath.Dir(path), 0755) 567 c.Assert(err, IsNil) 568 err = ioutil.WriteFile(path, nil, 0644) 569 c.Assert(err, IsNil) 570 } 571 572 func (s *SystemdTestSuite) TestAddMountUnit(c *C) { 573 rootDir := dirs.GlobalRootDir 574 575 restore := squashfs.MockNeedsFuse(false) 576 defer restore() 577 578 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 579 makeMockFile(c, mockSnapPath) 580 581 mountUnitName, err := NewUnderRoot(rootDir, SystemMode, nil).AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs") 582 c.Assert(err, IsNil) 583 defer os.Remove(mountUnitName) 584 585 c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(` 586 [Unit] 587 Description=Mount unit for foo, revision 42 588 Before=snapd.service 589 590 [Mount] 591 What=%s 592 Where=/snap/snapname/123 593 Type=squashfs 594 Options=nodev,ro,x-gdu.hide 595 LazyUnmount=yes 596 597 [Install] 598 WantedBy=multi-user.target 599 `[1:], mockSnapPath)) 600 601 c.Assert(s.argses, DeepEquals, [][]string{ 602 {"daemon-reload"}, 603 {"--root", rootDir, "enable", "snap-snapname-123.mount"}, 604 {"start", "snap-snapname-123.mount"}, 605 }) 606 } 607 608 func (s *SystemdTestSuite) TestAddMountUnitForDirs(c *C) { 609 restore := squashfs.MockNeedsFuse(false) 610 defer restore() 611 612 // a directory instead of a file produces a different output 613 snapDir := c.MkDir() 614 mountUnitName, err := New(SystemMode, nil).AddMountUnitFile("foodir", "x1", snapDir, "/snap/snapname/x1", "squashfs") 615 c.Assert(err, IsNil) 616 defer os.Remove(mountUnitName) 617 618 c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(` 619 [Unit] 620 Description=Mount unit for foodir, revision x1 621 Before=snapd.service 622 623 [Mount] 624 What=%s 625 Where=/snap/snapname/x1 626 Type=none 627 Options=nodev,ro,x-gdu.hide,bind 628 LazyUnmount=yes 629 630 [Install] 631 WantedBy=multi-user.target 632 `[1:], snapDir)) 633 634 c.Assert(s.argses, DeepEquals, [][]string{ 635 {"daemon-reload"}, 636 {"enable", "snap-snapname-x1.mount"}, 637 {"start", "snap-snapname-x1.mount"}, 638 }) 639 } 640 641 func (s *SystemdTestSuite) TestWriteSELinuxMountUnit(c *C) { 642 restore := selinux.MockIsEnabled(func() (bool, error) { return true, nil }) 643 defer restore() 644 restore = selinux.MockIsEnforcing(func() (bool, error) { return true, nil }) 645 defer restore() 646 restore = squashfs.MockNeedsFuse(false) 647 defer restore() 648 649 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 650 err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755) 651 c.Assert(err, IsNil) 652 err = ioutil.WriteFile(mockSnapPath, nil, 0644) 653 c.Assert(err, IsNil) 654 655 mountUnitName, err := New(SystemMode, nil).AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs") 656 c.Assert(err, IsNil) 657 defer os.Remove(mountUnitName) 658 659 c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(` 660 [Unit] 661 Description=Mount unit for foo, revision 42 662 Before=snapd.service 663 664 [Mount] 665 What=%s 666 Where=/snap/snapname/123 667 Type=squashfs 668 Options=nodev,context=system_u:object_r:snappy_snap_t:s0,ro,x-gdu.hide 669 LazyUnmount=yes 670 671 [Install] 672 WantedBy=multi-user.target 673 `[1:], mockSnapPath)) 674 } 675 676 func (s *SystemdTestSuite) TestFuseInContainer(c *C) { 677 if !osutil.FileExists("/dev/fuse") { 678 c.Skip("No /dev/fuse on the system") 679 } 680 681 systemdCmd := testutil.MockCommand(c, "systemd-detect-virt", ` 682 echo lxc 683 exit 0 684 `) 685 defer systemdCmd.Restore() 686 687 fuseCmd := testutil.MockCommand(c, "squashfuse", ` 688 exit 0 689 `) 690 defer fuseCmd.Restore() 691 692 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 693 err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755) 694 c.Assert(err, IsNil) 695 err = ioutil.WriteFile(mockSnapPath, nil, 0644) 696 c.Assert(err, IsNil) 697 698 mountUnitName, err := New(SystemMode, nil).AddMountUnitFile("foo", "x1", mockSnapPath, "/snap/snapname/123", "squashfs") 699 c.Assert(err, IsNil) 700 defer os.Remove(mountUnitName) 701 702 c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(` 703 [Unit] 704 Description=Mount unit for foo, revision x1 705 Before=snapd.service 706 707 [Mount] 708 What=%s 709 Where=/snap/snapname/123 710 Type=fuse.squashfuse 711 Options=nodev,ro,x-gdu.hide,allow_other 712 LazyUnmount=yes 713 714 [Install] 715 WantedBy=multi-user.target 716 `[1:], mockSnapPath)) 717 } 718 719 func (s *SystemdTestSuite) TestFuseOutsideContainer(c *C) { 720 systemdCmd := testutil.MockCommand(c, "systemd-detect-virt", ` 721 echo none 722 exit 0 723 `) 724 defer systemdCmd.Restore() 725 726 fuseCmd := testutil.MockCommand(c, "squashfuse", ` 727 exit 0 728 `) 729 defer fuseCmd.Restore() 730 731 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 732 err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755) 733 c.Assert(err, IsNil) 734 err = ioutil.WriteFile(mockSnapPath, nil, 0644) 735 c.Assert(err, IsNil) 736 737 mountUnitName, err := New(SystemMode, nil).AddMountUnitFile("foo", "x1", mockSnapPath, "/snap/snapname/123", "squashfs") 738 c.Assert(err, IsNil) 739 defer os.Remove(mountUnitName) 740 741 c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(` 742 [Unit] 743 Description=Mount unit for foo, revision x1 744 Before=snapd.service 745 746 [Mount] 747 What=%s 748 Where=/snap/snapname/123 749 Type=squashfs 750 Options=nodev,ro,x-gdu.hide 751 LazyUnmount=yes 752 753 [Install] 754 WantedBy=multi-user.target 755 `[1:], mockSnapPath)) 756 } 757 758 func (s *SystemdTestSuite) TestJctl(c *C) { 759 var args []string 760 var err error 761 MockOsutilStreamCommand(func(name string, myargs ...string) (io.ReadCloser, error) { 762 c.Check(cap(myargs) <= len(myargs)+2, Equals, true, Commentf("cap:%d, len:%d", cap(myargs), len(myargs))) 763 args = myargs 764 return nil, nil 765 }) 766 767 _, err = Jctl([]string{"foo", "bar"}, 10, false) 768 c.Assert(err, IsNil) 769 c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "-n", "10", "-u", "foo", "-u", "bar"}) 770 _, err = Jctl([]string{"foo", "bar", "baz"}, 99, true) 771 c.Assert(err, IsNil) 772 c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "-n", "99", "-f", "-u", "foo", "-u", "bar", "-u", "baz"}) 773 _, err = Jctl([]string{"foo", "bar"}, -1, false) 774 c.Assert(err, IsNil) 775 c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "--no-tail", "-u", "foo", "-u", "bar"}) 776 } 777 778 func (s *SystemdTestSuite) TestIsActiveUnderRoot(c *C) { 779 sysErr := &Error{} 780 // manpage states that systemctl returns exit code 3 for inactive 781 // services, however we should check any non-0 exit status 782 sysErr.SetExitCode(1) 783 sysErr.SetMsg([]byte("inactive\n")) 784 s.errors = []error{sysErr} 785 786 _, err := NewUnderRoot("xyzzy", SystemMode, s.rep).IsActive("foo") 787 c.Assert(err, IsNil) 788 c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "is-active", "foo"}}) 789 } 790 791 func (s *SystemdTestSuite) TestIsActiveIsInactive(c *C) { 792 sysErr := &Error{} 793 // manpage states that systemctl returns exit code 3 for inactive 794 // services, however we should check any non-0 exit status 795 sysErr.SetExitCode(1) 796 sysErr.SetMsg([]byte("inactive\n")) 797 s.errors = []error{sysErr} 798 799 active, err := New(SystemMode, s.rep).IsActive("foo") 800 c.Assert(active, Equals, false) 801 c.Assert(err, IsNil) 802 c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}}) 803 } 804 805 func (s *SystemdTestSuite) TestIsActiveIsFailed(c *C) { 806 sysErr := &Error{} 807 // seen in the wild to be reported for a 'failed' service 808 sysErr.SetExitCode(3) 809 sysErr.SetMsg([]byte("failed\n")) 810 s.errors = []error{sysErr} 811 812 active, err := New(SystemMode, s.rep).IsActive("foo") 813 c.Assert(active, Equals, false) 814 c.Assert(err, IsNil) 815 c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}}) 816 } 817 818 func (s *SystemdTestSuite) TestIsActiveIsActive(c *C) { 819 s.errors = []error{nil} 820 821 active, err := New(SystemMode, s.rep).IsActive("foo") 822 c.Assert(active, Equals, true) 823 c.Assert(err, IsNil) 824 c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}}) 825 } 826 827 func (s *SystemdTestSuite) TestIsActiveUnexpectedErr(c *C) { 828 sysErr := &Error{} 829 sysErr.SetExitCode(1) 830 sysErr.SetMsg([]byte("random-failure\n")) 831 s.errors = []error{sysErr} 832 833 active, err := NewUnderRoot("xyzzy", SystemMode, s.rep).IsActive("foo") 834 c.Assert(active, Equals, false) 835 c.Assert(err, ErrorMatches, ".* failed with exit status 1: random-failure\n") 836 } 837 838 func makeMockMountUnit(c *C, mountDir string) string { 839 mountUnit := MountUnitPath(dirs.StripRootDir(mountDir)) 840 err := ioutil.WriteFile(mountUnit, nil, 0644) 841 c.Assert(err, IsNil) 842 return mountUnit 843 } 844 845 // FIXME: also test for the "IsMounted" case 846 func (s *SystemdTestSuite) TestRemoveMountUnit(c *C) { 847 rootDir := dirs.GlobalRootDir 848 849 restore := osutil.MockMountInfo("") 850 defer restore() 851 852 mountDir := rootDir + "/snap/foo/42" 853 mountUnit := makeMockMountUnit(c, mountDir) 854 err := NewUnderRoot(rootDir, SystemMode, nil).RemoveMountUnitFile(mountDir) 855 856 c.Assert(err, IsNil) 857 // the file is gone 858 c.Check(osutil.FileExists(mountUnit), Equals, false) 859 // and the unit is disabled and the daemon reloaded 860 c.Check(s.argses, DeepEquals, [][]string{ 861 {"--root", rootDir, "disable", "snap-foo-42.mount"}, 862 {"daemon-reload"}, 863 }) 864 } 865 866 func (s *SystemdTestSuite) TestDaemonReloadMutex(c *C) { 867 s.testDaemonReloadMutex(c, Systemd.DaemonReload) 868 } 869 870 func (s *SystemdTestSuite) testDaemonReloadMutex(c *C, reload func(Systemd) error) { 871 rootDir := dirs.GlobalRootDir 872 sysd := NewUnderRoot(rootDir, SystemMode, nil) 873 874 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 875 makeMockFile(c, mockSnapPath) 876 877 // create a go-routine that will try to daemon-reload like crazy 878 stopCh := make(chan bool, 1) 879 stoppedCh := make(chan bool, 1) 880 go func() { 881 for { 882 sysd.DaemonReload() 883 select { 884 case <-stopCh: 885 close(stoppedCh) 886 return 887 default: 888 //pass 889 } 890 } 891 }() 892 893 // And now add a mount unit file while the go-routine tries to 894 // daemon-reload. This will be serialized, if not this would 895 // panic because systemd.daemonReloadNoLock ensures the lock is 896 // taken when this happens. 897 _, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/foo/42", "squashfs") 898 c.Assert(err, IsNil) 899 close(stopCh) 900 <-stoppedCh 901 } 902 903 func (s *SystemdTestSuite) TestDaemonReexecMutex(c *C) { 904 s.testDaemonReloadMutex(c, Systemd.DaemonReexec) 905 } 906 907 func (s *SystemdTestSuite) TestUserMode(c *C) { 908 rootDir := dirs.GlobalRootDir 909 sysd := NewUnderRoot(rootDir, UserMode, nil) 910 911 c.Assert(sysd.Enable("foo"), IsNil) 912 c.Check(s.argses[0], DeepEquals, []string{"--user", "--root", rootDir, "enable", "foo"}) 913 c.Assert(sysd.Start("foo"), IsNil) 914 c.Check(s.argses[1], DeepEquals, []string{"--user", "start", "foo"}) 915 } 916 917 func (s *SystemdTestSuite) TestGlobalUserMode(c *C) { 918 rootDir := dirs.GlobalRootDir 919 sysd := NewUnderRoot(rootDir, GlobalUserMode, nil) 920 921 c.Assert(sysd.Enable("foo"), IsNil) 922 c.Check(s.argses[0], DeepEquals, []string{"--user", "--global", "--root", rootDir, "enable", "foo"}) 923 c.Assert(sysd.Disable("foo"), IsNil) 924 c.Check(s.argses[1], DeepEquals, []string{"--user", "--global", "--root", rootDir, "disable", "foo"}) 925 c.Assert(sysd.Mask("foo"), IsNil) 926 c.Check(s.argses[2], DeepEquals, []string{"--user", "--global", "--root", rootDir, "mask", "foo"}) 927 c.Assert(sysd.Unmask("foo"), IsNil) 928 c.Check(s.argses[3], DeepEquals, []string{"--user", "--global", "--root", rootDir, "unmask", "foo"}) 929 _, err := sysd.IsEnabled("foo") 930 c.Check(err, IsNil) 931 c.Check(s.argses[4], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "foo"}) 932 933 // Commands that don't make sense for GlobalUserMode panic 934 c.Check(sysd.DaemonReload, Panics, "cannot call daemon-reload with GlobalUserMode") 935 c.Check(sysd.DaemonReexec, Panics, "cannot call daemon-reexec with GlobalUserMode") 936 c.Check(func() { sysd.Start("foo") }, Panics, "cannot call start with GlobalUserMode") 937 c.Check(func() { sysd.StartNoBlock("foo") }, Panics, "cannot call start with GlobalUserMode") 938 c.Check(func() { sysd.Stop("foo", 0) }, Panics, "cannot call stop with GlobalUserMode") 939 c.Check(func() { sysd.Restart("foo", 0) }, Panics, "cannot call restart with GlobalUserMode") 940 c.Check(func() { sysd.Kill("foo", "HUP", "") }, Panics, "cannot call kill with GlobalUserMode") 941 c.Check(func() { sysd.Status("foo") }, Panics, "cannot call status with GlobalUserMode") 942 c.Check(func() { sysd.IsActive("foo") }, Panics, "cannot call is-active with GlobalUserMode") 943 } 944 945 const unitTemplate = ` 946 [Unit] 947 Description=Mount unit for foo, revision 42 948 Before=snapd.service 949 950 [Mount] 951 What=%s 952 Where=/snap/snapname/123 953 Type=%s 954 Options=%s 955 LazyUnmount=yes 956 957 [Install] 958 WantedBy=multi-user.target 959 ` 960 961 func (s *SystemdTestSuite) TestPreseedModeAddMountUnit(c *C) { 962 sysd := NewEmulationMode(dirs.GlobalRootDir) 963 964 restore := squashfs.MockNeedsFuse(false) 965 defer restore() 966 967 mockMountCmd := testutil.MockCommand(c, "mount", "") 968 defer mockMountCmd.Restore() 969 970 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 971 makeMockFile(c, mockSnapPath) 972 973 mountUnitName, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs") 974 c.Assert(err, IsNil) 975 defer os.Remove(mountUnitName) 976 977 // systemd was not called 978 c.Check(s.argses, HasLen, 0) 979 // mount was called 980 c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", mockSnapPath, "/snap/snapname/123", "-o", "nodev,ro,x-gdu.hide"}) 981 // unit was enabled with a symlink 982 c.Check(osutil.IsSymlink(filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", mountUnitName)), Equals, true) 983 c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(unitTemplate[1:], mockSnapPath, "squashfs", "nodev,ro,x-gdu.hide")) 984 } 985 986 func (s *SystemdTestSuite) TestPreseedModeAddMountUnitWithFuse(c *C) { 987 sysd := NewEmulationMode(dirs.GlobalRootDir) 988 989 restore := MockSquashFsType(func() (string, []string) { return "fuse.squashfuse", []string{"a,b,c"} }) 990 defer restore() 991 992 mockMountCmd := testutil.MockCommand(c, "mount", "") 993 defer mockMountCmd.Restore() 994 995 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 996 makeMockFile(c, mockSnapPath) 997 998 mountUnitName, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs") 999 c.Assert(err, IsNil) 1000 defer os.Remove(mountUnitName) 1001 1002 c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "fuse.squashfuse", mockSnapPath, "/snap/snapname/123", "-o", "nodev,a,b,c"}) 1003 c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(unitTemplate[1:], mockSnapPath, "squashfs", "nodev,ro,x-gdu.hide")) 1004 } 1005 1006 func (s *SystemdTestSuite) TestPreseedModeMountError(c *C) { 1007 sysd := NewEmulationMode(dirs.GlobalRootDir) 1008 1009 restore := squashfs.MockNeedsFuse(false) 1010 defer restore() 1011 1012 mockMountCmd := testutil.MockCommand(c, "mount", `echo "some failure"; exit 1`) 1013 defer mockMountCmd.Restore() 1014 1015 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 1016 makeMockFile(c, mockSnapPath) 1017 1018 _, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs") 1019 c.Assert(err, ErrorMatches, `cannot mount .*/var/lib/snappy/snaps/foo_1.0.snap \(squashfs\) at /snap/snapname/123 in preseed mode: exit status 1; some failure\n`) 1020 } 1021 1022 func (s *SystemdTestSuite) TestPreseedModeRemoveMountUnit(c *C) { 1023 mountDir := dirs.GlobalRootDir + "/snap/foo/42" 1024 1025 restore := MockOsutilIsMounted(func(path string) (bool, error) { 1026 c.Check(path, Equals, mountDir) 1027 return true, nil 1028 }) 1029 defer restore() 1030 1031 mockUmountCmd := testutil.MockCommand(c, "umount", "") 1032 defer mockUmountCmd.Restore() 1033 1034 sysd := NewEmulationMode(dirs.GlobalRootDir) 1035 1036 mountUnit := makeMockMountUnit(c, mountDir) 1037 symlinkPath := filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", filepath.Base(mountUnit)) 1038 c.Assert(os.Symlink(mountUnit, symlinkPath), IsNil) 1039 c.Assert(sysd.RemoveMountUnitFile(mountDir), IsNil) 1040 1041 // the file is gone 1042 c.Check(osutil.FileExists(mountUnit), Equals, false) 1043 // unit symlink is gone 1044 c.Check(osutil.IsSymlink(symlinkPath), Equals, false) 1045 // and systemd was not called 1046 c.Check(s.argses, HasLen, 0) 1047 // umount was called 1048 c.Check(mockUmountCmd.Calls(), DeepEquals, [][]string{{"umount", "-d", "-l", mountDir}}) 1049 } 1050 1051 func (s *SystemdTestSuite) TestPreseedModeRemoveMountUnitUnmounted(c *C) { 1052 mountDir := dirs.GlobalRootDir + "/snap/foo/42" 1053 1054 restore := MockOsutilIsMounted(func(path string) (bool, error) { 1055 c.Check(path, Equals, mountDir) 1056 return false, nil 1057 }) 1058 defer restore() 1059 1060 mockUmountCmd := testutil.MockCommand(c, "umount", "") 1061 defer mockUmountCmd.Restore() 1062 1063 sysd := NewEmulationMode(dirs.GlobalRootDir) 1064 mountUnit := makeMockMountUnit(c, mountDir) 1065 symlinkPath := filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", filepath.Base(mountUnit)) 1066 c.Assert(os.Symlink(mountUnit, symlinkPath), IsNil) 1067 1068 c.Assert(sysd.RemoveMountUnitFile(mountDir), IsNil) 1069 1070 // the file is gone 1071 c.Check(osutil.FileExists(mountUnit), Equals, false) 1072 // unit symlink is gone 1073 c.Check(osutil.IsSymlink(symlinkPath), Equals, false) 1074 // and systemd was not called 1075 c.Check(s.argses, HasLen, 0) 1076 // umount was not called 1077 c.Check(mockUmountCmd.Calls(), HasLen, 0) 1078 } 1079 1080 func (s *SystemdTestSuite) TestPreseedModeBindmountNotSupported(c *C) { 1081 sysd := NewEmulationMode(dirs.GlobalRootDir) 1082 1083 restore := squashfs.MockNeedsFuse(false) 1084 defer restore() 1085 1086 mockSnapPath := c.MkDir() 1087 1088 _, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "") 1089 c.Assert(err, ErrorMatches, `bind-mounted directory is not supported in emulation mode`) 1090 } 1091 1092 func (s *SystemdTestSuite) TestEnableInEmulationMode(c *C) { 1093 sysd := NewEmulationMode("/path") 1094 c.Assert(sysd.Enable("foo"), IsNil) 1095 1096 sysd = NewEmulationMode("") 1097 c.Assert(sysd.Enable("bar"), IsNil) 1098 c.Check(s.argses, DeepEquals, [][]string{ 1099 {"--root", "/path", "enable", "foo"}, 1100 {"--root", dirs.GlobalRootDir, "enable", "bar"}}) 1101 } 1102 1103 func (s *SystemdTestSuite) TestDisableInEmulationMode(c *C) { 1104 sysd := NewEmulationMode("/path") 1105 c.Assert(sysd.Disable("foo"), IsNil) 1106 1107 c.Check(s.argses, DeepEquals, [][]string{ 1108 {"--root", "/path", "disable", "foo"}}) 1109 } 1110 1111 func (s *SystemdTestSuite) TestMaskInEmulationMode(c *C) { 1112 sysd := NewEmulationMode("/path") 1113 c.Assert(sysd.Mask("foo"), IsNil) 1114 1115 c.Check(s.argses, DeepEquals, [][]string{ 1116 {"--root", "/path", "mask", "foo"}}) 1117 } 1118 1119 func (s *SystemdTestSuite) TestUnmaskInEmulationMode(c *C) { 1120 sysd := NewEmulationMode("/path") 1121 c.Assert(sysd.Unmask("foo"), IsNil) 1122 1123 c.Check(s.argses, DeepEquals, [][]string{ 1124 {"--root", "/path", "unmask", "foo"}}) 1125 }