Add Clusters::Applications services tests
This commit is contained in:
parent
317c3cdd33
commit
61501a07cb
9 changed files with 184 additions and 57 deletions
|
@ -24,7 +24,7 @@ module Clusters
|
||||||
end
|
end
|
||||||
|
|
||||||
event :make_scheduled do
|
event :make_scheduled do
|
||||||
transition any - [:scheduled] => :scheduled
|
transition %i(installable errored) => :scheduled
|
||||||
end
|
end
|
||||||
|
|
||||||
before_transition any => [:scheduled] do |app_status, _|
|
before_transition any => [:scheduled] do |app_status, _|
|
||||||
|
|
|
@ -6,7 +6,7 @@ module Clusters
|
||||||
|
|
||||||
case installation_phase
|
case installation_phase
|
||||||
when Gitlab::Kubernetes::Pod::SUCCEEDED
|
when Gitlab::Kubernetes::Pod::SUCCEEDED
|
||||||
on_succeeded
|
finalize_installation
|
||||||
when Gitlab::Kubernetes::Pod::FAILED
|
when Gitlab::Kubernetes::Pod::FAILED
|
||||||
on_failed
|
on_failed
|
||||||
else
|
else
|
||||||
|
@ -18,14 +18,6 @@ module Clusters
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def on_succeeded
|
|
||||||
if app.make_installed
|
|
||||||
finalize_installation
|
|
||||||
else
|
|
||||||
app.make_errored!("Failed to update app record; #{app.errors}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def on_failed
|
def on_failed
|
||||||
app.make_errored!(installation_errors || 'Installation silently failed')
|
app.make_errored!(installation_errors || 'Installation silently failed')
|
||||||
finalize_installation
|
finalize_installation
|
||||||
|
@ -34,6 +26,7 @@ module Clusters
|
||||||
def check_timeout
|
def check_timeout
|
||||||
if Time.now.utc - app.updated_at.to_time.utc > ClusterWaitForAppInstallationWorker::TIMEOUT
|
if Time.now.utc - app.updated_at.to_time.utc > ClusterWaitForAppInstallationWorker::TIMEOUT
|
||||||
app.make_errored!('Installation timeouted')
|
app.make_errored!('Installation timeouted')
|
||||||
|
finalize_installation
|
||||||
else
|
else
|
||||||
ClusterWaitForAppInstallationWorker.perform_in(
|
ClusterWaitForAppInstallationWorker.perform_in(
|
||||||
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
|
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
|
||||||
|
|
|
@ -4,13 +4,7 @@ module Clusters
|
||||||
def execute
|
def execute
|
||||||
helm_api.delete_installation_pod!(app)
|
helm_api.delete_installation_pod!(app)
|
||||||
|
|
||||||
app.make_errored!('Installation aborted') if aborted?
|
app.make_installed! if app.installing?
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def aborted?
|
|
||||||
app.installing? || app.scheduled?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,14 +5,11 @@ module Clusters
|
||||||
return unless app.scheduled?
|
return unless app.scheduled?
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
app.make_installing!
|
||||||
helm_api.install(app)
|
helm_api.install(app)
|
||||||
|
|
||||||
if app.make_installing
|
ClusterWaitForAppInstallationWorker.perform_in(
|
||||||
ClusterWaitForAppInstallationWorker.perform_in(
|
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
|
||||||
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
|
|
||||||
else
|
|
||||||
app.make_errored!("Failed to update app record; #{app.errors}")
|
|
||||||
end
|
|
||||||
rescue KubeException => ke
|
rescue KubeException => ke
|
||||||
app.make_errored!("Kubernetes error: #{ke.message}")
|
app.make_errored!("Kubernetes error: #{ke.message}")
|
||||||
rescue StandardError
|
rescue StandardError
|
||||||
|
|
|
@ -2,15 +2,10 @@ module Clusters
|
||||||
module Applications
|
module Applications
|
||||||
class ScheduleInstallationService < ::BaseService
|
class ScheduleInstallationService < ::BaseService
|
||||||
def execute
|
def execute
|
||||||
application = application_class.find_or_create_by!(cluster: cluster)
|
application_class.find_or_create_by!(cluster: cluster).try do |application|
|
||||||
|
application.make_scheduled!
|
||||||
application.make_scheduled!
|
ClusterInstallAppWorker.perform_async(application.name, application.id)
|
||||||
ClusterInstallAppWorker.perform_async(application.name, application.id)
|
end
|
||||||
true
|
|
||||||
rescue ActiveRecord::RecordInvalid
|
|
||||||
false
|
|
||||||
rescue StateMachines::InvalidTransition
|
|
||||||
false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -3,20 +3,27 @@ require 'spec_helper'
|
||||||
describe Clusters::Applications::CheckInstallationProgressService do
|
describe Clusters::Applications::CheckInstallationProgressService do
|
||||||
RESCHEDULE_PHASES = Gitlab::Kubernetes::Pod::PHASES - [Gitlab::Kubernetes::Pod::SUCCEEDED, Gitlab::Kubernetes::Pod::FAILED].freeze
|
RESCHEDULE_PHASES = Gitlab::Kubernetes::Pod::PHASES - [Gitlab::Kubernetes::Pod::SUCCEEDED, Gitlab::Kubernetes::Pod::FAILED].freeze
|
||||||
|
|
||||||
def mock_helm_api(phase, errors: nil)
|
let(:application) { create(:applications_helm, :installing) }
|
||||||
expect(service).to receive(:installation_phase).once.and_return(phase)
|
let(:service) { described_class.new(application) }
|
||||||
expect(service).to receive(:installation_errors).once.and_return(errors) if errors.present?
|
let(:phase) { Gitlab::Kubernetes::Pod::UNKNOWN }
|
||||||
|
let(:errors) { nil }
|
||||||
|
|
||||||
|
shared_examples 'a terminated installation' do
|
||||||
|
it 'finalize the installation' do
|
||||||
|
expect(service).to receive(:finalize_installation).once
|
||||||
|
|
||||||
|
service.execute
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'not yet completed phase' do |phase|
|
shared_examples 'a not yet terminated installation' do |a_phase|
|
||||||
context "when the installation POD phase is #{phase}" do
|
let(:phase) { a_phase }
|
||||||
before do
|
|
||||||
mock_helm_api(phase)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
context "when phase is #{a_phase}" do
|
||||||
context 'when not timeouted' do
|
context 'when not timeouted' do
|
||||||
it 'reschedule a new check' do
|
it 'reschedule a new check' do
|
||||||
expect(ClusterWaitForAppInstallationWorker).to receive(:perform_in).once
|
expect(ClusterWaitForAppInstallationWorker).to receive(:perform_in).once
|
||||||
|
expect(service).not_to receive(:finalize_installation)
|
||||||
|
|
||||||
service.execute
|
service.execute
|
||||||
|
|
||||||
|
@ -28,6 +35,8 @@ describe Clusters::Applications::CheckInstallationProgressService do
|
||||||
context 'when timeouted' do
|
context 'when timeouted' do
|
||||||
let(:application) { create(:applications_helm, :timeouted) }
|
let(:application) { create(:applications_helm, :timeouted) }
|
||||||
|
|
||||||
|
it_behaves_like 'a terminated installation'
|
||||||
|
|
||||||
it 'make the application errored' do
|
it 'make the application errored' do
|
||||||
expect(ClusterWaitForAppInstallationWorker).not_to receive(:perform_in)
|
expect(ClusterWaitForAppInstallationWorker).not_to receive(:perform_in)
|
||||||
|
|
||||||
|
@ -40,36 +49,34 @@ describe Clusters::Applications::CheckInstallationProgressService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
expect(service).to receive(:installation_phase).once.and_return(phase)
|
||||||
|
|
||||||
|
allow(service).to receive(:installation_errors).and_return(errors)
|
||||||
|
allow(service).to receive(:finalize_installation).and_return(nil)
|
||||||
|
end
|
||||||
|
|
||||||
describe '#execute' do
|
describe '#execute' do
|
||||||
let(:application) { create(:applications_helm, :installing) }
|
|
||||||
let(:service) { described_class.new(application) }
|
|
||||||
|
|
||||||
context 'when installation POD succeeded' do
|
context 'when installation POD succeeded' do
|
||||||
it 'make the application installed' do
|
let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED }
|
||||||
mock_helm_api(Gitlab::Kubernetes::Pod::SUCCEEDED)
|
|
||||||
expect(service).to receive(:finalize_installation).once
|
|
||||||
|
|
||||||
service.execute
|
it_behaves_like 'a terminated installation'
|
||||||
|
|
||||||
expect(application).to be_installed
|
|
||||||
expect(application.status_reason).to be_nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when installation POD failed' do
|
context 'when installation POD failed' do
|
||||||
let(:error_message) { 'test installation failed' }
|
let(:phase) { Gitlab::Kubernetes::Pod::FAILED }
|
||||||
|
let(:errors) { 'test installation failed' }
|
||||||
|
|
||||||
|
it_behaves_like 'a terminated installation'
|
||||||
|
|
||||||
it 'make the application errored' do
|
it 'make the application errored' do
|
||||||
mock_helm_api(Gitlab::Kubernetes::Pod::FAILED, errors: error_message)
|
|
||||||
expect(service).to receive(:finalize_installation).once
|
|
||||||
|
|
||||||
service.execute
|
service.execute
|
||||||
|
|
||||||
expect(application).to be_errored
|
expect(application).to be_errored
|
||||||
expect(application.status_reason).to eq(error_message)
|
expect(application.status_reason).to eq(errors)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
RESCHEDULE_PHASES.each { |phase| it_behaves_like 'not yet completed phase', phase }
|
RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Clusters::Applications::FinalizeInstallationService do
|
||||||
|
describe '#execute' do
|
||||||
|
let(:application) { create(:applications_helm, :installing) }
|
||||||
|
let(:service) { described_class.new(application) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
expect_any_instance_of(Gitlab::Kubernetes::Helm).to receive(:delete_installation_pod!).with(application)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when installation POD succeeded' do
|
||||||
|
it 'make the application installed' do
|
||||||
|
service.execute
|
||||||
|
|
||||||
|
expect(application).to be_installed
|
||||||
|
expect(application.status_reason).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when installation POD failed' do
|
||||||
|
let(:application) { create(:applications_helm, :errored) }
|
||||||
|
|
||||||
|
it 'make the application errored' do
|
||||||
|
service.execute
|
||||||
|
|
||||||
|
expect(application).to be_errored
|
||||||
|
expect(application.status_reason).not_to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
54
spec/services/clusters/applications/install_service_spec.rb
Normal file
54
spec/services/clusters/applications/install_service_spec.rb
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Clusters::Applications::InstallService do
|
||||||
|
describe '#execute' do
|
||||||
|
let(:application) { create(:applications_helm, :scheduled) }
|
||||||
|
let(:service) { described_class.new(application) }
|
||||||
|
|
||||||
|
context 'when there are no errors' do
|
||||||
|
before do
|
||||||
|
expect_any_instance_of(Gitlab::Kubernetes::Helm).to receive(:install).with(application)
|
||||||
|
allow(ClusterWaitForAppInstallationWorker).to receive(:perform_in).and_return(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'make the application installing' do
|
||||||
|
service.execute
|
||||||
|
|
||||||
|
expect(application).to be_installing
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'schedule async installation status check' do
|
||||||
|
expect(ClusterWaitForAppInstallationWorker).to receive(:perform_in).once
|
||||||
|
|
||||||
|
service.execute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when k8s cluster communication fails' do
|
||||||
|
before do
|
||||||
|
error = KubeException.new(500, 'system failure', nil)
|
||||||
|
expect_any_instance_of(Gitlab::Kubernetes::Helm).to receive(:install).with(application).and_raise(error)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'make the application errored' do
|
||||||
|
service.execute
|
||||||
|
|
||||||
|
expect(application).to be_errored
|
||||||
|
expect(application.status_reason).to match(/kubernetes error:/i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when application cannot be persisted' do
|
||||||
|
let(:application) { build(:applications_helm, :scheduled) }
|
||||||
|
|
||||||
|
it 'make the application errored' do
|
||||||
|
expect(application).to receive(:make_installing!).once.and_raise(ActiveRecord::RecordInvalid)
|
||||||
|
expect_any_instance_of(Gitlab::Kubernetes::Helm).not_to receive(:install)
|
||||||
|
|
||||||
|
service.execute
|
||||||
|
|
||||||
|
expect(application).to be_errored
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,55 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Clusters::Applications::ScheduleInstallationService do
|
||||||
|
def count_scheduled
|
||||||
|
application_class&.with_status(:scheduled)&.count || 0
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'a failing service' do
|
||||||
|
it 'raise an exception' do
|
||||||
|
expect(ClusterInstallAppWorker).not_to receive(:perform_async)
|
||||||
|
count_before = count_scheduled
|
||||||
|
|
||||||
|
expect { service.execute }.to raise_error(StandardError)
|
||||||
|
expect(count_scheduled).to eq(count_before)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#execute' do
|
||||||
|
let(:application_class) { Clusters::Applications::Helm }
|
||||||
|
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||||
|
let(:project) { cluster.project }
|
||||||
|
let(:service) { described_class.new(project, nil, cluster: cluster, application_class: application_class) }
|
||||||
|
|
||||||
|
it 'creates a new application' do
|
||||||
|
expect { service.execute }.to change { application_class.count }.by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'make the application scheduled' do
|
||||||
|
expect(ClusterInstallAppWorker).to receive(:perform_async).with(application_class.application_name, kind_of(Numeric)).once
|
||||||
|
|
||||||
|
expect { service.execute }.to change { application_class.with_status(:scheduled).count }.by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when installation is already in progress' do
|
||||||
|
let(:application) { create(:applications_helm, :installing) }
|
||||||
|
let(:cluster) { application.cluster }
|
||||||
|
|
||||||
|
it_behaves_like 'a failing service'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when application_class is nil' do
|
||||||
|
let(:application_class) { nil }
|
||||||
|
|
||||||
|
it_behaves_like 'a failing service'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when application cannot be persisted' do
|
||||||
|
before do
|
||||||
|
expect_any_instance_of(application_class).to receive(:make_scheduled!).once.and_raise(ActiveRecord::RecordInvalid)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'a failing service'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue