PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/spec/lib/gitlab/gitaly_client_spec.rb

https://gitlab.com/wolfgang42/gitlab-ce
Ruby | 419 lines | 334 code | 81 blank | 4 comment | 0 complexity | 56ad458240bc7755bbd7edcc781b944a MD5 | raw file
  1. require 'spec_helper'
  2. # We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want
  3. # those stubs while testing the GitalyClient itself.
  4. describe Gitlab::GitalyClient do
  5. let(:sample_cert) { Rails.root.join('spec/fixtures/clusters/sample_cert.pem').to_s }
  6. before do
  7. allow(described_class)
  8. .to receive(:stub_cert_paths)
  9. .and_return([sample_cert])
  10. end
  11. def stub_repos_storages(address)
  12. allow(Gitlab.config.repositories).to receive(:storages).and_return({
  13. 'default' => { 'gitaly_address' => address }
  14. })
  15. end
  16. describe '.long_timeout' do
  17. context 'default case' do
  18. it { expect(subject.long_timeout).to eq(6.hours) }
  19. end
  20. context 'running in Unicorn' do
  21. before do
  22. stub_const('Unicorn', 1)
  23. end
  24. it { expect(subject.long_timeout).to eq(55) }
  25. end
  26. context 'running in Puma' do
  27. before do
  28. stub_const('Puma', 1)
  29. end
  30. it { expect(subject.long_timeout).to eq(55) }
  31. end
  32. end
  33. describe '.filesystem_id_from_disk' do
  34. it 'catches errors' do
  35. [Errno::ENOENT, Errno::EACCES, JSON::ParserError].each do |error|
  36. allow(File).to receive(:read).with(described_class.storage_metadata_file_path('default')).and_raise(error)
  37. expect(described_class.filesystem_id_from_disk('default')).to be_nil
  38. end
  39. end
  40. end
  41. describe '.filesystem_id' do
  42. it 'returns an empty string when the storage is not found in the response' do
  43. response = double("response")
  44. allow(response).to receive(:storage_statuses).and_return([])
  45. allow_any_instance_of(Gitlab::GitalyClient::ServerService).to receive(:info).and_return(response)
  46. expect(described_class.filesystem_id('default')).to eq(nil)
  47. end
  48. end
  49. describe '.stub_class' do
  50. it 'returns the gRPC health check stub' do
  51. expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub)
  52. end
  53. it 'returns a Gitaly stub' do
  54. expect(described_class.stub_class(:ref_service)).to eq(::Gitaly::RefService::Stub)
  55. end
  56. end
  57. describe '.stub_address' do
  58. it 'returns the same result after being called multiple times' do
  59. address = 'tcp://localhost:9876'
  60. stub_repos_storages address
  61. 2.times do
  62. expect(described_class.stub_address('default')).to eq('localhost:9876')
  63. end
  64. end
  65. end
  66. describe '.stub_certs' do
  67. it 'skips certificates if OpenSSLError is raised and report it' do
  68. expect(Rails.logger).to receive(:error).at_least(:once)
  69. expect(Gitlab::Sentry)
  70. .to receive(:track_exception)
  71. .with(
  72. a_kind_of(OpenSSL::X509::CertificateError),
  73. extra: { cert_file: a_kind_of(String) }).at_least(:once)
  74. expect(OpenSSL::X509::Certificate)
  75. .to receive(:new)
  76. .and_raise(OpenSSL::X509::CertificateError).at_least(:once)
  77. expect(described_class.stub_certs).to be_a(String)
  78. end
  79. end
  80. describe '.stub_creds' do
  81. it 'returns :this_channel_is_insecure if unix' do
  82. address = 'unix:/tmp/gitaly.sock'
  83. stub_repos_storages address
  84. expect(described_class.stub_creds('default')).to eq(:this_channel_is_insecure)
  85. end
  86. it 'returns :this_channel_is_insecure if tcp' do
  87. address = 'tcp://localhost:9876'
  88. stub_repos_storages address
  89. expect(described_class.stub_creds('default')).to eq(:this_channel_is_insecure)
  90. end
  91. it 'returns Credentials object if tls' do
  92. address = 'tls://localhost:9876'
  93. stub_repos_storages address
  94. expect(described_class.stub_creds('default')).to be_a(GRPC::Core::ChannelCredentials)
  95. end
  96. end
  97. describe '.stub' do
  98. # Notice that this is referring to gRPC "stubs", not rspec stubs
  99. before do
  100. described_class.clear_stubs!
  101. end
  102. context 'when passed a UNIX socket address' do
  103. it 'passes the address as-is to GRPC' do
  104. address = 'unix:/tmp/gitaly.sock'
  105. stub_repos_storages address
  106. expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
  107. described_class.stub(:commit_service, 'default')
  108. end
  109. end
  110. context 'when passed a TLS address' do
  111. it 'strips tls:// prefix before passing it to GRPC::Core::Channel initializer' do
  112. address = 'localhost:9876'
  113. prefixed_address = "tls://#{address}"
  114. stub_repos_storages prefixed_address
  115. expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
  116. described_class.stub(:commit_service, 'default')
  117. end
  118. end
  119. context 'when passed a TCP address' do
  120. it 'strips tcp:// prefix before passing it to GRPC::Core::Channel initializer' do
  121. address = 'localhost:9876'
  122. prefixed_address = "tcp://#{address}"
  123. stub_repos_storages prefixed_address
  124. expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
  125. described_class.stub(:commit_service, 'default')
  126. end
  127. end
  128. end
  129. describe '.can_use_disk?' do
  130. it 'properly caches a false result' do
  131. # spec_helper stubs this globally
  132. allow(described_class).to receive(:can_use_disk?).and_call_original
  133. expect(described_class).to receive(:filesystem_id).once
  134. expect(described_class).to receive(:filesystem_id_from_disk).once
  135. 2.times do
  136. described_class.can_use_disk?('unknown')
  137. end
  138. end
  139. end
  140. describe '.connection_data' do
  141. it 'returns connection data' do
  142. address = 'tcp://localhost:9876'
  143. stub_repos_storages address
  144. expect(described_class.connection_data('default')).to eq({ 'address' => address, 'token' => 'secret' })
  145. end
  146. end
  147. describe 'allow_n_plus_1_calls' do
  148. context 'when RequestStore is enabled', :request_store do
  149. it 'returns the result of the allow_n_plus_1_calls block' do
  150. expect(described_class.allow_n_plus_1_calls { "result" }).to eq("result")
  151. end
  152. end
  153. context 'when RequestStore is not active' do
  154. it 'returns the result of the allow_n_plus_1_calls block' do
  155. expect(described_class.allow_n_plus_1_calls { "something" }).to eq("something")
  156. end
  157. end
  158. end
  159. describe '.request_kwargs' do
  160. context 'when catfile-cache feature is enabled' do
  161. before do
  162. stub_feature_flags('gitaly_catfile-cache': true)
  163. end
  164. it 'sets the gitaly-session-id in the metadata' do
  165. results = described_class.request_kwargs('default', timeout: 1)
  166. expect(results[:metadata]).to include('gitaly-session-id')
  167. end
  168. context 'when RequestStore is not enabled' do
  169. it 'sets a different gitaly-session-id per request' do
  170. gitaly_session_id = described_class.request_kwargs('default', timeout: 1)[:metadata]['gitaly-session-id']
  171. expect(described_class.request_kwargs('default', timeout: 1)[:metadata]['gitaly-session-id']).not_to eq(gitaly_session_id)
  172. end
  173. end
  174. context 'when RequestStore is enabled', :request_store do
  175. it 'sets the same gitaly-session-id on every outgoing request metadata' do
  176. gitaly_session_id = described_class.request_kwargs('default', timeout: 1)[:metadata]['gitaly-session-id']
  177. 3.times do
  178. expect(described_class.request_kwargs('default', timeout: 1)[:metadata]['gitaly-session-id']).to eq(gitaly_session_id)
  179. end
  180. end
  181. end
  182. end
  183. end
  184. describe 'enforce_gitaly_request_limits?' do
  185. def call_gitaly(count = 1)
  186. (1..count).each do
  187. described_class.enforce_gitaly_request_limits(:test)
  188. end
  189. end
  190. context 'when RequestStore is enabled and the maximum number of calls is not enforced by a feature flag', :request_store do
  191. before do
  192. stub_feature_flags(gitaly_enforce_requests_limits: false)
  193. end
  194. it 'allows up the maximum number of allowed calls' do
  195. expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS) }.not_to raise_error
  196. end
  197. it 'allows the maximum number of calls to be exceeded if GITALY_DISABLE_REQUEST_LIMITS is set' do
  198. stub_env('GITALY_DISABLE_REQUEST_LIMITS', 'true')
  199. expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1) }.not_to raise_error
  200. end
  201. context 'when the maximum number of calls has been reached' do
  202. before do
  203. call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS)
  204. end
  205. it 'fails on the next call' do
  206. expect { call_gitaly(1) }.to raise_error(Gitlab::GitalyClient::TooManyInvocationsError)
  207. end
  208. end
  209. it 'allows the maximum number of calls to be exceeded within an allow_n_plus_1_calls block' do
  210. expect do
  211. described_class.allow_n_plus_1_calls do
  212. call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1)
  213. end
  214. end.not_to raise_error
  215. end
  216. context 'when the maximum number of calls has been reached within an allow_n_plus_1_calls block' do
  217. before do
  218. described_class.allow_n_plus_1_calls do
  219. call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS)
  220. end
  221. end
  222. it 'allows up to the maximum number of calls outside of an allow_n_plus_1_calls block' do
  223. expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS) }.not_to raise_error
  224. end
  225. it 'does not allow the maximum number of calls to be exceeded outside of an allow_n_plus_1_calls block' do
  226. expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1) }.to raise_error(Gitlab::GitalyClient::TooManyInvocationsError)
  227. end
  228. end
  229. end
  230. context 'in production and when RequestStore is enabled', :request_store do
  231. before do
  232. stub_rails_env('production')
  233. end
  234. context 'when the maximum number of calls is enforced by a feature flag' do
  235. before do
  236. stub_feature_flags(gitaly_enforce_requests_limits: true)
  237. end
  238. it 'does not allow the maximum number of calls to be exceeded' do
  239. expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1) }.to raise_error(Gitlab::GitalyClient::TooManyInvocationsError)
  240. end
  241. end
  242. context 'when the maximum number of calls is not enforced by a feature flag' do
  243. before do
  244. stub_feature_flags(gitaly_enforce_requests_limits: false)
  245. end
  246. it 'allows the maximum number of calls to be exceeded' do
  247. expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1) }.not_to raise_error
  248. end
  249. end
  250. end
  251. context 'when RequestStore is not active' do
  252. it 'does not raise errors when the maximum number of allowed calls is exceeded' do
  253. expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 2) }.not_to raise_error
  254. end
  255. it 'does not fail when the maximum number of calls is exceeded within an allow_n_plus_1_calls block' do
  256. expect do
  257. described_class.allow_n_plus_1_calls do
  258. call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1)
  259. end
  260. end.not_to raise_error
  261. end
  262. end
  263. end
  264. describe 'get_request_count' do
  265. context 'when RequestStore is enabled', :request_store do
  266. context 'when enforce_gitaly_request_limits is called outside of allow_n_plus_1_calls blocks' do
  267. before do
  268. described_class.enforce_gitaly_request_limits(:call)
  269. end
  270. it 'counts gitaly calls' do
  271. expect(described_class.get_request_count).to eq(1)
  272. end
  273. end
  274. context 'when enforce_gitaly_request_limits is called inside and outside of allow_n_plus_1_calls blocks' do
  275. before do
  276. described_class.enforce_gitaly_request_limits(:call)
  277. described_class.allow_n_plus_1_calls do
  278. described_class.enforce_gitaly_request_limits(:call)
  279. end
  280. end
  281. it 'counts gitaly calls' do
  282. expect(described_class.get_request_count).to eq(2)
  283. end
  284. end
  285. context 'when reset_counts is called' do
  286. before do
  287. described_class.enforce_gitaly_request_limits(:call)
  288. described_class.reset_counts
  289. end
  290. it 'resets counts' do
  291. expect(described_class.get_request_count).to eq(0)
  292. end
  293. end
  294. end
  295. context 'when RequestStore is not active' do
  296. before do
  297. described_class.enforce_gitaly_request_limits(:call)
  298. end
  299. it 'returns zero' do
  300. expect(described_class.get_request_count).to eq(0)
  301. end
  302. end
  303. end
  304. describe 'timeouts' do
  305. context 'with default values' do
  306. before do
  307. stub_application_setting(gitaly_timeout_default: 55)
  308. stub_application_setting(gitaly_timeout_medium: 30)
  309. stub_application_setting(gitaly_timeout_fast: 10)
  310. end
  311. it 'returns expected values' do
  312. expect(described_class.default_timeout).to be(55)
  313. expect(described_class.medium_timeout).to be(30)
  314. expect(described_class.fast_timeout).to be(10)
  315. end
  316. end
  317. end
  318. describe 'Peek Performance bar details' do
  319. let(:gitaly_server) { Gitaly::Server.all.first }
  320. before do
  321. Gitlab::SafeRequestStore[:peek_enabled] = true
  322. end
  323. context 'when the request store is active', :request_store do
  324. it 'records call details if a RPC is called' do
  325. expect(described_class).to receive(:measure_timings).and_call_original
  326. gitaly_server.server_version
  327. expect(described_class.list_call_details).not_to be_empty
  328. expect(described_class.list_call_details.size).to be(1)
  329. end
  330. end
  331. context 'when no request store is active' do
  332. it 'records nothing' do
  333. gitaly_server.server_version
  334. expect(described_class.list_call_details).to be_empty
  335. end
  336. end
  337. end
  338. end