go.temporal.io/server@v1.23.0/common/archiver/filestore/history_archiver.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 // Filestore History Archiver will archive workflow histories to local disk. 26 27 // Each Archive() request results in a file named in the format of 28 // hash(namespaceID, workflowID, runID)_version.history being created in the specified 29 // directory. Workflow histories stored in that file are encoded in JSON format. 30 31 // The Get() method retrieves the archived histories from the directory specified in the 32 // URI. It optionally takes in a NextPageToken which specifies the workflow close failover 33 // version and the index of the first history batch that should be returned. Instead of 34 // NextPageToken, caller can also provide a close failover version, in which case, Get() method 35 // will return history batches starting from the beginning of that history version. If neither 36 // of NextPageToken or close failover version is specified, the highest close failover version 37 // will be picked. 38 39 package filestore 40 41 import ( 42 "context" 43 "errors" 44 "os" 45 "path" 46 "strconv" 47 48 historypb "go.temporal.io/api/history/v1" 49 "go.temporal.io/api/serviceerror" 50 51 "go.temporal.io/server/common" 52 "go.temporal.io/server/common/archiver" 53 "go.temporal.io/server/common/codec" 54 "go.temporal.io/server/common/config" 55 "go.temporal.io/server/common/log" 56 "go.temporal.io/server/common/log/tag" 57 ) 58 59 const ( 60 // URIScheme is the scheme for the filestore implementation 61 URIScheme = "file" 62 63 errEncodeHistory = "failed to encode history batches" 64 errMakeDirectory = "failed to make directory" 65 errWriteFile = "failed to write history to file" 66 67 targetHistoryBlobSize = 2 * 1024 * 1024 // 2MB 68 ) 69 70 var ( 71 errInvalidFileMode = errors.New("invalid file mode") 72 errInvalidDirMode = errors.New("invalid directory mode") 73 ) 74 75 type ( 76 historyArchiver struct { 77 container *archiver.HistoryBootstrapContainer 78 fileMode os.FileMode 79 dirMode os.FileMode 80 81 // only set in test code 82 historyIterator archiver.HistoryIterator 83 } 84 85 getHistoryToken struct { 86 CloseFailoverVersion int64 87 NextBatchIdx int 88 } 89 ) 90 91 // NewHistoryArchiver creates a new archiver.HistoryArchiver based on filestore 92 func NewHistoryArchiver( 93 container *archiver.HistoryBootstrapContainer, 94 config *config.FilestoreArchiver, 95 ) (archiver.HistoryArchiver, error) { 96 return newHistoryArchiver(container, config, nil) 97 } 98 99 func newHistoryArchiver( 100 container *archiver.HistoryBootstrapContainer, 101 config *config.FilestoreArchiver, 102 historyIterator archiver.HistoryIterator, 103 ) (*historyArchiver, error) { 104 fileMode, err := strconv.ParseUint(config.FileMode, 0, 32) 105 if err != nil { 106 return nil, errInvalidFileMode 107 } 108 dirMode, err := strconv.ParseUint(config.DirMode, 0, 32) 109 if err != nil { 110 return nil, errInvalidDirMode 111 } 112 return &historyArchiver{ 113 container: container, 114 fileMode: os.FileMode(fileMode), 115 dirMode: os.FileMode(dirMode), 116 historyIterator: historyIterator, 117 }, nil 118 } 119 120 func (h *historyArchiver) Archive( 121 ctx context.Context, 122 URI archiver.URI, 123 request *archiver.ArchiveHistoryRequest, 124 opts ...archiver.ArchiveOption, 125 ) (err error) { 126 featureCatalog := archiver.GetFeatureCatalog(opts...) 127 defer func() { 128 if err != nil && !common.IsPersistenceTransientError(err) && featureCatalog.NonRetryableError != nil { 129 err = featureCatalog.NonRetryableError() 130 } 131 }() 132 133 logger := archiver.TagLoggerWithArchiveHistoryRequestAndURI(h.container.Logger, request, URI.String()) 134 135 if err := h.ValidateURI(URI); err != nil { 136 logger.Error(archiver.ArchiveNonRetryableErrorMsg, tag.ArchivalArchiveFailReason(archiver.ErrReasonInvalidURI), tag.Error(err)) 137 return err 138 } 139 140 if err := archiver.ValidateHistoryArchiveRequest(request); err != nil { 141 logger.Error(archiver.ArchiveNonRetryableErrorMsg, tag.ArchivalArchiveFailReason(archiver.ErrReasonInvalidArchiveRequest), tag.Error(err)) 142 return err 143 } 144 145 historyIterator := h.historyIterator 146 if historyIterator == nil { // will only be set by testing code 147 historyIterator = archiver.NewHistoryIterator(request, h.container.ExecutionManager, targetHistoryBlobSize) 148 } 149 150 var historyBatches []*historypb.History 151 for historyIterator.HasNext() { 152 historyBlob, err := historyIterator.Next(ctx) 153 if err != nil { 154 if _, isNotFound := err.(*serviceerror.NotFound); isNotFound { 155 // workflow history no longer exists, may due to duplicated archival signal 156 // this may happen even in the middle of iterating history as two archival signals 157 // can be processed concurrently. 158 logger.Info(archiver.ArchiveSkippedInfoMsg) 159 return nil 160 } 161 162 logger = log.With(logger, tag.ArchivalArchiveFailReason(archiver.ErrReasonReadHistory), tag.Error(err)) 163 if !common.IsPersistenceTransientError(err) { 164 logger.Error(archiver.ArchiveNonRetryableErrorMsg) 165 } else { 166 logger.Error(archiver.ArchiveTransientErrorMsg) 167 } 168 return err 169 } 170 171 if historyMutated(request, historyBlob.Body, historyBlob.Header.IsLast) { 172 logger.Error(archiver.ArchiveNonRetryableErrorMsg, tag.ArchivalArchiveFailReason(archiver.ErrReasonHistoryMutated)) 173 return archiver.ErrHistoryMutated 174 } 175 176 historyBatches = append(historyBatches, historyBlob.Body...) 177 } 178 179 encoder := codec.NewJSONPBEncoder() 180 encodedHistoryBatches, err := encoder.EncodeHistories(historyBatches) 181 if err != nil { 182 logger.Error(archiver.ArchiveNonRetryableErrorMsg, tag.ArchivalArchiveFailReason(errEncodeHistory), tag.Error(err)) 183 return err 184 } 185 186 dirPath := URI.Path() 187 if err = mkdirAll(dirPath, h.dirMode); err != nil { 188 logger.Error(archiver.ArchiveNonRetryableErrorMsg, tag.ArchivalArchiveFailReason(errMakeDirectory), tag.Error(err)) 189 return err 190 } 191 192 filename := constructHistoryFilename(request.NamespaceID, request.WorkflowID, request.RunID, request.CloseFailoverVersion) 193 if err := writeFile(path.Join(dirPath, filename), encodedHistoryBatches, h.fileMode); err != nil { 194 logger.Error(archiver.ArchiveNonRetryableErrorMsg, tag.ArchivalArchiveFailReason(errWriteFile), tag.Error(err)) 195 return err 196 } 197 198 return nil 199 } 200 201 func (h *historyArchiver) Get( 202 ctx context.Context, 203 URI archiver.URI, 204 request *archiver.GetHistoryRequest, 205 ) (*archiver.GetHistoryResponse, error) { 206 if err := h.ValidateURI(URI); err != nil { 207 return nil, serviceerror.NewInvalidArgument(archiver.ErrInvalidURI.Error()) 208 } 209 210 if err := archiver.ValidateGetRequest(request); err != nil { 211 return nil, serviceerror.NewInvalidArgument(archiver.ErrInvalidGetHistoryRequest.Error()) 212 } 213 214 dirPath := URI.Path() 215 exists, err := directoryExists(dirPath) 216 if err != nil { 217 return nil, serviceerror.NewInternal(err.Error()) 218 } 219 if !exists { 220 return nil, serviceerror.NewNotFound(archiver.ErrHistoryNotExist.Error()) 221 } 222 223 var token *getHistoryToken 224 if request.NextPageToken != nil { 225 token, err = deserializeGetHistoryToken(request.NextPageToken) 226 if err != nil { 227 return nil, serviceerror.NewInvalidArgument(archiver.ErrNextPageTokenCorrupted.Error()) 228 } 229 } else if request.CloseFailoverVersion != nil { 230 token = &getHistoryToken{ 231 CloseFailoverVersion: *request.CloseFailoverVersion, 232 NextBatchIdx: 0, 233 } 234 } else { 235 highestVersion, err := getHighestVersion(dirPath, request) 236 if err != nil { 237 return nil, serviceerror.NewInternal(err.Error()) 238 } 239 token = &getHistoryToken{ 240 CloseFailoverVersion: *highestVersion, 241 NextBatchIdx: 0, 242 } 243 } 244 245 filename := constructHistoryFilename(request.NamespaceID, request.WorkflowID, request.RunID, token.CloseFailoverVersion) 246 filepath := path.Join(dirPath, filename) 247 exists, err = fileExists(filepath) 248 if err != nil { 249 return nil, serviceerror.NewInternal(err.Error()) 250 } 251 if !exists { 252 return nil, serviceerror.NewNotFound(archiver.ErrHistoryNotExist.Error()) 253 } 254 255 encodedHistoryBatches, err := readFile(filepath) 256 if err != nil { 257 return nil, serviceerror.NewInternal(err.Error()) 258 } 259 260 encoder := codec.NewJSONPBEncoder() 261 historyBatches, err := encoder.DecodeHistories(encodedHistoryBatches) 262 if err != nil { 263 return nil, serviceerror.NewInternal(err.Error()) 264 } 265 historyBatches = historyBatches[token.NextBatchIdx:] 266 267 response := &archiver.GetHistoryResponse{} 268 numOfEvents := 0 269 numOfBatches := 0 270 for _, batch := range historyBatches { 271 response.HistoryBatches = append(response.HistoryBatches, batch) 272 numOfBatches++ 273 numOfEvents += len(batch.Events) 274 if numOfEvents >= request.PageSize { 275 break 276 } 277 } 278 279 if numOfBatches < len(historyBatches) { 280 token.NextBatchIdx += numOfBatches 281 nextToken, err := serializeToken(token) 282 if err != nil { 283 return nil, serviceerror.NewInternal(err.Error()) 284 } 285 response.NextPageToken = nextToken 286 } 287 288 return response, nil 289 } 290 291 func (h *historyArchiver) ValidateURI(URI archiver.URI) error { 292 if URI.Scheme() != URIScheme { 293 return archiver.ErrURISchemeMismatch 294 } 295 296 return validateDirPath(URI.Path()) 297 } 298 299 func getHighestVersion(dirPath string, request *archiver.GetHistoryRequest) (*int64, error) { 300 filenames, err := listFilesByPrefix(dirPath, constructHistoryFilenamePrefix(request.NamespaceID, request.WorkflowID, request.RunID)) 301 if err != nil { 302 return nil, err 303 } 304 305 var highestVersion *int64 306 for _, filename := range filenames { 307 version, err := extractCloseFailoverVersion(filename) 308 if err != nil { 309 continue 310 } 311 if highestVersion == nil || version > *highestVersion { 312 highestVersion = &version 313 } 314 } 315 if highestVersion == nil { 316 return nil, archiver.ErrHistoryNotExist 317 } 318 return highestVersion, nil 319 }