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  }