a2aa160cea
Remove Kn services cache from Clusters::Application::Knative Knative function can exist even if user did not installed Knative via GitLab managed apps. -> Move responsibility of finding services into the Cluster -> Responsability is inside Clusters::Cluster::KnativeServiceFinder -> Projects::Serverless::FunctionsFinder now calls depends solely on a cluster to find the Kn services. -> Detect Knative by resource presence instead of service presence -> Mock knative_installed response temporarily for frontend to develop Display loader while `installed === 'checking'` Added frontend work to determine if Knative is installed Memoize with_reactive_cache(*args, &block) to avoid race conditions When calling with_reactive_cache more than once, it's possible that the second call will already have the value populated. Therefore, in cases where we need the sequential calls to have consistent results, we'd fall under a race condition. Check knative installation via Knative resource presence Only load pods if Knative is discovered Always return a response in FunctionsController#index - Always indicate if Knative is installed, not installed or checking - Always indicate the partial response for functions. Final response is guaranteed when knative_installed is either true | false. Adds specs for Clusters::Cluster#knative_services_finder Fix method name when calling on specs Add an explicit check for functions Added an explicit check to see if there are any functions available Fix Serverless feature spec - we don't find knative installation via database anymore, rather via Knative resource Display error message for request timeouts Display an error message if the request times out Adds feature specs for when functions exist Remove a test purposed hardcoded flag Add ability to partially load functions Added the ability to partially load functions on the frontend Add frontend unit tests Added tests for the new frontend additions Generate new translations Generated new frontend translations Address review comments Cleaned up the frontend unit test. Added computed prop for `isInstalled`. Move string to constant Simplify nil to array conversion Put knative_installed states in a frozen hash for better read Pluralize list of Knative states Quey services and pods filtering name This way we don't need to filter the namespace in memory. Also, the data we get from the network is much smaller. Simplify cache_key and fix bug - Simplifies the cache_key by removing namespace duplicate - Fixes a bug with reactive_cache memoization
740 lines
21 KiB
Ruby
740 lines
21 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
|
|
include ReactiveCachingHelpers
|
|
include KubernetesHelpers
|
|
|
|
it_behaves_like 'having unique enum values'
|
|
|
|
subject { build(:cluster) }
|
|
|
|
it { is_expected.to belong_to(:user) }
|
|
it { is_expected.to have_many(:cluster_projects) }
|
|
it { is_expected.to have_many(:projects) }
|
|
it { is_expected.to have_many(:cluster_groups) }
|
|
it { is_expected.to have_many(:groups) }
|
|
it { is_expected.to have_one(:provider_gcp) }
|
|
it { is_expected.to have_one(:platform_kubernetes) }
|
|
it { is_expected.to have_one(:application_helm) }
|
|
it { is_expected.to have_one(:application_ingress) }
|
|
it { is_expected.to have_one(:application_prometheus) }
|
|
it { is_expected.to have_one(:application_runner) }
|
|
it { is_expected.to have_many(:kubernetes_namespaces) }
|
|
it { is_expected.to have_one(:cluster_project) }
|
|
|
|
it { is_expected.to delegate_method(:status).to(:provider) }
|
|
it { is_expected.to delegate_method(:status_reason).to(:provider) }
|
|
it { is_expected.to delegate_method(:on_creation?).to(:provider) }
|
|
it { is_expected.to delegate_method(:active?).to(:platform_kubernetes).with_prefix }
|
|
it { is_expected.to delegate_method(:rbac?).to(:platform_kubernetes).with_prefix }
|
|
it { is_expected.to delegate_method(:available?).to(:application_helm).with_prefix }
|
|
it { is_expected.to delegate_method(:available?).to(:application_ingress).with_prefix }
|
|
it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix }
|
|
it { is_expected.to delegate_method(:available?).to(:application_knative).with_prefix }
|
|
it { is_expected.to delegate_method(:external_ip).to(:application_ingress).with_prefix }
|
|
it { is_expected.to delegate_method(:external_hostname).to(:application_ingress).with_prefix }
|
|
|
|
it { is_expected.to respond_to :project }
|
|
|
|
it do
|
|
expect(subject.knative_services_finder(subject.project))
|
|
.to be_instance_of(Clusters::Cluster::KnativeServicesFinder)
|
|
end
|
|
|
|
describe '.enabled' do
|
|
subject { described_class.enabled }
|
|
|
|
let!(:cluster) { create(:cluster, enabled: true) }
|
|
|
|
before do
|
|
create(:cluster, enabled: false)
|
|
end
|
|
|
|
it { is_expected.to contain_exactly(cluster) }
|
|
end
|
|
|
|
describe '.disabled' do
|
|
subject { described_class.disabled }
|
|
|
|
let!(:cluster) { create(:cluster, enabled: false) }
|
|
|
|
before do
|
|
create(:cluster, enabled: true)
|
|
end
|
|
|
|
it { is_expected.to contain_exactly(cluster) }
|
|
end
|
|
|
|
describe '.user_provided' do
|
|
subject { described_class.user_provided }
|
|
|
|
let!(:cluster) { create(:cluster, :provided_by_user) }
|
|
|
|
before do
|
|
create(:cluster, :provided_by_gcp)
|
|
end
|
|
|
|
it { is_expected.to contain_exactly(cluster) }
|
|
end
|
|
|
|
describe '.gcp_provided' do
|
|
subject { described_class.gcp_provided }
|
|
|
|
let!(:cluster) { create(:cluster, :provided_by_gcp) }
|
|
|
|
before do
|
|
create(:cluster, :provided_by_user)
|
|
end
|
|
|
|
it { is_expected.to contain_exactly(cluster) }
|
|
end
|
|
|
|
describe '.gcp_installed' do
|
|
subject { described_class.gcp_installed }
|
|
|
|
let!(:cluster) { create(:cluster, :provided_by_gcp) }
|
|
|
|
before do
|
|
create(:cluster, :providing_by_gcp)
|
|
end
|
|
|
|
it { is_expected.to contain_exactly(cluster) }
|
|
end
|
|
|
|
describe '.managed' do
|
|
subject do
|
|
described_class.managed
|
|
end
|
|
|
|
context 'cluster is not managed' do
|
|
let!(:cluster) { create(:cluster, :not_managed) }
|
|
|
|
it { is_expected.not_to include(cluster) }
|
|
end
|
|
|
|
context 'cluster is managed' do
|
|
let!(:cluster) { create(:cluster) }
|
|
|
|
it { is_expected.to include(cluster) }
|
|
end
|
|
end
|
|
|
|
describe '.missing_kubernetes_namespace' do
|
|
let!(:cluster) { create(:cluster, :provided_by_gcp, :project) }
|
|
let(:project) { cluster.project }
|
|
let(:kubernetes_namespaces) { project.kubernetes_namespaces }
|
|
|
|
subject do
|
|
described_class.joins(:projects).where(projects: { id: project.id }).missing_kubernetes_namespace(kubernetes_namespaces)
|
|
end
|
|
|
|
it { is_expected.to contain_exactly(cluster) }
|
|
|
|
context 'kubernetes namespace exists' do
|
|
before do
|
|
create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
|
|
end
|
|
|
|
it { is_expected.to be_empty }
|
|
end
|
|
end
|
|
|
|
describe 'validations' do
|
|
subject { cluster.valid? }
|
|
|
|
context 'when validates name' do
|
|
context 'when provided by user' do
|
|
let!(:cluster) { build(:cluster, :provided_by_user, name: name) }
|
|
|
|
context 'when name is empty' do
|
|
let(:name) { '' }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
context 'when name is nil' do
|
|
let(:name) { nil }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
context 'when name is present' do
|
|
let(:name) { 'cluster-name-1' }
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
end
|
|
|
|
context 'when provided by gcp' do
|
|
let!(:cluster) { build(:cluster, :provided_by_gcp, name: name) }
|
|
|
|
context 'when name is shorter than 1' do
|
|
let(:name) { '' }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
context 'when name is longer than 63' do
|
|
let(:name) { 'a' * 64 }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
context 'when name includes invalid character' do
|
|
let(:name) { '!!!!!!' }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
context 'when name is present' do
|
|
let(:name) { 'cluster-name-1' }
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
context 'when record is persisted' do
|
|
let(:name) { 'cluster-name-1' }
|
|
|
|
before do
|
|
cluster.save!
|
|
end
|
|
|
|
context 'when name is changed' do
|
|
before do
|
|
cluster.name = 'new-cluster-name'
|
|
end
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
context 'when name is same' do
|
|
before do
|
|
cluster.name = name
|
|
end
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when validates restrict_modification' do
|
|
context 'when creation is on going' do
|
|
let!(:cluster) { create(:cluster, :providing_by_gcp) }
|
|
|
|
it { expect(cluster.update(enabled: false)).to be_falsey }
|
|
end
|
|
|
|
context 'when creation is done' do
|
|
let!(:cluster) { create(:cluster, :provided_by_gcp) }
|
|
|
|
it { expect(cluster.update(enabled: false)).to be_truthy }
|
|
end
|
|
end
|
|
|
|
describe 'cluster_type validations' do
|
|
let(:instance_cluster) { create(:cluster, :instance) }
|
|
let(:group_cluster) { create(:cluster, :group) }
|
|
let(:project_cluster) { create(:cluster, :project) }
|
|
|
|
it 'validates presence' do
|
|
cluster = build(:cluster, :project, cluster_type: nil)
|
|
|
|
expect(cluster).not_to be_valid
|
|
expect(cluster.errors.full_messages).to include("Cluster type can't be blank")
|
|
end
|
|
|
|
context 'project_type cluster' do
|
|
it 'does not allow setting group' do
|
|
project_cluster.groups << build(:group)
|
|
|
|
expect(project_cluster).not_to be_valid
|
|
expect(project_cluster.errors.full_messages).to include('Cluster cannot have groups assigned')
|
|
end
|
|
end
|
|
|
|
context 'group_type cluster' do
|
|
it 'does not allow setting project' do
|
|
group_cluster.projects << build(:project)
|
|
|
|
expect(group_cluster).not_to be_valid
|
|
expect(group_cluster.errors.full_messages).to include('Cluster cannot have projects assigned')
|
|
end
|
|
end
|
|
|
|
context 'instance_type cluster' do
|
|
it 'does not allow setting group' do
|
|
instance_cluster.groups << build(:group)
|
|
|
|
expect(instance_cluster).not_to be_valid
|
|
expect(instance_cluster.errors.full_messages).to include('Cluster cannot have groups assigned')
|
|
end
|
|
|
|
it 'does not allow setting project' do
|
|
instance_cluster.projects << build(:project)
|
|
|
|
expect(instance_cluster).not_to be_valid
|
|
expect(instance_cluster.errors.full_messages).to include('Cluster cannot have projects assigned')
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'domain validation' do
|
|
let(:cluster) { build(:cluster) }
|
|
|
|
subject { cluster }
|
|
|
|
context 'when cluster has domain' do
|
|
let(:cluster) { build(:cluster, :with_domain) }
|
|
|
|
it { is_expected.to be_valid }
|
|
end
|
|
|
|
context 'when cluster is not a valid hostname' do
|
|
let(:cluster) { build(:cluster, domain: 'http://not.a.valid.hostname') }
|
|
|
|
it 'adds an error on domain' do
|
|
expect(subject).not_to be_valid
|
|
expect(subject.errors[:domain].first).to eq('contains invalid characters (valid characters: [a-z0-9\\-])')
|
|
end
|
|
end
|
|
|
|
context 'when cluster does not have a domain' do
|
|
it { is_expected.to be_valid }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.ancestor_clusters_for_clusterable' do
|
|
let(:group_cluster) { create(:cluster, :provided_by_gcp, :group) }
|
|
let(:group) { group_cluster.group }
|
|
let(:hierarchy_order) { :desc }
|
|
let(:clusterable) { project }
|
|
|
|
subject do
|
|
described_class.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: hierarchy_order)
|
|
end
|
|
|
|
context 'when project does not belong to this group' do
|
|
let(:project) { create(:project, group: create(:group)) }
|
|
|
|
it 'returns nothing' do
|
|
is_expected.to be_empty
|
|
end
|
|
end
|
|
|
|
context 'when group has a configured kubernetes cluster' do
|
|
let(:project) { create(:project, group: group) }
|
|
|
|
it 'returns the group cluster' do
|
|
is_expected.to eq([group_cluster])
|
|
end
|
|
end
|
|
|
|
context 'when group and instance have configured kubernetes clusters' do
|
|
let(:project) { create(:project, group: group) }
|
|
let!(:instance_cluster) { create(:cluster, :provided_by_gcp, :instance) }
|
|
|
|
it 'returns clusters in order, descending the hierachy' do
|
|
is_expected.to eq([group_cluster, instance_cluster])
|
|
end
|
|
end
|
|
|
|
context 'when sub-group has configured kubernetes cluster', :nested_groups do
|
|
let(:sub_group_cluster) { create(:cluster, :provided_by_gcp, :group) }
|
|
let(:sub_group) { sub_group_cluster.group }
|
|
let(:project) { create(:project, group: sub_group) }
|
|
|
|
before do
|
|
sub_group.update!(parent: group)
|
|
end
|
|
|
|
it 'returns clusters in order, descending the hierachy' do
|
|
is_expected.to eq([group_cluster, sub_group_cluster])
|
|
end
|
|
|
|
it 'avoids N+1 queries' do
|
|
another_project = create(:project)
|
|
control_count = ActiveRecord::QueryRecorder.new do
|
|
described_class.ancestor_clusters_for_clusterable(another_project, hierarchy_order: hierarchy_order)
|
|
end.count
|
|
|
|
cluster2 = create(:cluster, :provided_by_gcp, :group)
|
|
child2 = cluster2.group
|
|
child2.update!(parent: sub_group)
|
|
project = create(:project, group: child2)
|
|
|
|
expect do
|
|
described_class.ancestor_clusters_for_clusterable(project, hierarchy_order: hierarchy_order)
|
|
end.not_to exceed_query_limit(control_count)
|
|
end
|
|
|
|
context 'for a group' do
|
|
let(:clusterable) { sub_group }
|
|
|
|
it 'returns clusters in order for a group' do
|
|
is_expected.to eq([group_cluster])
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'scope chaining' do
|
|
let(:project) { create(:project, group: group) }
|
|
|
|
subject { described_class.none.ancestor_clusters_for_clusterable(project) }
|
|
|
|
it 'returns nothing' do
|
|
is_expected.to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#provider' do
|
|
subject { cluster.provider }
|
|
|
|
context 'when provider is gcp' do
|
|
let(:cluster) { create(:cluster, :provided_by_gcp) }
|
|
|
|
it 'returns a provider' do
|
|
is_expected.to eq(cluster.provider_gcp)
|
|
expect(subject.class.name.deconstantize).to eq(Clusters::Providers.to_s)
|
|
end
|
|
end
|
|
|
|
context 'when provider is user' do
|
|
let(:cluster) { create(:cluster, :provided_by_user) }
|
|
|
|
it { is_expected.to be_nil }
|
|
end
|
|
end
|
|
|
|
describe '#platform' do
|
|
subject { cluster.platform }
|
|
|
|
context 'when platform is kubernetes' do
|
|
let(:cluster) { create(:cluster, :provided_by_user) }
|
|
|
|
it 'returns a platform' do
|
|
is_expected.to eq(cluster.platform_kubernetes)
|
|
expect(subject.class.name.deconstantize).to eq(Clusters::Platforms.to_s)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#all_projects' do
|
|
let(:project) { create(:project) }
|
|
let(:cluster) { create(:cluster, projects: [project]) }
|
|
|
|
subject { cluster.all_projects }
|
|
|
|
context 'project cluster' do
|
|
it 'returns project' do
|
|
is_expected.to eq([project])
|
|
end
|
|
end
|
|
|
|
context 'group cluster' do
|
|
let(:cluster) { create(:cluster, :group) }
|
|
let(:group) { cluster.group }
|
|
let(:project) { create(:project, group: group) }
|
|
let(:subgroup) { create(:group, parent: group) }
|
|
let(:subproject) { create(:project, group: subgroup) }
|
|
|
|
it 'returns all projects for group' do
|
|
is_expected.to contain_exactly(project, subproject)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#first_project' do
|
|
subject { cluster.first_project }
|
|
|
|
context 'when cluster belongs to a project' do
|
|
let(:cluster) { create(:cluster, :project) }
|
|
let(:project) { Clusters::Project.find_by_cluster_id(cluster.id).project }
|
|
|
|
it { is_expected.to eq(project) }
|
|
end
|
|
|
|
context 'when cluster does not belong to projects' do
|
|
let(:cluster) { create(:cluster) }
|
|
|
|
it { is_expected.to be_nil }
|
|
end
|
|
end
|
|
|
|
describe '#group' do
|
|
subject { cluster.group }
|
|
|
|
context 'when cluster belongs to a group' do
|
|
let(:cluster) { create(:cluster, :group) }
|
|
let(:group) { cluster.groups.first }
|
|
|
|
it { is_expected.to eq(group) }
|
|
end
|
|
|
|
context 'when cluster does not belong to any group' do
|
|
let(:cluster) { create(:cluster) }
|
|
|
|
it { is_expected.to be_nil }
|
|
end
|
|
end
|
|
|
|
describe '#applications' do
|
|
set(:cluster) { create(:cluster) }
|
|
|
|
subject { cluster.applications }
|
|
|
|
context 'when none of applications are created' do
|
|
it 'returns a list of a new objects' do
|
|
is_expected.not_to be_empty
|
|
end
|
|
end
|
|
|
|
context 'when applications are created' do
|
|
let!(:helm) { create(:clusters_applications_helm, cluster: cluster) }
|
|
let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) }
|
|
let!(:cert_manager) { create(:clusters_applications_cert_managers, cluster: cluster) }
|
|
let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) }
|
|
let!(:runner) { create(:clusters_applications_runner, cluster: cluster) }
|
|
let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) }
|
|
let!(:knative) { create(:clusters_applications_knative, cluster: cluster) }
|
|
|
|
it 'returns a list of created applications' do
|
|
is_expected.to contain_exactly(helm, ingress, cert_manager, prometheus, runner, jupyter, knative)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#allow_user_defined_namespace?' do
|
|
let(:cluster) { create(:cluster, :provided_by_gcp) }
|
|
|
|
subject { cluster.allow_user_defined_namespace? }
|
|
|
|
context 'project type cluster' do
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
context 'group type cluster' do
|
|
let(:cluster) { create(:cluster, :provided_by_gcp, :group) }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
context 'instance type cluster' do
|
|
let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
end
|
|
|
|
describe '#kube_ingress_domain' do
|
|
let(:cluster) { create(:cluster, :provided_by_gcp) }
|
|
|
|
subject { cluster.kube_ingress_domain }
|
|
|
|
context 'with domain set in cluster' do
|
|
let(:cluster) { create(:cluster, :provided_by_gcp, :with_domain) }
|
|
|
|
it { is_expected.to eq(cluster.domain) }
|
|
end
|
|
|
|
context 'with no domain on cluster' do
|
|
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
|
let(:project) { cluster.project }
|
|
|
|
context 'with domain set at instance level' do
|
|
before do
|
|
stub_application_setting(auto_devops_domain: 'global_domain.com')
|
|
end
|
|
|
|
it { is_expected.to eq('global_domain.com') }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#predefined_variables' do
|
|
subject { cluster.predefined_variables }
|
|
|
|
context 'with an instance domain' do
|
|
let(:cluster) { create(:cluster, :provided_by_gcp) }
|
|
|
|
before do
|
|
stub_application_setting(auto_devops_domain: 'global_domain.com')
|
|
end
|
|
|
|
it 'includes KUBE_INGRESS_BASE_DOMAIN' do
|
|
expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'global_domain.com')
|
|
end
|
|
end
|
|
|
|
context 'with a cluster domain' do
|
|
let(:cluster) { create(:cluster, :provided_by_gcp, domain: 'example.com') }
|
|
|
|
it 'includes KUBE_INGRESS_BASE_DOMAIN' do
|
|
expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'example.com')
|
|
end
|
|
end
|
|
|
|
context 'with no domain' do
|
|
let(:cluster) { create(:cluster, :provided_by_gcp, :project) }
|
|
|
|
it 'returns an empty array' do
|
|
expect(subject.to_hash).to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#provided_by_user?' do
|
|
subject { cluster.provided_by_user? }
|
|
|
|
context 'with a GCP provider' do
|
|
let(:cluster) { create(:cluster, :provided_by_gcp) }
|
|
|
|
it { is_expected.to be_falsy }
|
|
end
|
|
|
|
context 'with an user provider' do
|
|
let(:cluster) { create(:cluster, :provided_by_user) }
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
end
|
|
|
|
describe '#status_name' do
|
|
subject { cluster.status_name }
|
|
|
|
context 'the cluster has a provider' do
|
|
let(:cluster) { create(:cluster, :provided_by_gcp) }
|
|
|
|
before do
|
|
cluster.provider.make_errored!
|
|
end
|
|
|
|
it { is_expected.to eq :errored }
|
|
end
|
|
|
|
context 'there is a cached connection status' do
|
|
let(:cluster) { create(:cluster, :provided_by_user) }
|
|
|
|
before do
|
|
allow(cluster).to receive(:connection_status).and_return(:connected)
|
|
end
|
|
|
|
it { is_expected.to eq :connected }
|
|
end
|
|
|
|
context 'there is no connection status in the cache' do
|
|
let(:cluster) { create(:cluster, :provided_by_user) }
|
|
|
|
before do
|
|
allow(cluster).to receive(:connection_status).and_return(nil)
|
|
end
|
|
|
|
it { is_expected.to eq :created }
|
|
end
|
|
end
|
|
|
|
describe '#connection_status' do
|
|
let(:cluster) { create(:cluster) }
|
|
let(:status) { :connected }
|
|
|
|
subject { cluster.connection_status }
|
|
|
|
it { is_expected.to be_nil }
|
|
|
|
context 'with a cached status' do
|
|
before do
|
|
stub_reactive_cache(cluster, connection_status: status)
|
|
end
|
|
|
|
it { is_expected.to eq(status) }
|
|
end
|
|
end
|
|
|
|
describe '#calculate_reactive_cache' do
|
|
subject { cluster.calculate_reactive_cache }
|
|
|
|
context 'cluster is disabled' do
|
|
let(:cluster) { create(:cluster, :disabled) }
|
|
|
|
it 'does not populate the cache' do
|
|
expect(cluster).not_to receive(:retrieve_connection_status)
|
|
|
|
is_expected.to be_nil
|
|
end
|
|
end
|
|
|
|
context 'cluster is enabled' do
|
|
let(:cluster) { create(:cluster, :provided_by_user, :group) }
|
|
|
|
context 'connection to the cluster is successful' do
|
|
before do
|
|
stub_kubeclient_discover(cluster.platform.api_url)
|
|
end
|
|
|
|
it { is_expected.to eq(connection_status: :connected) }
|
|
end
|
|
|
|
context 'cluster cannot be reached' do
|
|
before do
|
|
allow(cluster.kubeclient.core_client).to receive(:discover)
|
|
.and_raise(SocketError)
|
|
end
|
|
|
|
it { is_expected.to eq(connection_status: :unreachable) }
|
|
end
|
|
|
|
context 'cluster cannot be authenticated to' do
|
|
before do
|
|
allow(cluster.kubeclient.core_client).to receive(:discover)
|
|
.and_raise(OpenSSL::X509::CertificateError.new("Certificate error"))
|
|
end
|
|
|
|
it { is_expected.to eq(connection_status: :authentication_failure) }
|
|
end
|
|
|
|
describe 'Kubeclient::HttpError' do
|
|
let(:error_code) { 403 }
|
|
let(:error_message) { "Forbidden" }
|
|
|
|
before do
|
|
allow(cluster.kubeclient.core_client).to receive(:discover)
|
|
.and_raise(Kubeclient::HttpError.new(error_code, error_message, nil))
|
|
end
|
|
|
|
it { is_expected.to eq(connection_status: :authentication_failure) }
|
|
|
|
context 'generic timeout' do
|
|
let(:error_message) { 'Timed out connecting to server'}
|
|
|
|
it { is_expected.to eq(connection_status: :unreachable) }
|
|
end
|
|
|
|
context 'gateway timeout' do
|
|
let(:error_message) { '504 Gateway Timeout for GET https://kubernetes.example.com/api/v1'}
|
|
|
|
it { is_expected.to eq(connection_status: :unreachable) }
|
|
end
|
|
end
|
|
|
|
context 'an uncategorised error is raised' do
|
|
before do
|
|
allow(cluster.kubeclient.core_client).to receive(:discover)
|
|
.and_raise(StandardError)
|
|
end
|
|
|
|
it { is_expected.to eq(connection_status: :unknown_failure) }
|
|
|
|
it 'notifies Sentry' do
|
|
expect(Gitlab::Sentry).to receive(:track_acceptable_exception)
|
|
.with(instance_of(StandardError), hash_including(extra: { cluster_id: cluster.id }))
|
|
|
|
subject
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|