aws_advanced_ruby_driver_wrapper 1.0.0 → 1.1.0
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 +4 -4
- data/CHANGELOG.md +48 -2
- data/README.md +115 -6
- data/THIRD-PARTY-LICENSES +70 -11
- data/aws_advanced_ruby_driver_wrapper.gemspec +9 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_action_cable_postgresql_support.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_connection_handler.rb +47 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +23 -9
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +65 -10
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_trilogy_adapter.rb +80 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/type_adapter_alias.rb +61 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +48 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +11 -4
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql2_driver_dialect.rb +129 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +9 -81
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +28 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/trilogy_driver_dialect.rb +118 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +16 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +10 -5
- data/lib/aws_advanced_ruby_driver_wrapper/errors/trilogy_error_handler.rb +127 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +1 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +11 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +14 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +6 -0
- data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +27 -9
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +125 -7
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/drop_blue_connections_marker.rb +84 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +3 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +7 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +68 -41
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +4 -2
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +5 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +7 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +10 -13
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +95 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +11 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +4 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +14 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +68 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/federated_auth/base_saml_auth_plugin.rb +266 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/federated_auth/federated_auth_plugin.rb +134 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/federated_auth/okta_auth_plugin.rb +113 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +30 -9
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +16 -2
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +4 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +6 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +13 -4
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +29 -6
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql/trigger_mysql.sql +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql/trigger_pg.sql +54 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +23 -1
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +49 -23
- data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +63 -1
- data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +3 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +20 -7
- data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +34 -8
- data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +23 -4
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +80 -29
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +10 -2
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +4 -1
- data/lib/aws_advanced_ruby_driver_wrapper/trilogy.rb +167 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +25 -15
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +18 -7
- data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +2 -2
- data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +13 -4
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +15 -31
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +55 -76
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_text.rb +215 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +11 -20
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +2 -3
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +21 -14
- data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +2 -1
- data/lib/aws_advanced_ruby_driver_wrapper/utils/saml_utils.rb +133 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +29 -8
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/null_telemetry.rb +89 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_context.rb +124 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/{ar_constants.rb → telemetry/open_telemetry_counter.rb} +19 -5
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_factory.rb +122 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_gauge.rb +36 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/telemetry_const.rb +50 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/telemetry_factory.rb +143 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +9 -2
- data/lib/aws_advanced_ruby_driver_wrapper/version.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper.rb +47 -2
- metadata +57 -3
|
@@ -54,8 +54,41 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
54
54
|
type: String)
|
|
55
55
|
DIALECT = WrapperProperty.new(:wrapper_dialect, 'The database dialect identifier for the database in use.', type: String)
|
|
56
56
|
AWS_CREDENTIALS_PROVIDER = WrapperProperty.new(:aws_credentials_provider,
|
|
57
|
-
'AWS credentials provider for
|
|
57
|
+
'AWS credentials provider for the plugins that call AWS services',
|
|
58
58
|
default_value: nil)
|
|
59
|
+
# -- Telemetry --
|
|
60
|
+
ENABLE_TELEMETRY = WrapperProperty.new(
|
|
61
|
+
:enable_telemetry,
|
|
62
|
+
'Enables telemetry and observability of the wrapper.',
|
|
63
|
+
default_value: false,
|
|
64
|
+
type: :boolean
|
|
65
|
+
)
|
|
66
|
+
TELEMETRY_SUBMIT_TOP_LEVEL = WrapperProperty.new(
|
|
67
|
+
:telemetry_submit_top_level,
|
|
68
|
+
'Submit the traces of wrapper calls as top-level traces, rather than only as children of a trace already in progress.',
|
|
69
|
+
default_value: false,
|
|
70
|
+
type: :boolean
|
|
71
|
+
)
|
|
72
|
+
TELEMETRY_TRACES_BACKEND = WrapperProperty.new(
|
|
73
|
+
:telemetry_traces_backend,
|
|
74
|
+
'Method to export the telemetry traces of the wrapper. Valid values are otlp and none.',
|
|
75
|
+
default_value: nil,
|
|
76
|
+
type: String
|
|
77
|
+
)
|
|
78
|
+
TELEMETRY_METRICS_BACKEND = WrapperProperty.new(
|
|
79
|
+
:telemetry_metrics_backend,
|
|
80
|
+
'Method to export the telemetry metrics of the wrapper. Valid values are otlp and none.',
|
|
81
|
+
default_value: nil,
|
|
82
|
+
type: String
|
|
83
|
+
)
|
|
84
|
+
TELEMETRY_FAILOVER_ADDITIONAL_TOP_TRACE = WrapperProperty.new(
|
|
85
|
+
:telemetry_failover_additional_top_trace,
|
|
86
|
+
'Also submit the trace of each failover as a top-level trace of its own. Only a failover that was ' \
|
|
87
|
+
'traced is submitted: one inside a trace in progress, or inside a wrapper call that was submitted ' \
|
|
88
|
+
'as a top-level trace.',
|
|
89
|
+
default_value: false,
|
|
90
|
+
type: :boolean
|
|
91
|
+
)
|
|
59
92
|
# -- Failover --
|
|
60
93
|
FAILOVER_TIMEOUT_SEC = WrapperProperty.new(
|
|
61
94
|
:failover_timeout_sec,
|
|
@@ -159,6 +192,15 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
159
192
|
default_value: 0.1, type: Float, validator: POSITIVE_FLOAT)
|
|
160
193
|
BG_SWITCHOVER_TIMEOUT_SEC = WrapperProperty.new(:bg_switchover_timeout_sec, 'Blue/Green Deployment switchover timeout (in seconds).',
|
|
161
194
|
default_value: 180.0, type: Float, validator: POSITIVE_FLOAT)
|
|
195
|
+
BG_DROP_BLUE_CONNECTIONS = WrapperProperty.new(
|
|
196
|
+
:bg_drop_blue_connections,
|
|
197
|
+
'Whether to invalidate connections that were opened to the old blue environment once a Blue/Green ' \
|
|
198
|
+
'switchover completes. When enabled, the next use of such a connection closes it: with the failover ' \
|
|
199
|
+
'plugin enabled it reconnects through Blue/Green routing and raises FailoverSuccessError, and without ' \
|
|
200
|
+
'failover the driver raises its closed-connection error. Either way the caller gets an explicit error ' \
|
|
201
|
+
'instead of a stale read from old blue.',
|
|
202
|
+
default_value: true, type: :boolean
|
|
203
|
+
)
|
|
162
204
|
|
|
163
205
|
# -- IAM Authentication --
|
|
164
206
|
IAM_HOST = WrapperProperty.new(:iam_host, 'Overrides the host used to generate the IAM token', default_value: nil, type: String)
|
|
@@ -171,6 +213,26 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
171
213
|
IAM_ACCESS_TOKEN_PROPERTY_NAME = WrapperProperty.new(:iam_access_token_property_name, 'Property name used to pass the IAM token',
|
|
172
214
|
default_value: :password, type: Symbol)
|
|
173
215
|
|
|
216
|
+
# -- Federated / Okta Authentication (SAML) --
|
|
217
|
+
IDP_ENDPOINT = WrapperProperty.new(:idp_endpoint, 'The hosting URL of the Identity Provider', default_value: nil, type: String)
|
|
218
|
+
IDP_PORT = WrapperProperty.new(:idp_port, 'The hosting port of the Identity Provider', default_value: 443, type: Integer)
|
|
219
|
+
IDP_USERNAME = WrapperProperty.new(:idp_username, 'The federated user name', default_value: nil, type: String)
|
|
220
|
+
IDP_PASSWORD = WrapperProperty.new(:idp_password, 'The federated user password', default_value: nil, type: String)
|
|
221
|
+
IAM_ROLE_ARN = WrapperProperty.new(:iam_role_arn, 'The ARN of the IAM Role that is to be assumed', default_value: nil, type: String)
|
|
222
|
+
IAM_IDP_ARN = WrapperProperty.new(:iam_idp_arn, 'The ARN of the Identity Provider', default_value: nil, type: String)
|
|
223
|
+
RP_IDENTIFIER = WrapperProperty.new(:rp_identifier, 'The relying party identifier', default_value: 'urn:amazon:webservices',
|
|
224
|
+
type: String)
|
|
225
|
+
DB_USER = WrapperProperty.new(:db_user, 'The database user used to access the database', default_value: nil, type: String)
|
|
226
|
+
SSL_INSECURE = WrapperProperty.new(:ssl_insecure, "Whether the IdP server's TLS certificate chain should not be verified",
|
|
227
|
+
default_value: false, type: :boolean)
|
|
228
|
+
APP_ID = WrapperProperty.new(:app_id, 'The ID of the AWS application configured on Okta', default_value: nil, type: String)
|
|
229
|
+
HTTP_CLIENT_SOCKET_TIMEOUT_SEC = WrapperProperty.new(:http_client_socket_timeout_sec,
|
|
230
|
+
'Socket timeout in seconds for IdP HTTP requests',
|
|
231
|
+
default_value: 60.0, type: Float, validator: POSITIVE_FLOAT)
|
|
232
|
+
HTTP_CLIENT_CONNECT_TIMEOUT_SEC = WrapperProperty.new(:http_client_connect_timeout_sec,
|
|
233
|
+
'Connect timeout in seconds for IdP HTTP requests',
|
|
234
|
+
default_value: 60.0, type: Float, validator: POSITIVE_FLOAT)
|
|
235
|
+
|
|
174
236
|
# -- Initial Connection Strategy --
|
|
175
237
|
INITIAL_CONNECTION_SUBSTITUTE_HOST = WrapperProperty.new(
|
|
176
238
|
:initial_connection_substitute_host,
|
|
@@ -46,6 +46,9 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
46
46
|
CONNECTION_ABANDON_RESULTS = define('connection.abandon_results!', check_bounded_connection: true)
|
|
47
47
|
CONNECTION_SET_SERVER_OPTION = define('connection.set_server_option', check_bounded_connection: false)
|
|
48
48
|
|
|
49
|
+
# -- Connection methods (trilogy-specific) --
|
|
50
|
+
CONNECTION_CHANGE_DB = define('connection.change_db', check_bounded_connection: false)
|
|
51
|
+
|
|
49
52
|
# -- Connection methods (pg-specific) --
|
|
50
53
|
CONNECTION_EXEC = define('connection.exec', check_bounded_connection: false)
|
|
51
54
|
CONNECTION_ASYNC_EXEC = define('connection.async_exec', check_bounded_connection: false)
|
|
@@ -61,7 +61,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
61
61
|
}.freeze
|
|
62
62
|
|
|
63
63
|
# @param connection_service [ConnectionService]
|
|
64
|
-
# @param driver_name [Symbol] :mysql2 or :postgresql
|
|
64
|
+
# @param driver_name [Symbol] :mysql2, :trilogy or :postgresql
|
|
65
65
|
def initialize(connection_service, driver_name)
|
|
66
66
|
@connection_service = connection_service
|
|
67
67
|
@dialect_cache = {}
|
|
@@ -164,6 +164,21 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
164
164
|
@error_handler.read_only_error?(error)
|
|
165
165
|
end
|
|
166
166
|
|
|
167
|
+
# Whether the driver reported the error as a database error (it carries a SQLSTATE, or for
|
|
168
|
+
# trilogy a MySQL error code). Not a strict server-vs-client split; see ErrorHandler#database_error?.
|
|
169
|
+
# @param error [Exception]
|
|
170
|
+
# @return [Boolean]
|
|
171
|
+
def database_error?(error)
|
|
172
|
+
@error_handler.database_error?(error)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Whether the error is a schema/syntax error (SQLSTATE class '42' or its per-driver equivalent).
|
|
176
|
+
# @param error [Exception]
|
|
177
|
+
# @return [Boolean]
|
|
178
|
+
def schema_error?(error)
|
|
179
|
+
@error_handler.schema_error?(error)
|
|
180
|
+
end
|
|
181
|
+
|
|
167
182
|
private
|
|
168
183
|
|
|
169
184
|
# Resolves the initial database dialect from the initial connection info.
|
|
@@ -205,12 +220,10 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
205
220
|
|
|
206
221
|
rds_type = Utils::RdsUtils.identify_rds_type(host)
|
|
207
222
|
|
|
208
|
-
@dialect_code =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
else
|
|
213
|
-
DialectCodes::UNKNOWN
|
|
223
|
+
@dialect_code = case @driver_dialect.database_engine
|
|
224
|
+
when :mysql then resolve_mysql_dialect(rds_type)
|
|
225
|
+
when :postgresql then resolve_pg_dialect(rds_type)
|
|
226
|
+
else DialectCodes::UNKNOWN
|
|
214
227
|
end
|
|
215
228
|
|
|
216
229
|
@db_dialect = dialect_for_code(@dialect_code)
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
17
|
require 'concurrent/map'
|
|
18
|
+
require_relative '../errors'
|
|
18
19
|
require_relative '../host/random_host_selector'
|
|
19
20
|
|
|
20
21
|
module AwsAdvancedRubyDriverWrapper
|
|
@@ -47,18 +48,45 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
47
48
|
# Register a non-default host selector. The selector is shared by every HostService in the process,
|
|
48
49
|
# so it must be safe to call from multiple threads.
|
|
49
50
|
#
|
|
51
|
+
# Strategy names are case-insensitive: they are normalized here and in #host_selector, so a
|
|
52
|
+
# selector registered as 'LeastLag' is found when a connection names 'leastlag'.
|
|
53
|
+
#
|
|
50
54
|
# @param name [String] strategy name
|
|
51
55
|
# @param selector [#select_host] any object responding to select_host(hosts, role, props)
|
|
52
56
|
def register_host_selector(name, selector)
|
|
53
|
-
|
|
57
|
+
normalized = normalize_strategy(name)
|
|
58
|
+
raise Errors::AwsError, "Cannot override default host selection strategy: '#{name}'" if DEFAULT_HOST_SELECTORS.key?(normalized)
|
|
54
59
|
|
|
55
|
-
@strategies[
|
|
60
|
+
@strategies[normalized] = selector
|
|
56
61
|
end
|
|
57
62
|
|
|
58
63
|
# @param name [String] strategy name
|
|
59
64
|
# @return [#select_host, nil] the registered selector, or nil if the name is unknown
|
|
60
65
|
def host_selector(name)
|
|
61
|
-
@strategies[name]
|
|
66
|
+
@strategies[normalize_strategy(name)]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Raises when no selector is registered under the given strategy name. Plugins call this when
|
|
70
|
+
# they are constructed, because they treat a selection failure during connect or failover as
|
|
71
|
+
# "no eligible host", which would otherwise hide a misspelled strategy.
|
|
72
|
+
#
|
|
73
|
+
# @param name [String] strategy name
|
|
74
|
+
def validate_strategy!(name)
|
|
75
|
+
raise unsupported_strategy_error(name) if host_selector(name).nil?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def normalize_strategy(name)
|
|
79
|
+
name.to_s.strip.downcase
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @param name [String] the strategy name as configured, reported without normalization
|
|
83
|
+
# @return [Errors::AwsError]
|
|
84
|
+
def unsupported_strategy_error(name)
|
|
85
|
+
Errors::AwsError.new(
|
|
86
|
+
"Unsupported host selection strategy: '#{name}'. Registered strategies: #{@strategies.keys.sort.join(', ')}. " \
|
|
87
|
+
'Register a custom strategy with AwsAdvancedRubyDriverWrapper::Services::HostService.register_host_selector ' \
|
|
88
|
+
'before opening a connection that uses it.'
|
|
89
|
+
)
|
|
62
90
|
end
|
|
63
91
|
|
|
64
92
|
# Removes every non-default host selector. For testing only.
|
|
@@ -76,9 +104,8 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
76
104
|
# @param props [Hash, nil]
|
|
77
105
|
# @return [Host::HostInfo]
|
|
78
106
|
def select_host(hosts, role, strategy, props = nil)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
raise Errors::AwsError, "Unsupported host selection strategy: '#{strategy}'" if selector.nil?
|
|
107
|
+
selector = self.class.host_selector(strategy)
|
|
108
|
+
raise self.class.unsupported_strategy_error(strategy) if selector.nil?
|
|
82
109
|
|
|
83
110
|
selector.select_host(hosts, role, props)
|
|
84
111
|
end
|
|
@@ -90,8 +117,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
90
117
|
def hosts
|
|
91
118
|
rules = @service_container.storage_service.get_if_registered(
|
|
92
119
|
:custom_endpoint_allowed_blocked,
|
|
93
|
-
@service_container.connection_service.initial_host_info&.url
|
|
94
|
-
register_access: false
|
|
120
|
+
@service_container.connection_service.initial_host_info&.url
|
|
95
121
|
)
|
|
96
122
|
return @all_hosts if rules.nil?
|
|
97
123
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
|
+
require 'concurrent'
|
|
17
18
|
require_relative '../monitoring/monitor_state'
|
|
18
19
|
require_relative '../logging'
|
|
19
20
|
require_relative '../utils/storage/sliding_expiration_cache'
|
|
@@ -36,7 +37,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
36
37
|
def initialize(event_publisher:)
|
|
37
38
|
@caches = {}
|
|
38
39
|
@lock = Mutex.new
|
|
39
|
-
@running = true
|
|
40
|
+
@running = Concurrent::AtomicBoolean.new(true)
|
|
40
41
|
@cleanup_thread = start_cleanup_thread
|
|
41
42
|
event_publisher.subscribe(
|
|
42
43
|
self,
|
|
@@ -112,7 +113,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
112
113
|
end
|
|
113
114
|
|
|
114
115
|
def shutdown(grace_period:)
|
|
115
|
-
@running
|
|
116
|
+
@running.make_false
|
|
116
117
|
begin
|
|
117
118
|
@cleanup_thread&.wakeup
|
|
118
119
|
rescue ThreadError
|
|
@@ -122,10 +123,28 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
122
123
|
stop_and_remove_all
|
|
123
124
|
end
|
|
124
125
|
|
|
126
|
+
# Resets this service in a forked child. Monitor threads do not survive a fork, so the inherited
|
|
127
|
+
# monitors are detached (keeping the parent's connections open) and forgotten, letting
|
|
128
|
+
# run_if_absent start live ones. A monitor that fails to detach is still forgotten. The cleanup
|
|
129
|
+
# thread is restarted unless it is still alive, so calling this again does not start a second one.
|
|
130
|
+
def restart_after_fork
|
|
131
|
+
@lock.synchronize { @caches.values }.each do |container|
|
|
132
|
+
container.cache.entries.each_key do |key|
|
|
133
|
+
container.cache.remove(key)&.release_after_fork
|
|
134
|
+
rescue StandardError => e
|
|
135
|
+
logger.warn("Failed to release monitor #{key} after fork: #{e.message}")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
return if @cleanup_thread&.alive?
|
|
139
|
+
|
|
140
|
+
@running.make_true
|
|
141
|
+
@cleanup_thread = start_cleanup_thread
|
|
142
|
+
end
|
|
143
|
+
|
|
125
144
|
# Processes events from the event publisher.
|
|
126
145
|
# @param event [Event] the event to process.
|
|
127
146
|
def process_event(event)
|
|
128
|
-
return unless event
|
|
147
|
+
return unless event.is_a?(Utils::Events::DataAccessEvent)
|
|
129
148
|
|
|
130
149
|
handle_data_access_event(event)
|
|
131
150
|
end
|
|
@@ -142,7 +161,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
142
161
|
|
|
143
162
|
def start_cleanup_thread
|
|
144
163
|
thread = Thread.new do
|
|
145
|
-
while @running
|
|
164
|
+
while @running.true?
|
|
146
165
|
sleep(CLEANUP_INTERVAL_SEC)
|
|
147
166
|
run_cleanup
|
|
148
167
|
end
|
|
@@ -19,10 +19,13 @@ require_relative '../errors'
|
|
|
19
19
|
require_relative '../property_definition'
|
|
20
20
|
require_relative '../ruby_method'
|
|
21
21
|
require_relative '../utils/sql_encoding'
|
|
22
|
+
require_relative '../utils/telemetry/telemetry_const'
|
|
22
23
|
require_relative '../plugins/default_plugin'
|
|
23
24
|
require_relative '../plugins/failover_plugin'
|
|
24
25
|
require_relative '../plugins/gdb/gdb_failover_plugin'
|
|
25
26
|
require_relative '../plugins/iam_auth_plugin'
|
|
27
|
+
require_relative '../plugins/federated_auth/federated_auth_plugin'
|
|
28
|
+
require_relative '../plugins/federated_auth/okta_auth_plugin'
|
|
26
29
|
require_relative '../plugins/initial_connection_strategy_plugin'
|
|
27
30
|
require_relative '../plugins/kms_encryption/kms_encryption_plugin'
|
|
28
31
|
require_relative '../plugins/secrets_manager_plugin'
|
|
@@ -44,6 +47,8 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
44
47
|
'failover' => Plugins::FailoverPlugin,
|
|
45
48
|
'gdb_failover' => Plugins::Gdb::GdbFailoverPlugin,
|
|
46
49
|
'iam' => Plugins::IamAuthPlugin,
|
|
50
|
+
'federated_auth' => Plugins::FederatedAuth::FederatedAuthPlugin,
|
|
51
|
+
'okta' => Plugins::FederatedAuth::OktaAuthPlugin,
|
|
47
52
|
'initial_connection' => Plugins::InitialConnectionStrategyPlugin,
|
|
48
53
|
'kms_encryption' => Plugins::KmsEncryptionPlugin,
|
|
49
54
|
'secrets_manager' => Plugins::SecretsManagerPlugin
|
|
@@ -60,9 +65,18 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
60
65
|
Plugins::Gdb::GdbFailoverPlugin => 500,
|
|
61
66
|
Plugins::IamAuthPlugin => 1800,
|
|
62
67
|
Plugins::SecretsManagerPlugin => 1900,
|
|
68
|
+
Plugins::FederatedAuth::FederatedAuthPlugin => 2000,
|
|
69
|
+
Plugins::FederatedAuth::OktaAuthPlugin => 2010,
|
|
63
70
|
Plugins::KmsEncryptionPlugin => 2050
|
|
64
71
|
}
|
|
65
72
|
|
|
73
|
+
# Plugins that authenticate by minting an RDS IAM token into the shared IAM token cache.
|
|
74
|
+
IAM_TOKEN_PLUGIN_CLASSES = [
|
|
75
|
+
Plugins::IamAuthPlugin,
|
|
76
|
+
Plugins::FederatedAuth::FederatedAuthPlugin,
|
|
77
|
+
Plugins::FederatedAuth::OktaAuthPlugin
|
|
78
|
+
].freeze
|
|
79
|
+
|
|
66
80
|
class << self
|
|
67
81
|
def register_plugin(plugin_code, plugin_class, weight: WEIGHT_RELATIVE_TO_PRIOR_PLUGIN)
|
|
68
82
|
@plugin_classes[plugin_code] = plugin_class
|
|
@@ -73,30 +87,35 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
73
87
|
end
|
|
74
88
|
|
|
75
89
|
def initialize(service_container)
|
|
90
|
+
@telemetry_factory = service_container.telemetry_factory
|
|
76
91
|
@plugins = load_plugins(service_container)
|
|
77
92
|
@pipeline_cache = {}
|
|
78
93
|
end
|
|
79
94
|
|
|
80
95
|
def connect(host_info, driver_props, is_initial_connection, plugin_to_skip: nil)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
@telemetry_factory.in_context('connect', Utils::Telemetry::TraceLevel::NESTED) do
|
|
97
|
+
execute_with_subscribed_plugins(
|
|
98
|
+
'connect',
|
|
99
|
+
lambda do |plugin, next_plugin_callable|
|
|
100
|
+
plugin.connect(host_info, driver_props, is_initial_connection, next_plugin_callable)
|
|
101
|
+
end,
|
|
102
|
+
NOOP_CALLABLE,
|
|
103
|
+
plugin_to_skip:
|
|
104
|
+
)
|
|
105
|
+
end
|
|
89
106
|
end
|
|
90
107
|
|
|
91
108
|
def internal_connect(host_info, driver_props, wrapper_props, is_initial_connection, plugin_to_skip: nil)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
109
|
+
@telemetry_factory.in_context('internal_connect', Utils::Telemetry::TraceLevel::NESTED) do
|
|
110
|
+
execute_with_subscribed_plugins(
|
|
111
|
+
'internal_connect',
|
|
112
|
+
lambda do |plugin, next_plugin_callable|
|
|
113
|
+
plugin.internal_connect(host_info, driver_props, wrapper_props, is_initial_connection, next_plugin_callable)
|
|
114
|
+
end,
|
|
115
|
+
NOOP_CALLABLE,
|
|
116
|
+
plugin_to_skip:
|
|
117
|
+
)
|
|
118
|
+
end
|
|
100
119
|
end
|
|
101
120
|
|
|
102
121
|
# The context of the call currently being executed, for plugins that need to know more about
|
|
@@ -140,15 +159,18 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
140
159
|
Thread.current[CURRENT_CALL_CONTEXT_KEY] = context
|
|
141
160
|
|
|
142
161
|
begin
|
|
143
|
-
|
|
144
|
-
method_name
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
162
|
+
@telemetry_factory.in_context(method_name, Utils::Telemetry::TraceLevel::TOP_LEVEL, record_result: true) do |telemetry_context|
|
|
163
|
+
telemetry_context.set_attribute(Utils::Telemetry::TelemetryConst::RUBY_CALL_ANNOTATION, method_name)
|
|
164
|
+
execute_with_subscribed_plugins(
|
|
165
|
+
method_name,
|
|
166
|
+
lambda do |plugin, next_plugin_callable|
|
|
167
|
+
# Read from the context rather than from args and block, so that a plugin which
|
|
168
|
+
# replaced either is honoured by the plugins after it and by the target method.
|
|
169
|
+
plugin.execute(method_name, next_plugin_callable, *context.args, **kwargs, &context.block)
|
|
170
|
+
end,
|
|
171
|
+
target_callable
|
|
172
|
+
)
|
|
173
|
+
end
|
|
152
174
|
ensure
|
|
153
175
|
Thread.current[CURRENT_CALL_CONTEXT_KEY] = previous_context
|
|
154
176
|
end
|
|
@@ -164,6 +186,12 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
164
186
|
@plugins.any?(plugin_class)
|
|
165
187
|
end
|
|
166
188
|
|
|
189
|
+
# Whether any plugin that mints RDS IAM tokens into the shared cache is active.
|
|
190
|
+
# @see IAM_TOKEN_PLUGIN_CLASSES
|
|
191
|
+
def iam_token_plugin_in_use?
|
|
192
|
+
IAM_TOKEN_PLUGIN_CLASSES.any? { |plugin_class| plugin_in_use?(plugin_class) }
|
|
193
|
+
end
|
|
194
|
+
|
|
167
195
|
private
|
|
168
196
|
|
|
169
197
|
def load_plugins(service_container)
|
|
@@ -185,7 +213,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
185
213
|
end
|
|
186
214
|
|
|
187
215
|
def ensure_single_auth_plugin(plugin_code_list)
|
|
188
|
-
auth_plugins_used = plugin_code_list & %w[iam secrets_manager].freeze
|
|
216
|
+
auth_plugins_used = plugin_code_list & %w[iam secrets_manager federated_auth okta].freeze
|
|
189
217
|
|
|
190
218
|
return unless auth_plugins_used.length > 1
|
|
191
219
|
|
|
@@ -245,29 +273,52 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
245
273
|
|
|
246
274
|
# Builds the plugin pipeline function chain. The pipeline allows plugins to perform logic both before and after
|
|
247
275
|
# the target driver function is called.
|
|
276
|
+
#
|
|
277
|
+
# When traces are recorded, each plugin's part of the call is recorded as a span named after the
|
|
278
|
+
# plugin. That is decided here, once per pipeline, so a pipeline built without traces does no
|
|
279
|
+
# tracing work at all.
|
|
248
280
|
def make_pipeline(target_method_name)
|
|
249
281
|
subscribed_plugins = @plugins.select do |p|
|
|
250
282
|
p.subscribed_methods.include?('*') || p.subscribed_methods.include?(target_method_name)
|
|
251
283
|
end
|
|
252
284
|
raise Errors::AwsError, 'Plugin pipeline is nil' if subscribed_plugins.empty?
|
|
253
285
|
|
|
286
|
+
traced = @telemetry_factory.traces_in_use?
|
|
254
287
|
base_plugin = subscribed_plugins.last
|
|
288
|
+
base_plugin_name = plugin_name(base_plugin) if traced
|
|
255
289
|
base = lambda do |plugin_callable, target_driver_callable, *, **|
|
|
256
|
-
|
|
290
|
+
if traced
|
|
291
|
+
call_traced(plugin_callable, base_plugin, base_plugin_name, target_driver_callable)
|
|
292
|
+
else
|
|
293
|
+
plugin_callable.call(base_plugin, target_driver_callable)
|
|
294
|
+
end
|
|
257
295
|
end
|
|
258
296
|
|
|
259
297
|
subscribed_plugins[0...-1].reverse.reduce(base) do |next_plugin_callable, plugin|
|
|
298
|
+
name = plugin_name(plugin) if traced
|
|
260
299
|
lambda do |plugin_callable, target_driver_callable, method_name, plugin_to_skip|
|
|
261
300
|
if plugin_to_skip == plugin
|
|
262
301
|
next_plugin_callable.call(plugin_callable, target_driver_callable, method_name, plugin_to_skip)
|
|
263
302
|
else
|
|
264
|
-
|
|
303
|
+
call_next = lambda do
|
|
265
304
|
next_plugin_callable.call(plugin_callable, target_driver_callable, method_name, plugin_to_skip)
|
|
266
|
-
end
|
|
305
|
+
end
|
|
306
|
+
traced ? call_traced(plugin_callable, plugin, name, call_next) : plugin_callable.call(plugin, call_next)
|
|
267
307
|
end
|
|
268
308
|
end
|
|
269
309
|
end
|
|
270
310
|
end
|
|
311
|
+
|
|
312
|
+
# A plugin's span is not marked as failed when the call raises: the error passes out through every
|
|
313
|
+
# plugin's span, and is recorded on the call's top-level span instead.
|
|
314
|
+
def call_traced(plugin_callable, plugin, name, next_plugin_callable)
|
|
315
|
+
@telemetry_factory.in_context(name, Utils::Telemetry::TraceLevel::NESTED) { plugin_callable.call(plugin, next_plugin_callable) }
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# The plugin's code (such as +failover+), or the class name of a plugin that has none.
|
|
319
|
+
def plugin_name(plugin)
|
|
320
|
+
self.class.plugin_classes.key(plugin.class) || plugin.class.name&.split('::')&.last || plugin.class.to_s
|
|
321
|
+
end
|
|
271
322
|
end
|
|
272
323
|
end
|
|
273
324
|
end
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
|
+
require_relative '../utils/telemetry/null_telemetry'
|
|
18
|
+
|
|
17
19
|
module AwsAdvancedRubyDriverWrapper
|
|
18
20
|
module Services
|
|
19
21
|
ServiceContainer = Struct.new(
|
|
@@ -24,7 +26,13 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
24
26
|
:plugin_manager,
|
|
25
27
|
:session_state_service,
|
|
26
28
|
:storage_service,
|
|
27
|
-
:monitor_service
|
|
28
|
-
|
|
29
|
+
:monitor_service,
|
|
30
|
+
:telemetry_factory
|
|
31
|
+
) do
|
|
32
|
+
# A container that was given no telemetry factory records no telemetry.
|
|
33
|
+
def telemetry_factory
|
|
34
|
+
self[:telemetry_factory] || Utils::Telemetry::NullTelemetryFactory::INSTANCE
|
|
35
|
+
end
|
|
36
|
+
end
|
|
29
37
|
end
|
|
30
38
|
end
|
|
@@ -23,6 +23,7 @@ require_relative 'monitor_service'
|
|
|
23
23
|
require_relative 'session_state_service'
|
|
24
24
|
require_relative '../utils/storage/storage_service'
|
|
25
25
|
require_relative '../utils/events/batching_event_publisher'
|
|
26
|
+
require_relative '../utils/telemetry/telemetry_factory'
|
|
26
27
|
|
|
27
28
|
module AwsAdvancedRubyDriverWrapper
|
|
28
29
|
module Services
|
|
@@ -50,9 +51,10 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
module ServiceUtility
|
|
53
|
-
def self.create_standard_container(config)
|
|
54
|
+
def self.create_standard_container(config, telemetry_factory: nil)
|
|
54
55
|
container = ServiceContainer.new
|
|
55
56
|
container.event_publisher = CoreServices.event_publisher
|
|
57
|
+
container.telemetry_factory = telemetry_factory || Utils::Telemetry::TelemetryFactory.new(config.wrapper_props)
|
|
56
58
|
container.connection_service = ConnectionService.new(container, config)
|
|
57
59
|
container.dialect_service = DialectService.new(container.connection_service, config.driver_name)
|
|
58
60
|
container.host_service = HostService.new(container)
|
|
@@ -71,6 +73,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
71
73
|
container.event_publisher = parent_container.event_publisher
|
|
72
74
|
container.dialect_service = parent_container.dialect_service
|
|
73
75
|
container.host_service = parent_container.host_service
|
|
76
|
+
container.telemetry_factory = parent_container.telemetry_factory
|
|
74
77
|
container
|
|
75
78
|
end
|
|
76
79
|
end
|