activerecord-aurora-serverless-adapter 5.2.1 → 5.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f3ad604df999e26ca0a4db13f00ca1c71616b6dbceecc24549cf38ea4c9c28a5
4
- data.tar.gz: b6a78002100ba027c0a83809f77de08887d40e360051c6efc583d181192b93f4
3
+ metadata.gz: 60b59ea21f9e813832a9b157fd65ae5d9d686afb0f04c51700cd5664eeb2edcd
4
+ data.tar.gz: 6555d62f42ccb9c421382353f5fa14990c456a2d3555437b03965a3ed0d3d97d
5
5
  SHA512:
6
- metadata.gz: 7dccc50158b4681545a944ddb3d4df0c28dbad0db48ee005edcf195aeaf5c8413ca58e97567cee3d7945afd853b962f224e21487f5759df739656b6e11a4c836
7
- data.tar.gz: ada81597e30e524a533391229e684b9d0de77171c92a875e7caa7ab4ed3d260cc303180f7d6f9a54aa85257c8f5cb83727b228ae2329e12c0c20d947c996b92c
6
+ metadata.gz: 4271e26bc9ad5f0a8de508b30914cd39d7d870e45c5985a6857ec1881fcc2c6ff6b23a38d1c611c9beba607a658cabd4723b8d45140afd365121a0d992f9b762
7
+ data.tar.gz: 4cc64d2e19eff3da7f7fdc5d939f90a40c952cfdaf72fd6168b3fd7280fec7aa409038707c6c691da3522a29c49dc313f0949e5f01b6fb21b43d3fe491bb9e2a
data/Gemfile.lock CHANGED
@@ -67,9 +67,10 @@ GIT
67
67
  PATH
68
68
  remote: .
69
69
  specs:
70
- activerecord-aurora-serverless-adapter (5.2.1)
70
+ activerecord-aurora-serverless-adapter (5.2.2)
71
71
  activerecord (~> 5.2.0)
72
72
  aws-sdk-rdsdataservice
73
+ retriable
73
74
 
74
75
  GEM
75
76
  remote: https://rubygems.org/
@@ -81,8 +82,8 @@ GEM
81
82
  thor (>= 0.14.0)
82
83
  arel (9.0.0)
83
84
  aws-eventstream (1.0.3)
84
- aws-partitions (1.260.0)
85
- aws-sdk-core (3.86.0)
85
+ aws-partitions (1.265.0)
86
+ aws-sdk-core (3.89.1)
86
87
  aws-eventstream (~> 1.0, >= 1.0.2)
87
88
  aws-partitions (~> 1, >= 1.239.0)
88
89
  aws-sigv4 (~> 1.1)
@@ -141,6 +142,7 @@ GEM
141
142
  rails-html-sanitizer (1.3.0)
142
143
  loofah (~> 2.3)
143
144
  rake (13.0.1)
145
+ retriable (3.1.2)
144
146
  ruby-progressbar (1.10.1)
145
147
  sprockets (4.0.0)
146
148
  concurrent-ruby (~> 1.0)
@@ -12,13 +12,16 @@ Gem::Specification.new do |spec|
12
12
  spec.homepage = 'https://github.com/customink/activerecord-aurora-serverless-adapter'
13
13
  spec.license = 'MIT'
14
14
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
15
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
+ `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features|docker)/i})
17
+ end
16
18
  end
17
19
  spec.bindir = 'exe'
18
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
21
  spec.require_paths = ['lib']
20
22
  spec.add_runtime_dependency 'activerecord', '~> 5.2.0'
21
23
  spec.add_runtime_dependency 'aws-sdk-rdsdataservice'
24
+ spec.add_runtime_dependency 'retriable'
22
25
  spec.add_development_dependency 'appraisal'
23
26
  spec.add_development_dependency 'dotenv'
24
27
  spec.add_development_dependency 'minitest'
@@ -23,6 +23,14 @@ module ActiveRecord
23
23
  "#<#{self.class} database: #{database.inspect}, raw_client: #{raw_client.inspect}>"
24
24
  end
25
25
 
26
+ def execute_statement_retry(sql)
27
+ if @connected
28
+ execute_statement(sql)
29
+ else
30
+ auto_paused_retry { execute_statement(sql) }
31
+ end
32
+ end
33
+
26
34
  def execute_statement(sql)
27
35
  id = @transactions.first
28
36
  debug_transactions "EXECUTE: #{sql}", id
@@ -34,6 +42,7 @@ module ActiveRecord
34
42
  include_result_metadata: true,
35
43
  transaction_id: id
36
44
  }).tap do |r|
45
+ @connected = true
37
46
  @affected_rows = affected_rows_result(r)
38
47
  @last_id = last_id_result(r)
39
48
  end
@@ -100,6 +109,22 @@ module ActiveRecord
100
109
  field.long_value || field.string_value || field.double_value
101
110
  end
102
111
 
112
+ def auto_paused_retry
113
+ error_klass = Aws::RDSDataService::Errors::BadRequestException
114
+ error_msg = /last packet sent successfully to the server was/
115
+ retry_msg = 'Aurora auto paused, retrying...'
116
+ on_retry = Proc.new { sleep(1) ; ::Rails.logger.info(retry_msg) }
117
+ Retriable.retriable({
118
+ on: { error_klass => error_msg },
119
+ on_retry: on_retry,
120
+ tries: auto_paused_retry_count
121
+ }) { yield }
122
+ end
123
+
124
+ def auto_paused_retry_count
125
+ 10
126
+ end
127
+
103
128
  def debug_transactions(name, id = 'NOID')
104
129
  return unless @debug_transactions
105
130
  ActiveRecord::Base.logger.debug " \e[36m#{name} #{id} #{object_id}\e[0m"
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  module AuroraServerless
4
- VERSION = "5.2.1"
4
+ VERSION = "5.2.2"
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,163 @@
1
+ ENV['AASA_ENV'] = 'test'
2
+ require 'bundler/setup'
3
+ Bundler.require :default, :development
4
+ Dotenv.load('.env')
5
+ require 'minitest/spec'
6
+ require 'minitest/autorun'
7
+ require 'minitest/retry'
8
+ require 'minitest/reporters'
9
+ require_relative 'support/aasa_mysqlpatch'
10
+ require 'cases/helper' unless ENV['TEST_FILES'] || ENV['ONLY_AASA']
11
+ require_relative 'support/aasa_coerceable'
12
+ require_relative 'support/aasa_env'
13
+ require_relative 'support/aasa_fixtures'
14
+ require_relative 'support/aasa_minitest'
15
+ Rails.backtrace_cleaner.remove_silencers! if ENV['REMOVE_SILENCERS']
16
+
17
+ module ActiveRecord
18
+ module ConnectionAdapters
19
+ module AuroraServerless
20
+ class TestCase < Minitest::Spec
21
+
22
+ before { setup_table }
23
+ after { drop_table }
24
+
25
+ private
26
+
27
+ def client
28
+ @client ||= AuroraServerless::Client.new(
29
+ 'activerecord_unittest',
30
+ ENV['AASA_RESOURCE_ARN'],
31
+ ENV['AASA_SECRET_ARN']
32
+ )
33
+ end
34
+
35
+ def execute(sql)
36
+ r = client.execute_statement(sql)
37
+ # TODO: [PG] Make this a conditional result wrapper.
38
+ AuroraServerless::Mysql2::Result.new(r)
39
+ end
40
+
41
+ def setup_table
42
+ # TODO: [PG] Make this a conditional for constant SQL.
43
+ execute MYSQL_CREATE_TABLE_SQL
44
+ execute MYSQL_INSERT_SQL
45
+ end
46
+
47
+ def drop_table
48
+ execute 'DROP TABLE IF EXISTS aurora_test'
49
+ end
50
+
51
+ MYSQL_CREATE_TABLE_SQL = %[
52
+ CREATE TABLE IF NOT EXISTS aurora_test (
53
+ id int NOT NULL AUTO_INCREMENT,
54
+ PRIMARY KEY (id),
55
+ null_test VARCHAR(10),
56
+ bit_test BIT,
57
+ tiny_int_test TINYINT,
58
+ small_int_test SMALLINT,
59
+ medium_int_test MEDIUMINT,
60
+ int_test INT,
61
+ big_int_test BIGINT,
62
+ float_test FLOAT(10,3),
63
+ float_zero_test FLOAT(10,3),
64
+ double_test DOUBLE(10,3),
65
+ decimal_test DECIMAL(10,3),
66
+ decimal_zero_test DECIMAL(10,3),
67
+ date_test DATE,
68
+ date_time_test DATETIME,
69
+ timestamp_test TIMESTAMP,
70
+ time_test TIME,
71
+ year_test YEAR(4),
72
+ char_test CHAR(10),
73
+ varchar_test VARCHAR(10),
74
+ binary_test BINARY(10),
75
+ varbinary_test VARBINARY(10),
76
+ tiny_blob_test TINYBLOB,
77
+ tiny_text_test TINYTEXT,
78
+ blob_test BLOB,
79
+ text_test TEXT,
80
+ medium_blob_test MEDIUMBLOB,
81
+ medium_text_test MEDIUMTEXT,
82
+ long_blob_test LONGBLOB,
83
+ long_text_test LONGTEXT,
84
+ enum_test ENUM('val1', 'val2'),
85
+ set_test SET('val1', 'val2')
86
+ ) DEFAULT CHARSET=utf8
87
+ ]
88
+
89
+ MYSQL_INSERT_SQL = %[
90
+ INSERT INTO aurora_test (
91
+ null_test,
92
+ bit_test,
93
+ tiny_int_test,
94
+ small_int_test,
95
+ medium_int_test,
96
+ int_test,
97
+ big_int_test,
98
+ float_test,
99
+ float_zero_test,
100
+ double_test,
101
+ decimal_test,
102
+ decimal_zero_test,
103
+ date_test,
104
+ date_time_test,
105
+ timestamp_test,
106
+ time_test,
107
+ year_test,
108
+ char_test,
109
+ varchar_test,
110
+ binary_test,
111
+ varbinary_test,
112
+ tiny_blob_test,
113
+ tiny_text_test,
114
+ blob_test,
115
+ text_test,
116
+ medium_blob_test,
117
+ medium_text_test,
118
+ long_blob_test,
119
+ long_text_test,
120
+ enum_test,
121
+ set_test
122
+ )
123
+ VALUES (
124
+ NULL,
125
+ 1,
126
+ 5,
127
+ 32766,
128
+ 8388606,
129
+ 2147483646,
130
+ 9223372036854775806,
131
+ 156.68449197860963,
132
+ 0.0,
133
+ 606682.8877005348,
134
+ 676254.5454545454,
135
+ 0,
136
+ '2010-4-4',
137
+ '2010-4-4 11:44:00',
138
+ '2010-4-4 11:44:00',
139
+ '11:44:00',
140
+ 2019,
141
+ 'abcdefg',
142
+ 'abcdefg',
143
+ 'abcdefg',
144
+ 'abcdefg',
145
+ 'abcdefg',
146
+ 'abcdefg',
147
+ 'abcdefg',
148
+ 'abcdefg',
149
+ 'abcdefg',
150
+ 'abcdefg',
151
+ 'abcdefg',
152
+ 'abcdefg',
153
+ 'val1',
154
+ 'val1,val2'
155
+ )
156
+ ]
157
+
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ require "mocha/setup"
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import "source-map-support/register";
3
+ import cdk = require("@aws-cdk/core");
4
+ import { AuroraServerlessStack } from "../lib/aurora-serverless-stack";
5
+
6
+ const APP = new cdk.App();
7
+
8
+ new AuroraServerlessStack(APP, "AuroraServerlessStack", {
9
+ stackName: "aasa-mysql-unit",
10
+ description: "Test customink/activerecord-aurora-serverless-adapter.",
11
+ tags: {
12
+ env: "dev",
13
+ group: "shared",
14
+ application: "activerecord-aurora-serverless-adapter",
15
+ owner: "kcollins"
16
+ }
17
+ });
@@ -0,0 +1,3 @@
1
+ {
2
+ "@aws-cdk/core:enableStackNameDuplicates": "true"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "app": "ts-node bin/aurora-serverless.ts"
3
+ }
@@ -0,0 +1,194 @@
1
+ // Thanks to https://almirzulic.com/posts/create-serverless-aurora-cluster-with-cdk/
2
+
3
+ import { CfnOutput, Construct, Stack, StackProps } from "@aws-cdk/core";
4
+ import { Vpc, SubnetType } from "@aws-cdk/aws-ec2";
5
+ import {
6
+ CfnDBCluster,
7
+ CfnDBSubnetGroup,
8
+ CfnDBClusterProps,
9
+ CfnDBClusterParameterGroup
10
+ } from "@aws-cdk/aws-rds";
11
+ import { CfnSecret, CfnSecretProps } from "@aws-cdk/aws-secretsmanager";
12
+ import { User, Policy, PolicyStatement, CfnAccessKey } from "@aws-cdk/aws-iam";
13
+
14
+ const MASTER_USER = process.env.AASA_MASTER_USER;
15
+ const MASTER_PASS = process.env.AASA_MASTER_PASS;
16
+ const DB_NAME = "activerecord_unittest";
17
+ const DB_CLUSTER_ID = "aasa-mysql-unit";
18
+
19
+ function auroraProps(
20
+ dbName: string,
21
+ dbClusterId: string,
22
+ dbSubnetGroupName: string | undefined,
23
+ dbParamGroup: CfnDBClusterParameterGroup
24
+ ) {
25
+ return {
26
+ databaseName: dbName,
27
+ dbClusterIdentifier: dbClusterId,
28
+ engine: "aurora",
29
+ engineMode: "serverless",
30
+ masterUsername: MASTER_USER,
31
+ masterUserPassword: MASTER_PASS,
32
+ enableHttpEndpoint: true,
33
+ port: 3306,
34
+ dbSubnetGroupName: dbSubnetGroupName,
35
+ dbClusterParameterGroupName: dbParamGroup.ref,
36
+ scalingConfiguration: {
37
+ autoPause: true,
38
+ minCapacity: 1,
39
+ maxCapacity: 4,
40
+ secondsUntilAutoPause: 3600
41
+ }
42
+ } as CfnDBClusterProps;
43
+ }
44
+
45
+ function secretProps(aurora: CfnDBCluster, dbClusterId: string) {
46
+ return {
47
+ secretString: JSON.stringify({
48
+ username: MASTER_USER,
49
+ password: MASTER_PASS,
50
+ engine: "mysql",
51
+ host: aurora.attrEndpointAddress,
52
+ port: aurora.attrEndpointPort,
53
+ dbClusterIdentifier: dbClusterId
54
+ }),
55
+ description: "AASA Master Credentials."
56
+ } as CfnSecretProps;
57
+ }
58
+
59
+ export class AuroraServerlessStack extends Stack {
60
+ constructor(scope: Construct, id: string, props?: StackProps) {
61
+ super(scope, id, props);
62
+
63
+ // VPC
64
+
65
+ const vpc = new Vpc(this, "Vpc", {
66
+ cidr: "10.0.0.0/16",
67
+ natGateways: 0,
68
+ subnetConfiguration: [
69
+ { name: "aasa_isolated", subnetType: SubnetType.ISOLATED }
70
+ ]
71
+ });
72
+ const subnetIds: string[] = [];
73
+ vpc.isolatedSubnets.forEach(subnet => {
74
+ subnetIds.push(subnet.subnetId);
75
+ });
76
+
77
+ // SUBNET GROUP
78
+
79
+ const dbSubnetGroup: CfnDBSubnetGroup = new CfnDBSubnetGroup(
80
+ this,
81
+ "AuroraSubnetGroup",
82
+ {
83
+ dbSubnetGroupDescription: "Subnet group to AASA Aurora",
84
+ dbSubnetGroupName: "aasa-subnet-group",
85
+ subnetIds
86
+ }
87
+ );
88
+
89
+ // RDS PARAMETER GROUP
90
+
91
+ const dbParamGroup = new CfnDBClusterParameterGroup(
92
+ this,
93
+ "ParameterGroup",
94
+ {
95
+ family: "aurora5.6",
96
+ description: "Test customink/activerecord-aurora-serverless-adapter.",
97
+ parameters: {
98
+ innodb_large_prefix: "1",
99
+ innodb_file_per_table: "1",
100
+ innodb_file_format: "Barracuda",
101
+ character_set_client: "utf8mb4",
102
+ character_set_connection: "utf8mb4",
103
+ character_set_database: "utf8mb4",
104
+ character_set_results: "utf8mb4",
105
+ character_set_server: "utf8mb4",
106
+ collation_server: "utf8mb4_unicode_ci",
107
+ collation_connection: "utf8mb4_unicode_ci"
108
+ }
109
+ }
110
+ );
111
+ dbParamGroup.addDependsOn(dbSubnetGroup);
112
+
113
+ // AURORA SERVERLESS CLUSTERS
114
+
115
+ const aurora = new CfnDBCluster(
116
+ this,
117
+ "AuroraServerless",
118
+ auroraProps(
119
+ DB_NAME,
120
+ DB_CLUSTER_ID,
121
+ dbSubnetGroup.dbSubnetGroupName,
122
+ dbParamGroup
123
+ )
124
+ );
125
+ const aurora2 = new CfnDBCluster(
126
+ this,
127
+ "AuroraServerless2",
128
+ auroraProps(
129
+ `${DB_NAME}2`,
130
+ `${DB_CLUSTER_ID}2`,
131
+ dbSubnetGroup.dbSubnetGroupName,
132
+ dbParamGroup
133
+ )
134
+ );
135
+ aurora.addDependsOn(dbParamGroup);
136
+ aurora2.addDependsOn(dbParamGroup);
137
+ new CfnOutput(this, "AASAResourceArn", {
138
+ value: `arn:aws:rds:${this.region}:${this.account}:cluster:${DB_CLUSTER_ID}`
139
+ });
140
+ new CfnOutput(this, "AASAResourceArn2", {
141
+ value: `arn:aws:rds:${this.region}:${this.account}:cluster:${DB_CLUSTER_ID}2`
142
+ });
143
+
144
+ // SECRETS
145
+
146
+ const secret = new CfnSecret(
147
+ this,
148
+ "Secret",
149
+ secretProps(aurora, DB_CLUSTER_ID)
150
+ );
151
+ const secret2 = new CfnSecret(
152
+ this,
153
+ "Secret2",
154
+ secretProps(aurora2, `${DB_CLUSTER_ID}2`)
155
+ );
156
+ secret.addDependsOn(aurora);
157
+ secret2.addDependsOn(aurora2);
158
+ new CfnOutput(this, "AASASecretArn", {
159
+ value: secret.ref
160
+ });
161
+ new CfnOutput(this, "AASASecretArn2", {
162
+ value: secret2.ref
163
+ });
164
+
165
+ // TEST USER
166
+
167
+ const user = new User(this, "TestUser");
168
+ const policy = new Policy(this, "TestUserPolicy", {
169
+ statements: [
170
+ new PolicyStatement({
171
+ actions: ["rds-data:*"],
172
+ resources: [
173
+ `arn:aws:rds:${this.region}:${this.account}:cluster:${DB_CLUSTER_ID}*`,
174
+ `arn:aws:rds:${this.region}:${this.account}:cluster:${DB_CLUSTER_ID}2*`
175
+ ]
176
+ }),
177
+ new PolicyStatement({
178
+ actions: ["secretsmanager:*"],
179
+ resources: [`${secret.ref}*`, `${secret2.ref}*`]
180
+ })
181
+ ]
182
+ });
183
+ user.attachInlinePolicy(policy);
184
+ const key = new CfnAccessKey(this, "TestUserKey", {
185
+ userName: user.userName
186
+ });
187
+ new CfnOutput(this, "AASAUserAccessKeyId", {
188
+ value: key.ref
189
+ });
190
+ new CfnOutput(this, "AASAUserSecretAccessKey", {
191
+ value: key.attrSecretAccessKey
192
+ });
193
+ }
194
+ }