activerecord-aurora-serverless-adapter 5.2.1 → 5.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }