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 }