diff --git a/pkg/dwn/downlink.go b/pkg/dwn/downlink.go index 1f5c2104..7e4a4b11 100644 --- a/pkg/dwn/downlink.go +++ b/pkg/dwn/downlink.go @@ -292,7 +292,9 @@ func (s *DownlinkSession) eventLoop() { // Make sure to clean up the tmp file once we finish defer os.Remove(stagingFileName) defer func() { - s.msg.TimeEnd = timestamppb.New(time.Now().UTC()) + if s.msg.TimeEnd == nil { + s.msg.TimeEnd = timestamppb.New(time.Now().UTC()) + } s.span.AddEvent( "finished", trace.WithAttributes( @@ -498,16 +500,7 @@ func (s *DownlinkSession) eventLoop() { s.msg.Size = offset - s.span.AddEvent("writing metadata file") - mdData, err := proto.Marshal(s.msg) - if err != nil { - s.logger.Error("failed to encode downlink metadata", "path", metadataFilePath, "err", err) - } else { - _, err = metadataFile.Write(mdData) - if err != nil { - s.logger.Error("failed to write downlink metadata", "path", metadataFilePath, "err", err) - } - } + s.writeMetadataFile(metadataFile, metadataFilePath) s.span.AddEvent("syncing filesystem") outFile.Sync() @@ -538,6 +531,31 @@ func (s *DownlinkSession) eventLoop() { } } } + + s.msg.TimeEnd = timestamppb.New(time.Now().UTC()) + s.writeMetadataFile(metadataFile, metadataFilePath) + metadataFile.Sync() +} + +func (s *DownlinkSession) writeMetadataFile(metadataFile *os.File, metadataFilePath string) { + s.span.AddEvent("writing metadata file") + mdData, err := proto.Marshal(s.msg) + if err != nil { + s.logger.Error("failed to encode downlink metadata", "path", metadataFilePath, "err", err) + return + } + + if _, err = metadataFile.Seek(0, 0); err != nil { + s.logger.Error("failed to seek downlink metadata", "path", metadataFilePath, "err", err) + return + } + if err = metadataFile.Truncate(0); err != nil { + s.logger.Error("failed to truncate downlink metadata", "path", metadataFilePath, "err", err) + return + } + if _, err = metadataFile.Write(mdData); err != nil { + s.logger.Error("failed to write downlink metadata", "path", metadataFilePath, "err", err) + } } // Wait for file downlink to be processed diff --git a/pkg/dwn/downlink_test.go b/pkg/dwn/downlink_test.go index ce38229e..12e437c1 100644 --- a/pkg/dwn/downlink_test.go +++ b/pkg/dwn/downlink_test.go @@ -15,6 +15,7 @@ import ( "github.com/nasa/hermes/pkg/log" "github.com/nasa/hermes/pkg/pb" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/proto" ) func AssertFileContents(t *testing.T, path string, expectedContents []byte) { @@ -45,6 +46,20 @@ func AssertFileContents(t *testing.T, path string, expectedContents []byte) { assert.EqualValues(t, expectedContents, contents, "file contents don't match expected", path) } +func ReadDownlinkMetadata(t *testing.T, path string) *pb.FileDownlink { + t.Helper() + data, err := os.ReadFile(path + ".md.pb") + if !assert.NoError(t, err) { + return nil + } + + metadata := &pb.FileDownlink{} + if !assert.NoError(t, proto.Unmarshal(data, metadata)) { + return nil + } + return metadata +} + func ClearFromSlice(s []byte, i, j int) { for ; i < j; i++ { s[i] = 0 @@ -114,6 +129,13 @@ func TestCompleteSuccess(t *testing.T) { assert.Equal(t, "/root/test.dat", dl.SourcePath) assert.Equal(t, pb.FileDownlinkCompletionStatus_DOWNLINK_COMPLETED, dl.Status) AssertFileContents(t, dl.FilePath, fullFile) + + metadata := ReadDownlinkMetadata(t, dl.FilePath) + if metadata != nil { + assert.Equal(t, dl.Status, metadata.Status) + assert.NotNil(t, metadata.TimeEnd) + assert.True(t, metadata.TimeEnd.AsTime().Equal(dl.TimeEnd.AsTime())) + } } func TestMissingChunkMiddle(t *testing.T) {