Skip to content

Commit 9a82ba2

Browse files
committed
Merge pull request #22 from philips/more-dbus-tests
More dbus tests
2 parents a7e90d6 + efb8ea1 commit 9a82ba2

File tree

9 files changed

+252
-14
lines changed

9 files changed

+252
-14
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,25 @@ http://godoc.org/github.com/coreos/go-systemd
1010

1111
See an example in `examples/activation/httpserver.go`. For easy debugging use
1212
`/usr/lib/systemd/systemd-activate`
13+
14+
## D-Bus
15+
16+
The D-Bus API lets you start, stop and introspect systemd units. The API docs are here:
17+
18+
http://godoc.org/github.com/coreos/go-systemd/dbus
19+
20+
### Debugging
21+
22+
Create `/etc/dbus-1/system-local.conf` that looks like this:
23+
24+
```
25+
<!DOCTYPE busconfig PUBLIC
26+
"-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
27+
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
28+
<busconfig>
29+
<policy user="root">
30+
<allow eavesdrop="true"/>
31+
<allow eavesdrop="true" send_destination="*"/>
32+
</policy>
33+
</busconfig>
34+
```

dbus/dbus.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func ObjectPath(path string) dbus.ObjectPath {
3636
return dbus.ObjectPath(path)
3737
}
3838

39+
// Conn is a connection to systemds dbus endpoint.
3940
type Conn struct {
4041
sysconn *dbus.Conn
4142
sysobj *dbus.Object
@@ -53,6 +54,7 @@ type Conn struct {
5354
dispatch map[string]func(dbus.Signal)
5455
}
5556

57+
// New() establishes a connection to the system bus and authenticates.
5658
func New() (*Conn, error) {
5759
c := new(Conn)
5860

@@ -85,7 +87,11 @@ func (c *Conn) initConnection() error {
8587

8688
c.sysobj = c.sysconn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
8789

90+
// Setup the listeners on jobs so that we can get completions
91+
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
92+
"type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'")
93+
c.initSubscription()
94+
c.initDispatch()
95+
8896
return nil
8997
}
90-
91-

dbus/dbus_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"testing"
2121
)
2222

23+
// TestObjectPath ensures path encoding of the systemd rules works.
2324
func TestObjectPath(t *testing.T) {
2425
input := "/silly-path/to@a/unit..service"
2526
output := ObjectPath(input)
@@ -29,3 +30,12 @@ func TestObjectPath(t *testing.T) {
2930
t.Fatalf("Output '%s' did not match expected '%s'", output, expected)
3031
}
3132
}
33+
34+
// TestNew ensures that New() works without errors.
35+
func TestNew(t *testing.T) {
36+
_, err := New()
37+
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
}

dbus/methods.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
153153
}
154154

155155
out := make(map[string]interface{}, len(props))
156-
for k, v := range(props) {
156+
for k, v := range props {
157157
out[k] = v.Value()
158158
}
159159

dbus/methods_test.go

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,109 @@ limitations under the License.
1717
package dbus
1818

1919
import (
20+
"os"
21+
"path/filepath"
2022
"testing"
2123
)
2224

23-
// TestActivation forks out a copy of activation.go example and reads back two
24-
// strings from the pipes that are passed in.
25-
func TestGetUnitProperties(t *testing.T) {
25+
func setupConn(t *testing.T) *Conn {
2626
conn, err := New()
2727
if err != nil {
2828
t.Fatal(err)
2929
}
30-
//defer conn.Close()
30+
31+
return conn
32+
}
33+
34+
func setupUnit(target string, conn *Conn, t *testing.T) {
35+
// Blindly stop the unit in case it is running
36+
conn.StopUnit(target, "replace")
37+
38+
// Blindly remove the symlink in case it exists
39+
targetRun := filepath.Join("/run/systemd/system/", target)
40+
err := os.Remove(targetRun)
41+
42+
// 1. Enable the unit
43+
abs, err := filepath.Abs("../fixtures/" + target)
44+
if err != nil {
45+
t.Fatal(err)
46+
}
47+
48+
fixture := []string{abs}
49+
50+
install, changes, err := conn.EnableUnitFiles(fixture, true, true)
51+
52+
if install != false {
53+
t.Fatal("Install was true")
54+
}
55+
56+
if len(changes) < 1 {
57+
t.Fatal("Expected one change, got %v", changes)
58+
}
59+
60+
if changes[0].Filename != targetRun {
61+
t.Fatal("Unexpected target filename")
62+
}
63+
}
64+
65+
// Ensure that basic unit starting and stopping works.
66+
func TestStartStopUnit(t *testing.T) {
67+
target := "start-stop.service"
68+
conn := setupConn(t)
69+
70+
setupUnit(target, conn, t)
71+
72+
// 2. Start the unit
73+
job, err := conn.StartUnit(target, "replace")
74+
if err != nil {
75+
t.Fatal(err)
76+
}
77+
78+
if job != "done" {
79+
t.Fatal("Job is not done, %v", job)
80+
}
81+
82+
units, err := conn.ListUnits()
83+
84+
var unit *UnitStatus
85+
for _, u := range units {
86+
if u.Name == target {
87+
unit = &u
88+
}
89+
}
90+
91+
if unit == nil {
92+
t.Fatalf("Test unit not found in list")
93+
}
94+
95+
if unit.ActiveState != "active" {
96+
t.Fatalf("Test unit not active")
97+
}
98+
99+
// 3. Stop the unit
100+
job, err = conn.StopUnit(target, "replace")
101+
if err != nil {
102+
t.Fatal(err)
103+
}
104+
105+
units, err = conn.ListUnits()
106+
107+
unit = nil
108+
for _, u := range units {
109+
if u.Name == target {
110+
unit = &u
111+
}
112+
}
113+
114+
if unit != nil {
115+
t.Fatalf("Test unit found in list, should be stopped")
116+
}
117+
}
118+
119+
// TestGetUnitProperties reads the `-.mount` which should exist on all systemd
120+
// systems and ensures that one of its properties is valid.
121+
func TestGetUnitProperties(t *testing.T) {
122+
conn := setupConn(t)
31123

32124
unit := "-.mount"
33125

dbus/subscription.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ const (
2828
ignoreInterval = int64(30 * time.Millisecond)
2929
)
3030

31-
// Subscribe sets up this connection to subscribe to all dbus events. This is
32-
// required before calling SubscribeUnits.
31+
// Subscribe sets up this connection to subscribe to all systemd dbus events.
32+
// This is required before calling SubscribeUnits. When the connection closes
33+
// systemd will automatically stop sending signals so there is no need to
34+
// explicitly call Unsubscribe().
3335
func (c *Conn) Subscribe() error {
34-
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
35-
"type='signal',interface='org.freedesktop.systemd1.Manager',member='JobRemoved'")
3636
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
3737
"type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'")
3838
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
@@ -44,8 +44,16 @@ func (c *Conn) Subscribe() error {
4444
return err
4545
}
4646

47-
c.initSubscription()
48-
c.initDispatch()
47+
return nil
48+
}
49+
50+
// Unsubscribe this connection from systemd dbus events.
51+
func (c *Conn) Unsubscribe() error {
52+
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
53+
if err != nil {
54+
c.sysconn.Close()
55+
return err
56+
}
4957

5058
return nil
5159
}
@@ -142,7 +150,7 @@ type SubStateUpdate struct {
142150
}
143151

144152
// SetSubStateSubscriber writes to updateCh when any unit's substate changes.
145-
// Althrough this writes to updateCh on every state change, the reported state
153+
// Although this writes to updateCh on every state change, the reported state
146154
// may be more recent than the change that generated it (due to an unavoidable
147155
// race in the systemd dbus interface). That is, this method provides a good
148156
// way to keep a current view of all units' states, but is not guaranteed to

dbus/subscription_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package dbus
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
// TestSubscribe exercises the basics of subscription
9+
func TestSubscribe(t *testing.T) {
10+
conn, err := New()
11+
12+
if err != nil {
13+
t.Fatal(err)
14+
}
15+
16+
err = conn.Subscribe()
17+
if err != nil {
18+
t.Fatal(err)
19+
}
20+
21+
err = conn.Unsubscribe()
22+
if err != nil {
23+
t.Fatal(err)
24+
}
25+
}
26+
27+
// TestSubscribeUnit exercises the basics of subscription of a particular unit.
28+
func TestSubscribeUnit(t *testing.T) {
29+
target := "subscribe-events.service"
30+
31+
conn, err := New()
32+
33+
if err != nil {
34+
t.Fatal(err)
35+
}
36+
37+
err = conn.Subscribe()
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
42+
err = conn.Unsubscribe()
43+
if err != nil {
44+
t.Fatal(err)
45+
}
46+
47+
evChan, errChan := conn.SubscribeUnits(time.Second)
48+
49+
setupUnit(target, conn, t)
50+
51+
job, err := conn.StartUnit(target, "replace")
52+
if err != nil {
53+
t.Fatal(err)
54+
}
55+
56+
if job != "done" {
57+
t.Fatal("Couldn't start", target)
58+
}
59+
60+
timeout := make(chan bool, 1)
61+
go func() {
62+
time.Sleep(3 * time.Second)
63+
close(timeout)
64+
}()
65+
66+
for {
67+
select {
68+
case changes := <-evChan:
69+
tCh, ok := changes[target]
70+
71+
// Just continue until we see our event.
72+
if !ok {
73+
continue
74+
}
75+
76+
if tCh.ActiveState == "active" && tCh.Name == target {
77+
goto success
78+
}
79+
case err = <-errChan:
80+
t.Fatal(err)
81+
case <-timeout:
82+
t.Fatal("Reached timeout")
83+
}
84+
}
85+
86+
success:
87+
return
88+
}
89+
90+

fixtures/start-stop.service

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[Unit]
2+
Description=start stop test
3+
4+
[Service]
5+
ExecStart=/bin/sleep 400

fixtures/subscribe-events.service

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[Unit]
2+
Description=start stop test
3+
4+
[Service]
5+
ExecStart=/bin/sleep 400

0 commit comments

Comments
 (0)