Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>
  • Loading branch information
invidian committed Dec 2, 2021
1 parent 7463b41 commit 0b61c70
Show file tree
Hide file tree
Showing 16 changed files with 841 additions and 953 deletions.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ go 1.17

require (
github.com/blang/semver/v4 v4.0.0
github.com/coreos/go-systemd/v22 v22.3.2
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f
github.com/flatcar-linux/locksmith v0.6.2
github.com/godbus/dbus/v5 v5.0.6
Expand Down
3 changes: 0 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand Down Expand Up @@ -73,7 +71,6 @@ github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
Expand Down
22 changes: 17 additions & 5 deletions pkg/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"sync"
"time"

"github.com/coreos/go-systemd/v22/login1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -27,7 +26,9 @@ import (
"k8s.io/klog/v2"

"github.com/flatcar-linux/flatcar-linux-update-operator/pkg/constants"
"github.com/flatcar-linux/flatcar-linux-update-operator/pkg/dbus"
"github.com/flatcar-linux/flatcar-linux-update-operator/pkg/k8sutil"
"github.com/flatcar-linux/flatcar-linux-update-operator/pkg/login1"
"github.com/flatcar-linux/flatcar-linux-update-operator/pkg/updateengine"
)

Expand All @@ -37,7 +38,7 @@ type Klocksmith struct {
kc kubernetes.Interface
nc corev1client.NodeInterface
ue updateengine.Client
lc *login1.Conn
lc Rebooter
reapTimeout time.Duration
}

Expand All @@ -55,6 +56,10 @@ var shouldRebootSelector = fields.Set(map[string]string{
constants.AnnotationRebootNeeded: constants.True,
}).AsSelector()

type Rebooter interface {
Reboot(ctx context.Context) error
}

// New returns initialized Klocksmith.
func New(node string, reapTimeout time.Duration) (*Klocksmith, error) {
// Set up kubernetes in-cluster client.
Expand All @@ -66,14 +71,19 @@ func New(node string, reapTimeout time.Duration) (*Klocksmith, error) {
// Node interface.
nc := kc.CoreV1().Nodes()

dbusClient, err := dbus.New(dbus.SystemPrivateConnector)
if err != nil {
return nil, fmt.Errorf("creating D-Bus client: %w", err)
}

// Set up update_engine client.
updateEngineClient, err := updateengine.New(updateengine.DBusSystemPrivateConnector)
updateEngineClient, err := updateengine.New(dbusClient)
if err != nil {
return nil, fmt.Errorf("establishing connection to update_engine dbus: %w", err)
}

// Set up login1 client for our eventual reboot.
lc, err := login1.New()
lc, err := login1.New(dbusClient)
if err != nil {
return nil, fmt.Errorf("establishing connection to logind dbus: %w", err)
}
Expand Down Expand Up @@ -267,7 +277,9 @@ func (k *Klocksmith) process(stop <-chan struct{}) error {
klog.Info("Node drained, rebooting")

// Reboot.
k.lc.Reboot(false)
if err := k.lc.Reboot(context.TODO()); err != nil {
return fmt.Errorf("rebooting: %w", err)
}

// Cross fingers.
sleepOrDone(24*7*time.Hour, stop)
Expand Down
64 changes: 64 additions & 0 deletions pkg/dbus/conn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Package dbus provides a helper function for creating new D-Bus client.
package dbus

import (
"fmt"
"os"
"strconv"

godbus "github.com/godbus/dbus/v5"
)

// Client is an interface describing capabilities of internal D-Bus client.
type Client interface {
AddMatchSignal(matchOptions ...godbus.MatchOption) error
Signal(ch chan<- *godbus.Signal)
Object(dest string, path godbus.ObjectPath) godbus.BusObject
Close() error
}

// Connection is an interface describing how much functionality we need from object providing D-Bus connection.
type Connection interface {
Auth(authMethods []godbus.Auth) error
Hello() error

Client
}

// Connector is a constructor function providing D-Bus connection.
type Connector func() (Connection, error)

// SystemPrivateConnector is a standard connector using system bus.
func SystemPrivateConnector() (Connection, error) {
return godbus.SystemBusPrivate()
}

// New creates new D-Bus client using given connector.
func New(connector Connector) (Client, error) {
if connector == nil {
return nil, fmt.Errorf("no connection creator given")
}

conn, err := connector()
if err != nil {
return nil, fmt.Errorf("connecting to D-Bus: %w", err)
}

methods := []godbus.Auth{godbus.AuthExternal(strconv.Itoa(os.Getuid()))}

if err := conn.Auth(methods); err != nil {
// Best effort closing the connection.
_ = conn.Close()

return nil, fmt.Errorf("authenticating to D-Bus: %w", err)
}

if err := conn.Hello(); err != nil {
// Best effort closing the connection.
_ = conn.Close()

return nil, fmt.Errorf("sending hello to D-Bus: %w", err)
}

return conn, nil
}
30 changes: 30 additions & 0 deletions pkg/dbus/conn_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//go:build integration
// +build integration

package dbus_test

import (
"fmt"
"os"
"testing"

"github.com/flatcar-linux/flatcar-linux-update-operator/pkg/dbus"
)

const (
testDbusSocketEnv = "FLUO_TEST_DBUS_SOCKET"
)

//nolint:paralleltest // This test use environment variables.
func Test_System_private_connector_successfully_connects_to_running_system_bus(t *testing.T) {
t.Setenv("DBUS_SYSTEM_BUS_ADDRESS", fmt.Sprintf("unix:path=%s", os.Getenv(testDbusSocketEnv)))

client, err := dbus.New(dbus.SystemPrivateConnector)
if err != nil {
t.Fatalf("Failed creating client: %v", err)
}

if client == nil {
t.Fatalf("Expected not nil client when new succeeds")
}
}
189 changes: 189 additions & 0 deletions pkg/dbus/conn_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package dbus_test

import (
"encoding/hex"
"errors"
"fmt"
"os"
"strconv"
"testing"

godbus "github.com/godbus/dbus/v5"

"github.com/flatcar-linux/flatcar-linux-update-operator/pkg/dbus"
)

func Test_Creating_client_authenticates_using_user_id(t *testing.T) {
t.Parallel()

authCheckingConnection := &mockConnection{
authF: func(authMethods []godbus.Auth) error {
uidAuthFound := false

for i, method := range authMethods {
_, data, _ := method.FirstData()

decodedData, err := hex.DecodeString(string(data))
if err != nil {
t.Fatalf("Received auth method %d has bad hex data %v: %v", i, data, err)
}

potentialUID, err := strconv.Atoi(string(decodedData))
if err != nil {
t.Logf("Data %q couldn't be converted to UID: %v", string(decodedData), err)
}

if potentialUID == os.Getuid() {
uidAuthFound = true
}
}

if !uidAuthFound {
t.Fatalf("Expected auth method with user id")
}

return nil
},
}

client, err := dbus.New(func() (dbus.Connection, error) { return authCheckingConnection, nil })
if err != nil {
t.Fatalf("Unexpected error creating client: %v", err)
}

if client == nil {
t.Fatalf("When new succeeds, returned client should not be nil")
}
}

//nolint:funlen // Just many subtests.
func Test_Creating_client_returns_error_when(t *testing.T) {
t.Parallel()

t.Run("no_connector_is_given", func(t *testing.T) {
t.Parallel()

testNewError(t, nil, nil)
})

t.Run("connecting_to_D-Bus_socket_fails", func(t *testing.T) {
t.Parallel()

expectedErr := fmt.Errorf("connection error")

failingConnectionConnector := func() (dbus.Connection, error) { return nil, expectedErr }

testNewError(t, failingConnectionConnector, expectedErr)
})

t.Run("authenticating_to_D-Bus_fails", func(t *testing.T) {
t.Parallel()

expectedErr := fmt.Errorf("auth error")

closeCalled := false

failingAuthConnection := &mockConnection{
authF: func([]godbus.Auth) error {
return expectedErr
},
closeF: func() error {
closeCalled = true

return fmt.Errorf("closing error")
},
}

testNewError(t, func() (dbus.Connection, error) { return failingAuthConnection, nil }, expectedErr)

t.Run("and_tries_to_close_the_client_while_ignoring_closing_error", func(t *testing.T) {
if !closeCalled {
t.Fatalf("Expected close function to be called")
}
})
})

t.Run("sending_hello_to_D-Bus_fails", func(t *testing.T) {
t.Parallel()

expectedErr := fmt.Errorf("hello error")

closeCalled := false

failingHelloConnection := &mockConnection{
helloF: func() error {
return expectedErr
},
closeF: func() error {
closeCalled = true

return fmt.Errorf("closing error")
},
}

testNewError(t, func() (dbus.Connection, error) { return failingHelloConnection, nil }, expectedErr)

t.Run("and_tries_to_close_the_client_while_ignoring_closing_error", func(t *testing.T) {
if !closeCalled {
t.Fatalf("Expected close function to be called")
}
})
})
}

func testNewError(t *testing.T, connector dbus.Connector, expectedErr error) {
t.Helper()

client, err := dbus.New(connector)
if err == nil {
t.Fatalf("Expected error creating client")
}

if client != nil {
t.Fatalf("Client should not be returned when creation error occurs")
}

if expectedErr != nil && !errors.Is(err, expectedErr) {
t.Fatalf("Unexpected error occurred, expected %q, got %q", expectedErr, err)
}
}

type mockConnection struct {
authF func(authMethods []godbus.Auth) error
helloF func() error
closeF func() error
}

func (m *mockConnection) Auth(authMethods []godbus.Auth) error {
if m.authF == nil {
return nil
}

return m.authF(authMethods)
}

func (m *mockConnection) Hello() error {
if m.helloF == nil {
return nil
}

return m.helloF()
}

func (m *mockConnection) AddMatchSignal(matchOptions ...godbus.MatchOption) error {
return nil
}

func (m *mockConnection) Signal(ch chan<- *godbus.Signal) {}

func (m *mockConnection) Object(dest string, path godbus.ObjectPath) godbus.BusObject {
return nil
}

func (m *mockConnection) Close() error {
if m.closeF == nil {
return nil
}

return m.closeF()
}
Loading

0 comments on commit 0b61c70

Please sign in to comment.