Skip to content

Commit 12a4b2a

Browse files
committed
pkg/login1: initial commit
This commit adds login1 package, which is a small subset of github.com/coreos/go-systemd/v22/login1 package with ability to use shared D-Bus connection and with proper error handling for Reboot method call, which is not yet provided by the upstream. The idea is to use this package in favor of github.com/coreos/go-systemd in agent code responsible for rebooting the node. However, this requires tests in agent code, so it will be done in the next step. See coreos/go-systemd#387 for more details. Signed-off-by: Mateusz Gozdek <[email protected]>
1 parent 64b7f88 commit 12a4b2a

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
lines changed

pkg/login1/login1.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Package login1 is a small subset of github.com/coreos/go-systemd/v22/login1 package with
2+
// ability to use shared D-Bus connection and with proper error handling for Reboot method call, which
3+
// is not yet provided by the upstream.
4+
package login1
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
godbus "github.com/godbus/dbus/v5"
11+
)
12+
13+
const (
14+
// DBusDest is an object path used by systemd-logind.
15+
DBusDest = "org.freedesktop.login1"
16+
// DBusInterface is an systemd-logind intefrace name.
17+
DBusInterface = "org.freedesktop.login1.Manager"
18+
// DBusPath is a standard path to systemd-logind interface.
19+
DBusPath = "/org/freedesktop/login1"
20+
// DBusMethodNameReboot is a login1 manager interface method name responsible for rebooting.
21+
DBusMethodNameReboot = "Reboot"
22+
)
23+
24+
// Client describes functionality of provided login1 client.
25+
type Client interface {
26+
Reboot(context.Context) error
27+
}
28+
29+
// Objector describes functionality required from given D-Bus connection.
30+
type Objector interface {
31+
Object(string, godbus.ObjectPath) godbus.BusObject
32+
}
33+
34+
// Caller describes required functionality from D-Bus object.
35+
type Caller interface {
36+
CallWithContext(ctx context.Context, method string, flags godbus.Flags, args ...interface{}) *godbus.Call
37+
}
38+
39+
type rebooter struct {
40+
caller Caller
41+
}
42+
43+
// New creates new login1 client using given D-Bus connection.
44+
func New(objector Objector) (Client, error) {
45+
if objector == nil {
46+
return nil, fmt.Errorf("no objector given")
47+
}
48+
49+
return &rebooter{
50+
caller: objector.Object(DBusDest, DBusPath),
51+
}, nil
52+
}
53+
54+
// Reboot reboots machine on which it's called.
55+
func (r *rebooter) Reboot(ctx context.Context) error {
56+
if call := r.caller.CallWithContext(ctx, DBusInterface+"."+DBusMethodNameReboot, 0, false); call.Err != nil {
57+
return fmt.Errorf("calling reboot: %w", call.Err)
58+
}
59+
60+
return nil
61+
}

pkg/login1/login1_test.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package login1_test
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"testing"
8+
9+
godbus "github.com/godbus/dbus/v5"
10+
11+
"github.com/flatcar-linux/flatcar-linux-update-operator/pkg/dbus"
12+
"github.com/flatcar-linux/flatcar-linux-update-operator/pkg/login1"
13+
)
14+
15+
func Test_Creating_new_client(t *testing.T) {
16+
t.Parallel()
17+
18+
t.Run("connects_to_global_login1_path_and_interface", func(t *testing.T) {
19+
t.Parallel()
20+
21+
objectConstructorCalled := false
22+
23+
connectionWithContextCheck := &dbus.MockConnection{
24+
ObjectF: func(dest string, path godbus.ObjectPath) godbus.BusObject {
25+
objectConstructorCalled = true
26+
27+
expectedDest := "org.freedesktop.login1"
28+
29+
if dest != expectedDest {
30+
t.Fatalf("Expected D-Bus destination %q, got %q", expectedDest, dest)
31+
}
32+
33+
expectedPath := godbus.ObjectPath("/org/freedesktop/login1")
34+
35+
if path != expectedPath {
36+
t.Fatalf("Expected D-Bus path %q, got %q", expectedPath, path)
37+
}
38+
39+
return nil
40+
},
41+
}
42+
43+
if _, err := login1.New(connectionWithContextCheck); err != nil {
44+
t.Fatalf("Unexpected error creating client: %v", err)
45+
}
46+
47+
if !objectConstructorCalled {
48+
t.Fatalf("Expected object constructor to be called")
49+
}
50+
})
51+
52+
t.Run("returns_error_when_no_objector_is_given", func(t *testing.T) {
53+
t.Parallel()
54+
55+
client, err := login1.New(nil)
56+
if err == nil {
57+
t.Fatalf("Expected error creating client with no connector")
58+
}
59+
60+
if client != nil {
61+
t.Fatalf("Expected client to be nil when New returns error")
62+
}
63+
})
64+
}
65+
66+
func Test_Rebooting(t *testing.T) {
67+
t.Parallel()
68+
69+
t.Run("calls_login1_reboot_method_on_manager_interface", func(t *testing.T) {
70+
t.Parallel()
71+
72+
rebootCalled := false
73+
74+
connectionWithContextCheck := &dbus.MockConnection{
75+
ObjectF: func(string, godbus.ObjectPath) godbus.BusObject {
76+
return &dbus.MockObject{
77+
CallWithContextF: func(ctx context.Context, method string, flags godbus.Flags, args ...interface{}) *godbus.Call {
78+
rebootCalled = true
79+
80+
expectedMethodName := "org.freedesktop.login1.Manager.Reboot"
81+
82+
if method != expectedMethodName {
83+
t.Fatalf("Expected method %q being called, got %q", expectedMethodName, method)
84+
}
85+
86+
return &godbus.Call{}
87+
},
88+
}
89+
},
90+
}
91+
92+
client, err := login1.New(connectionWithContextCheck)
93+
if err != nil {
94+
t.Fatalf("Unexpected error creating client: %v", err)
95+
}
96+
97+
if err := client.Reboot(context.Background()); err != nil {
98+
t.Fatalf("Unexpected error rebooting: %v", err)
99+
}
100+
101+
if !rebootCalled {
102+
t.Fatalf("Expected reboot method call on given D-Bus connection")
103+
}
104+
})
105+
106+
t.Run("use_given_context_for_D-Bus_call", func(t *testing.T) {
107+
t.Parallel()
108+
109+
testKey := struct{}{}
110+
expectedValue := "bar"
111+
112+
ctx := context.WithValue(context.Background(), testKey, expectedValue)
113+
114+
connectionWithContextCheck := &dbus.MockConnection{
115+
ObjectF: func(string, godbus.ObjectPath) godbus.BusObject {
116+
return &dbus.MockObject{
117+
CallWithContextF: func(ctx context.Context, method string, flags godbus.Flags, args ...interface{}) *godbus.Call {
118+
if val := ctx.Value(testKey); val != expectedValue {
119+
t.Fatalf("Got unexpected context on call")
120+
}
121+
122+
return &godbus.Call{}
123+
},
124+
}
125+
},
126+
}
127+
128+
client, err := login1.New(connectionWithContextCheck)
129+
if err != nil {
130+
t.Fatalf("Unexpected error creating client: %v", err)
131+
}
132+
133+
if err := client.Reboot(ctx); err != nil {
134+
t.Fatalf("Unexpected error rebooting: %v", err)
135+
}
136+
})
137+
138+
t.Run("returns_error_when_D-Bus_call_fails", func(t *testing.T) {
139+
t.Parallel()
140+
141+
expectedError := fmt.Errorf("reboot error")
142+
143+
connectionWithFailingObjectCall := &dbus.MockConnection{
144+
ObjectF: func(string, godbus.ObjectPath) godbus.BusObject {
145+
return &dbus.MockObject{
146+
CallWithContextF: func(ctx context.Context, method string, flags godbus.Flags, args ...interface{}) *godbus.Call {
147+
return &godbus.Call{
148+
Err: expectedError,
149+
}
150+
},
151+
}
152+
},
153+
}
154+
155+
client, err := login1.New(connectionWithFailingObjectCall)
156+
if err != nil {
157+
t.Fatalf("Unexpected error creating client: %v", err)
158+
}
159+
160+
if err := client.Reboot(context.Background()); !errors.Is(err, expectedError) {
161+
t.Fatalf("Unexpected error rebooting: %v", err)
162+
}
163+
})
164+
}

0 commit comments

Comments
 (0)