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