| // Copyright 2015, 2018 CoreOS, Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package dbus |
| |
| import ( |
| "fmt" |
| "os" |
| "os/exec" |
| "path" |
| "path/filepath" |
| "reflect" |
| "syscall" |
| "testing" |
| "time" |
| |
| "github.com/godbus/dbus" |
| ) |
| |
| type TrUnitProp struct { |
| name string |
| props []Property |
| } |
| |
| func setupConn(t *testing.T) *Conn { |
| conn, err := New() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| return conn |
| } |
| |
| func findFixture(target string, t *testing.T) string { |
| abs, err := filepath.Abs("../fixtures/" + target) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return abs |
| } |
| |
| func setupUnit(target string, conn *Conn, t *testing.T) { |
| // Blindly stop the unit in case it is running |
| conn.StopUnit(target, "replace", nil) |
| |
| // Blindly remove the symlink in case it exists |
| targetRun := filepath.Join("/run/systemd/system/", target) |
| os.Remove(targetRun) |
| } |
| |
| func linkUnit(target string, conn *Conn, t *testing.T) { |
| abs := findFixture(target, t) |
| fixture := []string{abs} |
| |
| changes, err := conn.LinkUnitFiles(fixture, true, true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if len(changes) < 1 { |
| t.Fatalf("Expected one change, got %v", changes) |
| } |
| |
| runPath := filepath.Join("/run/systemd/system/", target) |
| if changes[0].Filename != runPath { |
| t.Fatal("Unexpected target filename") |
| } |
| } |
| |
| func getUnitStatus(units []UnitStatus, name string) *UnitStatus { |
| for _, u := range units { |
| if u.Name == name { |
| return &u |
| } |
| } |
| return nil |
| } |
| |
| func getUnitStatusSingle(conn *Conn, name string) *UnitStatus { |
| units, err := conn.ListUnits() |
| if err != nil { |
| return nil |
| } |
| return getUnitStatus(units, name) |
| } |
| |
| func getUnitFile(units []UnitFile, name string) *UnitFile { |
| for _, u := range units { |
| if path.Base(u.Path) == name { |
| return &u |
| } |
| } |
| return nil |
| } |
| |
| func runStartTrUnit(t *testing.T, conn *Conn, trTarget TrUnitProp) error { |
| reschan := make(chan string) |
| _, err := conn.StartTransientUnit(trTarget.name, "replace", trTarget.props, reschan) |
| if err != nil { |
| return err |
| } |
| |
| job := <-reschan |
| if job != "done" { |
| return fmt.Errorf("Job is not done: %s", job) |
| } |
| |
| return nil |
| } |
| |
| func runStopUnit(t *testing.T, conn *Conn, trTarget TrUnitProp) error { |
| reschan := make(chan string) |
| _, err := conn.StopUnit(trTarget.name, "replace", reschan) |
| if err != nil { |
| return err |
| } |
| |
| // wait for StopUnit job to complete |
| <-reschan |
| |
| return nil |
| } |
| |
| // Ensure that basic unit starting and stopping works. |
| func TestStartStopUnit(t *testing.T) { |
| target := "start-stop.service" |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| setupUnit(target, conn, t) |
| linkUnit(target, conn, t) |
| |
| // 2. Start the unit |
| reschan := make(chan string) |
| _, err := conn.StartUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| job := <-reschan |
| if job != "done" { |
| t.Fatal("Job is not done:", job) |
| } |
| |
| units, err := conn.ListUnits() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit := getUnitStatus(units, target) |
| |
| if unit == nil { |
| t.Fatalf("Test unit not found in list") |
| } else if unit.ActiveState != "active" { |
| t.Fatalf("Test unit not active") |
| } |
| |
| // 3. Stop the unit |
| _, err = conn.StopUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // wait for StopUnit job to complete |
| <-reschan |
| |
| units, err = conn.ListUnits() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit = getUnitStatus(units, target) |
| |
| if unit != nil { |
| t.Fatalf("Test unit found in list, should be stopped") |
| } |
| } |
| |
| // Ensure that basic unit restarting works. |
| func TestRestartUnit(t *testing.T) { |
| target := "start-stop.service" |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| setupUnit(target, conn, t) |
| linkUnit(target, conn, t) |
| |
| // Start the unit |
| reschan := make(chan string) |
| _, err := conn.StartUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| job := <-reschan |
| if job != "done" { |
| t.Fatal("Job is not done:", job) |
| } |
| |
| units, err := conn.ListUnits() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit := getUnitStatus(units, target) |
| if unit == nil { |
| t.Fatalf("Test unit not found in list") |
| } else if unit.ActiveState != "active" { |
| t.Fatalf("Test unit not active") |
| } |
| |
| // Restart the unit |
| reschan = make(chan string) |
| _, err = conn.RestartUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| job = <-reschan |
| if job != "done" { |
| t.Fatal("Job is not done:", job) |
| } |
| |
| // Stop the unit |
| _, err = conn.StopUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // wait for StopUnit job to complete |
| <-reschan |
| |
| units, err = conn.ListUnits() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit = getUnitStatus(units, target) |
| if unit != nil { |
| t.Fatalf("Test unit found in list, should be stopped") |
| } |
| |
| // Try to restart the unit. |
| // It should still succeed, even if the unit is inactive. |
| reschan = make(chan string) |
| _, err = conn.TryRestartUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // wait for StopUnit job to complete |
| <-reschan |
| |
| units, err = conn.ListUnits() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit = getUnitStatus(units, target) |
| if unit != nil { |
| t.Fatalf("Test unit found in list, should be stopped") |
| } |
| } |
| |
| // Ensure that basic unit reloading works. |
| func TestReloadUnit(t *testing.T) { |
| target := "reload.service" |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| err := conn.Subscribe() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| subSet := conn.NewSubscriptionSet() |
| evChan, errChan := subSet.Subscribe() |
| |
| subSet.Add(target) |
| |
| setupUnit(target, conn, t) |
| linkUnit(target, conn, t) |
| |
| // Start the unit |
| reschan := make(chan string) |
| _, err = conn.StartUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| job := <-reschan |
| if job != "done" { |
| t.Fatal("Job is not done:", job) |
| } |
| |
| units, err := conn.ListUnits() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit := getUnitStatus(units, target) |
| if unit == nil { |
| t.Fatalf("Test unit not found in list") |
| } else if unit.ActiveState != "active" { |
| t.Fatalf("Test unit not active") |
| } |
| |
| // Reload the unit |
| reschan = make(chan string) |
| |
| _, err = conn.ReloadUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| job = <-reschan |
| if job != "done" { |
| t.Fatal("Job is not done:", job) |
| } |
| |
| timeout := make(chan bool, 1) |
| go func() { |
| time.Sleep(3 * time.Second) |
| close(timeout) |
| }() |
| |
| // Wait for the event, expecting the target UnitStatus meets all of the |
| // following conditions: |
| // * target is non-nil |
| // * target's ActiveState is active. |
| waitevent: |
| for { |
| select { |
| case changes := <-evChan: |
| tch, ok := changes[target] |
| if !ok { |
| continue waitevent |
| } |
| if tch != nil && tch.Name == target && tch.ActiveState == "active" { |
| break waitevent |
| } |
| case err = <-errChan: |
| t.Fatal(err) |
| case <-timeout: |
| t.Fatal("Reached timeout") |
| } |
| } |
| } |
| |
| // Ensure that basic unit reload-or-restarting works. |
| func TestReloadOrRestartUnit(t *testing.T) { |
| target := "reload.service" |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| setupUnit(target, conn, t) |
| linkUnit(target, conn, t) |
| |
| // Start the unit |
| reschan := make(chan string) |
| _, err := conn.StartUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| job := <-reschan |
| if job != "done" { |
| t.Fatal("Job is not done:", job) |
| } |
| |
| units, err := conn.ListUnits() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit := getUnitStatus(units, target) |
| if unit == nil { |
| t.Fatalf("Test unit not found in list") |
| } else if unit.ActiveState != "active" { |
| t.Fatalf("Test unit not active") |
| } |
| |
| // Reload or restart the unit |
| reschan = make(chan string) |
| _, err = conn.ReloadOrRestartUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| job = <-reschan |
| if job != "done" { |
| t.Fatal("Job is not done:", job) |
| } |
| |
| // Stop the unit |
| _, err = conn.StopUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // wait for StopUnit job to complete |
| <-reschan |
| |
| units, err = conn.ListUnits() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit = getUnitStatus(units, target) |
| if unit != nil && unit.ActiveState == "active" { |
| t.Fatalf("Test unit still active, should be inactive.") |
| } |
| |
| // Reload or try to restart the unit |
| // It should still succeed, even if the unit is inactive. |
| reschan = make(chan string) |
| _, err = conn.ReloadOrTryRestartUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| job = <-reschan |
| if job != "done" { |
| t.Fatal("Job is not done:", job) |
| } |
| } |
| |
| // Ensure that ListUnitsByNames works. |
| func TestListUnitsByNames(t *testing.T) { |
| target1 := "systemd-journald.service" |
| target2 := "unexisting.service" |
| |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| units, err := conn.ListUnitsByNames([]string{target1, target2}) |
| |
| if err != nil { |
| t.Skip(err) |
| } |
| |
| unit := getUnitStatus(units, target1) |
| |
| if unit == nil { |
| t.Fatalf("%s unit not found in list", target1) |
| } else if unit.ActiveState != "active" { |
| t.Fatalf("%s unit should be active but it is %s", target1, unit.ActiveState) |
| } |
| |
| unit = getUnitStatus(units, target2) |
| |
| if unit == nil { |
| t.Fatalf("Unexisting test unit not found in list") |
| } else if unit.ActiveState != "inactive" { |
| t.Fatalf("Test unit should be inactive") |
| } |
| } |
| |
| // Ensure that ListUnitsByPatterns works. |
| func TestListUnitsByPatterns(t *testing.T) { |
| target1 := "systemd-journald.service" |
| target2 := "unexisting.service" |
| |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| units, err := conn.ListUnitsByPatterns([]string{}, []string{"systemd-journald*", target2}) |
| |
| if err != nil { |
| t.Skip(err) |
| } |
| |
| unit := getUnitStatus(units, target1) |
| |
| if unit == nil { |
| t.Fatalf("%s unit not found in list", target1) |
| } else if unit.ActiveState != "active" { |
| t.Fatalf("Test unit should be active") |
| } |
| |
| unit = getUnitStatus(units, target2) |
| |
| if unit != nil { |
| t.Fatalf("Unexisting test unit found in list") |
| } |
| } |
| |
| // Ensure that ListUnitsFiltered works. |
| func TestListUnitsFiltered(t *testing.T) { |
| target := "systemd-journald.service" |
| |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| units, err := conn.ListUnitsFiltered([]string{"active"}) |
| |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit := getUnitStatus(units, target) |
| |
| if unit == nil { |
| t.Fatalf("%s unit not found in list", target) |
| } else if unit.ActiveState != "active" { |
| t.Fatalf("Test unit should be active") |
| } |
| |
| units, err = conn.ListUnitsFiltered([]string{"inactive"}) |
| |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit = getUnitStatus(units, target) |
| |
| if unit != nil { |
| t.Fatalf("Inactive unit should not be found in list") |
| } |
| } |
| |
| // Ensure that ListUnitFilesByPatterns works. |
| func TestListUnitFilesByPatterns(t *testing.T) { |
| target1 := "systemd-journald.service" |
| target2 := "exit.target" |
| |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| units, err := conn.ListUnitFilesByPatterns([]string{"static"}, []string{"systemd-journald*", target2}) |
| |
| if err != nil { |
| t.Skip(err) |
| } |
| |
| unit := getUnitFile(units, target1) |
| |
| if unit == nil { |
| t.Fatalf("%s unit not found in list", target1) |
| } else if unit.Type != "static" { |
| t.Fatalf("Test unit file should be static") |
| } |
| |
| units, err = conn.ListUnitFilesByPatterns([]string{"disabled"}, []string{"systemd-journald*", target2}) |
| |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit = getUnitFile(units, target2) |
| |
| if unit == nil { |
| t.Fatalf("%s unit not found in list", target2) |
| } else if unit.Type != "disabled" { |
| t.Fatalf("%s unit file should be disabled", target2) |
| } |
| } |
| |
| func TestListUnitFiles(t *testing.T) { |
| target1 := "systemd-journald.service" |
| target2 := "exit.target" |
| |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| units, err := conn.ListUnitFiles() |
| |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit := getUnitFile(units, target1) |
| |
| if unit == nil { |
| t.Fatalf("%s unit not found in list", target1) |
| } else if unit.Type != "static" { |
| t.Fatalf("Test unit file should be static") |
| } |
| |
| unit = getUnitFile(units, target2) |
| |
| if unit == nil { |
| t.Fatalf("%s unit not found in list", target2) |
| } else if unit.Type != "disabled" { |
| t.Fatalf("%s unit file should be disabled", target2) |
| } |
| } |
| |
| // Enables a unit and then immediately tears it down |
| func TestEnableDisableUnit(t *testing.T) { |
| target := "enable-disable.service" |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| setupUnit(target, conn, t) |
| abs := findFixture(target, t) |
| runPath := filepath.Join("/run/systemd/system/", target) |
| |
| // 1. Enable the unit |
| install, changes, err := conn.EnableUnitFiles([]string{abs}, true, true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if install { |
| t.Log("Install was true") |
| } |
| |
| if len(changes) < 1 { |
| t.Fatalf("Expected one change, got %v", changes) |
| } |
| |
| if changes[0].Filename != runPath { |
| t.Fatal("Unexpected target filename") |
| } |
| |
| // 2. Disable the unit |
| dChanges, err := conn.DisableUnitFiles([]string{target}, true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if len(dChanges) != 1 { |
| t.Fatalf("Changes should include the path, %v", dChanges) |
| } |
| if dChanges[0].Filename != runPath { |
| t.Fatalf("Change should include correct filename, %+v", dChanges[0]) |
| } |
| if dChanges[0].Destination != "" { |
| t.Fatalf("Change destination should be empty, %+v", dChanges[0]) |
| } |
| } |
| |
| // TestSystemState tests if system state is one of the valid states |
| func TestSystemState(t *testing.T) { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| prop, err := conn.SystemState() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if prop.Name != "SystemState" { |
| t.Fatalf("unexpected property name: %v", prop.Name) |
| } |
| |
| val := prop.Value.Value().(string) |
| |
| switch val { |
| case "initializing": |
| case "starting": |
| case "running": |
| case "degraded": |
| case "maintenance": |
| case "stopping": |
| case "offline": |
| case "unknown": |
| // valid systemd state - do nothing |
| |
| default: |
| t.Fatalf("unexpected property value: %v", val) |
| } |
| } |
| |
| // TestGetUnitProperties reads the `-.mount` which should exist on all systemd |
| // systems and ensures that one of its properties is valid. |
| func TestGetUnitProperties(t *testing.T) { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| unit := "-.mount" |
| |
| info, err := conn.GetUnitProperties(unit) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| desc, _ := info["Description"].(string) |
| |
| prop, err := conn.GetUnitProperty(unit, "Description") |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if prop.Name != "Description" { |
| t.Fatal("unexpected property name") |
| } |
| |
| val := prop.Value.Value().(string) |
| if !reflect.DeepEqual(val, desc) { |
| t.Fatal("unexpected property value") |
| } |
| } |
| |
| // TestGetUnitPropertiesRejectsInvalidName attempts to get the properties for a |
| // unit with an invalid name. This test should be run with --test.timeout set, |
| // as a fail will manifest as GetUnitProperties hanging indefinitely. |
| func TestGetUnitPropertiesRejectsInvalidName(t *testing.T) { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| unit := "//invalid#$^/" |
| |
| _, err := conn.GetUnitProperties(unit) |
| if err == nil { |
| t.Fatal("Expected an error, got nil") |
| } |
| |
| _, err = conn.GetUnitProperty(unit, "Wants") |
| if err == nil { |
| t.Fatal("Expected an error, got nil") |
| } |
| } |
| |
| // TestGetServiceProperty reads the `systemd-udevd.service` which should exist |
| // on all systemd systems and ensures that one of its property is valid. |
| func TestGetServiceProperty(t *testing.T) { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| service := "systemd-udevd.service" |
| |
| prop, err := conn.GetServiceProperty(service, "Type") |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if prop.Name != "Type" { |
| t.Fatal("unexpected property name") |
| } |
| |
| if _, ok := prop.Value.Value().(string); !ok { |
| t.Fatal("invalid property value") |
| } |
| } |
| |
| // TestSetUnitProperties changes a cgroup setting on the `-.mount` |
| // which should exist on all systemd systems and ensures that the |
| // property was set. |
| func TestSetUnitProperties(t *testing.T) { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| unit := "-.mount" |
| |
| if err := conn.SetUnitProperties(unit, true, Property{"CPUShares", dbus.MakeVariant(uint64(1023))}); err != nil { |
| t.Fatal(err) |
| } |
| |
| info, err := conn.GetUnitTypeProperties(unit, "Mount") |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| value, _ := info["CPUShares"].(uint64) |
| if value != 1023 { |
| t.Fatal("CPUShares of unit is not 1023:", value) |
| } |
| } |
| |
| // Ensure that oneshot transient unit starting and stopping works. |
| func TestStartStopTransientUnitAll(t *testing.T) { |
| testCases := []struct { |
| trTarget TrUnitProp |
| trDep TrUnitProp |
| checkFunc func(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error |
| }{ |
| { |
| trTarget: TrUnitProp{ |
| name: "testing-transient.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| }, |
| }, |
| trDep: TrUnitProp{"", nil}, |
| checkFunc: checkTransientUnit, |
| }, |
| { |
| trTarget: TrUnitProp{ |
| name: "testing-transient-oneshot.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/true"}, false), |
| PropType("oneshot"), |
| PropRemainAfterExit(true), |
| }, |
| }, |
| trDep: TrUnitProp{"", nil}, |
| checkFunc: checkTransientUnitOneshot, |
| }, |
| { |
| trTarget: TrUnitProp{ |
| name: "testing-transient-requires.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| PropRequires("testing-transient-requiresdep.service"), |
| }, |
| }, |
| trDep: TrUnitProp{ |
| name: "testing-transient-requiresdep.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| }, |
| }, |
| checkFunc: checkTransientUnitRequires, |
| }, |
| { |
| trTarget: TrUnitProp{ |
| name: "testing-transient-requires-ov.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| PropRequires("testing-transient-requiresdep-ov.service"), |
| }, |
| }, |
| trDep: TrUnitProp{ |
| name: "testing-transient-requiresdep-ov.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| }, |
| }, |
| checkFunc: checkTransientUnitRequiresOv, |
| }, |
| { |
| trTarget: TrUnitProp{ |
| name: "testing-transient-requisite.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| PropRequisite("testing-transient-requisitedep.service"), |
| }, |
| }, |
| trDep: TrUnitProp{ |
| name: "testing-transient-requisitedep.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| }, |
| }, |
| checkFunc: checkTransientUnitRequisite, |
| }, |
| { |
| trTarget: TrUnitProp{ |
| name: "testing-transient-requisite-ov.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| PropRequisiteOverridable("testing-transient-requisitedep-ov.service"), |
| }, |
| }, |
| trDep: TrUnitProp{ |
| name: "testing-transient-requisitedep-ov.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| }, |
| }, |
| checkFunc: checkTransientUnitRequisiteOv, |
| }, |
| { |
| trTarget: TrUnitProp{ |
| name: "testing-transient-wants.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| PropWants("testing-transient-wantsdep.service"), |
| }, |
| }, |
| trDep: TrUnitProp{ |
| name: "testing-transient-wantsdep.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| }, |
| }, |
| checkFunc: checkTransientUnitWants, |
| }, |
| { |
| trTarget: TrUnitProp{ |
| name: "testing-transient-bindsto.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| PropBindsTo("testing-transient-bindstodep.service"), |
| }, |
| }, |
| trDep: TrUnitProp{ |
| name: "testing-transient-bindstodep.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| }, |
| }, |
| checkFunc: checkTransientUnitBindsTo, |
| }, |
| { |
| trTarget: TrUnitProp{ |
| name: "testing-transient-conflicts.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| PropConflicts("testing-transient-conflictsdep.service"), |
| }, |
| }, |
| trDep: TrUnitProp{ |
| name: "testing-transient-conflictsdep.service", |
| props: []Property{ |
| PropExecStart([]string{"/bin/sleep", "400"}, false), |
| }, |
| }, |
| checkFunc: checkTransientUnitConflicts, |
| }, |
| } |
| |
| for i, tt := range testCases { |
| if err := tt.checkFunc(t, tt.trTarget, tt.trDep); err != nil { |
| t.Errorf("case %d: failed test with unit %s. err: %v", i, tt.trTarget.name, err) |
| } |
| } |
| } |
| |
| // Ensure that basic transient unit starting and stopping works. |
| func checkTransientUnit(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| // Start the unit |
| err := runStartTrUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| unit := getUnitStatusSingle(conn, trTarget.name) |
| if unit == nil { |
| return fmt.Errorf("Test unit not found in list") |
| } else if unit.ActiveState != "active" { |
| return fmt.Errorf("Test unit not active") |
| } |
| |
| // Stop the unit |
| err = runStopUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| unit = getUnitStatusSingle(conn, trTarget.name) |
| if unit != nil { |
| return fmt.Errorf("Test unit found in list, should be stopped") |
| } |
| |
| return nil |
| } |
| |
| func checkTransientUnitOneshot(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| // Start the unit |
| err := runStartTrUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| unit := getUnitStatusSingle(conn, trTarget.name) |
| if unit == nil { |
| return fmt.Errorf("Test unit not found in list") |
| } else if unit.ActiveState != "active" { |
| return fmt.Errorf("Test unit not active") |
| } |
| |
| // Stop the unit |
| err = runStopUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| unit = getUnitStatusSingle(conn, trTarget.name) |
| if unit != nil { |
| return fmt.Errorf("Test unit found in list, should be stopped") |
| } |
| |
| return nil |
| } |
| |
| // Ensure that transient unit with Requires starting and stopping works. |
| func checkTransientUnitRequires(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| // Start the dependent unit |
| err := runStartTrUnit(t, conn, trDep) |
| if err != nil { |
| return err |
| } |
| |
| // Start the target unit |
| err = runStartTrUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| unit := getUnitStatusSingle(conn, trTarget.name) |
| if unit == nil { |
| return fmt.Errorf("Test unit not found in list") |
| } else if unit.ActiveState != "active" { |
| return fmt.Errorf("Test unit not active") |
| } |
| |
| // Stop the unit |
| err = runStopUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| unit = getUnitStatusSingle(conn, trTarget.name) |
| if unit != nil { |
| return fmt.Errorf("Test unit found in list, should be stopped") |
| } |
| |
| // Stop the dependent unit |
| err = runStopUnit(t, conn, trDep) |
| if err != nil { |
| return err |
| } |
| |
| unit = getUnitStatusSingle(conn, trDep.name) |
| if unit != nil { |
| return fmt.Errorf("Test unit found in list, should be stopped") |
| } |
| |
| return nil |
| } |
| |
| // Ensure that transient unit with RequiresOverridable starting and stopping works. |
| func checkTransientUnitRequiresOv(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| // Start the dependent unit |
| err := runStartTrUnit(t, conn, trDep) |
| if err != nil { |
| return err |
| } |
| |
| // Start the target unit |
| err = runStartTrUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| unit := getUnitStatusSingle(conn, trTarget.name) |
| if unit == nil { |
| return fmt.Errorf("Test unit not found in list") |
| } else if unit.ActiveState != "active" { |
| return fmt.Errorf("Test unit not active") |
| } |
| |
| // Stop the unit |
| err = runStopUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| unit = getUnitStatusSingle(conn, trTarget.name) |
| if unit != nil { |
| return fmt.Errorf("Test unit found in list, should be stopped") |
| } |
| |
| // Stop the dependent unit |
| err = runStopUnit(t, conn, trDep) |
| if err != nil { |
| return err |
| } |
| |
| unit = getUnitStatusSingle(conn, trDep.name) |
| if unit != nil { |
| return fmt.Errorf("Test unit found in list, should be stopped") |
| } |
| |
| return nil |
| } |
| |
| // Ensure that transient unit with Requisite starting and stopping works. |
| // It's expected for target unit to fail, as its child is not started at all. |
| func checkTransientUnitRequisite(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| // Start the target unit |
| err := runStartTrUnit(t, conn, trTarget) |
| if err == nil { |
| return fmt.Errorf("Unit %s is expected to fail, but succeeded", trTarget.name) |
| } |
| |
| unit := getUnitStatusSingle(conn, trTarget.name) |
| if unit != nil && unit.ActiveState == "active" { |
| return fmt.Errorf("Test unit %s is active, should be inactive", trTarget.name) |
| } |
| |
| return nil |
| } |
| |
| // Ensure that transient unit with RequisiteOverridable starting and stopping works. |
| // It's expected for target unit to fail, as its child is not started at all. |
| func checkTransientUnitRequisiteOv(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| // Start the target unit |
| err := runStartTrUnit(t, conn, trTarget) |
| if err == nil { |
| return fmt.Errorf("Unit %s is expected to fail, but succeeded", trTarget.name) |
| } |
| |
| unit := getUnitStatusSingle(conn, trTarget.name) |
| if unit != nil && unit.ActiveState == "active" { |
| return fmt.Errorf("Test unit %s is active, should be inactive", trTarget.name) |
| } |
| |
| return nil |
| } |
| |
| // Ensure that transient unit with Wants starting and stopping works. |
| // It's expected for target to successfully start, even when its child is not started. |
| func checkTransientUnitWants(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| // Start the target unit |
| err := runStartTrUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| unit := getUnitStatusSingle(conn, trTarget.name) |
| if unit == nil { |
| return fmt.Errorf("Test unit not found in list") |
| } else if unit.ActiveState != "active" { |
| return fmt.Errorf("Test unit not active") |
| } |
| |
| // Stop the unit |
| err = runStopUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| unit = getUnitStatusSingle(conn, trTarget.name) |
| if unit != nil { |
| return fmt.Errorf("Test unit found in list, should be stopped") |
| } |
| |
| return nil |
| } |
| |
| // Ensure that transient unit with BindsTo starting and stopping works. |
| // Stopping its child should result in stopping target unit. |
| func checkTransientUnitBindsTo(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| // Start the dependent unit |
| err := runStartTrUnit(t, conn, trDep) |
| if err != nil { |
| return err |
| } |
| |
| // Start the target unit |
| err = runStartTrUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| unit := getUnitStatusSingle(conn, trTarget.name) |
| if unit == nil { |
| t.Fatalf("Test unit not found in list") |
| } else if unit.ActiveState != "active" { |
| t.Fatalf("Test unit not active") |
| } |
| |
| // Stop the dependent unit |
| err = runStopUnit(t, conn, trDep) |
| if err != nil { |
| return err |
| } |
| |
| unit = getUnitStatusSingle(conn, trDep.name) |
| if unit != nil { |
| t.Fatalf("Test unit found in list, should be stopped") |
| } |
| |
| // Then the target unit should be gone |
| unit = getUnitStatusSingle(conn, trTarget.name) |
| if unit != nil { |
| t.Fatalf("Test unit found in list, should be stopped") |
| } |
| |
| return nil |
| } |
| |
| // Ensure that transient unit with Conflicts starting and stopping works. |
| func checkTransientUnitConflicts(t *testing.T, trTarget TrUnitProp, trDep TrUnitProp) error { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| // Start the dependent unit |
| err := runStartTrUnit(t, conn, trDep) |
| if err != nil { |
| return err |
| } |
| |
| // Start the target unit |
| err = runStartTrUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| isTargetActive := false |
| unit := getUnitStatusSingle(conn, trTarget.name) |
| if unit != nil && unit.ActiveState == "active" { |
| isTargetActive = true |
| } |
| |
| isReqDepActive := false |
| unit = getUnitStatusSingle(conn, trDep.name) |
| if unit != nil && unit.ActiveState == "active" { |
| isReqDepActive = true |
| } |
| |
| if isTargetActive && isReqDepActive { |
| return fmt.Errorf("Conflicts didn't take place") |
| } |
| |
| // Stop the target unit |
| if isTargetActive { |
| err = runStopUnit(t, conn, trTarget) |
| if err != nil { |
| return err |
| } |
| |
| unit = getUnitStatusSingle(conn, trTarget.name) |
| if unit != nil { |
| return fmt.Errorf("Test unit %s found in list, should be stopped", trTarget.name) |
| } |
| } |
| |
| // Stop the dependent unit |
| if isReqDepActive { |
| err = runStopUnit(t, conn, trDep) |
| if err != nil { |
| return err |
| } |
| |
| unit = getUnitStatusSingle(conn, trDep.name) |
| if unit != nil { |
| return fmt.Errorf("Test unit %s found in list, should be stopped", trDep.name) |
| } |
| } |
| |
| return nil |
| } |
| |
| // Ensure that putting running programs into scopes works |
| func TestStartStopTransientScope(t *testing.T) { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| cmd := exec.Command("/bin/sleep", "400") |
| err := cmd.Start() |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer cmd.Process.Kill() |
| |
| props := []Property{ |
| PropPids(uint32(cmd.Process.Pid)), |
| } |
| target := fmt.Sprintf("testing-transient-%d.scope", cmd.Process.Pid) |
| |
| // Start the unit |
| reschan := make(chan string) |
| _, err = conn.StartTransientUnit(target, "replace", props, reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| job := <-reschan |
| if job != "done" { |
| t.Fatal("Job is not done:", job) |
| } |
| |
| units, err := conn.ListUnits() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit := getUnitStatus(units, target) |
| |
| if unit == nil { |
| t.Fatalf("Test unit not found in list") |
| } else if unit.ActiveState != "active" { |
| t.Fatalf("Test unit not active") |
| } |
| |
| // maybe check if pid is really a member of the just created scope |
| // systemd uses the following api which does not use dbus, but directly |
| // accesses procfs for cgroup information. |
| // int sd_pid_get_unit(pid_t pid, char **session) |
| } |
| |
| // Ensure that basic unit gets killed by SIGTERM |
| func TestKillUnit(t *testing.T) { |
| target := "start-stop.service" |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| err := conn.Subscribe() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| subSet := conn.NewSubscriptionSet() |
| evChan, errChan := subSet.Subscribe() |
| |
| subSet.Add(target) |
| |
| setupUnit(target, conn, t) |
| linkUnit(target, conn, t) |
| |
| // Start the unit |
| reschan := make(chan string) |
| _, err = conn.StartUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| job := <-reschan |
| if job != "done" { |
| t.Fatal("Job is not done:", job) |
| } |
| |
| // send SIGTERM |
| conn.KillUnit(target, int32(syscall.SIGTERM)) |
| |
| timeout := make(chan bool, 1) |
| go func() { |
| time.Sleep(3 * time.Second) |
| close(timeout) |
| }() |
| |
| // Wait for the event, expecting the target UnitStatus meets one of the |
| // following conditions: |
| // * target is nil, meaning the unit has completely gone. |
| // * target is non-nil, and its ActiveState is not active. |
| waitevent: |
| for { |
| select { |
| case changes := <-evChan: |
| tch, ok := changes[target] |
| if !ok { |
| continue waitevent |
| } |
| if tch == nil || (tch != nil && tch.Name == target && tch.ActiveState != "active") { |
| break waitevent |
| } |
| case err = <-errChan: |
| t.Fatal(err) |
| case <-timeout: |
| t.Fatal("Reached timeout") |
| } |
| } |
| } |
| |
| // Ensure that a failed unit gets reset |
| func TestResetFailedUnit(t *testing.T) { |
| target := "start-failed.service" |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| setupUnit(target, conn, t) |
| linkUnit(target, conn, t) |
| |
| // Start the unit |
| reschan := make(chan string) |
| _, err := conn.StartUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| job := <-reschan |
| if job != "failed" { |
| t.Fatal("Job is not failed:", job) |
| } |
| |
| units, err := conn.ListUnits() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| unit := getUnitStatus(units, target) |
| if unit == nil { |
| t.Fatalf("Test unit not found in list") |
| } |
| |
| // reset the failed unit |
| err = conn.ResetFailedUnit(target) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Ensure that the target unit is actually gone |
| units, err = conn.ListUnits() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| found := false |
| for _, u := range units { |
| if u.Name == target { |
| found = true |
| break |
| } |
| } |
| if found { |
| t.Fatalf("Test unit still found in list. units = %v", units) |
| } |
| } |
| |
| func TestConnJobListener(t *testing.T) { |
| target := "start-stop.service" |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| setupUnit(target, conn, t) |
| linkUnit(target, conn, t) |
| |
| jobSize := len(conn.jobListener.jobs) |
| |
| reschan := make(chan string) |
| _, err := conn.StartUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| <-reschan |
| |
| _, err = conn.StopUnit(target, "replace", reschan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| <-reschan |
| |
| currentJobSize := len(conn.jobListener.jobs) |
| if jobSize != currentJobSize { |
| t.Fatal("JobListener jobs leaked") |
| } |
| } |
| |
| // Enables a unit and then masks/unmasks it |
| func TestMaskUnmask(t *testing.T) { |
| target := "mask-unmask.service" |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| setupUnit(target, conn, t) |
| abs := findFixture(target, t) |
| runPath := filepath.Join("/run/systemd/system/", target) |
| |
| // 1. Enable the unit |
| install, changes, err := conn.EnableUnitFiles([]string{abs}, true, true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if install { |
| t.Log("Install was true") |
| } |
| |
| if len(changes) < 1 { |
| t.Fatalf("Expected one change, got %v", changes) |
| } |
| |
| if changes[0].Filename != runPath { |
| t.Fatal("Unexpected target filename") |
| } |
| |
| // 2. Mask the unit |
| mChanges, err := conn.MaskUnitFiles([]string{target}, true, true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if mChanges[0].Filename != runPath { |
| t.Fatalf("Change should include correct filename, %+v", mChanges[0]) |
| } |
| if mChanges[0].Destination != "" { |
| t.Fatalf("Change destination should be empty, %+v", mChanges[0]) |
| } |
| |
| // 3. Unmask the unit |
| uChanges, err := conn.UnmaskUnitFiles([]string{target}, true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if uChanges[0].Filename != runPath { |
| t.Fatalf("Change should include correct filename, %+v", uChanges[0]) |
| } |
| if uChanges[0].Destination != "" { |
| t.Fatalf("Change destination should be empty, %+v", uChanges[0]) |
| } |
| |
| } |
| |
| // Test a global Reload |
| func TestReload(t *testing.T) { |
| conn := setupConn(t) |
| defer conn.Close() |
| |
| err := conn.Reload() |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestUnitName(t *testing.T) { |
| for _, unit := range []string{ |
| "", |
| "foo.service", |
| "foobar", |
| "woof@woof.service", |
| "0123456", |
| "account_db.service", |
| "got-dashes", |
| } { |
| got := unitName(unitPath(unit)) |
| if got != unit { |
| t.Errorf("bad result for unitName(%s): got %q, want %q", unit, got, unit) |
| } |
| } |
| } |