diff --git a/pkg/api/bytes_test.go b/pkg/api/bytes_test.go index 576e263983d..e7ed6877308 100644 --- a/pkg/api/bytes_test.go +++ b/pkg/api/bytes_test.go @@ -20,6 +20,7 @@ import ( "github.com/ethersphere/bee/v2/pkg/log" mockbatchstore "github.com/ethersphere/bee/v2/pkg/postage/batchstore/mock" mockpost "github.com/ethersphere/bee/v2/pkg/postage/mock" + "github.com/ethersphere/bee/v2/pkg/storage/inmemchunkstore" mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" "github.com/ethersphere/bee/v2/pkg/swarm" "gitlab.com/nolash/go-mockbytes" @@ -622,3 +623,16 @@ func TestBytesHeadErrorsMatchGet(t *testing.T) { }) } } + +// TestBytesGetHandlerNoPeers verifies that a retrieval failure caused by having no available peers +func TestBytesGetHandlerNoPeers(t *testing.T) { + t.Parallel() + + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: mockstorer.NewWithChunkStore(noPeersChunkStore{inmemchunkstore.New()}), + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + ref := swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000001") + jsonhttptest.Request(t, client, http.MethodGet, "/bytes/"+ref.String(), http.StatusServiceUnavailable) +} diff --git a/pkg/api/bzz.go b/pkg/api/bzz.go index 6ffef43fa0b..0164a438301 100644 --- a/pkg/api/bzz.go +++ b/pkg/api/bzz.go @@ -696,6 +696,8 @@ FETCH: } jsonhttp.NotFound(w, "path address not found") + } else if errors.Is(err, topology.ErrNotFound) { + jsonhttp.ServiceUnavailable(w, "content could not be retrieved because no usable peer was available") } else { jsonhttp.NotFound(w, nil) } @@ -768,12 +770,18 @@ func (s *Service) downloadHandler(logger log.Logger, w http.ResponseWriter, r *h reader, l, err = joiner.New(ctx, s.storer.Download(cache), s.storer.Cache(), reference, rLevel) } if err != nil { - if errors.Is(err, storage.ErrNotFound) || errors.Is(err, topology.ErrNotFound) { + if errors.Is(err, storage.ErrNotFound) { logger.Debug("api download: not found ", "address", reference, "error", err) logger.Error(nil, err.Error()) jsonhttp.NotFound(w, nil) return } + if errors.Is(err, topology.ErrNotFound) { + logger.Debug("api download: no peers available", "address", reference, "error", err) + logger.Error(nil, err.Error()) + jsonhttp.ServiceUnavailable(w, "content could not be retrieved because no usable peer was available") + return + } logger.Debug("api download: unexpected error", "address", reference, "error", err) logger.Error(nil, "api download: unexpected error") jsonhttp.InternalServerError(w, "joiner failed") diff --git a/pkg/api/chunk.go b/pkg/api/chunk.go index 80583731aac..57e513b8d1c 100644 --- a/pkg/api/chunk.go +++ b/pkg/api/chunk.go @@ -22,6 +22,7 @@ import ( "github.com/ethersphere/bee/v2/pkg/postage" "github.com/ethersphere/bee/v2/pkg/storage" "github.com/ethersphere/bee/v2/pkg/swarm" + "github.com/ethersphere/bee/v2/pkg/topology" "github.com/gorilla/mux" ) @@ -256,15 +257,18 @@ func (s *Service) chunkGetHandler(w http.ResponseWriter, r *http.Request) { chunk, err := s.storer.Download(cache).Get(r.Context(), address) if err != nil { - if errors.Is(err, storage.ErrNotFound) { + switch { + case errors.Is(err, storage.ErrNotFound): loggerV1.Debug("chunk not found", "address", address) jsonhttp.NotFound(w, "chunk not found") - return - + case errors.Is(err, topology.ErrNotFound): + loggerV1.Debug("no peers available to retrieve chunk", "address", address) + jsonhttp.ServiceUnavailable(w, "chunk could not be retrieved because no usable peer was available") + default: + logger.Debug("read chunk failed", "chunk_address", address, "error", err) + logger.Error(nil, "read chunk failed") + jsonhttp.InternalServerError(w, "read chunk failed") } - logger.Debug("read chunk failed", "chunk_address", address, "error", err) - logger.Error(nil, "read chunk failed") - jsonhttp.InternalServerError(w, "read chunk failed") return } w.Header().Set(ContentTypeHeader, "binary/octet-stream") diff --git a/pkg/api/chunk_test.go b/pkg/api/chunk_test.go index b2512aa2ae8..2393e9cfb90 100644 --- a/pkg/api/chunk_test.go +++ b/pkg/api/chunk_test.go @@ -20,7 +20,9 @@ import ( mockbatchstore "github.com/ethersphere/bee/v2/pkg/postage/batchstore/mock" mockpost "github.com/ethersphere/bee/v2/pkg/postage/mock" "github.com/ethersphere/bee/v2/pkg/spinlock" + "github.com/ethersphere/bee/v2/pkg/storage/inmemchunkstore" mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" + "github.com/ethersphere/bee/v2/pkg/topology" "github.com/ethersphere/bee/v2/pkg/api" "github.com/ethersphere/bee/v2/pkg/jsonhttp" @@ -30,6 +32,17 @@ import ( "github.com/ethersphere/bee/v2/pkg/swarm" ) +// noPeersChunkStore simulates a netstore whose network retrieval failed +// because no peers were available, as opposed to the chunk genuinely +// not existing anywhere. +type noPeersChunkStore struct { + *inmemchunkstore.ChunkStore +} + +func (noPeersChunkStore) Get(context.Context, swarm.Address) (swarm.Chunk, error) { + return nil, topology.ErrNotFound +} + // nolint:paralleltest,tparallel // TestChunkUploadDownload uploads a chunk to an API that verifies the chunk according // to a given validator, then tries to download the uploaded data. @@ -138,6 +151,21 @@ func TestChunkHasHandler(t *testing.T) { }) } +// nolint:paralleltest,tparallel +func TestChunkGetHandlerNoPeers(t *testing.T) { + storerMock := mockstorer.NewWithChunkStore(noPeersChunkStore{inmemchunkstore.New()}) + testServer, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + }) + + jsonhttptest.Request(t, testServer, http.MethodGet, "/chunks/"+swarm.MustParseHexAddress("aabbcc").String(), http.StatusServiceUnavailable, + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "chunk could not be retrieved because no usable peer was available", + Code: http.StatusServiceUnavailable, + }), + ) +} + func TestChunkHandlersInvalidInputs(t *testing.T) { t.Parallel()