github.com/klaytn/klaytn@v1.12.1/storage/database/dynamodb_test.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  // For local test, please run the below.
    18  //    $ docker run -d -p 4566:4566 localstack/localstack:0.13.0
    19  
    20  package database
    21  
    22  import (
    23  	"net"
    24  	"strconv"
    25  	"strings"
    26  	"sync"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/klaytn/klaytn/common"
    31  	"github.com/klaytn/klaytn/common/hexutil"
    32  	"github.com/klaytn/klaytn/log"
    33  	"github.com/klaytn/klaytn/storage"
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/suite"
    36  )
    37  
    38  // GetTestDynamoConfig gets dynamo config for local test
    39  //
    40  // Please Run DynamoDB local with docker
    41  //    $ docker run -d -p 4566:4566 localstack/localstack:0.11.5
    42  func GetTestDynamoConfig() *DynamoDBConfig {
    43  	return &DynamoDBConfig{
    44  		Region:             "us-east-1",
    45  		Endpoint:           "http://localhost:4566",
    46  		S3Endpoint:         "http://localhost:4566",
    47  		TableName:          "klaytn-default" + strconv.Itoa(time.Now().Nanosecond()),
    48  		IsProvisioned:      false,
    49  		ReadCapacityUnits:  10000,
    50  		WriteCapacityUnits: 10000,
    51  		ReadOnly:           false,
    52  		PerfCheck:          false,
    53  	}
    54  }
    55  
    56  type SuiteDynamoDB struct {
    57  	suite.Suite
    58  	database Database
    59  }
    60  
    61  func TestDynamoDB(t *testing.T) {
    62  	storage.SkipLocalTest(t)
    63  	db, remove, _ := newTestDynamoS3DB()
    64  	defer remove()
    65  
    66  	suite.Run(t, &SuiteDynamoDB{database: db})
    67  }
    68  
    69  func (s *SuiteDynamoDB) TestDynamoDB_Put() {
    70  	storage.SkipLocalTest(s.T())
    71  
    72  	dynamo := s.database
    73  
    74  	testKey := common.MakeRandomBytes(32)
    75  	testVal := common.MakeRandomBytes(500)
    76  
    77  	val, err := dynamo.Get(testKey)
    78  
    79  	s.Nil(val)
    80  	s.Error(err)
    81  	s.Equal(err.Error(), dataNotFoundErr.Error())
    82  
    83  	s.NoError(dynamo.Put(testKey, testVal))
    84  	returnedVal, returnedErr := dynamo.Get(testKey)
    85  	s.Equal(testVal, returnedVal)
    86  	s.NoError(returnedErr)
    87  }
    88  
    89  func (s *SuiteDynamoDB) TestDynamoBatch_Write() {
    90  	storage.SkipLocalTest(s.T())
    91  
    92  	dynamo := s.database
    93  
    94  	var testKeys [][]byte
    95  	var testVals [][]byte
    96  	batch := dynamo.NewBatch()
    97  
    98  	itemNum := 25
    99  	for i := 0; i < itemNum; i++ {
   100  		testKey := common.MakeRandomBytes(32)
   101  		testVal := common.MakeRandomBytes(500)
   102  
   103  		testKeys = append(testKeys, testKey)
   104  		testVals = append(testVals, testVal)
   105  
   106  		s.NoError(batch.Put(testKey, testVal))
   107  	}
   108  	s.NoError(batch.Write())
   109  
   110  	// check if exist
   111  	for i := 0; i < itemNum; i++ {
   112  		returnedVal, returnedErr := dynamo.Get(testKeys[i])
   113  		s.NoError(returnedErr)
   114  		s.Equal(hexutil.Encode(testVals[i]), hexutil.Encode(returnedVal))
   115  	}
   116  }
   117  
   118  func (s *SuiteDynamoDB) TestDynamoBatch_Write_LargeData() {
   119  	storage.SkipLocalTest(s.T())
   120  
   121  	dynamo := s.database
   122  
   123  	var testKeys [][]byte
   124  	var testVals [][]byte
   125  	batch := dynamo.NewBatch()
   126  
   127  	itemNum := 26
   128  	for i := 0; i < itemNum; i++ {
   129  		testKey := common.MakeRandomBytes(32)
   130  		testVal := common.MakeRandomBytes(500 * 1024)
   131  
   132  		testKeys = append(testKeys, testKey)
   133  		testVals = append(testVals, testVal)
   134  
   135  		s.NoError(batch.Put(testKey, testVal))
   136  	}
   137  	s.NoError(batch.Write())
   138  
   139  	// check if exist
   140  	for i := 0; i < itemNum; i++ {
   141  		returnedVal, returnedErr := dynamo.Get(testKeys[i])
   142  		s.NoError(returnedErr)
   143  		s.Equal(hexutil.Encode(testVals[i]), hexutil.Encode(returnedVal))
   144  	}
   145  }
   146  
   147  func (s *SuiteDynamoDB) TestDynamoBatch_Write_DuplicatedKey() {
   148  	storage.SkipLocalTest(s.T())
   149  
   150  	dynamo := s.database
   151  
   152  	var testKeys [][]byte
   153  	var testVals [][]byte
   154  	batch := dynamo.NewBatch()
   155  
   156  	itemNum := 25
   157  	for i := 0; i < itemNum; i++ {
   158  		testKey := common.MakeRandomBytes(256)
   159  		testVal := common.MakeRandomBytes(600)
   160  
   161  		testKeys = append(testKeys, testKey)
   162  		testVals = append(testVals, testVal)
   163  
   164  		s.NoError(batch.Put(testKey, testVal))
   165  		s.NoError(batch.Put(testKey, testVal))
   166  		s.NoError(batch.Put(testKey, testVal))
   167  	}
   168  	s.NoError(batch.Write())
   169  
   170  	// check if exist
   171  	for i := 0; i < itemNum; i++ {
   172  		returnedVal, returnedErr := dynamo.Get(testKeys[i])
   173  		s.NoError(returnedErr)
   174  		s.Equal(hexutil.Encode(testVals[i]), hexutil.Encode(returnedVal))
   175  	}
   176  }
   177  
   178  // TestDynamoBatch_Write_MultiTables checks if there is no error when working with more than one tables.
   179  // This also checks if shared workers works as expected.
   180  func (s *SuiteDynamoDB) TestDynamoBatch_Write_MultiTables() {
   181  	storage.SkipLocalTest(s.T())
   182  	log.EnableLogForTest(log.LvlCrit, log.LvlTrace) // this test might end with Crit, enable Log to find out the log
   183  
   184  	// create DynamoDB1
   185  	dynamo := s.database
   186  
   187  	dynamo2, err := newDynamoDB(GetTestDynamoConfig())
   188  	if err != nil {
   189  		s.FailNow("failed to create dynamoDB", err)
   190  	}
   191  	defer func() {
   192  		dynamo2.Close()
   193  		dynamo2.deleteTable()
   194  	}()
   195  
   196  	var (
   197  		testKeys, testKeys2 [][]byte
   198  		testVals, testVals2 [][]byte
   199  	)
   200  
   201  	batch := dynamo.NewBatch()
   202  	batch2 := dynamo2.NewBatch()
   203  
   204  	itemNum := WorkerNum * 2
   205  	for i := 0; i < itemNum; i++ {
   206  		// write batch items to db1 and db2 in turn
   207  		for j := 0; j < dynamoBatchSize; j++ {
   208  			// write key and val to db1
   209  			testKey := common.MakeRandomBytes(10)
   210  			testVal := common.MakeRandomBytes(20)
   211  
   212  			testKeys = append(testKeys, testKey)
   213  			testVals = append(testVals, testVal)
   214  
   215  			s.NoError(batch.Put(testKey, testVal))
   216  
   217  			// write key2 and val2 to db2
   218  			testKey2 := common.MakeRandomBytes(10)
   219  			testVal2 := common.MakeRandomBytes(20)
   220  
   221  			testKeys2 = append(testKeys2, testKey2)
   222  			testVals2 = append(testVals2, testVal2)
   223  
   224  			s.NoError(batch2.Put(testKey2, testVal2))
   225  		}
   226  	}
   227  	s.NoError(batch.Write())
   228  	s.NoError(batch2.Write())
   229  
   230  	// check if exist
   231  	for i := 0; i < itemNum; i++ {
   232  		// dynamodb 1 - check if wrote key and val
   233  		returnedVal, returnedErr := dynamo.Get(testKeys[i])
   234  		s.NoError(returnedErr)
   235  		s.Equal(hexutil.Encode(testVals[i]), hexutil.Encode(returnedVal))
   236  		// dynamodb 1 - check if not wrote key2 and val2
   237  		returnedVal, returnedErr = dynamo.Get(testKeys2[i])
   238  		s.Nil(returnedVal, "the entry should not be put in this table")
   239  
   240  		// dynamodb 2 - check if wrote key2 and val2
   241  		returnedVal, returnedErr = dynamo2.Get(testKeys2[i])
   242  		s.NoError(returnedErr)
   243  		s.Equal(hexutil.Encode(testVals2[i]), hexutil.Encode(returnedVal))
   244  		// dynamodb 2 - check if not wrote key and val
   245  		returnedVal, returnedErr = dynamo2.Get(testKeys[i])
   246  		s.Nil(returnedVal, "the entry should not be put in this table")
   247  	}
   248  }
   249  
   250  // TestDynamoDB_Retry tests whether dynamoDB client retries successfully.
   251  // A fake server is setup to simulate a server with a request count.
   252  func TestDynamoDB_Retry(t *testing.T) {
   253  	storage.SkipLocalTest(t)
   254  	// This test needs a new dynamoDBClient having a fake endpoint.
   255  	oldClient := dynamoDBClient
   256  	dynamoDBClient = nil
   257  	defer func() {
   258  		dynamoDBClient = oldClient
   259  	}()
   260  
   261  	// fakeEndpoint allows TCP handshakes, but doesn't answer anything to client.
   262  	// The fake server is used to produce a network failure scenario.
   263  	fakeEndpoint := "localhost:14566"
   264  	requestCnt := 0
   265  
   266  	serverReadyWg := sync.WaitGroup{}
   267  	serverReadyWg.Add(1)
   268  
   269  	go func() {
   270  		tcpAddr, err := net.ResolveTCPAddr("tcp", fakeEndpoint)
   271  		if err != nil {
   272  			t.Error(err)
   273  			return
   274  		}
   275  
   276  		listen, err := net.ListenTCP("tcp", tcpAddr)
   277  		if err != nil {
   278  			t.Error(err)
   279  			return
   280  		}
   281  		defer listen.Close()
   282  
   283  		serverReadyWg.Done()
   284  
   285  		// expected request number: dynamoMaxRetry+1. It will wait one more time after all retry done.
   286  		for i := 0; i < dynamoMaxRetry+1+1; i++ {
   287  			// Deadline prevents infinite waiting of the fake server
   288  			// Wait longer than (maxRetries+1) * timeout
   289  			if err := listen.SetDeadline(time.Now().Add(10 * time.Second)); err != nil {
   290  				t.Error(err)
   291  				return
   292  			}
   293  			conn, err := listen.AcceptTCP()
   294  			if err != nil {
   295  				// the fake server ends silently when it meets deadline
   296  				if strings.Contains(err.Error(), "timeout") {
   297  					return
   298  				}
   299  			}
   300  			requestCnt++
   301  			_ = conn.Close()
   302  		}
   303  	}()
   304  
   305  	conf := GetTestDynamoConfig() // dummy values to create dynamoDB
   306  	conf.Endpoint = fakeEndpoint
   307  
   308  	serverReadyWg.Wait()
   309  
   310  	dynamoDBClient = nil
   311  	_, err := NewDynamoDB(conf)
   312  	assert.NotNil(t, err)
   313  	assert.Equal(t, dynamoMaxRetry+1, requestCnt)
   314  }