Skip to content

Commit

Permalink
feat: map blobstore implements filesystemer
Browse files Browse the repository at this point in the history
  • Loading branch information
alanshaw committed Oct 26, 2024
1 parent b92b292 commit a5bc7d1
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 20 deletions.
14 changes: 1 addition & 13 deletions pkg/service/blobs/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,7 @@ func NewBlobGetHandler(blobs blobstore.Blobstore) func(http.ResponseWriter, *htt
if fsblobs, ok := blobs.(blobstore.FileSystemer); ok {
serveHTTP := http.FileServer(fsblobs.FileSystem()).ServeHTTP
return func(w http.ResponseWriter, r *http.Request) {
_, bytes, err := multibase.Decode(r.PathValue("blob"))
if err != nil {
http.Error(w, fmt.Sprintf("decoding multibase encoded digest: %s", err), http.StatusBadRequest)
return
}

digest, err := multihash.Cast(bytes)
if err != nil {
http.Error(w, fmt.Sprintf("invalid multihash digest: %s", err), http.StatusBadRequest)
return
}

r.URL.Path = fsblobs.EncodePath(digest)
r.URL.Path = r.URL.Path[len("/blob"):]
serveHTTP(w, r)
}
}
Expand Down
1 change: 1 addition & 0 deletions pkg/service/blobs/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func requireRetrievableBlob(t *testing.T, endpoint url.URL, digest multihash.Mul
res, err := http.Get(bloburl.String())
require.NoError(t, err)

require.Equal(t, http.StatusOK, res.StatusCode)
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
require.Equal(t, data, body)
Expand Down
20 changes: 20 additions & 0 deletions pkg/store/blobstore/blobstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

"github.com/multiformats/go-multihash"
"github.com/storacha/storage/pkg/internal/digestutil"
"github.com/storacha/storage/pkg/internal/testutil"
"github.com/storacha/storage/pkg/store"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -59,5 +60,24 @@ func TestBlobstore(t *testing.T) {
err := s.Put(context.Background(), digest, uint64(len(data)), bytes.NewBuffer(baddata))
require.Equal(t, ErrDataInconsistent, err)
})

t.Run("filesystemer "+k, func(t *testing.T) {
data := testutil.RandomBytes(10)
digest := testutil.Must(multihash.Sum(data, multihash.SHA2_256, -1))(t)

err := s.Put(context.Background(), digest, uint64(len(data)), bytes.NewBuffer(data))
require.NoError(t, err)

fsr, ok := s.(FileSystemer)
require.True(t, ok)

f, err := fsr.FileSystem().Open(fmt.Sprintf("/%s", digestutil.Format(digest)))
require.NoError(t, err)

b, err := io.ReadAll(f)
require.NoError(t, err)

require.Equal(t, data, b)
})
}
}
25 changes: 20 additions & 5 deletions pkg/store/blobstore/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os"
"path"

"github.com/multiformats/go-multibase"
"github.com/multiformats/go-multihash"
"github.com/storacha/storage/pkg/internal/digestutil"
"github.com/storacha/storage/pkg/store"
Expand Down Expand Up @@ -71,13 +72,9 @@ type FsBlobstore struct {
tmpdir string
}

func (b *FsBlobstore) EncodePath(digest multihash.Multihash) string {
return encodePath(digest)
}

// FileSystem returns a filesystem interface for reading blobs.
func (b *FsBlobstore) FileSystem() http.FileSystem {
return http.Dir(b.rootdir)
return &fsDir{http.Dir(b.rootdir)}
}

func (b *FsBlobstore) Get(ctx context.Context, digest multihash.Multihash, opts ...GetOption) (Object, error) {
Expand Down Expand Up @@ -194,3 +191,21 @@ func NewFsBlobstore(rootdir string, tmpdir string) (*FsBlobstore, error) {
}
return &FsBlobstore{rootdir, tmpdir}, nil
}

type fsDir struct {
fs http.FileSystem
}

var _ http.FileSystem = (*fsDir)(nil)

func (d *fsDir) Open(path string) (http.File, error) {
_, bytes, err := multibase.Decode(path[1:])
if err != nil {
return nil, fs.ErrNotExist
}
digest, err := multihash.Cast(bytes)
if err != nil {
return nil, fs.ErrNotExist
}
return d.fs.Open(encodePath(digest))
}
2 changes: 0 additions & 2 deletions pkg/store/blobstore/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,4 @@ type Blobstore interface {
type FileSystemer interface {
// FileSystem returns a filesystem interface for reading blobs.
FileSystem() http.FileSystem
// EncodePath converts a digest to a filesystem path.
EncodePath(digest multihash.Multihash) string
}
59 changes: 59 additions & 0 deletions pkg/store/blobstore/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import (
"crypto/sha256"
"fmt"
"io"
"io/fs"
"net/http"
"os"
"time"

"github.com/multiformats/go-multihash"
"github.com/storacha/storage/pkg/internal/digestutil"
Expand Down Expand Up @@ -86,10 +90,65 @@ func (mb *MapBlobstore) Put(ctx context.Context, digest multihash.Multihash, siz
return nil
}

func (mb *MapBlobstore) FileSystem() http.FileSystem {
return &mapDir{mb.data}
}

var _ Blobstore = (*MapBlobstore)(nil)

// NewMapBlobstore creates a [Blobstore] backed by an in-memory map.
func NewMapBlobstore() *MapBlobstore {
data := map[string][]byte{}
return &MapBlobstore{data}
}

type mapDir struct {
data map[string][]byte
}

var _ http.FileSystem = (*mapDir)(nil)

func (d *mapDir) Open(path string) (http.File, error) {
name := path[1:]
data, ok := d.data[name]
if !ok {
return nil, fs.ErrNotExist
}
return &mapFile{
Reader: bytes.NewReader(data),
info: mapFileInfo{name, int64(len(data))},
}, nil
}

type mapFile struct {
*bytes.Reader
info fs.FileInfo
}

func (m *mapFile) Close() error {
return nil
}

func (m *mapFile) Readdir(count int) ([]fs.FileInfo, error) {
panic("unimplemented") // should not be called - there are no directories
}

func (m *mapFile) Stat() (fs.FileInfo, error) {
return m.info, nil
}

var _ http.File = (*mapFile)(nil)

type mapFileInfo struct {
name string
size int64
}

func (mfi mapFileInfo) Name() string { return mfi.name }
func (mfi mapFileInfo) Size() int64 { return mfi.size }
func (mfi mapFileInfo) Mode() os.FileMode { return 0444 }
func (mfi mapFileInfo) ModTime() time.Time { return time.Time{} }
func (mfi mapFileInfo) IsDir() bool { return false }
func (mfi mapFileInfo) Sys() interface{} { return nil }

var _ fs.FileInfo = (*mapFileInfo)(nil)

0 comments on commit a5bc7d1

Please sign in to comment.