go.temporal.io/server@v1.23.0/common/archiver/gcloud/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 gcloud 26 27 import ( 28 "encoding/json" 29 "errors" 30 "fmt" 31 "strconv" 32 "strings" 33 "time" 34 35 "github.com/dgryski/go-farm" 36 commonpb "go.temporal.io/api/common/v1" 37 workflowpb "go.temporal.io/api/workflow/v1" 38 "google.golang.org/protobuf/proto" 39 40 archiverspb "go.temporal.io/server/api/archiver/v1" 41 "go.temporal.io/server/common/archiver" 42 "go.temporal.io/server/common/archiver/gcloud/connector" 43 "go.temporal.io/server/common/codec" 44 "go.temporal.io/server/common/searchattribute" 45 ) 46 47 func encode(message proto.Message) ([]byte, error) { 48 encoder := codec.NewJSONPBEncoder() 49 return encoder.Encode(message) 50 } 51 52 func constructHistoryFilenameMultipart(namespaceID, workflowID, runID string, version int64, partNumber int) string { 53 combinedHash := constructHistoryFilenamePrefix(namespaceID, workflowID, runID) 54 return fmt.Sprintf("%s_%v_%v.history", combinedHash, version, partNumber) 55 } 56 57 func constructHistoryFilenamePrefix(namespaceID, workflowID, runID string) string { 58 return strings.Join([]string{hash(namespaceID), hash(workflowID), hash(runID)}, "") 59 } 60 61 func constructVisibilityFilenamePrefix(namespaceID, tag string) string { 62 return fmt.Sprintf("%s/%s", namespaceID, tag) 63 } 64 65 func constructTimeBasedSearchKey(namespaceID, tag string, t time.Time, precision string) string { 66 var timeFormat = "" 67 switch precision { 68 case PrecisionSecond: 69 timeFormat = ":05" 70 fallthrough 71 case PrecisionMinute: 72 timeFormat = ":04" + timeFormat 73 fallthrough 74 case PrecisionHour: 75 timeFormat = "15" + timeFormat 76 fallthrough 77 case PrecisionDay: 78 timeFormat = "2006-01-02T" + timeFormat 79 } 80 81 return fmt.Sprintf("%s_%s", constructVisibilityFilenamePrefix(namespaceID, tag), t.Format(timeFormat)) 82 } 83 84 func hash(s string) (result string) { 85 if s != "" { 86 return fmt.Sprintf("%v", farm.Fingerprint64([]byte(s))) 87 } 88 return 89 } 90 91 func deserializeGetHistoryToken(bytes []byte) (*getHistoryToken, error) { 92 token := &getHistoryToken{} 93 err := json.Unmarshal(bytes, token) 94 return token, err 95 } 96 97 func extractCloseFailoverVersion(filename string) (int64, int, error) { 98 filenameParts := strings.FieldsFunc(filename, func(r rune) bool { 99 return r == '_' || r == '.' 100 }) 101 if len(filenameParts) != 4 { 102 return -1, 0, errors.New("unknown filename structure") 103 } 104 105 failoverVersion, err := strconv.ParseInt(filenameParts[1], 10, 64) 106 if err != nil { 107 return -1, 0, err 108 } 109 110 highestPart, err := strconv.Atoi(filenameParts[2]) 111 return failoverVersion, highestPart, err 112 } 113 114 func serializeToken(token interface{}) ([]byte, error) { 115 if token == nil { 116 return nil, nil 117 } 118 return json.Marshal(token) 119 } 120 121 func decodeVisibilityRecord(data []byte) (*archiverspb.VisibilityRecord, error) { 122 record := &archiverspb.VisibilityRecord{} 123 encoder := codec.NewJSONPBEncoder() 124 err := encoder.Decode(data, record) 125 if err != nil { 126 return nil, err 127 } 128 return record, nil 129 } 130 131 func constructVisibilityFilename(namespace, workflowTypeName, workflowID, runID, tag string, t time.Time) string { 132 prefix := constructVisibilityFilenamePrefix(namespace, tag) 133 return fmt.Sprintf("%s_%s_%s_%s_%s.visibility", prefix, t.Format(time.RFC3339), hash(workflowTypeName), hash(workflowID), hash(runID)) 134 } 135 136 func deserializeQueryVisibilityToken(bytes []byte) (*queryVisibilityToken, error) { 137 token := &queryVisibilityToken{} 138 err := json.Unmarshal(bytes, token) 139 return token, err 140 } 141 142 func convertToExecutionInfo(record *archiverspb.VisibilityRecord, saTypeMap searchattribute.NameTypeMap) (*workflowpb.WorkflowExecutionInfo, error) { 143 searchAttributes, err := searchattribute.Parse(record.SearchAttributes, &saTypeMap) 144 if err != nil { 145 return nil, err 146 } 147 148 return &workflowpb.WorkflowExecutionInfo{ 149 Execution: &commonpb.WorkflowExecution{ 150 WorkflowId: record.GetWorkflowId(), 151 RunId: record.GetRunId(), 152 }, 153 Type: &commonpb.WorkflowType{ 154 Name: record.WorkflowTypeName, 155 }, 156 StartTime: record.StartTime, 157 ExecutionTime: record.ExecutionTime, 158 CloseTime: record.CloseTime, 159 Status: record.Status, 160 HistoryLength: record.HistoryLength, 161 Memo: record.Memo, 162 SearchAttributes: searchAttributes, 163 }, nil 164 } 165 166 func newRunIDPrecondition(runID string) connector.Precondition { 167 return func(subject interface{}) bool { 168 169 if runID == "" { 170 return true 171 } 172 173 fileName, ok := subject.(string) 174 if !ok { 175 return false 176 } 177 178 if strings.Contains(fileName, runID) { 179 fileNameParts := strings.Split(fileName, "_") 180 if len(fileNameParts) != 5 { 181 return true 182 } 183 return strings.Contains(fileName, fileNameParts[4]) 184 } 185 186 return false 187 } 188 } 189 190 func newWorkflowIDPrecondition(workflowID string) connector.Precondition { 191 return func(subject interface{}) bool { 192 193 if workflowID == "" { 194 return true 195 } 196 197 fileName, ok := subject.(string) 198 if !ok { 199 return false 200 } 201 202 if strings.Contains(fileName, workflowID) { 203 fileNameParts := strings.Split(fileName, "_") 204 if len(fileNameParts) != 5 { 205 return true 206 } 207 return strings.Contains(fileName, fileNameParts[3]) 208 } 209 210 return false 211 } 212 } 213 214 func newWorkflowTypeNamePrecondition(workflowTypeName string) connector.Precondition { 215 return func(subject interface{}) bool { 216 217 if workflowTypeName == "" { 218 return true 219 } 220 221 fileName, ok := subject.(string) 222 if !ok { 223 return false 224 } 225 226 if strings.Contains(fileName, workflowTypeName) { 227 fileNameParts := strings.Split(fileName, "_") 228 if len(fileNameParts) != 5 { 229 return true 230 } 231 return strings.Contains(fileName, fileNameParts[2]) 232 } 233 234 return false 235 } 236 } 237 238 func isRetryableError(err error) (retryable bool) { 239 switch err.Error() { 240 case connector.ErrBucketNotFound.Error(), 241 archiver.ErrURISchemeMismatch.Error(), 242 archiver.ErrInvalidURI.Error(): 243 retryable = false 244 default: 245 retryable = true 246 } 247 248 return 249 }