Skip to content

Commit 0d29d06

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 0d29d06

File tree

2 files changed

+230
-0
lines changed

2 files changed

+230
-0
lines changed

pkg/login1/login1.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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 a 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+
// Rebooter is an internal type implementing Client interface.
40+
type rebooter struct {
41+
caller Caller
42+
}
43+
44+
// Rebooter must implement Client interface.
45+
var _ Client = &rebooter{}
46+
47+
// New creates new login1 client using given D-Bus connection.
48+
func New(objector Objector) (Client, error) {
49+
if objector == nil {
50+
return nil, fmt.Errorf("no objector given")
51+
}
52+
53+
return &rebooter{
54+
caller: objector.Object(DBusDest, DBusPath),
55+
}, nil
56+
}
57+
58+
// Reboot reboots machine on which it's called.
59+
func (r *rebooter) Reboot(ctx context.Context) error {
60+
if call := r.caller.CallWithContext(ctx, DBusInterface+"."+DBusMethodNameReboot, 0, false); call.Err != nil {
61+
return fmt.Errorf("calling reboot: %w", call.Err)
62+
}
63+
64+
return nil
65+
}

pkg/login1/login1_test.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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+
//nolint:funlen // Many subtests.
67+
func Test_Rebooting(t *testing.T) {
68+
t.Parallel()
69+
70+
t.Run("calls_login1_reboot_method_on_manager_interface", func(t *testing.T) {
71+
t.Parallel()
72+
73+
rebootCalled := false
74+
75+
connectionWithContextCheck := &dbus.MockConnection{
76+
ObjectF: func(string, godbus.ObjectPath) godbus.BusObject {
77+
return &dbus.MockObject{
78+
CallWithContextF: func(ctx context.Context, method string, flags godbus.Flags, args ...interface{}) *godbus.Call {
79+
rebootCalled = true
80+
81+
expectedMethodName := "org.freedesktop.login1.Manager.Reboot"
82+
83+
if method != expectedMethodName {
84+
t.Fatalf("Expected method %q being called, got %q", expectedMethodName, method)
85+
}
86+
87+
return &godbus.Call{}
88+
},
89+
}
90+
},
91+
}
92+
93+
client, err := login1.New(connectionWithContextCheck)
94+
if err != nil {
95+
t.Fatalf("Unexpected error creating client: %v", err)
96+
}
97+
98+
if err := client.Reboot(context.Background()); err != nil {
99+
t.Fatalf("Unexpected error rebooting: %v", err)
100+
}
101+
102+
if !rebootCalled {
103+
t.Fatalf("Expected reboot method call on given D-Bus connection")
104+
}
105+
})
106+
107+
t.Run("use_given_context_for_D-Bus_call", func(t *testing.T) {
108+
t.Parallel()
109+
110+
testKey := struct{}{}
111+
expectedValue := "bar"
112+
113+
ctx := context.WithValue(context.Background(), testKey, expectedValue)
114+
115+
connectionWithContextCheck := &dbus.MockConnection{
116+
ObjectF: func(string, godbus.ObjectPath) godbus.BusObject {
117+
return &dbus.MockObject{
118+
CallWithContextF: func(ctx context.Context, method string, flags godbus.Flags, args ...interface{}) *godbus.Call {
119+
if val := ctx.Value(testKey); val != expectedValue {
120+
t.Fatalf("Got unexpected context on call")
121+
}
122+
123+
return &godbus.Call{}
124+
},
125+
}
126+
},
127+
}
128+
129+
client, err := login1.New(connectionWithContextCheck)
130+
if err != nil {
131+
t.Fatalf("Unexpected error creating client: %v", err)
132+
}
133+
134+
if err := client.Reboot(ctx); err != nil {
135+
t.Fatalf("Unexpected error rebooting: %v", err)
136+
}
137+
})
138+
139+
t.Run("returns_error_when_D-Bus_call_fails", func(t *testing.T) {
140+
t.Parallel()
141+
142+
expectedError := fmt.Errorf("reboot error")
143+
144+
connectionWithFailingObjectCall := &dbus.MockConnection{
145+
ObjectF: func(string, godbus.ObjectPath) godbus.BusObject {
146+
return &dbus.MockObject{
147+
CallWithContextF: func(ctx context.Context, method string, flags godbus.Flags, args ...interface{}) *godbus.Call {
148+
return &godbus.Call{
149+
Err: expectedError,
150+
}
151+
},
152+
}
153+
},
154+
}
155+
156+
client, err := login1.New(connectionWithFailingObjectCall)
157+
if err != nil {
158+
t.Fatalf("Unexpected error creating client: %v", err)
159+
}
160+
161+
if err := client.Reboot(context.Background()); !errors.Is(err, expectedError) {
162+
t.Fatalf("Unexpected error rebooting: %v", err)
163+
}
164+
})
165+
}

0 commit comments

Comments
 (0)