splitclient-rb 8.4.0 → 8.11.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/.github/workflows/ci.yml +9 -2
- data/CHANGES.txt +24 -0
- data/LICENSE +169 -13
- data/NOTICE.txt +5 -0
- data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +9 -7
- data/lib/splitclient-rb/cache/filter/bloom_filter.rb +10 -6
- data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +1 -0
- data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
- data/lib/splitclient-rb/cache/repositories/segments_repository.rb +21 -2
- data/lib/splitclient-rb/cache/repositories/splits_repository.rb +42 -12
- data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +24 -10
- data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +2 -1
- data/lib/splitclient-rb/clients/split_client.rb +146 -62
- data/lib/splitclient-rb/engine/api/client.rb +8 -0
- data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +1 -0
- data/lib/splitclient-rb/engine/api/splits.rb +100 -24
- data/lib/splitclient-rb/engine/auth_api_client.rb +3 -3
- data/lib/splitclient-rb/engine/common/impressions_manager.rb +56 -30
- data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
- data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
- data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
- data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
- data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
- data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
- data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +58 -11
- data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +3 -1
- data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
- data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +78 -0
- data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
- data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
- data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
- data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
- data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
- data/lib/splitclient-rb/engine/models/label.rb +1 -0
- data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
- data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
- data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
- data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +14 -0
- data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
- data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
- data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
- data/lib/splitclient-rb/engine/parser/condition.rb +9 -0
- data/lib/splitclient-rb/engine/parser/evaluator.rb +24 -30
- data/lib/splitclient-rb/engine/status_manager.rb +7 -1
- data/lib/splitclient-rb/engine/sync_manager.rb +6 -6
- data/lib/splitclient-rb/engine/synchronizer.rb +37 -17
- data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
- data/lib/splitclient-rb/helpers/repository_helper.rb +39 -1
- data/lib/splitclient-rb/helpers/util.rb +12 -3
- data/lib/splitclient-rb/managers/split_manager.rb +2 -1
- data/lib/splitclient-rb/spec.rb +1 -1
- data/lib/splitclient-rb/split_config.rb +45 -5
- data/lib/splitclient-rb/split_factory.rb +27 -21
- data/lib/splitclient-rb/sse/event_source/client.rb +86 -29
- data/lib/splitclient-rb/sse/event_source/event_parser.rb +2 -2
- data/lib/splitclient-rb/sse/event_source/event_types.rb +1 -0
- data/lib/splitclient-rb/sse/notification_manager_keeper.rb +3 -3
- data/lib/splitclient-rb/sse/notification_processor.rb +3 -1
- data/lib/splitclient-rb/sse/workers/segments_worker.rb +4 -4
- data/lib/splitclient-rb/sse/workers/splits_worker.rb +63 -24
- data/lib/splitclient-rb/utilitites.rb +6 -10
- data/lib/splitclient-rb/validators.rb +56 -25
- data/lib/splitclient-rb/version.rb +1 -1
- data/lib/splitclient-rb.rb +22 -0
- data/sonar-project.properties +1 -0
- data/splitclient-rb.gemspec +3 -4
- metadata +32 -38
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: false
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'socket'
|
|
4
|
+
require 'openssl'
|
|
4
5
|
require 'uri'
|
|
6
|
+
require 'timeout'
|
|
5
7
|
|
|
6
8
|
module SplitIoClient
|
|
7
9
|
module SSE
|
|
@@ -36,26 +38,28 @@ module SplitIoClient
|
|
|
36
38
|
|
|
37
39
|
def close(status = nil)
|
|
38
40
|
unless connected?
|
|
39
|
-
@config.logger.
|
|
41
|
+
@config.logger.debug('SSEClient already disconected.') if @config.debug_enabled
|
|
40
42
|
return
|
|
41
43
|
end
|
|
44
|
+
@config.logger.debug("Closing SSEClient socket") if @config.debug_enabled
|
|
42
45
|
|
|
43
|
-
@connected.make_false
|
|
44
|
-
@socket&.close
|
|
45
46
|
push_status(status)
|
|
47
|
+
@connected.make_false
|
|
48
|
+
@socket.sync_close = true if @socket.is_a? OpenSSL::SSL::SSLSocket
|
|
49
|
+
@socket.close
|
|
50
|
+
@config.logger.debug("SSEClient socket state #{@socket.state}") if @socket.is_a? OpenSSL::SSL::SSLSocket && @config.debug_enabled
|
|
46
51
|
rescue StandardError => e
|
|
47
52
|
@config.logger.error("SSEClient close Error: #{e.inspect}")
|
|
48
53
|
end
|
|
49
54
|
|
|
50
55
|
def start(url)
|
|
51
56
|
if connected?
|
|
52
|
-
@config.logger.debug('SSEClient already running.')
|
|
57
|
+
@config.logger.debug('SSEClient already running.') if @config.debug_enabled
|
|
53
58
|
return true
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
@uri = URI(url)
|
|
57
62
|
latch = Concurrent::CountDownLatch.new(1)
|
|
58
|
-
|
|
59
63
|
connect_thread(latch)
|
|
60
64
|
|
|
61
65
|
return false unless latch.wait(CONNECT_TIMEOUT)
|
|
@@ -74,42 +78,72 @@ module SplitIoClient
|
|
|
74
78
|
|
|
75
79
|
def connect_thread(latch)
|
|
76
80
|
@config.threads[:connect_stream] = Thread.new do
|
|
77
|
-
@config.logger.info('Starting connect_stream thread ...')
|
|
81
|
+
@config.logger.info('Starting connect_stream thread ...')
|
|
78
82
|
new_status = connect_stream(latch)
|
|
79
83
|
push_status(new_status)
|
|
80
|
-
@config.logger.info('connect_stream thread finished.')
|
|
84
|
+
@config.logger.info('connect_stream thread finished.')
|
|
81
85
|
end
|
|
82
86
|
end
|
|
83
87
|
|
|
84
88
|
def connect_stream(latch)
|
|
85
89
|
return Constants::PUSH_NONRETRYABLE_ERROR unless socket_write(latch)
|
|
86
|
-
|
|
87
90
|
while connected? || @first_event.value
|
|
88
|
-
begin
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
91
|
+
begin
|
|
92
|
+
if IO.select([@socket], nil, nil, @read_timeout)
|
|
93
|
+
begin
|
|
94
|
+
partial_data = @socket.readpartial(10_000)
|
|
95
|
+
read_first_event(partial_data, latch)
|
|
96
|
+
|
|
97
|
+
raise 'eof exception' if partial_data == :eof
|
|
98
|
+
rescue IO::WaitReadable => e
|
|
99
|
+
@config.logger.debug("SSE client IO::WaitReadable transient error: #{e.inspect}") if @config.debug_enabled
|
|
100
|
+
IO.select([@socket], nil, nil, @read_timeout)
|
|
101
|
+
retry
|
|
102
|
+
rescue Errno::EAGAIN => e
|
|
103
|
+
@config.logger.debug("SSE client transient error: #{e.inspect}") if @config.debug_enabled
|
|
104
|
+
IO.select([@socket], nil, nil, @read_timeout)
|
|
105
|
+
retry
|
|
106
|
+
rescue Errno::ETIMEDOUT => e
|
|
107
|
+
@config.logger.error("SSE read operation timed out!: #{e.inspect}")
|
|
108
|
+
return Constants::PUSH_RETRYABLE_ERROR
|
|
109
|
+
rescue EOFError => e
|
|
110
|
+
@config.logger.error("SSE read operation EOF Exception!: #{e.inspect}")
|
|
111
|
+
raise 'eof exception'
|
|
112
|
+
rescue Errno::EBADF, IOError => e
|
|
113
|
+
@config.logger.error("SSE read operation EBADF or IOError: #{e.inspect}")
|
|
114
|
+
return Constants::PUSH_RETRYABLE_ERROR
|
|
115
|
+
rescue StandardError => e
|
|
116
|
+
@config.logger.error("SSE read operation StandardError: #{e.inspect}")
|
|
117
|
+
return nil if ENV['SPLITCLIENT_ENV'] == 'test'
|
|
118
|
+
|
|
119
|
+
@config.logger.error("Error reading partial data: #{e.inspect}")
|
|
120
|
+
return Constants::PUSH_RETRYABLE_ERROR
|
|
121
|
+
end
|
|
122
|
+
else
|
|
123
|
+
@config.logger.error("SSE read operation timed out, no data available.")
|
|
124
|
+
return Constants::PUSH_RETRYABLE_ERROR
|
|
125
|
+
end
|
|
126
|
+
rescue Errno::EBADF
|
|
127
|
+
@config.logger.debug("SSE socket is not connected (Errno::EBADF)") if @config.debug_enabled
|
|
128
|
+
break
|
|
129
|
+
rescue RuntimeError
|
|
130
|
+
raise 'eof exception'
|
|
131
|
+
rescue Exception => e
|
|
132
|
+
@config.logger.debug("SSE socket is not connected: #{e.inspect}") if @config.debug_enabled
|
|
133
|
+
break
|
|
102
134
|
end
|
|
103
135
|
|
|
104
136
|
process_data(partial_data)
|
|
105
137
|
end
|
|
138
|
+
@config.logger.info("SSE read operation exited: #{connected?}")
|
|
139
|
+
|
|
106
140
|
nil
|
|
107
141
|
end
|
|
108
142
|
|
|
109
143
|
def socket_write(latch)
|
|
110
144
|
@first_event.make_true
|
|
111
145
|
@socket = socket_connect
|
|
112
|
-
@socket.
|
|
146
|
+
@socket.puts(build_request(@uri))
|
|
113
147
|
true
|
|
114
148
|
rescue StandardError => e
|
|
115
149
|
@config.logger.error("Error during connecting to #{@uri.host}. Error: #{e.inspect}")
|
|
@@ -121,7 +155,7 @@ module SplitIoClient
|
|
|
121
155
|
return unless @first_event.value
|
|
122
156
|
|
|
123
157
|
response_code = @event_parser.first_event(data)
|
|
124
|
-
@config.logger.debug("SSE client first event code: #{response_code}")
|
|
158
|
+
@config.logger.debug("SSE client first event code: #{response_code}") if @config.debug_enabled
|
|
125
159
|
|
|
126
160
|
error_event = false
|
|
127
161
|
events = @event_parser.parse(data)
|
|
@@ -130,6 +164,7 @@ module SplitIoClient
|
|
|
130
164
|
|
|
131
165
|
if response_code == OK_CODE && !error_event
|
|
132
166
|
@connected.make_true
|
|
167
|
+
@config.logger.debug("SSE client first event Connected is true") if @config.debug_enabled
|
|
133
168
|
@telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::SSE_CONNECTION_ESTABLISHED, nil)
|
|
134
169
|
push_status(Constants::PUSH_CONNECTED)
|
|
135
170
|
end
|
|
@@ -138,15 +173,37 @@ module SplitIoClient
|
|
|
138
173
|
end
|
|
139
174
|
|
|
140
175
|
def socket_connect
|
|
141
|
-
|
|
176
|
+
tcp_socket = TCPSocket.new(@uri.host, @uri.port)
|
|
177
|
+
if @uri.scheme.casecmp('https').zero?
|
|
178
|
+
begin
|
|
179
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
180
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
|
|
181
|
+
ssl_socket.hostname = @uri.host
|
|
182
|
+
|
|
183
|
+
begin
|
|
184
|
+
ssl_socket.connect_nonblock
|
|
185
|
+
rescue IO::WaitReadable
|
|
186
|
+
IO.select([ssl_socket])
|
|
187
|
+
retry
|
|
188
|
+
rescue IO::WaitWritable
|
|
189
|
+
IO.select(nil, [ssl_socket])
|
|
190
|
+
retry
|
|
191
|
+
end
|
|
192
|
+
return ssl_socket
|
|
193
|
+
|
|
194
|
+
rescue Exception => e
|
|
195
|
+
@config.logger.error("socket connect error: #{e.inspect}")
|
|
196
|
+
return nil
|
|
197
|
+
end
|
|
198
|
+
end
|
|
142
199
|
|
|
143
|
-
|
|
200
|
+
tcp_socket
|
|
144
201
|
end
|
|
145
202
|
|
|
146
203
|
def process_data(partial_data)
|
|
204
|
+
@config.logger.debug("Event partial data: #{partial_data}") if @config.debug_enabled
|
|
147
205
|
return if partial_data.nil? || partial_data == KEEP_ALIVE_RESPONSE
|
|
148
206
|
|
|
149
|
-
@config.logger.debug("Event partial data: #{partial_data}") if @config.debug_enabled
|
|
150
207
|
events = @event_parser.parse(partial_data)
|
|
151
208
|
events.each { |event| process_event(event) }
|
|
152
209
|
rescue StandardError => e
|
|
@@ -197,7 +254,7 @@ module SplitIoClient
|
|
|
197
254
|
def push_status(status)
|
|
198
255
|
return if status.nil?
|
|
199
256
|
|
|
200
|
-
@config.logger.debug("Pushing new sse status: #{status}")
|
|
257
|
+
@config.logger.debug("Pushing new sse status: #{status}") if @config.debug_enabled
|
|
201
258
|
@status_queue.push(status)
|
|
202
259
|
end
|
|
203
260
|
end
|
|
@@ -29,14 +29,14 @@ module SplitIoClient
|
|
|
29
29
|
|
|
30
30
|
events
|
|
31
31
|
rescue StandardError => e
|
|
32
|
-
@config.logger.debug("Error during parsing a event: #{e.inspect}")
|
|
32
|
+
@config.logger.debug("Error during parsing a event: #{e.inspect}") if @config.debug_enabled
|
|
33
33
|
[]
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def first_event(raw_data)
|
|
37
37
|
raw_data.split("\n")[0].split(' ')[1].to_i
|
|
38
38
|
rescue StandardError => e
|
|
39
|
-
@config.logger.
|
|
39
|
+
@config.logger.error("Error parsing first event: #{e.inspect}")
|
|
40
40
|
BAD_REQUEST_CODE
|
|
41
41
|
end
|
|
42
42
|
|
|
@@ -42,12 +42,12 @@ module SplitIoClient
|
|
|
42
42
|
@telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::STREAMING_STATUS, DISABLED)
|
|
43
43
|
push_status(Constants::PUSH_SUBSYSTEM_OFF)
|
|
44
44
|
else
|
|
45
|
-
@config.logger.error("Incorrect event type: #{incoming_notification}")
|
|
45
|
+
@config.logger.error("Incorrect event type: #{incoming_notification}") if @config.debug_enabled
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def process_event_occupancy(channel, publishers)
|
|
50
|
-
@config.logger.debug("Processed occupancy event with #{publishers} publishers. Channel: #{channel}")
|
|
50
|
+
@config.logger.debug("Processed occupancy event with #{publishers} publishers. Channel: #{channel}") if @config.debug_enabled
|
|
51
51
|
|
|
52
52
|
update_publishers(channel, publishers)
|
|
53
53
|
|
|
@@ -76,7 +76,7 @@ module SplitIoClient
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def push_status(status)
|
|
79
|
-
@config.logger.debug("Pushing occupancy status: #{status}")
|
|
79
|
+
@config.logger.debug("Pushing occupancy status: #{status}") if @config.debug_enabled
|
|
80
80
|
@status_queue.push(status)
|
|
81
81
|
end
|
|
82
82
|
end
|
|
@@ -13,6 +13,8 @@ module SplitIoClient
|
|
|
13
13
|
case incoming_notification.data['type']
|
|
14
14
|
when SSE::EventSource::EventTypes::SPLIT_UPDATE
|
|
15
15
|
process_split_update(incoming_notification)
|
|
16
|
+
when SSE::EventSource::EventTypes::RB_SEGMENT_UPDATE
|
|
17
|
+
process_split_update(incoming_notification)
|
|
16
18
|
when SSE::EventSource::EventTypes::SPLIT_KILL
|
|
17
19
|
process_split_kill(incoming_notification)
|
|
18
20
|
when SSE::EventSource::EventTypes::SEGMENT_UPDATE
|
|
@@ -25,7 +27,7 @@ module SplitIoClient
|
|
|
25
27
|
private
|
|
26
28
|
|
|
27
29
|
def process_split_update(notification)
|
|
28
|
-
@config.logger.debug("
|
|
30
|
+
@config.logger.debug("#{notification.event_type} notification received: #{notification}") if @config.debug_enabled
|
|
29
31
|
@splits_worker.add_to_queue(notification)
|
|
30
32
|
end
|
|
31
33
|
|
|
@@ -14,13 +14,13 @@ module SplitIoClient
|
|
|
14
14
|
|
|
15
15
|
def add_to_queue(change_number, segment_name)
|
|
16
16
|
item = { change_number: change_number, segment_name: segment_name }
|
|
17
|
-
@config.logger.debug("SegmentsWorker add to queue #{item}")
|
|
17
|
+
@config.logger.debug("SegmentsWorker add to queue #{item}") if @config.debug_enabled
|
|
18
18
|
@queue.push(item)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def start
|
|
22
22
|
if @running.value
|
|
23
|
-
@config.logger.debug('segments worker already running.')
|
|
23
|
+
@config.logger.debug('segments worker already running.') if @config.debug_enabled
|
|
24
24
|
return
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -30,7 +30,7 @@ module SplitIoClient
|
|
|
30
30
|
|
|
31
31
|
def stop
|
|
32
32
|
unless @running.value
|
|
33
|
-
@config.logger.debug('segments worker not running.')
|
|
33
|
+
@config.logger.debug('segments worker not running.') if @config.debug_enabled
|
|
34
34
|
return
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -44,7 +44,7 @@ module SplitIoClient
|
|
|
44
44
|
while (item = @queue.pop)
|
|
45
45
|
segment_name = item[:segment_name]
|
|
46
46
|
cn = item[:change_number]
|
|
47
|
-
@config.logger.debug("SegmentsWorker change_number dequeue #{segment_name}, #{cn}")
|
|
47
|
+
@config.logger.debug("SegmentsWorker change_number dequeue #{segment_name}, #{cn}") if @config.debug_enabled
|
|
48
48
|
|
|
49
49
|
@synchronizer.fetch_segment(segment_name, cn)
|
|
50
50
|
end
|
|
@@ -4,7 +4,8 @@ module SplitIoClient
|
|
|
4
4
|
module SSE
|
|
5
5
|
module Workers
|
|
6
6
|
class SplitsWorker
|
|
7
|
-
def initialize(synchronizer, config, feature_flags_repository, telemetry_runtime_producer,
|
|
7
|
+
def initialize(synchronizer, config, feature_flags_repository, telemetry_runtime_producer,
|
|
8
|
+
segment_fetcher, rule_based_segment_repository)
|
|
8
9
|
@synchronizer = synchronizer
|
|
9
10
|
@config = config
|
|
10
11
|
@feature_flags_repository = feature_flags_repository
|
|
@@ -12,11 +13,12 @@ module SplitIoClient
|
|
|
12
13
|
@running = Concurrent::AtomicBoolean.new(false)
|
|
13
14
|
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
14
15
|
@segment_fetcher = segment_fetcher
|
|
16
|
+
@rule_based_segment_repository = rule_based_segment_repository
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def start
|
|
18
20
|
if @running.value
|
|
19
|
-
@config.logger.debug('feature_flags_worker already running.')
|
|
21
|
+
@config.logger.debug('feature_flags_worker already running.') if @config.debug_enabled
|
|
20
22
|
return
|
|
21
23
|
end
|
|
22
24
|
|
|
@@ -26,7 +28,7 @@ module SplitIoClient
|
|
|
26
28
|
|
|
27
29
|
def stop
|
|
28
30
|
unless @running.value
|
|
29
|
-
@config.logger.debug('feature_flags_worker not running.')
|
|
31
|
+
@config.logger.debug('feature_flags_worker not running.') if @config.debug_enabled
|
|
30
32
|
return
|
|
31
33
|
end
|
|
32
34
|
|
|
@@ -35,7 +37,7 @@ module SplitIoClient
|
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
def add_to_queue(notification)
|
|
38
|
-
@config.logger.debug("feature_flags_worker add to queue #{notification.data['changeNumber']}")
|
|
40
|
+
@config.logger.debug("feature_flags_worker add to queue #{notification.data['changeNumber']}") if @config.debug_enabled
|
|
39
41
|
@queue.push(notification)
|
|
40
42
|
end
|
|
41
43
|
|
|
@@ -50,11 +52,14 @@ module SplitIoClient
|
|
|
50
52
|
|
|
51
53
|
def perform
|
|
52
54
|
while (notification = @queue.pop)
|
|
53
|
-
@config.logger.debug("feature_flags_worker change_number dequeue #{notification.data['changeNumber']}")
|
|
55
|
+
@config.logger.debug("feature_flags_worker change_number dequeue #{notification.data['changeNumber']}") if @config.debug_enabled
|
|
54
56
|
case notification.data['type']
|
|
55
57
|
when SSE::EventSource::EventTypes::SPLIT_UPDATE
|
|
56
58
|
success = update_feature_flag(notification)
|
|
57
|
-
@synchronizer.fetch_splits(notification.data['changeNumber']) unless success
|
|
59
|
+
@synchronizer.fetch_splits(notification.data['changeNumber'], 0) unless success
|
|
60
|
+
when SSE::EventSource::EventTypes::RB_SEGMENT_UPDATE
|
|
61
|
+
success = update_rule_based_segment(notification)
|
|
62
|
+
@synchronizer.fetch_splits(0, notification.data['changeNumber']) unless success
|
|
58
63
|
when SSE::EventSource::EventTypes::SPLIT_KILL
|
|
59
64
|
kill_feature_flag(notification)
|
|
60
65
|
end
|
|
@@ -65,11 +70,12 @@ module SplitIoClient
|
|
|
65
70
|
return true if @feature_flags_repository.get_change_number.to_i >= notification.data['changeNumber']
|
|
66
71
|
return false unless !notification.data['d'].nil? && @feature_flags_repository.get_change_number == notification.data['pcn']
|
|
67
72
|
|
|
68
|
-
new_split =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
new_split = update_feature_flag_repository(notification)
|
|
74
|
+
fetch_segments_if_not_exists(Helpers::Util.segment_names_by_object(new_split, 'IN_SEGMENT'), @feature_flags_repository)
|
|
75
|
+
if fetch_rule_based_segments_if_not_exists(Helpers::Util.segment_names_by_object(new_split, 'IN_RULE_BASED_SEGMENT'),
|
|
76
|
+
notification.data['changeNumber'])
|
|
77
|
+
return true
|
|
78
|
+
end
|
|
73
79
|
|
|
74
80
|
@telemetry_runtime_producer.record_updates_from_sse(Telemetry::Domain::Constants::SPLITS)
|
|
75
81
|
|
|
@@ -80,30 +86,63 @@ module SplitIoClient
|
|
|
80
86
|
false
|
|
81
87
|
end
|
|
82
88
|
|
|
89
|
+
def update_feature_flag_repository(notification)
|
|
90
|
+
new_split = return_object_from_json(notification)
|
|
91
|
+
SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@feature_flags_repository, [new_split],
|
|
92
|
+
notification.data['changeNumber'], @config, false)
|
|
93
|
+
new_split
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def update_rule_based_segment(notification)
|
|
97
|
+
return true if @rule_based_segment_repository.get_change_number.to_i >= notification.data['changeNumber']
|
|
98
|
+
return false unless !notification.data['d'].nil? &&
|
|
99
|
+
@rule_based_segment_repository.get_change_number == notification.data['pcn']
|
|
100
|
+
|
|
101
|
+
new_rb_segment = return_object_from_json(notification)
|
|
102
|
+
SplitIoClient::Helpers::RepositoryHelper.update_rule_based_segment_repository(@rule_based_segment_repository,
|
|
103
|
+
[new_rb_segment],
|
|
104
|
+
notification.data['changeNumber'], @config)
|
|
105
|
+
fetch_segments_if_not_exists(Helpers::Util.segment_names_in_rb_segment(new_rb_segment, 'IN_SEGMENT'),
|
|
106
|
+
@rule_based_segment_repository)
|
|
107
|
+
|
|
108
|
+
# @telemetry_runtime_producer.record_updates_from_sse(Telemetry::Domain::Constants::SPLITS)
|
|
109
|
+
|
|
110
|
+
true
|
|
111
|
+
rescue StandardError => e
|
|
112
|
+
@config.logger.debug("Failed to update Split: #{e.inspect}") if @config.debug_enabled
|
|
113
|
+
|
|
114
|
+
false
|
|
115
|
+
end
|
|
116
|
+
|
|
83
117
|
def kill_feature_flag(notification)
|
|
84
118
|
return if @feature_flags_repository.get_change_number.to_i > notification.data['changeNumber']
|
|
85
119
|
|
|
86
|
-
@config.logger.debug("feature_flags_worker kill #{notification.data['splitName']}, #{notification.data['changeNumber']}")
|
|
87
|
-
@feature_flags_repository.kill(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)
|
|
92
|
-
@synchronizer.fetch_splits(notification.data['changeNumber'])
|
|
120
|
+
@config.logger.debug("feature_flags_worker kill #{notification.data['splitName']}, #{notification.data['changeNumber']}") if @config.debug_enabled
|
|
121
|
+
@feature_flags_repository.kill(notification.data['changeNumber'],
|
|
122
|
+
notification.data['splitName'],
|
|
123
|
+
notification.data['defaultTreatment'])
|
|
124
|
+
@synchronizer.fetch_splits(notification.data['changeNumber'], 0)
|
|
93
125
|
end
|
|
94
126
|
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
JSON.parse(
|
|
127
|
+
def return_object_from_json(notification)
|
|
128
|
+
object_json = Helpers::DecryptionHelper.get_encoded_definition(notification.data['c'], notification.data['d'])
|
|
129
|
+
JSON.parse(object_json, symbolize_names: true)
|
|
98
130
|
end
|
|
99
131
|
|
|
100
|
-
def fetch_segments_if_not_exists(
|
|
101
|
-
segment_names = Helpers::Util.segment_names_by_feature_flag(feature_flag)
|
|
132
|
+
def fetch_segments_if_not_exists(segment_names, object_repository)
|
|
102
133
|
return if segment_names.nil?
|
|
103
134
|
|
|
104
|
-
|
|
135
|
+
object_repository.set_segment_names(segment_names)
|
|
105
136
|
@segment_fetcher.fetch_segments_if_not_exists(segment_names)
|
|
106
137
|
end
|
|
138
|
+
|
|
139
|
+
def fetch_rule_based_segments_if_not_exists(segment_names, change_number)
|
|
140
|
+
return false if segment_names.nil? || segment_names.empty? || @rule_based_segment_repository.contains?(segment_names.to_a)
|
|
141
|
+
|
|
142
|
+
@synchronizer.fetch_splits(0, change_number)
|
|
143
|
+
|
|
144
|
+
true
|
|
145
|
+
end
|
|
107
146
|
end
|
|
108
147
|
end
|
|
109
148
|
end
|
|
@@ -38,16 +38,12 @@ module SplitIoClient
|
|
|
38
38
|
interval * random_factor
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def split_bulk_to_send(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
count += 1
|
|
48
|
-
end
|
|
49
|
-
rescue StandardError
|
|
50
|
-
[]
|
|
41
|
+
def split_bulk_to_send(items, divisions)
|
|
42
|
+
to_return = []
|
|
43
|
+
items.to_a.each_slice(divisions) {|bulk|
|
|
44
|
+
to_return.push(bulk.to_set)
|
|
45
|
+
}
|
|
46
|
+
to_return
|
|
51
47
|
end
|
|
52
48
|
end
|
|
53
49
|
end
|
|
@@ -4,6 +4,8 @@ module SplitIoClient
|
|
|
4
4
|
class Validators
|
|
5
5
|
|
|
6
6
|
Flagset_regex = /^[a-z0-9][_a-z0-9]{0,49}$/
|
|
7
|
+
Fallback_treatment_regex = /^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$/
|
|
8
|
+
Fallback_treatment_size = 100
|
|
7
9
|
|
|
8
10
|
def initialize(config)
|
|
9
11
|
@config = config
|
|
@@ -55,8 +57,12 @@ module SplitIoClient
|
|
|
55
57
|
end
|
|
56
58
|
without_nil = Array.new
|
|
57
59
|
flag_sets.each { |flag_set|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
if !flag_set.nil?
|
|
61
|
+
without_nil.push(flag_set)
|
|
62
|
+
next
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
log_nil("flag set", method)
|
|
60
66
|
}
|
|
61
67
|
if without_nil.length() == 0
|
|
62
68
|
log_invalid_flag_set_type(method)
|
|
@@ -68,7 +74,7 @@ module SplitIoClient
|
|
|
68
74
|
log_invalid_flag_set_type(method)
|
|
69
75
|
elsif flag_set.is_a?(String) && flag_set.empty?
|
|
70
76
|
log_invalid_flag_set_type(method)
|
|
71
|
-
elsif !flag_set.empty? && string_match?(flag_set.strip.downcase, method)
|
|
77
|
+
elsif !flag_set.empty? && string_match?(flag_set.strip.downcase, method, Flagset_regex, :log_invalid_match)
|
|
72
78
|
valid_flag_sets.add(flag_set.strip.downcase)
|
|
73
79
|
else
|
|
74
80
|
log_invalid_flag_set_type(method)
|
|
@@ -77,6 +83,46 @@ module SplitIoClient
|
|
|
77
83
|
!valid_flag_sets.empty? ? valid_flag_sets.to_a.sort : []
|
|
78
84
|
end
|
|
79
85
|
|
|
86
|
+
def validate_fallback_treatment(method, fallback_treatment)
|
|
87
|
+
if !fallback_treatment.is_a? Engine::Models::FallbackTreatment
|
|
88
|
+
@config.logger.warn("#{method}: Fallback treatment instance should be FallbackTreatment, input is discarded")
|
|
89
|
+
return false
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if !fallback_treatment.treatment.is_a? String
|
|
93
|
+
@config.logger.warn("#{method}: Fallback treatment value should be str type, input is discarded")
|
|
94
|
+
return false
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
return false unless string_match?(fallback_treatment.treatment, method, Fallback_treatment_regex, :log_invalid_fallback_treatment)
|
|
98
|
+
|
|
99
|
+
if fallback_treatment.treatment.size > Fallback_treatment_size
|
|
100
|
+
@config.logger.warn("#{method}: Fallback treatment size should not exceed #{Fallback_treatment_size} characters")
|
|
101
|
+
return false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def valid_split_name?(method, split_name)
|
|
108
|
+
if split_name.nil?
|
|
109
|
+
log_nil(:split_name, method)
|
|
110
|
+
return false
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
unless string?(split_name)
|
|
114
|
+
log_invalid_type(:split_name, method)
|
|
115
|
+
return false
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if empty_string?(split_name)
|
|
119
|
+
log_empty_string(:split_name, method)
|
|
120
|
+
return false
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
true
|
|
124
|
+
end
|
|
125
|
+
|
|
80
126
|
private
|
|
81
127
|
|
|
82
128
|
def string?(value)
|
|
@@ -91,9 +137,9 @@ module SplitIoClient
|
|
|
91
137
|
(value.is_a?(Numeric) && !value.to_f.nan?) || string?(value)
|
|
92
138
|
end
|
|
93
139
|
|
|
94
|
-
def string_match?(value, method)
|
|
95
|
-
if
|
|
96
|
-
|
|
140
|
+
def string_match?(value, method, regex_exp, log_if_invalid)
|
|
141
|
+
if regex_exp.match(value) == nil
|
|
142
|
+
method(log_if_invalid).call(value, method)
|
|
97
143
|
false
|
|
98
144
|
else
|
|
99
145
|
true
|
|
@@ -132,25 +178,6 @@ module SplitIoClient
|
|
|
132
178
|
@config.logger.error("#{method}: #{key} is too long - must be #{@config.max_key_size} characters or less")
|
|
133
179
|
end
|
|
134
180
|
|
|
135
|
-
def valid_split_name?(method, split_name)
|
|
136
|
-
if split_name.nil?
|
|
137
|
-
log_nil(:split_name, method)
|
|
138
|
-
return false
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
unless string?(split_name)
|
|
142
|
-
log_invalid_type(:split_name, method)
|
|
143
|
-
return false
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
if empty_string?(split_name)
|
|
147
|
-
log_empty_string(:split_name, method)
|
|
148
|
-
return false
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
true
|
|
152
|
-
end
|
|
153
|
-
|
|
154
181
|
def valid_key?(method, key)
|
|
155
182
|
if key.nil?
|
|
156
183
|
log_nil(:key, method)
|
|
@@ -326,5 +353,9 @@ module SplitIoClient
|
|
|
326
353
|
|
|
327
354
|
true
|
|
328
355
|
end
|
|
356
|
+
|
|
357
|
+
def log_invalid_fallback_treatment(key, method)
|
|
358
|
+
@config.logger.warn("#{method}: Invalid treatment #{key}, Fallback treatment should match regex #{Fallback_treatment_regex}")
|
|
359
|
+
end
|
|
329
360
|
end
|
|
330
361
|
end
|