go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/appengine/coordinator/endpoints/services/archiveStream.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package services 16 17 import ( 18 "context" 19 20 "google.golang.org/grpc/codes" 21 "google.golang.org/grpc/status" 22 "google.golang.org/protobuf/types/known/emptypb" 23 24 "go.chromium.org/luci/common/clock" 25 log "go.chromium.org/luci/common/logging" 26 ds "go.chromium.org/luci/gae/service/datastore" 27 logdog "go.chromium.org/luci/logdog/api/endpoints/coordinator/services/v1" 28 "go.chromium.org/luci/logdog/appengine/coordinator" 29 ) 30 31 func (b *server) ArchiveStream(c context.Context, req *logdog.ArchiveStreamRequest) (*emptypb.Empty, error) { 32 id := coordinator.HashID(req.Id) 33 if err := id.Normalize(); err != nil { 34 return nil, status.Errorf(codes.InvalidArgument, "Invalid ID (%s): %s", id, err) 35 } 36 37 // Verify that the request is minimially valid. 38 switch { 39 case req.IndexUrl == "": 40 return nil, status.Errorf(codes.InvalidArgument, "missing required index archive URL") 41 case req.StreamUrl == "": 42 return nil, status.Errorf(codes.InvalidArgument, "missing required stream archive URL") 43 } 44 45 lst := coordinator.NewLogStreamState(c, id) 46 47 // Post the archival results to the Coordinator. 48 now := clock.Now(c).UTC() 49 var ierr error 50 err := ds.RunInTransaction(c, func(c context.Context) error { 51 ierr = nil 52 53 // Note that within this transaction, we have two return values: 54 // - Non-nil to abort the transaction. 55 // - Specific error via "ierr". 56 if err := ds.Get(c, lst); err != nil { 57 return err 58 } 59 60 switch as := lst.ArchivalState(); { 61 case as.Archived(): 62 // Return nil if the log stream is already archived (idempotent). 63 log.Warningf(c, "Log stream is already archived.") 64 return nil 65 66 // If our log stream is not in in a tasked archival state, we will reject 67 // this archive request with FailedPrecondition. 68 case as != coordinator.ArchiveTasked: 69 log.Fields{ 70 "state": as, 71 }.Errorf(c, "Log stream archival is not tasked.") 72 ierr = status.Errorf(codes.FailedPrecondition, "Log stream has not tasked an archival.") 73 return ierr 74 } 75 76 // If this request contained an error, we will record an empty archival and 77 // log a warning. 78 if req.Error != "" { 79 log.Fields{ 80 "archiveError": req.Error, 81 }.Warningf(c, "Log stream archival indicated error. Archiving empty stream.") 82 83 req.TerminalIndex = -1 84 req.LogEntryCount = 0 85 } 86 87 // Update archival information. Make sure this actually marks the stream as 88 // archived. 89 lst.Updated = now 90 lst.ArchivedTime = now 91 lst.ArchivalKey = nil // No point in wasting datastore space on this. 92 93 if lst.TerminalIndex < 0 { 94 // Also set the terminated time. 95 lst.TerminatedTime = now 96 } 97 lst.TerminalIndex = req.TerminalIndex 98 99 lst.ArchiveLogEntryCount = req.LogEntryCount 100 lst.ArchiveStreamURL = req.StreamUrl 101 lst.ArchiveStreamSize = req.StreamSize 102 lst.ArchiveIndexURL = req.IndexUrl 103 lst.ArchiveIndexSize = req.IndexSize 104 105 // Update the log stream. 106 if err := ds.Put(c, lst); err != nil { 107 log.WithError(err).Errorf(c, "Failed to update log stream.") 108 return err 109 } 110 111 return nil 112 }, nil) 113 if ierr != nil { 114 log.WithError(ierr).Errorf(c, "Failed to mark stream as archived.") 115 return nil, ierr 116 } 117 if err != nil { 118 log.WithError(err).Errorf(c, "Internal error.") 119 return nil, status.Error(codes.Internal, "internal server error") 120 } 121 122 return &emptypb.Empty{}, nil 123 }