go.temporal.io/server@v1.23.0/common/archiver/s3store/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 s3store 26 27 import ( 28 "bytes" 29 "context" 30 "encoding/json" 31 "fmt" 32 "io" 33 "strings" 34 "time" 35 36 "github.com/aws/aws-sdk-go/aws" 37 "github.com/aws/aws-sdk-go/aws/awserr" 38 "github.com/aws/aws-sdk-go/service/s3" 39 "github.com/aws/aws-sdk-go/service/s3/s3iface" 40 commonpb "go.temporal.io/api/common/v1" 41 historypb "go.temporal.io/api/history/v1" 42 "go.temporal.io/api/serviceerror" 43 workflowpb "go.temporal.io/api/workflow/v1" 44 "go.uber.org/multierr" 45 "google.golang.org/protobuf/proto" 46 47 archiverspb "go.temporal.io/server/api/archiver/v1" 48 "go.temporal.io/server/common/archiver" 49 "go.temporal.io/server/common/codec" 50 "go.temporal.io/server/common/searchattribute" 51 ) 52 53 // encoding & decoding util 54 55 func Encode(message proto.Message) ([]byte, error) { 56 encoder := codec.NewJSONPBEncoder() 57 return encoder.Encode(message) 58 } 59 60 func decodeVisibilityRecord(data []byte) (*archiverspb.VisibilityRecord, error) { 61 record := &archiverspb.VisibilityRecord{} 62 encoder := codec.NewJSONPBEncoder() 63 err := encoder.Decode(data, record) 64 if err != nil { 65 return nil, err 66 } 67 return record, nil 68 } 69 70 func SerializeToken(token interface{}) ([]byte, error) { 71 if token == nil { 72 return nil, nil 73 } 74 return json.Marshal(token) 75 } 76 77 func deserializeGetHistoryToken(bytes []byte) (*getHistoryToken, error) { 78 token := &getHistoryToken{} 79 err := json.Unmarshal(bytes, token) 80 return token, err 81 } 82 83 func deserializeQueryVisibilityToken(bytes []byte) *string { 84 var ret = string(bytes) 85 return &ret 86 } 87 func serializeQueryVisibilityToken(token string) []byte { 88 return []byte(token) 89 } 90 91 // Only validates the scheme and buckets are passed 92 func SoftValidateURI(URI archiver.URI) error { 93 if URI.Scheme() != URIScheme { 94 return archiver.ErrURISchemeMismatch 95 } 96 if len(URI.Hostname()) == 0 { 97 return errNoBucketSpecified 98 } 99 return nil 100 } 101 102 func BucketExists(ctx context.Context, s3cli s3iface.S3API, URI archiver.URI) error { 103 ctx, cancel := ensureContextTimeout(ctx) 104 defer cancel() 105 _, err := s3cli.HeadBucketWithContext(ctx, &s3.HeadBucketInput{ 106 Bucket: aws.String(URI.Hostname()), 107 }) 108 if err == nil { 109 return nil 110 } 111 if IsNotFoundError(err) { 112 return errBucketNotExists 113 } 114 return err 115 } 116 117 func KeyExists(ctx context.Context, s3cli s3iface.S3API, URI archiver.URI, key string) (bool, error) { 118 ctx, cancel := ensureContextTimeout(ctx) 119 defer cancel() 120 _, err := s3cli.HeadObjectWithContext(ctx, &s3.HeadObjectInput{ 121 Bucket: aws.String(URI.Hostname()), 122 Key: aws.String(key), 123 }) 124 if err != nil { 125 if IsNotFoundError(err) { 126 return false, nil 127 } 128 return false, err 129 } 130 return true, nil 131 } 132 133 func IsNotFoundError(err error) bool { 134 aerr, ok := err.(awserr.Error) 135 return ok && (aerr.Code() == "NotFound") 136 } 137 138 // Key construction 139 func constructHistoryKey(path, namespaceID, workflowID, runID string, version int64, batchIdx int) string { 140 prefix := constructHistoryKeyPrefixWithVersion(path, namespaceID, workflowID, runID, version) 141 return fmt.Sprintf("%s%d", prefix, batchIdx) 142 } 143 144 func constructHistoryKeyPrefixWithVersion(path, namespaceID, workflowID, runID string, version int64) string { 145 prefix := constructHistoryKeyPrefix(path, namespaceID, workflowID, runID) 146 return fmt.Sprintf("%s/%v/", prefix, version) 147 } 148 149 func constructHistoryKeyPrefix(path, namespaceID, workflowID, runID string) string { 150 return strings.TrimLeft(strings.Join([]string{path, namespaceID, "history", workflowID, runID}, "/"), "/") 151 } 152 153 func constructTimeBasedSearchKey(path, namespaceID, primaryIndexKey, primaryIndexValue, secondaryIndexKey string, t time.Time, precision string) string { 154 var timeFormat = "" 155 switch precision { 156 case PrecisionSecond: 157 timeFormat = ":05" 158 fallthrough 159 case PrecisionMinute: 160 timeFormat = ":04" + timeFormat 161 fallthrough 162 case PrecisionHour: 163 timeFormat = "15" + timeFormat 164 fallthrough 165 case PrecisionDay: 166 timeFormat = "2006-01-02T" + timeFormat 167 } 168 169 return fmt.Sprintf( 170 "%s/%s", 171 constructIndexedVisibilitySearchPrefix(path, namespaceID, primaryIndexKey, primaryIndexValue, secondaryIndexKey), 172 t.Format(timeFormat), 173 ) 174 } 175 176 func constructTimestampIndex(path, namespaceID, primaryIndexKey, primaryIndexValue, secondaryIndexKey string, secondaryIndexValue time.Time, runID string) string { 177 return fmt.Sprintf( 178 "%s/%s/%s", 179 constructIndexedVisibilitySearchPrefix(path, namespaceID, primaryIndexKey, primaryIndexValue, secondaryIndexKey), 180 secondaryIndexValue.Format(time.RFC3339), 181 runID, 182 ) 183 } 184 185 func constructIndexedVisibilitySearchPrefix( 186 path string, 187 namespaceID string, 188 primaryIndexKey string, 189 primaryIndexValue string, 190 secondaryIndexType string, 191 ) string { 192 return strings.TrimLeft( 193 strings.Join( 194 []string{path, namespaceID, "visibility", primaryIndexKey, primaryIndexValue, secondaryIndexType}, 195 "/", 196 ), 197 "/", 198 ) 199 } 200 201 func constructVisibilitySearchPrefix(path, namespaceID string) string { 202 return strings.TrimLeft(strings.Join([]string{path, namespaceID, "visibility"}, "/"), "/") 203 } 204 205 func ensureContextTimeout(ctx context.Context) (context.Context, context.CancelFunc) { 206 if _, ok := ctx.Deadline(); ok { 207 return ctx, func() {} 208 } 209 return context.WithTimeout(ctx, defaultBlobstoreTimeout) 210 } 211 func Upload(ctx context.Context, s3cli s3iface.S3API, URI archiver.URI, key string, data []byte) error { 212 ctx, cancel := ensureContextTimeout(ctx) 213 defer cancel() 214 215 _, err := s3cli.PutObjectWithContext(ctx, &s3.PutObjectInput{ 216 Bucket: aws.String(URI.Hostname()), 217 Key: aws.String(key), 218 Body: bytes.NewReader(data), 219 }) 220 if err != nil { 221 if aerr, ok := err.(awserr.Error); ok { 222 if aerr.Code() == s3.ErrCodeNoSuchBucket { 223 return serviceerror.NewInvalidArgument(errBucketNotExists.Error()) 224 } 225 } 226 return err 227 } 228 return nil 229 } 230 231 func Download(ctx context.Context, s3cli s3iface.S3API, URI archiver.URI, key string) ([]byte, error) { 232 ctx, cancel := ensureContextTimeout(ctx) 233 defer cancel() 234 result, err := s3cli.GetObjectWithContext(ctx, &s3.GetObjectInput{ 235 Bucket: aws.String(URI.Hostname()), 236 Key: aws.String(key), 237 }) 238 239 if err != nil { 240 if aerr, ok := err.(awserr.Error); ok { 241 if aerr.Code() == s3.ErrCodeNoSuchBucket { 242 return nil, serviceerror.NewInvalidArgument(errBucketNotExists.Error()) 243 } 244 245 if aerr.Code() == s3.ErrCodeNoSuchKey { 246 return nil, serviceerror.NewNotFound(archiver.ErrHistoryNotExist.Error()) 247 } 248 } 249 return nil, err 250 } 251 252 defer func() { 253 if ierr := result.Body.Close(); ierr != nil { 254 err = multierr.Append(err, ierr) 255 } 256 }() 257 258 body, err := io.ReadAll(result.Body) 259 if err != nil { 260 return nil, err 261 } 262 return body, nil 263 } 264 265 func historyMutated(request *archiver.ArchiveHistoryRequest, historyBatches []*historypb.History, isLast bool) bool { 266 lastBatch := historyBatches[len(historyBatches)-1].Events 267 lastEvent := lastBatch[len(lastBatch)-1] 268 lastFailoverVersion := lastEvent.GetVersion() 269 if lastFailoverVersion > request.CloseFailoverVersion { 270 return true 271 } 272 273 if !isLast { 274 return false 275 } 276 lastEventID := lastEvent.GetEventId() 277 return lastFailoverVersion != request.CloseFailoverVersion || lastEventID+1 != request.NextEventID 278 } 279 280 func convertToExecutionInfo(record *archiverspb.VisibilityRecord, saTypeMap searchattribute.NameTypeMap) (*workflowpb.WorkflowExecutionInfo, error) { 281 searchAttributes, err := searchattribute.Parse(record.SearchAttributes, &saTypeMap) 282 if err != nil { 283 return nil, err 284 } 285 286 return &workflowpb.WorkflowExecutionInfo{ 287 Execution: &commonpb.WorkflowExecution{ 288 WorkflowId: record.GetWorkflowId(), 289 RunId: record.GetRunId(), 290 }, 291 Type: &commonpb.WorkflowType{ 292 Name: record.WorkflowTypeName, 293 }, 294 StartTime: record.StartTime, 295 ExecutionTime: record.ExecutionTime, 296 CloseTime: record.CloseTime, 297 Status: record.Status, 298 HistoryLength: record.HistoryLength, 299 Memo: record.Memo, 300 SearchAttributes: searchAttributes, 301 }, nil 302 }