2017-04-03 13:27:14 +00:00
|
|
|
require 'spec_helper'
|
|
|
|
|
2017-05-31 21:06:01 +00:00
|
|
|
# We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want
|
|
|
|
# those stubs while testing the GitalyClient itself.
|
2017-07-10 14:24:02 +00:00
|
|
|
describe Gitlab::GitalyClient, skip_gitaly_mock: true do
|
2017-05-10 12:18:59 +00:00
|
|
|
describe '.stub' do
|
2017-05-31 21:06:01 +00:00
|
|
|
# Notice that this is referring to gRPC "stubs", not rspec stubs
|
2017-06-14 18:18:56 +00:00
|
|
|
before do
|
|
|
|
described_class.clear_stubs!
|
|
|
|
end
|
2017-05-10 12:18:59 +00:00
|
|
|
|
2017-04-03 13:27:14 +00:00
|
|
|
context 'when passed a UNIX socket address' do
|
2017-05-10 12:18:59 +00:00
|
|
|
it 'passes the address as-is to GRPC' do
|
2017-04-03 13:27:14 +00:00
|
|
|
address = 'unix:/tmp/gitaly.sock'
|
2017-05-10 12:18:59 +00:00
|
|
|
allow(Gitlab.config.repositories).to receive(:storages).and_return({
|
|
|
|
'default' => { 'gitaly_address' => address }
|
|
|
|
})
|
2017-04-03 13:27:14 +00:00
|
|
|
|
2017-07-18 07:59:36 +00:00
|
|
|
expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
|
2017-04-03 13:27:14 +00:00
|
|
|
|
2017-07-18 07:59:36 +00:00
|
|
|
described_class.stub(:commit_service, 'default')
|
2017-04-03 13:27:14 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when passed a TCP address' do
|
|
|
|
it 'strips tcp:// prefix before passing it to GRPC::Core::Channel initializer' do
|
|
|
|
address = 'localhost:9876'
|
|
|
|
prefixed_address = "tcp://#{address}"
|
|
|
|
|
2017-05-10 12:18:59 +00:00
|
|
|
allow(Gitlab.config.repositories).to receive(:storages).and_return({
|
|
|
|
'default' => { 'gitaly_address' => prefixed_address }
|
|
|
|
})
|
|
|
|
|
2017-07-18 07:59:36 +00:00
|
|
|
expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
|
2017-04-03 13:27:14 +00:00
|
|
|
|
2017-07-18 07:59:36 +00:00
|
|
|
described_class.stub(:commit_service, 'default')
|
2017-04-03 13:27:14 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-05-31 21:06:01 +00:00
|
|
|
|
2017-09-19 10:55:37 +00:00
|
|
|
describe 'allow_n_plus_1_calls' do
|
|
|
|
context 'when RequestStore is enabled', :request_store do
|
|
|
|
it 'returns the result of the allow_n_plus_1_calls block' do
|
|
|
|
expect(described_class.allow_n_plus_1_calls { "result" }).to eq("result")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when RequestStore is not active' do
|
|
|
|
it 'returns the result of the allow_n_plus_1_calls block' do
|
|
|
|
expect(described_class.allow_n_plus_1_calls { "something" }).to eq("something")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'enforce_gitaly_request_limits?' do
|
|
|
|
def call_gitaly(count = 1)
|
|
|
|
(1..count).each do
|
|
|
|
described_class.enforce_gitaly_request_limits(:test)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when RequestStore is enabled', :request_store do
|
|
|
|
it 'allows up the maximum number of allowed calls' do
|
|
|
|
expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS) }.not_to raise_error
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the maximum number of calls has been reached' do
|
|
|
|
before do
|
|
|
|
call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'fails on the next call' do
|
|
|
|
expect { call_gitaly(1) }.to raise_error(Gitlab::GitalyClient::TooManyInvocationsError)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'allows the maximum number of calls to be exceeded within an allow_n_plus_1_calls block' do
|
|
|
|
expect do
|
|
|
|
described_class.allow_n_plus_1_calls do
|
|
|
|
call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1)
|
|
|
|
end
|
|
|
|
end.not_to raise_error
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the maximum number of calls has been reached within an allow_n_plus_1_calls block' do
|
|
|
|
before do
|
|
|
|
described_class.allow_n_plus_1_calls do
|
|
|
|
call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'allows up to the maximum number of calls outside of an allow_n_plus_1_calls block' do
|
|
|
|
expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS) }.not_to raise_error
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not allow the maximum number of calls to be exceeded outside of an allow_n_plus_1_calls block' do
|
|
|
|
expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1) }.to raise_error(Gitlab::GitalyClient::TooManyInvocationsError)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when RequestStore is not active' do
|
|
|
|
it 'does not raise errors when the maximum number of allowed calls is exceeded' do
|
|
|
|
expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 2) }.not_to raise_error
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not fail when the maximum number of calls is exceeded within an allow_n_plus_1_calls block' do
|
|
|
|
expect do
|
|
|
|
described_class.allow_n_plus_1_calls do
|
|
|
|
call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1)
|
|
|
|
end
|
|
|
|
end.not_to raise_error
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'get_request_count' do
|
|
|
|
context 'when RequestStore is enabled', :request_store do
|
|
|
|
context 'when enforce_gitaly_request_limits is called outside of allow_n_plus_1_calls blocks' do
|
|
|
|
before do
|
|
|
|
described_class.enforce_gitaly_request_limits(:call)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'counts gitaly calls' do
|
|
|
|
expect(described_class.get_request_count).to eq(1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when enforce_gitaly_request_limits is called inside and outside of allow_n_plus_1_calls blocks' do
|
|
|
|
before do
|
|
|
|
described_class.enforce_gitaly_request_limits(:call)
|
|
|
|
described_class.allow_n_plus_1_calls do
|
|
|
|
described_class.enforce_gitaly_request_limits(:call)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'counts gitaly calls' do
|
|
|
|
expect(described_class.get_request_count).to eq(2)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when reset_counts is called' do
|
|
|
|
before do
|
|
|
|
described_class.enforce_gitaly_request_limits(:call)
|
|
|
|
described_class.reset_counts
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'resets counts' do
|
|
|
|
expect(described_class.get_request_count).to eq(0)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when RequestStore is not active' do
|
|
|
|
before do
|
|
|
|
described_class.enforce_gitaly_request_limits(:call)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns zero' do
|
|
|
|
expect(described_class.get_request_count).to eq(0)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-31 21:06:01 +00:00
|
|
|
describe 'feature_enabled?' do
|
|
|
|
let(:feature_name) { 'my_feature' }
|
|
|
|
let(:real_feature_name) { "gitaly_#{feature_name}" }
|
|
|
|
|
|
|
|
context 'when Gitaly is disabled' do
|
2017-06-14 18:18:56 +00:00
|
|
|
before do
|
|
|
|
allow(described_class).to receive(:enabled?).and_return(false)
|
|
|
|
end
|
2017-05-31 21:06:01 +00:00
|
|
|
|
|
|
|
it 'returns false' do
|
|
|
|
expect(described_class.feature_enabled?(feature_name)).to be(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the feature status is DISABLED' do
|
|
|
|
let(:feature_status) { Gitlab::GitalyClient::MigrationStatus::DISABLED }
|
|
|
|
|
|
|
|
it 'returns false' do
|
|
|
|
expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the feature_status is OPT_IN' do
|
|
|
|
let(:feature_status) { Gitlab::GitalyClient::MigrationStatus::OPT_IN }
|
|
|
|
|
|
|
|
context "when the feature flag hasn't been set" do
|
|
|
|
it 'returns false' do
|
|
|
|
expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when the feature flag is set to disable" do
|
2017-06-14 18:18:56 +00:00
|
|
|
before do
|
|
|
|
Feature.get(real_feature_name).disable
|
|
|
|
end
|
2017-05-31 21:06:01 +00:00
|
|
|
|
|
|
|
it 'returns false' do
|
|
|
|
expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when the feature flag is set to enable" do
|
2017-06-14 18:18:56 +00:00
|
|
|
before do
|
|
|
|
Feature.get(real_feature_name).enable
|
|
|
|
end
|
2017-05-31 21:06:01 +00:00
|
|
|
|
|
|
|
it 'returns true' do
|
|
|
|
expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when the feature flag is set to a percentage of time" do
|
2017-06-14 18:18:56 +00:00
|
|
|
before do
|
|
|
|
Feature.get(real_feature_name).enable_percentage_of_time(70)
|
|
|
|
end
|
2017-05-31 21:06:01 +00:00
|
|
|
|
|
|
|
it 'bases the result on pseudo-random numbers' do
|
|
|
|
expect(Random).to receive(:rand).and_return(0.3)
|
|
|
|
expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
|
|
|
|
|
|
|
|
expect(Random).to receive(:rand).and_return(0.8)
|
|
|
|
expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
|
|
|
|
end
|
|
|
|
end
|
2017-09-07 17:39:00 +00:00
|
|
|
|
|
|
|
context "when a feature is not persisted" do
|
|
|
|
it 'returns false when opt_into_all_features is off' do
|
|
|
|
allow(Feature).to receive(:persisted?).and_return(false)
|
|
|
|
allow(described_class).to receive(:opt_into_all_features?).and_return(false)
|
|
|
|
|
|
|
|
expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns true when the override is on' do
|
|
|
|
allow(Feature).to receive(:persisted?).and_return(false)
|
|
|
|
allow(described_class).to receive(:opt_into_all_features?).and_return(true)
|
|
|
|
|
|
|
|
expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
|
|
|
|
end
|
|
|
|
end
|
2017-05-31 21:06:01 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the feature_status is OPT_OUT' do
|
|
|
|
let(:feature_status) { Gitlab::GitalyClient::MigrationStatus::OPT_OUT }
|
|
|
|
|
|
|
|
context "when the feature flag hasn't been set" do
|
|
|
|
it 'returns true' do
|
|
|
|
expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when the feature flag is set to disable" do
|
2017-06-14 18:18:56 +00:00
|
|
|
before do
|
|
|
|
Feature.get(real_feature_name).disable
|
|
|
|
end
|
2017-05-31 21:06:01 +00:00
|
|
|
|
|
|
|
it 'returns false' do
|
|
|
|
expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-11-29 09:12:12 +00:00
|
|
|
|
|
|
|
describe 'timeouts' do
|
|
|
|
context 'with default values' do
|
|
|
|
before do
|
|
|
|
stub_application_setting(gitaly_timeout_default: 55)
|
|
|
|
stub_application_setting(gitaly_timeout_medium: 30)
|
|
|
|
stub_application_setting(gitaly_timeout_fast: 10)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns expected values' do
|
|
|
|
expect(described_class.default_timeout).to be(55)
|
|
|
|
expect(described_class.medium_timeout).to be(30)
|
|
|
|
expect(described_class.fast_timeout).to be(10)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-04-03 13:27:14 +00:00
|
|
|
end
|