aws_advanced_ruby_driver_wrapper 1.0.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 +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +175 -0
- data/NOTICE +1 -0
- data/README.md +168 -0
- data/THIRD-PARTY-LICENSES +473 -0
- data/aws_advanced_ruby_driver_wrapper.gemspec +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/custom_configuration.rb +58 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_mysql_dialect.rb +103 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_pg_dialect.rb +124 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/dialect_codes.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_mysql_dialect.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_pg_dialect.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_mysql_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/mysql_dialect.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/pg_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_mysql_dialect.rb +88 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/unknown_dialect.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/utils/dialect_utils.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +154 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +55 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +165 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +201 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +80 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/pg_error_handler.rb +126 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +59 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/connection_string_host_list_provider.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/global_aurora_host_list_provider.rb +65 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability.rb +24 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability_strategy.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_info.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_role.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/random_host_selector.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/rds_host_list_provider.rb +206 -0
- data/lib/aws_advanced_ruby_driver_wrapper/logging.rb +110 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +709 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/global_cluster_topology_monitor.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +57 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_state.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +429 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +205 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +132 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/iam_host_tracker.rb +84 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interim_status.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interval_rate.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase.rb +69 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_event_log.rb +85 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_time_info.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/role.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/reject_connect_routing.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +136 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +53 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +244 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_info.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +564 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +414 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_state.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_timer.rb +46 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +266 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +158 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/info.rb +111 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/member_list_type.rb +31 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/role.rb +45 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_mode.rb +43 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +467 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_mode.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +403 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +485 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/audit_logger.rb +157 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_cipher.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_encryption_config.rb +61 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/connection_source.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/data_key_cache.rb +220 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_algorithm.rb +75 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_config.rb +146 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_service.rb +391 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/error_context.rb +198 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/errors.rb +259 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +435 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +378 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_metadata.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +890 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_utility.rb +281 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/metadata_manager.rb +332 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sanitizer.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_name.rb +70 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_validator.rb +211 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql_runner.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/type_marker.rb +109 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +358 -0
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +659 -0
- data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +409 -0
- data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +122 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/connection_service.rb +143 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +267 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +199 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +186 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_call_context.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +273 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +78 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/session_state_service.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/accessible_regions.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aurora_topology_utils.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aws_credentials_utils.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +368 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/conversion_utils.rb +51 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +119 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/data_access_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/monitor_reset_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +185 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/host_list_utils.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +112 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/multi_az_topology_utils.rb +117 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +641 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +502 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_type.rb +35 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint_parser.rb +50 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +139 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_url_type.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_utils.rb +575 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +153 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_encoding.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_method_analyzer.rb +195 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/cache_entry.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/expiration_cache.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/sliding_expiration_cache.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +172 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +127 -0
- data/lib/aws_advanced_ruby_driver_wrapper/version.rb +19 -0
- data/lib/aws_advanced_ruby_driver_wrapper/wrapper_property.rb +64 -0
- data/lib/aws_advanced_ruby_driver_wrapper.rb +116 -0
- metadata +227 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require 'concurrent'
|
|
18
|
+
require_relative '../errors'
|
|
19
|
+
require_relative '../host/host_role'
|
|
20
|
+
require_relative '../host/host_availability'
|
|
21
|
+
require_relative '../logging'
|
|
22
|
+
require_relative '../property_definition'
|
|
23
|
+
require_relative '../utils/accessible_regions'
|
|
24
|
+
require_relative '../utils/rds_utils'
|
|
25
|
+
require_relative '../utils/rds_url_type'
|
|
26
|
+
|
|
27
|
+
module AwsAdvancedRubyDriverWrapper
|
|
28
|
+
module Plugins
|
|
29
|
+
class InitialConnectionStrategyPlugin
|
|
30
|
+
include Logging
|
|
31
|
+
|
|
32
|
+
SUBSCRIBED_METHODS = Set['connect'].freeze
|
|
33
|
+
|
|
34
|
+
SUBSTITUTION_STRATEGIES = {
|
|
35
|
+
'writer' => :substitute_writer,
|
|
36
|
+
'reader' => :substitute_reader,
|
|
37
|
+
'any' => :substitute_any,
|
|
38
|
+
'none' => :none
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
VERIFY_ROLES = {
|
|
42
|
+
'writer' => :writer,
|
|
43
|
+
'reader' => :reader,
|
|
44
|
+
'none' => :none
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
attr_reader :subscribed_methods
|
|
48
|
+
|
|
49
|
+
def initialize(service_container, props = ::Concurrent::Map.new)
|
|
50
|
+
@service_container = service_container
|
|
51
|
+
|
|
52
|
+
@retry_timeout_sec = PropertyDefinition::INITIAL_CONNECTION_RETRY_TIMEOUT_SEC.get_float(props)
|
|
53
|
+
@retry_interval_sec = PropertyDefinition::INITIAL_CONNECTION_RETRY_INTERVAL_SEC.get_float(props)
|
|
54
|
+
@wait_for_topology_sec = PropertyDefinition::INITIAL_CONNECTION_WAIT_FOR_TOPOLOGY_SEC.get_float(props)
|
|
55
|
+
@host_selector_strategy = PropertyDefinition::INITIAL_CONNECTION_HOST_SELECTOR_STRATEGY.get(props)
|
|
56
|
+
@accessible_regions = Utils::AccessibleRegions.parse(props)
|
|
57
|
+
@subscribed_methods = SUBSCRIBED_METHODS
|
|
58
|
+
|
|
59
|
+
parse_role_props(props)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def connect(host_info, driver_props, is_initial_connection, pipeline_callable)
|
|
63
|
+
return pipeline_callable.call unless is_initial_connection
|
|
64
|
+
|
|
65
|
+
host = host_info&.host
|
|
66
|
+
url_type = Utils::RdsUtils.identify_rds_type(host)
|
|
67
|
+
|
|
68
|
+
# Wait for the cluster topology to be discovered before deciding, if the caller opted in.
|
|
69
|
+
wait_for_topology_if_configured if cluster_url?(url_type)
|
|
70
|
+
|
|
71
|
+
# Classify a plain writer cluster URL as single-region, global-active, global-inactive, or unresolved.
|
|
72
|
+
classification = url_type == Utils::RdsUrlType::RDS_WRITER_CLUSTER ? classify_writer_cluster(host) : nil
|
|
73
|
+
substitution_strategy = determine_substitution_strategy(url_type, classification)
|
|
74
|
+
role_to_verify = determine_role_to_verify(url_type, classification, substitution_strategy)
|
|
75
|
+
|
|
76
|
+
# Only cluster-type endpoints are candidates for substitution/verification. Anything else
|
|
77
|
+
# (instance, proxy, IP, custom domain, ...) connects directly to the provided URL.
|
|
78
|
+
return pipeline_callable.call unless cluster_url?(url_type)
|
|
79
|
+
|
|
80
|
+
connect_with_retry(host_info, url_type, substitution_strategy, role_to_verify, driver_props, pipeline_callable)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def connect_with_retry(host_info, url_type, substitution_strategy, role_to_verify, driver_props, pipeline_callable)
|
|
86
|
+
deadline = monotonic_time + @retry_timeout_sec
|
|
87
|
+
conn = nil
|
|
88
|
+
success = false
|
|
89
|
+
|
|
90
|
+
begin
|
|
91
|
+
while monotonic_time < deadline
|
|
92
|
+
candidate_host = resolve_candidate_host(host_info, url_type, substitution_strategy)
|
|
93
|
+
|
|
94
|
+
begin
|
|
95
|
+
conn = open_connection_to(
|
|
96
|
+
candidate_host, host_info, substitution_strategy, driver_props, pipeline_callable
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if conn.nil?
|
|
100
|
+
sleep(@retry_interval_sec)
|
|
101
|
+
next
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if role_to_verify.nil?
|
|
105
|
+
success = true
|
|
106
|
+
return conn
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
conn_role = dialect_service.db_dialect.host_role(conn)
|
|
110
|
+
if conn_role == role_to_verify
|
|
111
|
+
success = true
|
|
112
|
+
return conn
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
host_service.force_refresh_host_list?
|
|
116
|
+
if role_to_verify == Host::HostRole::READER && !readers_in_topology?(host_service.all_hosts)
|
|
117
|
+
logger.warn('Reader verification expected but no readers exist in topology; accepting connection with writer role')
|
|
118
|
+
success = true
|
|
119
|
+
return conn
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
logger.debug("Connection to #{candidate_host&.host} has role #{conn_role}, expected #{role_to_verify}; retrying")
|
|
123
|
+
close_connection(conn)
|
|
124
|
+
conn = nil
|
|
125
|
+
sleep(@retry_interval_sec)
|
|
126
|
+
rescue StandardError => e
|
|
127
|
+
close_connection(conn)
|
|
128
|
+
conn = nil
|
|
129
|
+
|
|
130
|
+
raise if dialect_service.login_error?(e)
|
|
131
|
+
|
|
132
|
+
if dialect_service.network_error?(e)
|
|
133
|
+
host_service.set_availability(candidate_host, Host::HostAvailability::UNAVAILABLE) if candidate_host
|
|
134
|
+
# Back off before retrying, as the other retry paths do. Without this, a persistently
|
|
135
|
+
# unreachable topology (e.g. every instance down) busy-loops to the deadline, spinning the
|
|
136
|
+
# CPU and flooding the logs.
|
|
137
|
+
sleep(@retry_interval_sec)
|
|
138
|
+
next
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if dialect_service.read_only_error?(e) && substitution_strategy == :substitute_writer
|
|
142
|
+
sleep(@retry_interval_sec)
|
|
143
|
+
next
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
raise
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
ensure
|
|
150
|
+
close_connection(conn) unless success
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
raise Errors::AwsError,
|
|
154
|
+
"Initial connection strategy timed out after #{@retry_timeout_sec}s. " \
|
|
155
|
+
"Substitution: #{substitution_strategy}, verification: #{role_to_verify}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def resolve_candidate_host(original_host_info, url_type, substitution_strategy)
|
|
159
|
+
return original_host_info if substitution_strategy == :none
|
|
160
|
+
|
|
161
|
+
candidate = select_candidate_host(original_host_info, url_type, substitution_strategy)
|
|
162
|
+
return candidate if candidate && Utils::RdsUtils.rds_instance?(candidate.host)
|
|
163
|
+
|
|
164
|
+
# No instance URL available to substitute. This happens when topology hasn't been successfully queried yet.
|
|
165
|
+
# Fall back to connecting via the initial endpoint.
|
|
166
|
+
# Callers that want to wait for topology first opt in via INITIAL_CONNECTION_WAIT_FOR_TOPOLOGY_SEC (handled in #connect).
|
|
167
|
+
logger.debug("Unable to resolve a substitute instance host for strategy '#{substitution_strategy}'; \
|
|
168
|
+
connecting via the original endpoint '#{original_host_info&.host}'")
|
|
169
|
+
original_host_info
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# When INITIAL_CONNECTION_WAIT_FOR_TOPOLOGY_SEC is positive and only the initial (non-instance)
|
|
173
|
+
# endpoint is known, block up to the timeout for the topology monitor to discover instance URLs
|
|
174
|
+
# before making substitution/verification decisions. Limitation: force_refresh_host_list returns
|
|
175
|
+
# the initial host list when the dialect is not final, so topology may still be unavailable after waiting.
|
|
176
|
+
def wait_for_topology_if_configured
|
|
177
|
+
return unless @wait_for_topology_sec.positive?
|
|
178
|
+
return unless only_initial_endpoint_known?
|
|
179
|
+
|
|
180
|
+
host_service.force_refresh_host_list?(timeout_sec: @wait_for_topology_sec)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# True when the topology contains a single host that is not an instance URL, i.e. we only have
|
|
184
|
+
# the initial connection endpoint and the topology has not been queried yet.
|
|
185
|
+
def only_initial_endpoint_known?
|
|
186
|
+
hosts = host_service.all_hosts
|
|
187
|
+
hosts.size <= 1 && hosts.none? { |h| Utils::RdsUtils.rds_instance?(h.host) }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# True once real topology has been discovered: more than one host, or a single instance URL.
|
|
191
|
+
def topology_available?
|
|
192
|
+
hosts = host_service.all_hosts
|
|
193
|
+
return false if hosts.empty?
|
|
194
|
+
|
|
195
|
+
hosts.size > 1 || Utils::RdsUtils.rds_instance?(hosts.first.host)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def open_connection_to(candidate_host, original_host_info, substitution_strategy, driver_props, pipeline_callable)
|
|
199
|
+
if substitution_strategy == :none || candidate_host == original_host_info
|
|
200
|
+
conn = pipeline_callable.call
|
|
201
|
+
# Refresh topology in background when connecting via cluster endpoint
|
|
202
|
+
host_service.force_refresh_host_list? if substitution_strategy != :none
|
|
203
|
+
return conn
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
plugin_manager.connect(candidate_host, driver_props, true, plugin_to_skip: self)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# A writer cluster URL falls into one of these buckets, decided by the dialect first
|
|
210
|
+
# (authoritative) and topology second (only to split active vs inactive within a global cluster):
|
|
211
|
+
# :single_region - dialect is final and not global -> connected to the writer's own cluster
|
|
212
|
+
# :global_active - global dialect, topology shows the writer is in this endpoint's region
|
|
213
|
+
# :global_inactive - global dialect, topology shows the writer is in another region
|
|
214
|
+
# :unresolved - dialect not final, or global but topology can't tell us which cluster this is
|
|
215
|
+
def classify_writer_cluster(host)
|
|
216
|
+
return :unresolved unless dialect_service.dialect_final?
|
|
217
|
+
return :single_region unless dialect_service.db_dialect.global?
|
|
218
|
+
|
|
219
|
+
# Global cluster: use the confirmed cross-region topology to locate the writer.
|
|
220
|
+
return :unresolved unless topology_available?
|
|
221
|
+
|
|
222
|
+
writer = find_writer_in_topology
|
|
223
|
+
return :unresolved if writer.nil? || !Utils::RdsUtils.rds_instance?(writer.host)
|
|
224
|
+
|
|
225
|
+
Utils::RdsUtils.same_region?(writer.host, host) ? :global_active : :global_inactive
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def determine_substitution_strategy(url_type, classification)
|
|
229
|
+
# @explicit_substitution was parsed at init; only the URL-dependent validity is checked here.
|
|
230
|
+
if @explicit_substitution
|
|
231
|
+
validate_substitution_strategy(@explicit_substitution, url_type)
|
|
232
|
+
return @explicit_substitution
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
case url_type
|
|
236
|
+
when Utils::RdsUrlType::RDS_GLOBAL_WRITER_CLUSTER
|
|
237
|
+
:substitute_writer
|
|
238
|
+
when Utils::RdsUrlType::RDS_WRITER_CLUSTER
|
|
239
|
+
writer_cluster_substitution(classification)
|
|
240
|
+
when Utils::RdsUrlType::RDS_READER_CLUSTER
|
|
241
|
+
:substitute_reader
|
|
242
|
+
else
|
|
243
|
+
:none
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def determine_role_to_verify(url_type, classification, substitution_strategy)
|
|
248
|
+
role = resolve_verify_role(url_type, classification, substitution_strategy)
|
|
249
|
+
|
|
250
|
+
# :none is the explicit "skip verification" sentinel; normalize it to nil, which
|
|
251
|
+
# connect_with_retry treats as "no role to verify".
|
|
252
|
+
role == :none ? nil : role
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def resolve_verify_role(url_type, classification, substitution_strategy)
|
|
256
|
+
# @explicit_verify_role was parsed at init; only the URL-dependent validity is checked here.
|
|
257
|
+
if @explicit_verify_role
|
|
258
|
+
validate_verify_role(@explicit_verify_role, url_type)
|
|
259
|
+
return @explicit_verify_role
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
case url_type
|
|
263
|
+
when Utils::RdsUrlType::RDS_GLOBAL_WRITER_CLUSTER
|
|
264
|
+
Host::HostRole::WRITER
|
|
265
|
+
when Utils::RdsUrlType::RDS_WRITER_CLUSTER
|
|
266
|
+
writer_cluster_verification(classification, substitution_strategy)
|
|
267
|
+
when Utils::RdsUrlType::RDS_READER_CLUSTER
|
|
268
|
+
Host::HostRole::READER
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def writer_cluster_substitution(classification)
|
|
273
|
+
case classification
|
|
274
|
+
when :single_region, :global_active
|
|
275
|
+
:substitute_writer
|
|
276
|
+
when :global_inactive
|
|
277
|
+
# INITIAL_CONNECTION_INACTIVE_SUBSTITUTE_HOST governs inactive cluster endpoints. When unset,
|
|
278
|
+
# pass the endpoint through untouched. Users who want cross-region writer substitution must opt in explicitly.
|
|
279
|
+
@inactive_substitution || :none
|
|
280
|
+
else
|
|
281
|
+
# :unresolved - we don't know enough to substitute safely; connect via the original endpoint.
|
|
282
|
+
:none
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def writer_cluster_verification(classification, substitution_strategy)
|
|
287
|
+
case classification
|
|
288
|
+
when :single_region, :global_active
|
|
289
|
+
Host::HostRole::WRITER
|
|
290
|
+
when :global_inactive
|
|
291
|
+
# INITIAL_CONNECTION_INACTIVE_VERIFY_ROLE takes priority when set. When unset, verify writer only if we substituted
|
|
292
|
+
# a writer, which only happens when the user opted into substitution explicitly via INITIAL_CONNECTION_SUBSTITUTE_HOST
|
|
293
|
+
# or INITIAL_CONNECTION_INACTIVE_SUBSTITUTE_HOST. Otherwise, do not verify role.
|
|
294
|
+
if @inactive_verify_role
|
|
295
|
+
@inactive_verify_role
|
|
296
|
+
elsif substitution_strategy == :substitute_writer
|
|
297
|
+
Host::HostRole::WRITER
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
# :unresolved -> nil (no verification)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def select_candidate_host(original_host_info, url_type, substitution_strategy)
|
|
304
|
+
return original_host_info if substitution_strategy == :none
|
|
305
|
+
|
|
306
|
+
all_hosts = host_service.all_hosts
|
|
307
|
+
return nil if all_hosts.empty?
|
|
308
|
+
|
|
309
|
+
if substitution_strategy == :substitute_writer
|
|
310
|
+
filtered = Utils::AccessibleRegions.filter_by_region(all_hosts, @accessible_regions)
|
|
311
|
+
return filtered.find { |h| h.role == Host::HostRole::WRITER }
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
target_role = substitution_strategy == :substitute_reader ? Host::HostRole::READER : nil
|
|
315
|
+
|
|
316
|
+
available_hosts = Utils::AccessibleRegions.filter_by_region(host_service.hosts, @accessible_regions)
|
|
317
|
+
|
|
318
|
+
endpoint_region = url_type_has_region?(url_type) ? Utils::RdsUtils.rds_region(original_host_info.host) : nil
|
|
319
|
+
if endpoint_region
|
|
320
|
+
available_hosts = available_hosts.select do |h|
|
|
321
|
+
Utils::RdsUtils.rds_region(h.host)&.casecmp(endpoint_region)&.zero?
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
host_service.select_host(available_hosts, target_role, @host_selector_strategy)
|
|
326
|
+
rescue StandardError
|
|
327
|
+
nil
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def find_writer_in_topology
|
|
331
|
+
host_service.all_hosts.find { |h| h.role == Host::HostRole::WRITER }
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Cluster-type endpoints are the only ones eligible for substitution/verification.
|
|
335
|
+
def cluster_url?(url_type)
|
|
336
|
+
[
|
|
337
|
+
Utils::RdsUrlType::RDS_GLOBAL_WRITER_CLUSTER,
|
|
338
|
+
Utils::RdsUrlType::RDS_WRITER_CLUSTER,
|
|
339
|
+
Utils::RdsUrlType::RDS_READER_CLUSTER,
|
|
340
|
+
Utils::RdsUrlType::RDS_CUSTOM_CLUSTER
|
|
341
|
+
].include?(url_type)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Parse the substitution/verification props up front so a malformed value fails fast at wrapper construction.
|
|
345
|
+
# URL-dependent validity (e.g. 'writer' on a reader cluster) will checked in #connect.
|
|
346
|
+
# An unset prop parses to nil, an explicit 'none' parses to :none.
|
|
347
|
+
def parse_role_props(props)
|
|
348
|
+
raw_substitution = PropertyDefinition::INITIAL_CONNECTION_SUBSTITUTE_HOST.get(props)
|
|
349
|
+
@explicit_substitution = raw_substitution && parse_substitution_value(raw_substitution)
|
|
350
|
+
|
|
351
|
+
raw_verify_role = PropertyDefinition::INITIAL_CONNECTION_VERIFY_ROLE.get(props)
|
|
352
|
+
@explicit_verify_role = raw_verify_role && parse_verify_role_value(raw_verify_role)
|
|
353
|
+
|
|
354
|
+
raw_inactive_substitution = PropertyDefinition::INITIAL_CONNECTION_INACTIVE_SUBSTITUTE_HOST.get(props)
|
|
355
|
+
@inactive_substitution = raw_inactive_substitution && parse_inactive_substitution_value(raw_inactive_substitution)
|
|
356
|
+
|
|
357
|
+
raw_inactive_verify_role = PropertyDefinition::INITIAL_CONNECTION_INACTIVE_VERIFY_ROLE.get(props)
|
|
358
|
+
@inactive_verify_role = raw_inactive_verify_role && parse_inactive_verify_role_value(raw_inactive_verify_role)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def parse_inactive_substitution_value(value)
|
|
362
|
+
strategy = parse_substitution_value(value)
|
|
363
|
+
return strategy if %i[substitute_writer none].include?(strategy)
|
|
364
|
+
|
|
365
|
+
raise Errors::AwsError,
|
|
366
|
+
"#{PropertyDefinition::INITIAL_CONNECTION_INACTIVE_SUBSTITUTE_HOST.name}: '#{value}' is not valid. " \
|
|
367
|
+
"Valid values are 'writer' or 'none'."
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def parse_inactive_verify_role_value(value)
|
|
371
|
+
role = parse_verify_role_value(value)
|
|
372
|
+
return role if [Host::HostRole::WRITER, :none].include?(role)
|
|
373
|
+
|
|
374
|
+
raise Errors::AwsError,
|
|
375
|
+
"#{PropertyDefinition::INITIAL_CONNECTION_INACTIVE_VERIFY_ROLE.name}: '#{value}' is not valid. " \
|
|
376
|
+
"Valid values are 'writer' or 'none'."
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def parse_substitution_value(value)
|
|
380
|
+
normalized = value.to_s.downcase
|
|
381
|
+
strategy = SUBSTITUTION_STRATEGIES[normalized]
|
|
382
|
+
unless strategy
|
|
383
|
+
raise Errors::AwsError,
|
|
384
|
+
"Invalid #{PropertyDefinition::INITIAL_CONNECTION_SUBSTITUTE_HOST.name} value: '#{value}'. " \
|
|
385
|
+
"Valid values: #{SUBSTITUTION_STRATEGIES.keys.join(', ')}"
|
|
386
|
+
end
|
|
387
|
+
strategy
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def parse_verify_role_value(value)
|
|
391
|
+
normalized = value.to_s.downcase
|
|
392
|
+
unless VERIFY_ROLES.key?(normalized)
|
|
393
|
+
raise Errors::AwsError,
|
|
394
|
+
"Invalid #{PropertyDefinition::INITIAL_CONNECTION_VERIFY_ROLE.name} value: '#{value}'. " \
|
|
395
|
+
"Valid values: #{VERIFY_ROLES.keys.join(', ')}"
|
|
396
|
+
end
|
|
397
|
+
VERIFY_ROLES[normalized]
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def validate_substitution_strategy(strategy, url_type)
|
|
401
|
+
return if strategy == :none
|
|
402
|
+
|
|
403
|
+
if url_type == Utils::RdsUrlType::RDS_INSTANCE
|
|
404
|
+
raise Errors::AwsError,
|
|
405
|
+
"#{PropertyDefinition::INITIAL_CONNECTION_SUBSTITUTE_HOST.name} cannot be set when connecting to an instance endpoint"
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
if strategy == :substitute_writer &&
|
|
409
|
+
[Utils::RdsUrlType::RDS_READER_CLUSTER, Utils::RdsUrlType::RDS_CUSTOM_CLUSTER].include?(url_type)
|
|
410
|
+
raise Errors::AwsError,
|
|
411
|
+
"#{PropertyDefinition::INITIAL_CONNECTION_SUBSTITUTE_HOST.name}: 'writer' is invalid for reader or custom cluster endpoints"
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
if strategy == :substitute_reader &&
|
|
415
|
+
[Utils::RdsUrlType::RDS_WRITER_CLUSTER, Utils::RdsUrlType::RDS_GLOBAL_WRITER_CLUSTER].include?(url_type)
|
|
416
|
+
raise Errors::AwsError,
|
|
417
|
+
"#{PropertyDefinition::INITIAL_CONNECTION_SUBSTITUTE_HOST.name}: 'reader' is invalid for writer or global cluster endpoints"
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
return unless strategy == :substitute_any && url_type != Utils::RdsUrlType::RDS_CUSTOM_CLUSTER
|
|
421
|
+
|
|
422
|
+
raise Errors::AwsError,
|
|
423
|
+
"#{PropertyDefinition::INITIAL_CONNECTION_SUBSTITUTE_HOST.name}: 'any' is only valid for custom cluster endpoints"
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def validate_verify_role(role, url_type)
|
|
427
|
+
return if role.nil? || role == :none
|
|
428
|
+
|
|
429
|
+
if role == Host::HostRole::READER &&
|
|
430
|
+
[Utils::RdsUrlType::RDS_WRITER_CLUSTER, Utils::RdsUrlType::RDS_GLOBAL_WRITER_CLUSTER].include?(url_type)
|
|
431
|
+
raise Errors::AwsError,
|
|
432
|
+
"#{PropertyDefinition::INITIAL_CONNECTION_VERIFY_ROLE.name}: 'reader' is invalid for writer or global cluster endpoints"
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# A custom endpoint can only be of type 'reader' or 'any', so writer verification is not allowed.
|
|
436
|
+
return unless role == Host::HostRole::WRITER &&
|
|
437
|
+
[Utils::RdsUrlType::RDS_READER_CLUSTER, Utils::RdsUrlType::RDS_CUSTOM_CLUSTER].include?(url_type)
|
|
438
|
+
|
|
439
|
+
raise Errors::AwsError,
|
|
440
|
+
"#{PropertyDefinition::INITIAL_CONNECTION_VERIFY_ROLE.name}: 'writer' is invalid for reader or custom cluster endpoints"
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def readers_in_topology?(hosts)
|
|
444
|
+
return false if hosts.nil? || hosts.empty?
|
|
445
|
+
|
|
446
|
+
hosts.any? { |h| h.role == Host::HostRole::READER }
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def url_type_has_region?(url_type)
|
|
450
|
+
[
|
|
451
|
+
Utils::RdsUrlType::RDS_WRITER_CLUSTER,
|
|
452
|
+
Utils::RdsUrlType::RDS_READER_CLUSTER,
|
|
453
|
+
Utils::RdsUrlType::RDS_CUSTOM_CLUSTER,
|
|
454
|
+
Utils::RdsUrlType::RDS_INSTANCE,
|
|
455
|
+
Utils::RdsUrlType::RDS_PROXY,
|
|
456
|
+
Utils::RdsUrlType::RDS_PROXY_ENDPOINT
|
|
457
|
+
].include?(url_type)
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def close_connection(conn)
|
|
461
|
+
return if conn.nil?
|
|
462
|
+
|
|
463
|
+
dialect_service.driver_dialect.close_connection(conn)
|
|
464
|
+
rescue StandardError
|
|
465
|
+
# Ignore errors when closing a connection during cleanup
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def monotonic_time
|
|
469
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def host_service
|
|
473
|
+
@service_container.host_service
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def dialect_service
|
|
477
|
+
@service_container.dialect_service
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def plugin_manager
|
|
481
|
+
@service_container.plugin_manager
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require 'time'
|
|
18
|
+
require_relative '../../logging'
|
|
19
|
+
require_relative 'sanitizer'
|
|
20
|
+
|
|
21
|
+
module AwsAdvancedRubyDriverWrapper
|
|
22
|
+
module Plugins
|
|
23
|
+
module Encryption
|
|
24
|
+
# Emits one audit record per key management, kms_encryption, decryption, and metadata
|
|
25
|
+
# operation, enabled with the +encryption_audit_logging_enabled+ property.
|
|
26
|
+
#
|
|
27
|
+
# Records are written to the wrapper's logger as a single +AUDIT+ line of +key=value+
|
|
28
|
+
# fields: successful operations at info level, failures at warn level. Key ids, ARNs,
|
|
29
|
+
# identifiers, and error messages are redacted by {Sanitizer} before being written, so
|
|
30
|
+
# the audit trail never contains key material, credentials, or column values.
|
|
31
|
+
class AuditLogger
|
|
32
|
+
include Logging
|
|
33
|
+
|
|
34
|
+
AUDIT_PREFIX = 'AUDIT'
|
|
35
|
+
|
|
36
|
+
# @param enabled [Boolean] when false every method is a no-op
|
|
37
|
+
def initialize(enabled)
|
|
38
|
+
@enabled = enabled
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Boolean]
|
|
42
|
+
def enabled?
|
|
43
|
+
@enabled
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param master_key_arn [String, nil]
|
|
47
|
+
# @param description [String, nil]
|
|
48
|
+
# @param success [Boolean]
|
|
49
|
+
# @param error_message [String, nil]
|
|
50
|
+
# @return [void]
|
|
51
|
+
def log_key_creation(master_key_arn:, description: nil, success: true, error_message: nil)
|
|
52
|
+
record('KEY_CREATION', success, error_message,
|
|
53
|
+
master_key_arn: Sanitizer.arn(master_key_arn),
|
|
54
|
+
description: Sanitizer.description(description))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [void]
|
|
58
|
+
def log_data_key_generation(master_key_arn:, key_id: nil, success: true, error_message: nil)
|
|
59
|
+
record('DATA_KEY_GENERATION', success, error_message,
|
|
60
|
+
master_key_arn: Sanitizer.arn(master_key_arn),
|
|
61
|
+
key_id: Sanitizer.key_id(key_id))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [void]
|
|
65
|
+
def log_data_key_decryption(master_key_arn:, key_id: nil, success: true, error_message: nil)
|
|
66
|
+
record('DATA_KEY_DECRYPTION', success, error_message,
|
|
67
|
+
master_key_arn: Sanitizer.arn(master_key_arn),
|
|
68
|
+
key_id: Sanitizer.key_id(key_id))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [void]
|
|
72
|
+
def log_encryption(table_name:, column_name:, key_id: nil, success: true, error_message: nil)
|
|
73
|
+
record('ENCRYPTION', success, error_message,
|
|
74
|
+
table: Sanitizer.table_name(table_name),
|
|
75
|
+
column: Sanitizer.column_name(column_name),
|
|
76
|
+
key_id: Sanitizer.key_id(key_id))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @return [void]
|
|
80
|
+
def log_decryption(table_name:, column_name:, key_id: nil, success: true, error_message: nil)
|
|
81
|
+
record('DECRYPTION', success, error_message,
|
|
82
|
+
table: Sanitizer.table_name(table_name),
|
|
83
|
+
column: Sanitizer.column_name(column_name),
|
|
84
|
+
key_id: Sanitizer.key_id(key_id))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @param operation [String] the metadata operation, e.g. 'load' or 'refresh'
|
|
88
|
+
# @return [void]
|
|
89
|
+
def log_metadata_operation(operation:, table_name: nil, column_name: nil, success: true, error_message: nil)
|
|
90
|
+
record("METADATA_#{operation.to_s.upcase}", success, error_message,
|
|
91
|
+
table: Sanitizer.table_name(table_name),
|
|
92
|
+
column: Sanitizer.column_name(column_name))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @return [void]
|
|
96
|
+
def log_configuration_change(config_type:, details: nil, success: true, error_message: nil)
|
|
97
|
+
record('CONFIGURATION_CHANGE', success, error_message,
|
|
98
|
+
config_type: Sanitizer.truncate(config_type, Sanitizer::MAX_NAME_LENGTH),
|
|
99
|
+
details: Sanitizer.config_details(details))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @return [void]
|
|
103
|
+
def log_connection_parameter_extraction(strategy:, connection_type:, success: true, error_message: nil)
|
|
104
|
+
record('CONNECTION_PARAMETER_EXTRACTION', success, error_message,
|
|
105
|
+
strategy: Sanitizer.truncate(strategy, Sanitizer::MAX_NAME_LENGTH),
|
|
106
|
+
connection_type: Sanitizer.truncate(connection_type, Sanitizer::MAX_NAME_LENGTH))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @param active [Boolean] whether connection sharing is currently in effect
|
|
110
|
+
# @return [void]
|
|
111
|
+
def log_connection_sharing_fallback(reason:, original_failure: nil, active: false)
|
|
112
|
+
return unless @enabled
|
|
113
|
+
|
|
114
|
+
# The fallback deactivating (returning to normal) is worth an info record; while it is
|
|
115
|
+
# active the steady state is logged at debug.
|
|
116
|
+
emit(active ? :debug : :info, 'CONNECTION_SHARING_FALLBACK', true, nil,
|
|
117
|
+
reason: Sanitizer.description(reason),
|
|
118
|
+
original_failure: Sanitizer.error_message(original_failure),
|
|
119
|
+
active: active)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# @return [void]
|
|
123
|
+
def log_connection_health_check(connection_type:, healthy:, success_count:, failure_count:, success_rate:)
|
|
124
|
+
return unless @enabled
|
|
125
|
+
|
|
126
|
+
emit(healthy ? :info : :warn, 'CONNECTION_HEALTH_CHECK', healthy, nil,
|
|
127
|
+
connection_type: Sanitizer.truncate(connection_type, Sanitizer::MAX_NAME_LENGTH),
|
|
128
|
+
healthy: healthy,
|
|
129
|
+
successful: success_count,
|
|
130
|
+
failed: failure_count,
|
|
131
|
+
success_rate: format('%.2f%%', success_rate * 100))
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def record(operation, success, error_message, fields)
|
|
137
|
+
return unless @enabled
|
|
138
|
+
|
|
139
|
+
emit(success ? :info : :warn, operation, success, error_message, fields)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def emit(level, operation, success, error_message, fields)
|
|
143
|
+
logger.public_send(level, build_line(operation, success, error_message, fields))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def build_line(operation, success, error_message, fields)
|
|
147
|
+
entries = { operation: operation, success: success }
|
|
148
|
+
fields.each { |key, value| entries[key] = value unless value.nil? }
|
|
149
|
+
entries[:error] = Sanitizer.error_message(error_message) if error_message
|
|
150
|
+
entries[:timestamp] = Time.now.utc.iso8601
|
|
151
|
+
|
|
152
|
+
"#{AUDIT_PREFIX} #{entries.map { |key, value| "#{key}=#{value}" }.join(' ')}"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|