1// Copyright 2015, 2018 CoreOS, Inc. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package dbus 16 17import ( 18 "fmt" 19 "os" 20 "os/exec" 21 "path" 22 "path/filepath" 23 "reflect" 24 "syscall" 25 "testing" 26 "time" 27 28 "github.com/godbus/dbus/v5" 29) 30 31type TrUnitProp struct { 32 name string 33 props []Property 34} 35 36func setupConn(t *testing.T) *Conn { 37 conn, err := New() 38 if err != nil { 39 t.Fatal(err) 40 } 41 42 return conn 43} 44 45func findFixture(target string, t *testing.T) string { 46 abs, err := filepath.Abs("../fixtures/" + target) 47 if err != nil { 48 t.Fatal(err) 49 } 50 return abs 51} 52 53func setupUnit(target string, conn *Conn, t *testing.T) { 54 // Blindly stop the unit in case it is running 55 conn.StopUnit(target, "replace", nil) 56 57 // Blindly remove the symlink in case it exists 58 targetRun := filepath.Join("/run/systemd/system/", target) 59 os.Remove(targetRun) 60} 61 62func linkUnit(target string, conn *Conn, t *testing.T) { 63 abs := findFixture(target, t) 64 fixture := []string{abs} 65 66 changes, err := conn.LinkUnitFiles(fixture, true, true) 67 if err != nil { 68 t.Fatal(err) 69 } 70 71 if len(changes) < 1 { 72 t.Fatalf("Expected one change, got %v", changes) 73 } 74 75 runPath := filepath.Join("/run/systemd/system/", target) 76 if changes[0].Filename != runPath { 77 t.Fatal("Unexpected target filename") 78 } 79} 80 81func getUnitStatus(units []UnitStatus, name string) *UnitStatus { 82 for _, u := range units { 83 if u.Name == name { 84 return &u 85 } 86 } 87 return nil 88} 89 90func getUnitStatusSingle(conn *Conn, name string) *UnitStatus { 91 units, err := conn.ListUnits() 92 if err != nil { 93 return nil 94 } 95 return getUnitStatus(units, name) 96} 97 98func getUnitFile(units []UnitFile, name string) *UnitFile { 99 for _, u := range units { 100 if path.Base(u.Path) == name { 101 return &u 102 } 103 } 104 return nil 105} 106 107func runStartTrUnit(t *testing.T, conn *Conn, trTarget TrUnitProp) error { 108 reschan := make(chan string) 109 _, err := conn.StartTransientUnit(trTarget.name, "replace", trTarget.props, reschan) 110 if err != nil { 111 return err 112 } 113 114 job := <-reschan 115 if job != "done" { 116 return fmt.Errorf("Job is not done: %s", job) 117 } 118 119 return nil 120} 121 122func runStopUnit(t *testing.T, conn *Conn, trTarget TrUnitProp) error { 123 reschan := make(chan string) 124 _, err := conn.StopUnit(trTarget.name, "replace", reschan) 125 if err != nil { 126 return err 127 } 128 129 // wait for StopUnit job to complete 130 <-reschan 131 132 return nil 133} 134 135// Ensure that basic unit starting and stopping works. 136func TestStartStopUnit(t *testing.T) { 137 target := "start-stop.service" 138 conn := setupConn(t) 139 defer conn.Close() 140 141 setupUnit(target, conn, t) 142 linkUnit(target, conn, t) 143 144 // 2. Start the unit 145 reschan := make(chan string) 146 _, err := conn.StartUnit(target, "replace", reschan) 147 if err != nil { 148 t.Fatal(err) 149 } 150 151 job := <-reschan 152 if job != "done" { 153 t.Fatal("Job is not done:", job) 154 } 155 156 units, err := conn.ListUnits() 157 if err != nil { 158 t.Fatal(err) 159 } 160 161 unit := getUnitStatus(units, target) 162 163 if unit == nil { 164 t.Fatalf("Test unit not found in list") 165 } else if unit.ActiveState != "active" { 166 t.Fatalf("Test unit not active") 167 } 168 169 // 3. Stop the unit 170 _, err = conn.StopUnit(target, "replace", reschan) 171 if err != nil { 172 t.Fatal(err) 173 } 174 175 // wait for StopUnit job to complete 176 <-reschan 177 178 units, err = conn.ListUnits() 179 if err != nil { 180 t.Fatal(err) 181 } 182 183 unit = getUnitStatus(units, target) 184 185 if unit != nil { 186 t.Fatalf("Test unit found in list, should be stopped") 187 } 188} 189 190// Ensure that basic unit restarting works. 191func TestRestartUnit(t *testing.T) { 192 target := "start-stop.service" 193 conn := setupConn(t) 194 defer conn.Close() 195 196 setupUnit(target, conn, t) 197 linkUnit(target, conn, t) 198 199 // Start the unit 200 reschan := make(chan string) 201 _, err := conn.StartUnit(target, "replace", reschan) 202 if err != nil { 203 t.Fatal(err) 204 } 205 206 job := <-reschan 207 if job != "done" { 208 t.Fatal("Job is not done:", job) 209 } 210 211 units, err := conn.ListUnits() 212 if err != nil { 213 t.Fatal(err) 214 } 215 216 unit := getUnitStatus(units, target) 217 if unit == nil { 218 t.Fatalf("Test unit not found in list") 219 } else if unit.ActiveState != "active" { 220 t.Fatalf("Test unit not active") 221 } 222 223 // Restart the unit 224 reschan = make(chan string) 225 _, err = conn.RestartUnit(target, "replace", reschan) 226 if err != nil { 227 t.Fatal(err) 228 } 229 230 job = <-reschan 231 if job != "done" { 232 t.Fatal("Job is not done:", job) 233 } 234 235 // Stop the unit 236 _, err = conn.StopUnit(target, "replace", reschan) 237 if err != nil { 238 t.Fatal(err) 239 } 240 241 // wait for StopUnit job to complete 242 <-reschan 243 244 units, err = conn.ListUnits() 245 if err != nil { 246 t.Fatal(err) 247 } 248 249 unit = getUnitStatus(units, target) 250 if unit != nil { 251 t.Fatalf("Test unit found in list, should be stopped") 252 } 253 254 // Try to restart the unit. 255 // It should still succeed, even if the unit is inactive. 256 reschan = make(chan string) 257 _, err = conn.TryRestartUnit(target, "replace", reschan) 258 if err != nil { 259 t.Fatal(err) 260 } 261 262 // wait for StopUnit job to complete 263 <-reschan 264 265 units, err = conn.ListUnits() 266 if err != nil { 267 t.Fatal(err) 268 } 269 270 unit = getUnitStatus(units, target) 271 if unit != nil { 272 t.Fatalf("Test unit found in list, should be stopped") 273 } 274} 275 276// Ensure that basic unit reloading works. 277func TestReloadUnit(t *testing.T) { 278 target := "reload.service" 279 conn := setupConn(t) 280 defer conn.Close() 281 282 err := conn.Subscribe() 283 if err != nil { 284 t.Fatal(err) 285 } 286 287 subSet := conn.NewSubscriptionSet() 288 evChan, errChan := subSet.Subscribe() 289 290 subSet.Add(target) 291 292 setupUnit(target, conn, t) 293 linkUnit(target, conn, t) 294 295 // Start the unit 296 reschan := make(chan string) 297 _, err = conn.StartUnit(target, "replace", reschan) 298 if err != nil { 299 t.Fatal(err) 300 } 301 302 job := <-reschan 303 if job != "done" { 304 t.Fatal("Job is not done:", job) 305 } 306 307 units, err := conn.ListUnits() 308 if err != nil { 309 t.Fatal(err) 310 } 311 312 unit := getUnitStatus(units, target) 313 if unit == nil { 314 t.Fatalf("Test unit not found in list") 315 } else if unit.ActiveState != "active" { 316 t.Fatalf("Test unit not active") 317 } 318 319 // Reload the unit 320 reschan = make(chan string) 321 322 _, err = conn.ReloadUnit(target, "replace", reschan) 323 if err != nil { 324 t.Fatal(err) 325 } 326 327 job = <-reschan 328 if job != "done" { 329 t.Fatal("Job is not done:", job) 330 } 331 332 timeout := make(chan bool, 1) 333 go func() { 334 time.Sleep(3 * time.Second) 335 close(timeout) 336 }() 337 338 // Wait for the event, expecting the target UnitStatus meets all of the 339 // following conditions: 340 // * target is non-nil 341 // * target's ActiveState is active. 342waitevent: 343 for { 344 select { 345 case changes := <-evChan: 346 tch, ok := changes[target] 347 if !ok { 348 continue waitevent 349 } 350 if tch != nil && tch.Name == target && tch.ActiveState == "active" { 351 break waitevent 352 } 353 case err = <-errChan: 354 t.Fatal(err) 355 case <-timeout: 356 t.Fatal("Reached timeout") 357 } 358 } 359} 360 361// Ensure that basic unit reload-or-restarting works. 362func TestReloadOrRestartUnit(t *testing.T) { 363 target := "reload.service" 364 conn := setupConn(t) 365 defer conn.Close() 366 367 setupUnit(target, conn, t) 368 linkUnit(target, conn, t) 369 370 // Start the unit 371 reschan := make(chan string) 372 _, err := conn.StartUnit(target, "replace", reschan) 373 if err != nil { 374 t.Fatal(err) 375 } 376 377 job := <-reschan 378 if job != "done" { 379 t.Fatal("Job is not done:", job) 380 } 381 382 units, err := conn.ListUnits() 383 if err != nil { 384 t.Fatal(err) 385 } 386 387 unit := getUnitStatus(units, target) 388 if unit == nil { 389 t.Fatalf("Test unit not found in list") 390 } else if unit.ActiveState != "active" { 391 t.Fatalf("Test unit not active") 392 } 393 394 // Reload or restart the unit 395 reschan = make(chan string) 396 _, err = conn.ReloadOrRestartUnit(target, "replace", reschan) 397 if err != nil { 398 t.Fatal(err) 399 } 400 401 job = <-reschan 402 if job != "done" { 403 t.Fatal("Job is not done:", job) 404 } 405 406 // Stop the unit 407 _, err = conn.StopUnit(target, "replace", reschan) 408 if err != nil { 409 t.Fatal(err) 410 } 411 412 // wait for StopUnit job to complete 413 <-reschan 414 415 units, err = conn.ListUnits() 416 if err != nil { 417 t.Fatal(err) 418 } 419 420 unit = getUnitStatus(units, target) 421 if unit != nil && unit.ActiveState == "active" { 422 t.Fatalf("Test unit still active, should be inactive.") 423 } 424 425 // Reload or try to restart the unit 426 // It should still succeed, even if the unit is inactive. 427 reschan = make(chan string) 428 _, err = conn.ReloadOrTryRestartUnit(target, "replace", reschan) 429 if err != nil { 430 t.Fatal(err) 431 } 432 433 job = <-reschan 434 if job != "done" { 435 t.Fatal("Job is not done:", job) 436 } 437} 438 439// Ensure that ListUnitsByNames works. 440func TestListUnitsByNames(t *testing.T) { 441 target1 := "systemd-journald.service" 442 target2 := "unexisting.service" 443 444 conn := setupConn(t) 445 defer conn.Close() 446 447 units, err := conn.ListUnitsByNames([]string{target1, target2}) 448 449 if err != nil { 450 t.Skip(err) 451 } 452 453 unit := getUnitStatus(units, target1) 454 455 if unit == nil { 456 t.Fatalf("%s unit not found in list", target1) 457 } else if unit.ActiveState != "active" { 458 t.Fatalf("%s unit should be active but it is %s", target1, unit.ActiveState) 459 } 460 461 unit = getUnitStatus(units, target2) 462 463 if unit == nil { 464 t.Fatalf("Unexisting test unit not found in list") 465 } else if unit.ActiveState != "inactive" { 466 t.Fatalf("Test unit should be inactive") 467 } 468} 469 470// Ensure that ListUnitsByPatterns works. 471func TestListUnitsByPatterns(t *testing.T) { 472 target1 := "systemd-journald.service" 473 target2 := "unexisting.service" 474 475 conn := setupConn(t) 476 defer conn.Close() 477 478 units, err := conn.ListUnitsByPatterns([]string{}, []string{"systemd-journald*", target2}) 479 480 if err != nil { 481 t.Skip(err) 482 } 483 484 unit := getUnitStatus(units, target1) 485 486 if unit == nil { 487 t.Fatalf("%s unit not found in list", target1) 488 } else if unit.ActiveState != "active" { 489 t.Fatalf("Test unit should be active") 490 } 491 492 unit = getUnitStatus(units, target2) 493 494 if unit != nil { 495 t.Fatalf("Unexisting test unit found in list") 496 } 497} 498 499// Ensure that ListUnitsFiltered works. 500func TestListUnitsFiltered(t *testing.T) { 501 target := "systemd-journald.service" 502 503 conn := setupConn(t) 504 defer conn.Close() 505 506 units, err := conn.ListUnitsFiltered([]string{"active"}) 507 508 if err != nil { 509 t.Fatal(err) 510 } 511 512 unit := getUnitStatus(units, target) 513 514 if unit == nil { 515 t.Fatalf("%s unit not found in list", target) 516 } else if unit.ActiveState != "active" { 517 t.Fatalf("Test unit should be active") 518 } 519 520 units, err = conn.ListUnitsFiltered([]string{"inactive"}) 521 522 if err != nil { 523 t.Fatal(err) 524 } 525 526 unit = getUnitStatus(units, target) 527 528 if unit != nil { 529 t.Fatalf("Inactive unit should not be found in list") 530 } 531} 532 533// Ensure that ListUnitFilesByPatterns works. 534func TestListUnitFilesByPatterns(t *testing.T) { 535 target1 := "systemd-journald.service" 536 target2 := "exit.target" 537 538 conn := setupConn(t) 539 defer conn.Close() 540 541 units, err := conn.ListUnitFilesByPatterns([]string{"static"}, []string{"systemd-journald*", target2}) 542 543 if err != nil { 544 t.Skip(err) 545 } 546 547 unit := getUnitFile(units, target1) 548 549 if unit == nil { 550 t.Fatalf("%s unit not found in list", target1) 551 } else if unit.Type != "static" { 552 t.Fatalf("Test unit file should be static") 553 } 554 555 units, err = conn.ListUnitFilesByPatterns([]string{"disabled"}, []string{"systemd-journald*", target2}) 556 557 if err != nil { 558 t.Fatal(err) 559 } 560 561 unit = getUnitFile(units, target2) 562 563 if unit == nil { 564 t.Fatalf("%s unit not found in list", target2) 565 } else if unit.Type != "disabled" { 566 t.Fatalf("%s unit file should be disabled", target2) 567 } 568} 569 570func TestListUnitFiles(t *testing.T) { 571 target1 := "systemd-journald.service" 572 target2 := "exit.target" 573 574 conn := setupConn(t) 575 defer conn.Close() 576 577 units, err := conn.ListUnitFiles() 578 579 if err != nil { 580 t.Fatal(err) 581 } 582 583 unit := getUnitFile(units, target1) 584 585 if unit == nil { 586 t.Fatalf("%s unit not found in list", target1) 587 } else if unit.Type != "static" { 588 t.Fatalf("Test unit file should be static") 589 } 590 591 unit = getUnitFile(units, target2) 592 593 if unit == nil { 594 t.Fatalf("%s unit not found in list", target2) 595 } else if unit.Type != "disabled" { 596 t.Fatalf("%s unit file should be disabled", target2) 597 } 598} 599 600// Enables a unit and then immediately tears it down 601func TestEnableDisableUnit(t *testing.T) { 602 target := "enable-disable.service" 603 conn := setupConn(t) 604 defer conn.Close() 605 606 setupUnit(target, conn, t) 607 abs := findFixture(target, t) 608 runPath := filepath.Join("/run/systemd/system/", target) 609 610 // 1. Enable the unit 611 install, changes, err := conn.EnableUnitFiles([]string{abs}, true, true) 612 if err != nil { 613 t.Fatal(err) 614 } 615 616 if install { 617 t.Log("Install was true") 618 } 619 620 if len(changes) < 1 { 621 t.Fatalf("Expected one change, got %v", changes) 622 } 623 624 if changes[0].Filename != runPath { 625 t.Fatal("Unexpected target filename") 626 } 627 628 // 2. Disable the unit 629 dChanges, err := conn.DisableUnitFiles([]string{target}, true) 630 if err != nil { 631 t.Fatal(err) 632 } 633 634 if len(dChanges) != 1 { 635 t.Fatalf("Changes should include the path, %v", dChanges) 636 } 637 if dChanges[0].Filename != runPath { 638 t.Fatalf("Change should include correct filename, %+v", dChanges[0]) 639 } 640 if dChanges[0].Destination != "" { 641 t.Fatalf("Change destination should be empty, %+v", dChanges[0]) 642 } 643} 644 645// TestSystemState tests if system state is one of the valid states 646func TestSystemState(t *testing.T) { 647 conn := setupConn(t) 648 defer conn.Close() 649 650 prop, err := conn.SystemState() 651 if err != nil { 652 t.Fatal(err) 653 } 654 655 if prop.Name != "SystemState" { 656 t.Fatalf("unexpected property name: %v", prop.Name) 657 } 658 659 val := prop.Value.Value().(string) 660 661 switch val { 662 case "initializing": 663 case "starting": 664 case "running": 665 case "degraded": 666 case "maintenance": 667 case "stopping": 668 case "offline": 669 case "unknown": 670 // valid systemd state - do nothing 671 672 default: 673 t.Fatalf("unexpected property value: %v", val) 674 } 675} 676 677// TestGetUnitProperties reads the `-.mount` which should exist on all systemd 678// systems and ensures that one of its properties is valid. 679func TestGetUnitProperties(t *testing.T) { 680 conn := setupConn(t) 681 defer conn.Close() 682 683 unit := "-.mount" 684 685 info, err := conn.GetUnitProperties(unit) 686 if err != nil { 687 t.Fatal(err) 688 } 689 690 desc, _ := info["Description"].(string) 691 692 prop, err := conn.GetUnitProperty(unit, "Description") 693 if err != nil { 694 t.Fatal(err) 695 } 696 697 if prop.Name != "Description" { 698 t.Fatal("unexpected property name") 699 } 700 701 val := prop.Value.Value().(string) 702 if !reflect.DeepEqual(val, desc) { 703 t.Fatal("unexpected property value") 704 } 705} 706 707// TestGetUnitPropertiesRejectsInvalidName attempts to get the properties for a 708// unit with an invalid name. This test should be run with --test.timeout set, 709// as a fail will manifest as GetUnitProperties hanging indefinitely. 710func TestGetUnitPropertiesRejectsInvalidName(t *testing.T) { 711 conn := setupConn(t) 712 defer conn.Close() 713 714 unit := "//invalid#$^/" 715 716 _, err := conn.GetUnitProperties(unit) 717 if err == nil { 718 t.Fatal("Expected an error, got nil") 719 } 720 721 _, err = conn.GetUnitProperty(unit, "Wants") 722 if err == nil { 723 t.Fatal("Expected an error, got nil") 724 } 725} 726 727// TestGetServiceProperty reads the `systemd-udevd.service` which should exist 728// on all systemd systems and ensures that one of its property is valid. 729func TestGetServiceProperty(t *testing.T) { 730 conn := setupConn(t) 731 defer conn.Close() 732 733 service := "systemd-udevd.service" 734 735 prop, err := conn.GetServiceProperty(service, "Type") 736 if err != nil { 737 t.Fatal(err) 738 } 739 740 if prop.Name != "Type" { 741 t.Fatal("unexpected property name") 742 } 743 744 if _, ok := prop.Value.Value().(string); !ok { 745 t.Fatal("invalid property value") 746 } 747} 748 749// TestSetUnitProperties changes a cgroup setting on the `-.mount` 750// which should exist on all systemd systems and ensures that the 751// property was set. 752func TestSetUnitProperties(t *testing.T) { 753 conn := setupConn(t) 754 defer conn.Close() 755 756 unit := "-.mount" 757 758 if err := conn.SetUnitProperties(unit, true, Property{"CPUShares", dbus.MakeVariant(uint64(1023))}); err != nil { 759 t.Fatal(err) 760 } 761 762 info, err := conn.GetUnitTypeProperties(unit, "Mount") 763 if err != nil { 764 t.Fatal(err) 765 } 766 767 value, _ := info["CPUShares"].(uint64) 768 if value != 1023 { 769 t.Fatal("CPUShares of unit is not 1023:", value) 770 } 771} 772 773// Ensure that oneshot transient unit starting and stopping works. 774func TestStartStopTransientUnitAll(t *testing.T) { 775 testCases := []struct { 776 trTarget TrUnitProp 777 trDep TrUnitProp 778 checkFunc func(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error 779 }{ 780 { 781 trTarget: TrUnitProp{ 782 name: "testing-transient.service", 783 props: []Property{ 784 PropExecStart([]string{"/bin/sleep", "400"}, false), 785 }, 786 }, 787 trDep: TrUnitProp{"", nil}, 788 checkFunc: checkTransientUnit, 789 }, 790 { 791 trTarget: TrUnitProp{ 792 name: "testing-transient-oneshot.service", 793 props: []Property{ 794 PropExecStart([]string{"/bin/true"}, false), 795 PropType("oneshot"), 796 PropRemainAfterExit(true), 797 }, 798 }, 799 trDep: TrUnitProp{"", nil}, 800 checkFunc: checkTransientUnitOneshot, 801 }, 802 { 803 trTarget: TrUnitProp{ 804 name: "testing-transient-requires.service", 805 props: []Property{ 806 PropExecStart([]string{"/bin/sleep", "400"}, false), 807 PropRequires("testing-transient-requiresdep.service"), 808 }, 809 }, 810 trDep: TrUnitProp{ 811 name: "testing-transient-requiresdep.service", 812 props: []Property{ 813 PropExecStart([]string{"/bin/sleep", "400"}, false), 814 }, 815 }, 816 checkFunc: checkTransientUnitRequires, 817 }, 818 { 819 trTarget: TrUnitProp{ 820 name: "testing-transient-requires-ov.service", 821 props: []Property{ 822 PropExecStart([]string{"/bin/sleep", "400"}, false), 823 PropRequires("testing-transient-requiresdep-ov.service"), 824 }, 825 }, 826 trDep: TrUnitProp{ 827 name: "testing-transient-requiresdep-ov.service", 828 props: []Property{ 829 PropExecStart([]string{"/bin/sleep", "400"}, false), 830 }, 831 }, 832 checkFunc: checkTransientUnitRequiresOv, 833 }, 834 { 835 trTarget: TrUnitProp{ 836 name: "testing-transient-requisite.service", 837 props: []Property{ 838 PropExecStart([]string{"/bin/sleep", "400"}, false), 839 PropRequisite("testing-transient-requisitedep.service"), 840 }, 841 }, 842 trDep: TrUnitProp{ 843 name: "testing-transient-requisitedep.service", 844 props: []Property{ 845 PropExecStart([]string{"/bin/sleep", "400"}, false), 846 }, 847 }, 848 checkFunc: checkTransientUnitRequisite, 849 }, 850 { 851 trTarget: TrUnitProp{ 852 name: "testing-transient-requisite-ov.service", 853 props: []Property{ 854 PropExecStart([]string{"/bin/sleep", "400"}, false), 855 PropRequisiteOverridable("testing-transient-requisitedep-ov.service"), 856 }, 857 }, 858 trDep: TrUnitProp{ 859 name: "testing-transient-requisitedep-ov.service", 860 props: []Property{ 861 PropExecStart([]string{"/bin/sleep", "400"}, false), 862 }, 863 }, 864 checkFunc: checkTransientUnitRequisiteOv, 865 }, 866 { 867 trTarget: TrUnitProp{ 868 name: "testing-transient-wants.service", 869 props: []Property{ 870 PropExecStart([]string{"/bin/sleep", "400"}, false), 871 PropWants("testing-transient-wantsdep.service"), 872 }, 873 }, 874 trDep: TrUnitProp{ 875 name: "testing-transient-wantsdep.service", 876 props: []Property{ 877 PropExecStart([]string{"/bin/sleep", "400"}, false), 878 }, 879 }, 880 checkFunc: checkTransientUnitWants, 881 }, 882 { 883 trTarget: TrUnitProp{ 884 name: "testing-transient-bindsto.service", 885 props: []Property{ 886 PropExecStart([]string{"/bin/sleep", "400"}, false), 887 PropBindsTo("testing-transient-bindstodep.service"), 888 }, 889 }, 890 trDep: TrUnitProp{ 891 name: "testing-transient-bindstodep.service", 892 props: []Property{ 893 PropExecStart([]string{"/bin/sleep", "400"}, false), 894 }, 895 }, 896 checkFunc: checkTransientUnitBindsTo, 897 }, 898 { 899 trTarget: TrUnitProp{ 900 name: "testing-transient-conflicts.service", 901 props: []Property{ 902 PropExecStart([]string{"/bin/sleep", "400"}, false), 903 PropConflicts("testing-transient-conflictsdep.service"), 904 }, 905 }, 906 trDep: TrUnitProp{ 907 name: "testing-transient-conflictsdep.service", 908 props: []Property{ 909 PropExecStart([]string{"/bin/sleep", "400"}, false), 910 }, 911 }, 912 checkFunc: checkTransientUnitConflicts, 913 }, 914 } 915 916 for i, tt := range testCases { 917 if err := tt.checkFunc(t, tt.trTarget, tt.trDep); err != nil { 918 t.Errorf("case %d: failed test with unit %s. err: %v", i, tt.trTarget.name, err) 919 } 920 } 921} 922 923// Ensure that basic transient unit starting and stopping works. 924func checkTransientUnit(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { 925 conn := setupConn(t) 926 defer conn.Close() 927 928 // Start the unit 929 err := runStartTrUnit(t, conn, trTarget) 930 if err != nil { 931 return err 932 } 933 934 unit := getUnitStatusSingle(conn, trTarget.name) 935 if unit == nil { 936 return fmt.Errorf("Test unit not found in list") 937 } else if unit.ActiveState != "active" { 938 return fmt.Errorf("Test unit not active") 939 } 940 941 // Stop the unit 942 err = runStopUnit(t, conn, trTarget) 943 if err != nil { 944 return err 945 } 946 947 unit = getUnitStatusSingle(conn, trTarget.name) 948 if unit != nil { 949 return fmt.Errorf("Test unit found in list, should be stopped") 950 } 951 952 return nil 953} 954 955func checkTransientUnitOneshot(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { 956 conn := setupConn(t) 957 defer conn.Close() 958 959 // Start the unit 960 err := runStartTrUnit(t, conn, trTarget) 961 if err != nil { 962 return err 963 } 964 965 unit := getUnitStatusSingle(conn, trTarget.name) 966 if unit == nil { 967 return fmt.Errorf("Test unit not found in list") 968 } else if unit.ActiveState != "active" { 969 return fmt.Errorf("Test unit not active") 970 } 971 972 // Stop the unit 973 err = runStopUnit(t, conn, trTarget) 974 if err != nil { 975 return err 976 } 977 978 unit = getUnitStatusSingle(conn, trTarget.name) 979 if unit != nil { 980 return fmt.Errorf("Test unit found in list, should be stopped") 981 } 982 983 return nil 984} 985 986// Ensure that transient unit with Requires starting and stopping works. 987func checkTransientUnitRequires(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { 988 conn := setupConn(t) 989 defer conn.Close() 990 991 // Start the dependent unit 992 err := runStartTrUnit(t, conn, trDep) 993 if err != nil { 994 return err 995 } 996 997 // Start the target unit 998 err = runStartTrUnit(t, conn, trTarget) 999 if err != nil { 1000 return err 1001 } 1002 1003 unit := getUnitStatusSingle(conn, trTarget.name) 1004 if unit == nil { 1005 return fmt.Errorf("Test unit not found in list") 1006 } else if unit.ActiveState != "active" { 1007 return fmt.Errorf("Test unit not active") 1008 } 1009 1010 // Stop the unit 1011 err = runStopUnit(t, conn, trTarget) 1012 if err != nil { 1013 return err 1014 } 1015 1016 unit = getUnitStatusSingle(conn, trTarget.name) 1017 if unit != nil { 1018 return fmt.Errorf("Test unit found in list, should be stopped") 1019 } 1020 1021 // Stop the dependent unit 1022 err = runStopUnit(t, conn, trDep) 1023 if err != nil { 1024 return err 1025 } 1026 1027 unit = getUnitStatusSingle(conn, trDep.name) 1028 if unit != nil { 1029 return fmt.Errorf("Test unit found in list, should be stopped") 1030 } 1031 1032 return nil 1033} 1034 1035// Ensure that transient unit with RequiresOverridable starting and stopping works. 1036func checkTransientUnitRequiresOv(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { 1037 conn := setupConn(t) 1038 defer conn.Close() 1039 1040 // Start the dependent unit 1041 err := runStartTrUnit(t, conn, trDep) 1042 if err != nil { 1043 return err 1044 } 1045 1046 // Start the target unit 1047 err = runStartTrUnit(t, conn, trTarget) 1048 if err != nil { 1049 return err 1050 } 1051 1052 unit := getUnitStatusSingle(conn, trTarget.name) 1053 if unit == nil { 1054 return fmt.Errorf("Test unit not found in list") 1055 } else if unit.ActiveState != "active" { 1056 return fmt.Errorf("Test unit not active") 1057 } 1058 1059 // Stop the unit 1060 err = runStopUnit(t, conn, trTarget) 1061 if err != nil { 1062 return err 1063 } 1064 1065 unit = getUnitStatusSingle(conn, trTarget.name) 1066 if unit != nil { 1067 return fmt.Errorf("Test unit found in list, should be stopped") 1068 } 1069 1070 // Stop the dependent unit 1071 err = runStopUnit(t, conn, trDep) 1072 if err != nil { 1073 return err 1074 } 1075 1076 unit = getUnitStatusSingle(conn, trDep.name) 1077 if unit != nil { 1078 return fmt.Errorf("Test unit found in list, should be stopped") 1079 } 1080 1081 return nil 1082} 1083 1084// Ensure that transient unit with Requisite starting and stopping works. 1085// It's expected for target unit to fail, as its child is not started at all. 1086func checkTransientUnitRequisite(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { 1087 conn := setupConn(t) 1088 defer conn.Close() 1089 1090 // Start the target unit 1091 err := runStartTrUnit(t, conn, trTarget) 1092 if err == nil { 1093 return fmt.Errorf("Unit %s is expected to fail, but succeeded", trTarget.name) 1094 } 1095 1096 unit := getUnitStatusSingle(conn, trTarget.name) 1097 if unit != nil && unit.ActiveState == "active" { 1098 return fmt.Errorf("Test unit %s is active, should be inactive", trTarget.name) 1099 } 1100 1101 return nil 1102} 1103 1104// Ensure that transient unit with RequisiteOverridable starting and stopping works. 1105// It's expected for target unit to fail, as its child is not started at all. 1106func checkTransientUnitRequisiteOv(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { 1107 conn := setupConn(t) 1108 defer conn.Close() 1109 1110 // Start the target unit 1111 err := runStartTrUnit(t, conn, trTarget) 1112 if err == nil { 1113 return fmt.Errorf("Unit %s is expected to fail, but succeeded", trTarget.name) 1114 } 1115 1116 unit := getUnitStatusSingle(conn, trTarget.name) 1117 if unit != nil && unit.ActiveState == "active" { 1118 return fmt.Errorf("Test unit %s is active, should be inactive", trTarget.name) 1119 } 1120 1121 return nil 1122} 1123 1124// Ensure that transient unit with Wants starting and stopping works. 1125// It's expected for target to successfully start, even when its child is not started. 1126func checkTransientUnitWants(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { 1127 conn := setupConn(t) 1128 defer conn.Close() 1129 1130 // Start the target unit 1131 err := runStartTrUnit(t, conn, trTarget) 1132 if err != nil { 1133 return err 1134 } 1135 1136 unit := getUnitStatusSingle(conn, trTarget.name) 1137 if unit == nil { 1138 return fmt.Errorf("Test unit not found in list") 1139 } else if unit.ActiveState != "active" { 1140 return fmt.Errorf("Test unit not active") 1141 } 1142 1143 // Stop the unit 1144 err = runStopUnit(t, conn, trTarget) 1145 if err != nil { 1146 return err 1147 } 1148 1149 unit = getUnitStatusSingle(conn, trTarget.name) 1150 if unit != nil { 1151 return fmt.Errorf("Test unit found in list, should be stopped") 1152 } 1153 1154 return nil 1155} 1156 1157// Ensure that transient unit with BindsTo starting and stopping works. 1158// Stopping its child should result in stopping target unit. 1159func checkTransientUnitBindsTo(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { 1160 conn := setupConn(t) 1161 defer conn.Close() 1162 1163 // Start the dependent unit 1164 err := runStartTrUnit(t, conn, trDep) 1165 if err != nil { 1166 return err 1167 } 1168 1169 // Start the target unit 1170 err = runStartTrUnit(t, conn, trTarget) 1171 if err != nil { 1172 return err 1173 } 1174 1175 unit := getUnitStatusSingle(conn, trTarget.name) 1176 if unit == nil { 1177 t.Fatalf("Test unit not found in list") 1178 } else if unit.ActiveState != "active" { 1179 t.Fatalf("Test unit not active") 1180 } 1181 1182 // Stop the dependent unit 1183 err = runStopUnit(t, conn, trDep) 1184 if err != nil { 1185 return err 1186 } 1187 1188 unit = getUnitStatusSingle(conn, trDep.name) 1189 if unit != nil { 1190 t.Fatalf("Test unit found in list, should be stopped") 1191 } 1192 1193 // Then the target unit should be gone 1194 unit = getUnitStatusSingle(conn, trTarget.name) 1195 if unit != nil { 1196 t.Fatalf("Test unit found in list, should be stopped") 1197 } 1198 1199 return nil 1200} 1201 1202// Ensure that transient unit with Conflicts starting and stopping works. 1203func checkTransientUnitConflicts(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { 1204 conn := setupConn(t) 1205 defer conn.Close() 1206 1207 // Start the dependent unit 1208 err := runStartTrUnit(t, conn, trDep) 1209 if err != nil { 1210 return err 1211 } 1212 1213 // Start the target unit 1214 err = runStartTrUnit(t, conn, trTarget) 1215 if err != nil { 1216 return err 1217 } 1218 1219 isTargetActive := false 1220 unit := getUnitStatusSingle(conn, trTarget.name) 1221 if unit != nil && unit.ActiveState == "active" { 1222 isTargetActive = true 1223 } 1224 1225 isReqDepActive := false 1226 unit = getUnitStatusSingle(conn, trDep.name) 1227 if unit != nil && unit.ActiveState == "active" { 1228 isReqDepActive = true 1229 } 1230 1231 if isTargetActive && isReqDepActive { 1232 return fmt.Errorf("Conflicts didn't take place") 1233 } 1234 1235 // Stop the target unit 1236 if isTargetActive { 1237 err = runStopUnit(t, conn, trTarget) 1238 if err != nil { 1239 return err 1240 } 1241 1242 unit = getUnitStatusSingle(conn, trTarget.name) 1243 if unit != nil { 1244 return fmt.Errorf("Test unit %s found in list, should be stopped", trTarget.name) 1245 } 1246 } 1247 1248 // Stop the dependent unit 1249 if isReqDepActive { 1250 err = runStopUnit(t, conn, trDep) 1251 if err != nil { 1252 return err 1253 } 1254 1255 unit = getUnitStatusSingle(conn, trDep.name) 1256 if unit != nil { 1257 return fmt.Errorf("Test unit %s found in list, should be stopped", trDep.name) 1258 } 1259 } 1260 1261 return nil 1262} 1263 1264// Ensure that putting running programs into scopes works 1265func TestStartStopTransientScope(t *testing.T) { 1266 conn := setupConn(t) 1267 defer conn.Close() 1268 1269 cmd := exec.Command("/bin/sleep", "400") 1270 err := cmd.Start() 1271 if err != nil { 1272 t.Fatal(err) 1273 } 1274 defer cmd.Process.Kill() 1275 1276 props := []Property{ 1277 PropPids(uint32(cmd.Process.Pid)), 1278 } 1279 target := fmt.Sprintf("testing-transient-%d.scope", cmd.Process.Pid) 1280 1281 // Start the unit 1282 reschan := make(chan string) 1283 _, err = conn.StartTransientUnit(target, "replace", props, reschan) 1284 if err != nil { 1285 t.Fatal(err) 1286 } 1287 1288 job := <-reschan 1289 if job != "done" { 1290 t.Fatal("Job is not done:", job) 1291 } 1292 1293 units, err := conn.ListUnits() 1294 if err != nil { 1295 t.Fatal(err) 1296 } 1297 1298 unit := getUnitStatus(units, target) 1299 1300 if unit == nil { 1301 t.Fatalf("Test unit not found in list") 1302 } else if unit.ActiveState != "active" { 1303 t.Fatalf("Test unit not active") 1304 } 1305 1306 // maybe check if pid is really a member of the just created scope 1307 // systemd uses the following api which does not use dbus, but directly 1308 // accesses procfs for cgroup information. 1309 // int sd_pid_get_unit(pid_t pid, char **session) 1310} 1311 1312// Ensure that basic unit gets killed by SIGTERM 1313func TestKillUnit(t *testing.T) { 1314 target := "start-stop.service" 1315 conn := setupConn(t) 1316 defer conn.Close() 1317 1318 err := conn.Subscribe() 1319 if err != nil { 1320 t.Fatal(err) 1321 } 1322 1323 subSet := conn.NewSubscriptionSet() 1324 evChan, errChan := subSet.Subscribe() 1325 1326 subSet.Add(target) 1327 1328 setupUnit(target, conn, t) 1329 linkUnit(target, conn, t) 1330 1331 // Start the unit 1332 reschan := make(chan string) 1333 _, err = conn.StartUnit(target, "replace", reschan) 1334 if err != nil { 1335 t.Fatal(err) 1336 } 1337 1338 job := <-reschan 1339 if job != "done" { 1340 t.Fatal("Job is not done:", job) 1341 } 1342 1343 // send SIGTERM 1344 conn.KillUnit(target, int32(syscall.SIGTERM)) 1345 1346 timeout := make(chan bool, 1) 1347 go func() { 1348 time.Sleep(3 * time.Second) 1349 close(timeout) 1350 }() 1351 1352 // Wait for the event, expecting the target UnitStatus meets one of the 1353 // following conditions: 1354 // * target is nil, meaning the unit has completely gone. 1355 // * target is non-nil, and its ActiveState is not active. 1356waitevent: 1357 for { 1358 select { 1359 case changes := <-evChan: 1360 tch, ok := changes[target] 1361 if !ok { 1362 continue waitevent 1363 } 1364 if tch == nil || (tch != nil && tch.Name == target && tch.ActiveState != "active") { 1365 break waitevent 1366 } 1367 case err = <-errChan: 1368 t.Fatal(err) 1369 case <-timeout: 1370 t.Fatal("Reached timeout") 1371 } 1372 } 1373} 1374 1375// Ensure that a failed unit gets reset 1376func TestResetFailedUnit(t *testing.T) { 1377 target := "start-failed.service" 1378 conn := setupConn(t) 1379 defer conn.Close() 1380 1381 setupUnit(target, conn, t) 1382 linkUnit(target, conn, t) 1383 1384 // Start the unit 1385 reschan := make(chan string) 1386 _, err := conn.StartUnit(target, "replace", reschan) 1387 if err != nil { 1388 t.Fatal(err) 1389 } 1390 1391 job := <-reschan 1392 if job != "failed" { 1393 t.Fatal("Job is not failed:", job) 1394 } 1395 1396 units, err := conn.ListUnits() 1397 if err != nil { 1398 t.Fatal(err) 1399 } 1400 1401 unit := getUnitStatus(units, target) 1402 if unit == nil { 1403 t.Fatalf("Test unit not found in list") 1404 } 1405 1406 // reset the failed unit 1407 err = conn.ResetFailedUnit(target) 1408 if err != nil { 1409 t.Fatal(err) 1410 } 1411 1412 // Ensure that the target unit is actually gone 1413 units, err = conn.ListUnits() 1414 if err != nil { 1415 t.Fatal(err) 1416 } 1417 1418 found := false 1419 for _, u := range units { 1420 if u.Name == target { 1421 found = true 1422 break 1423 } 1424 } 1425 if found { 1426 t.Fatalf("Test unit still found in list. units = %v", units) 1427 } 1428} 1429 1430func TestConnJobListener(t *testing.T) { 1431 target := "start-stop.service" 1432 conn := setupConn(t) 1433 defer conn.Close() 1434 1435 setupUnit(target, conn, t) 1436 linkUnit(target, conn, t) 1437 1438 jobSize := len(conn.jobListener.jobs) 1439 1440 reschan := make(chan string) 1441 _, err := conn.StartUnit(target, "replace", reschan) 1442 if err != nil { 1443 t.Fatal(err) 1444 } 1445 1446 <-reschan 1447 1448 _, err = conn.StopUnit(target, "replace", reschan) 1449 if err != nil { 1450 t.Fatal(err) 1451 } 1452 1453 <-reschan 1454 1455 currentJobSize := len(conn.jobListener.jobs) 1456 if jobSize != currentJobSize { 1457 t.Fatal("JobListener jobs leaked") 1458 } 1459} 1460 1461// Enables a unit and then masks/unmasks it 1462func TestMaskUnmask(t *testing.T) { 1463 target := "mask-unmask.service" 1464 conn := setupConn(t) 1465 defer conn.Close() 1466 1467 setupUnit(target, conn, t) 1468 abs := findFixture(target, t) 1469 runPath := filepath.Join("/run/systemd/system/", target) 1470 1471 // 1. Enable the unit 1472 install, changes, err := conn.EnableUnitFiles([]string{abs}, true, true) 1473 if err != nil { 1474 t.Fatal(err) 1475 } 1476 1477 if install { 1478 t.Log("Install was true") 1479 } 1480 1481 if len(changes) < 1 { 1482 t.Fatalf("Expected one change, got %v", changes) 1483 } 1484 1485 if changes[0].Filename != runPath { 1486 t.Fatal("Unexpected target filename") 1487 } 1488 1489 // 2. Mask the unit 1490 mChanges, err := conn.MaskUnitFiles([]string{target}, true, true) 1491 if err != nil { 1492 t.Fatal(err) 1493 } 1494 if mChanges[0].Filename != runPath { 1495 t.Fatalf("Change should include correct filename, %+v", mChanges[0]) 1496 } 1497 if mChanges[0].Destination != "" { 1498 t.Fatalf("Change destination should be empty, %+v", mChanges[0]) 1499 } 1500 1501 // 3. Unmask the unit 1502 uChanges, err := conn.UnmaskUnitFiles([]string{target}, true) 1503 if err != nil { 1504 t.Fatal(err) 1505 } 1506 if uChanges[0].Filename != runPath { 1507 t.Fatalf("Change should include correct filename, %+v", uChanges[0]) 1508 } 1509 if uChanges[0].Destination != "" { 1510 t.Fatalf("Change destination should be empty, %+v", uChanges[0]) 1511 } 1512 1513} 1514 1515// Test a global Reload 1516func TestReload(t *testing.T) { 1517 conn := setupConn(t) 1518 defer conn.Close() 1519 1520 err := conn.Reload() 1521 if err != nil { 1522 t.Fatal(err) 1523 } 1524} 1525 1526func TestUnitName(t *testing.T) { 1527 for _, unit := range []string{ 1528 "", 1529 "foo.service", 1530 "foobar", 1531 "woof@woof.service", 1532 "0123456", 1533 "account_db.service", 1534 "got-dashes", 1535 } { 1536 got := unitName(unitPath(unit)) 1537 if got != unit { 1538 t.Errorf("bad result for unitName(%s): got %q, want %q", unit, got, unit) 1539 } 1540 } 1541} 1542