launchdarkly-server-sdk 7.3.2 → 8.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 +4 -4
- data/README.md +1 -1
- data/lib/ldclient-rb/config.rb +3 -68
- data/lib/ldclient-rb/context.rb +0 -47
- data/lib/ldclient-rb/evaluation_detail.rb +5 -1
- data/lib/ldclient-rb/events.rb +78 -7
- data/lib/ldclient-rb/impl/big_segments.rb +1 -1
- data/lib/ldclient-rb/impl/event_types.rb +61 -3
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +1 -1
- data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
- data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +23 -0
- data/lib/ldclient-rb/impl/sampler.rb +25 -0
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +84 -15
- data/lib/ldclient-rb/interfaces.rb +97 -0
- data/lib/ldclient-rb/ldclient.rb +106 -17
- data/lib/ldclient-rb/migrations.rb +230 -0
- data/lib/ldclient-rb/util.rb +63 -0
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +1 -0
- metadata +8 -4
@@ -0,0 +1,287 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module LaunchDarkly
|
4
|
+
module Impl
|
5
|
+
module Migrations
|
6
|
+
|
7
|
+
#
|
8
|
+
# A migration config stores references to callable methods which execute customer defined read or write
|
9
|
+
# operations on old or new origins of information. For read operations, an optional comparison function also be
|
10
|
+
# defined.
|
11
|
+
#
|
12
|
+
class MigrationConfig
|
13
|
+
#
|
14
|
+
# @param old [#call] Refer to {#old}
|
15
|
+
# @param new [#call] Refer to {#new}
|
16
|
+
# @param comparison [#call, nil] Refer to {#comparison}
|
17
|
+
#
|
18
|
+
def initialize(old, new, comparison)
|
19
|
+
@old = old
|
20
|
+
@new = new
|
21
|
+
@comparison = comparison
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Callable which receives a nullable payload parameter and returns an {LaunchDarkly::Result}.
|
26
|
+
#
|
27
|
+
# This function call should affect the old migration origin when called.
|
28
|
+
#
|
29
|
+
# @return [#call]
|
30
|
+
#
|
31
|
+
attr_reader :old
|
32
|
+
|
33
|
+
#
|
34
|
+
# Callable which receives a nullable payload parameter and returns an {LaunchDarkly::Result}.
|
35
|
+
#
|
36
|
+
# This function call should affect the new migration origin when called.
|
37
|
+
#
|
38
|
+
# @return [#call]
|
39
|
+
#
|
40
|
+
attr_reader :new
|
41
|
+
|
42
|
+
#
|
43
|
+
# Optional callable which receives two objects of any kind and returns a boolean representing equality.
|
44
|
+
#
|
45
|
+
# The result of this comparison can be sent upstream to LaunchDarkly to enhance migration observability.
|
46
|
+
#
|
47
|
+
# @return [#call, nil]
|
48
|
+
#
|
49
|
+
attr_reader :comparison
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# An implementation of the [LaunchDarkly::Interfaces::Migrations::Migrator] interface, capable of supporting
|
54
|
+
# feature-flag backed technology migrations.
|
55
|
+
#
|
56
|
+
class Migrator
|
57
|
+
include LaunchDarkly::Interfaces::Migrations::Migrator
|
58
|
+
|
59
|
+
#
|
60
|
+
# @param client [LaunchDarkly::LDClient]
|
61
|
+
# @param read_execution_order [Symbol]
|
62
|
+
# @param read_config [MigrationConfig]
|
63
|
+
# @param write_config [MigrationConfig]
|
64
|
+
# @param measure_latency [Boolean]
|
65
|
+
# @param measure_errors [Boolean]
|
66
|
+
#
|
67
|
+
def initialize(client, read_execution_order, read_config, write_config, measure_latency, measure_errors)
|
68
|
+
@client = client
|
69
|
+
@read_execution_order = read_execution_order
|
70
|
+
@read_config = read_config
|
71
|
+
@write_config = write_config
|
72
|
+
@measure_latency = measure_latency
|
73
|
+
@measure_errors = measure_errors
|
74
|
+
@sampler = LaunchDarkly::Impl::Sampler.new(Random.new)
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Perform the configured read operations against the appropriate old and/or new origins.
|
79
|
+
#
|
80
|
+
# @param key [String] The migration-based flag key to use for determining migration stages
|
81
|
+
# @param context [LaunchDarkly::LDContext] The context to use for evaluating the migration flag
|
82
|
+
# @param default_stage [Symbol] The stage to fallback to if one could not be determined for the requested flag
|
83
|
+
# @param payload [String] An optional payload to pass through to the configured read operations.
|
84
|
+
#
|
85
|
+
# @return [LaunchDarkly::Migrations::OperationResult]
|
86
|
+
#
|
87
|
+
def read(key, context, default_stage, payload = nil)
|
88
|
+
stage, tracker = @client.migration_variation(key, context, default_stage)
|
89
|
+
tracker.operation(LaunchDarkly::Migrations::OP_READ)
|
90
|
+
|
91
|
+
old = Executor.new(@client.logger, LaunchDarkly::Migrations::ORIGIN_OLD, @read_config.old, tracker, @measure_latency, @measure_errors, payload)
|
92
|
+
new = Executor.new(@client.logger, LaunchDarkly::Migrations::ORIGIN_NEW, @read_config.new, tracker, @measure_latency, @measure_errors, payload)
|
93
|
+
|
94
|
+
case stage
|
95
|
+
when LaunchDarkly::Migrations::STAGE_OFF
|
96
|
+
result = old.run
|
97
|
+
when LaunchDarkly::Migrations::STAGE_DUALWRITE
|
98
|
+
result = old.run
|
99
|
+
when LaunchDarkly::Migrations::STAGE_SHADOW
|
100
|
+
result = read_both(old, new, @read_config.comparison, @read_execution_order, tracker)
|
101
|
+
when LaunchDarkly::Migrations::STAGE_LIVE
|
102
|
+
result = read_both(new, old, @read_config.comparison, @read_execution_order, tracker)
|
103
|
+
when LaunchDarkly::Migrations::STAGE_RAMPDOWN
|
104
|
+
result = new.run
|
105
|
+
when LaunchDarkly::Migrations::STAGE_COMPLETE
|
106
|
+
result = new.run
|
107
|
+
else
|
108
|
+
result = LaunchDarkly::Migrations::OperationResult.new(
|
109
|
+
LaunchDarkly::Migrations::ORIGIN_OLD,
|
110
|
+
LaunchDarkly::Result.fail("invalid stage #{stage}; cannot execute read")
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
@client.track_migration_op(tracker)
|
115
|
+
|
116
|
+
result
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Perform the configured write operations against the appropriate old and/or new origins.
|
121
|
+
#
|
122
|
+
# @param key [String] The migration-based flag key to use for determining migration stages
|
123
|
+
# @param context [LaunchDarkly::LDContext] The context to use for evaluating the migration flag
|
124
|
+
# @param default_stage [Symbol] The stage to fallback to if one could not be determined for the requested flag
|
125
|
+
# @param payload [String] An optional payload to pass through to the configured write operations.
|
126
|
+
#
|
127
|
+
# @return [LaunchDarkly::Migrations::WriteResult]
|
128
|
+
#
|
129
|
+
def write(key, context, default_stage, payload = nil)
|
130
|
+
stage, tracker = @client.migration_variation(key, context, default_stage)
|
131
|
+
tracker.operation(LaunchDarkly::Migrations::OP_WRITE)
|
132
|
+
|
133
|
+
old = Executor.new(@client.logger, LaunchDarkly::Migrations::ORIGIN_OLD, @write_config.old, tracker, @measure_latency, @measure_errors, payload)
|
134
|
+
new = Executor.new(@client.logger, LaunchDarkly::Migrations::ORIGIN_NEW, @write_config.new, tracker, @measure_latency, @measure_errors, payload)
|
135
|
+
|
136
|
+
case stage
|
137
|
+
when LaunchDarkly::Migrations::STAGE_OFF
|
138
|
+
result = old.run()
|
139
|
+
write_result = LaunchDarkly::Migrations::WriteResult.new(result)
|
140
|
+
when LaunchDarkly::Migrations::STAGE_DUALWRITE
|
141
|
+
authoritative_result, nonauthoritative_result = write_both(old, new, tracker)
|
142
|
+
write_result = LaunchDarkly::Migrations::WriteResult.new(authoritative_result, nonauthoritative_result)
|
143
|
+
when LaunchDarkly::Migrations::STAGE_SHADOW
|
144
|
+
authoritative_result, nonauthoritative_result = write_both(old, new, tracker)
|
145
|
+
write_result = LaunchDarkly::Migrations::WriteResult.new(authoritative_result, nonauthoritative_result)
|
146
|
+
when LaunchDarkly::Migrations::STAGE_LIVE
|
147
|
+
authoritative_result, nonauthoritative_result = write_both(new, old, tracker)
|
148
|
+
write_result = LaunchDarkly::Migrations::WriteResult.new(authoritative_result, nonauthoritative_result)
|
149
|
+
when LaunchDarkly::Migrations::STAGE_RAMPDOWN
|
150
|
+
authoritative_result, nonauthoritative_result = write_both(new, old, tracker)
|
151
|
+
write_result = LaunchDarkly::Migrations::WriteResult.new(authoritative_result, nonauthoritative_result)
|
152
|
+
when LaunchDarkly::Migrations::STAGE_COMPLETE
|
153
|
+
result = new.run()
|
154
|
+
write_result = LaunchDarkly::Migrations::WriteResult.new(result)
|
155
|
+
else
|
156
|
+
result = LaunchDarkly::Migrations::OperationResult.fail(
|
157
|
+
LaunchDarkly::Migrations::ORIGIN_OLD,
|
158
|
+
LaunchDarkly::Result.fail("invalid stage #{stage}; cannot execute write")
|
159
|
+
)
|
160
|
+
write_result = LaunchDarkly::Migrations::WriteResult.new(result)
|
161
|
+
end
|
162
|
+
|
163
|
+
@client.track_migration_op(tracker)
|
164
|
+
|
165
|
+
write_result
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# Execute both read methods in accordance with the requested execution order.
|
170
|
+
#
|
171
|
+
# This method always returns the {LaunchDarkly::Migrations::OperationResult} from running the authoritative read operation. The
|
172
|
+
# non-authoritative executor may fail but it will not affect the return value.
|
173
|
+
#
|
174
|
+
# @param authoritative [Executor]
|
175
|
+
# @param nonauthoritative [Executor]
|
176
|
+
# @param comparison [#call]
|
177
|
+
# @param execution_order [Symbol]
|
178
|
+
# @param tracker [LaunchDarkly::Interfaces::Migrations::OpTracker]
|
179
|
+
#
|
180
|
+
# @return [LaunchDarkly::Migrations::OperationResult]
|
181
|
+
#
|
182
|
+
private def read_both(authoritative, nonauthoritative, comparison, execution_order, tracker)
|
183
|
+
authoritative_result = nil
|
184
|
+
nonauthoritative_result = nil
|
185
|
+
|
186
|
+
case execution_order
|
187
|
+
when LaunchDarkly::Migrations::MigratorBuilder::EXECUTION_PARALLEL
|
188
|
+
auth_handler = Thread.new { authoritative_result = authoritative.run }
|
189
|
+
nonauth_handler = Thread.new { nonauthoritative_result = nonauthoritative.run }
|
190
|
+
|
191
|
+
auth_handler.join()
|
192
|
+
nonauth_handler.join()
|
193
|
+
when LaunchDarkly::Migrations::MigratorBuilder::EXECUTION_RANDOM && @sampler.sample(2)
|
194
|
+
nonauthoritative_result = nonauthoritative.run
|
195
|
+
authoritative_result = authoritative.run
|
196
|
+
else
|
197
|
+
authoritative_result = authoritative.run
|
198
|
+
nonauthoritative_result = nonauthoritative.run
|
199
|
+
end
|
200
|
+
|
201
|
+
return authoritative_result if comparison.nil?
|
202
|
+
|
203
|
+
if authoritative_result.success? && nonauthoritative_result.success?
|
204
|
+
tracker.consistent(->{ comparison.call(authoritative_result.value, nonauthoritative_result.value) })
|
205
|
+
end
|
206
|
+
|
207
|
+
authoritative_result
|
208
|
+
end
|
209
|
+
|
210
|
+
#
|
211
|
+
# Execute both operations sequentially.
|
212
|
+
#
|
213
|
+
# If the authoritative executor fails, do not run the non-authoritative one. As a result, this method will
|
214
|
+
# always return an authoritative {LaunchDarkly::Migrations::OperationResult} as the first value, and optionally the non-authoritative
|
215
|
+
# {LaunchDarkly::Migrations::OperationResult} as the second value.
|
216
|
+
#
|
217
|
+
# @param authoritative [Executor]
|
218
|
+
# @param nonauthoritative [Executor]
|
219
|
+
# @param tracker [LaunchDarkly::Interfaces::Migrations::OpTracker]
|
220
|
+
#
|
221
|
+
# @return [Array<LaunchDarkly::Migrations::OperationResult, [LaunchDarkly::Migrations::OperationResult, nil]>]
|
222
|
+
#
|
223
|
+
private def write_both(authoritative, nonauthoritative, tracker)
|
224
|
+
authoritative_result = authoritative.run()
|
225
|
+
tracker.invoked(authoritative.origin)
|
226
|
+
|
227
|
+
return authoritative_result, nil unless authoritative_result.success?
|
228
|
+
|
229
|
+
nonauthoritative_result = nonauthoritative.run()
|
230
|
+
tracker.invoked(nonauthoritative.origin)
|
231
|
+
|
232
|
+
[authoritative_result, nonauthoritative_result]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
#
|
237
|
+
# Utility class for executing migration operations while also tracking our built-in migration measurements.
|
238
|
+
#
|
239
|
+
class Executor
|
240
|
+
#
|
241
|
+
# @return [Symbol]
|
242
|
+
#
|
243
|
+
attr_reader :origin
|
244
|
+
|
245
|
+
#
|
246
|
+
# @param origin [Symbol]
|
247
|
+
# @param fn [#call]
|
248
|
+
# @param tracker [LaunchDarkly::Interfaces::Migrations::OpTracker]
|
249
|
+
# @param measure_latency [Boolean]
|
250
|
+
# @param measure_errors [Boolean]
|
251
|
+
# @param payload [Object, nil]
|
252
|
+
#
|
253
|
+
def initialize(logger, origin, fn, tracker, measure_latency, measure_errors, payload)
|
254
|
+
@logger = logger
|
255
|
+
@origin = origin
|
256
|
+
@fn = fn
|
257
|
+
@tracker = tracker
|
258
|
+
@measure_latency = measure_latency
|
259
|
+
@measure_errors = measure_errors
|
260
|
+
@payload = payload
|
261
|
+
end
|
262
|
+
|
263
|
+
#
|
264
|
+
# Execute the configured operation and track any available measurements.
|
265
|
+
#
|
266
|
+
# @return [LaunchDarkly::Migrations::OperationResult]
|
267
|
+
#
|
268
|
+
def run()
|
269
|
+
start = Time.now
|
270
|
+
|
271
|
+
begin
|
272
|
+
result = @fn.call(@payload)
|
273
|
+
rescue => e
|
274
|
+
LaunchDarkly::Util.log_exception(@logger, "Unexpected error running method for '#{origin}' origin", e)
|
275
|
+
result = LaunchDarkly::Result.fail("'#{origin}' operation raised an exception", e)
|
276
|
+
end
|
277
|
+
|
278
|
+
@tracker.latency(@origin, (Time.now - start) * 1_000) if @measure_latency
|
279
|
+
@tracker.error(@origin) if @measure_errors && !result.success?
|
280
|
+
@tracker.invoked(@origin)
|
281
|
+
|
282
|
+
LaunchDarkly::Migrations::OperationResult.new(@origin, result)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require "set"
|
2
|
+
require "ldclient-rb/impl/sampler"
|
3
|
+
require "logger"
|
4
|
+
|
5
|
+
module LaunchDarkly
|
6
|
+
module Impl
|
7
|
+
module Migrations
|
8
|
+
class OpTracker
|
9
|
+
include LaunchDarkly::Interfaces::Migrations::OpTracker
|
10
|
+
|
11
|
+
#
|
12
|
+
# @param logger [Logger] logger
|
13
|
+
# @param key [string] key
|
14
|
+
# @param flag [LaunchDarkly::Impl::Model::FeatureFlag] flag
|
15
|
+
# @param context [LaunchDarkly::LDContext] context
|
16
|
+
# @param detail [LaunchDarkly::EvaluationDetail] detail
|
17
|
+
# @param default_stage [Symbol] default_stage
|
18
|
+
#
|
19
|
+
def initialize(logger, key, flag, context, detail, default_stage)
|
20
|
+
@logger = logger
|
21
|
+
@key = key
|
22
|
+
@flag = flag
|
23
|
+
@context = context
|
24
|
+
@detail = detail
|
25
|
+
@default_stage = default_stage
|
26
|
+
@sampler = LaunchDarkly::Impl::Sampler.new(Random.new)
|
27
|
+
|
28
|
+
@mutex = Mutex.new
|
29
|
+
|
30
|
+
# @type [Symbol, nil]
|
31
|
+
@operation = nil
|
32
|
+
|
33
|
+
# @type [Set<Symbol>]
|
34
|
+
@invoked = Set.new
|
35
|
+
# @type [Boolean, nil]
|
36
|
+
@consistent = nil
|
37
|
+
|
38
|
+
# @type [Int]
|
39
|
+
@consistent_ratio = @flag&.migration_settings&.check_ratio
|
40
|
+
@consistent_ratio = 1 if @consistent_ratio.nil?
|
41
|
+
|
42
|
+
# @type [Set<Symbol>]
|
43
|
+
@errors = Set.new
|
44
|
+
# @type [Hash<Symbol, Float>]
|
45
|
+
@latencies = Hash.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def operation(operation)
|
49
|
+
return unless LaunchDarkly::Migrations::VALID_OPERATIONS.include? operation
|
50
|
+
|
51
|
+
@mutex.synchronize do
|
52
|
+
@operation = operation
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def invoked(origin)
|
57
|
+
return unless LaunchDarkly::Migrations::VALID_ORIGINS.include? origin
|
58
|
+
|
59
|
+
@mutex.synchronize do
|
60
|
+
@invoked.add(origin)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def consistent(is_consistent)
|
65
|
+
@mutex.synchronize do
|
66
|
+
if @sampler.sample(@consistent_ratio)
|
67
|
+
begin
|
68
|
+
@consistent = is_consistent.call
|
69
|
+
rescue => e
|
70
|
+
LaunchDarkly::Util.log_exception(@logger, "Exception raised during consistency check; failed to record measurement", e)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def error(origin)
|
77
|
+
return unless LaunchDarkly::Migrations::VALID_ORIGINS.include? origin
|
78
|
+
|
79
|
+
@mutex.synchronize do
|
80
|
+
@errors.add(origin)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def latency(origin, duration)
|
85
|
+
return unless LaunchDarkly::Migrations::VALID_ORIGINS.include? origin
|
86
|
+
return unless duration.is_a? Numeric
|
87
|
+
return if duration < 0
|
88
|
+
|
89
|
+
@mutex.synchronize do
|
90
|
+
@latencies[origin] = duration
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def build
|
95
|
+
@mutex.synchronize do
|
96
|
+
return "operation cannot contain an empty key" if @key.empty?
|
97
|
+
return "operation not provided" if @operation.nil?
|
98
|
+
return "no origins were invoked" if @invoked.empty?
|
99
|
+
return "provided context was invalid" unless @context.valid?
|
100
|
+
|
101
|
+
result = check_invoked_consistency
|
102
|
+
return result unless result == true
|
103
|
+
|
104
|
+
LaunchDarkly::Impl::MigrationOpEvent.new(
|
105
|
+
LaunchDarkly::Impl::Util.current_time_millis,
|
106
|
+
@context,
|
107
|
+
@key,
|
108
|
+
@flag,
|
109
|
+
@operation,
|
110
|
+
@default_stage,
|
111
|
+
@detail,
|
112
|
+
@invoked,
|
113
|
+
@consistent,
|
114
|
+
@consistent_ratio,
|
115
|
+
@errors,
|
116
|
+
@latencies
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private def check_invoked_consistency
|
122
|
+
LaunchDarkly::Migrations::VALID_ORIGINS.each do |origin|
|
123
|
+
next if @invoked.include? origin
|
124
|
+
|
125
|
+
return "provided latency for origin '#{origin}' without recording invocation" if @latencies.include? origin
|
126
|
+
return "provided error for origin '#{origin}' without recording invocation" if @errors.include? origin
|
127
|
+
end
|
128
|
+
|
129
|
+
return "provided consistency without recording both invocations" if !@consistent.nil? && @invoked.size != 2
|
130
|
+
|
131
|
+
true
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -26,6 +26,10 @@ module LaunchDarkly
|
|
26
26
|
@version = data[:version]
|
27
27
|
@deleted = !!data[:deleted]
|
28
28
|
return if @deleted
|
29
|
+
migration_settings = data[:migration] || {}
|
30
|
+
@migration_settings = MigrationSettings.new(migration_settings[:checkRatio])
|
31
|
+
@sampling_ratio = data[:samplingRatio]
|
32
|
+
@exclude_from_summaries = !!data[:excludeFromSummaries]
|
29
33
|
@variations = data[:variations] || []
|
30
34
|
@on = !!data[:on]
|
31
35
|
fallthrough = data[:fallthrough] || {}
|
@@ -63,6 +67,12 @@ module LaunchDarkly
|
|
63
67
|
attr_reader :version
|
64
68
|
# @return [Boolean]
|
65
69
|
attr_reader :deleted
|
70
|
+
# @return [MigrationSettings, nil]
|
71
|
+
attr_reader :migration_settings
|
72
|
+
# @return [Integer, nil]
|
73
|
+
attr_reader :sampling_ratio
|
74
|
+
# @return [Boolean, nil]
|
75
|
+
attr_reader :exclude_from_summaries
|
66
76
|
# @return [Array]
|
67
77
|
attr_reader :variations
|
68
78
|
# @return [Boolean]
|
@@ -173,6 +183,19 @@ module LaunchDarkly
|
|
173
183
|
attr_reader :variation_or_rollout
|
174
184
|
end
|
175
185
|
|
186
|
+
|
187
|
+
class MigrationSettings
|
188
|
+
#
|
189
|
+
# @param check_ratio [Int, nil]
|
190
|
+
#
|
191
|
+
def initialize(check_ratio)
|
192
|
+
@check_ratio = check_ratio
|
193
|
+
end
|
194
|
+
|
195
|
+
# @return [Integer, nil]
|
196
|
+
attr_reader :check_ratio
|
197
|
+
end
|
198
|
+
|
176
199
|
class VariationOrRollout
|
177
200
|
def initialize(variation, rollout_data, flag = nil, errors_out = nil, description = nil)
|
178
201
|
@variation = variation
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module LaunchDarkly
|
2
|
+
module Impl
|
3
|
+
class Sampler
|
4
|
+
#
|
5
|
+
# @param random [Random]
|
6
|
+
#
|
7
|
+
def initialize(random)
|
8
|
+
@random = random
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# @param ratio [Int]
|
13
|
+
#
|
14
|
+
# @return [Boolean]
|
15
|
+
#
|
16
|
+
def sample(ratio)
|
17
|
+
return false unless ratio.is_a? Integer
|
18
|
+
return false if ratio <= 0
|
19
|
+
return true if ratio == 1
|
20
|
+
|
21
|
+
@random.rand(1.0) < 1.0 / ratio
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -43,6 +43,47 @@ module LaunchDarkly
|
|
43
43
|
self
|
44
44
|
end
|
45
45
|
|
46
|
+
#
|
47
|
+
# Set the migration related settings for this feature flag.
|
48
|
+
#
|
49
|
+
# The settings hash should be built using the {FlagMigrationSettingsBuilder}.
|
50
|
+
#
|
51
|
+
# @param settings [Hash]
|
52
|
+
# @return [FlagBuilder] the builder
|
53
|
+
#
|
54
|
+
def migration_settings(settings)
|
55
|
+
@migration_settings = settings
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Set the sampling ratio for this flag. This ratio is used to control the emission rate of feature, debug, and
|
61
|
+
# migration op events.
|
62
|
+
#
|
63
|
+
# General usage should not require interacting with this method.
|
64
|
+
#
|
65
|
+
# @param ratio [Integer]
|
66
|
+
# @return [FlagBuilder]
|
67
|
+
#
|
68
|
+
def sampling_ratio(ratio)
|
69
|
+
@sampling_ratio = ratio
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Set the option to exclude this flag from summary events. This is used to control the size of the summary event
|
75
|
+
# in the event certain flag payloads are large.
|
76
|
+
#
|
77
|
+
# General usage should not require interacting with this method.
|
78
|
+
#
|
79
|
+
# @param exclude [Boolean]
|
80
|
+
# @return [FlagBuilder]
|
81
|
+
#
|
82
|
+
def exclude_from_summaries(exclude)
|
83
|
+
@exclude_from_summaries = exclude
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
46
87
|
#
|
47
88
|
# Specifies the fallthrough variation. The fallthrough is the value
|
48
89
|
# that is returned if targeting is on and the context was not matched by a more specific
|
@@ -128,11 +169,6 @@ module LaunchDarkly
|
|
128
169
|
end
|
129
170
|
end
|
130
171
|
|
131
|
-
#
|
132
|
-
# @deprecated Backwards compatibility alias for #variation_for_all
|
133
|
-
#
|
134
|
-
alias_method :variation_for_all_users, :variation_for_all
|
135
|
-
|
136
172
|
#
|
137
173
|
# Sets the flag to always return the specified variation value for all context.
|
138
174
|
#
|
@@ -148,11 +184,6 @@ module LaunchDarkly
|
|
148
184
|
variations(value).variation_for_all(0)
|
149
185
|
end
|
150
186
|
|
151
|
-
#
|
152
|
-
# @deprecated Backwards compatibility alias for #value_for_all
|
153
|
-
#
|
154
|
-
alias_method :value_for_all_users, :value_for_all
|
155
|
-
|
156
187
|
#
|
157
188
|
# Sets the flag to return the specified variation for a specific context key when targeting
|
158
189
|
# is on.
|
@@ -315,11 +346,6 @@ module LaunchDarkly
|
|
315
346
|
self
|
316
347
|
end
|
317
348
|
|
318
|
-
#
|
319
|
-
# @deprecated Backwards compatibility alias for #clear_targets
|
320
|
-
#
|
321
|
-
alias_method :clear_user_targets, :clear_targets
|
322
|
-
|
323
349
|
#
|
324
350
|
# Removes any existing rules from the flag.
|
325
351
|
# This undoes the effect of methods like {#if_match}
|
@@ -376,6 +402,18 @@ module LaunchDarkly
|
|
376
402
|
res[:fallthrough] = { variation: @fallthrough_variation }
|
377
403
|
end
|
378
404
|
|
405
|
+
unless @migration_settings.nil?
|
406
|
+
res[:migration] = @migration_settings
|
407
|
+
end
|
408
|
+
|
409
|
+
unless @sampling_ratio.nil? || @sampling_ratio == 1
|
410
|
+
res[:samplingRatio] = @sampling_ratio
|
411
|
+
end
|
412
|
+
|
413
|
+
unless @exclude_from_summaries.nil? || !@exclude_from_summaries
|
414
|
+
res[:excludeFromSummaries] = @exclude_from_summaries
|
415
|
+
end
|
416
|
+
|
379
417
|
unless @targets.nil?
|
380
418
|
targets = []
|
381
419
|
context_targets = []
|
@@ -403,6 +441,37 @@ module LaunchDarkly
|
|
403
441
|
res
|
404
442
|
end
|
405
443
|
|
444
|
+
#
|
445
|
+
# A builder for feature flag migration settings to be used with {FlagBuilder}.
|
446
|
+
#
|
447
|
+
# In the LaunchDarkly model, a flag can be a standard feature flag, or it can be a migration-related flag, in
|
448
|
+
# which case it has migration-specified related settings. These settings control things like the rate at which
|
449
|
+
# reads are tested for consistency between origins.
|
450
|
+
#
|
451
|
+
class FlagMigrationSettingsBuilder
|
452
|
+
def initialize()
|
453
|
+
@check_ratio = nil
|
454
|
+
end
|
455
|
+
|
456
|
+
#
|
457
|
+
# @param ratio [Integer]
|
458
|
+
# @return [FlagMigrationSettingsBuilder]
|
459
|
+
#
|
460
|
+
def check_ratio(ratio)
|
461
|
+
return unless ratio.is_a? Integer
|
462
|
+
@check_ratio = ratio
|
463
|
+
self
|
464
|
+
end
|
465
|
+
|
466
|
+
def build
|
467
|
+
return nil if @check_ratio.nil? || @check_ratio == 1
|
468
|
+
|
469
|
+
{
|
470
|
+
"checkRatio": @check_ratio,
|
471
|
+
}
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
406
475
|
#
|
407
476
|
# A builder for feature flag rules to be used with {FlagBuilder}.
|
408
477
|
#
|