diff --git a/lib/feature.rb b/lib/feature.rb index b9307ce66dd1a77b33213a2f1e9bd7f9df609f6e..e2b2039a04f7e1de14a01ef14d3dc6454d498e5c 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -44,6 +44,16 @@ def flipper_id end end + # Generates the same flipper_id for a given kubernetes pod, + # or for the entire gitlab application if deployed on a single host. + class FlipperPod + attr_reader :flipper_id + + def initialize + @flipper_id = "FlipperPod:#{Socket.gethostname}".freeze + end + end + # Generates a unique flipper_id for the current GitLab instance. class FlipperGitlabInstance attr_reader :flipper_id @@ -253,6 +263,10 @@ def current_request end end + def current_pod + @flipper_pod ||= FlipperPod.new + end + def gitlab_instance @flipper_gitlab_instance ||= FlipperGitlabInstance.new end @@ -290,6 +304,8 @@ def sanitized_thing(thing) gitlab_instance when :request, :current_request current_request + when :pod, :current_pod + current_pod else thing end diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb index acf39fbbe5350413a2e3513ed5f8488a4dc71d6e..b2bc24970a1dfd4e4bee0bb17db235ea219904a9 100644 --- a/spec/lib/feature_spec.rb +++ b/spec/lib/feature_spec.rb @@ -55,6 +55,29 @@ def wrap_all_methods_with_flag_check(lb, flag) end end + describe '.current_pod' do + it 'returns a FlipperPod with a flipper_id' do + expect(described_class.current_pod).to respond_to(:flipper_id) + end + + it 'is the same flipper_id within a process' do + previous_id = described_class.current_pod.flipper_id + + expect(previous_id).to eq(described_class.current_pod.flipper_id) + end + + it 'is a different flipper_id in a new host' do + previous_id = described_class.current_pod.flipper_id + + # Simulate a new process by changing host, + previous_host = Socket.gethostname + allow(Socket).to receive(:gethostname).and_return("#{previous_host}-1") + + new_id = Feature::FlipperPod.new.flipper_id # Bypass caching + expect(previous_id).not_to eq(new_id) + end + end + describe '.gitlab_instance' do it 'returns a FlipperGitlabInstance with a flipper_id' do flipper_request = described_class.gitlab_instance @@ -407,6 +430,35 @@ def wrap_all_methods_with_flag_check(lb, flag) end end + context 'with :pod actor' do + before do + stub_feature_flag_definition(:enabled_feature_flag) + end + + it 'returns the same value in the same host' do + described_class.enable(:enabled_feature_flag, :current_pod) + + expect(described_class.enabled?(:enabled_feature_flag, :current_pod)).to be_truthy + end + + it 'returns different values in different hosts' do + number_of_times = 1_000 + percentage = 50 + described_class.enable_percentage_of_actors(:enabled_feature_flag, percentage) + results = { true => 0, false => 0 } + original_hostname = Socket.gethostname + number_of_times.times do |i| + allow(Socket).to receive(:gethostname).and_return("#{original_hostname}-#{i}") + flipper_thing = Feature::FlipperPod.new # Create a new one to bypass caching, we are simulating many different pods + result = described_class.enabled?(:enabled_feature_flag, flipper_thing) + results[result] += 1 + end + + percent_true = (results[true].to_f / (results[true] + results[false])) * 100 + expect(percent_true).to be_within(5).of(percentage) + end + end + context 'with a group member' do let(:key) { :awesome_feature } let(:guinea_pigs) { create_list(:user, 3) }