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