ff-ruby-server-sdk 0.0.2 → 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 +4 -4
- data/.run/build.sh.run.xml +17 -0
- data/.run/install.sh.run.xml +17 -0
- data/.run/openapi.sh.run.xml +17 -0
- data/.run/publish.sh.run.xml +17 -0
- data/.run/sdk_test.rb.run.xml +3 -3
- data/.run/unpublish.sh.run.xml +17 -0
- data/CHANGELOG.md +1 -1
- data/Gemfile +20 -3
- data/README.md +155 -7
- data/api.yaml +736 -0
- data/example/example.rb +99 -3
- data/lib/ff/ruby/server/sdk/api/auth_service.rb +91 -0
- data/lib/ff/ruby/server/sdk/api/cf_client.rb +93 -0
- data/lib/ff/ruby/server/sdk/api/client_callback.rb +45 -0
- data/lib/ff/ruby/server/sdk/api/config.rb +140 -0
- data/lib/ff/ruby/server/sdk/api/config_builder.rb +116 -0
- data/lib/ff/ruby/server/sdk/api/default_cache.rb +112 -0
- data/lib/ff/ruby/server/sdk/api/evaluation.rb +29 -0
- data/lib/ff/ruby/server/sdk/api/evaluator.rb +526 -0
- data/lib/ff/ruby/server/sdk/api/file_map_store.rb +60 -0
- data/lib/ff/ruby/server/sdk/api/flag_evaluate_callback.rb +13 -0
- data/lib/ff/ruby/server/sdk/api/inner_client.rb +311 -0
- data/lib/ff/ruby/server/sdk/api/inner_client_flag_evaluate_callback.rb +30 -0
- data/lib/ff/ruby/server/sdk/api/inner_client_metrics_callback.rb +33 -0
- data/lib/ff/ruby/server/sdk/api/inner_client_repository_callback.rb +44 -0
- data/lib/ff/ruby/server/sdk/api/inner_client_updater.rb +63 -0
- data/lib/ff/ruby/server/sdk/api/metrics_callback.rb +19 -0
- data/lib/ff/ruby/server/sdk/api/metrics_event.rb +16 -0
- data/lib/ff/ruby/server/sdk/api/metrics_processor.rb +297 -0
- data/lib/ff/ruby/server/sdk/api/operators.rb +20 -0
- data/lib/ff/ruby/server/sdk/api/polling_processor.rb +164 -0
- data/lib/ff/ruby/server/sdk/api/repository_callback.rb +28 -0
- data/lib/ff/ruby/server/sdk/api/storage_repository.rb +263 -0
- data/lib/ff/ruby/server/sdk/api/summary_metrics.rb +16 -0
- data/lib/ff/ruby/server/sdk/api/update_processor.rb +149 -0
- data/lib/ff/ruby/server/sdk/common/cache.rb +27 -0
- data/lib/ff/ruby/server/sdk/common/closeable.rb +7 -0
- data/lib/ff/ruby/server/sdk/common/destroyable.rb +12 -0
- data/lib/ff/ruby/server/sdk/common/repository.rb +45 -0
- data/lib/ff/ruby/server/sdk/common/storage.rb +29 -0
- data/lib/ff/ruby/server/sdk/connector/connector.rb +44 -0
- data/lib/ff/ruby/server/sdk/connector/events.rb +118 -0
- data/lib/ff/ruby/server/sdk/connector/harness_connector.rb +236 -0
- data/lib/ff/ruby/server/sdk/connector/service.rb +19 -0
- data/lib/ff/ruby/server/sdk/connector/updater.rb +32 -0
- data/lib/ff/ruby/server/sdk/dto/message.rb +13 -0
- data/lib/ff/ruby/server/sdk/dto/target.rb +24 -0
- data/lib/ff/ruby/server/sdk/version.rb +2 -1
- data/lib/ff/ruby/server/sdk.rb +39 -3
- data/openapitools.json +7 -0
- data/scripts/openapi.sh +35 -0
- data/scripts/sdk_specs.sh +1 -1
- metadata +46 -3
- data/lib/ff/ruby/server/sdk/cf_client.rb +0 -6
@@ -0,0 +1,297 @@
|
|
1
|
+
require "time"
|
2
|
+
require "concurrent-ruby"
|
3
|
+
|
4
|
+
require_relative "../dto/target"
|
5
|
+
require_relative "../../sdk/version"
|
6
|
+
require_relative "../common/closeable"
|
7
|
+
require_relative "../api/metrics_event"
|
8
|
+
require_relative "../api/summary_metrics"
|
9
|
+
|
10
|
+
class MetricsProcessor < Closeable
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
|
14
|
+
connector,
|
15
|
+
config,
|
16
|
+
callback
|
17
|
+
)
|
18
|
+
|
19
|
+
unless connector.kind_of?(Connector)
|
20
|
+
|
21
|
+
raise "The 'connector' must be of '" + Connector.to_s + "' data type"
|
22
|
+
end
|
23
|
+
|
24
|
+
unless callback.kind_of?(MetricsCallback)
|
25
|
+
|
26
|
+
raise "The 'callback' must be of '" + MetricsCallback.to_s + "' data type"
|
27
|
+
end
|
28
|
+
|
29
|
+
unless config.kind_of?(Config)
|
30
|
+
|
31
|
+
raise "The 'config' must be of '" + Config.to_s + "' data type"
|
32
|
+
end
|
33
|
+
|
34
|
+
@config = config
|
35
|
+
@callback = callback
|
36
|
+
@connector = connector
|
37
|
+
|
38
|
+
@sdk_type = "SDK_TYPE"
|
39
|
+
@global_target_set = Set[]
|
40
|
+
@staging_target_set = Set[]
|
41
|
+
@target_attribute = "target"
|
42
|
+
@global_target = "__global__cf_target" # <--- This target identifier is used to aggregate and send data for all
|
43
|
+
# targets as a summary
|
44
|
+
|
45
|
+
@ready = false
|
46
|
+
@jar_version = ""
|
47
|
+
@server = "server"
|
48
|
+
@sdk_version = "SDK_VERSION"
|
49
|
+
@sdk_language = "SDK_LANGUAGE"
|
50
|
+
@global_target_name = "Global Target"
|
51
|
+
@feature_name_attribute = "featureName"
|
52
|
+
@variation_identifier_attribute = "variationIdentifier"
|
53
|
+
|
54
|
+
@queue = SizedQueue.new(@config.buffer_size)
|
55
|
+
@executor = Concurrent::FixedThreadPool.new(100)
|
56
|
+
|
57
|
+
@callback.on_metrics_ready
|
58
|
+
end
|
59
|
+
|
60
|
+
def start
|
61
|
+
|
62
|
+
@config.logger.info "Starting metrics processor with request interval: " + @config.frequency.to_s
|
63
|
+
start_async
|
64
|
+
end
|
65
|
+
|
66
|
+
def stop
|
67
|
+
|
68
|
+
@config.logger.info "Stopping metrics processor"
|
69
|
+
stop_async
|
70
|
+
end
|
71
|
+
|
72
|
+
def close
|
73
|
+
|
74
|
+
stop
|
75
|
+
@config.logger.info "Closing metrics processor"
|
76
|
+
end
|
77
|
+
|
78
|
+
def push_to_queue(
|
79
|
+
|
80
|
+
target,
|
81
|
+
feature_config,
|
82
|
+
variation
|
83
|
+
)
|
84
|
+
|
85
|
+
@executor.post do
|
86
|
+
|
87
|
+
@config.logger.debug "Pushing to the metrics queue: START"
|
88
|
+
|
89
|
+
event = MetricsEvent.new(feature_config, target, variation)
|
90
|
+
@queue.push(event)
|
91
|
+
|
92
|
+
@config.logger.debug "Pushing to the metrics queue: END, queue size: " + @queue.size.to_s
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def send_data_and_reset_cache(data)
|
98
|
+
|
99
|
+
@config.logger.debug "Reading from queue and building cache"
|
100
|
+
|
101
|
+
@jar_version = get_version
|
102
|
+
|
103
|
+
unless data.empty?
|
104
|
+
|
105
|
+
map = {}
|
106
|
+
|
107
|
+
data.each do |event|
|
108
|
+
|
109
|
+
new_value = 1
|
110
|
+
current = map[event]
|
111
|
+
|
112
|
+
if current != nil
|
113
|
+
|
114
|
+
new_value = current + 1
|
115
|
+
end
|
116
|
+
|
117
|
+
map[event] = new_value
|
118
|
+
end
|
119
|
+
|
120
|
+
metrics = prepare_summary_metrics_body(map)
|
121
|
+
|
122
|
+
if !metrics.metrics_data.empty? && !metrics.target_data.empty?
|
123
|
+
|
124
|
+
start_time = (Time.now.to_f * 1000).to_i
|
125
|
+
|
126
|
+
@connector.post_metrics(metrics)
|
127
|
+
|
128
|
+
end_time = (Time.now.to_f * 1000).to_i
|
129
|
+
|
130
|
+
if end_time - start_time > @config.metrics_service_acceptable_duration
|
131
|
+
|
132
|
+
@config.logger.debug "Metrics service API duration=[" + (end_time - start_time).to_s + "]"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
@global_target_set.merge(@staging_target_set)
|
137
|
+
@staging_target_set.clear
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
protected
|
142
|
+
|
143
|
+
def run_one_iteration
|
144
|
+
|
145
|
+
@config.logger.debug "Async metrics iteration: " + @thread.to_s
|
146
|
+
|
147
|
+
data = []
|
148
|
+
|
149
|
+
until @queue.empty?
|
150
|
+
|
151
|
+
item = @queue.pop
|
152
|
+
data.push(item)
|
153
|
+
end
|
154
|
+
|
155
|
+
send_data_and_reset_cache(data)
|
156
|
+
end
|
157
|
+
|
158
|
+
def prepare_summary_metrics_body(data)
|
159
|
+
|
160
|
+
summary_metrics_data = {}
|
161
|
+
metrics = OpenapiClient::Metrics.new({ :target_data => [], :metrics_data => [] })
|
162
|
+
|
163
|
+
add_target_data(
|
164
|
+
|
165
|
+
metrics,
|
166
|
+
Target.new(
|
167
|
+
|
168
|
+
name = @global_target_name,
|
169
|
+
identifier = @global_target
|
170
|
+
)
|
171
|
+
)
|
172
|
+
|
173
|
+
data.each do |key, value|
|
174
|
+
|
175
|
+
target = key.target
|
176
|
+
|
177
|
+
add_target_data(metrics, target)
|
178
|
+
|
179
|
+
summary_metrics = prepare_summary_metrics_key(key)
|
180
|
+
|
181
|
+
summary_metrics_data[summary_metrics] = value
|
182
|
+
end
|
183
|
+
|
184
|
+
summary_metrics_data.each do |key, value|
|
185
|
+
|
186
|
+
metrics_data = OpenapiClient::MetricsData.new({ :attributes => [] })
|
187
|
+
metrics_data.timestamp = (Time.now.to_f * 1000).to_i
|
188
|
+
metrics_data.count = value
|
189
|
+
metrics_data.metrics_type = "FFMETRICS"
|
190
|
+
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @feature_name_attribute, :value => key.feature_name }))
|
191
|
+
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @variation_identifier_attribute, :value => key.variation_identifier }))
|
192
|
+
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @target_attribute, :value => @global_target }))
|
193
|
+
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_type, :value => @server }))
|
194
|
+
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_language, :value => "ruby" }))
|
195
|
+
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_version, :value => @jar_version }))
|
196
|
+
|
197
|
+
metrics.metrics_data.push(metrics_data)
|
198
|
+
end
|
199
|
+
|
200
|
+
metrics
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
def start_async
|
206
|
+
|
207
|
+
@config.logger.debug "Async starting: " + self.to_s
|
208
|
+
|
209
|
+
@ready = true
|
210
|
+
|
211
|
+
@thread = Thread.new do
|
212
|
+
|
213
|
+
@config.logger.debug "Async started: " + self.to_s
|
214
|
+
|
215
|
+
while @ready do
|
216
|
+
|
217
|
+
unless @initialized
|
218
|
+
|
219
|
+
@initialized = true
|
220
|
+
@config.logger.info "Metrics processor initialized"
|
221
|
+
end
|
222
|
+
|
223
|
+
sleep(@config.frequency)
|
224
|
+
|
225
|
+
run_one_iteration
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
@thread.run
|
230
|
+
end
|
231
|
+
|
232
|
+
def stop_async
|
233
|
+
|
234
|
+
@queue.clear
|
235
|
+
|
236
|
+
@ready = false
|
237
|
+
@initialized = false
|
238
|
+
end
|
239
|
+
|
240
|
+
def prepare_summary_metrics_key(key)
|
241
|
+
|
242
|
+
SummaryMetrics.new(
|
243
|
+
|
244
|
+
feature_name = key.feature_config.feature,
|
245
|
+
variation_identifier = key.variation.identifier,
|
246
|
+
variation_value = key.variation.value
|
247
|
+
)
|
248
|
+
end
|
249
|
+
|
250
|
+
def add_target_data(metrics, target)
|
251
|
+
|
252
|
+
target_data = OpenapiClient::TargetData.new({ :attributes => [] })
|
253
|
+
private_attributes = target.private_attributes
|
254
|
+
|
255
|
+
if !@staging_target_set.include?(target) && !@global_target_set.include?(target) && !target.is_private
|
256
|
+
|
257
|
+
@staging_target_set.add(target)
|
258
|
+
|
259
|
+
attributes = target.attributes
|
260
|
+
|
261
|
+
attributes.each do |k, v|
|
262
|
+
|
263
|
+
key_value = OpenapiClient::KeyValue.new
|
264
|
+
|
265
|
+
if !private_attributes.empty?
|
266
|
+
|
267
|
+
unless private_attributes.include?(k)
|
268
|
+
|
269
|
+
key_value = OpenapiClient::KeyValue.new({ :key => k, :value => v.to_s })
|
270
|
+
end
|
271
|
+
else
|
272
|
+
|
273
|
+
key_value = OpenapiClient::KeyValue.new({ :key => k, :value => v.to_s })
|
274
|
+
end
|
275
|
+
|
276
|
+
target_data.attributes.push(key_value)
|
277
|
+
end
|
278
|
+
|
279
|
+
target_data.identifier = target.identifier
|
280
|
+
|
281
|
+
if target.name == nil || target.name == ""
|
282
|
+
|
283
|
+
target_data.name = target.identifier
|
284
|
+
else
|
285
|
+
|
286
|
+
target_data.name = target.name
|
287
|
+
end
|
288
|
+
|
289
|
+
metrics.target_data.push(target_data)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def get_version
|
294
|
+
|
295
|
+
Ff::Ruby::Server::Sdk::VERSION
|
296
|
+
end
|
297
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require_relative "../common/closeable"
|
2
|
+
|
3
|
+
class PollingProcessor < Closeable
|
4
|
+
|
5
|
+
def initialize(
|
6
|
+
|
7
|
+
connector,
|
8
|
+
repository,
|
9
|
+
poll_interval_in_sec,
|
10
|
+
callback,
|
11
|
+
logger = nil
|
12
|
+
)
|
13
|
+
|
14
|
+
@callback = callback
|
15
|
+
@connector = connector
|
16
|
+
@repository = repository
|
17
|
+
@poll_interval_in_sec = poll_interval_in_sec
|
18
|
+
|
19
|
+
if logger != nil
|
20
|
+
|
21
|
+
@logger = logger
|
22
|
+
else
|
23
|
+
|
24
|
+
@logger = Logger.new(STDOUT)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def retrieve_flags
|
29
|
+
|
30
|
+
flags = []
|
31
|
+
|
32
|
+
@logger.info "Fetching flags started"
|
33
|
+
|
34
|
+
result = @connector.get_flags
|
35
|
+
|
36
|
+
if result != nil
|
37
|
+
|
38
|
+
@logger.info "Flags are fetched"
|
39
|
+
|
40
|
+
result.each { |fc|
|
41
|
+
|
42
|
+
if fc != nil
|
43
|
+
|
44
|
+
@repository.set_flag(fc.feature, fc)
|
45
|
+
flags.push(fc)
|
46
|
+
end
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
@logger.info "Fetching flags finished"
|
51
|
+
|
52
|
+
flags
|
53
|
+
end
|
54
|
+
|
55
|
+
def retrieve_segments
|
56
|
+
|
57
|
+
segments = []
|
58
|
+
|
59
|
+
@logger.info "Fetching segments started"
|
60
|
+
|
61
|
+
result = @connector.get_segments
|
62
|
+
|
63
|
+
if result != nil
|
64
|
+
|
65
|
+
@logger.info "Segments are fetched"
|
66
|
+
|
67
|
+
result.each { |s|
|
68
|
+
|
69
|
+
if s != nil
|
70
|
+
|
71
|
+
@repository.set_flag(s.identifier, s)
|
72
|
+
flags.push(s)
|
73
|
+
end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
@logger.info "Fetching segments finished"
|
78
|
+
|
79
|
+
segments
|
80
|
+
end
|
81
|
+
|
82
|
+
def start_async
|
83
|
+
|
84
|
+
@logger.debug "Async starting: " + self.to_s
|
85
|
+
|
86
|
+
@ready = true
|
87
|
+
|
88
|
+
@thread = Thread.new do
|
89
|
+
|
90
|
+
@logger.debug "Async started: " + self.to_s
|
91
|
+
|
92
|
+
while @ready do
|
93
|
+
|
94
|
+
@logger.debug "Async poll iteration"
|
95
|
+
|
96
|
+
if @callback != nil
|
97
|
+
|
98
|
+
@callback.on_poller_iteration(self)
|
99
|
+
end
|
100
|
+
|
101
|
+
begin
|
102
|
+
|
103
|
+
retrieve_flags
|
104
|
+
retrieve_segments
|
105
|
+
|
106
|
+
unless @initialized
|
107
|
+
|
108
|
+
@initialized = true
|
109
|
+
@logger.info "PollingProcessor initialized"
|
110
|
+
|
111
|
+
if @callback != nil
|
112
|
+
|
113
|
+
@callback.on_poller_ready(self)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
rescue OpenapiClient::ApiError => e
|
118
|
+
|
119
|
+
if @callback != nil
|
120
|
+
|
121
|
+
@callback.on_poller_error(e)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
sleep(@poll_interval_in_sec)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
@thread.run
|
130
|
+
end
|
131
|
+
|
132
|
+
def stop_async
|
133
|
+
|
134
|
+
@ready = false
|
135
|
+
@initialized = false
|
136
|
+
end
|
137
|
+
|
138
|
+
def start
|
139
|
+
|
140
|
+
@logger.info "Starting PollingProcessor with request interval: " + @poll_interval_in_sec.to_s
|
141
|
+
start_async
|
142
|
+
end
|
143
|
+
|
144
|
+
def stop
|
145
|
+
|
146
|
+
@logger.info "Stopping PollingProcessor"
|
147
|
+
stop_async
|
148
|
+
unless @ready
|
149
|
+
|
150
|
+
@logger.info "PollingProcessor stopped"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def close
|
155
|
+
|
156
|
+
stop
|
157
|
+
@logger.info "Closing PollingProcessor"
|
158
|
+
end
|
159
|
+
|
160
|
+
def is_ready
|
161
|
+
|
162
|
+
@ready && @initialized
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class RepositoryCallback
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
|
6
|
+
@tbi = "To be implemented"
|
7
|
+
end
|
8
|
+
|
9
|
+
def on_flag_stored(identifier)
|
10
|
+
|
11
|
+
raise @tbi
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_flag_deleted(identifier)
|
15
|
+
|
16
|
+
raise @tbi
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_segment_stored(identifier)
|
20
|
+
|
21
|
+
raise @tbi
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_segment_deleted(identifier)
|
25
|
+
|
26
|
+
raise @tbi
|
27
|
+
end
|
28
|
+
end
|