test(bsr/fstest): Add new FS for testing out of space errors (#3305)
This provides a new storage.FS implementation that expands the on fstest.MemFS by allowing tests to mark the filesystem as out of space. When this happens any write/create operation will start returning an error indicating that the filesystem is out of space.
This commit is contained in:
1 parent
f7823d3f71
commit
837734b745
2 files changed
+232
No files matched your search
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr/internal/fstest"
|
||||
"github.com/hashicorp/boundary/internal/storage"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -144,6 +145,10 @@ func TestFSOpen(t *testing.T) {
|
||||
return fstest.NewLocalFS(ctx, d)
|
||||
},
|
||||
},
|
||||
{
|
||||
"LimitedSpaceFS",
|
||||
func(t *testing.T) storage.FS { return fstest.NewLimitedSpaceFS() },
|
||||
},
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -210,6 +215,10 @@ func TestContainerOpenFile(t *testing.T) {
|
||||
return fstest.NewLocalFS(ctx, d)
|
||||
},
|
||||
},
|
||||
{
|
||||
"LimitedSpaceFS",
|
||||
func(t *testing.T) storage.FS { return fstest.NewLimitedSpaceFS() },
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
@@ -320,6 +329,10 @@ func TestContainerCreate(t *testing.T) {
|
||||
return fstest.NewLocalFS(ctx, d)
|
||||
},
|
||||
},
|
||||
{
|
||||
"LimitedSpaceFS",
|
||||
func(t *testing.T) storage.FS { return fstest.NewLimitedSpaceFS() },
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
@@ -392,6 +405,10 @@ func TestMemContainerSubContainer(t *testing.T) {
|
||||
return fstest.NewLocalFS(ctx, d)
|
||||
},
|
||||
},
|
||||
{
|
||||
"LimitedSpaceFS",
|
||||
func(t *testing.T) storage.FS { return fstest.NewLimitedSpaceFS() },
|
||||
},
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -478,3 +495,65 @@ func TestMemContainerSubContainer(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutOfSpace(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
line := []byte("foo")
|
||||
|
||||
fs := fstest.NewLimitedSpaceFS()
|
||||
|
||||
// Create some containers, subcontains and files.
|
||||
r, err := fs.New(ctx, "root")
|
||||
require.NoError(t, err)
|
||||
|
||||
sub, err := r.SubContainer(ctx, "sub", storage.WithCreateFile(), storage.WithFileAccessMode(storage.ReadWrite))
|
||||
require.NoError(t, err)
|
||||
|
||||
f1, err := r.Create(ctx, "f1")
|
||||
require.NoError(t, err)
|
||||
c, err := f1.Write(line)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, c, len(line))
|
||||
|
||||
f2, err := sub.Create(ctx, "f2")
|
||||
require.NoError(t, err)
|
||||
c, err = f2.Write(line)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, c, len(line))
|
||||
|
||||
// Now mark out of space
|
||||
fs.SetOutOfSpace(true)
|
||||
|
||||
// eixsing files can no longer write
|
||||
c, err = f1.Write(line)
|
||||
require.ErrorIs(t, err, fstest.ErrOutOfSpace)
|
||||
assert.Equal(t, 0, c)
|
||||
|
||||
c, err = f2.Write(line)
|
||||
require.ErrorIs(t, err, fstest.ErrOutOfSpace)
|
||||
assert.Equal(t, 0, c)
|
||||
|
||||
// existing containers cannot create new files
|
||||
f3, err := r.Create(ctx, "f3")
|
||||
require.ErrorIs(t, err, fstest.ErrOutOfSpace)
|
||||
assert.Nil(t, f3)
|
||||
|
||||
f4, err := sub.Create(ctx, "f4")
|
||||
require.ErrorIs(t, err, fstest.ErrOutOfSpace)
|
||||
assert.Nil(t, f4)
|
||||
|
||||
// existing containers cannot create sub containers
|
||||
sub2, err := r.SubContainer(ctx, "sub2", storage.WithCreateFile(), storage.WithFileAccessMode(storage.ReadWrite))
|
||||
require.ErrorIs(t, err, fstest.ErrOutOfSpace)
|
||||
assert.Nil(t, sub2)
|
||||
|
||||
sub3, err := sub.SubContainer(ctx, "sub3", storage.WithCreateFile(), storage.WithFileAccessMode(storage.ReadWrite))
|
||||
require.ErrorIs(t, err, fstest.ErrOutOfSpace)
|
||||
assert.Nil(t, sub3)
|
||||
|
||||
// fs cannot make a new container
|
||||
r2, err := fs.New(ctx, "root2")
|
||||
require.ErrorIs(t, err, fstest.ErrOutOfSpace)
|
||||
assert.Nil(t, r2)
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Package fstest provides test implementations of the fs interfaces.
|
||||
package fstest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
sfs "io/fs"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/storage"
|
||||
)
|
||||
|
||||
// ErrOutOfSpace is used when the FS is out of space.
|
||||
var ErrOutOfSpace = errors.New("no space left on device")
|
||||
|
||||
// LimitedSpaceFS is a test FS that can simulate running out of disk space.
|
||||
type LimitedSpaceFS struct {
|
||||
*MemFS
|
||||
|
||||
outOfSpace bool
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// SetOutOfSpace is a helper to mark the file system as being out of space.
|
||||
func (l *LimitedSpaceFS) SetOutOfSpace(b bool) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
l.outOfSpace = b
|
||||
}
|
||||
|
||||
// OutOfSpace is used to check if the filesystem is out of space in a concurrent safe way.
|
||||
func (l *LimitedSpaceFS) OutOfSpace() bool {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.outOfSpace
|
||||
}
|
||||
|
||||
// NewLimitedSpaceFS creates a LimitedSpaceFS. It supports WithNewFunc, WithReadOnly.
|
||||
func NewLimitedSpaceFS(options ...Option) *LimitedSpaceFS {
|
||||
return &LimitedSpaceFS{
|
||||
MemFS: NewMemFS(options...),
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a storage.Container in the LimitedSpaceFS.
|
||||
func (l *LimitedSpaceFS) New(ctx context.Context, n string) (storage.Container, error) {
|
||||
if l.OutOfSpace() {
|
||||
return nil, ErrOutOfSpace
|
||||
}
|
||||
c, err := l.MemFS.New(ctx, n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc := c.(*MemContainer)
|
||||
return &LimitedSpaceContainer{
|
||||
MemContainer: cc,
|
||||
fs: l,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open opens an existing a storage.Container from the LimitedSpaceFS.
|
||||
func (l *LimitedSpaceFS) Open(ctx context.Context, n string) (storage.Container, error) {
|
||||
return l.MemFS.Open(ctx, n)
|
||||
}
|
||||
|
||||
// LimitedSpaceContainer is a storage.Container that resides in memory.
|
||||
type LimitedSpaceContainer struct {
|
||||
*MemContainer
|
||||
|
||||
fs *LimitedSpaceFS
|
||||
}
|
||||
|
||||
// Close closes the container.
|
||||
func (l *LimitedSpaceContainer) Close() error {
|
||||
return l.MemContainer.Close()
|
||||
}
|
||||
|
||||
// Create makes a new storage.File in the container.
|
||||
func (l *LimitedSpaceContainer) Create(ctx context.Context, n string) (storage.File, error) {
|
||||
if l.fs.OutOfSpace() {
|
||||
return nil, ErrOutOfSpace
|
||||
}
|
||||
f, err := l.MemContainer.Create(ctx, n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ff := f.(*MemFile)
|
||||
return &LimitedSpaceFile{
|
||||
MemFile: ff,
|
||||
fs: l.fs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// OpenFile creates a storage.File in the container using the provided options
|
||||
// It supports WithCloseSyncMode.
|
||||
func (l *LimitedSpaceContainer) OpenFile(ctx context.Context, n string, option ...storage.Option) (storage.File, error) {
|
||||
return l.MemContainer.OpenFile(ctx, n, option...)
|
||||
}
|
||||
|
||||
// SubContainer creates a new storage.Container in the container.
|
||||
func (l *LimitedSpaceContainer) SubContainer(ctx context.Context, n string, option ...storage.Option) (storage.Container, error) {
|
||||
if l.fs.OutOfSpace() {
|
||||
return nil, ErrOutOfSpace
|
||||
}
|
||||
c, err := l.MemContainer.SubContainer(ctx, n, option...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc := c.(*MemContainer)
|
||||
return &LimitedSpaceContainer{
|
||||
MemContainer: cc,
|
||||
fs: l.fs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LimitedSpaceFile is a storage.File that resides in memory.
|
||||
type LimitedSpaceFile struct {
|
||||
*MemFile
|
||||
|
||||
fs *LimitedSpaceFS
|
||||
}
|
||||
|
||||
// Stat returns the FileInfo for the file.
|
||||
func (l *LimitedSpaceFile) Stat() (sfs.FileInfo, error) {
|
||||
return l.MemFile.Stat()
|
||||
}
|
||||
|
||||
func (l *LimitedSpaceFile) Read(p []byte) (int, error) {
|
||||
return l.MemFile.Read(p)
|
||||
}
|
||||
|
||||
// Close closes the file.
|
||||
func (l *LimitedSpaceFile) Close() error {
|
||||
return l.MemFile.Close()
|
||||
}
|
||||
|
||||
// WriteString implements io.StringWriter.
|
||||
func (l *LimitedSpaceFile) WriteString(s string) (n int, err error) {
|
||||
return l.Write([]byte(s))
|
||||
}
|
||||
|
||||
func (l *LimitedSpaceFile) Write(p []byte) (n int, err error) {
|
||||
if l.fs.OutOfSpace() {
|
||||
return 0, fmt.Errorf("%s %w", l.MemFile.name, ErrOutOfSpace)
|
||||
}
|
||||
return l.MemFile.Write(p)
|
||||
}
|
||||
Reference in new issue
Block a user