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