Skip to content

Commit 03287b3

Browse files
committed
many: refactor fdstore for easier test mocking
Signed-off-by: Zeyad Gouda <zeyad.gouda@canonical.com>
1 parent 1799020 commit 03287b3

6 files changed

Lines changed: 158 additions & 97 deletions

File tree

netutil/activation.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ func GetListener(socketPath string, listenerMap map[string]net.Listener) (net.Li
7373
// ActivationListeners builds a map of addresses to listeners that were passed
7474
// during systemd activation
7575
func ActivationListeners() (listenerByAddr map[string]net.Listener, err error) {
76-
listeners, err := fdstore.ActivationListeners()
76+
fds := fdstore.New()
77+
listeners, err := fds.ActivationListeners()
7778
if err != nil {
7879
return nil, err
7980
}

overlord/fdestate/backend/export_test.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,8 @@
2020
package backend
2121

2222
import (
23-
"os"
24-
2523
"github.com/snapcore/snapd/boot"
2624
"github.com/snapcore/snapd/secboot"
27-
"github.com/snapcore/snapd/systemd/fdstore"
2825
"github.com/snapcore/snapd/testutil"
2926
)
3027

@@ -66,14 +63,6 @@ func MockSecbootPCRPolicyCounterHandles(f func(uk secboot.UpdatedKeys) []uint32)
6663
}
6764
}
6865

69-
func MockFdstoreAdd(f func(name fdstore.FdName, f *os.File) error) (restore func()) {
70-
return testutil.Mock(&fdstoreAdd, f)
71-
}
72-
73-
func MockFdstoreGet(f func(name fdstore.FdName) (*os.File, error)) (restore func()) {
74-
return testutil.Mock(&fdstoreGet, f)
75-
}
76-
7766
func MockUnixMmap(f func(fd int, offset int64, length int, prot int, flags int) ([]byte, error)) (restore func()) {
7867
return testutil.Mock(&unixMmap, f)
7968
}

overlord/fdestate/backend/secret_state.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"golang.org/x/sys/unix"
3232

3333
"github.com/snapcore/snapd/logger"
34+
"github.com/snapcore/snapd/osutil"
3435
"github.com/snapcore/snapd/overlord/state"
3536
"github.com/snapcore/snapd/strutil"
3637
"github.com/snapcore/snapd/systemd/fdstore"
@@ -43,9 +44,7 @@ var (
4344
)
4445

4546
var (
46-
fdstoreAdd = fdstore.Add
47-
fdstoreGet = fdstore.Get
48-
47+
fdstoreNew = fdstore.New
4948
unixMemfdSecret = unix.MemfdSecret
5049
unixMemfdCreate = unix.MemfdCreate
5150
unixMmap = unix.Mmap
@@ -282,7 +281,9 @@ func (s *secretState) closeLocked() error {
282281
}
283282

284283
func openSecretStateFile() (*os.File, error) {
285-
f, err := fdstoreGet(fdstore.FdNameMemfdSecretState)
284+
fds := fdstoreNew()
285+
286+
f, err := fds.Get(fdstore.FdNameMemfdSecretState)
286287
if errors.Is(err, fdstore.ErrNotFound) || errors.Is(err, fdstore.ErrUnsupportedSystemdVersion) {
287288
fdstoreSupported := !errors.Is(err, fdstore.ErrUnsupportedSystemdVersion)
288289
fd, err := unixMemfdSecret(0)
@@ -309,7 +310,7 @@ func openSecretStateFile() (*os.File, error) {
309310
// version is too old, we will just use the memfd without adding
310311
// it to the fdstore, persistence across snapd restarts will be
311312
// lost but it is better than crashing.
312-
if err := fdstoreAdd(fdstore.FdNameMemfdSecretState, f); err != nil {
313+
if err := fds.Add(fdstore.FdNameMemfdSecretState, f); err != nil {
313314
return nil, fmt.Errorf("cannot add secret state to fdstore: %w", err)
314315
}
315316
} else {
@@ -396,3 +397,12 @@ func OpenSecretState(stateChecker stateLockChecker) (retState SecretState, retEr
396397
runtime.SetFinalizer(s, (*secretState).closeLocked)
397398
return s, nil
398399
}
400+
401+
func MockFdstoreNew(f func() fdstore.Store) (restore func()) {
402+
osutil.MustBeTestBinary("fdstoreNew only can be mocked in tests")
403+
old := fdstoreNew
404+
fdstoreNew = f
405+
return func() {
406+
fdstoreNew = old
407+
}
408+
}

overlord/fdestate/backend/secret_state_test.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package backend_test
2222
import (
2323
"errors"
2424
"fmt"
25+
"net"
2526
"os"
2627

2728
"golang.org/x/sys/unix"
@@ -42,6 +43,7 @@ type secretStateSuite struct {
4243
ops []string
4344
failOn map[string]error
4445
stateLockChecker *mockStateLockChecker
46+
fdstore fdstore.Store
4547
}
4648

4749
var _ = Suite(&secretStateSuite{})
@@ -56,6 +58,33 @@ func (m *mockStateLockChecker) EnsureLocked() {
5658
}
5759
}
5860

61+
type mockFdstore struct {
62+
get func(name fdstore.FdName) (*os.File, error)
63+
add func(name fdstore.FdName, f *os.File) error
64+
}
65+
66+
func (m *mockFdstore) Add(name fdstore.FdName, f *os.File) error {
67+
if m.add == nil {
68+
return errors.New("mockFdstore.Add() not implemented")
69+
}
70+
return m.add(name, f)
71+
}
72+
73+
func (m *mockFdstore) Get(name fdstore.FdName) (*os.File, error) {
74+
if m.get == nil {
75+
return nil, errors.New("mockFdstore.Get() not implemented")
76+
}
77+
return m.get(name)
78+
}
79+
80+
func (m *mockFdstore) Remove(name fdstore.FdName) error {
81+
return errors.New("mockFdstore.Remove() not implemented")
82+
}
83+
84+
func (m *mockFdstore) ActivationListeners() ([]net.Listener, error) {
85+
return nil, errors.New("mockFdstore.ActivationListeners() not implemented")
86+
}
87+
5988
func dupFile(name fdstore.FdName, f *os.File) (*os.File, error) {
6089
duplicatedFd, err := unix.Dup(int(f.Fd()))
6190
if err != nil {
@@ -148,8 +177,12 @@ func (s *secretStateSuite) SetUpTest(c *C) {
148177
s.failOn = make(map[string]error)
149178
s.stateLockChecker = &mockStateLockChecker{}
150179

151-
s.AddCleanup(backend.MockFdstoreGet(s.fdstoreGet))
152-
s.AddCleanup(backend.MockFdstoreAdd(s.fdstoreAdd))
180+
s.AddCleanup(backend.MockFdstoreNew(func() fdstore.Store {
181+
return &mockFdstore{
182+
get: s.fdstoreGet,
183+
add: s.fdstoreAdd,
184+
}
185+
}))
153186
s.AddCleanup(backend.MockUnixMmap(s.mmap))
154187
s.AddCleanup(backend.MockUnixMunmap(s.munmap))
155188
s.AddCleanup(backend.MockUnixMemfdSecret(s.memfdSecret))

systemd/fdstore/fdstore.go

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,43 @@ func (name FdName) isSocket() bool {
5656
return strings.HasSuffix(string(name), ".socket")
5757
}
5858

59+
type Store interface {
60+
// Add passes a file descriptor to systemd associated with a name
61+
// to reuse it across snapd restarts.
62+
//
63+
// - The file descriptors can be retrieved by calling Get().
64+
// - Only a single file descriptor can associated with a FdName.
65+
//
66+
// Maintains a copy of the underlying file descriptor internally. It
67+
// is the caller's responsibility to close f when finished.
68+
Add(name FdName, f *os.File) error
69+
// Get retrieves a duplicate of the file descriptor passed from systemd by
70+
// its name. close-on-exec is set on the returned file descriptor. An error
71+
// matching ErrNotFound is returned if no matching file descriptor is found.
72+
// Passed name cannot be a socket (i.e. cannot end in ".socket"), for
73+
// activation sockets use ActivationListeners() instead.
74+
//
75+
// The fdstore holds a copy of the file descriptor, the caller needs to
76+
// call Remove() on top of closing all privately held references in order
77+
// to release all resources associated with a given fd.
78+
Get(name FdName) (*os.File, error)
79+
// Remove removes file descriptors from systemd given their name.
80+
// Remove cannot remove activation sockets.
81+
Remove(name FdName) error
82+
// ActivationListeners returns activation listeners that were passed
83+
// from systemd. Only sockets whose name has a ".socket" suffix are
84+
// returned. Order of returned listeners is not deterministic.
85+
//
86+
// It is the caller's responsibility to close returned listeners when finished.
87+
ActivationListeners() ([]net.Listener, error)
88+
}
89+
90+
type store struct{}
91+
92+
func New() Store {
93+
return &store{}
94+
}
95+
5996
var (
6097
osGetpid = os.Getpid
6198
osFileClose = (*os.File).Close
@@ -76,7 +113,7 @@ var mu sync.RWMutex
76113
// passed from systemd.
77114
const sd_LISTEN_FDS_START = 3
78115

79-
func initFdstore() {
116+
func (s *store) initFdstore() {
80117
mu.Lock()
81118
defer mu.Unlock()
82119

@@ -149,7 +186,7 @@ func initFdstore() {
149186
}
150187
if shouldRemove {
151188
logger.Noticef("removing unexpected fdstore entry %q", name)
152-
if err := remove(name); err != nil {
189+
if err := s.remove(name); err != nil {
153190
logger.Noticef("internal error: cannot remove fdstore entry %q: %v", name, err)
154191
continue
155192
}
@@ -172,10 +209,8 @@ func checkSystemdVersion() error {
172209
return nil
173210
}
174211

175-
// Remove removes file descriptors from systemd given their name.
176-
// Remove cannot remove activation sockets.
177-
func Remove(name FdName) error {
178-
initFdstore()
212+
func (s *store) Remove(name FdName) error {
213+
s.initFdstore()
179214

180215
if err := checkSystemdVersion(); err != nil {
181216
return fmt.Errorf("cannot remove file descriptor from fdstore: %w", err)
@@ -194,13 +229,13 @@ func Remove(name FdName) error {
194229
return fmt.Errorf("cannot remove file descriptor from fdstore: %w", ErrNotFound)
195230
}
196231

197-
return remove(name)
232+
return s.remove(name)
198233
}
199234

200235
// remove file descriptors from systemd given their name.
201236
//
202237
// Caller must hold the fdstore lock.
203-
func remove(name FdName) error {
238+
func (s *store) remove(name FdName) error {
204239
state := fmt.Sprintf("FDSTOREREMOVE=1\nFDNAME=%s", name)
205240
if err := sdNotify(state); err != nil {
206241
return err
@@ -226,17 +261,8 @@ func duplicateFile(name FdName, f *os.File) (*os.File, error) {
226261
return os.NewFile(uintptr(duplicatedFd), string(name)), nil
227262
}
228263

229-
// Get retrieves a duplicate of the file descriptor passed from systemd by
230-
// its name. close-on-exec is set on the returned file descriptor. An error
231-
// matching ErrNotFound is returned if no matching file descriptor is found.
232-
// Passed name cannot be a socket (i.e. cannot end in ".socket"), for
233-
// activation sockets use ActivationListeners() instead.
234-
//
235-
// The fdstore holds a copy of the file descriptor, the caller needs to
236-
// call Remove() on top of closing all privately held references in order
237-
// to release all resources associated with a given fd.
238-
func Get(name FdName) (*os.File, error) {
239-
initFdstore()
264+
func (s *store) Get(name FdName) (*os.File, error) {
265+
s.initFdstore()
240266

241267
if err := checkSystemdVersion(); err != nil {
242268
return nil, fmt.Errorf("cannot get file descriptor from fdstore: %w", err)
@@ -261,16 +287,8 @@ func Get(name FdName) (*os.File, error) {
261287
return duplicateFile(name, fds[0])
262288
}
263289

264-
// Add passes a file descriptor to systemd associated with a name
265-
// to reuse it across snapd restarts.
266-
//
267-
// - The file descriptors can be retrieved by calling Get().
268-
// - Only a single file descriptor can associated with a FdName.
269-
//
270-
// Maintains a copy of the underlying file descriptor internally. It
271-
// is the caller's responsibility to close f when finished.
272-
func Add(name FdName, f *os.File) error {
273-
initFdstore()
290+
func (s *store) Add(name FdName, f *os.File) error {
291+
s.initFdstore()
274292

275293
if err := checkSystemdVersion(); err != nil {
276294
return fmt.Errorf("cannot add file descriptor to fdstore: %w", err)
@@ -307,13 +325,8 @@ func Add(name FdName, f *os.File) error {
307325
return nil
308326
}
309327

310-
// ActivationListeners returns activation listeners that were passed
311-
// from systemd. Only sockets whose name has a ".socket" suffix are
312-
// returned. Order of returned listeners is not deterministic.
313-
//
314-
// It is the caller's responsibility to close returned listeners when finished.
315-
func ActivationListeners() (retListeners []net.Listener, retErr error) {
316-
initFdstore()
328+
func (s *store) ActivationListeners() (retListeners []net.Listener, retErr error) {
329+
s.initFdstore()
317330

318331
mu.RLock()
319332
defer mu.RUnlock()

0 commit comments

Comments
 (0)