github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/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 "encoding/json" 25 "fmt" 26 "io" 27 "io/ioutil" 28 "os" 29 "path/filepath" 30 "strconv" 31 "testing" 32 "time" 33 34 . "gopkg.in/check.v1" 35 36 "github.com/snapcore/snapd/dirs" 37 "github.com/snapcore/snapd/gadget/quantity" 38 "github.com/snapcore/snapd/osutil" 39 "github.com/snapcore/snapd/osutil/squashfs" 40 "github.com/snapcore/snapd/sandbox/selinux" 41 "github.com/snapcore/snapd/testutil" 42 43 . "github.com/snapcore/snapd/systemd" 44 ) 45 46 type testreporter struct { 47 msgs []string 48 } 49 50 func (tr *testreporter) Notify(msg string) { 51 tr.msgs = append(tr.msgs, msg) 52 } 53 54 // Hook up check.v1 into the "go test" runner 55 func Test(t *testing.T) { TestingT(t) } 56 57 // systemd's testsuite 58 type SystemdTestSuite struct { 59 i int 60 argses [][]string 61 errors []error 62 outs [][]byte 63 64 j int 65 jns []string 66 jsvcs [][]string 67 jouts [][]byte 68 jerrs []error 69 jfollows []bool 70 71 rep *testreporter 72 73 restoreSystemctl func() 74 restoreJournalctl func() 75 restoreSELinux func() 76 } 77 78 var _ = Suite(&SystemdTestSuite{}) 79 80 func (s *SystemdTestSuite) SetUpTest(c *C) { 81 dirs.SetRootDir(c.MkDir()) 82 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 83 c.Assert(err, IsNil) 84 c.Assert(os.MkdirAll(filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants"), 0755), IsNil) 85 86 // force UTC timezone, for reproducible timestamps 87 os.Setenv("TZ", "") 88 89 s.restoreSystemctl = MockSystemctl(s.myRun) 90 s.i = 0 91 s.argses = nil 92 s.errors = nil 93 s.outs = nil 94 95 s.restoreJournalctl = MockJournalctl(s.myJctl) 96 s.j = 0 97 s.jns = nil 98 s.jsvcs = nil 99 s.jouts = nil 100 s.jerrs = nil 101 s.jfollows = nil 102 103 s.rep = new(testreporter) 104 105 s.restoreSELinux = selinux.MockIsEnabled(func() (bool, error) { return false, nil }) 106 } 107 108 func (s *SystemdTestSuite) TearDownTest(c *C) { 109 s.restoreSystemctl() 110 s.restoreJournalctl() 111 s.restoreSELinux() 112 } 113 114 func (s *SystemdTestSuite) myRun(args ...string) (out []byte, err error) { 115 s.argses = append(s.argses, args) 116 if s.i < len(s.outs) { 117 out = s.outs[s.i] 118 } 119 if s.i < len(s.errors) { 120 err = s.errors[s.i] 121 } 122 s.i++ 123 return out, err 124 } 125 126 func (s *SystemdTestSuite) myJctl(svcs []string, n int, follow bool) (io.ReadCloser, error) { 127 var err error 128 var out []byte 129 130 s.jns = append(s.jns, strconv.Itoa(n)) 131 s.jsvcs = append(s.jsvcs, svcs) 132 s.jfollows = append(s.jfollows, follow) 133 134 if s.j < len(s.jouts) { 135 out = s.jouts[s.j] 136 } 137 if s.j < len(s.jerrs) { 138 err = s.jerrs[s.j] 139 } 140 s.j++ 141 142 if out == nil { 143 return nil, err 144 } 145 146 return ioutil.NopCloser(bytes.NewReader(out)), err 147 } 148 149 func (s *SystemdTestSuite) TestDaemonReload(c *C) { 150 err := New(SystemMode, s.rep).DaemonReload() 151 c.Assert(err, IsNil) 152 c.Assert(s.argses, DeepEquals, [][]string{{"daemon-reload"}}) 153 } 154 155 func (s *SystemdTestSuite) TestDaemonReexec(c *C) { 156 err := New(SystemMode, s.rep).DaemonReexec() 157 c.Assert(err, IsNil) 158 c.Assert(s.argses, DeepEquals, [][]string{{"daemon-reexec"}}) 159 } 160 161 func (s *SystemdTestSuite) TestStart(c *C) { 162 err := New(SystemMode, s.rep).Start("foo") 163 c.Assert(err, IsNil) 164 c.Check(s.argses, DeepEquals, [][]string{{"start", "foo"}}) 165 } 166 167 func (s *SystemdTestSuite) TestStartMany(c *C) { 168 err := New(SystemMode, s.rep).Start("foo", "bar", "baz") 169 c.Assert(err, IsNil) 170 c.Check(s.argses, DeepEquals, [][]string{{"start", "foo", "bar", "baz"}}) 171 } 172 173 func (s *SystemdTestSuite) TestStop(c *C) { 174 restore := MockStopDelays(time.Millisecond, 25*time.Second) 175 defer restore() 176 s.outs = [][]byte{ 177 nil, // for the "stop" itself 178 []byte("ActiveState=whatever\n"), 179 []byte("ActiveState=active\n"), 180 []byte("ActiveState=inactive\n"), 181 } 182 s.errors = []error{nil, nil, nil, nil, &Timeout{}} 183 err := New(SystemMode, s.rep).Stop("foo", 1*time.Second) 184 c.Assert(err, IsNil) 185 c.Assert(s.argses, HasLen, 4) 186 c.Check(s.argses[0], DeepEquals, []string{"stop", "foo"}) 187 c.Check(s.argses[1], DeepEquals, []string{"show", "--property=ActiveState", "foo"}) 188 c.Check(s.argses[1], DeepEquals, s.argses[2]) 189 c.Check(s.argses[1], DeepEquals, s.argses[3]) 190 } 191 192 func (s *SystemdTestSuite) TestStatus(c *C) { 193 s.outs = [][]byte{ 194 []byte(` 195 Type=simple 196 Id=foo.service 197 ActiveState=active 198 UnitFileState=enabled 199 200 Type=simple 201 Id=bar.service 202 ActiveState=reloading 203 UnitFileState=static 204 205 Type=potato 206 Id=baz.service 207 ActiveState=inactive 208 UnitFileState=disabled 209 210 Type= 211 Id=missing.service 212 ActiveState=inactive 213 UnitFileState= 214 `[1:]), 215 []byte(` 216 Id=some.timer 217 ActiveState=active 218 UnitFileState=enabled 219 220 Id=other.socket 221 ActiveState=active 222 UnitFileState=disabled 223 `[1:]), 224 } 225 s.errors = []error{nil} 226 out, err := New(SystemMode, s.rep).Status("foo.service", "bar.service", "baz.service", "missing.service", "some.timer", "other.socket") 227 c.Assert(err, IsNil) 228 c.Check(out, DeepEquals, []*UnitStatus{ 229 { 230 Daemon: "simple", 231 UnitName: "foo.service", 232 Active: true, 233 Enabled: true, 234 Installed: true, 235 }, { 236 Daemon: "simple", 237 UnitName: "bar.service", 238 Active: true, 239 Enabled: true, 240 Installed: true, 241 }, { 242 Daemon: "potato", 243 UnitName: "baz.service", 244 Active: false, 245 Enabled: false, 246 Installed: true, 247 }, { 248 Daemon: "", 249 UnitName: "missing.service", 250 Active: false, 251 Enabled: false, 252 Installed: false, 253 }, { 254 UnitName: "some.timer", 255 Active: true, 256 Enabled: true, 257 Installed: true, 258 }, { 259 UnitName: "other.socket", 260 Active: true, 261 Enabled: false, 262 Installed: true, 263 }, 264 }) 265 c.Check(s.rep.msgs, IsNil) 266 c.Assert(s.argses, DeepEquals, [][]string{ 267 {"show", "--property=Id,ActiveState,UnitFileState,Type", "foo.service", "bar.service", "baz.service", "missing.service"}, 268 {"show", "--property=Id,ActiveState,UnitFileState", "some.timer", "other.socket"}, 269 }) 270 } 271 272 func (s *SystemdTestSuite) TestStatusBadNumberOfValues(c *C) { 273 s.outs = [][]byte{ 274 []byte(` 275 Type=simple 276 Id=foo.service 277 ActiveState=active 278 UnitFileState=enabled 279 280 Type=simple 281 Id=foo.service 282 ActiveState=active 283 UnitFileState=enabled 284 `[1:]), 285 } 286 s.errors = []error{nil} 287 out, err := New(SystemMode, s.rep).Status("foo.service") 288 c.Check(err, ErrorMatches, "cannot get unit status: expected 1 results, got 2") 289 c.Check(out, IsNil) 290 c.Check(s.rep.msgs, IsNil) 291 } 292 293 func (s *SystemdTestSuite) TestStatusBadLine(c *C) { 294 s.outs = [][]byte{ 295 []byte(` 296 Type=simple 297 Id=foo.service 298 ActiveState=active 299 UnitFileState=enabled 300 Potatoes 301 `[1:]), 302 } 303 s.errors = []error{nil} 304 out, err := New(SystemMode, s.rep).Status("foo.service") 305 c.Assert(err, ErrorMatches, `.* bad line "Potatoes" .*`) 306 c.Check(out, IsNil) 307 } 308 309 func (s *SystemdTestSuite) TestStatusBadId(c *C) { 310 s.outs = [][]byte{ 311 []byte(` 312 Type=simple 313 Id=bar.service 314 ActiveState=active 315 UnitFileState=enabled 316 `[1:]), 317 } 318 s.errors = []error{nil} 319 out, err := New(SystemMode, s.rep).Status("foo.service") 320 c.Assert(err, ErrorMatches, `.* queried status of "foo.service" but got status of "bar.service"`) 321 c.Check(out, IsNil) 322 } 323 324 func (s *SystemdTestSuite) TestStatusBadField(c *C) { 325 s.outs = [][]byte{ 326 []byte(` 327 Type=simple 328 Id=foo.service 329 ActiveState=active 330 UnitFileState=enabled 331 Potatoes=false 332 `[1:]), 333 } 334 s.errors = []error{nil} 335 out, err := New(SystemMode, s.rep).Status("foo.service") 336 c.Assert(err, ErrorMatches, `.* unexpected field "Potatoes" .*`) 337 c.Check(out, IsNil) 338 } 339 340 func (s *SystemdTestSuite) TestStatusMissingRequiredFieldService(c *C) { 341 s.outs = [][]byte{ 342 []byte(` 343 Id=foo.service 344 ActiveState=active 345 `[1:]), 346 } 347 s.errors = []error{nil} 348 out, err := New(SystemMode, s.rep).Status("foo.service") 349 c.Assert(err, ErrorMatches, `.* missing UnitFileState, Type .*`) 350 c.Check(out, IsNil) 351 } 352 353 func (s *SystemdTestSuite) TestStatusMissingRequiredFieldTimer(c *C) { 354 s.outs = [][]byte{ 355 []byte(` 356 Id=foo.timer 357 ActiveState=active 358 `[1:]), 359 } 360 s.errors = []error{nil} 361 out, err := New(SystemMode, s.rep).Status("foo.timer") 362 c.Assert(err, ErrorMatches, `.* missing UnitFileState .*`) 363 c.Check(out, IsNil) 364 } 365 366 func (s *SystemdTestSuite) TestStatusDupeField(c *C) { 367 s.outs = [][]byte{ 368 []byte(` 369 Type=simple 370 Id=foo.service 371 ActiveState=active 372 ActiveState=active 373 UnitFileState=enabled 374 `[1:]), 375 } 376 s.errors = []error{nil} 377 out, err := New(SystemMode, s.rep).Status("foo.service") 378 c.Assert(err, ErrorMatches, `.* duplicate field "ActiveState" .*`) 379 c.Check(out, IsNil) 380 } 381 382 func (s *SystemdTestSuite) TestStatusEmptyField(c *C) { 383 s.outs = [][]byte{ 384 []byte(` 385 Type=simple 386 Id= 387 ActiveState=active 388 UnitFileState=enabled 389 `[1:]), 390 } 391 s.errors = []error{nil} 392 out, err := New(SystemMode, s.rep).Status("foo.service") 393 c.Assert(err, ErrorMatches, `.* empty field "Id" .*`) 394 c.Check(out, IsNil) 395 } 396 397 func (s *SystemdTestSuite) TestStopTimeout(c *C) { 398 restore := MockStopDelays(time.Millisecond, 25*time.Second) 399 defer restore() 400 err := New(SystemMode, s.rep).Stop("foo", 10*time.Millisecond) 401 c.Assert(err, FitsTypeOf, &Timeout{}) 402 c.Assert(len(s.rep.msgs) > 0, Equals, true) 403 c.Check(s.rep.msgs[0], Equals, "Waiting for foo to stop.") 404 } 405 406 func (s *SystemdTestSuite) TestDisable(c *C) { 407 err := New(SystemMode, s.rep).Disable("foo") 408 c.Assert(err, IsNil) 409 c.Check(s.argses, DeepEquals, [][]string{{"disable", "foo"}}) 410 } 411 412 func (s *SystemdTestSuite) TestUnderRootDisable(c *C) { 413 err := NewUnderRoot("xyzzy", SystemMode, s.rep).Disable("foo") 414 c.Assert(err, IsNil) 415 c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "disable", "foo"}}) 416 } 417 418 func (s *SystemdTestSuite) TestAvailable(c *C) { 419 err := Available() 420 c.Assert(err, IsNil) 421 c.Check(s.argses, DeepEquals, [][]string{{"--version"}}) 422 } 423 424 func (s *SystemdTestSuite) TestVersion(c *C) { 425 s.outs = [][]byte{ 426 []byte("systemd 223\n+PAM\n"), 427 []byte("systemd 245 (245.4-4ubuntu3)\n+PAM +AUDIT +SELINUX +IMA\n"), 428 // error cases 429 []byte("foo 223\n+PAM\n"), 430 []byte(""), 431 []byte("systemd abc\n+PAM\n"), 432 } 433 434 v, err := Version() 435 c.Assert(err, IsNil) 436 c.Check(v, Equals, 223) 437 438 v, err = Version() 439 c.Assert(err, IsNil) 440 c.Check(v, Equals, 245) 441 442 _, err = Version() 443 c.Assert(err, ErrorMatches, `cannot parse systemd version: expected "systemd", got "foo"`) 444 445 _, err = Version() 446 c.Assert(err, ErrorMatches, `cannot read systemd version: <nil>`) 447 448 _, err = Version() 449 c.Assert(err, ErrorMatches, `cannot convert systemd version to number: abc`) 450 451 c.Check(s.argses, DeepEquals, [][]string{ 452 {"--version"}, 453 {"--version"}, 454 {"--version"}, 455 {"--version"}, 456 {"--version"}, 457 }) 458 } 459 460 func (s *SystemdTestSuite) TestEnable(c *C) { 461 err := New(SystemMode, s.rep).Enable("foo") 462 c.Assert(err, IsNil) 463 c.Check(s.argses, DeepEquals, [][]string{{"enable", "foo"}}) 464 } 465 466 func (s *SystemdTestSuite) TestEnableUnderRoot(c *C) { 467 err := NewUnderRoot("xyzzy", SystemMode, s.rep).Enable("foo") 468 c.Assert(err, IsNil) 469 c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "enable", "foo"}}) 470 } 471 472 func (s *SystemdTestSuite) TestMask(c *C) { 473 err := New(SystemMode, s.rep).Mask("foo") 474 c.Assert(err, IsNil) 475 c.Check(s.argses, DeepEquals, [][]string{{"mask", "foo"}}) 476 } 477 478 func (s *SystemdTestSuite) TestMaskUnderRoot(c *C) { 479 err := NewUnderRoot("xyzzy", SystemMode, s.rep).Mask("foo") 480 c.Assert(err, IsNil) 481 c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "mask", "foo"}}) 482 } 483 484 func (s *SystemdTestSuite) TestUnmask(c *C) { 485 err := New(SystemMode, s.rep).Unmask("foo") 486 c.Assert(err, IsNil) 487 c.Check(s.argses, DeepEquals, [][]string{{"unmask", "foo"}}) 488 } 489 490 func (s *SystemdTestSuite) TestUnmaskUnderRoot(c *C) { 491 err := NewUnderRoot("xyzzy", SystemMode, s.rep).Unmask("foo") 492 c.Assert(err, IsNil) 493 c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "unmask", "foo"}}) 494 } 495 496 func (s *SystemdTestSuite) TestRestart(c *C) { 497 restore := MockStopDelays(time.Millisecond, 25*time.Second) 498 defer restore() 499 s.outs = [][]byte{ 500 nil, // for the "stop" itself 501 []byte("ActiveState=inactive\n"), 502 nil, // for the "start" 503 } 504 s.errors = []error{nil, nil, nil, nil, &Timeout{}} 505 err := New(SystemMode, s.rep).Restart("foo", 100*time.Millisecond) 506 c.Assert(err, IsNil) 507 c.Check(s.argses, HasLen, 3) 508 c.Check(s.argses[0], DeepEquals, []string{"stop", "foo"}) 509 c.Check(s.argses[1], DeepEquals, []string{"show", "--property=ActiveState", "foo"}) 510 c.Check(s.argses[2], DeepEquals, []string{"start", "foo"}) 511 } 512 513 func (s *SystemdTestSuite) TestKill(c *C) { 514 c.Assert(New(SystemMode, s.rep).Kill("foo", "HUP", ""), IsNil) 515 c.Check(s.argses, DeepEquals, [][]string{{"kill", "foo", "-s", "HUP", "--kill-who=all"}}) 516 } 517 518 func (s *SystemdTestSuite) TestIsTimeout(c *C) { 519 c.Check(IsTimeout(os.ErrInvalid), Equals, false) 520 c.Check(IsTimeout(&Timeout{}), Equals, true) 521 } 522 523 func (s *SystemdTestSuite) TestLogErrJctl(c *C) { 524 s.jerrs = []error{&Timeout{}} 525 526 reader, err := New(SystemMode, s.rep).LogReader([]string{"foo"}, 24, false) 527 c.Check(err, NotNil) 528 c.Check(reader, IsNil) 529 c.Check(s.jns, DeepEquals, []string{"24"}) 530 c.Check(s.jsvcs, DeepEquals, [][]string{{"foo"}}) 531 c.Check(s.jfollows, DeepEquals, []bool{false}) 532 c.Check(s.j, Equals, 1) 533 } 534 535 func (s *SystemdTestSuite) TestLogs(c *C) { 536 expected := `{"a": 1} 537 {"a": 2} 538 ` 539 s.jouts = [][]byte{[]byte(expected)} 540 541 reader, err := New(SystemMode, s.rep).LogReader([]string{"foo"}, 24, false) 542 c.Check(err, IsNil) 543 logs, err := ioutil.ReadAll(reader) 544 c.Assert(err, IsNil) 545 c.Check(string(logs), Equals, expected) 546 c.Check(s.jns, DeepEquals, []string{"24"}) 547 c.Check(s.jsvcs, DeepEquals, [][]string{{"foo"}}) 548 c.Check(s.jfollows, DeepEquals, []bool{false}) 549 c.Check(s.j, Equals, 1) 550 } 551 552 // mustJSONMarshal panic's if the value cannot be marshaled 553 func mustJSONMarshal(v interface{}) *json.RawMessage { 554 b, err := json.Marshal(v) 555 if err != nil { 556 panic(fmt.Sprintf("couldn't marshal json in test fixture: %v", err)) 557 } 558 msg := json.RawMessage(b) 559 return &msg 560 } 561 562 func (s *SystemdTestSuite) TestLogPIDWithNonTrivialKeyValues(c *C) { 563 l1 := Log{ 564 "_PID": mustJSONMarshal([]string{}), 565 } 566 l2 := Log{ 567 "_PID": mustJSONMarshal(6), 568 } 569 l3 := Log{ 570 "_PID": mustJSONMarshal([]string{"pid1", "pid2", "pid3"}), 571 } 572 l4 := Log{ 573 "SYSLOG_PID": mustJSONMarshal([]string{"pid1", "pid2", "pid3"}), 574 } 575 l5 := Log{ 576 "_PID": mustJSONMarshal("42"), 577 "SYSLOG_PID": mustJSONMarshal([]string{"pid1", "pid2", "pid3"}), 578 } 579 l6 := Log{ 580 "_PID": mustJSONMarshal([]string{"42"}), 581 "SYSLOG_PID": mustJSONMarshal([]string{"pid1", "pid2", "pid3"}), 582 } 583 l7 := Log{ 584 "_PID": mustJSONMarshal([]string{"42", "42"}), 585 "SYSLOG_PID": mustJSONMarshal([]string{"singlepid"}), 586 } 587 c.Check(Log{}.PID(), Equals, "-") 588 c.Check(l1.PID(), Equals, "-") 589 c.Check(l2.PID(), Equals, "-") 590 c.Check(l3.PID(), Equals, "-") 591 c.Check(l4.PID(), Equals, "-") 592 // things starting with underscore are "trusted", so we trust 593 // them more than the user-settable ones: 594 c.Check(l5.PID(), Equals, "42") 595 c.Check(l6.PID(), Equals, "42") 596 c.Check(l7.PID(), Equals, "singlepid") 597 } 598 599 func (s *SystemdTestSuite) TestLogsMessageWithNonUniqueKeys(c *C) { 600 601 tt := []struct { 602 msg *json.RawMessage 603 exp string 604 comment string 605 }{ 606 { 607 mustJSONMarshal("m1"), 608 "m1", 609 "simple string", 610 }, 611 { 612 mustJSONMarshal("Я"), 613 "Я", 614 "simple utf-8 string", 615 }, 616 { 617 mustJSONMarshal([]rune{65, 66, 67, 192, 69}), 618 "ABC\xc0E", 619 "invalid utf-8 bytes", 620 }, 621 { 622 mustJSONMarshal(""), 623 "", 624 "empty string", 625 }, 626 { 627 mustJSONMarshal([]string{"m1", "m2", "m3"}), 628 "m1\nm2\nm3", 629 "slice of strings", 630 }, 631 { 632 // this is just "hello" in ascii 633 mustJSONMarshal([]rune{104, 101, 108, 108, 111}), 634 "hello", 635 "rune arrays are converted to strings", 636 }, 637 { 638 // this is "hello\r" in ascii, the \r char is unprintable 639 mustJSONMarshal([]rune{104, 101, 108, 108, 111, 13}), 640 "hello\r", 641 "rune arrays are converted to strings", 642 }, 643 { 644 // this is "hel" and "lo" in ascii 645 mustJSONMarshal([][]rune{ 646 {104, 101, 108}, 647 {108, 111}, 648 }), 649 "hel\nlo", 650 "arrays of rune arrays are converted to arrays of strings", 651 }, 652 { 653 // this is invalid utf-8 string followed by a valid one 654 mustJSONMarshal([][]byte{ 655 {65, 66, 67, 192, 69}, 656 {104, 101, 108, 108, 111}, 657 }), 658 "ABC\xc0E\nhello", 659 "arrays of bytes, some are invalid utf-8 strings", 660 }, 661 { 662 mustJSONMarshal(5), 663 "- (error decoding original message: unsupported JSON encoding format)", 664 "invalid message format of raw scalar number", 665 }, 666 { 667 mustJSONMarshal(map[string]int{"hello": 1}), 668 "- (error decoding original message: unsupported JSON encoding format)", 669 "invalid message format of map object", 670 }, 671 } 672 673 // trivial case 674 c.Check(Log{}.Message(), Equals, "-") 675 676 // case where the JSON has a "null" JSON value for the key, which happens if 677 // the actual message is too large for journald to send 678 // we can't use the mustJSONMarshal helper for this in the test table 679 // because that gets decoded by Go differently than a verbatim nil here, it 680 // gets interpreted as the empty string rather than nil directly 681 c.Check(Log{"MESSAGE": nil}.Message(), Equals, "- (error decoding original message: message key \"MESSAGE\" truncated)") 682 683 for _, t := range tt { 684 if t.msg == nil { 685 686 } 687 c.Check(Log{ 688 "MESSAGE": t.msg, 689 }.Message(), Equals, t.exp, Commentf(t.comment)) 690 } 691 } 692 693 func (s *SystemdTestSuite) TestLogSID(c *C) { 694 c.Check(Log{}.SID(), Equals, "-") 695 c.Check(Log{"SYSLOG_IDENTIFIER": mustJSONMarshal("abcdef")}.SID(), Equals, "abcdef") 696 c.Check(Log{"SYSLOG_IDENTIFIER": mustJSONMarshal([]string{"abcdef"})}.SID(), Equals, "abcdef") 697 // multiple string values are not supported 698 c.Check(Log{"SYSLOG_IDENTIFIER": mustJSONMarshal([]string{"abc", "def"})}.SID(), Equals, "-") 699 700 } 701 702 func (s *SystemdTestSuite) TestLogPID(c *C) { 703 c.Check(Log{}.PID(), Equals, "-") 704 c.Check(Log{"_PID": mustJSONMarshal("99")}.PID(), Equals, "99") 705 c.Check(Log{"SYSLOG_PID": mustJSONMarshal("99")}.PID(), Equals, "99") 706 // things starting with underscore are "trusted", so we trust 707 // them more than the user-settable ones: 708 c.Check(Log{ 709 "_PID": mustJSONMarshal("42"), 710 "SYSLOG_PID": mustJSONMarshal("99"), 711 }.PID(), Equals, "42") 712 } 713 714 func (s *SystemdTestSuite) TestTime(c *C) { 715 t, err := Log{}.Time() 716 c.Check(t.IsZero(), Equals, true) 717 c.Check(err, ErrorMatches, "key \"__REALTIME_TIMESTAMP\" missing from message") 718 719 // multiple timestampe keys mean we don't have a timestamp 720 t, err = Log{"__REALTIME_TIMESTAMP": mustJSONMarshal([]string{"1", "2"})}.Time() 721 c.Check(t.IsZero(), Equals, true) 722 c.Check(err, ErrorMatches, `no timestamp`) 723 724 t, err = Log{"__REALTIME_TIMESTAMP": mustJSONMarshal("what")}.Time() 725 c.Check(t.IsZero(), Equals, true) 726 c.Check(err, ErrorMatches, `timestamp not a decimal number: "what"`) 727 728 t, err = Log{"__REALTIME_TIMESTAMP": mustJSONMarshal("0")}.Time() 729 c.Check(err, IsNil) 730 c.Check(t.String(), Equals, "1970-01-01 00:00:00 +0000 UTC") 731 732 t, err = Log{"__REALTIME_TIMESTAMP": mustJSONMarshal("42")}.Time() 733 c.Check(err, IsNil) 734 c.Check(t.String(), Equals, "1970-01-01 00:00:00.000042 +0000 UTC") 735 } 736 737 func (s *SystemdTestSuite) TestMountUnitPath(c *C) { 738 c.Assert(MountUnitPath("/apps/hello/1.1"), Equals, filepath.Join(dirs.SnapServicesDir, "apps-hello-1.1.mount")) 739 } 740 741 func makeMockFile(c *C, path string) { 742 err := os.MkdirAll(filepath.Dir(path), 0755) 743 c.Assert(err, IsNil) 744 err = ioutil.WriteFile(path, nil, 0644) 745 c.Assert(err, IsNil) 746 } 747 748 func (s *SystemdTestSuite) TestAddMountUnit(c *C) { 749 rootDir := dirs.GlobalRootDir 750 751 restore := squashfs.MockNeedsFuse(false) 752 defer restore() 753 754 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 755 makeMockFile(c, mockSnapPath) 756 757 mountUnitName, err := NewUnderRoot(rootDir, SystemMode, nil).AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs") 758 c.Assert(err, IsNil) 759 defer os.Remove(mountUnitName) 760 761 c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(` 762 [Unit] 763 Description=Mount unit for foo, revision 42 764 Before=snapd.service 765 After=zfs-mount.service 766 767 [Mount] 768 What=%s 769 Where=/snap/snapname/123 770 Type=squashfs 771 Options=nodev,ro,x-gdu.hide,x-gvfs-hide 772 LazyUnmount=yes 773 774 [Install] 775 WantedBy=multi-user.target 776 `[1:], mockSnapPath)) 777 778 c.Assert(s.argses, DeepEquals, [][]string{ 779 {"daemon-reload"}, 780 {"--root", rootDir, "enable", "snap-snapname-123.mount"}, 781 {"start", "snap-snapname-123.mount"}, 782 }) 783 } 784 785 func (s *SystemdTestSuite) TestAddMountUnitForDirs(c *C) { 786 restore := squashfs.MockNeedsFuse(false) 787 defer restore() 788 789 // a directory instead of a file produces a different output 790 snapDir := c.MkDir() 791 mountUnitName, err := New(SystemMode, nil).AddMountUnitFile("foodir", "x1", snapDir, "/snap/snapname/x1", "squashfs") 792 c.Assert(err, IsNil) 793 defer os.Remove(mountUnitName) 794 795 c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(` 796 [Unit] 797 Description=Mount unit for foodir, revision x1 798 Before=snapd.service 799 After=zfs-mount.service 800 801 [Mount] 802 What=%s 803 Where=/snap/snapname/x1 804 Type=none 805 Options=nodev,ro,x-gdu.hide,x-gvfs-hide,bind 806 LazyUnmount=yes 807 808 [Install] 809 WantedBy=multi-user.target 810 `[1:], snapDir)) 811 812 c.Assert(s.argses, DeepEquals, [][]string{ 813 {"daemon-reload"}, 814 {"enable", "snap-snapname-x1.mount"}, 815 {"start", "snap-snapname-x1.mount"}, 816 }) 817 } 818 819 func (s *SystemdTestSuite) TestWriteSELinuxMountUnit(c *C) { 820 restore := selinux.MockIsEnabled(func() (bool, error) { return true, nil }) 821 defer restore() 822 restore = selinux.MockIsEnforcing(func() (bool, error) { return true, nil }) 823 defer restore() 824 restore = squashfs.MockNeedsFuse(false) 825 defer restore() 826 827 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 828 err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755) 829 c.Assert(err, IsNil) 830 err = ioutil.WriteFile(mockSnapPath, nil, 0644) 831 c.Assert(err, IsNil) 832 833 mountUnitName, err := New(SystemMode, nil).AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs") 834 c.Assert(err, IsNil) 835 defer os.Remove(mountUnitName) 836 837 c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(` 838 [Unit] 839 Description=Mount unit for foo, revision 42 840 Before=snapd.service 841 After=zfs-mount.service 842 843 [Mount] 844 What=%s 845 Where=/snap/snapname/123 846 Type=squashfs 847 Options=nodev,context=system_u:object_r:snappy_snap_t:s0,ro,x-gdu.hide,x-gvfs-hide 848 LazyUnmount=yes 849 850 [Install] 851 WantedBy=multi-user.target 852 `[1:], mockSnapPath)) 853 } 854 855 func (s *SystemdTestSuite) TestFuseInContainer(c *C) { 856 if !osutil.FileExists("/dev/fuse") { 857 c.Skip("No /dev/fuse on the system") 858 } 859 860 systemdCmd := testutil.MockCommand(c, "systemd-detect-virt", ` 861 echo lxc 862 exit 0 863 `) 864 defer systemdCmd.Restore() 865 866 fuseCmd := testutil.MockCommand(c, "squashfuse", ` 867 exit 0 868 `) 869 defer fuseCmd.Restore() 870 871 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 872 err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755) 873 c.Assert(err, IsNil) 874 err = ioutil.WriteFile(mockSnapPath, nil, 0644) 875 c.Assert(err, IsNil) 876 877 mountUnitName, err := New(SystemMode, nil).AddMountUnitFile("foo", "x1", mockSnapPath, "/snap/snapname/123", "squashfs") 878 c.Assert(err, IsNil) 879 defer os.Remove(mountUnitName) 880 881 c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(` 882 [Unit] 883 Description=Mount unit for foo, revision x1 884 Before=snapd.service 885 After=zfs-mount.service 886 887 [Mount] 888 What=%s 889 Where=/snap/snapname/123 890 Type=fuse.squashfuse 891 Options=nodev,ro,x-gdu.hide,x-gvfs-hide,allow_other 892 LazyUnmount=yes 893 894 [Install] 895 WantedBy=multi-user.target 896 `[1:], mockSnapPath)) 897 } 898 899 func (s *SystemdTestSuite) TestFuseOutsideContainer(c *C) { 900 systemdCmd := testutil.MockCommand(c, "systemd-detect-virt", ` 901 echo none 902 exit 0 903 `) 904 defer systemdCmd.Restore() 905 906 fuseCmd := testutil.MockCommand(c, "squashfuse", ` 907 exit 0 908 `) 909 defer fuseCmd.Restore() 910 911 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 912 err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755) 913 c.Assert(err, IsNil) 914 err = ioutil.WriteFile(mockSnapPath, nil, 0644) 915 c.Assert(err, IsNil) 916 917 mountUnitName, err := New(SystemMode, nil).AddMountUnitFile("foo", "x1", mockSnapPath, "/snap/snapname/123", "squashfs") 918 c.Assert(err, IsNil) 919 defer os.Remove(mountUnitName) 920 921 c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(` 922 [Unit] 923 Description=Mount unit for foo, revision x1 924 Before=snapd.service 925 After=zfs-mount.service 926 927 [Mount] 928 What=%s 929 Where=/snap/snapname/123 930 Type=squashfs 931 Options=nodev,ro,x-gdu.hide,x-gvfs-hide 932 LazyUnmount=yes 933 934 [Install] 935 WantedBy=multi-user.target 936 `[1:], mockSnapPath)) 937 } 938 939 func (s *SystemdTestSuite) TestJctl(c *C) { 940 var args []string 941 var err error 942 MockOsutilStreamCommand(func(name string, myargs ...string) (io.ReadCloser, error) { 943 c.Check(cap(myargs) <= len(myargs)+2, Equals, true, Commentf("cap:%d, len:%d", cap(myargs), len(myargs))) 944 args = myargs 945 return nil, nil 946 }) 947 948 _, err = Jctl([]string{"foo", "bar"}, 10, false) 949 c.Assert(err, IsNil) 950 c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "-n", "10", "-u", "foo", "-u", "bar"}) 951 _, err = Jctl([]string{"foo", "bar", "baz"}, 99, true) 952 c.Assert(err, IsNil) 953 c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "-n", "99", "-f", "-u", "foo", "-u", "bar", "-u", "baz"}) 954 _, err = Jctl([]string{"foo", "bar"}, -1, false) 955 c.Assert(err, IsNil) 956 c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "--no-tail", "-u", "foo", "-u", "bar"}) 957 } 958 959 func (s *SystemdTestSuite) TestIsActiveUnderRoot(c *C) { 960 sysErr := &Error{} 961 // manpage states that systemctl returns exit code 3 for inactive 962 // services, however we should check any non-0 exit status 963 sysErr.SetExitCode(1) 964 sysErr.SetMsg([]byte("inactive\n")) 965 s.errors = []error{sysErr} 966 967 _, err := NewUnderRoot("xyzzy", SystemMode, s.rep).IsActive("foo") 968 c.Assert(err, IsNil) 969 c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "is-active", "foo"}}) 970 } 971 972 func (s *SystemdTestSuite) TestIsActiveIsInactive(c *C) { 973 sysErr := &Error{} 974 // manpage states that systemctl returns exit code 3 for inactive 975 // services, however we should check any non-0 exit status 976 sysErr.SetExitCode(1) 977 sysErr.SetMsg([]byte("inactive\n")) 978 s.errors = []error{sysErr} 979 980 active, err := New(SystemMode, s.rep).IsActive("foo") 981 c.Assert(active, Equals, false) 982 c.Assert(err, IsNil) 983 c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}}) 984 } 985 986 func (s *SystemdTestSuite) TestIsActiveIsInactiveAlternativeMessage(c *C) { 987 sysErr := &Error{} 988 // on Centos 7, with systemd 219 we see "unknown" returned when querying the 989 // active state for a slice unit which does not exist, check that we handle 990 // this case properly as well 991 sysErr.SetExitCode(3) 992 sysErr.SetMsg([]byte("unknown\n")) 993 s.errors = []error{sysErr} 994 995 active, err := New(SystemMode, s.rep).IsActive("foo") 996 c.Assert(active, Equals, false) 997 c.Assert(err, IsNil) 998 c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}}) 999 } 1000 1001 func (s *SystemdTestSuite) TestIsActiveIsFailed(c *C) { 1002 sysErr := &Error{} 1003 // seen in the wild to be reported for a 'failed' service 1004 sysErr.SetExitCode(3) 1005 sysErr.SetMsg([]byte("failed\n")) 1006 s.errors = []error{sysErr} 1007 1008 active, err := New(SystemMode, s.rep).IsActive("foo") 1009 c.Assert(active, Equals, false) 1010 c.Assert(err, IsNil) 1011 c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}}) 1012 } 1013 1014 func (s *SystemdTestSuite) TestIsActiveIsActive(c *C) { 1015 s.errors = []error{nil} 1016 1017 active, err := New(SystemMode, s.rep).IsActive("foo") 1018 c.Assert(active, Equals, true) 1019 c.Assert(err, IsNil) 1020 c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}}) 1021 } 1022 1023 func (s *SystemdTestSuite) TestIsActiveUnexpectedErr(c *C) { 1024 sysErr := &Error{} 1025 sysErr.SetExitCode(1) 1026 sysErr.SetMsg([]byte("random-failure\n")) 1027 s.errors = []error{sysErr} 1028 1029 active, err := NewUnderRoot("xyzzy", SystemMode, s.rep).IsActive("foo") 1030 c.Assert(active, Equals, false) 1031 c.Assert(err, ErrorMatches, ".* failed with exit status 1: random-failure\n") 1032 } 1033 1034 func makeMockMountUnit(c *C, mountDir string) string { 1035 mountUnit := MountUnitPath(dirs.StripRootDir(mountDir)) 1036 err := ioutil.WriteFile(mountUnit, nil, 0644) 1037 c.Assert(err, IsNil) 1038 return mountUnit 1039 } 1040 1041 // FIXME: also test for the "IsMounted" case 1042 func (s *SystemdTestSuite) TestRemoveMountUnit(c *C) { 1043 rootDir := dirs.GlobalRootDir 1044 1045 restore := osutil.MockMountInfo("") 1046 defer restore() 1047 1048 mountDir := rootDir + "/snap/foo/42" 1049 mountUnit := makeMockMountUnit(c, mountDir) 1050 err := NewUnderRoot(rootDir, SystemMode, nil).RemoveMountUnitFile(mountDir) 1051 1052 c.Assert(err, IsNil) 1053 // the file is gone 1054 c.Check(osutil.FileExists(mountUnit), Equals, false) 1055 // and the unit is disabled and the daemon reloaded 1056 c.Check(s.argses, DeepEquals, [][]string{ 1057 {"--root", rootDir, "disable", "snap-foo-42.mount"}, 1058 {"daemon-reload"}, 1059 }) 1060 } 1061 1062 func (s *SystemdTestSuite) TestDaemonReloadMutex(c *C) { 1063 s.testDaemonReloadMutex(c, Systemd.DaemonReload) 1064 } 1065 1066 func (s *SystemdTestSuite) testDaemonReloadMutex(c *C, reload func(Systemd) error) { 1067 rootDir := dirs.GlobalRootDir 1068 sysd := NewUnderRoot(rootDir, SystemMode, nil) 1069 1070 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 1071 makeMockFile(c, mockSnapPath) 1072 1073 // create a go-routine that will try to daemon-reload like crazy 1074 stopCh := make(chan bool, 1) 1075 stoppedCh := make(chan bool, 1) 1076 go func() { 1077 for { 1078 sysd.DaemonReload() 1079 select { 1080 case <-stopCh: 1081 close(stoppedCh) 1082 return 1083 default: 1084 //pass 1085 } 1086 } 1087 }() 1088 1089 // And now add a mount unit file while the go-routine tries to 1090 // daemon-reload. This will be serialized, if not this would 1091 // panic because systemd.daemonReloadNoLock ensures the lock is 1092 // taken when this happens. 1093 _, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/foo/42", "squashfs") 1094 c.Assert(err, IsNil) 1095 close(stopCh) 1096 <-stoppedCh 1097 } 1098 1099 func (s *SystemdTestSuite) TestDaemonReexecMutex(c *C) { 1100 s.testDaemonReloadMutex(c, Systemd.DaemonReexec) 1101 } 1102 1103 func (s *SystemdTestSuite) TestUserMode(c *C) { 1104 rootDir := dirs.GlobalRootDir 1105 sysd := NewUnderRoot(rootDir, UserMode, nil) 1106 1107 c.Assert(sysd.Enable("foo"), IsNil) 1108 c.Check(s.argses[0], DeepEquals, []string{"--user", "--root", rootDir, "enable", "foo"}) 1109 c.Assert(sysd.Start("foo"), IsNil) 1110 c.Check(s.argses[1], DeepEquals, []string{"--user", "start", "foo"}) 1111 } 1112 1113 func (s *SystemdTestSuite) TestGlobalUserMode(c *C) { 1114 rootDir := dirs.GlobalRootDir 1115 sysd := NewUnderRoot(rootDir, GlobalUserMode, nil) 1116 1117 c.Assert(sysd.Enable("foo"), IsNil) 1118 c.Check(s.argses[0], DeepEquals, []string{"--user", "--global", "--root", rootDir, "enable", "foo"}) 1119 c.Assert(sysd.Disable("foo"), IsNil) 1120 c.Check(s.argses[1], DeepEquals, []string{"--user", "--global", "--root", rootDir, "disable", "foo"}) 1121 c.Assert(sysd.Mask("foo"), IsNil) 1122 c.Check(s.argses[2], DeepEquals, []string{"--user", "--global", "--root", rootDir, "mask", "foo"}) 1123 c.Assert(sysd.Unmask("foo"), IsNil) 1124 c.Check(s.argses[3], DeepEquals, []string{"--user", "--global", "--root", rootDir, "unmask", "foo"}) 1125 _, err := sysd.IsEnabled("foo") 1126 c.Check(err, IsNil) 1127 c.Check(s.argses[4], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "foo"}) 1128 1129 // Commands that don't make sense for GlobalUserMode panic 1130 c.Check(sysd.DaemonReload, Panics, "cannot call daemon-reload with GlobalUserMode") 1131 c.Check(sysd.DaemonReexec, Panics, "cannot call daemon-reexec with GlobalUserMode") 1132 c.Check(func() { sysd.Start("foo") }, Panics, "cannot call start with GlobalUserMode") 1133 c.Check(func() { sysd.StartNoBlock("foo") }, Panics, "cannot call start with GlobalUserMode") 1134 c.Check(func() { sysd.Stop("foo", 0) }, Panics, "cannot call stop with GlobalUserMode") 1135 c.Check(func() { sysd.Restart("foo", 0) }, Panics, "cannot call restart with GlobalUserMode") 1136 c.Check(func() { sysd.Kill("foo", "HUP", "") }, Panics, "cannot call kill with GlobalUserMode") 1137 c.Check(func() { sysd.IsActive("foo") }, Panics, "cannot call is-active with GlobalUserMode") 1138 } 1139 1140 func (s *SystemdTestSuite) TestStatusGlobalUserMode(c *C) { 1141 output := []byte("enabled\ndisabled\nstatic\n") 1142 sysdErr := &Error{} 1143 sysdErr.SetExitCode(1) 1144 sysdErr.SetMsg(output) 1145 1146 s.outs = [][]byte{output, nil, output} 1147 s.errors = []error{nil, sysdErr, nil} 1148 1149 rootDir := dirs.GlobalRootDir 1150 sysd := NewUnderRoot(rootDir, GlobalUserMode, nil) 1151 sts, err := sysd.Status("foo", "bar", "baz") 1152 c.Check(err, IsNil) 1153 c.Check(sts, DeepEquals, []*UnitStatus{ 1154 {UnitName: "foo", Enabled: true}, 1155 {UnitName: "bar", Enabled: false}, 1156 {UnitName: "baz", Enabled: true}, 1157 }) 1158 c.Check(s.argses[0], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "foo", "bar", "baz"}) 1159 1160 // Output is collected if systemctl has a non-zero exit status 1161 sts, err = sysd.Status("one", "two", "three") 1162 c.Check(err, IsNil) 1163 c.Check(sts, DeepEquals, []*UnitStatus{ 1164 {UnitName: "one", Enabled: true}, 1165 {UnitName: "two", Enabled: false}, 1166 {UnitName: "three", Enabled: true}, 1167 }) 1168 c.Check(s.argses[1], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "one", "two", "three"}) 1169 1170 // An error is returned if the wrong number of statuses are returned 1171 sts, err = sysd.Status("one") 1172 c.Check(err, ErrorMatches, "cannot get enabled status of services: expected 1 results, got 3") 1173 c.Check(sts, IsNil) 1174 c.Check(s.argses[2], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "one"}) 1175 } 1176 1177 const unitTemplate = ` 1178 [Unit] 1179 Description=Mount unit for foo, revision 42 1180 Before=snapd.service 1181 After=zfs-mount.service 1182 1183 [Mount] 1184 What=%s 1185 Where=/snap/snapname/123 1186 Type=%s 1187 Options=%s 1188 LazyUnmount=yes 1189 1190 [Install] 1191 WantedBy=multi-user.target 1192 ` 1193 1194 func (s *SystemdTestSuite) TestPreseedModeAddMountUnit(c *C) { 1195 sysd := NewEmulationMode(dirs.GlobalRootDir) 1196 1197 restore := squashfs.MockNeedsFuse(false) 1198 defer restore() 1199 1200 mockMountCmd := testutil.MockCommand(c, "mount", "") 1201 defer mockMountCmd.Restore() 1202 1203 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 1204 makeMockFile(c, mockSnapPath) 1205 1206 mountUnitName, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs") 1207 c.Assert(err, IsNil) 1208 defer os.Remove(mountUnitName) 1209 1210 // systemd was not called 1211 c.Check(s.argses, HasLen, 0) 1212 // mount was called 1213 c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", mockSnapPath, "/snap/snapname/123", "-o", "nodev,ro,x-gdu.hide,x-gvfs-hide"}) 1214 // unit was enabled with a symlink 1215 c.Check(osutil.IsSymlink(filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", mountUnitName)), Equals, true) 1216 c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(unitTemplate[1:], mockSnapPath, "squashfs", "nodev,ro,x-gdu.hide,x-gvfs-hide")) 1217 } 1218 1219 func (s *SystemdTestSuite) TestPreseedModeAddMountUnitWithFuse(c *C) { 1220 sysd := NewEmulationMode(dirs.GlobalRootDir) 1221 1222 restore := MockSquashFsType(func() (string, []string) { return "fuse.squashfuse", []string{"a,b,c"} }) 1223 defer restore() 1224 1225 mockMountCmd := testutil.MockCommand(c, "mount", "") 1226 defer mockMountCmd.Restore() 1227 1228 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 1229 makeMockFile(c, mockSnapPath) 1230 1231 mountUnitName, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs") 1232 c.Assert(err, IsNil) 1233 defer os.Remove(mountUnitName) 1234 1235 c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "fuse.squashfuse", mockSnapPath, "/snap/snapname/123", "-o", "nodev,a,b,c"}) 1236 c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(unitTemplate[1:], mockSnapPath, "squashfs", "nodev,ro,x-gdu.hide,x-gvfs-hide")) 1237 } 1238 1239 func (s *SystemdTestSuite) TestPreseedModeMountError(c *C) { 1240 sysd := NewEmulationMode(dirs.GlobalRootDir) 1241 1242 restore := squashfs.MockNeedsFuse(false) 1243 defer restore() 1244 1245 mockMountCmd := testutil.MockCommand(c, "mount", `echo "some failure"; exit 1`) 1246 defer mockMountCmd.Restore() 1247 1248 mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") 1249 makeMockFile(c, mockSnapPath) 1250 1251 _, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs") 1252 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`) 1253 } 1254 1255 func (s *SystemdTestSuite) TestPreseedModeRemoveMountUnit(c *C) { 1256 mountDir := dirs.GlobalRootDir + "/snap/foo/42" 1257 1258 restore := MockOsutilIsMounted(func(path string) (bool, error) { 1259 c.Check(path, Equals, mountDir) 1260 return true, nil 1261 }) 1262 defer restore() 1263 1264 mockUmountCmd := testutil.MockCommand(c, "umount", "") 1265 defer mockUmountCmd.Restore() 1266 1267 sysd := NewEmulationMode(dirs.GlobalRootDir) 1268 1269 mountUnit := makeMockMountUnit(c, mountDir) 1270 symlinkPath := filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", filepath.Base(mountUnit)) 1271 c.Assert(os.Symlink(mountUnit, symlinkPath), IsNil) 1272 c.Assert(sysd.RemoveMountUnitFile(mountDir), IsNil) 1273 1274 // the file is gone 1275 c.Check(osutil.FileExists(mountUnit), Equals, false) 1276 // unit symlink is gone 1277 c.Check(osutil.IsSymlink(symlinkPath), Equals, false) 1278 // and systemd was not called 1279 c.Check(s.argses, HasLen, 0) 1280 // umount was called 1281 c.Check(mockUmountCmd.Calls(), DeepEquals, [][]string{{"umount", "-d", "-l", mountDir}}) 1282 } 1283 1284 func (s *SystemdTestSuite) TestPreseedModeRemoveMountUnitUnmounted(c *C) { 1285 mountDir := dirs.GlobalRootDir + "/snap/foo/42" 1286 1287 restore := MockOsutilIsMounted(func(path string) (bool, error) { 1288 c.Check(path, Equals, mountDir) 1289 return false, nil 1290 }) 1291 defer restore() 1292 1293 mockUmountCmd := testutil.MockCommand(c, "umount", "") 1294 defer mockUmountCmd.Restore() 1295 1296 sysd := NewEmulationMode(dirs.GlobalRootDir) 1297 mountUnit := makeMockMountUnit(c, mountDir) 1298 symlinkPath := filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", filepath.Base(mountUnit)) 1299 c.Assert(os.Symlink(mountUnit, symlinkPath), IsNil) 1300 1301 c.Assert(sysd.RemoveMountUnitFile(mountDir), IsNil) 1302 1303 // the file is gone 1304 c.Check(osutil.FileExists(mountUnit), Equals, false) 1305 // unit symlink is gone 1306 c.Check(osutil.IsSymlink(symlinkPath), Equals, false) 1307 // and systemd was not called 1308 c.Check(s.argses, HasLen, 0) 1309 // umount was not called 1310 c.Check(mockUmountCmd.Calls(), HasLen, 0) 1311 } 1312 1313 func (s *SystemdTestSuite) TestPreseedModeBindmountNotSupported(c *C) { 1314 sysd := NewEmulationMode(dirs.GlobalRootDir) 1315 1316 restore := squashfs.MockNeedsFuse(false) 1317 defer restore() 1318 1319 mockSnapPath := c.MkDir() 1320 1321 _, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "") 1322 c.Assert(err, ErrorMatches, `bind-mounted directory is not supported in emulation mode`) 1323 } 1324 1325 func (s *SystemdTestSuite) TestEnableInEmulationMode(c *C) { 1326 sysd := NewEmulationMode("/path") 1327 c.Assert(sysd.Enable("foo"), IsNil) 1328 1329 sysd = NewEmulationMode("") 1330 c.Assert(sysd.Enable("bar"), IsNil) 1331 c.Check(s.argses, DeepEquals, [][]string{ 1332 {"--root", "/path", "enable", "foo"}, 1333 {"--root", dirs.GlobalRootDir, "enable", "bar"}}) 1334 } 1335 1336 func (s *SystemdTestSuite) TestDisableInEmulationMode(c *C) { 1337 sysd := NewEmulationMode("/path") 1338 c.Assert(sysd.Disable("foo"), IsNil) 1339 1340 c.Check(s.argses, DeepEquals, [][]string{ 1341 {"--root", "/path", "disable", "foo"}}) 1342 } 1343 1344 func (s *SystemdTestSuite) TestMaskInEmulationMode(c *C) { 1345 sysd := NewEmulationMode("/path") 1346 c.Assert(sysd.Mask("foo"), IsNil) 1347 1348 c.Check(s.argses, DeepEquals, [][]string{ 1349 {"--root", "/path", "mask", "foo"}}) 1350 } 1351 1352 func (s *SystemdTestSuite) TestUnmaskInEmulationMode(c *C) { 1353 sysd := NewEmulationMode("/path") 1354 c.Assert(sysd.Unmask("foo"), IsNil) 1355 1356 c.Check(s.argses, DeepEquals, [][]string{ 1357 {"--root", "/path", "unmask", "foo"}}) 1358 } 1359 1360 func (s *SystemdTestSuite) TestMountHappy(c *C) { 1361 sysd := New(SystemMode, nil) 1362 1363 cmd := testutil.MockCommand(c, "systemd-mount", "") 1364 defer cmd.Restore() 1365 1366 c.Assert(sysd.Mount("foo", "bar"), IsNil) 1367 c.Check(cmd.Calls(), DeepEquals, [][]string{ 1368 {"systemd-mount", "foo", "bar"}, 1369 }) 1370 cmd.ForgetCalls() 1371 c.Assert(sysd.Mount("foo", "bar", "-o", "bind"), IsNil) 1372 c.Check(cmd.Calls(), DeepEquals, [][]string{ 1373 {"systemd-mount", "-o", "bind", "foo", "bar"}, 1374 }) 1375 } 1376 1377 func (s *SystemdTestSuite) TestMountErr(c *C) { 1378 sysd := New(SystemMode, nil) 1379 1380 cmd := testutil.MockCommand(c, "systemd-mount", `echo "failed"; exit 111`) 1381 defer cmd.Restore() 1382 1383 err := sysd.Mount("foo", "bar") 1384 c.Assert(err, ErrorMatches, "failed") 1385 c.Check(cmd.Calls(), DeepEquals, [][]string{ 1386 {"systemd-mount", "foo", "bar"}, 1387 }) 1388 } 1389 1390 func (s *SystemdTestSuite) TestUmountHappy(c *C) { 1391 sysd := New(SystemMode, nil) 1392 1393 cmd := testutil.MockCommand(c, "systemd-mount", "") 1394 defer cmd.Restore() 1395 1396 c.Assert(sysd.Umount("bar"), IsNil) 1397 c.Check(cmd.Calls(), DeepEquals, [][]string{ 1398 {"systemd-mount", "--umount", "bar"}, 1399 }) 1400 } 1401 1402 func (s *SystemdTestSuite) TestUmountErr(c *C) { 1403 sysd := New(SystemMode, nil) 1404 1405 cmd := testutil.MockCommand(c, "systemd-mount", `echo "failed"; exit 111`) 1406 defer cmd.Restore() 1407 1408 err := sysd.Umount("bar") 1409 c.Assert(err, ErrorMatches, "failed") 1410 c.Check(cmd.Calls(), DeepEquals, [][]string{ 1411 {"systemd-mount", "--umount", "bar"}, 1412 }) 1413 } 1414 1415 func (s *SystemdTestSuite) TestCurrentUsageFamilyReallyInvalid(c *C) { 1416 s.outs = [][]byte{ 1417 []byte(`gahstringsarehard`), 1418 []byte(`gahstringsarehard`), 1419 } 1420 sysd := New(SystemMode, s.rep) 1421 _, err := sysd.CurrentMemoryUsage("bar.service") 1422 c.Assert(err, ErrorMatches, `invalid property format from systemd for MemoryCurrent \(got gahstringsarehard\)`) 1423 _, err = sysd.CurrentTasksCount("bar.service") 1424 c.Assert(err, ErrorMatches, `invalid property format from systemd for TasksCurrent \(got gahstringsarehard\)`) 1425 c.Check(s.argses, DeepEquals, [][]string{ 1426 {"show", "--property", "MemoryCurrent", "bar.service"}, 1427 {"show", "--property", "TasksCurrent", "bar.service"}, 1428 }) 1429 } 1430 1431 func (s *SystemdTestSuite) TestCurrentUsageFamilyInactive(c *C) { 1432 s.outs = [][]byte{ 1433 []byte(`MemoryCurrent=[not set]`), 1434 []byte(`TasksCurrent=[not set]`), 1435 } 1436 sysd := New(SystemMode, s.rep) 1437 _, err := sysd.CurrentMemoryUsage("bar.service") 1438 c.Assert(err, ErrorMatches, "memory usage unavailable") 1439 _, err = sysd.CurrentTasksCount("bar.service") 1440 c.Assert(err, ErrorMatches, "tasks count unavailable") 1441 c.Check(s.argses, DeepEquals, [][]string{ 1442 {"show", "--property", "MemoryCurrent", "bar.service"}, 1443 {"show", "--property", "TasksCurrent", "bar.service"}, 1444 }) 1445 } 1446 1447 func (s *SystemdTestSuite) TestCurrentUsageFamilyInvalid(c *C) { 1448 s.outs = [][]byte{ 1449 []byte(`MemoryCurrent=blahhhhhhhhhhhhhh`), 1450 []byte(`TasksCurrent=blahhhhhhhhhhhhhh`), 1451 } 1452 sysd := New(SystemMode, s.rep) 1453 _, err := sysd.CurrentMemoryUsage("bar.service") 1454 c.Assert(err, ErrorMatches, `invalid property value from systemd for MemoryCurrent: cannot parse "blahhhhhhhhhhhhhh" as an integer`) 1455 _, err = sysd.CurrentTasksCount("bar.service") 1456 c.Assert(err, ErrorMatches, `invalid property value from systemd for TasksCurrent: cannot parse "blahhhhhhhhhhhhhh" as an integer`) 1457 c.Check(s.argses, DeepEquals, [][]string{ 1458 {"show", "--property", "MemoryCurrent", "bar.service"}, 1459 {"show", "--property", "TasksCurrent", "bar.service"}, 1460 }) 1461 } 1462 1463 func (s *SystemdTestSuite) TestCurrentUsageFamilyHappy(c *C) { 1464 s.outs = [][]byte{ 1465 []byte(`MemoryCurrent=1024`), 1466 []byte(`MemoryCurrent=18446744073709551615`), // special value from systemd bug 1467 []byte(`TasksCurrent=10`), 1468 } 1469 sysd := New(SystemMode, s.rep) 1470 memUsage, err := sysd.CurrentMemoryUsage("bar.service") 1471 c.Assert(err, IsNil) 1472 c.Assert(memUsage, Equals, quantity.SizeKiB) 1473 memUsage, err = sysd.CurrentMemoryUsage("bar.service") 1474 c.Assert(err, IsNil) 1475 const sixteenExb = quantity.Size(1<<64 - 1) 1476 c.Assert(memUsage, Equals, sixteenExb) 1477 tasksUsage, err := sysd.CurrentTasksCount("bar.service") 1478 c.Assert(tasksUsage, Equals, uint64(10)) 1479 c.Assert(err, IsNil) 1480 c.Check(s.argses, DeepEquals, [][]string{ 1481 {"show", "--property", "MemoryCurrent", "bar.service"}, 1482 {"show", "--property", "MemoryCurrent", "bar.service"}, 1483 {"show", "--property", "TasksCurrent", "bar.service"}, 1484 }) 1485 } 1486 1487 func (s *SystemdTestSuite) TestInactiveEnterTimestampZero(c *C) { 1488 s.outs = [][]byte{ 1489 []byte(`InactiveEnterTimestamp=`), 1490 } 1491 sysd := New(SystemMode, s.rep) 1492 stamp, err := sysd.InactiveEnterTimestamp("bar.service") 1493 c.Assert(err, IsNil) 1494 c.Check(s.argses, DeepEquals, [][]string{ 1495 {"show", "--property", "InactiveEnterTimestamp", "bar.service"}, 1496 }) 1497 c.Check(stamp.IsZero(), Equals, true) 1498 } 1499 1500 func (s *SystemdTestSuite) TestInactiveEnterTimestampValidWhitespace(c *C) { 1501 s.outs = [][]byte{ 1502 []byte(`InactiveEnterTimestamp=Fri 2021-04-16 15:32:21 UTC 1503 `), 1504 } 1505 1506 stamp, err := New(SystemMode, s.rep).InactiveEnterTimestamp("bar.service") 1507 c.Assert(err, IsNil) 1508 c.Check(s.argses, DeepEquals, [][]string{ 1509 {"show", "--property", "InactiveEnterTimestamp", "bar.service"}, 1510 }) 1511 c.Check(stamp.Equal(time.Date(2021, time.April, 16, 15, 32, 21, 0, time.UTC)), Equals, true) 1512 } 1513 1514 func (s *SystemdTestSuite) TestInactiveEnterTimestampValid(c *C) { 1515 s.outs = [][]byte{ 1516 []byte(`InactiveEnterTimestamp=Fri 2021-04-16 15:32:21 UTC`), 1517 } 1518 1519 stamp, err := New(SystemMode, s.rep).InactiveEnterTimestamp("bar.service") 1520 c.Assert(err, IsNil) 1521 c.Check(s.argses, DeepEquals, [][]string{ 1522 {"show", "--property", "InactiveEnterTimestamp", "bar.service"}, 1523 }) 1524 c.Check(stamp.Equal(time.Date(2021, time.April, 16, 15, 32, 21, 0, time.UTC)), Equals, true) 1525 } 1526 1527 func (s *SystemdTestSuite) TestInactiveEnterTimestampFailure(c *C) { 1528 s.outs = [][]byte{ 1529 []byte(`mocked failure`), 1530 } 1531 s.errors = []error{ 1532 fmt.Errorf("mocked failure"), 1533 } 1534 stamp, err := New(SystemMode, s.rep).InactiveEnterTimestamp("bar.service") 1535 c.Assert(err, ErrorMatches, "mocked failure") 1536 c.Check(stamp.IsZero(), Equals, true) 1537 } 1538 1539 func (s *SystemdTestSuite) TestInactiveEnterTimestampMalformed(c *C) { 1540 s.outs = [][]byte{ 1541 []byte(`InactiveEnterTimestamp`), 1542 []byte(``), 1543 []byte(`some random garbage 1544 with newlines`), 1545 } 1546 sysd := New(SystemMode, s.rep) 1547 for i := 0; i < len(s.outs); i++ { 1548 s.argses = nil 1549 stamp, err := sysd.InactiveEnterTimestamp("bar.service") 1550 c.Assert(err.Error(), testutil.Contains, `invalid property format from systemd for InactiveEnterTimestamp (got`) 1551 c.Check(s.argses, DeepEquals, [][]string{ 1552 {"show", "--property", "InactiveEnterTimestamp", "bar.service"}, 1553 }) 1554 c.Check(stamp.IsZero(), Equals, true) 1555 } 1556 } 1557 1558 func (s *SystemdTestSuite) TestInactiveEnterTimestampMalformedMore(c *C) { 1559 s.outs = [][]byte{ 1560 []byte(`InactiveEnterTimestamp=0`), // 0 is valid for InactiveEnterTimestampMonotonic 1561 } 1562 sysd := New(SystemMode, s.rep) 1563 1564 stamp, err := sysd.InactiveEnterTimestamp("bar.service") 1565 1566 c.Assert(err, ErrorMatches, `internal error: systemctl time output \(0\) is malformed`) 1567 c.Check(s.argses, DeepEquals, [][]string{ 1568 {"show", "--property", "InactiveEnterTimestamp", "bar.service"}, 1569 }) 1570 c.Check(stamp.IsZero(), Equals, true) 1571 } 1572 1573 type systemdErrorSuite struct{} 1574 1575 var _ = Suite(&systemdErrorSuite{}) 1576 1577 func (s *systemdErrorSuite) TestErrorStringNormalError(c *C) { 1578 systemctl := testutil.MockCommand(c, "systemctl", `echo "I fail"; exit 11`) 1579 defer systemctl.Restore() 1580 1581 _, err := Version() 1582 c.Check(err, ErrorMatches, `systemctl command \[--version\] failed with exit status 11: I fail\n`) 1583 } 1584 1585 func (s *systemdErrorSuite) TestErrorStringNoOutput(c *C) { 1586 systemctl := testutil.MockCommand(c, "systemctl", `exit 22`) 1587 defer systemctl.Restore() 1588 1589 _, err := Version() 1590 c.Check(err, ErrorMatches, `systemctl command \[--version\] failed with exit status 22`) 1591 } 1592 1593 func (s *systemdErrorSuite) TestErrorStringNoSystemctl(c *C) { 1594 oldPath := os.Getenv("PATH") 1595 os.Setenv("PATH", "/xxx") 1596 defer func() { os.Setenv("PATH", oldPath) }() 1597 1598 _, err := Version() 1599 c.Check(err, ErrorMatches, `systemctl command \[--version\] failed with: exec: "systemctl": executable file not found in \$PATH`) 1600 }