aws_advanced_ruby_driver_wrapper 1.0.1 → 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 +32 -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_postgresql_adapter.rb +17 -3
- 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 +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +43 -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 -91
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +18 -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 +1 -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 +115 -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 +6 -13
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +90 -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 +14 -0
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +41 -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 +1 -1
- 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/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 +9 -5
- 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/telemetry/open_telemetry_counter.rb +39 -0
- 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 +9 -2
- metadata +54 -2
|
@@ -20,6 +20,7 @@ require_relative '../aws_advanced_ruby_driver_wrapper'
|
|
|
20
20
|
require_relative 'utils/connection_config_parser'
|
|
21
21
|
require_relative 'utils/sql_encoding'
|
|
22
22
|
require_relative 'services/service_utility'
|
|
23
|
+
require_relative 'utils/telemetry/telemetry_factory'
|
|
23
24
|
require_relative 'ruby_method'
|
|
24
25
|
require_relative 'errors'
|
|
25
26
|
|
|
@@ -34,13 +35,16 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
34
35
|
def initialize(**)
|
|
35
36
|
ensure_mysql2!
|
|
36
37
|
config = Utils::ConnectionConfigParser.parse(:mysql2, **)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
telemetry_factory = Utils::Telemetry::TelemetryFactory.new(config.wrapper_props)
|
|
39
|
+
telemetry_factory.in_context("#{self.class.name}.new", Utils::Telemetry::TraceLevel::TOP_LEVEL, record_result: true) do
|
|
40
|
+
@service_container = Services::ServiceUtility.create_standard_container(config, telemetry_factory:)
|
|
41
|
+
@service_container.host_service.refresh_host_list
|
|
42
|
+
@async_conn = nil
|
|
43
|
+
@async_sql = nil
|
|
44
|
+
@last_sql = nil
|
|
45
|
+
conn_service = @service_container.connection_service
|
|
46
|
+
@service_container.plugin_manager.connect(conn_service.initial_host_info, conn_service.driver_props, true)
|
|
47
|
+
end
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
# Explicitly define critical methods (bypass method_missing to avoid method_missing overhead)
|
|
@@ -53,6 +57,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
53
57
|
# What is remembered is the copy of the SQL that plugins inspect, made once here rather than on
|
|
54
58
|
# every call that is handed it later. The driver is sent the SQL as the application wrote it.
|
|
55
59
|
def query(sql, options = {})
|
|
60
|
+
ensure_connected!
|
|
56
61
|
inspected = Utils::SqlEncoding.inspectable(sql)
|
|
57
62
|
result = pm.execute(RubyMethod::CONNECTION_QUERY, current_conn, ->(*a) { current_conn.query(*a) }, sql, options,
|
|
58
63
|
sql: inspected)
|
|
@@ -76,10 +81,14 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
76
81
|
end
|
|
77
82
|
|
|
78
83
|
def ping
|
|
84
|
+
return false if current_conn.nil?
|
|
85
|
+
|
|
79
86
|
pm.execute(RubyMethod::CONNECTION_PING, current_conn, -> { current_conn.ping })
|
|
80
87
|
end
|
|
81
88
|
|
|
82
89
|
def close
|
|
90
|
+
return if current_conn.nil?
|
|
91
|
+
|
|
83
92
|
pm.execute(RubyMethod::CONNECTION_CLOSE, current_conn, -> { current_conn.close })
|
|
84
93
|
end
|
|
85
94
|
|
|
@@ -143,8 +152,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
143
152
|
BOUNDED_TO_ASYNC = Set[:abandon_results!].freeze
|
|
144
153
|
|
|
145
154
|
def method_missing(method_name, *args, **kwargs, &)
|
|
146
|
-
conn =
|
|
147
|
-
raise NoMethodError, 'Connection not initialized' if conn.nil?
|
|
155
|
+
conn = ensure_connected!
|
|
148
156
|
raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless conn.respond_to?(method_name)
|
|
149
157
|
|
|
150
158
|
method_key = "connection.#{method_name}"
|
|
@@ -197,6 +205,16 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
197
205
|
@service_container.connection_service.current_connection
|
|
198
206
|
end
|
|
199
207
|
|
|
208
|
+
# Guards the entry points that reach for the current connection directly (the query path and
|
|
209
|
+
# method_missing). Without a live connection AR expects the driver's own "not connected" error,
|
|
210
|
+
# which it translates to a connection error; a bare NoMethodError on nil would not be recognized.
|
|
211
|
+
def ensure_connected!
|
|
212
|
+
conn = current_conn
|
|
213
|
+
raise Mysql2::Error::ConnectionError, 'MySQL client is not connected' if conn.nil?
|
|
214
|
+
|
|
215
|
+
conn
|
|
216
|
+
end
|
|
217
|
+
|
|
200
218
|
def pm
|
|
201
219
|
@service_container.plugin_manager
|
|
202
220
|
end
|
|
@@ -19,6 +19,7 @@ require_relative '../../logging'
|
|
|
19
19
|
require_relative '../../ruby_method'
|
|
20
20
|
require_relative '../../property_definition'
|
|
21
21
|
require_relative '../../utils/rds_utils'
|
|
22
|
+
require_relative 'drop_blue_connections_marker'
|
|
22
23
|
require_relative 'phase'
|
|
23
24
|
require_relative 'role'
|
|
24
25
|
require_relative 'status'
|
|
@@ -32,6 +33,14 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
32
33
|
|
|
33
34
|
BLUE_GREEN_NAME = :blue_green
|
|
34
35
|
|
|
36
|
+
# Storage partition for the drop-blue-connections marker, kept separate from BLUE_GREEN_NAME so
|
|
37
|
+
# it can outlive the status cache's TTL. The marker is stored once per switchover and never
|
|
38
|
+
# re-stamped, so a connection that sits idle longer than the status cache's TTL must still find
|
|
39
|
+
# the marker on its next use. A long TTL is safe: a marker only matches connections opened before
|
|
40
|
+
# its own switchover, and a later switchover overwrites it.
|
|
41
|
+
DROP_MARKER_NAME = :blue_green_drop_marker
|
|
42
|
+
DROP_MARKER_TTL_SEC = 86_400
|
|
43
|
+
|
|
35
44
|
# When present in wrapper_props, internal_connect calls bypass BG routing.
|
|
36
45
|
# Used by BG monitoring connections to avoid being intercepted by their own plugin.
|
|
37
46
|
BG_SKIP_ROUTING_KEY = :'bg.skip_routing'
|
|
@@ -43,6 +52,13 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
43
52
|
|
|
44
53
|
PROVIDERS = Concurrent::Map.new
|
|
45
54
|
|
|
55
|
+
# Storage key, within the DROP_MARKER_NAME cache, under which the drop-blue-connections marker
|
|
56
|
+
# for a deployment is kept. DROP_MARKER_NAME is a separate partition from the status cache, so
|
|
57
|
+
# the marker survives both the status cache's TTL and the provider's reset_context_when_completed.
|
|
58
|
+
def self.drop_marker_key(bgd_id)
|
|
59
|
+
"#{bgd_id}::drop_blue_connections_marker"
|
|
60
|
+
end
|
|
61
|
+
|
|
46
62
|
def initialize(service_container, props = ::Concurrent::Map.new)
|
|
47
63
|
@service_container = service_container
|
|
48
64
|
@wrapper_props = props
|
|
@@ -52,7 +68,17 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
52
68
|
@start_time_ns = Concurrent::AtomicReference.new(0)
|
|
53
69
|
@end_time_ns = Concurrent::AtomicReference.new(0)
|
|
54
70
|
|
|
71
|
+
# Per-connection drop-blue-connections state. Each wrapper client has its own plugin instance,
|
|
72
|
+
# so these describe this connection: when it was opened, the host it was opened to, and the
|
|
73
|
+
# identity of the last marker it was checked against.
|
|
74
|
+
@drop_blue_connections = PropertyDefinition::BG_DROP_BLUE_CONNECTIONS.get_bool(props)
|
|
75
|
+
@connection_open_time_ns = nil
|
|
76
|
+
@connect_time_host = nil
|
|
77
|
+
@checked_drop_marker_id = nil
|
|
78
|
+
@pending_stale_marker_id = nil
|
|
79
|
+
|
|
55
80
|
service_container.storage_service.register(BLUE_GREEN_NAME, ttl: 3600)
|
|
81
|
+
service_container.storage_service.register(DROP_MARKER_NAME, ttl: DROP_MARKER_TTL_SEC)
|
|
56
82
|
|
|
57
83
|
network_methods = service_container.dialect_service.driver_dialect.network_bound_methods
|
|
58
84
|
@subscribed_methods = (Set['connect', 'internal_connect'] | network_methods).freeze
|
|
@@ -61,7 +87,12 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
61
87
|
attr_reader :subscribed_methods
|
|
62
88
|
|
|
63
89
|
def connect(host_info, driver_props, is_initial_connection, pipeline_callable)
|
|
64
|
-
route_connect(host_info, driver_props, is_initial_connection, pipeline_callable, is_internal: false)
|
|
90
|
+
conn = route_connect(host_info, driver_props, is_initial_connection, pipeline_callable, is_internal: false)
|
|
91
|
+
# Only this client's own connect describes its connection. Internal connects (topology
|
|
92
|
+
# monitoring, KMS metadata reads) run through this same plugin instance but open separate
|
|
93
|
+
# connections, so they must not overwrite this connection's open time, host, or marker latches.
|
|
94
|
+
record_connection_open(host_info)
|
|
95
|
+
conn
|
|
65
96
|
end
|
|
66
97
|
|
|
67
98
|
def internal_connect(host_info, driver_props, wrapper_props, is_initial_connection, pipeline_callable)
|
|
@@ -79,7 +110,21 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
79
110
|
|
|
80
111
|
return pipeline_callable.call if CLOSING_METHODS.include?(method_name)
|
|
81
112
|
|
|
113
|
+
# Read the status before the stale check. The provider stores the active drop marker before
|
|
114
|
+
# it writes the POST/COMPLETED status, so reading status first closes the race where a stale
|
|
115
|
+
# check sees a still-pending marker and the COMPLETED early return below then skips re-checking
|
|
116
|
+
# the now-active marker: if the switchover has completed, the check below sees the active marker.
|
|
82
117
|
@bg_status = storage_service.get(BLUE_GREEN_NAME, @bgd_id)
|
|
118
|
+
|
|
119
|
+
# Invalidate a connection opened to the old blue environment before a confirmed switchover,
|
|
120
|
+
# so the failover plugin reconnects through BG routing instead of serving a stale read. This
|
|
121
|
+
# runs before the NOT_CREATED/COMPLETED early return below, because after the switchover the
|
|
122
|
+
# status is COMPLETED (then NOT_CREATED) and would otherwise skip the check entirely.
|
|
123
|
+
if stale_blue_connection?
|
|
124
|
+
invalidate_stale_blue_connection
|
|
125
|
+
return pipeline_callable.call
|
|
126
|
+
end
|
|
127
|
+
|
|
83
128
|
if @bg_status.nil? || @bg_status.current_phase == Phase::NOT_CREATED || @bg_status.current_phase == Phase::COMPLETED
|
|
84
129
|
return pipeline_callable.call
|
|
85
130
|
end
|
|
@@ -95,20 +140,24 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
95
140
|
result = nil
|
|
96
141
|
|
|
97
142
|
while routing && result.nil?
|
|
98
|
-
result = routing.apply(method_name, @wrapper_props, storage_service)
|
|
143
|
+
result = routing.apply(method_name, @wrapper_props, storage_service, telemetry_factory: @service_container.telemetry_factory)
|
|
99
144
|
|
|
100
145
|
next unless result.nil?
|
|
101
146
|
|
|
102
147
|
@bg_status = storage_service.get(BLUE_GREEN_NAME, @bgd_id)
|
|
103
|
-
if @bg_status.nil?
|
|
104
|
-
|
|
105
|
-
return pipeline_callable.call
|
|
106
|
-
end
|
|
148
|
+
break if @bg_status.nil?
|
|
149
|
+
|
|
107
150
|
routing = @bg_status.execute_routing.find { |r| r.match?(current_host, host_role) }
|
|
108
151
|
end
|
|
109
152
|
|
|
110
153
|
@end_time_ns.set(nano_time)
|
|
111
|
-
result.nil?
|
|
154
|
+
return result unless result.nil?
|
|
155
|
+
|
|
156
|
+
# The call may have been held through the switchover (e.g. suspended during IN_PROGRESS and
|
|
157
|
+
# released at POST, which has no execute routing). Re-check whether this connection is now a
|
|
158
|
+
# stale blue connection before running it, so a held call is not run against retired blue.
|
|
159
|
+
invalidate_stale_blue_connection if stale_blue_connection?
|
|
160
|
+
pipeline_callable.call
|
|
112
161
|
ensure
|
|
113
162
|
@end_time_ns.set(nano_time) if @start_time_ns.get.positive? && @end_time_ns.get.zero?
|
|
114
163
|
end
|
|
@@ -202,6 +251,65 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
202
251
|
end
|
|
203
252
|
end
|
|
204
253
|
|
|
254
|
+
# Remembers when this connection was opened and the host it was opened to, so a later switchover
|
|
255
|
+
# can tell whether it is a blue connection that predates the switchover.
|
|
256
|
+
def record_connection_open(host_info)
|
|
257
|
+
return unless @drop_blue_connections
|
|
258
|
+
|
|
259
|
+
@connection_open_time_ns = nano_time
|
|
260
|
+
@connect_time_host = host_info&.host&.downcase
|
|
261
|
+
# A (re)connect produces a fresh connection (e.g. after failover): reset the per-connection
|
|
262
|
+
# marker latches so this new incarnation re-evaluates the current marker from a clean slate
|
|
263
|
+
# instead of inheriting the retired connection's checked/pending state.
|
|
264
|
+
@checked_drop_marker_id = nil
|
|
265
|
+
@pending_stale_marker_id = nil
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Whether this connection should be invalidated because it was opened to the old blue environment
|
|
269
|
+
# before a now-confirmed switchover. The marker is read on every call so a later switchover's
|
|
270
|
+
# marker is noticed. A marker that does not apply to this connection is latched immediately and
|
|
271
|
+
# never re-examined; a marker that does apply is only latched once #invalidate_stale_blue_connection
|
|
272
|
+
# confirms the close, so a transient close failure leaves the connection to be invalidated again on
|
|
273
|
+
# its next use instead of silently serving stale reads for the rest of its life.
|
|
274
|
+
def stale_blue_connection?
|
|
275
|
+
return false unless @drop_blue_connections
|
|
276
|
+
|
|
277
|
+
marker = storage_service.get(DROP_MARKER_NAME, self.class.drop_marker_key(@bgd_id), register_access: false)
|
|
278
|
+
return false unless marker.is_a?(DropBlueConnectionsMarker) && marker.active?
|
|
279
|
+
return false if marker.id == @checked_drop_marker_id
|
|
280
|
+
|
|
281
|
+
if marker.stale?(@connect_time_host, @connection_open_time_ns)
|
|
282
|
+
@pending_stale_marker_id = marker.id
|
|
283
|
+
true
|
|
284
|
+
else
|
|
285
|
+
# This connection does not predate the switchover and never will against this marker (markers
|
|
286
|
+
# are immutable), so latch it now to skip re-reading the marker on every future call.
|
|
287
|
+
@checked_drop_marker_id = marker.id
|
|
288
|
+
false
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Closes the connection directly through the driver dialect rather than the pipeline's close
|
|
293
|
+
# method. Going through the pipeline would set the failover plugin's @closed_explicitly, which
|
|
294
|
+
# would stop it from reconnecting. Closing it out from under the pipeline instead lets the
|
|
295
|
+
# failover plugin (if enabled) see a connection closed that it did not close, fail over, and
|
|
296
|
+
# raise FailoverSuccessError; without failover the driver raises its closed-connection error.
|
|
297
|
+
def invalidate_stale_blue_connection
|
|
298
|
+
logger.info do
|
|
299
|
+
"[bgd_id: '#{@bgd_id}'] Invalidating a connection to #{@connect_time_host} from the old blue environment " \
|
|
300
|
+
'retired by a Blue/Green switchover. It will fail over (or raise a closed-connection error) on this use.'
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
conn = @service_container.connection_service.current_connection
|
|
304
|
+
@service_container.dialect_service.driver_dialect.close_connection(conn) unless conn.nil?
|
|
305
|
+
# Only now that the close is confirmed do we latch the marker as handled. If the close raised
|
|
306
|
+
# and was swallowed below, the marker stays unlatched so the next use retries the invalidation
|
|
307
|
+
# rather than serving a stale read from retired blue for the rest of the connection's life.
|
|
308
|
+
@checked_drop_marker_id = @pending_stale_marker_id
|
|
309
|
+
rescue StandardError => e
|
|
310
|
+
logger.debug { "[bgd_id: '#{@bgd_id}'] Failed to invalidate stale blue connection: #{e.class}: #{e.message}" }
|
|
311
|
+
end
|
|
312
|
+
|
|
205
313
|
def storage_service
|
|
206
314
|
@service_container.storage_service
|
|
207
315
|
end
|
|
@@ -0,0 +1,84 @@
|
|
|
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 'securerandom'
|
|
18
|
+
|
|
19
|
+
module AwsAdvancedRubyDriverWrapper
|
|
20
|
+
module Plugins
|
|
21
|
+
module BlueGreen
|
|
22
|
+
# Records that a Blue/Green switchover is retiring the blue environment, so that connections
|
|
23
|
+
# opened to blue before the switchover can be invalidated the next time they are used.
|
|
24
|
+
#
|
|
25
|
+
# Ruby cannot safely abort a connection from another thread, so instead of dropping blue connections
|
|
26
|
+
# from the monitor thread, each connection checks this marker on its own thread the next time it is
|
|
27
|
+
# used. See the +bg_drop_blue_connections+ property and BlueGreenPlugin#execute.
|
|
28
|
+
#
|
|
29
|
+
# A marker is immutable. It becomes +pending+ when the switchover reaches IN_PROGRESS and is
|
|
30
|
+
# replaced by an +active+ copy (see #activated) once the switchover is confirmed by POST or
|
|
31
|
+
# COMPLETED. It is cleared on rollback.
|
|
32
|
+
class DropBlueConnectionsMarker
|
|
33
|
+
# @return [String] a unique identity for this switchover's marker, so a connection can tell
|
|
34
|
+
# whether it has already been checked against it.
|
|
35
|
+
attr_reader :id
|
|
36
|
+
|
|
37
|
+
# @return [Integer] monotonic time (nanoseconds) at which the switchover reached IN_PROGRESS.
|
|
38
|
+
attr_reader :switchover_time_ns
|
|
39
|
+
|
|
40
|
+
# @return [Set<String>] the blue host set: downcased cluster and instance endpoint names plus
|
|
41
|
+
# their pre-switchover IP addresses.
|
|
42
|
+
attr_reader :blue_hosts
|
|
43
|
+
|
|
44
|
+
# @param blue_hosts [Enumerable<String>] blue endpoint names and their pre-switchover IPs
|
|
45
|
+
# @param switchover_time_ns [Integer] monotonic time the switchover reached IN_PROGRESS
|
|
46
|
+
# @param id [String] unique identity for this marker
|
|
47
|
+
# @param active [Boolean] false while the switchover is in progress, true once it is confirmed
|
|
48
|
+
def initialize(blue_hosts:, switchover_time_ns:, id: SecureRandom.uuid, active: false)
|
|
49
|
+
@id = id
|
|
50
|
+
@switchover_time_ns = switchover_time_ns
|
|
51
|
+
@blue_hosts = blue_hosts.to_set.freeze
|
|
52
|
+
@active = active
|
|
53
|
+
freeze
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def active?
|
|
57
|
+
@active
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [DropBlueConnectionsMarker] an active copy of this marker, keeping its identity.
|
|
61
|
+
def activated
|
|
62
|
+
return self if @active
|
|
63
|
+
|
|
64
|
+
DropBlueConnectionsMarker.new(
|
|
65
|
+
blue_hosts: @blue_hosts, switchover_time_ns: @switchover_time_ns, id: @id, active: true
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Whether a connection opened to the given host at the given time is now a stale blue connection.
|
|
70
|
+
#
|
|
71
|
+
# @param host [String, nil] the downcased connect-time host of the connection
|
|
72
|
+
# @param open_time_ns [Integer, nil] the monotonic time the connection was opened
|
|
73
|
+
# @return [Boolean]
|
|
74
|
+
def stale?(host, open_time_ns)
|
|
75
|
+
return false unless @active
|
|
76
|
+
return false if host.nil? || open_time_ns.nil?
|
|
77
|
+
return false unless open_time_ns < @switchover_time_ns
|
|
78
|
+
|
|
79
|
+
@blue_hosts.include?(host)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -39,8 +39,10 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
39
39
|
build_cluster_pairs(source_status, target_status)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
# Keyed by downcased host so lookups line up with role_by_host (also downcased); the blue host
|
|
43
|
+
# set the drop-blue-connections marker is built from indexes this map with downcased endpoints.
|
|
42
44
|
def merge_ips(ip_map)
|
|
43
|
-
@host_ip_addresses.
|
|
45
|
+
ip_map.each { |host, ip| @host_ip_addresses[host.downcase] = ip }
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
def register_role(host_names, role)
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
17
|
require_relative '../../../logging'
|
|
18
|
+
require_relative '../../../utils/telemetry/telemetry_const'
|
|
18
19
|
|
|
19
20
|
module AwsAdvancedRubyDriverWrapper
|
|
20
21
|
module Plugins
|
|
@@ -24,6 +25,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
24
25
|
include Logging
|
|
25
26
|
|
|
26
27
|
MINIMUM_SLEEP_CHUNK_SEC = 0.05
|
|
28
|
+
TELEMETRY_SWITCHOVER = 'Blue/Green Deployment switchover'
|
|
27
29
|
|
|
28
30
|
def match?(host_info, role)
|
|
29
31
|
(@host.nil? || @host.downcase == host_info&.host&.downcase) &&
|
|
@@ -39,6 +41,11 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
39
41
|
"bgd_id: #{defined?(@bgd_id) ? (@bgd_id || '<null>') : '<n/a>'}]"
|
|
40
42
|
end
|
|
41
43
|
|
|
44
|
+
# Records the time a call is held while the switchover is in progress as a span.
|
|
45
|
+
def in_switchover_span(telemetry_factory, &)
|
|
46
|
+
telemetry_factory.in_context(TELEMETRY_SWITCHOVER, Utils::Telemetry::TraceLevel::NESTED, &)
|
|
47
|
+
end
|
|
48
|
+
|
|
42
49
|
# Blocks until the Blue/Green switchover phase is no longer IN_PROGRESS, or until the
|
|
43
50
|
# configured timeout elapses.
|
|
44
51
|
def wait_while_in_progress(wrapper_props, storage_service, bgd_id, sleep_time_sec = MINIMUM_SLEEP_CHUNK_SEC)
|
data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb
CHANGED
|
@@ -52,57 +52,37 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
52
52
|
bg_plugin: nil)
|
|
53
53
|
plugin_manager = service_container.plugin_manager
|
|
54
54
|
dialect_service = service_container.dialect_service
|
|
55
|
+
driver_dialect = dialect_service.driver_dialect
|
|
55
56
|
|
|
56
57
|
unless Utils::RdsUtils.ip?(@substitute_host.host)
|
|
57
58
|
return open_connection(plugin_manager, @substitute_host, driver_props, wrapper_props, is_initial_connection, is_internal,
|
|
58
59
|
bg_plugin)
|
|
59
60
|
end
|
|
60
61
|
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
# The substitute host is a raw IP, so a connection made by hostname could resolve (via DNS)
|
|
63
|
+
# to the old instance during the switchover window. Connect to the IP directly instead.
|
|
64
|
+
if plugin_manager.iam_token_plugin_in_use?
|
|
65
|
+
return apply_iam(service_container, driver_props, wrapper_props, is_initial_connection, bg_plugin)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# pg can pin the socket to the IP (hostaddr) while keeping the original hostname for TLS
|
|
69
|
+
# verification, so the pinned connection reaches the right instance and still passes
|
|
70
|
+
# verify-full whatever the CA source.
|
|
71
|
+
pinned_props = driver_dialect.props_pinned_to_ip(driver_props, @substitute_host.host)
|
|
72
|
+
if pinned_props
|
|
67
73
|
hostname_host = @substitute_host.deep_dup(host: @host)
|
|
68
|
-
return open_connection(plugin_manager, hostname_host,
|
|
74
|
+
return open_connection(plugin_manager, hostname_host, pinned_props, wrapper_props, is_initial_connection, is_internal,
|
|
69
75
|
bg_plugin)
|
|
70
76
|
end
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
rerouted_host.id = iam_host.id
|
|
81
|
-
rerouted_host.availability = Host::HostAvailability::AVAILABLE
|
|
82
|
-
|
|
83
|
-
rerouted_wrapper_props = wrapper_props.dup
|
|
84
|
-
PropertyDefinition::IAM_HOST.set(rerouted_wrapper_props, iam_host.host)
|
|
85
|
-
PropertyDefinition::IAM_PORT.set(rerouted_wrapper_props, iam_host.port.to_s) if iam_host.port_specified?
|
|
86
|
-
|
|
87
|
-
begin
|
|
88
|
-
conn = plugin_manager.internal_connect(
|
|
89
|
-
rerouted_host,
|
|
90
|
-
driver_props,
|
|
91
|
-
rerouted_wrapper_props,
|
|
92
|
-
is_initial_connection,
|
|
93
|
-
plugin_to_skip: bg_plugin
|
|
94
|
-
)
|
|
95
|
-
begin
|
|
96
|
-
@iam_successful_connect_notify&.call(iam_host.host)
|
|
97
|
-
rescue StandardError
|
|
98
|
-
nil
|
|
99
|
-
end
|
|
100
|
-
return conn
|
|
101
|
-
rescue StandardError => e
|
|
102
|
-
raise unless dialect_service.login_error?(e)
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
return nil
|
|
78
|
+
# mysql2/trilogy use :host for both the socket and TLS CN verification, so connecting to a
|
|
79
|
+
# raw IP with TLS active causes a CN mismatch. When TLS is active, fall back to the hostname
|
|
80
|
+
# (DNS resolves to the correct IP post-switchover), matching the effective behaviour of the
|
|
81
|
+
# Java/Go drivers which use the original hostname for TLS regardless of the socket address.
|
|
82
|
+
if driver_props[driver_dialect.tls_ca_property_key]
|
|
83
|
+
hostname_host = @substitute_host.deep_dup(host: @host)
|
|
84
|
+
return open_connection(plugin_manager, hostname_host, driver_props, wrapper_props, is_initial_connection, is_internal,
|
|
85
|
+
bg_plugin)
|
|
106
86
|
end
|
|
107
87
|
|
|
108
88
|
open_connection(plugin_manager, @substitute_host, driver_props, wrapper_props, is_initial_connection, is_internal, bg_plugin)
|
|
@@ -114,6 +94,53 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
114
94
|
|
|
115
95
|
private
|
|
116
96
|
|
|
97
|
+
# Connects to the substitute IP with IAM auth active, trying each configured IAM host in turn
|
|
98
|
+
# so the IAM plugin can resolve the correct token hostname. pg pins the socket to the IP
|
|
99
|
+
# (hostaddr) while presenting the IAM host name for TLS verification; the IAM token is still
|
|
100
|
+
# generated for the IAM host via the IAM_HOST property. Other drivers connect to the IP as-is.
|
|
101
|
+
def apply_iam(service_container, driver_props, wrapper_props, is_initial_connection, bg_plugin)
|
|
102
|
+
plugin_manager = service_container.plugin_manager
|
|
103
|
+
dialect_service = service_container.dialect_service
|
|
104
|
+
driver_dialect = dialect_service.driver_dialect
|
|
105
|
+
|
|
106
|
+
if @iam_hosts.nil? || @iam_hosts.empty?
|
|
107
|
+
raise StandardError,
|
|
108
|
+
'Connecting with IP address when IAM authentication is enabled requires an \'iam_host\' parameter.'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
@iam_hosts.each do |iam_host|
|
|
112
|
+
rerouted_host = @substitute_host.deep_dup
|
|
113
|
+
rerouted_host.id = iam_host.id
|
|
114
|
+
rerouted_host.availability = Host::HostAvailability::AVAILABLE
|
|
115
|
+
|
|
116
|
+
rerouted_wrapper_props = wrapper_props.dup
|
|
117
|
+
PropertyDefinition::IAM_HOST.set(rerouted_wrapper_props, iam_host.host)
|
|
118
|
+
PropertyDefinition::IAM_PORT.set(rerouted_wrapper_props, iam_host.port.to_s) if iam_host.port_specified?
|
|
119
|
+
|
|
120
|
+
connect_props = driver_props
|
|
121
|
+
pinned_props = driver_dialect.props_pinned_to_ip(driver_props, @substitute_host.host)
|
|
122
|
+
if pinned_props
|
|
123
|
+
connect_props = pinned_props
|
|
124
|
+
rerouted_host = rerouted_host.deep_dup(host: iam_host.host)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
begin
|
|
128
|
+
conn = plugin_manager.internal_connect(
|
|
129
|
+
rerouted_host, connect_props, rerouted_wrapper_props, is_initial_connection, plugin_to_skip: bg_plugin
|
|
130
|
+
)
|
|
131
|
+
begin
|
|
132
|
+
@iam_successful_connect_notify&.call(iam_host.host)
|
|
133
|
+
rescue StandardError
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
return conn
|
|
137
|
+
rescue StandardError => e
|
|
138
|
+
raise unless dialect_service.login_error?(e)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
nil
|
|
142
|
+
end
|
|
143
|
+
|
|
117
144
|
# Opens a connection through the plugin pipeline. Monitoring connections (is_internal: true)
|
|
118
145
|
# must use internal_connect so they do NOT mutate the shared current connection — otherwise
|
|
119
146
|
# one monitor thread's connect would close another monitor thread's live connection via
|
data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb
CHANGED
|
@@ -35,8 +35,10 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
35
35
|
storage_service = service_container.storage_service
|
|
36
36
|
hold_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
in_switchover_span(service_container.telemetry_factory) do
|
|
39
|
+
wait_while_in_progress(wrapper_props, storage_service, @bgd_id, SLEEP_TIME_SEC) do |timeout_sec|
|
|
40
|
+
"Blue/Green Deployment switchover is still in progress after #{timeout_sec} s. Try to connect again later."
|
|
41
|
+
end
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - hold_start_time) * 1000).round
|
data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb
CHANGED
|
@@ -31,11 +31,13 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Blocks until switchover is no longer IN_PROGRESS
|
|
34
|
-
def apply(method_name, wrapper_props, storage_service)
|
|
34
|
+
def apply(method_name, wrapper_props, storage_service, telemetry_factory:)
|
|
35
35
|
hold_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
in_switchover_span(telemetry_factory) do
|
|
38
|
+
wait_while_in_progress(wrapper_props, storage_service, @bgd_id, SLEEP_TIME_SEC) do |timeout_sec|
|
|
39
|
+
"Blue/Green Deployment switchover is still in progress after #{timeout_sec} s. Try '#{method_name}' again later."
|
|
40
|
+
end
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - hold_start_time) * 1000).round
|
|
@@ -35,8 +35,14 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
35
35
|
# Blocks until a corresponding green host is found for host_info.host, the switchover
|
|
36
36
|
# completes, or the timeout elapses.
|
|
37
37
|
def apply(host_info, _, wrapper_props, _is_initial_connection, service_container, is_internal: false, bg_plugin: nil)
|
|
38
|
-
|
|
38
|
+
in_switchover_span(service_container.telemetry_factory) do
|
|
39
|
+
hold_until_corresponding_host_found(host_info, wrapper_props, service_container.storage_service)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
39
44
|
|
|
45
|
+
def hold_until_corresponding_host_found(host_info, wrapper_props, storage_service)
|
|
40
46
|
bg_status = storage_service.get(BlueGreenPlugin::BLUE_GREEN_NAME, @bgd_id)
|
|
41
47
|
corresponding_pair = bg_status&.corresponding_hosts&.[](host_info.host)
|
|
42
48
|
|
|
@@ -223,7 +223,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
223
223
|
next [] if host_pair.nil?
|
|
224
224
|
|
|
225
225
|
blue_host_spec = host_pair[0]
|
|
226
|
-
blue_ip_entry = @host_mapper.host_ip_addresses[blue_host_spec.host]
|
|
226
|
+
blue_ip_entry = @host_mapper.host_ip_addresses[blue_host_spec.host.downcase]
|
|
227
227
|
blue_ip_host_spec = blue_ip_entry ? Host::HostInfo.new(host: blue_ip_entry, port: blue_host_spec.port,
|
|
228
228
|
role: blue_host_spec.role) : blue_host_spec
|
|
229
229
|
|
|
@@ -394,7 +394,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
394
394
|
end
|
|
395
395
|
|
|
396
396
|
def iam_enabled?
|
|
397
|
-
@service_container.plugin_manager.
|
|
397
|
+
@service_container.plugin_manager.iam_token_plugin_in_use?
|
|
398
398
|
end
|
|
399
399
|
|
|
400
400
|
def driver_dialect
|
|
@@ -421,14 +421,6 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
421
421
|
true
|
|
422
422
|
end
|
|
423
423
|
|
|
424
|
-
def syntax_error?(err)
|
|
425
|
-
driver_dialect.sql_state(err)&.start_with?('42') || false
|
|
426
|
-
end
|
|
427
|
-
|
|
428
|
-
def db_error?(err)
|
|
429
|
-
!driver_dialect.sql_state(err).nil?
|
|
430
|
-
end
|
|
431
|
-
|
|
432
424
|
# @note Callers must hold @connection_mutex (invoke within a @connection_mutex.synchronize block).
|
|
433
425
|
def status_available?(conn)
|
|
434
426
|
dialect = @service_container.dialect_service.db_dialect
|
|
@@ -508,11 +500,11 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
508
500
|
end
|
|
509
501
|
|
|
510
502
|
def handle_collect_status_error(err, conn)
|
|
511
|
-
if
|
|
503
|
+
if @service_container.dialect_service.schema_error?(err)
|
|
512
504
|
@current_phase = Phase::NOT_CREATED
|
|
513
505
|
logger.warn { "[#{@role}] current_phase: #{@current_phase}, exception while querying for blue/green status." }
|
|
514
|
-
elsif
|
|
515
|
-
if err.message.include?('
|
|
506
|
+
elsif @service_container.dialect_service.database_error?(err)
|
|
507
|
+
if err.message.include?('while retrieving the blue/green fast switchover metadata')
|
|
516
508
|
@current_phase = Phase::NOT_CREATED
|
|
517
509
|
return
|
|
518
510
|
end
|
|
@@ -557,7 +549,8 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
557
549
|
@service_container.plugin_manager,
|
|
558
550
|
@service_container.session_state_service,
|
|
559
551
|
@service_container.storage_service,
|
|
560
|
-
@service_container.monitor_service
|
|
552
|
+
@service_container.monitor_service,
|
|
553
|
+
@service_container.telemetry_factory
|
|
561
554
|
)
|
|
562
555
|
|
|
563
556
|
@host_list_provider = @service_container.dialect_service.db_dialect.create_host_list_provider(scoped_container)
|