PageRenderTime 56ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/ee/spec/models/ee/clusters/platforms/kubernetes_spec.rb

https://gitlab.com/markglenfletcher/gitlab-ee
Ruby | 392 lines | 307 code | 84 blank | 1 comment | 0 complexity | 7941de476bc88610673a554a87675032 MD5 | raw file
  1. # frozen_string_literal: true
  2. require 'spec_helper'
  3. describe Clusters::Platforms::Kubernetes do
  4. include KubernetesHelpers
  5. include ReactiveCachingHelpers
  6. shared_examples 'resource not found error' do |message|
  7. it 'raises error' do
  8. result = subject
  9. expect(result[:error]).to eq(message)
  10. expect(result[:status]).to eq(:error)
  11. end
  12. end
  13. shared_examples 'kubernetes API error' do |error_code|
  14. it 'raises error' do
  15. result = subject
  16. expect(result[:error]).to eq("Kubernetes API returned status code: #{error_code}")
  17. expect(result[:status]).to eq(:error)
  18. end
  19. end
  20. describe '#rollout_status' do
  21. let(:deployments) { [] }
  22. let(:pods) { [] }
  23. let(:service) { create(:cluster_platform_kubernetes, :configured) }
  24. let!(:cluster) { create(:cluster, :project, enabled: true, platform_kubernetes: service) }
  25. let(:project) { cluster.project }
  26. let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") }
  27. let(:cache_data) { Hash(deployments: deployments, pods: pods) }
  28. subject(:rollout_status) { service.rollout_status(environment, cache_data) }
  29. context 'legacy deployments based on app label' do
  30. let(:legacy_deployment) do
  31. kube_deployment(name: 'legacy-deployment').tap do |deployment|
  32. deployment['metadata']['annotations'].delete('app.gitlab.com/env')
  33. deployment['metadata']['annotations'].delete('app.gitlab.com/app')
  34. deployment['metadata']['labels']['app'] = environment.slug
  35. end
  36. end
  37. let(:legacy_pod) do
  38. kube_pod(name: 'legacy-pod').tap do |pod|
  39. pod['metadata']['annotations'].delete('app.gitlab.com/env')
  40. pod['metadata']['annotations'].delete('app.gitlab.com/app')
  41. pod['metadata']['labels']['app'] = environment.slug
  42. end
  43. end
  44. context 'only legacy deployments' do
  45. let(:deployments) { [legacy_deployment] }
  46. let(:pods) { [legacy_pod] }
  47. it 'contains nothing' do
  48. expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus)
  49. expect(rollout_status.deployments).to eq([])
  50. end
  51. it 'has the has_legacy_app_label flag' do
  52. expect(rollout_status).to be_has_legacy_app_label
  53. end
  54. end
  55. context 'new deployment based on annotations' do
  56. let(:matched_deployment) { kube_deployment(name: 'matched-deployment', environment_slug: environment.slug, project_slug: project.full_path_slug) }
  57. let(:matched_pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) }
  58. let(:deployments) { [matched_deployment, legacy_deployment] }
  59. let(:pods) { [matched_pod, legacy_pod] }
  60. it 'contains only matching deployments' do
  61. expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus)
  62. expect(rollout_status.deployments.map(&:name)).to contain_exactly('matched-deployment')
  63. end
  64. it 'does have the has_legacy_app_label flag' do
  65. expect(rollout_status).to be_has_legacy_app_label
  66. end
  67. end
  68. context 'deployment with app label not matching the environment' do
  69. let(:other_deployment) do
  70. kube_deployment(name: 'other-deployment').tap do |deployment|
  71. deployment['metadata']['annotations'].delete('app.gitlab.com/env')
  72. deployment['metadata']['annotations'].delete('app.gitlab.com/app')
  73. deployment['metadata']['labels']['app'] = 'helm-app-label'
  74. end
  75. end
  76. let(:other_pod) do
  77. kube_pod(name: 'other-pod').tap do |pod|
  78. pod['metadata']['annotations'].delete('app.gitlab.com/env')
  79. pod['metadata']['annotations'].delete('app.gitlab.com/app')
  80. pod['metadata']['labels']['app'] = environment.slug
  81. end
  82. end
  83. let(:deployments) { [other_deployment] }
  84. let(:pods) { [other_pod] }
  85. it 'does not have the has_legacy_app_label flag' do
  86. expect(rollout_status).not_to be_has_legacy_app_label
  87. end
  88. end
  89. end
  90. context 'with valid deployments' do
  91. let(:matched_deployment) { kube_deployment(environment_slug: environment.slug, project_slug: project.full_path_slug) }
  92. let(:unmatched_deployment) { kube_deployment }
  93. let(:matched_pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) }
  94. let(:unmatched_pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: 'Pending') }
  95. let(:deployments) { [matched_deployment, unmatched_deployment] }
  96. let(:pods) { [matched_pod, unmatched_pod] }
  97. it 'creates a matching RolloutStatus' do
  98. expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus)
  99. expect(rollout_status.deployments.map(&:annotations)).to eq([
  100. { 'app.gitlab.com/app' => project.full_path_slug, 'app.gitlab.com/env' => 'env-000000' }
  101. ])
  102. end
  103. end
  104. context 'with empty list of deployments' do
  105. it 'creates a matching RolloutStatus' do
  106. expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus)
  107. expect(rollout_status).to be_not_found
  108. end
  109. end
  110. end
  111. describe '#read_pod_logs' do
  112. let(:environment) { create(:environment) }
  113. let(:cluster) { create(:cluster, :project, platform_kubernetes: service) }
  114. let(:service) { create(:cluster_platform_kubernetes, :configured) }
  115. let(:pod_name) { 'pod-1' }
  116. let(:namespace) { 'app' }
  117. let(:container) { 'some-container' }
  118. subject { service.read_pod_logs(environment.id, pod_name, namespace, container: container) }
  119. shared_examples 'successful log request' do
  120. it do
  121. expect(subject[:logs]).to eq(["Log 1", "Log 2", "Log 3"])
  122. expect(subject[:status]).to eq(:success)
  123. expect(subject[:pod_name]).to eq(pod_name)
  124. expect(subject[:container_name]).to eq(container)
  125. end
  126. end
  127. shared_examples 'returns pod_name and container_name' do
  128. it do
  129. expect(subject[:pod_name]).to eq(pod_name)
  130. expect(subject[:container_name]).to eq(container)
  131. end
  132. end
  133. context 'with reactive cache' do
  134. before do
  135. synchronous_reactive_cache(service)
  136. end
  137. context 'when ElasticSearch is enabled' do
  138. let(:cluster) { create(:cluster, :project, platform_kubernetes: service) }
  139. let!(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: cluster) }
  140. before do
  141. expect_any_instance_of(::Clusters::Applications::ElasticStack).to receive(:elasticsearch_client).at_least(:once).and_return(Elasticsearch::Transport::Client.new)
  142. expect_any_instance_of(::Gitlab::Elasticsearch::Logs).to receive(:pod_logs).and_return(["Log 1", "Log 2", "Log 3"])
  143. stub_feature_flags(enable_cluster_application_elastic_stack: true)
  144. end
  145. include_examples 'successful log request'
  146. end
  147. context 'when kubernetes responds with valid logs' do
  148. before do
  149. stub_kubeclient_logs(pod_name, namespace, container: container)
  150. end
  151. context 'on a project level cluster' do
  152. let(:cluster) { create(:cluster, :project, platform_kubernetes: service) }
  153. include_examples 'successful log request'
  154. end
  155. context 'on a group level cluster' do
  156. let(:cluster) { create(:cluster, :group, platform_kubernetes: service) }
  157. include_examples 'successful log request'
  158. end
  159. context 'on an instance level cluster' do
  160. let(:cluster) { create(:cluster, :instance, platform_kubernetes: service) }
  161. include_examples 'successful log request'
  162. end
  163. end
  164. context 'when kubernetes responds with 500s' do
  165. before do
  166. stub_kubeclient_logs(pod_name, namespace, container: 'some-container', status: 500)
  167. end
  168. it_behaves_like 'kubernetes API error', 500
  169. it_behaves_like 'returns pod_name and container_name'
  170. end
  171. context 'when container does not exist' do
  172. before do
  173. container = 'some-container'
  174. stub_kubeclient_logs(pod_name, namespace, container: container,
  175. status: 400, message: "container #{container} is not valid for pod #{pod_name}")
  176. end
  177. it_behaves_like 'kubernetes API error', 400
  178. it_behaves_like 'returns pod_name and container_name'
  179. end
  180. context 'when kubernetes responds with 404s' do
  181. before do
  182. stub_kubeclient_logs(pod_name, namespace, container: 'some-container', status: 404)
  183. end
  184. it_behaves_like 'resource not found error', 'Pod not found'
  185. it_behaves_like 'returns pod_name and container_name'
  186. end
  187. context 'when container name is not specified' do
  188. let(:container) { 'container-0' }
  189. subject { service.read_pod_logs(environment.id, pod_name, namespace) }
  190. before do
  191. stub_kubeclient_pod_details(pod_name, namespace)
  192. stub_kubeclient_logs(pod_name, namespace, container: container)
  193. end
  194. include_examples 'successful log request'
  195. end
  196. end
  197. context 'with caching', :use_clean_rails_memory_store_caching do
  198. let(:opts) do
  199. [
  200. 'get_pod_log',
  201. {
  202. 'environment_id' => environment.id,
  203. 'pod_name' => pod_name,
  204. 'namespace' => namespace,
  205. 'container' => container
  206. }
  207. ]
  208. end
  209. context 'result is cacheable' do
  210. before do
  211. stub_kubeclient_logs(pod_name, namespace, container: container)
  212. end
  213. it do
  214. result = subject
  215. expect { stub_reactive_cache(service, result, opts) }.not_to raise_error
  216. end
  217. end
  218. context 'when value present in cache' do
  219. let(:return_value) { { 'status' => :success, 'logs' => 'logs' } }
  220. before do
  221. stub_reactive_cache(service, return_value, opts)
  222. end
  223. it 'returns cached value' do
  224. result = subject
  225. expect(result).to eq(return_value)
  226. end
  227. end
  228. context 'when value not present in cache' do
  229. it 'returns nil' do
  230. expect(ReactiveCachingWorker)
  231. .to receive(:perform_async)
  232. .with(service.class, service.id, *opts)
  233. result = subject
  234. expect(result).to eq(nil)
  235. end
  236. end
  237. end
  238. context '#reactive_cache_updated' do
  239. let(:opts) do
  240. {
  241. 'environment_id' => environment.id,
  242. 'pod_name' => pod_name,
  243. 'namespace' => namespace,
  244. 'container' => container
  245. }
  246. end
  247. subject { service.reactive_cache_updated('get_pod_log', opts) }
  248. it 'expires k8s_pod_logs etag cache' do
  249. expect_next_instance_of(Gitlab::EtagCaching::Store) do |store|
  250. expect(store).to receive(:touch)
  251. .with(
  252. ::Gitlab::Routing.url_helpers.k8s_pod_logs_project_environment_path(
  253. environment.project,
  254. environment,
  255. opts['pod_name'],
  256. opts['container_name'],
  257. format: :json
  258. )
  259. )
  260. .and_call_original
  261. end
  262. subject
  263. end
  264. end
  265. end
  266. describe '#calculate_reactive_cache_for' do
  267. let(:cluster) { create(:cluster, :project, platform_kubernetes: service) }
  268. let(:service) { create(:cluster_platform_kubernetes, :configured) }
  269. let(:namespace) { 'app' }
  270. let(:environment) { instance_double(Environment, deployment_namespace: namespace) }
  271. subject { service.calculate_reactive_cache_for(environment) }
  272. before do
  273. allow(service).to receive(:read_pods).and_return([])
  274. end
  275. context 'when kubernetes responds with valid deployments' do
  276. before do
  277. stub_kubeclient_deployments(namespace)
  278. end
  279. shared_examples 'successful deployment request' do
  280. it { is_expected.to include(deployments: [kube_deployment]) }
  281. end
  282. context 'on a project level cluster' do
  283. let(:cluster) { create(:cluster, :project, platform_kubernetes: service) }
  284. include_examples 'successful deployment request'
  285. end
  286. context 'on a group level cluster' do
  287. let(:cluster) { create(:cluster, :group, platform_kubernetes: service) }
  288. include_examples 'successful deployment request'
  289. end
  290. context 'on an instance level cluster' do
  291. let(:cluster) { create(:cluster, :instance, platform_kubernetes: service) }
  292. include_examples 'successful deployment request'
  293. end
  294. end
  295. context 'when kubernetes responds with 500s' do
  296. before do
  297. stub_kubeclient_deployments(namespace, status: 500)
  298. end
  299. it { expect { subject }.to raise_error(::Kubeclient::HttpError) }
  300. end
  301. context 'when kubernetes responds with 404s' do
  302. before do
  303. stub_kubeclient_deployments(namespace, status: 404)
  304. end
  305. it { is_expected.to include(deployments: []) }
  306. end
  307. end
  308. end