splitclient-rb 7.0.0 → 7.0.1.pre.rc1

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 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