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  }