github.com/matrixorigin/matrixone@v1.2.0/pkg/backup/fs.go (about)

     1  // Copyright 2023 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //	http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package backup
    16  
    17  import (
    18  	"context"
    19  	"crypto/sha256"
    20  	"encoding/csv"
    21  	"fmt"
    22  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    23  	"github.com/matrixorigin/matrixone/pkg/fileservice"
    24  	"github.com/matrixorigin/matrixone/pkg/logutil"
    25  	"strconv"
    26  	"strings"
    27  )
    28  
    29  // setupFilesystem returns a FileService for ETL which the reader outside the matrixone
    30  // can read the content. a FileService for Backup which only the matrixone
    31  // can read the content.
    32  func setupFilesystem(ctx context.Context, path string, forETL bool) (res fileservice.FileService, readPath string, err error) {
    33  	return setupFileservice(ctx, &pathConfig{
    34  		isS3:             false,
    35  		forETL:           forETL,
    36  		filesystemConfig: filesystemConfig{path: path},
    37  	})
    38  }
    39  
    40  // setupS3 returns a FileService for ETL which the reader outside the matrixone
    41  // can read the content.a FileService for Backup which only the matrixone
    42  // can read the content.
    43  func setupS3(ctx context.Context, s3 *s3Config, forETL bool) (res fileservice.FileService, readPath string, err error) {
    44  	return setupFileservice(ctx, &pathConfig{
    45  		isS3:     true,
    46  		forETL:   forETL,
    47  		s3Config: *s3,
    48  	})
    49  }
    50  
    51  func setupFileservice(ctx context.Context, conf *pathConfig) (res fileservice.FileService, readPath string, err error) {
    52  	var s3opts string
    53  	if conf.isS3 {
    54  		s3opts, err = makeS3Opts(&conf.s3Config)
    55  		if err != nil {
    56  			return nil, "", err
    57  		}
    58  		if conf.forETL {
    59  			s3path := fileservice.JoinPath(s3opts, etlFSDir(conf.filepath))
    60  			//TODO:remove debug
    61  			logutil.Debugf("==>s3path: %s", s3path)
    62  			res, readPath, err = fileservice.GetForETL(ctx, nil, s3path)
    63  			if err != nil {
    64  				return nil, "", err
    65  			}
    66  		} else {
    67  			s3path := fileservice.JoinPath(s3opts, conf.filepath)
    68  			res, err = fileservice.GetForBackup(ctx, s3path)
    69  			if err != nil {
    70  				return nil, "", err
    71  			}
    72  		}
    73  		res = fileservice.SubPath(res, conf.filepath)
    74  	} else {
    75  		if conf.forETL {
    76  			res, readPath, err = fileservice.GetForETL(ctx, nil, etlFSDir(conf.path))
    77  			if err != nil {
    78  				return nil, "", err
    79  			}
    80  		} else {
    81  			res, err = fileservice.GetForBackup(ctx, conf.path)
    82  			if err != nil {
    83  				return nil, "", err
    84  			}
    85  		}
    86  	}
    87  
    88  	return res, readPath, err
    89  }
    90  
    91  func makeS3Opts(s3 *s3Config) (string, error) {
    92  	var err error
    93  	buf := new(strings.Builder)
    94  	w := csv.NewWriter(buf)
    95  	opts := []string{
    96  		"s3-opts",
    97  		"endpoint=" + s3.endpoint,
    98  		"region=" + s3.region,
    99  		"key=" + s3.accessKeyId,
   100  		"secret=" + s3.secretAccessKey,
   101  		"bucket=" + s3.bucket,
   102  		"role-arn=" + s3.roleArn,
   103  		"is-minio=" + strconv.FormatBool(s3.isMinio),
   104  		//"external-id="              /*+ param.S3Param.ExternalId*/,
   105  	}
   106  	if err = w.Write(opts); err != nil {
   107  		return "", err
   108  	}
   109  	w.Flush()
   110  	return buf.String(), nil
   111  }
   112  
   113  func etlFSDir(filepath string) string {
   114  	return filepath + "/_"
   115  }
   116  
   117  func writeFile(ctx context.Context, fs fileservice.FileService, path string, data []byte) error {
   118  	var err error
   119  	//write file
   120  	_, err = fileservice.DoWithRetry(
   121  		"BackupWrite",
   122  		func() (int, error) {
   123  			return 0, fs.Write(ctx, fileservice.IOVector{
   124  				FilePath: path,
   125  				Entries: []fileservice.IOEntry{
   126  					{
   127  						Offset: 0,
   128  						Size:   int64(len(data)),
   129  						Data:   data,
   130  					},
   131  				},
   132  			})
   133  		},
   134  		64,
   135  		fileservice.IsRetryableError,
   136  	)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	checksum := sha256.Sum256(data)
   142  
   143  	//write checksum file for the file
   144  	checksumFile := path + ".sha256"
   145  	_, err = fileservice.DoWithRetry(
   146  		"BackupWrite",
   147  		func() (int, error) {
   148  			return 0, fs.Write(ctx, fileservice.IOVector{
   149  				FilePath: checksumFile,
   150  				Entries: []fileservice.IOEntry{
   151  					{
   152  						Offset: 0,
   153  						Size:   int64(len(checksum)),
   154  						Data:   checksum[:],
   155  					},
   156  				},
   157  			})
   158  		},
   159  		64,
   160  		fileservice.IsRetryableError,
   161  	)
   162  	return err
   163  }
   164  
   165  func readFile(ctx context.Context, fs fileservice.FileService, path string) ([]byte, error) {
   166  	var (
   167  		err error
   168  	)
   169  	iov := &fileservice.IOVector{
   170  		FilePath: path,
   171  		Entries: []fileservice.IOEntry{
   172  			{
   173  				Offset: 0,
   174  				Size:   -1,
   175  			},
   176  		},
   177  	}
   178  	err = fs.Read(ctx, iov)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	return iov.Entries[0].Data, err
   183  }
   184  
   185  func hexStr(d []byte) string {
   186  	return fmt.Sprintf("%x", d)
   187  }
   188  
   189  // readFileAndCheck reads data and compare the checksum with the one in checksum file.
   190  // if the checksum is equal, it returns the data of the file.
   191  func readFileAndCheck(ctx context.Context, fs fileservice.FileService, path string) ([]byte, error) {
   192  	var (
   193  		err               error
   194  		data              []byte
   195  		savedChecksumData []byte
   196  		newChecksumData   []byte
   197  		savedChecksum     string
   198  		newChecksum       string
   199  	)
   200  	data, err = readFile(ctx, fs, path)
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	//calculate the checksum
   206  	hash := sha256.New()
   207  	hash.Write(data)
   208  	newChecksumData = hash.Sum(nil)
   209  	newChecksum = hexStr(newChecksumData)
   210  
   211  	checksumFile := path + ".sha256"
   212  	savedChecksumData, err = readFile(ctx, fs, checksumFile)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	savedChecksum = hexStr(savedChecksumData)
   217  	//3. compare the checksum
   218  	if strings.Compare(savedChecksum, newChecksum) != 0 {
   219  		return nil, moerr.NewInternalError(ctx, checksumErrorInfo(newChecksum, savedChecksum, path))
   220  	}
   221  	return data, err
   222  }
   223  
   224  func checksumErrorInfo(newChecksum, savedChecksum, path string) string {
   225  	return fmt.Sprintf("checksum %s of %s is not equal to %s ", newChecksum, path, savedChecksum)
   226  }