diff options
author | Thong Kuah <tkuah@gitlab.com> | 2019-01-18 14:02:02 +1300 |
---|---|---|
committer | Thong Kuah <tkuah@gitlab.com> | 2019-01-23 14:38:19 +1300 |
commit | 617015e007d6dcffa9593ef7335e186f01696227 (patch) | |
tree | 25610c99e68893d16106014aa3403f4c46854108 | |
parent | 79854159cc581aead1df5b03684cdcf2518b29ba (diff) | |
download | gitlab-ce-upgrade-runner-chart.tar.gz |
Add service to upgrade cluster applicationsupgrade-runner-chart
Something will have to call the service, in a later MR
9 files changed, 219 insertions, 57 deletions
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index 26bf73f4dd8..f50adb4693d 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -52,24 +52,16 @@ module Clusters ) end - def upgrade_command(values) + def upgrade_command(replaced_values: nil) ::Gitlab::Kubernetes::Helm::UpgradeCommand.new( name, version: VERSION, chart: chart, rbac: cluster.platform_kubernetes_rbac?, - files: files_with_replaced_values(values) + files: replaced_values ? files_with_replaced_values(replaced_values) : files ) end - # Returns a copy of files where the values of 'values.yaml' - # are replaced by the argument. - # - # See #values for the data format required - def files_with_replaced_values(replaced_values) - files.merge('values.yaml': replaced_values) - end - def prometheus_client return unless kube_client diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index 2cf8d47bded..5b4a5ad8ba1 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -40,13 +40,13 @@ module Clusters ) end - def upgrade_command + def upgrade_command(replaced_values: nil) ::Gitlab::Kubernetes::Helm::UpgradeCommand.new( name, version: VERSION, rbac: cluster.platform_kubernetes_rbac?, chart: chart, - files: files, + files: replaced_values ? files_with_replaced_values(replaced_values) : files, repository: repository ) end diff --git a/app/models/clusters/concerns/application_data.rb b/app/models/clusters/concerns/application_data.rb index 52498f123ff..556bf14888b 100644 --- a/app/models/clusters/concerns/application_data.rb +++ b/app/models/clusters/concerns/application_data.rb @@ -24,6 +24,14 @@ module Clusters end end + # Returns a copy of files where the values of 'values.yaml' + # are replaced by the argument. + # + # See #values for the data format required + def files_with_replaced_values(replaced_values) + files.merge('values.yaml': replaced_values) + end + private def certificate_files diff --git a/app/services/clusters/applications/base_helm_service.rb b/app/services/clusters/applications/base_helm_service.rb index 8a71730d5ec..c04fb8914f7 100644 --- a/app/services/clusters/applications/base_helm_service.rb +++ b/app/services/clusters/applications/base_helm_service.rb @@ -46,8 +46,8 @@ module Clusters @install_command ||= app.install_command end - def upgrade_command(new_values = "") - app.upgrade_command(new_values) + def upgrade_command(replaced_values: nil) + app.upgrade_command(replaced_values: replaced_values) end end end diff --git a/app/services/clusters/applications/update_service.rb b/app/services/clusters/applications/update_service.rb new file mode 100644 index 00000000000..fecc13f979d --- /dev/null +++ b/app/services/clusters/applications/update_service.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Clusters + module Applications + class UpdateService < BaseHelmService + def execute + return if app.updating? + + begin + app.make_updating! + helm_api.update(upgrade_command) + + ::ClusterWaitForAppUpdateWorker.perform_in(::ClusterWaitForAppUpdateWorker::INTERVAL, app.name, app.id) + rescue Kubeclient::HttpError => e + log_error(e) + app.make_update_errored!("Kubernetes error: #{e.error_code}") + rescue StandardError => e + log_error(e) + app.make_update_errored!("Can't start installation process.") + end + end + end + end +end diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index e50ba67c493..a70fe4225cd 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -217,20 +217,30 @@ describe Clusters::Applications::Prometheus do describe '#upgrade_command' do let(:prometheus) { build(:clusters_applications_prometheus) } - let(:values) { prometheus.values } + let(:replaced_values) { nil } it 'returns an instance of Gitlab::Kubernetes::Helm::GetCommand' do - expect(prometheus.upgrade_command(values)).to be_an_instance_of(::Gitlab::Kubernetes::Helm::UpgradeCommand) + expect(prometheus.upgrade_command(replaced_values: replaced_values)).to be_an_instance_of(::Gitlab::Kubernetes::Helm::UpgradeCommand) end it 'should be initialized with 3 arguments' do - command = prometheus.upgrade_command(values) + command = prometheus.upgrade_command(replaced_values: replaced_values) expect(command.name).to eq('prometheus') expect(command.chart).to eq('stable/prometheus') expect(command.version).to eq('6.7.3') expect(command.files).to eq(prometheus.files) end + + context 'with replaced values' do + let(:replaced_values) { {} } + + it 'use the replaced values for values.yaml' do + command = prometheus.upgrade_command(replaced_values: replaced_values) + + expect(command.files).to include('values.yaml': replaced_values) + end + end end describe '#update_in_progress?' do @@ -269,43 +279,4 @@ describe Clusters::Applications::Prometheus do expect(values).to include('serverFiles') end end - - describe '#files_with_replaced_values' do - let(:application) { build(:clusters_applications_prometheus) } - let(:files) { application.files } - - subject { application.files_with_replaced_values({ hello: :world }) } - - it 'does not modify #files' do - expect(subject[:'values.yaml']).not_to eq(files) - expect(files[:'values.yaml']).to eq(application.values) - end - - it 'returns values.yaml with replaced values' do - expect(subject[:'values.yaml']).to eq({ hello: :world }) - end - - it 'should include cert files' do - expect(subject[:'ca.pem']).to be_present - expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert) - - expect(subject[:'cert.pem']).to be_present - expect(subject[:'key.pem']).to be_present - - cert = OpenSSL::X509::Certificate.new(subject[:'cert.pem']) - expect(cert.not_after).to be < 60.minutes.from_now - end - - context 'when the helm application does not have a ca_cert' do - before do - application.cluster.application_helm.ca_cert = nil - end - - it 'should not include cert files' do - expect(subject[:'ca.pem']).not_to be_present - expect(subject[:'cert.pem']).not_to be_present - expect(subject[:'key.pem']).not_to be_present - end - end - end end diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index cd28a46ff08..852154c3d8d 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -71,8 +71,9 @@ describe Clusters::Applications::Runner do describe '#upgrade_command' do let(:gitlab_runner) { create(:clusters_applications_runner, runner: ci_runner) } + let(:replaced_values) { nil } - subject { gitlab_runner.upgrade_command } + subject { gitlab_runner.upgrade_command(replaced_values: replaced_values) } it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::UpgradeCommand) } diff --git a/spec/services/clusters/applications/update_service_spec.rb b/spec/services/clusters/applications/update_service_spec.rb new file mode 100644 index 00000000000..9d69456d381 --- /dev/null +++ b/spec/services/clusters/applications/update_service_spec.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Applications::UpdateService do + describe '#execute' do + let(:application) { create(:clusters_applications_runner, :installed) } + let(:service) { described_class.new(application) } + let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) } + + before do + allow(service).to receive(:helm_api).and_return(helm_client) + end + + context 'when there are no errors' do + before do + expect(helm_client).to receive(:update).with(kind_of(Gitlab::Kubernetes::Helm::UpgradeCommand)) + + allow(::ClusterWaitForAppUpdateWorker) + .to receive(:perform_in) + .and_return(nil) + end + + it 'makes the application updating' do + service.execute + + expect(application).to be_updating + end + + it 'schedules async update status check' do + expect(::ClusterWaitForAppUpdateWorker).to receive(:perform_in).once + + service.execute + end + end + + context 'when k8s cluster communication fails' do + let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) } + + before do + expect(helm_client).to receive(:update).with(kind_of(Gitlab::Kubernetes::Helm::UpgradeCommand)).and_raise(error) + end + + it 'make the application update_errored' do + service.execute + + expect(application).to be_update_errored + expect(application.status_reason).to match('Kubernetes error: 500') + end + + it 'logs errors' do + expect(service.send(:logger)).to receive(:error).with( + { + exception: 'Kubeclient::HttpError', + message: 'system failure', + service: 'Clusters::Applications::UpdateService', + app_id: application.id, + project_ids: application.cluster.project_ids, + group_ids: [], + error_code: 500 + } + ) + + expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( + error, + extra: { + exception: 'Kubeclient::HttpError', + message: 'system failure', + service: 'Clusters::Applications::UpdateService', + app_id: application.id, + project_ids: application.cluster.project_ids, + group_ids: [], + error_code: 500 + } + ) + + service.execute + end + end + + context 'a non kubernetes error happens' do + let(:error) { StandardError.new("something bad happened") } + + before do + expect(application).to receive(:make_updating!).once.and_raise(error) + end + + it 'make the application update_errored' do + expect(helm_client).not_to receive(:update) + + service.execute + + expect(application).to be_update_errored + expect(application.status_reason).to eq("Can't start installation process.") + end + + it 'logs errors' do + expect(service.send(:logger)).to receive(:error).with( + { + exception: 'StandardError', + error_code: nil, + message: 'something bad happened', + service: 'Clusters::Applications::UpdateService', + app_id: application.id, + project_ids: application.cluster.projects.pluck(:id), + group_ids: [] + } + ) + + expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( + error, + extra: { + exception: 'StandardError', + error_code: nil, + message: 'something bad happened', + service: 'Clusters::Applications::UpdateService', + app_id: application.id, + project_ids: application.cluster.projects.pluck(:id), + group_ids: [] + } + ) + + service.execute + end + end + end +end diff --git a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb index d87b3181e80..039998df4d0 100644 --- a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb @@ -22,4 +22,43 @@ shared_examples 'cluster application helm specs' do |application_name| expect(cert.not_after).to be < 60.minutes.from_now end end + + describe '#files_with_replaced_values' do + let(:application) { build(application_name) } + let(:files) { application.files } + + subject { application.files_with_replaced_values({ hello: :world }) } + + it 'does not modify #files' do + expect(subject[:'values.yaml']).not_to eq(files) + expect(files[:'values.yaml']).to eq(application.values) + end + + it 'returns values.yaml with replaced values' do + expect(subject[:'values.yaml']).to eq({ hello: :world }) + end + + it 'should include cert files' do + expect(subject[:'ca.pem']).to be_present + expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert) + + expect(subject[:'cert.pem']).to be_present + expect(subject[:'key.pem']).to be_present + + cert = OpenSSL::X509::Certificate.new(subject[:'cert.pem']) + expect(cert.not_after).to be < 60.minutes.from_now + end + + context 'when the helm application does not have a ca_cert' do + before do + application.cluster.application_helm.ca_cert = nil + end + + it 'should not include cert files' do + expect(subject[:'ca.pem']).not_to be_present + expect(subject[:'cert.pem']).not_to be_present + expect(subject[:'key.pem']).not_to be_present + end + end + end end |