activerecord-aurora-serverless-adapter 6.0.0 → 6.0.1

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: 753090515e0b5d6dc65ac1fdabd712a1c7c673af742340d6e89c41464d1f5863
4
- data.tar.gz: d48a50c7bc45a756202b4377ee16693db6d1ea153ba52d89459a3cb64f85f726
3
+ metadata.gz: 0eb80620f3d44cff64b9263ffb94bec8cfc5507794fc4318892655a229ec3981
4
+ data.tar.gz: f4d986df84720fc51d62412328959fbdefa9e16203b9dfc537670fa50e9d54eb
5
5
  SHA512:
6
- metadata.gz: 418749f96453b0dedf58dc2183e4886f09180423c008ab2f7c0126f2db4abf031b9ac3f3df80cba3c2088b4a14a1798b3861df5f31cf0e0847ebcbf6f4c4092d
7
- data.tar.gz: 700cab49d4ea92f32c24b74c2cc1edee63a1c0ad42004db8dcc56560486144ea7a1491cee584d9fba60ffef3d1aad6619180fc8f97a4d987c5108465bb2846b8
6
+ metadata.gz: caa6fce9998662d5446e5f26c54afffa92c3560ce64cc79b43167a3eb10e426147a66750d064cdfcbad9331eb31346c24f61a49abb99463414b5a17123c08b8c
7
+ data.tar.gz: e28ee33496c94f9814b1a644dbf6a730b5db87bddcfe67cca0dcac3707d9564b444401562b192262d7e51eea3ef5d5677ebffbeac69735d1671c0ce186089fe4
@@ -83,9 +83,10 @@ GIT
83
83
  PATH
84
84
  remote: .
85
85
  specs:
86
- activerecord-aurora-serverless-adapter (6.0.0)
86
+ activerecord-aurora-serverless-adapter (6.0.1)
87
87
  activerecord (>= 6.0)
88
88
  aws-sdk-rdsdataservice
89
+ retriable
89
90
 
90
91
  GEM
91
92
  remote: https://rubygems.org/
@@ -96,8 +97,8 @@ GEM
96
97
  rake
97
98
  thor (>= 0.14.0)
98
99
  aws-eventstream (1.0.3)
99
- aws-partitions (1.260.0)
100
- aws-sdk-core (3.86.0)
100
+ aws-partitions (1.265.0)
101
+ aws-sdk-core (3.89.1)
101
102
  aws-eventstream (~> 1.0, >= 1.0.2)
102
103
  aws-partitions (~> 1, >= 1.239.0)
103
104
  aws-sigv4 (~> 1.1)
@@ -152,6 +153,7 @@ GEM
152
153
  rails-html-sanitizer (1.3.0)
153
154
  loofah (~> 2.3)
154
155
  rake (13.0.1)
156
+ retriable (3.1.2)
155
157
  ruby-progressbar (1.10.1)
156
158
  sprockets (4.0.0)
157
159
  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', '>= 6.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 = "6.0.0"
4
+ VERSION = "6.0.1"
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,160 @@
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 'cases/helper' unless ENV['TEST_FILES'] || ENV['ONLY_AASA']
10
+ require_relative 'support/aasa_coerceable'
11
+ require_relative 'support/aasa_env'
12
+ require_relative 'support/aasa_fixtures'
13
+ require_relative 'support/aasa_minitest'
14
+ Rails.backtrace_cleaner.remove_silencers! if ENV['REMOVE_SILENCERS']
15
+
16
+ module ActiveRecord
17
+ module ConnectionAdapters
18
+ module AuroraServerless
19
+ class TestCase < Minitest::Spec
20
+
21
+ before { setup_table }
22
+ after { drop_table }
23
+
24
+ private
25
+
26
+ def client
27
+ @client ||= AuroraServerless::Client.new(
28
+ 'activerecord_unittest',
29
+ ENV['AASA_RESOURCE_ARN'],
30
+ ENV['AASA_SECRET_ARN']
31
+ )
32
+ end
33
+
34
+ def execute(sql)
35
+ r = client.execute_statement(sql)
36
+ # TODO: [PG] Make this a conditional result wrapper.
37
+ AuroraServerless::Mysql2::Result.new(r)
38
+ end
39
+
40
+ def setup_table
41
+ # TODO: [PG] Make this a conditional for constant SQL.
42
+ execute MYSQL_CREATE_TABLE_SQL
43
+ execute MYSQL_INSERT_SQL
44
+ end
45
+
46
+ def drop_table
47
+ execute 'DROP TABLE IF EXISTS aurora_test'
48
+ end
49
+
50
+ MYSQL_CREATE_TABLE_SQL = %[
51
+ CREATE TABLE IF NOT EXISTS aurora_test (
52
+ id int NOT NULL AUTO_INCREMENT,
53
+ PRIMARY KEY (id),
54
+ null_test VARCHAR(10),
55
+ bit_test BIT,
56
+ tiny_int_test TINYINT,
57
+ small_int_test SMALLINT,
58
+ medium_int_test MEDIUMINT,
59
+ int_test INT,
60
+ big_int_test BIGINT,
61
+ float_test FLOAT(10,3),
62
+ float_zero_test FLOAT(10,3),
63
+ double_test DOUBLE(10,3),
64
+ decimal_test DECIMAL(10,3),
65
+ decimal_zero_test DECIMAL(10,3),
66
+ date_test DATE,
67
+ date_time_test DATETIME,
68
+ timestamp_test TIMESTAMP,
69
+ time_test TIME,
70
+ year_test YEAR(4),
71
+ char_test CHAR(10),
72
+ varchar_test VARCHAR(10),
73
+ binary_test BINARY(10),
74
+ varbinary_test VARBINARY(10),
75
+ tiny_blob_test TINYBLOB,
76
+ tiny_text_test TINYTEXT,
77
+ blob_test BLOB,
78
+ text_test TEXT,
79
+ medium_blob_test MEDIUMBLOB,
80
+ medium_text_test MEDIUMTEXT,
81
+ long_blob_test LONGBLOB,
82
+ long_text_test LONGTEXT,
83
+ enum_test ENUM('val1', 'val2'),
84
+ set_test SET('val1', 'val2')
85
+ ) DEFAULT CHARSET=utf8
86
+ ]
87
+
88
+ MYSQL_INSERT_SQL = %[
89
+ INSERT INTO aurora_test (
90
+ null_test,
91
+ bit_test,
92
+ tiny_int_test,
93
+ small_int_test,
94
+ medium_int_test,
95
+ int_test,
96
+ big_int_test,
97
+ float_test,
98
+ float_zero_test,
99
+ double_test,
100
+ decimal_test,
101
+ decimal_zero_test,
102
+ date_test,
103
+ date_time_test,
104
+ timestamp_test,
105
+ time_test,
106
+ year_test,
107
+ char_test,
108
+ varchar_test,
109
+ binary_test,
110
+ varbinary_test,
111
+ tiny_blob_test,
112
+ tiny_text_test,
113
+ blob_test,
114
+ text_test,
115
+ medium_blob_test,
116
+ medium_text_test,
117
+ long_blob_test,
118
+ long_text_test,
119
+ enum_test,
120
+ set_test
121
+ )
122
+ VALUES (
123
+ NULL,
124
+ 1,
125
+ 5,
126
+ 32766,
127
+ 8388606,
128
+ 2147483646,
129
+ 9223372036854775806,
130
+ 156.68449197860963,
131
+ 0.0,
132
+ 606682.8877005348,
133
+ 676254.5454545454,
134
+ 0,
135
+ '2010-4-4',
136
+ '2010-4-4 11:44:00',
137
+ '2010-4-4 11:44:00',
138
+ '11:44:00',
139
+ 2019,
140
+ 'abcdefg',
141
+ 'abcdefg',
142
+ 'abcdefg',
143
+ 'abcdefg',
144
+ 'abcdefg',
145
+ 'abcdefg',
146
+ 'abcdefg',
147
+ 'abcdefg',
148
+ 'abcdefg',
149
+ 'abcdefg',
150
+ 'abcdefg',
151
+ 'abcdefg',
152
+ 'val1',
153
+ 'val1,val2'
154
+ )
155
+ ]
156
+
157
+ end
158
+ end
159
+ end
160
+ end
@@ -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
+ }