splitclient-rb 7.0.0 → 7.0.1.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13c9d6e5441382c090f658577eec8434780f09194158cdd08d594dc5f2dc964a
4
- data.tar.gz: d90969f20c1ccc77cc764b157b11c989a1344905ebe5e38f8326fc94183fd1fc
3
+ metadata.gz: f134a9d9e88674dafbdcf9f57b77c73942664cbf14e9df1a7f55066f1fc62742
4
+ data.tar.gz: ee65206af2cae21099c0a1f3ab5facff024384c5698df3556ad40191d26600a7
5
5
  SHA512:
6
- metadata.gz: 4cd0e62cc7cf6ae15c0efd576af57836520f33c2ac7e3223c3670cf225f1d68d82470a3e995ab06c292382ad2c2eb9c1a0f3d7eb32f168bb02b8340d3e961940
7
- data.tar.gz: 53819ca8d4a0a62ad5a6b21fbe7c2eb5a6faed6aecad51b8536749ff1bd25fc5302964804988456600e16c797f7be43242c6d4cd7b255c804676017d42e7e18a
6
+ metadata.gz: 698f220a7d5402b844997fddbfd64275f9141bcd5a196b18477df5490d741d150cf17c2cca205626c213e68fcb57efcbea1f037c2e0f3ef39c8a476796062470
7
+ data.tar.gz: 8bbdf861ad7e4a58cc7a8d5a5942a1882a218f6f36d6ce49019c84f02cf5f97aa0daa64c5ddd65135aa1efe5b4c8c85c97a54b3d0821434dd0cca21581a1fd5a
data/CHANGES.txt CHANGED
@@ -1,3 +1,8 @@
1
+ 7.0.1 (Sep 13, 2019)
2
+ - Updated localhost mode so that parsing of test files results in JSON data for splits analogous to that returned by the Split backend.
3
+ - Resulting localhost data is now used indifferently from actual backend data by the SDK code.
4
+ - Removed specific spare code dealing with localhost operations.
5
+
1
6
  7.0.0 (Aug 23, 2019)
2
7
  - BREAKING CHANGE: block_until_ready is now a method in both split_client and split_manager that needs to be explicitly called. The block_until_ready parameter is now ignored if passed in the configuration, and defaults to 15s unless passed as a parameter of the block_until_ready method.
3
8
  - Added warning to track calls when traffic type does not belong to an existing split (only issued in the online client and when SDK is ready).
@@ -25,18 +25,18 @@ require 'splitclient-rb/cache/senders/impressions_formatter'
25
25
  require 'splitclient-rb/cache/senders/impressions_sender'
26
26
  require 'splitclient-rb/cache/senders/metrics_sender'
27
27
  require 'splitclient-rb/cache/senders/events_sender'
28
+ require 'splitclient-rb/cache/senders/localhost_repo_cleaner'
29
+ require 'splitclient-rb/cache/stores/store_utils'
30
+ require 'splitclient-rb/cache/stores/localhost_split_builder'
28
31
  require 'splitclient-rb/cache/stores/sdk_blocker'
29
32
  require 'splitclient-rb/cache/stores/segment_store'
30
33
  require 'splitclient-rb/cache/stores/split_store'
34
+ require 'splitclient-rb/cache/stores/localhost_split_store'
31
35
 
32
- require 'splitclient-rb/localhost_utils'
33
- require 'splitclient-rb/clients/localhost_split_client'
34
36
  require 'splitclient-rb/clients/split_client'
35
- require 'splitclient-rb/managers/localhost_split_manager'
36
37
  require 'splitclient-rb/managers/split_manager'
37
38
  require 'splitclient-rb/split_factory'
38
39
  require 'splitclient-rb/split_factory_builder'
39
- require 'splitclient-rb/localhost_split_factory'
40
40
  require 'splitclient-rb/split_config'
41
41
  require 'splitclient-rb/split_logger'
42
42
  require 'splitclient-rb/validators'
@@ -121,6 +121,10 @@ module SplitIoClient
121
121
  end
122
122
  end
123
123
 
124
+ def empty?
125
+ @map.empty?
126
+ end
127
+
124
128
  # This method is used in Redis adapter
125
129
  # "stubbing" it here to keep the interface
126
130
  def pipelined(&block)
@@ -42,6 +42,10 @@ module SplitIoClient
42
42
  def length
43
43
  @current_size.value
44
44
  end
45
+
46
+ def empty?
47
+ @queue.empty?
48
+ end
45
49
  end
46
50
  end
47
51
  end
@@ -46,6 +46,10 @@ module SplitIoClient
46
46
  @adapter.clear
47
47
  end
48
48
 
49
+ def empty?
50
+ @adapter.empty?
51
+ end
52
+
49
53
  private
50
54
 
51
55
  def random_sampler
@@ -6,11 +6,11 @@ module SplitIoClient
6
6
  # Repository which forwards impressions interface to the selected adapter
7
7
  class ImpressionsRepository < Repository
8
8
  extend Forwardable
9
- def_delegators :@adapter, :add, :add_bulk, :batch, :clear, :empty?
9
+ def_delegators :@repository, :add, :add_bulk, :batch, :clear, :empty?
10
10
 
11
11
  def initialize(config)
12
12
  super(config)
13
- @adapter = case @config.impressions_adapter.class.to_s
13
+ @repository = case @config.impressions_adapter.class.to_s
14
14
  when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
15
15
  Repositories::Impressions::MemoryRepository.new(@config)
16
16
  when 'SplitIoClient::Cache::Adapters::RedisAdapter'
@@ -4,12 +4,12 @@ module SplitIoClient
4
4
  # Repository which forwards impressions interface to the selected adapter
5
5
  class MetricsRepository < Repository
6
6
  extend Forwardable
7
- def_delegators :@adapter, :add_count, :add_latency, :add_gauge, :counts, :latencies, :gauges,
7
+ def_delegators :@repository, :add_count, :add_latency, :add_gauge, :counts, :latencies, :gauges,
8
8
  :clear_counts, :clear_latencies, :clear_gauges, :clear, :fix_latencies
9
9
 
10
10
  def initialize(config)
11
11
  super(config)
12
- @adapter = case @config.metrics_adapter.class.to_s
12
+ @repository = case @config.metrics_adapter.class.to_s
13
13
  when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
14
14
  Repositories::Metrics::MemoryRepository.new(@config)
15
15
  when 'SplitIoClient::Cache::Adapters::RedisAdapter'
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class LocalhostRepoCleaner
7
+ def initialize(impressions_repository, metrics_repository, events_repository, config)
8
+ @impressions_repository = impressions_repository
9
+ @metrics_repository = metrics_repository
10
+ @events_repository = events_repository
11
+ @config = config
12
+ end
13
+
14
+ def call
15
+ if ENV['SPLITCLIENT_ENV'] == 'test'
16
+ clear_repositories
17
+ else
18
+ cleaner_thread
19
+
20
+ if defined?(PhusionPassenger)
21
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
22
+ cleaner_thread if forked
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def cleaner_thread
31
+ @config.threads[:repo_cleaner] = Thread.new do
32
+ @config.logger.info('Starting repositories cleanup service')
33
+ loop do
34
+ clear_repositories
35
+
36
+ sleep(SplitIoClient::Utilities.randomize_interval(@config.features_refresh_rate))
37
+ end
38
+ end
39
+ end
40
+
41
+ def clear_repositories
42
+ @impressions_repository.clear
43
+ @metrics_repository.clear
44
+ @events_repository.clear
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Stores
6
+ class LocalhostSplitBuilder
7
+ class << self
8
+ def build_splits(splits)
9
+ splits.map do |feature, treatments|
10
+ build_split(feature, treatments)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def build_split(feature, treatments)
17
+ {
18
+ name: feature,
19
+ status: 'ACTIVE',
20
+ killed: false,
21
+ trafficAllocation: 100,
22
+ seed: 2_089_907_429,
23
+ defaultTreatment: 'control_treatment',
24
+ configurations: build_configurations(treatments),
25
+ conditions: build_conditions(treatments)
26
+ }
27
+ end
28
+
29
+ def build_configurations(treatments)
30
+ treatments.reduce({}) do |hash, treatment|
31
+ hash.merge(treatment[:treatment].to_sym => treatment[:config])
32
+ end
33
+ end
34
+
35
+ def build_conditions(treatments)
36
+ conditions = treatments.map do |treatment|
37
+ if treatment[:keys]
38
+ build_whitelist_treatment(treatment[:treatment], Array(treatment[:keys]))
39
+ else
40
+ build_rollout_treatment
41
+ end
42
+ .merge(partitions: build_partitions(treatment[:treatment], treatments))
43
+ end
44
+
45
+ conditions.sort_by { |condition| condition[:conditionType] }.reverse!
46
+ end
47
+
48
+ def build_whitelist_treatment(treatment_name, whitelist_keys)
49
+ {
50
+ conditionType: 'WHITELIST',
51
+ matcherGroup: {
52
+ combiner: 'AND',
53
+ matchers: [{
54
+ keySelector: nil,
55
+ matcherType: 'WHITELIST',
56
+ negate: false,
57
+ whitelistMatcherData: {
58
+ whitelist: whitelist_keys
59
+ }
60
+ }]
61
+ },
62
+ label: "whitelisted #{treatment_name}"
63
+ }
64
+ end
65
+
66
+ def build_rollout_treatment
67
+ {
68
+ conditionType: 'ROLLOUT',
69
+ matcherGroup: {
70
+ combiner: 'AND',
71
+ matchers: [
72
+ {
73
+ matcherType: 'ALL_KEYS',
74
+ negate: false
75
+ }
76
+ ]
77
+ },
78
+ label: 'default rule'
79
+ }
80
+ end
81
+
82
+ def build_partitions(current_treatment_name, treatments)
83
+ treatments.map do |treatment|
84
+ {
85
+ treatment: treatment[:treatment],
86
+ size: treatment[:treatment] == current_treatment_name ? 100 : 0
87
+ }
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Stores
6
+ class LocalhostSplitStore
7
+ attr_reader :splits_repository
8
+
9
+ def initialize(splits_repository, config, sdk_blocker = nil)
10
+ @splits_repository = splits_repository
11
+ @config = config
12
+ @sdk_blocker = sdk_blocker
13
+ end
14
+
15
+ def call
16
+ if ENV['SPLITCLIENT_ENV'] == 'test'
17
+ store_splits
18
+ else
19
+ splits_thread
20
+
21
+ if defined?(PhusionPassenger)
22
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
23
+ splits_thread if forked
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def splits_thread
32
+ @config.threads[:split_store] = Thread.new do
33
+ @config.logger.info('Starting splits fetcher service')
34
+ loop do
35
+ store_splits
36
+
37
+ sleep(StoreUtils.random_interval(@config.features_refresh_rate))
38
+ end
39
+ end
40
+ end
41
+
42
+ def store_splits
43
+ load_features.each do |split|
44
+ store_split(split)
45
+ end
46
+
47
+ if @sdk_blocker
48
+ @sdk_blocker.splits_ready!
49
+ @sdk_blocker.segments_ready!
50
+ end
51
+ rescue StandardError => error
52
+ @config.logger.error('Error while parsing the split file. ' \
53
+ 'Check that the input file matches the expected format')
54
+ @config.log_found_exception(__method__.to_s, error)
55
+ end
56
+
57
+ def store_split(split)
58
+ @config.logger.debug("storing split (#{split[:name]})") if @config.debug_enabled
59
+
60
+ @splits_repository.add_split(split)
61
+ end
62
+
63
+ def load_features
64
+ yaml_extensions = ['.yml', '.yaml']
65
+ if yaml_extensions.include? File.extname(@config.split_file)
66
+ parse_yaml_features
67
+ else
68
+ @config.logger.warn('Localhost mode: .split mocks ' \
69
+ 'will be deprecated soon in favor of YAML files, which provide more ' \
70
+ 'targeting power. Take a look in our documentation.')
71
+
72
+ parse_plain_text_features
73
+ end
74
+ end
75
+
76
+ def parse_plain_text_features
77
+ splits = File.open(@config.split_file).each_with_object({}) do |line, memo|
78
+ feature, treatment = line.strip.split(' ')
79
+
80
+ next if line.start_with?('#') || line.strip.empty?
81
+
82
+ memo[feature] = [{ treatment: treatment }]
83
+ end
84
+
85
+ LocalhostSplitBuilder.build_splits(splits)
86
+ end
87
+
88
+ def parse_yaml_features
89
+ splits = YAML.safe_load(File.read(@config.split_file)).each_with_object({}) do |feature, memo|
90
+ feat_symbolized_keys = symbolize_feat_keys(feature)
91
+
92
+ feat_name = feature.keys.first
93
+
94
+ if memo[feat_name]
95
+ memo[feat_name] << feat_symbolized_keys
96
+ else
97
+ memo[feat_name] = [feat_symbolized_keys]
98
+ end
99
+ end
100
+
101
+ LocalhostSplitBuilder.build_splits(splits)
102
+ end
103
+
104
+ def symbolize_feat_keys(yaml_feature)
105
+ yaml_feature.values.first.each_with_object({}) do |(k, v), memo|
106
+ memo[k.to_sym] = k == 'config' ? v.to_json : v
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -38,7 +38,7 @@ module SplitIoClient
38
38
  store_segments
39
39
  @config.logger.debug("Segment names: #{@segments_repository.used_segment_names.to_a}") if @config.debug_enabled
40
40
 
41
- sleep_for = random_interval(@config.segments_refresh_rate)
41
+ sleep_for = StoreUtils.random_interval(@config.segments_refresh_rate)
42
42
  @config.logger.debug("Segments store is sleeping for: #{sleep_for} seconds") if @config.debug_enabled
43
43
  sleep(sleep_for)
44
44
  end
@@ -53,12 +53,6 @@ module SplitIoClient
53
53
  @config.log_found_exception(__method__.to_s, error)
54
54
  end
55
55
 
56
- def random_interval(interval)
57
- random_factor = Random.new.rand(50..100) / 100.0
58
-
59
- interval * random_factor
60
- end
61
-
62
56
  def segments_api
63
57
  @segments_api ||= SplitIoClient::Api::Segments.new(@api_key, @metrics, @segments_repository, @config)
64
58
  end
@@ -34,7 +34,7 @@ module SplitIoClient
34
34
  loop do
35
35
  store_splits
36
36
 
37
- sleep(random_interval(@config.features_refresh_rate))
37
+ sleep(StoreUtils.random_interval(@config.features_refresh_rate))
38
38
  end
39
39
  end
40
40
  end
@@ -57,12 +57,6 @@ module SplitIoClient
57
57
  @config.log_found_exception(__method__.to_s, error)
58
58
  end
59
59
 
60
- def random_interval(interval)
61
- random_factor = Random.new.rand(50..100) / 100.0
62
-
63
- interval * random_factor
64
- end
65
-
66
60
  def splits_since(since)
67
61
  splits_api.since(since)
68
62
  end
@@ -0,0 +1,13 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Stores
4
+ class StoreUtils
5
+ def self.random_interval(interval)
6
+ random_factor = Random.new.rand(50..100) / 100.0
7
+
8
+ interval * random_factor
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -133,7 +133,7 @@ module SplitIoClient
133
133
  end
134
134
  end
135
135
 
136
- if ready? && !@splits_repository.traffic_type_exists(traffic_type_name)
136
+ if ready? && !@config.localhost_mode && !@splits_repository.traffic_type_exists(traffic_type_name)
137
137
  @config.logger.warn("track: Traffic Type #{traffic_type_name} " \
138
138
  "does not have any corresponding Splits in this environment, make sure you're tracking " \
139
139
  'your events to a valid traffic type defined in the Split console')
@@ -44,7 +44,9 @@ module SplitIoClient
44
44
  @sdk_blocker = sdk_blocker
45
45
  @config = config
46
46
 
47
- start_standalone_components if @config.standalone?
47
+ start_localhost_components if @config.localhost_mode
48
+
49
+ start_standalone_components if @config.standalone? && !@config.localhost_mode
48
50
  end
49
51
 
50
52
  def start_standalone_components
@@ -55,6 +57,21 @@ module SplitIoClient
55
57
  events_sender
56
58
  end
57
59
 
60
+ def start_localhost_components
61
+ localhost_split_store
62
+ localhost_repo_cleaner
63
+ end
64
+
65
+ # Starts thread which loops constantly and retrieves splits from a file source
66
+ def localhost_split_store
67
+ LocalhostSplitStore.new(@splits_repository, @config, @sdk_blocker).call
68
+ end
69
+
70
+ # Starts thread which loops constantly and cleans up repositories to avoid memory issues in localhost mode
71
+ def localhost_repo_cleaner
72
+ LocalhostRepoCleaner.new(@impressions_repository, @metrics_repository, @events_repository, @config).call
73
+ end
74
+
58
75
  # Starts thread which loops constantly and stores splits in the splits_repository of choice
59
76
  def split_store
60
77
  SplitStore.new(@splits_repository, @api_key, @metrics, @config, @sdk_blocker).call
@@ -3,13 +3,9 @@ module SplitIoClient
3
3
  #
4
4
  # Creates a new split manager instance that connects to split.io API.
5
5
  #
6
- # @param api_key [String] the API key for your split account
7
- #
8
6
  # @return [SplitIoManager] split.io client instance
9
- def initialize(api_key, adapter = nil, splits_repository = nil, sdk_blocker, config)
10
- @localhost_mode_features = []
7
+ def initialize(splits_repository = nil, sdk_blocker, config)
11
8
  @splits_repository = splits_repository
12
- @adapter = adapter
13
9
  @sdk_blocker = sdk_blocker
14
10
  @config = config
15
11
  end
@@ -91,13 +87,18 @@ module SplitIoClient
91
87
  return {} unless split
92
88
 
93
89
  begin
94
- treatments = split[:conditions]
95
- .detect { |c| c[:conditionType] == 'ROLLOUT' }[:partitions]
96
- .map { |partition| partition[:treatment] }
90
+ if @config.localhost_mode
91
+ treatments = split[:conditions]
92
+ .first[:partitions]
93
+ .map { |partition| partition[:treatment] }
94
+ else
95
+ treatments = split[:conditions]
96
+ .detect { |c| c[:conditionType] == 'ROLLOUT' }[:partitions]
97
+ .map { |partition| partition[:treatment] }
98
+ end
97
99
  rescue StandardError
98
100
  treatments = []
99
101
  end
100
-
101
102
  {
102
103
  name: name,
103
104
  traffic_type_name: split[:trafficTypeName],
@@ -38,7 +38,18 @@ module SplitIoClient
38
38
  )
39
39
  @connection_timeout = opts[:connection_timeout] || SplitConfig.default_connection_timeout
40
40
  @read_timeout = opts[:read_timeout] || SplitConfig.default_read_timeout
41
- @features_refresh_rate = opts[:features_refresh_rate] || SplitConfig.default_features_refresh_rate
41
+
42
+ @logger = opts[:logger] || SplitConfig.default_logger
43
+
44
+ if(opts[:reload_rate])
45
+ @features_refresh_rate = opts[:reload_rate]
46
+ @logger.warn('Localhost mode: reload_rate will be deprecated soon in favor of ' \
47
+ 'features_refresh_rate. Take a look in our documentation.'
48
+ )
49
+ else
50
+ @features_refresh_rate = opts[:features_refresh_rate] || SplitConfig.default_features_refresh_rate
51
+ end
52
+
42
53
  @segments_refresh_rate = opts[:segments_refresh_rate] || SplitConfig.default_segments_refresh_rate
43
54
  @metrics_refresh_rate = opts[:metrics_refresh_rate] || SplitConfig.default_metrics_refresh_rate
44
55
 
@@ -56,7 +67,6 @@ module SplitIoClient
56
67
  opts[:cache_adapter] || SplitConfig.default_cache_adapter, :map_adapter, nil, @redis_url
57
68
  )
58
69
 
59
- @logger = opts[:logger] || SplitConfig.default_logger
60
70
  @debug_enabled = opts[:debug_enabled] || SplitConfig.default_debug
61
71
  @transport_debug_enabled = opts[:transport_debug_enabled] || SplitConfig.default_debug
62
72
  @block_until_ready = SplitConfig.default_block_until_ready
@@ -84,9 +94,14 @@ module SplitIoClient
84
94
  @events_adapter = SplitConfig.init_cache_adapter(
85
95
  opts[:cache_adapter] || SplitConfig.default_cache_adapter, :queue_adapter, @events_queue_size, @redis_url
86
96
  )
97
+
98
+ @split_file = opts[:split_file] || SplitConfig.default_split_file
99
+
87
100
  @valid_mode = true
88
101
  @split_logger = SplitIoClient::SplitLogger.new(self)
89
102
  @split_validator = SplitIoClient::Validators.new(self)
103
+ @localhost_mode = opts[:localhost_mode]
104
+
90
105
  startup_log
91
106
  end
92
107
 
@@ -235,6 +250,10 @@ module SplitIoClient
235
250
  # @return [Integer]
236
251
  attr_accessor :events_queue_size
237
252
 
253
+ attr_accessor :split_file
254
+
255
+ attr_accessor :localhost_mode
256
+
238
257
  #
239
258
  # The default split client configuration
240
259
  #
@@ -340,6 +359,14 @@ module SplitIoClient
340
359
  500
341
360
  end
342
361
 
362
+ def self.default_split_file
363
+ File.join(Dir.home, '.split')
364
+ end
365
+
366
+ def self.default_offline_refresh_rate
367
+ 5
368
+ end
369
+
343
370
  #
344
371
  # The default logger object
345
372
  #
@@ -2,6 +2,8 @@ module SplitIoClient
2
2
  class SplitFactory
3
3
  ROOT_PROCESS_ID = Process.pid
4
4
  SINGLETON_WARN = 'We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'
5
+ LOCALHOST_API_KEY = 'localhost'
6
+
5
7
  include SplitIoClient::Cache::Repositories
6
8
  include SplitIoClient::Cache::Stores
7
9
 
@@ -20,10 +22,10 @@ module SplitIoClient
20
22
  end
21
23
 
22
24
  @api_key = api_key
23
- @config = SplitConfig.new (config_hash)
25
+ @config = SplitConfig.new(config_hash.merge(localhost_mode: @api_key == LOCALHOST_API_KEY ))
24
26
 
25
27
  raise 'Invalid SDK mode' unless valid_mode
26
-
28
+
27
29
  @splits_repository = SplitsRepository.new(@config)
28
30
  @segments_repository = SegmentsRepository.new(@config)
29
31
  @impressions_repository = ImpressionsRepository.new(@config)
@@ -35,7 +37,7 @@ module SplitIoClient
35
37
  @adapter = start!
36
38
 
37
39
  @client = SplitClient.new(@api_key, @adapter, @splits_repository, @segments_repository, @impressions_repository, @metrics_repository, @events_repository, @sdk_blocker, @config)
38
- @manager = SplitManager.new(@api_key, @adapter, @splits_repository, @sdk_blocker, @config)
40
+ @manager = SplitManager.new(@splits_repository, @sdk_blocker, @config)
39
41
 
40
42
  validate_api_key
41
43
 
@@ -72,7 +74,12 @@ module SplitIoClient
72
74
  case @config.mode
73
75
  when :consumer
74
76
  if @config.cache_adapter.is_a? SplitIoClient::Cache::Adapters::RedisAdapter
75
- valid_startup_mode = true
77
+ if !@config.localhost_mode
78
+ valid_startup_mode = true
79
+ else
80
+ @config.logger.error('Localhost mode cannot be used with Redis. ' \
81
+ 'Use standalone mode and Memory adapter instead.')
82
+ end
76
83
  else
77
84
  @config.logger.error('Consumer mode cannot be used with Memory adapter. ' \
78
85
  'Use Redis adapter instead.')
@@ -1,27 +1,7 @@
1
- require 'logger'
2
-
3
1
  module SplitIoClient
4
2
  class SplitFactoryBuilder
5
3
  def self.build(api_key, config = {})
6
- case api_key
7
- when 'localhost'
8
- configuration = SplitConfig.new(config)
9
- LocalhostSplitFactory.new(split_file(config[:split_file], configuration.logger), configuration, config[:reload_rate])
10
- else
11
- SplitFactory.new(api_key, config)
12
- end
13
- end
14
-
15
- private
16
-
17
- def self.split_file(split_file_path, logger)
18
- return split_file_path unless split_file_path.nil?
19
-
20
- logger.warn('Localhost mode: .split mocks ' \
21
- 'will be deprecated soon in favor of YAML files, which provide more ' \
22
- 'targeting power. Take a look in our documentation.')
23
-
24
- File.join(Dir.home, '.split')
4
+ SplitFactory.new(api_key, config)
25
5
  end
26
6
  end
27
7
  end
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '7.0.0'
2
+ VERSION = '7.0.1.pre.rc1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: splitclient-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0
4
+ version: 7.0.1.pre.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Split Software
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-23 00:00:00.000000000 Z
11
+ date: 2019-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: allocation_stats
@@ -327,11 +327,14 @@ files:
327
327
  - lib/splitclient-rb/cache/senders/events_sender.rb
328
328
  - lib/splitclient-rb/cache/senders/impressions_formatter.rb
329
329
  - lib/splitclient-rb/cache/senders/impressions_sender.rb
330
+ - lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb
330
331
  - lib/splitclient-rb/cache/senders/metrics_sender.rb
332
+ - lib/splitclient-rb/cache/stores/localhost_split_builder.rb
333
+ - lib/splitclient-rb/cache/stores/localhost_split_store.rb
331
334
  - lib/splitclient-rb/cache/stores/sdk_blocker.rb
332
335
  - lib/splitclient-rb/cache/stores/segment_store.rb
333
336
  - lib/splitclient-rb/cache/stores/split_store.rb
334
- - lib/splitclient-rb/clients/localhost_split_client.rb
337
+ - lib/splitclient-rb/cache/stores/store_utils.rb
335
338
  - lib/splitclient-rb/clients/split_client.rb
336
339
  - lib/splitclient-rb/engine/api/client.rb
337
340
  - lib/splitclient-rb/engine/api/events.rb
@@ -374,9 +377,6 @@ files:
374
377
  - lib/splitclient-rb/engine/parser/partition.rb
375
378
  - lib/splitclient-rb/engine/parser/split_adapter.rb
376
379
  - lib/splitclient-rb/exceptions.rb
377
- - lib/splitclient-rb/localhost_split_factory.rb
378
- - lib/splitclient-rb/localhost_utils.rb
379
- - lib/splitclient-rb/managers/localhost_split_manager.rb
380
380
  - lib/splitclient-rb/managers/split_manager.rb
381
381
  - lib/splitclient-rb/redis_metrics_fixer.rb
382
382
  - lib/splitclient-rb/split_config.rb
@@ -405,9 +405,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
405
405
  version: '0'
406
406
  required_rubygems_version: !ruby/object:Gem::Requirement
407
407
  requirements:
408
- - - ">="
408
+ - - ">"
409
409
  - !ruby/object:Gem::Version
410
- version: '0'
410
+ version: 1.3.1
411
411
  requirements: []
412
412
  rubygems_version: 3.0.3
413
413
  signing_key:
@@ -1,184 +0,0 @@
1
- module SplitIoClient
2
- class LocalhostSplitClient
3
- include SplitIoClient::LocalhostUtils
4
-
5
- #
6
- # variables to if the sdk is being used in localhost mode and store the list of features
7
- attr_reader :localhost_mode
8
- attr_reader :localhost_mode_features
9
-
10
- #
11
- # Creates a new split client instance that reads from the given splits file
12
- #
13
- # @param splits_file [File] file that contains some splits
14
- #
15
- # @return [LocalhostSplitIoClient] split.io localhost client instance
16
- def initialize(splits_file, config, reload_rate = nil)
17
- @localhost_mode = true
18
- @localhost_mode_features = []
19
- load_localhost_mode_features(splits_file, reload_rate)
20
- @config = config
21
- end
22
-
23
- #
24
- # method that returns the sdk gem version
25
- #
26
- # @return [string] version value for this sdk
27
- def self.sdk_version
28
- 'ruby-'+SplitIoClient::VERSION
29
- end
30
-
31
- #
32
- # obtains the treatments and configs for a given set of features
33
- #
34
- # @param key [string] evaluation key, only used with yaml split files
35
- # @param split_names [array] name of the features being validated
36
- # @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls
37
- #
38
- # @return [hash] map of treatments (split_name, treatment)
39
- def get_treatments_with_config(key, split_names, attributes = nil)
40
- get_localhost_treatments(key, split_names, attributes, 'get_treatments_with_config')
41
- end
42
-
43
- #
44
- # obtains the treatments for a given set of features
45
- #
46
- # @param key [string] evaluation key, only used with yaml split files
47
- # @param split_names [array] name of the features being validated
48
- # @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls
49
- #
50
- # @return [hash] map of treatments (split_name, treatment_name)
51
- def get_treatments(key, split_names, attributes = nil)
52
- treatments = get_localhost_treatments(key, split_names, attributes)
53
- return treatments if treatments.nil?
54
- keys = treatments.keys
55
- treats = treatments.map { |_,t| t[:treatment] }
56
- Hash[keys.zip(treats)]
57
- end
58
-
59
- #
60
- # obtains the treatment for a given feature
61
- #
62
- # @param key [string] evaluation key, only used with yaml split files
63
- # @param split_name [string] name of the feature that is being validated
64
- # @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls
65
- #
66
- # @return [string] corresponding treatment
67
- def get_treatment(key, split_name, attributes = nil)
68
- get_localhost_treatment(key, split_name, attributes)[:treatment]
69
- end
70
-
71
- #
72
- # obtains the treatment and config for a given feature
73
- #
74
- # @param key [string] evaluation key, only used with yaml split files
75
- # @param split_name [string] name of the feature that is being validated
76
- # @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls
77
- #
78
- # @return [hash] corresponding treatment and config
79
- def get_treatment_with_config(key, split_name, attributes = nil)
80
- get_localhost_treatment(key, split_name, attributes, 'get_treatment_with_config')
81
- end
82
-
83
- def track
84
- end
85
-
86
- private
87
-
88
- #
89
- # method to check if the sdk is running in localhost mode based on api key
90
- #
91
- # @return [boolean] True if is in localhost mode, false otherwise
92
- def is_localhost_mode?
93
- true
94
- end
95
-
96
- # @param key [string] evaluation key, only used with yaml split files
97
- # @param split_name [string] name of the feature that is being validated
98
- #
99
- # @return [Hash] corresponding treatment and config, control otherwise
100
- def get_localhost_treatment(key, split_name, attributes, calling_method = 'get_treatment')
101
- control_treatment = { label: Engine::Models::Label::EXCEPTION, treatment: SplitIoClient::Engine::Models::Treatment::CONTROL, config: nil }
102
- parsed_control_treatment = parsed_treatment(control_treatment)
103
-
104
- bucketing_key, matching_key = keys_from_key(key)
105
- return parsed_control_treatment unless @config.split_validator.valid_get_treatment_parameters(calling_method, key, split_name, matching_key, bucketing_key, attributes)
106
-
107
- sanitized_split_name = split_name.to_s.strip
108
-
109
- if split_name.to_s != sanitized_split_name
110
- @config.logger.warn("get_treatment: split_name #{split_name} has extra whitespace, trimming")
111
- split_name = sanitized_split_name
112
- end
113
-
114
- treatment = @localhost_mode_features.select { |h| h[:feature] == split_name && has_key(h[:keys], key) }.last
115
-
116
- if treatment.nil?
117
- treatment = @localhost_mode_features.select { |h| h[:feature] == split_name && h[:keys] == nil }.last
118
- end
119
-
120
- if treatment && treatment[:treatment]
121
- {
122
- treatment: treatment[:treatment],
123
- config: treatment[:config]
124
- }
125
- else
126
- parsed_control_treatment
127
- end
128
- end
129
-
130
- def get_localhost_treatments(key, split_names, attributes = nil, calling_method = 'get_treatments')
131
- return nil unless @config.split_validator.valid_get_treatments_parameters(calling_method, split_names)
132
-
133
- sanitized_split_names = sanitize_split_names(calling_method, split_names)
134
-
135
- if sanitized_split_names.empty?
136
- @config.logger.error("#{calling_method}: split_names must be a non-empty Array")
137
- return {}
138
- end
139
-
140
- split_names.each_with_object({}) do |split_name, memo|
141
- memo.merge!(split_name => get_treatment_with_config(key, split_name, attributes))
142
- end
143
- end
144
-
145
- def sanitize_split_names(calling_method, split_names)
146
- split_names.compact.uniq.select do |split_name|
147
- if (split_name.is_a?(String) || split_name.is_a?(Symbol)) && !split_name.empty?
148
- true
149
- elsif split_name.is_a?(String) && split_name.empty?
150
- @config.logger.warn("#{calling_method}: you passed an empty split_name, split_name must be a non-empty String or a Symbol")
151
- false
152
- else
153
- @config.logger.warn("#{calling_method}: you passed an invalid split_name, split_name must be a non-empty String or a Symbol")
154
- false
155
- end
156
- end
157
- end
158
-
159
- def parsed_treatment(treatment_data)
160
- {
161
- treatment: treatment_data[:treatment],
162
- config: treatment_data[:config]
163
- }
164
- end
165
-
166
- def has_key(keys, key)
167
- case keys
168
- when Array then keys.include? key
169
- when String then keys == key
170
- else
171
- false
172
- end
173
- end
174
-
175
- def keys_from_key(key)
176
- case key
177
- when Hash
178
- key.values_at(:bucketing_key, :matching_key).map { |k| k.nil? ? nil : k }
179
- else
180
- [nil, key].map { |k| k.nil? ? nil : k }
181
- end
182
- end
183
- end
184
- end
@@ -1,14 +0,0 @@
1
- module SplitIoClient
2
- class LocalhostSplitFactory
3
- attr_reader :client, :manager
4
-
5
- def initialize(splits_file, config, reload_rate = nil, logger = nil)
6
- @splits_file = splits_file
7
- @reload_rate = reload_rate
8
- @config = config
9
-
10
- @client = LocalhostSplitClient.new(@splits_file, @config, @reload_rate)
11
- @manager = LocalhostSplitManager.new(@splits_file, @reload_rate)
12
- end
13
- end
14
- end
@@ -1,59 +0,0 @@
1
- module SplitIoClient
2
- module LocalhostUtils
3
-
4
- require 'yaml'
5
- #
6
- # method to set localhost mode features by reading the given .splits
7
- #
8
- # @param splits_file [File] the .split file that contains the splits
9
- # @param reload_rate [Integer] the number of seconds to reload splits_file
10
- # @return nil
11
- def load_localhost_mode_features(splits_file, reload_rate = nil)
12
- return @localhost_mode_features unless File.exists?(splits_file)
13
-
14
- store_features(splits_file)
15
-
16
- return unless reload_rate
17
-
18
- Thread.new do
19
- loop do
20
- @localhost_mode_features = []
21
- store_features(splits_file)
22
-
23
- sleep(SplitIoClient::Utilities.randomize_interval(reload_rate))
24
- end
25
- end
26
- end
27
-
28
- def store_features(splits_file)
29
- yaml_extensions = [".yml", ".yaml"]
30
- if yaml_extensions.include? File.extname(splits_file)
31
- store_yaml_features(splits_file)
32
- else
33
- store_plain_text_features(splits_file)
34
- end
35
- end
36
-
37
- private
38
-
39
- def store_plain_text_features(splits_file)
40
- File.open(splits_file).each do |line|
41
- feature, treatment = line.strip.split(' ')
42
-
43
- next if line.start_with?('#') || line.strip.empty?
44
-
45
- @localhost_mode_features << { feature: feature, treatment: treatment, key: nil, config: nil }
46
- end
47
- end
48
-
49
- def store_yaml_features(splits_file)
50
- YAML.load(File.read(splits_file)).each do |feature|
51
- feat_symbolized_keys = feature[feature.keys.first].inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
52
-
53
- feat_symbolized_keys[:config] = feat_symbolized_keys[:config].to_json
54
-
55
- @localhost_mode_features << { feature: feature.keys.first }.merge(feat_symbolized_keys)
56
- end
57
- end
58
- end
59
- end
@@ -1,60 +0,0 @@
1
- module SplitIoClient
2
- class LocalhostSplitManager
3
- include SplitIoClient::LocalhostUtils
4
-
5
- #
6
- # Creates a new split manager instance that holds the splits from a given file
7
- #
8
- # @param splits_file [File] the .split file that contains the splits
9
- # @param reload_rate [Integer] the number of seconds to reload splits_file
10
- #
11
- # @return [LocalhostSplitIoManager] split.io localhost manager instance
12
- def initialize(splits_file, reload_rate = nil)
13
- @localhost_mode = true
14
- @localhost_mode_features = []
15
-
16
- load_localhost_mode_features(splits_file, reload_rate)
17
- end
18
-
19
- #
20
- # method to get a split view
21
- #
22
- # @returns a split view
23
- def split(split_name)
24
- features = @localhost_mode_features.find_all { |feat| feat[:feature] == split_name }
25
-
26
- return nil if features.nil?
27
-
28
- treatments = features.map { |feat| feat[:treatment] }
29
-
30
- configs = Hash[ features.map { |feat| [ feat[:treatment].to_sym, feat[:config] ] } ]
31
-
32
- {
33
- change_number: nil,
34
- killed: false,
35
- name: split_name,
36
- traffic_type: nil,
37
- treatments: treatments,
38
- configs: configs
39
- }
40
- end
41
-
42
- #
43
- # method to get the split list from the client
44
- #
45
- # @returns Array of split view
46
- def splits
47
- split_names.map do |split_name|
48
- split(split_name)
49
- end
50
- end
51
-
52
- #
53
- # method to get the list of just split names. Ideal for ietrating and calling client.get_treatment
54
- #
55
- # @returns [object] array of split names (String)
56
- def split_names
57
- @localhost_mode_features.map{ |feat| feat[:feature]}.uniq
58
- end
59
- end
60
- end