2019-01-24 20:15:10 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
2018-09-28 07:59:27 +01:00
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package filestore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-06-29 05:34:35 +01:00
|
|
|
"encoding/hex"
|
2020-07-14 14:04:38 +01:00
|
|
|
"errors"
|
2020-04-30 13:06:01 +01:00
|
|
|
"io"
|
2018-09-28 07:59:27 +01:00
|
|
|
"os"
|
2019-12-21 13:11:24 +00:00
|
|
|
"path/filepath"
|
2019-11-26 16:25:21 +00:00
|
|
|
"time"
|
2018-09-28 07:59:27 +01:00
|
|
|
|
2019-11-08 20:40:39 +00:00
|
|
|
"github.com/spacemonkeygo/monkit/v3"
|
2018-09-28 07:59:27 +01:00
|
|
|
"github.com/zeebo/errs"
|
2019-08-08 02:47:30 +01:00
|
|
|
"go.uber.org/zap"
|
2018-09-28 07:59:27 +01:00
|
|
|
|
2020-04-14 13:39:42 +01:00
|
|
|
"storj.io/common/memory"
|
2020-07-10 20:36:39 +01:00
|
|
|
"storj.io/common/storj"
|
2023-04-05 18:03:06 +01:00
|
|
|
"storj.io/storj/storagenode/blobstore"
|
2018-09-28 07:59:27 +01:00
|
|
|
)
|
|
|
|
|
2019-06-05 14:06:06 +01:00
|
|
|
var (
|
2020-08-11 15:50:01 +01:00
|
|
|
// Error is the default filestore error class.
|
2019-06-05 14:06:06 +01:00
|
|
|
Error = errs.Class("filestore error")
|
2023-01-29 21:16:19 +00:00
|
|
|
// ErrIsDir is the error returned when we encounter a directory named like a blob file
|
|
|
|
// while traversing a blob namespace.
|
|
|
|
ErrIsDir = Error.New("file is a directory")
|
2018-09-28 07:59:27 +01:00
|
|
|
|
2021-06-29 05:34:35 +01:00
|
|
|
mon = monkit.Package()
|
2023-04-05 18:03:06 +01:00
|
|
|
// for backwards compatibility.
|
|
|
|
monStorage = monkit.ScopeNamed("storj.io/storj/storage/filestore")
|
2019-06-05 14:06:06 +01:00
|
|
|
|
2023-04-05 18:03:06 +01:00
|
|
|
_ blobstore.Blobs = (*blobStore)(nil)
|
2019-06-05 14:06:06 +01:00
|
|
|
)
|
2018-09-28 07:59:27 +01:00
|
|
|
|
2021-06-29 05:34:35 +01:00
|
|
|
func monFileInTrash(namespace []byte) *monkit.Meter {
|
2023-04-05 18:03:06 +01:00
|
|
|
return monStorage.Meter("open_file_in_trash", monkit.NewSeriesTag("namespace", hex.EncodeToString(namespace))) //mon:locked
|
2021-06-29 05:34:35 +01:00
|
|
|
}
|
|
|
|
|
2020-04-14 13:39:42 +01:00
|
|
|
// Config is configuration for the blob store.
|
|
|
|
type Config struct {
|
|
|
|
WriteBufferSize memory.Size `help:"in-memory buffer for uploads" default:"128KiB"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// DefaultConfig is the default value for Config.
|
|
|
|
var DefaultConfig = Config{
|
|
|
|
WriteBufferSize: 128 * memory.KiB,
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// blobStore implements a blob store.
|
2019-11-13 19:15:31 +00:00
|
|
|
type blobStore struct {
|
2020-04-14 13:39:42 +01:00
|
|
|
log *zap.Logger
|
|
|
|
dir *Dir
|
|
|
|
config Config
|
2018-09-28 07:59:27 +01:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// New creates a new disk blob store in the specified directory.
|
2023-04-05 18:03:06 +01:00
|
|
|
func New(log *zap.Logger, dir *Dir, config Config) blobstore.Blobs {
|
2020-04-14 13:39:42 +01:00
|
|
|
return &blobStore{dir: dir, log: log, config: config}
|
2018-09-28 07:59:27 +01:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// NewAt creates a new disk blob store in the specified directory.
|
2023-04-05 18:03:06 +01:00
|
|
|
func NewAt(log *zap.Logger, path string, config Config) (blobstore.Blobs, error) {
|
2020-05-22 19:28:26 +01:00
|
|
|
dir, err := NewDir(log, path)
|
2018-09-28 07:59:27 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
2020-04-14 13:39:42 +01:00
|
|
|
return &blobStore{dir: dir, log: log, config: config}, nil
|
2018-09-28 07:59:27 +01:00
|
|
|
}
|
|
|
|
|
2019-03-18 10:55:06 +00:00
|
|
|
// Close closes the store.
|
2019-11-13 19:15:31 +00:00
|
|
|
func (store *blobStore) Close() error { return nil }
|
2019-03-18 10:55:06 +00:00
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// Open loads blob with the specified hash.
|
2023-04-05 18:03:06 +01:00
|
|
|
func (store *blobStore) Open(ctx context.Context, ref blobstore.BlobRef) (_ blobstore.BlobReader, err error) {
|
2019-06-05 14:06:06 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-08-08 02:47:30 +01:00
|
|
|
file, formatVer, err := store.dir.Open(ctx, ref)
|
2019-06-03 10:17:09 +01:00
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil, err
|
2018-09-28 07:59:27 +01:00
|
|
|
}
|
2019-06-03 10:17:09 +01:00
|
|
|
return nil, Error.Wrap(err)
|
2018-09-28 07:59:27 +01:00
|
|
|
}
|
2019-08-08 02:47:30 +01:00
|
|
|
return newBlobReader(file, formatVer), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// OpenWithStorageFormat loads the already-located blob, avoiding the potential need to check multiple
|
|
|
|
// storage formats to find the blob.
|
2023-04-05 18:03:06 +01:00
|
|
|
func (store *blobStore) OpenWithStorageFormat(ctx context.Context, blobRef blobstore.BlobRef, formatVer blobstore.FormatVersion) (_ blobstore.BlobReader, err error) {
|
2019-08-08 02:47:30 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
file, err := store.dir.OpenWithStorageFormat(ctx, blobRef, formatVer)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
return newBlobReader(file, formatVer), nil
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// Stat looks up disk metadata on the blob file.
|
2023-04-05 18:03:06 +01:00
|
|
|
func (store *blobStore) Stat(ctx context.Context, ref blobstore.BlobRef) (_ blobstore.BlobInfo, err error) {
|
2019-08-08 02:47:30 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
info, err := store.dir.Stat(ctx, ref)
|
|
|
|
return info, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// StatWithStorageFormat looks up disk metadata on the blob file with the given storage format version.
|
2023-04-05 18:03:06 +01:00
|
|
|
func (store *blobStore) StatWithStorageFormat(ctx context.Context, ref blobstore.BlobRef, formatVer blobstore.FormatVersion) (_ blobstore.BlobInfo, err error) {
|
2019-08-08 02:47:30 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
info, err := store.dir.StatWithStorageFormat(ctx, ref, formatVer)
|
|
|
|
return info, Error.Wrap(err)
|
2018-09-28 07:59:27 +01:00
|
|
|
}
|
|
|
|
|
2019-12-02 11:18:20 +00:00
|
|
|
// Delete deletes blobs with the specified ref.
|
|
|
|
//
|
|
|
|
// It doesn't return an error if the blob isn't found for any reason or it cannot
|
|
|
|
// be deleted at this moment and it's delayed.
|
2023-04-05 18:03:06 +01:00
|
|
|
func (store *blobStore) Delete(ctx context.Context, ref blobstore.BlobRef) (err error) {
|
2019-06-05 14:06:06 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
err = store.dir.Delete(ctx, ref)
|
2019-03-11 08:06:56 +00:00
|
|
|
return Error.Wrap(err)
|
2018-09-28 07:59:27 +01:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// DeleteWithStorageFormat deletes blobs with the specified ref and storage format version.
|
2023-04-05 18:03:06 +01:00
|
|
|
func (store *blobStore) DeleteWithStorageFormat(ctx context.Context, ref blobstore.BlobRef, formatVer blobstore.FormatVersion) (err error) {
|
2019-11-04 16:59:45 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
err = store.dir.DeleteWithStorageFormat(ctx, ref, formatVer)
|
|
|
|
return Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2020-07-08 11:50:40 +01:00
|
|
|
// DeleteNamespace deletes blobs folder of specific satellite, used after successful GE only.
|
|
|
|
func (store *blobStore) DeleteNamespace(ctx context.Context, ref []byte) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
err = store.dir.DeleteNamespace(ctx, ref)
|
|
|
|
return Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// Trash moves the ref to a trash directory.
|
2023-04-05 18:03:06 +01:00
|
|
|
func (store *blobStore) Trash(ctx context.Context, ref blobstore.BlobRef) (err error) {
|
2019-11-14 22:19:15 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-12-21 13:11:24 +00:00
|
|
|
return Error.Wrap(store.dir.Trash(ctx, ref))
|
2019-11-14 22:19:15 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// RestoreTrash moves every piece in the trash back into the regular location.
|
2019-12-21 13:11:24 +00:00
|
|
|
func (store *blobStore) RestoreTrash(ctx context.Context, namespace []byte) (keysRestored [][]byte, err error) {
|
2019-11-14 22:19:15 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-12-21 13:11:24 +00:00
|
|
|
keysRestored, err = store.dir.RestoreTrash(ctx, namespace)
|
|
|
|
return keysRestored, Error.Wrap(err)
|
2019-11-14 22:19:15 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// // EmptyTrash removes all files in trash that have been there longer than trashExpiryDur.
|
2019-12-21 13:11:24 +00:00
|
|
|
func (store *blobStore) EmptyTrash(ctx context.Context, namespace []byte, trashedBefore time.Time) (bytesEmptied int64, keys [][]byte, err error) {
|
2019-11-26 16:25:21 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-12-21 13:11:24 +00:00
|
|
|
bytesEmptied, keys, err = store.dir.EmptyTrash(ctx, namespace, trashedBefore)
|
|
|
|
return bytesEmptied, keys, Error.Wrap(err)
|
2019-11-26 16:25:21 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// GarbageCollect tries to delete any files that haven't yet been deleted.
|
2019-11-13 19:15:31 +00:00
|
|
|
func (store *blobStore) GarbageCollect(ctx context.Context) (err error) {
|
2019-06-05 14:06:06 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
err = store.dir.GarbageCollect(ctx)
|
2019-03-11 08:06:56 +00:00
|
|
|
return Error.Wrap(err)
|
2018-09-28 07:59:27 +01:00
|
|
|
}
|
|
|
|
|
2020-07-16 16:27:24 +01:00
|
|
|
// Create creates a new blob that can be written.
|
|
|
|
// Optionally takes a size argument for performance improvements, -1 is unknown size.
|
2023-04-05 18:03:06 +01:00
|
|
|
func (store *blobStore) Create(ctx context.Context, ref blobstore.BlobRef, size int64) (_ blobstore.BlobWriter, err error) {
|
2019-06-05 14:06:06 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
file, err := store.dir.CreateTemporaryFile(ctx, size)
|
2018-09-28 07:59:27 +01:00
|
|
|
if err != nil {
|
2019-03-11 08:06:56 +00:00
|
|
|
return nil, Error.Wrap(err)
|
2018-09-28 07:59:27 +01:00
|
|
|
}
|
2020-04-14 13:39:42 +01:00
|
|
|
return newBlobWriter(ref, store, MaxFormatVersionSupported, file, store.config.WriteBufferSize.Int()), nil
|
2019-08-08 02:47:30 +01:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// SpaceUsedForBlobs adds up the space used in all namespaces for blob storage.
|
2019-12-21 13:11:24 +00:00
|
|
|
func (store *blobStore) SpaceUsedForBlobs(ctx context.Context) (space int64, err error) {
|
2019-08-08 02:47:30 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
var totalSpaceUsed int64
|
|
|
|
namespaces, err := store.ListNamespaces(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return 0, Error.New("failed to enumerate namespaces: %v", err)
|
|
|
|
}
|
|
|
|
for _, namespace := range namespaces {
|
2019-12-21 13:11:24 +00:00
|
|
|
used, err := store.SpaceUsedForBlobsInNamespace(ctx, namespace)
|
2019-08-08 02:47:30 +01:00
|
|
|
if err != nil {
|
|
|
|
return 0, Error.New("failed to sum space used: %v", err)
|
|
|
|
}
|
|
|
|
totalSpaceUsed += used
|
|
|
|
}
|
|
|
|
return totalSpaceUsed, nil
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// SpaceUsedForBlobsInNamespace adds up how much is used in the given namespace for blob storage.
|
2019-12-21 13:11:24 +00:00
|
|
|
func (store *blobStore) SpaceUsedForBlobsInNamespace(ctx context.Context, namespace []byte) (int64, error) {
|
2019-08-08 02:47:30 +01:00
|
|
|
var totalUsed int64
|
2023-04-05 18:03:06 +01:00
|
|
|
err := store.WalkNamespace(ctx, namespace, func(info blobstore.BlobInfo) error {
|
2019-08-08 02:47:30 +01:00
|
|
|
statInfo, statErr := info.Stat(ctx)
|
|
|
|
if statErr != nil {
|
|
|
|
store.log.Error("failed to stat blob", zap.Binary("namespace", namespace), zap.Binary("key", info.BlobRef().Key), zap.Error(statErr))
|
|
|
|
// keep iterating; we want a best effort total here.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
totalUsed += statInfo.Size()
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return totalUsed, nil
|
2018-09-28 07:59:27 +01:00
|
|
|
}
|
2019-03-18 10:55:06 +00:00
|
|
|
|
2020-04-30 13:06:01 +01:00
|
|
|
// TrashIsEmpty returns boolean value if trash dir is empty.
|
|
|
|
func (store *blobStore) TrashIsEmpty() (_ bool, err error) {
|
|
|
|
f, err := os.Open(store.dir.trashdir())
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
err = errs.Combine(err, f.Close())
|
|
|
|
}()
|
|
|
|
|
|
|
|
_, err = f.Readdirnames(1)
|
2020-07-14 14:04:38 +01:00
|
|
|
if errors.Is(err, io.EOF) {
|
2020-04-30 13:06:01 +01:00
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// SpaceUsedForTrash returns the total space used by the trash.
|
2019-12-21 13:11:24 +00:00
|
|
|
func (store *blobStore) SpaceUsedForTrash(ctx context.Context) (total int64, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2020-04-30 13:06:01 +01:00
|
|
|
|
|
|
|
empty, err := store.TrashIsEmpty()
|
|
|
|
if err != nil {
|
|
|
|
return total, err
|
|
|
|
}
|
|
|
|
if empty {
|
|
|
|
return 0, nil
|
|
|
|
}
|
2020-04-14 13:39:42 +01:00
|
|
|
err = filepath.Walk(store.dir.trashdir(), func(_ string, info os.FileInfo, walkErr error) error {
|
2019-12-21 13:11:24 +00:00
|
|
|
if walkErr != nil {
|
|
|
|
err = errs.Combine(err, walkErr)
|
|
|
|
return filepath.SkipDir
|
|
|
|
}
|
2020-04-30 13:06:01 +01:00
|
|
|
|
2019-12-21 13:11:24 +00:00
|
|
|
total += info.Size()
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return total, err
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// FreeSpace returns how much space left in underlying directory.
|
2021-09-10 14:05:29 +01:00
|
|
|
func (store *blobStore) FreeSpace(ctx context.Context) (int64, error) {
|
|
|
|
info, err := store.dir.Info(ctx)
|
2019-03-18 10:55:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return info.AvailableSpace, nil
|
|
|
|
}
|
2019-08-08 02:47:30 +01:00
|
|
|
|
2020-08-07 17:19:37 +01:00
|
|
|
// CheckWritability tests writability of the storage directory by creating and deleting a file.
|
2021-09-10 14:05:29 +01:00
|
|
|
func (store *blobStore) CheckWritability(ctx context.Context) error {
|
2022-10-31 15:12:17 +00:00
|
|
|
f, err := os.CreateTemp(store.dir.Path(), "write-test")
|
2020-08-07 17:19:37 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := f.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return os.Remove(f.Name())
|
|
|
|
}
|
|
|
|
|
2019-08-08 02:47:30 +01:00
|
|
|
// ListNamespaces finds all known namespace IDs in use in local storage. They are not
|
|
|
|
// guaranteed to contain any blobs.
|
2019-11-13 19:15:31 +00:00
|
|
|
func (store *blobStore) ListNamespaces(ctx context.Context) (ids [][]byte, err error) {
|
2019-08-08 02:47:30 +01:00
|
|
|
return store.dir.ListNamespaces(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// WalkNamespace executes walkFunc for each locally stored blob in the given namespace. If walkFunc
|
|
|
|
// returns a non-nil error, WalkNamespace will stop iterating and return the error immediately. The
|
|
|
|
// ctx parameter is intended specifically to allow canceling iteration early.
|
2023-04-05 18:03:06 +01:00
|
|
|
func (store *blobStore) WalkNamespace(ctx context.Context, namespace []byte, walkFunc func(blobstore.BlobInfo) error) (err error) {
|
2019-08-08 02:47:30 +01:00
|
|
|
return store.dir.WalkNamespace(ctx, namespace, walkFunc)
|
|
|
|
}
|
|
|
|
|
2019-11-13 19:15:31 +00:00
|
|
|
// TestCreateV0 creates a new V0 blob that can be written. This is ONLY appropriate in test situations.
|
2023-04-05 18:03:06 +01:00
|
|
|
func (store *blobStore) TestCreateV0(ctx context.Context, ref blobstore.BlobRef) (_ blobstore.BlobWriter, err error) {
|
2019-08-08 02:47:30 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-08-12 22:43:05 +01:00
|
|
|
file, err := store.dir.CreateTemporaryFile(ctx, -1)
|
2019-08-08 02:47:30 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
2020-04-14 13:39:42 +01:00
|
|
|
return newBlobWriter(ref, store, FormatV0, file, store.config.WriteBufferSize.Int()), nil
|
2019-08-08 02:47:30 +01:00
|
|
|
}
|
2020-07-10 20:36:39 +01:00
|
|
|
|
|
|
|
// CreateVerificationFile creates a file to be used for storage directory verification.
|
2021-09-10 14:05:29 +01:00
|
|
|
func (store *blobStore) CreateVerificationFile(ctx context.Context, id storj.NodeID) error {
|
|
|
|
return store.dir.CreateVerificationFile(ctx, id)
|
2020-07-10 20:36:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// VerifyStorageDir verifies that the storage directory is correct by checking for the existence and validity
|
|
|
|
// of the verification file.
|
2021-09-10 14:05:29 +01:00
|
|
|
func (store *blobStore) VerifyStorageDir(ctx context.Context, id storj.NodeID) error {
|
|
|
|
return store.dir.Verify(ctx, id)
|
2020-07-10 20:36:39 +01:00
|
|
|
}
|