96 lines
2.8 KiB
Go
96 lines
2.8 KiB
Go
package usecase
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/emil28092005/SciMesh/coordinator/internal/domain"
|
|
)
|
|
|
|
// UploadArtifact stores a worker's partial-result bytes and records the metadata.
|
|
type UploadArtifact struct {
|
|
tasks TaskRepository
|
|
artifacts ArtifactRepository
|
|
blobs BlobStore
|
|
clk Clock
|
|
}
|
|
|
|
func NewUploadArtifact(tasks TaskRepository, artifacts ArtifactRepository,
|
|
blobs BlobStore, clk Clock) *UploadArtifact {
|
|
return &UploadArtifact{tasks: tasks, artifacts: artifacts, blobs: blobs, clk: clk}
|
|
}
|
|
|
|
func (uc *UploadArtifact) Execute(ctx context.Context, in UploadArtifactInput) (*domain.Artifact, error) {
|
|
task, err := uc.tasks.Get(ctx, in.TaskID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Only the worker holding the current lease at this attempt may upload the
|
|
// task's output — the coordinator never trusts an ownership claim on faith.
|
|
if !task.IsLeaseHeldBy(in.WorkerID, in.Attempt, uc.clk.Now()) {
|
|
return nil, domain.ErrLeaseConflict
|
|
}
|
|
|
|
taskID := task.ID
|
|
art, err := domain.NewArtifact(task.JobID, &taskID, domain.ArtifactPartialResult,
|
|
in.Filename, in.ContentType, uc.clk.Now())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
attempt := in.Attempt
|
|
art.Attempt = &attempt
|
|
|
|
// Stream to storage first: size and checksum are measured here, by us, not
|
|
// taken from the worker. A large shard never sits in memory.
|
|
sum, size, err := uc.blobs.Put(ctx, art.StorageKey, in.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
art.SetContent(sum, size)
|
|
|
|
// The stream may take longer than the lease. Re-check after it finishes so
|
|
// an expired worker cannot leave a durable result record behind. Completion
|
|
// performs the same ownership check under its transaction.
|
|
current, err := uc.tasks.Get(ctx, in.TaskID)
|
|
if err != nil {
|
|
_ = uc.blobs.Delete(ctx, art.StorageKey)
|
|
return nil, err
|
|
}
|
|
if !current.IsLeaseHeldBy(in.WorkerID, in.Attempt, uc.clk.Now()) {
|
|
_ = uc.blobs.Delete(ctx, art.StorageKey)
|
|
return nil, domain.ErrLeaseConflict
|
|
}
|
|
|
|
// Persist the record. If that fails the blob would be an orphan, so remove it.
|
|
if err := uc.artifacts.Insert(ctx, art); err != nil {
|
|
_ = uc.blobs.Delete(ctx, art.StorageKey)
|
|
return nil, err
|
|
}
|
|
return art, nil
|
|
}
|
|
|
|
// DownloadArtifact returns an artifact's metadata together with a reader over
|
|
// its bytes. The caller must close the reader.
|
|
type DownloadArtifact struct {
|
|
artifacts ArtifactRepository
|
|
blobs BlobStore
|
|
}
|
|
|
|
func NewDownloadArtifact(artifacts ArtifactRepository, blobs BlobStore) *DownloadArtifact {
|
|
return &DownloadArtifact{artifacts: artifacts, blobs: blobs}
|
|
}
|
|
|
|
func (uc *DownloadArtifact) Execute(ctx context.Context, id uuid.UUID) (*domain.Artifact, io.ReadCloser, error) {
|
|
a, err := uc.artifacts.Get(ctx, id)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
rc, err := uc.blobs.Open(ctx, a.StorageKey)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return a, rc, nil
|
|
}
|