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:
Timothy Messier authored and GitHub committed 2023-06-09 16:32:24 -04:00
1 parent f7823d3f71
commit 837734b745
2 files changed
+232

No files matched your search

+79
View File
@@ -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)
}
+153
View File
@@ -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)
}