blob: 061605bf46e1d4b25615e2e43fa0768808d7dc96 [file] [log] [blame]
// 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)
}
}
}