github.com/snowflakedb/gosnowflake@v1.9.0/storage_client.go (about)

     1  // Copyright (c) 2021-2022 Snowflake Computing Inc. All rights reserved.
     2  
     3  package gosnowflake
     4  
     5  import (
     6  	"bytes"
     7  	"fmt"
     8  	"math"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"time"
    13  )
    14  
    15  const (
    16  	defaultConcurrency = 1
    17  	defaultMaxRetry    = 5
    18  )
    19  
    20  // implemented by localUtil and remoteStorageUtil
    21  type storageUtil interface {
    22  	createClient(*execResponseStageInfo, bool) (cloudClient, error)
    23  	uploadOneFileWithRetry(*fileMetadata) error
    24  	downloadOneFile(*fileMetadata) error
    25  }
    26  
    27  // implemented by snowflakeS3Util, snowflakeAzureUtil and snowflakeGcsUtil
    28  type cloudUtil interface {
    29  	createClient(*execResponseStageInfo, bool) (cloudClient, error)
    30  	getFileHeader(*fileMetadata, string) (*fileHeader, error)
    31  	uploadFile(string, *fileMetadata, *encryptMetadata, int, int64) error
    32  	nativeDownloadFile(*fileMetadata, string, int64) error
    33  }
    34  
    35  type cloudClient interface{}
    36  
    37  type remoteStorageUtil struct {
    38  }
    39  
    40  func (rsu *remoteStorageUtil) getNativeCloudType(cli string) cloudUtil {
    41  	if cloudType(cli) == s3Client {
    42  		return &snowflakeS3Client{}
    43  	} else if cloudType(cli) == azureClient {
    44  		return &snowflakeAzureClient{}
    45  	} else if cloudType(cli) == gcsClient {
    46  		return &snowflakeGcsClient{}
    47  	}
    48  	return nil
    49  }
    50  
    51  // call cloud utils' native create client methods
    52  func (rsu *remoteStorageUtil) createClient(info *execResponseStageInfo, useAccelerateEndpoint bool) (cloudClient, error) {
    53  	utilClass := rsu.getNativeCloudType(info.LocationType)
    54  	return utilClass.createClient(info, useAccelerateEndpoint)
    55  }
    56  
    57  func (rsu *remoteStorageUtil) uploadOneFile(meta *fileMetadata) error {
    58  	var encryptMeta *encryptMetadata
    59  	var dataFile string
    60  	var err error
    61  	if meta.encryptionMaterial != nil {
    62  		if meta.srcStream != nil {
    63  			var encryptedStream bytes.Buffer
    64  			srcStream := meta.srcStream
    65  			if meta.realSrcStream != nil {
    66  				srcStream = meta.realSrcStream
    67  			}
    68  			encryptMeta, err = encryptStream(meta.encryptionMaterial, srcStream, &encryptedStream, 0)
    69  			if err != nil {
    70  				return err
    71  			}
    72  			meta.realSrcStream = &encryptedStream
    73  			dataFile = meta.realSrcFileName
    74  		} else {
    75  			encryptMeta, dataFile, err = encryptFile(meta.encryptionMaterial, meta.realSrcFileName, 0, meta.tmpDir)
    76  			if err != nil {
    77  				return err
    78  			}
    79  		}
    80  	} else {
    81  		dataFile = meta.realSrcFileName
    82  	}
    83  
    84  	utilClass := rsu.getNativeCloudType(meta.stageInfo.LocationType)
    85  	maxConcurrency := int(meta.parallel)
    86  	var lastErr error
    87  	maxRetry := defaultMaxRetry
    88  	for retry := 0; retry < maxRetry; retry++ {
    89  		if !meta.overwrite {
    90  			header, err := utilClass.getFileHeader(meta, meta.dstFileName)
    91  			if meta.resStatus == notFoundFile {
    92  				err := utilClass.uploadFile(dataFile, meta, encryptMeta, maxConcurrency, meta.options.MultiPartThreshold)
    93  				if err != nil {
    94  					logger.Warnf("Error uploading %v. err: %v", dataFile, err)
    95  				}
    96  			} else if err != nil {
    97  				return err
    98  			}
    99  			if header != nil && meta.resStatus == uploaded {
   100  				meta.dstFileSize = 0
   101  				meta.resStatus = skipped
   102  				return nil
   103  			}
   104  		}
   105  		if meta.overwrite || meta.resStatus == notFoundFile {
   106  			err := utilClass.uploadFile(dataFile, meta, encryptMeta, maxConcurrency, meta.options.MultiPartThreshold)
   107  			if err != nil {
   108  				logger.Debugf("Error uploading %v. err: %v", dataFile, err)
   109  			}
   110  		}
   111  		if meta.resStatus == uploaded || meta.resStatus == renewToken || meta.resStatus == renewPresignedURL {
   112  			return nil
   113  		} else if meta.resStatus == needRetry {
   114  			if !meta.noSleepingTime {
   115  				sleepingTime := intMin(int(math.Exp2(float64(retry))), 16)
   116  				time.Sleep(time.Second * time.Duration(sleepingTime))
   117  			}
   118  		} else if meta.resStatus == needRetryWithLowerConcurrency {
   119  			maxConcurrency = int(meta.parallel) - (retry * int(meta.parallel) / maxRetry)
   120  			maxConcurrency = intMax(defaultConcurrency, maxConcurrency)
   121  			meta.lastMaxConcurrency = maxConcurrency
   122  
   123  			if !meta.noSleepingTime {
   124  				sleepingTime := intMin(int(math.Exp2(float64(retry))), 16)
   125  				time.Sleep(time.Second * time.Duration(sleepingTime))
   126  			}
   127  		}
   128  		lastErr = meta.lastError
   129  	}
   130  	if lastErr != nil {
   131  		return lastErr
   132  	}
   133  	return fmt.Errorf("unkown error uploading %v", dataFile)
   134  }
   135  
   136  func (rsu *remoteStorageUtil) uploadOneFileWithRetry(meta *fileMetadata) error {
   137  	utilClass := rsu.getNativeCloudType(meta.stageInfo.LocationType)
   138  	retryOuter := true
   139  	for i := 0; i < 10; i++ {
   140  		// retry
   141  		if err := rsu.uploadOneFile(meta); err != nil {
   142  			return err
   143  		}
   144  		retryInner := true
   145  		if meta.resStatus == uploaded || meta.resStatus == skipped {
   146  			for j := 0; j < 10; j++ {
   147  				status := meta.resStatus
   148  				utilClass.getFileHeader(meta, meta.dstFileName)
   149  				// check file header status and verify upload/skip
   150  				if meta.resStatus == notFoundFile {
   151  					time.Sleep(time.Second) // wait 1 second
   152  					continue
   153  				} else {
   154  					retryInner = false
   155  					meta.resStatus = status
   156  					break
   157  				}
   158  			}
   159  		}
   160  		if !retryInner {
   161  			retryOuter = false
   162  			break
   163  		} else {
   164  			continue
   165  		}
   166  	}
   167  	if retryOuter {
   168  		// wanted to continue retrying but could not upload/find file
   169  		meta.resStatus = errStatus
   170  	}
   171  	return nil
   172  }
   173  
   174  func (rsu *remoteStorageUtil) downloadOneFile(meta *fileMetadata) error {
   175  	fullDstFileName := path.Join(meta.localLocation, baseName(meta.dstFileName))
   176  	fullDstFileName, err := expandUser(fullDstFileName)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	if !filepath.IsAbs(fullDstFileName) {
   181  		cwd, err := os.Getwd()
   182  		if err != nil {
   183  			return err
   184  		}
   185  		fullDstFileName = filepath.Join(cwd, fullDstFileName)
   186  	}
   187  	baseDir, err := getDirectory()
   188  	if err != nil {
   189  		return err
   190  	}
   191  	if _, err = os.Stat(baseDir); os.IsNotExist(err) {
   192  		if err = os.MkdirAll(baseDir, os.ModePerm); err != nil {
   193  			return err
   194  		}
   195  	}
   196  
   197  	utilClass := rsu.getNativeCloudType(meta.stageInfo.LocationType)
   198  	header, err := utilClass.getFileHeader(meta, meta.srcFileName)
   199  	if err != nil {
   200  		return err
   201  	}
   202  	if header != nil {
   203  		meta.srcFileSize = header.contentLength
   204  	}
   205  
   206  	maxConcurrency := meta.parallel
   207  	var lastErr error
   208  	maxRetry := defaultMaxRetry
   209  	for retry := 0; retry < maxRetry; retry++ {
   210  		if err = utilClass.nativeDownloadFile(meta, fullDstFileName, maxConcurrency); err != nil {
   211  			return err
   212  		}
   213  		if meta.resStatus == downloaded {
   214  			if meta.encryptionMaterial != nil {
   215  				if meta.presignedURL != nil {
   216  					header, err = utilClass.getFileHeader(meta, meta.srcFileName)
   217  					if err != nil {
   218  						return err
   219  					}
   220  				}
   221  				tmpDstFileName, err := decryptFile(header.encryptionMetadata,
   222  					meta.encryptionMaterial, fullDstFileName, 0, meta.tmpDir)
   223  				if err != nil {
   224  					return err
   225  				}
   226  				if err = os.Rename(tmpDstFileName, fullDstFileName); err != nil {
   227  					return err
   228  				}
   229  			}
   230  			if fi, err := os.Stat(fullDstFileName); err == nil {
   231  				meta.dstFileSize = fi.Size()
   232  			}
   233  			return nil
   234  		}
   235  		lastErr = meta.lastError
   236  	}
   237  	if lastErr != nil {
   238  		return lastErr
   239  	}
   240  	return fmt.Errorf("unkown error downloading %v", fullDstFileName)
   241  }