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