github.com/klaytn/klaytn@v1.12.1/storage/database/s3filedb.go (about)

     1  // Copyright 2020 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // The klaytn library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The klaytn library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.
    16  //
    17  // FileDB implementation of AWS S3.
    18  //
    19  // [WARN] Using this DB may cause pricing in your AWS account.
    20  //
    21  // You need to set AWS credentials to access to S3.
    22  //    $ export AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY
    23  //    $ export AWS_SECRET_ACCESS_KEY=YOUR_SECRET
    24  
    25  package database
    26  
    27  import (
    28  	"bytes"
    29  	"fmt"
    30  	"io"
    31  	"time"
    32  
    33  	"github.com/klaytn/klaytn/common/hexutil"
    34  
    35  	"github.com/aws/aws-sdk-go/aws"
    36  	"github.com/aws/aws-sdk-go/aws/client"
    37  	"github.com/aws/aws-sdk-go/aws/session"
    38  	"github.com/aws/aws-sdk-go/service/s3"
    39  	"github.com/klaytn/klaytn/log"
    40  )
    41  
    42  // s3FileDB is an implementation of fileDB based on AWS S3.
    43  // It stores the data to the designated AWS S3 bucket.
    44  type s3FileDB struct {
    45  	region   string
    46  	endpoint string
    47  	bucket   string
    48  	s3       *s3.S3
    49  	logger   log.Logger
    50  }
    51  
    52  // newS3FileDB returns a new s3FileDB with the given region, endpoint and bucketName.
    53  // If the given bucket does not exist, it creates one.
    54  func newS3FileDB(region, endpoint, bucketName string) (*s3FileDB, error) {
    55  	localLogger := logger.NewWith("endpoint", endpoint, "bucketName", bucketName)
    56  	sessionConf, err := session.NewSession(&aws.Config{
    57  		Retryer: CustomRetryer{
    58  			DefaultRetryer: client.DefaultRetryer{
    59  				NumMaxRetries:    dynamoMaxRetry,
    60  				MaxRetryDelay:    time.Second,
    61  				MaxThrottleDelay: time.Second,
    62  			},
    63  		},
    64  		Region:           aws.String(region),
    65  		Endpoint:         aws.String(endpoint),
    66  		S3ForcePathStyle: aws.Bool(true),
    67  	})
    68  	if err != nil {
    69  		localLogger.Error("failed to create session", "region", region)
    70  		return nil, err
    71  	}
    72  
    73  	s3DB := &s3FileDB{
    74  		region:   region,
    75  		endpoint: endpoint,
    76  		bucket:   bucketName,
    77  		s3:       s3.New(sessionConf),
    78  		logger:   localLogger,
    79  	}
    80  
    81  	exist, err := s3DB.hasBucket(bucketName)
    82  	if err != nil {
    83  		localLogger.Error("failed to retrieve a bucket list", "err", err)
    84  		return nil, err
    85  	}
    86  
    87  	if !exist {
    88  		localLogger.Warn("creating a S3 bucket. You will be CHARGED until the bucket is deleted")
    89  		_, err = s3DB.s3.CreateBucket(&s3.CreateBucketInput{
    90  			Bucket: aws.String(bucketName),
    91  		})
    92  		if err != nil {
    93  			localLogger.Error("failed to create a bucket", "err", err)
    94  			return nil, err
    95  		}
    96  	}
    97  	localLogger.Info("successfully created S3 session")
    98  	return s3DB, nil
    99  }
   100  
   101  // hasBucket returns if the bucket exists in the endpoint of s3FileDB.
   102  func (s3DB *s3FileDB) hasBucket(bucketName string) (bool, error) {
   103  	output, err := s3DB.s3.ListBuckets(&s3.ListBucketsInput{})
   104  	if err != nil {
   105  		return false, err
   106  	}
   107  
   108  	bucketExist := false
   109  	for _, bucket := range output.Buckets {
   110  		if bucketName == *bucket.Name {
   111  			bucketExist = true
   112  			break
   113  		}
   114  	}
   115  	return bucketExist, nil
   116  }
   117  
   118  // write puts list of items to its bucket and returns the list of URIs.
   119  func (s3DB *s3FileDB) write(item item) (string, error) {
   120  	o := &s3.PutObjectInput{
   121  		Bucket:      aws.String(s3DB.bucket),
   122  		Key:         aws.String(hexutil.Encode(item.key)),
   123  		Body:        bytes.NewReader(item.val),
   124  		ContentType: aws.String("application/octet-stream"),
   125  	}
   126  
   127  	if _, err := s3DB.s3.PutObject(o); err != nil {
   128  		return "", fmt.Errorf("failed to write item to S3. key: %v, err: %w", string(item.key), err)
   129  	}
   130  
   131  	return hexutil.Encode(item.key), nil
   132  }
   133  
   134  // read gets the data from the bucket with the given key.
   135  func (s3DB *s3FileDB) read(key []byte) ([]byte, error) {
   136  	output, err := s3DB.s3.GetObject(&s3.GetObjectInput{
   137  		Bucket:              aws.String(s3DB.bucket),
   138  		Key:                 aws.String(hexutil.Encode(key)),
   139  		ResponseContentType: aws.String("application/octet-stream"),
   140  	})
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	returnVal, err := io.ReadAll(output.Body)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	return returnVal, nil
   151  }
   152  
   153  // delete removes the data with the given key from the bucket.
   154  // No error is returned if the data with the given key does not exist.
   155  func (s3DB *s3FileDB) delete(key []byte) error {
   156  	_, err := s3DB.s3.DeleteObject(&s3.DeleteObjectInput{
   157  		Bucket: aws.String(s3DB.bucket),
   158  		Key:    aws.String(hexutil.Encode(key)),
   159  	})
   160  	return err
   161  }
   162  
   163  // deleteBucket removes the bucket
   164  func (s3DB *s3FileDB) deleteBucket() {
   165  	if _, err := s3DB.s3.DeleteBucket(&s3.DeleteBucketInput{Bucket: aws.String(s3DB.bucket)}); err != nil {
   166  		s3DB.logger.Error("failed to delete the test bucket", "err", err, "bucketName", s3DB.bucket)
   167  	}
   168  }