Files
kopia/internal/blobtesting/faulty.go
Jarek Kowalski 9e5d0beccd refactoring: renamed storage.Storage to blob.Storage
This updates the terminology everywhere - blocks become blobs and
`storage.Storage` becomes `blob.Storage`.

Also introduced blob.ID which is a specialized string type, that's
different from CABS block ID.

Also renamed CLI subcommands from `kopia storage` to `kopia blob`.

While at it introduced `block.ErrBlockNotFound` and
`object.ErrObjectNotFound` that do not leak from lower layers.
2019-06-01 14:10:35 -07:00

116 lines
3.0 KiB
Go

package blobtesting
import (
"context"
"sync"
"time"
"github.com/kopia/kopia/internal/repologging"
"github.com/kopia/kopia/repo/blob"
)
var log = repologging.Logger("faulty-storage")
// Fault describes the behavior of a single fault.
type Fault struct {
Repeat int // how many times to repeat this fault
Sleep time.Duration // sleep before returning
ErrCallback func() error
WaitFor chan struct{} // waits until the given channel is closed before returning
Err error // error to return (can be nil in combination with Sleep and WaitFor)
}
// FaultyStorage implements fault injection for Storage.
type FaultyStorage struct {
Base blob.Storage
Faults map[string][]*Fault
mu sync.Mutex
}
// GetBlob implements blob.Storage
func (s *FaultyStorage) GetBlob(ctx context.Context, id blob.ID, offset, length int64) ([]byte, error) {
if err := s.getNextFault("GetBlob", id, offset, length); err != nil {
return nil, err
}
return s.Base.GetBlob(ctx, id, offset, length)
}
// PutBlob implements blob.Storage
func (s *FaultyStorage) PutBlob(ctx context.Context, id blob.ID, data []byte) error {
if err := s.getNextFault("PutBlob", id, len(data)); err != nil {
return err
}
return s.Base.PutBlob(ctx, id, data)
}
// DeleteBlob implements blob.Storage
func (s *FaultyStorage) DeleteBlob(ctx context.Context, id blob.ID) error {
if err := s.getNextFault("DeleteBlob", id); err != nil {
return err
}
return s.Base.DeleteBlob(ctx, id)
}
// ListBlobs implements blob.Storage
func (s *FaultyStorage) ListBlobs(ctx context.Context, prefix blob.ID, callback func(blob.Metadata) error) error {
if err := s.getNextFault("ListBlobs", prefix); err != nil {
return err
}
return s.Base.ListBlobs(ctx, prefix, func(bm blob.Metadata) error {
if err := s.getNextFault("ListBlobsItem", prefix); err != nil {
return err
}
return callback(bm)
})
}
// Close implements blob.Storage
func (s *FaultyStorage) Close(ctx context.Context) error {
if err := s.getNextFault("Close"); err != nil {
return err
}
return s.Base.Close(ctx)
}
// ConnectionInfo implements blob.Storage
func (s *FaultyStorage) ConnectionInfo() blob.ConnectionInfo {
return s.Base.ConnectionInfo()
}
func (s *FaultyStorage) getNextFault(method string, args ...interface{}) error {
s.mu.Lock()
faults := s.Faults[method]
if len(faults) == 0 {
s.mu.Unlock()
log.Debugf("no faults for %v %v", method, args)
return nil
}
f := faults[0]
if f.Repeat > 0 {
f.Repeat--
log.Debugf("will repeat %v more times the fault for %v %v", f.Repeat, method, args)
} else {
s.Faults[method] = faults[1:]
}
s.mu.Unlock()
if f.WaitFor != nil {
log.Debugf("waiting for channel to be closed in %v %v", method, args)
<-f.WaitFor
}
if f.Sleep > 0 {
log.Debugf("sleeping for %v in %v %v", f.Sleep, method, args)
}
if f.ErrCallback != nil {
err := f.ErrCallback()
log.Debugf("returning %v for %v %v", err, method, args)
return err
}
log.Debugf("returning %v for %v %v", f.Err, method, args)
return f.Err
}
var _ blob.Storage = (*FaultyStorage)(nil)