go.temporal.io/server@v1.23.0/common/archiver/filestore/util.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 package filestore 26 27 import ( 28 "encoding/json" 29 "errors" 30 "fmt" 31 "os" 32 "strconv" 33 "strings" 34 "time" 35 36 "github.com/dgryski/go-farm" 37 historypb "go.temporal.io/api/history/v1" 38 "go.uber.org/multierr" 39 "google.golang.org/protobuf/proto" 40 41 archiverspb "go.temporal.io/server/api/archiver/v1" 42 "go.temporal.io/server/common/archiver" 43 "go.temporal.io/server/common/codec" 44 ) 45 46 var ( 47 errDirectoryExpected = errors.New("a path to a directory was expected") 48 errFileExpected = errors.New("a path to a file was expected") 49 errEmptyDirectoryPath = errors.New("directory path is empty") 50 ) 51 52 // File I/O util 53 54 func fileExists(filepath string) (bool, error) { 55 if info, err := os.Stat(filepath); err != nil { 56 if os.IsNotExist(err) { 57 return false, nil 58 } 59 return false, err 60 } else if info.IsDir() { 61 return false, errFileExpected 62 } 63 return true, nil 64 } 65 66 func directoryExists(path string) (bool, error) { 67 if info, err := os.Stat(path); err != nil { 68 if os.IsNotExist(err) { 69 return false, nil 70 } 71 return false, err 72 } else if !info.IsDir() { 73 return false, errDirectoryExpected 74 } 75 return true, nil 76 } 77 78 func mkdirAll(path string, dirMode os.FileMode) error { 79 return os.MkdirAll(path, dirMode) 80 } 81 82 func writeFile(filepath string, data []byte, fileMode os.FileMode) (retErr error) { 83 if err := os.Remove(filepath); err != nil && !os.IsNotExist(err) { 84 return err 85 } 86 f, err := os.Create(filepath) 87 defer func() { 88 err := f.Close() 89 if err != nil { 90 retErr = err 91 } 92 }() 93 if err != nil { 94 return err 95 } 96 if err = f.Chmod(fileMode); err != nil { 97 return err 98 } 99 if _, err = f.Write(data); err != nil { 100 return err 101 } 102 return nil 103 } 104 105 // readFile reads the contents of a file specified by filepath 106 // WARNING: callers of this method should be extremely careful not to use it in a context where filepath is supplied by 107 // the user. 108 func readFile(filepath string) ([]byte, error) { 109 // #nosec 110 return os.ReadFile(filepath) 111 } 112 113 func listFiles(dirPath string) (fileNames []string, err error) { 114 if info, err := os.Stat(dirPath); err != nil { 115 return nil, err 116 } else if !info.IsDir() { 117 return nil, errDirectoryExpected 118 } 119 120 f, err := os.Open(dirPath) 121 if err != nil { 122 return nil, err 123 } 124 defer func() { 125 err = multierr.Combine(err, f.Close()) 126 }() 127 return f.Readdirnames(-1) 128 } 129 130 func listFilesByPrefix(dirPath string, prefix string) ([]string, error) { 131 fileNames, err := listFiles(dirPath) 132 if err != nil { 133 return nil, err 134 } 135 136 var filteredFileNames []string 137 for _, name := range fileNames { 138 if strings.HasPrefix(name, prefix) { 139 filteredFileNames = append(filteredFileNames, name) 140 } 141 } 142 return filteredFileNames, nil 143 } 144 145 // encoding & decoding util 146 147 func encode(message proto.Message) ([]byte, error) { 148 encoder := codec.NewJSONPBEncoder() 149 return encoder.Encode(message) 150 } 151 152 func encodeHistories(histories []*historypb.History) ([]byte, error) { 153 encoder := codec.NewJSONPBEncoder() 154 return encoder.EncodeHistories(histories) 155 } 156 157 func decodeVisibilityRecord(data []byte) (*archiverspb.VisibilityRecord, error) { 158 record := &archiverspb.VisibilityRecord{} 159 encoder := codec.NewJSONPBEncoder() 160 err := encoder.Decode(data, record) 161 if err != nil { 162 return nil, err 163 } 164 return record, nil 165 } 166 167 func serializeToken(token interface{}) ([]byte, error) { 168 if token == nil { 169 return nil, nil 170 } 171 return json.Marshal(token) 172 } 173 174 func deserializeGetHistoryToken(bytes []byte) (*getHistoryToken, error) { 175 token := &getHistoryToken{} 176 err := json.Unmarshal(bytes, token) 177 return token, err 178 } 179 180 func deserializeQueryVisibilityToken(bytes []byte) (*queryVisibilityToken, error) { 181 token := &queryVisibilityToken{} 182 err := json.Unmarshal(bytes, token) 183 return token, err 184 } 185 186 // File name construction 187 188 func constructHistoryFilename(namespaceID, workflowID, runID string, version int64) string { 189 combinedHash := constructHistoryFilenamePrefix(namespaceID, workflowID, runID) 190 return fmt.Sprintf("%s_%v.history", combinedHash, version) 191 } 192 193 func constructHistoryFilenamePrefix(namespaceID, workflowID, runID string) string { 194 return strings.Join([]string{hash(namespaceID), hash(workflowID), hash(runID)}, "") 195 } 196 197 func constructVisibilityFilename(closeTimestamp time.Time, runID string) string { 198 return fmt.Sprintf("%v_%s.visibility", closeTimestamp.UnixNano(), hash(runID)) 199 } 200 201 func hash(s string) string { 202 return fmt.Sprintf("%v", farm.Fingerprint64([]byte(s))) 203 } 204 205 // Validation 206 207 func validateDirPath(dirPath string) error { 208 if len(dirPath) == 0 { 209 return errEmptyDirectoryPath 210 } 211 info, err := os.Stat(dirPath) 212 if os.IsNotExist(err) { 213 return nil 214 } 215 if err != nil { 216 return err 217 } 218 if !info.IsDir() { 219 return errDirectoryExpected 220 } 221 return nil 222 } 223 224 // Misc. 225 226 func extractCloseFailoverVersion(filename string) (int64, error) { 227 filenameParts := strings.FieldsFunc(filename, func(r rune) bool { 228 return r == '_' || r == '.' 229 }) 230 if len(filenameParts) != 3 { 231 return -1, errors.New("unknown filename structure") 232 } 233 return strconv.ParseInt(filenameParts[1], 10, 64) 234 } 235 236 func historyMutated(request *archiver.ArchiveHistoryRequest, historyBatches []*historypb.History, isLast bool) bool { 237 lastBatch := historyBatches[len(historyBatches)-1].Events 238 lastEvent := lastBatch[len(lastBatch)-1] 239 lastFailoverVersion := lastEvent.GetVersion() 240 if lastFailoverVersion > request.CloseFailoverVersion { 241 return true 242 } 243 244 if !isLast { 245 return false 246 } 247 lastEventID := lastEvent.GetEventId() 248 return lastFailoverVersion != request.CloseFailoverVersion || lastEventID+1 != request.NextEventID 249 }