Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions service/contentprovider/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,17 +211,16 @@ func GetMetadataHandler(c echo.Context, db *gorm.DB) error {
return c.String(http.StatusBadRequest, "failed to parse piece CID: "+err.Error())
}

// filter to rows with an attachment -- orphaned cars (attachment_id NULL
// after ON DELETE SET NULL from a prior prep) share piece_cid but have
// no metadata to serve.
var car model.Car
ctx := c.Request().Context()
err = db.WithContext(ctx).Where("piece_cid = ?", model.CID(pieceCid)).First(&car).Error
err = db.WithContext(ctx).Where("piece_cid = ? AND attachment_id IS NOT NULL", model.CID(pieceCid)).First(&car).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return c.String(http.StatusNotFound, "piece not found")
}

if car.AttachmentID == nil {
return c.String(http.StatusNotFound, "piece metadata not found")
}

metadata, err := getPieceMetadata(ctx, db, car)
if err != nil {
return c.String(http.StatusInternalServerError, "Error: "+err.Error())
Expand Down
65 changes: 65 additions & 0 deletions service/contentprovider/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,71 @@ func TestHTTPServerHandler(t *testing.T) {
})
}

// orphan car (attachment_id NULL from ON DELETE SET NULL) coexists with a
// valid car for the same piece_cid. metadata must pick the valid one.
t.Run("orphan_car_skipped", func(t *testing.T) {
orphanPieceCID := cid.NewCidV1(cid.FilCommitmentUnsealed, util.Hash([]byte("orphan_test")))

// orphan car created first (lower PK), no associations
err := db.Create(&model.Car{
PieceCID: model.CID(orphanPieceCID),
PieceSize: 128,
PieceType: model.DataPiece,
RootCID: model.CID(testutil.TestCid),
}).Error
require.NoError(t, err)

// valid car with attachment, created second
prep := &model.Preparation{Name: "orphan_prep"}
require.NoError(t, db.Create(prep).Error)
storage := &model.Storage{Name: "orphan_storage", Type: "local"}
require.NoError(t, db.Create(storage).Error)
attachment := &model.SourceAttachment{
PreparationID: prep.ID,
StorageID: storage.ID,
}
require.NoError(t, db.Create(attachment).Error)
require.NoError(t, db.Create(&model.Car{
PieceCID: model.CID(orphanPieceCID),
PieceSize: 128,
PreparationID: &prep.ID,
AttachmentID: &attachment.ID,
PieceType: model.DataPiece,
RootCID: model.CID(testutil.TestCid),
}).Error)

req := httptest.NewRequest(http.MethodGet, "/piece/metadata/:id", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetPath("/piece/metadata/:id")
c.SetParamNames("id")
c.SetParamValues(orphanPieceCID.String())
err = s.getMetadataHandler(c)
require.NoError(t, err)
require.Equal(t, http.StatusOK, rec.Code)
})

// only an orphan exists -- 404
t.Run("orphan_only_returns_404", func(t *testing.T) {
onlyOrphanCID := cid.NewCidV1(cid.FilCommitmentUnsealed, util.Hash([]byte("only_orphan")))
require.NoError(t, db.Create(&model.Car{
PieceCID: model.CID(onlyOrphanCID),
PieceSize: 128,
PieceType: model.DataPiece,
RootCID: model.CID(testutil.TestCid),
}).Error)

req := httptest.NewRequest(http.MethodGet, "/piece/metadata/:id", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetPath("/piece/metadata/:id")
c.SetParamNames("id")
c.SetParamValues(onlyOrphanCID.String())
err := s.getMetadataHandler(c)
require.NoError(t, err)
require.Equal(t, http.StatusNotFound, rec.Code)
})

// Test DAG piece type
t.Run("dag_piece_metadata", func(t *testing.T) {
preparation := &model.Preparation{Name: "test_prep_dag"}
Expand Down
Loading