debian-mirror-gitlab/spec/models/clusters/cluster_spec.rb

1379 lines
40 KiB
Ruby
Raw Permalink Normal View History

2018-12-13 13:39:08 +05:30
# frozen_string_literal: true
2018-03-17 18:26:18 +05:30
require 'spec_helper'
2023-04-23 21:23:45 +05:30
RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching,
2023-07-09 08:55:56 +05:30
feature_category: :deployment_management do
2019-09-04 21:01:54 +05:30
include ReactiveCachingHelpers
include KubernetesHelpers
2019-02-15 15:39:39 +05:30
it_behaves_like 'having unique enum values'
2023-04-23 21:23:45 +05:30
subject(:cluster) { build(:cluster) }
2019-09-04 21:01:54 +05:30
2020-07-28 23:09:34 +05:30
it { is_expected.to include_module(HasEnvironmentScope) }
2018-03-17 18:26:18 +05:30
it { is_expected.to belong_to(:user) }
2019-12-21 20:55:43 +05:30
it { is_expected.to belong_to(:management_project).class_name('::Project') }
2018-12-13 13:39:08 +05:30
it { is_expected.to have_many(:cluster_projects) }
2018-03-17 18:26:18 +05:30
it { is_expected.to have_many(:projects) }
2018-12-13 13:39:08 +05:30
it { is_expected.to have_many(:cluster_groups) }
it { is_expected.to have_many(:groups) }
2020-01-01 13:55:28 +05:30
it { is_expected.to have_many(:groups_projects) }
2018-03-17 18:26:18 +05:30
it { is_expected.to have_one(:provider_gcp) }
2019-12-21 20:55:43 +05:30
it { is_expected.to have_one(:provider_aws) }
2018-03-17 18:26:18 +05:30
it { is_expected.to have_one(:platform_kubernetes) }
2021-04-29 21:17:54 +05:30
it { is_expected.to have_one(:integration_prometheus) }
2018-12-13 13:39:08 +05:30
it { is_expected.to have_many(:kubernetes_namespaces) }
it { is_expected.to have_one(:cluster_project) }
2020-03-13 15:44:24 +05:30
it { is_expected.to have_many(:deployment_clusters) }
2020-04-22 19:07:51 +05:30
it { is_expected.to have_many(:metrics_dashboard_annotations) }
2020-06-23 00:09:42 +05:30
it { is_expected.to have_many(:successful_deployments) }
it { is_expected.to have_many(:environments).through(:deployments) }
2018-12-13 13:39:08 +05:30
2018-03-17 18:26:18 +05:30
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
2018-12-13 13:39:08 +05:30
2018-03-17 18:26:18 +05:30
it { is_expected.to respond_to :project }
2021-01-03 14:25:43 +05:30
it { is_expected.to be_namespace_per_environment }
2018-03-17 18:26:18 +05:30
2023-01-13 00:05:48 +05:30
describe 'default values' do
it { expect(subject.helm_major_version).to eq(3) }
end
2022-07-23 23:45:48 +05:30
it_behaves_like 'it has loose foreign keys' do
let(:factory_name) { :cluster }
end
2018-03-17 18:26:18 +05:30
describe '.enabled' do
subject { described_class.enabled }
let!(:cluster) { create(:cluster, enabled: true) }
before do
2019-12-26 22:10:19 +05:30
create(:cluster, :disabled)
2018-03-17 18:26:18 +05:30
end
it { is_expected.to contain_exactly(cluster) }
end
describe '.disabled' do
subject { described_class.disabled }
2019-12-26 22:10:19 +05:30
let!(:cluster) { create(:cluster, :disabled) }
2018-03-17 18:26:18 +05:30
before do
create(:cluster, enabled: true)
end
it { is_expected.to contain_exactly(cluster) }
end
2018-05-09 12:01:36 +05:30
describe '.user_provided' do
subject { described_class.user_provided }
2019-12-26 22:10:19 +05:30
let!(:cluster) { create(:cluster_platform_kubernetes).cluster }
2018-05-09 12:01:36 +05:30
before do
2019-12-26 22:10:19 +05:30
create(:cluster_provider_gcp, :created)
2018-05-09 12:01:36 +05:30
end
it { is_expected.to contain_exactly(cluster) }
end
describe '.gcp_provided' do
subject { described_class.gcp_provided }
2019-12-26 22:10:19 +05:30
let!(:cluster) { create(:cluster_provider_gcp, :created).cluster }
2018-05-09 12:01:36 +05:30
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 }
2019-12-26 22:10:19 +05:30
let!(:cluster) { create(:cluster_provider_gcp, :created).cluster }
2018-05-09 12:01:36 +05:30
before do
create(:cluster, :providing_by_gcp)
end
it { is_expected.to contain_exactly(cluster) }
end
2019-12-21 20:55:43 +05:30
describe '.aws_provided' do
subject { described_class.aws_provided }
2019-12-26 22:10:19 +05:30
let!(:cluster) { create(:cluster_provider_aws, :created).cluster }
2019-12-21 20:55:43 +05:30
before do
create(:cluster, :provided_by_user)
end
it { is_expected.to contain_exactly(cluster) }
end
describe '.aws_installed' do
subject { described_class.aws_installed }
2019-12-26 22:10:19 +05:30
let!(:cluster) { create(:cluster_provider_aws, :created).cluster }
2019-12-21 20:55:43 +05:30
before do
2019-12-26 22:10:19 +05:30
errored_provider = create(:cluster_provider_aws)
errored_provider.make_errored!("Error message")
2019-12-21 20:55:43 +05:30
end
it { is_expected.to contain_exactly(cluster) }
end
2019-07-31 22:56:46 +05:30
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
2020-04-22 19:07:51 +05:30
describe '.with_management_project' do
subject { described_class.with_management_project }
context 'cluster has a management project' do
let!(:cluster) { create(:cluster, :management_project) }
it { is_expected.to include(cluster) }
end
context 'cluster does not have a management project' do
let!(:cluster) { create(:cluster) }
it { is_expected.not_to include(cluster) }
end
end
2021-12-11 22:18:48 +05:30
describe '.with_integration_prometheus' do
subject { described_class.with_integration_prometheus }
2020-06-23 00:09:42 +05:30
let!(:cluster) { create(:cluster) }
context 'cluster has prometheus application' do
2021-12-11 22:18:48 +05:30
let!(:application) { create(:clusters_integrations_prometheus, cluster: cluster) }
2020-06-23 00:09:42 +05:30
it { is_expected.to include(cluster) }
end
context 'cluster does not have prometheus application' do
let(:cluster) { create(:cluster) }
it { is_expected.not_to include(cluster) }
end
end
describe '.distinct_with_deployed_environments' do
subject { described_class.distinct_with_deployed_environments }
let_it_be(:cluster) { create(:cluster) }
context 'cluster has multiple successful deployment with environment' do
let!(:environment) { create(:environment) }
let!(:deployment) { create(:deployment, :success, cluster: cluster, environment: environment) }
let!(:deployment_2) { create(:deployment, :success, cluster: cluster, environment: environment) }
it { is_expected.to include(cluster) }
it 'lists only distinct environments' do
expect(subject.first.environments.count).to eq(1)
end
end
context 'cluster has only failed deployment with environment' do
let!(:environment) { create(:environment) }
let!(:deployment) { create(:deployment, :failed, cluster: cluster, environment: environment) }
it { is_expected.not_to include(cluster) }
end
context 'cluster does not have any deployment' do
it { is_expected.not_to include(cluster) }
end
end
2021-02-22 17:27:13 +05:30
describe '.with_project_http_integrations' do
subject { described_class.with_project_http_integrations(project_id) }
2020-06-23 00:09:42 +05:30
let!(:cluster) { create(:cluster, :project) }
let!(:project_id) { cluster.first_project.id }
context 'project has alert service data' do
2021-02-22 17:27:13 +05:30
let!(:integration) { create(:alert_management_http_integration, project: cluster.clusterable) }
2020-06-23 00:09:42 +05:30
it { is_expected.to include(cluster) }
end
context 'project has no alert service data' do
it { is_expected.not_to include(cluster) }
end
end
2019-12-26 22:10:19 +05:30
describe '.for_project_namespace' do
subject { described_class.for_project_namespace(namespace_id) }
let!(:cluster) { create(:cluster, :project) }
let!(:another_cluster) { create(:cluster, :project) }
let(:namespace_id) { cluster.first_project.namespace_id }
it { is_expected.to contain_exactly(cluster) }
end
2021-11-11 11:23:49 +05:30
describe '.with_name' do
subject { described_class.with_name(name) }
let(:name) { 'this-cluster' }
let!(:cluster) { create(:cluster, :project, name: name) }
let!(:another_cluster) { create(:cluster, :project) }
it { is_expected.to contain_exactly(cluster) }
end
2019-03-02 22:35:43 +05:30
describe 'validations' do
2018-03-17 18:26:18 +05:30
subject { cluster.valid? }
2020-07-28 23:09:34 +05:30
context 'when validates unique_environment_scope' do
context 'for a project cluster' do
let(:project) { create(:project) }
before do
create(:cluster, projects: [project], environment_scope: 'product/*')
end
context 'when identical environment scope exists in project' do
let(:cluster) { build(:cluster, projects: [project], environment_scope: 'product/*') }
it { is_expected.to be_falsey }
end
context 'when identical environment scope does not exist in project' do
let(:cluster) { build(:cluster, projects: [project], environment_scope: '*') }
it { is_expected.to be_truthy }
end
context 'when identical environment scope exists in different project' do
let(:project2) { create(:project) }
let(:cluster) { build(:cluster, projects: [project2], environment_scope: 'product/*') }
it { is_expected.to be_truthy }
end
end
context 'for a group cluster' do
let(:group) { create(:group) }
before do
create(:cluster, cluster_type: :group_type, groups: [group], environment_scope: 'product/*')
end
context 'when identical environment scope exists in group' do
let(:cluster) { build(:cluster, cluster_type: :group_type, groups: [group], environment_scope: 'product/*') }
it { is_expected.to be_falsey }
end
context 'when identical environment scope does not exist in group' do
let(:cluster) { build(:cluster, cluster_type: :group_type, groups: [group], environment_scope: '*') }
it { is_expected.to be_truthy }
end
context 'when identical environment scope exists in different group' do
let(:cluster) { build(:cluster, :group, environment_scope: 'product/*') }
it { is_expected.to be_truthy }
end
end
context 'for an instance cluster' do
before do
create(:cluster, :instance, environment_scope: 'product/*')
end
context 'identical environment scope exists' do
let(:cluster) { build(:cluster, :instance, environment_scope: 'product/*') }
it { is_expected.to be_falsey }
end
context 'identical environment scope does not exist' do
let(:cluster) { build(:cluster, :instance, environment_scope: '*') }
it { is_expected.to be_truthy }
end
end
end
2018-03-17 18:26:18 +05:30
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
2018-12-13 13:39:08 +05:30
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
2019-03-02 22:35:43 +05:30
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') }
2019-07-07 11:18:12 +05:30
it 'adds an error on domain' do
2019-03-02 22:35:43 +05:30
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
2019-12-21 20:55:43 +05:30
describe 'unique scope for management_project' do
let(:project) { create(:project) }
let!(:cluster_with_management_project) { create(:cluster, management_project: project) }
context 'duplicate scopes for the same management project' do
let(:cluster) { build(:cluster, management_project: project) }
it 'adds an error on environment_scope' do
expect(cluster).not_to be_valid
expect(cluster.errors[:environment_scope].first).to eq('cannot add duplicated environment scope')
end
end
end
2021-01-29 00:20:46 +05:30
describe 'helm_major_version can only be 2 or 3' do
using RSpec::Parameterized::TableSyntax
where(:helm_major_version, :expect_valid) do
2 | true
3 | true
4 | false
-1 | false
end
with_them do
let(:cluster) { build(:cluster, helm_major_version: helm_major_version) }
it { is_expected.to eq(expect_valid) }
end
end
end
it 'has default helm_major_version 3' do
expect(create(:cluster).helm_major_version).to eq(3)
2018-03-17 18:26:18 +05:30
end
2019-02-15 15:39:39 +05:30
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
2019-07-31 22:56:46 +05:30
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
2019-10-12 21:52:04 +05:30
context 'when sub-group has configured kubernetes cluster' do
2019-02-15 15:39:39 +05:30
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
2018-03-17 18:26:18 +05:30
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)
2019-12-21 20:55:43 +05:30
end
end
context 'when provider is aws' do
let(:cluster) { create(:cluster, :provided_by_aws) }
it 'returns a provider' do
is_expected.to eq(cluster.provider_aws)
2018-03-17 18:26:18 +05:30
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 '#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
2018-12-13 13:39:08 +05:30
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
2019-09-04 21:01:54 +05:30
describe '#allow_user_defined_namespace?' do
subject { cluster.allow_user_defined_namespace? }
2018-03-17 18:26:18 +05:30
2019-09-04 21:01:54 +05:30
context 'project type cluster' do
context 'gitlab managed' do
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :provided_by_gcp) }
2019-09-04 21:01:54 +05:30
it { is_expected.to be_truthy }
2018-03-17 18:26:18 +05:30
end
2019-09-04 21:01:54 +05:30
context 'not managed' do
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :provided_by_gcp, managed: false) }
2018-03-17 18:26:18 +05:30
2019-09-04 21:01:54 +05:30
it { is_expected.to be_truthy }
2018-03-17 18:26:18 +05:30
end
end
2019-02-15 15:39:39 +05:30
2019-09-04 21:01:54 +05:30
context 'group type cluster' do
context 'gitlab managed' do
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :provided_by_gcp, :group) }
2019-02-15 15:39:39 +05:30
2019-09-04 21:01:54 +05:30
it { is_expected.to be_falsey }
end
2019-02-15 15:39:39 +05:30
2019-09-04 21:01:54 +05:30
context 'not managed' do
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :provided_by_gcp, :group, managed: false) }
2019-02-15 15:39:39 +05:30
2019-09-04 21:01:54 +05:30
it { is_expected.to be_truthy }
end
2019-02-15 15:39:39 +05:30
end
context 'instance type cluster' do
2019-09-04 21:01:54 +05:30
context 'gitlab managed' do
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :provided_by_gcp, :instance) }
2019-02-15 15:39:39 +05:30
2019-09-04 21:01:54 +05:30
it { is_expected.to be_falsey }
end
context 'not managed' do
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :provided_by_gcp, :instance, managed: false) }
2019-09-04 21:01:54 +05:30
it { is_expected.to be_truthy }
end
2019-02-15 15:39:39 +05:30
end
end
2019-03-02 22:35:43 +05:30
2020-01-01 13:55:28 +05:30
describe '#all_projects' do
context 'cluster_type is project_type' do
let(:project) { create(:project) }
2023-06-20 00:43:36 +05:30
let(:cluster) { create(:cluster, projects: [project]) }
2020-01-01 13:55:28 +05:30
it 'returns projects' do
expect(cluster.all_projects).to match_array [project]
end
end
context 'cluster_type is group_type' do
let(:group) { create(:group) }
let!(:project) { create(:project, group: group) }
2023-06-20 00:43:36 +05:30
let(:cluster) { create(:cluster_for_group, groups: [group]) }
2020-01-01 13:55:28 +05:30
it 'returns group projects' do
expect(cluster.all_projects.ids).to match_array [project.id]
end
end
context 'cluster_type is instance_type' do
let!(:project) { create(:project) }
let(:cluster) { create(:cluster, :instance) }
it "returns all instance's projects" do
expect(cluster.all_projects.ids).to match_array [project.id]
end
end
end
2019-03-02 22:35:43 +05:30
describe '#kube_ingress_domain' do
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :provided_by_gcp) }
2019-03-02 22:35:43 +05:30
subject { cluster.kube_ingress_domain }
context 'with domain set in cluster' do
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :provided_by_gcp, :with_domain) }
2019-03-02 22:35:43 +05:30
it { is_expected.to eq(cluster.domain) }
end
context 'with no domain on cluster' do
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :project, :provided_by_gcp) }
2019-09-04 21:01:54 +05:30
let(:project) { cluster.project }
2019-03-02 22:35:43 +05:30
2019-09-04 21:01:54 +05:30
context 'with domain set at instance level' do
before do
stub_application_setting(auto_devops_domain: 'global_domain.com')
2019-03-02 22:35:43 +05:30
end
2019-09-04 21:01:54 +05:30
it { is_expected.to eq('global_domain.com') }
end
end
end
2019-03-02 22:35:43 +05:30
2019-10-12 21:52:04 +05:30
describe '#kubernetes_namespace_for' do
2020-04-08 14:13:33 +05:30
subject { cluster.kubernetes_namespace_for(environment, deployable: build) }
2019-03-02 22:35:43 +05:30
2020-04-08 14:13:33 +05:30
let(:environment_name) { 'the-environment-name' }
2021-11-11 11:23:49 +05:30
let(:environment) { create(:environment, name: environment_name, project: cluster.project) }
let(:build) { create(:ci_build, environment: environment, project: cluster.project) }
2020-04-08 14:13:33 +05:30
let(:cluster) { create(:cluster, :project, managed: managed_cluster) }
let(:managed_cluster) { true }
let(:default_namespace) { Gitlab::Kubernetes::DefaultNamespace.new(cluster, project: cluster.project).from_environment_slug(environment.slug) }
let(:build_options) { {} }
2019-03-02 22:35:43 +05:30
2020-04-08 14:13:33 +05:30
it 'validates the project id' do
environment.project_id = build.project_id + 1
expect { subject }.to raise_error ArgumentError, 'environment.project_id must match deployable.project_id'
2019-10-12 21:52:04 +05:30
end
2019-03-02 22:35:43 +05:30
2020-04-08 14:13:33 +05:30
context 'when environment has no last_deployable' do
let(:build) { nil }
2020-01-01 13:55:28 +05:30
2020-04-08 14:13:33 +05:30
it { is_expected.to eq default_namespace }
end
2019-09-04 21:01:54 +05:30
2020-04-08 14:13:33 +05:30
context 'when cluster is managed' do
2019-10-12 21:52:04 +05:30
before do
2020-04-08 14:13:33 +05:30
build.options = { environment: { kubernetes: { namespace: 'ci yaml namespace' } } }
2019-03-02 22:35:43 +05:30
end
2019-10-12 21:52:04 +05:30
2020-04-08 14:13:33 +05:30
it 'returns the cached namespace if present, ignoring CI config' do
cached_namespace = create(:cluster_kubernetes_namespace, cluster: cluster, environment: environment, namespace: 'the name', service_account_token: 'some token')
expect(subject).to eq cached_namespace.namespace
end
2020-01-01 13:55:28 +05:30
2020-04-08 14:13:33 +05:30
it 'returns the default namespace when no cached namespace, ignoring CI config' do
expect(subject).to eq default_namespace
end
2020-01-01 13:55:28 +05:30
end
2020-04-08 14:13:33 +05:30
context 'when cluster is not managed' do
let(:managed_cluster) { false }
2020-01-01 13:55:28 +05:30
2020-04-08 14:13:33 +05:30
it 'returns the cached namespace if present, regardless of CI config' do
cached_namespace = create(:cluster_kubernetes_namespace, cluster: cluster, environment: environment, namespace: 'the name', service_account_token: 'some token')
build.options = { environment: { kubernetes: { namespace: 'ci yaml namespace' } } }
expect(subject).to eq cached_namespace.namespace
end
2020-01-01 13:55:28 +05:30
2020-04-08 14:13:33 +05:30
it 'returns the CI YAML namespace when configured' do
build.options = { environment: { kubernetes: { namespace: 'ci yaml namespace' } } }
expect(subject).to eq 'ci yaml namespace'
end
2020-01-01 13:55:28 +05:30
2020-04-08 14:13:33 +05:30
it 'returns the default namespace when no namespace is configured' do
expect(subject).to eq default_namespace
end
2020-01-01 13:55:28 +05:30
end
2019-03-02 22:35:43 +05:30
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
2019-07-07 11:18:12 +05:30
it 'includes KUBE_INGRESS_BASE_DOMAIN' do
2019-03-02 22:35:43 +05:30
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') }
2019-07-07 11:18:12 +05:30
it 'includes KUBE_INGRESS_BASE_DOMAIN' do
2019-03-02 22:35:43 +05:30
expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'example.com')
end
end
context 'with no domain' do
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :provided_by_gcp, :project) }
2019-03-02 22:35:43 +05:30
2019-07-07 11:18:12 +05:30
it 'returns an empty array' do
2019-03-02 22:35:43 +05:30
expect(subject.to_hash).to be_empty
end
end
end
2019-07-07 11:18:12 +05:30
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
2019-09-04 21:01:54 +05:30
describe '#status_name' do
subject { cluster.status_name }
context 'the cluster has a provider' do
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :provided_by_gcp) }
2019-12-26 22:10:19 +05:30
let(:provider_status) { :errored }
2019-09-04 21:01:54 +05:30
before do
cluster.provider.make_errored!
end
2019-12-26 22:10:19 +05:30
it { is_expected.to eq provider_status }
context 'when cluster cleanup is ongoing' do
using RSpec::Parameterized::TableSyntax
where(:status_name, :cleanup_status) do
provider_status | :cleanup_not_started
:cleanup_ongoing | :cleanup_removing_project_namespaces
:cleanup_ongoing | :cleanup_removing_service_account
:cleanup_errored | :cleanup_errored
end
with_them do
it 'returns cleanup_ongoing when uninstalling applications' do
cluster.cleanup_status = described_class
.state_machines[:cleanup_status]
.states[cleanup_status]
.value
is_expected.to eq status_name
end
end
end
2019-09-04 21:01:54 +05:30
end
context 'there is a cached connection status' do
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :provided_by_user) }
2019-09-04 21:01:54 +05:30
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
2020-04-08 14:13:33 +05:30
let(:cluster) { build(:cluster, :provided_by_user) }
2019-09-04 21:01:54 +05:30
before do
allow(cluster).to receive(:connection_status).and_return(nil)
end
it { is_expected.to eq :created }
end
end
2019-12-26 22:10:19 +05:30
describe 'cleanup_status state_machine' do
shared_examples 'cleanup_status transition' do
let(:cluster) { create(:cluster, from_state) }
it 'transitions cleanup_status correctly' do
expect { subject }.to change { cluster.cleanup_status_name }
.from(from_state).to(to_state)
end
it 'schedules a Clusters::Cleanup::*Worker' do
expect(expected_worker_class).to receive(:perform_async).with(cluster.id)
subject
end
end
describe '#start_cleanup!' do
2021-09-04 01:27:46 +05:30
let(:expected_worker_class) { Clusters::Cleanup::ProjectNamespaceWorker }
let(:to_state) { :cleanup_removing_project_namespaces }
2019-12-26 22:10:19 +05:30
subject { cluster.start_cleanup! }
context 'when cleanup_status is cleanup_not_started' do
let(:from_state) { :cleanup_not_started }
it_behaves_like 'cleanup_status transition'
end
context 'when cleanup_status is errored' do
let(:from_state) { :cleanup_errored }
it_behaves_like 'cleanup_status transition'
end
end
describe '#make_cleanup_errored!' do
2020-05-24 23:13:21 +05:30
non_errored_states = Clusters::Cluster.state_machines[:cleanup_status].states.keys - [:cleanup_errored]
2019-12-26 22:10:19 +05:30
2020-05-24 23:13:21 +05:30
non_errored_states.each do |state|
2019-12-26 22:10:19 +05:30
it "transitions cleanup_status from #{state} to cleanup_errored" do
cluster = create(:cluster, state)
expect { cluster.make_cleanup_errored! }.to change { cluster.cleanup_status_name }
.from(state).to(:cleanup_errored)
end
it "sets error message" do
cluster = create(:cluster, state)
expect { cluster.make_cleanup_errored!("Error Message") }.to change { cluster.cleanup_status_reason }
.from(nil).to("Error Message")
end
end
end
describe '#continue_cleanup!' do
2021-09-04 01:27:46 +05:30
let(:expected_worker_class) { Clusters::Cleanup::ServiceAccountWorker }
let(:from_state) { :cleanup_removing_project_namespaces }
let(:to_state) { :cleanup_removing_service_account }
2019-12-26 22:10:19 +05:30
2021-09-04 01:27:46 +05:30
subject { cluster.continue_cleanup! }
2019-12-26 22:10:19 +05:30
2021-09-04 01:27:46 +05:30
it_behaves_like 'cleanup_status transition'
2019-12-26 22:10:19 +05:30
end
end
2019-09-04 21:01:54 +05:30
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
2020-10-24 23:57:45 +05:30
describe '#connection_error' do
let(:cluster) { create(:cluster) }
let(:error) { :unknown_error }
subject { cluster.connection_error }
it { is_expected.to be_nil }
context 'with a cached status' do
before do
stub_reactive_cache(cluster, connection_error: error)
end
it { is_expected.to eq(error) }
end
end
describe '#node_connection_error' do
let(:cluster) { create(:cluster) }
let(:error) { :unknown_error }
subject { cluster.node_connection_error }
it { is_expected.to be_nil }
context 'with a cached status' do
before do
stub_reactive_cache(cluster, node_connection_error: error)
end
it { is_expected.to eq(error) }
end
end
describe '#metrics_connection_error' do
let(:cluster) { create(:cluster) }
let(:error) { :unknown_error }
subject { cluster.metrics_connection_error }
it { is_expected.to be_nil }
context 'with a cached status' do
before do
stub_reactive_cache(cluster, metrics_connection_error: error)
end
it { is_expected.to eq(error) }
end
end
2020-05-24 23:13:21 +05:30
describe '#nodes' do
let(:cluster) { create(:cluster) }
subject { cluster.nodes }
it { is_expected.to be_nil }
context 'with a cached status' do
before do
stub_reactive_cache(cluster, nodes: [kube_node])
end
it { is_expected.to eq([kube_node]) }
end
end
2019-09-04 21:01:54 +05:30
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)
2020-05-24 23:13:21 +05:30
expect(cluster).not_to receive(:retrieve_nodes)
2019-09-04 21:01:54 +05:30
is_expected.to be_nil
end
end
context 'cluster is enabled' do
let(:cluster) { create(:cluster, :provided_by_user, :group) }
2020-07-28 23:09:34 +05:30
let(:gl_k8s_node_double) { double(Gitlab::Kubernetes::Node) }
2020-10-24 23:57:45 +05:30
let(:expected_nodes) { {} }
2019-09-04 21:01:54 +05:30
2020-05-24 23:13:21 +05:30
before do
2020-07-28 23:09:34 +05:30
stub_kubeclient_discover(cluster.platform.api_url)
allow(Gitlab::Kubernetes::Node).to receive(:new).with(cluster).and_return(gl_k8s_node_double)
2020-10-24 23:57:45 +05:30
allow(gl_k8s_node_double).to receive(:all).and_return(expected_nodes)
2020-05-24 23:13:21 +05:30
end
2019-09-04 21:01:54 +05:30
2020-05-24 23:13:21 +05:30
context 'connection to the cluster is successful' do
2020-10-24 23:57:45 +05:30
let(:expected_nodes) { { nodes: [kube_node.merge(kube_node_metrics)] } }
let(:connection_status) { { connection_status: :connected } }
2020-07-28 23:09:34 +05:30
before do
allow(gl_k8s_node_double).to receive(:all).and_return(expected_nodes)
end
2020-10-24 23:57:45 +05:30
it { is_expected.to eq(**connection_status, **expected_nodes) }
2019-09-04 21:01:54 +05:30
end
context 'cluster cannot be reached' do
2020-10-24 23:57:45 +05:30
let(:connection_status) { { connection_status: :unreachable, connection_error: :connection_error } }
2019-09-04 21:01:54 +05:30
before do
allow(cluster.kubeclient.core_client).to receive(:discover)
.and_raise(SocketError)
end
2020-10-24 23:57:45 +05:30
it { is_expected.to eq(**connection_status, **expected_nodes) }
2019-09-04 21:01:54 +05:30
end
context 'cluster cannot be authenticated to' do
2020-10-24 23:57:45 +05:30
let(:connection_status) { { connection_status: :authentication_failure, connection_error: :authentication_error } }
2019-09-04 21:01:54 +05:30
before do
allow(cluster.kubeclient.core_client).to receive(:discover)
.and_raise(OpenSSL::X509::CertificateError.new("Certificate error"))
end
2020-10-24 23:57:45 +05:30
it { is_expected.to eq(**connection_status, **expected_nodes) }
2019-09-04 21:01:54 +05:30
end
describe 'Kubeclient::HttpError' do
2020-10-24 23:57:45 +05:30
let(:connection_status) { { connection_status: :authentication_failure, connection_error: :http_error } }
2019-09-04 21:01:54 +05:30
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
2020-10-24 23:57:45 +05:30
it { is_expected.to eq(**connection_status, **expected_nodes) }
2019-09-04 21:01:54 +05:30
context 'generic timeout' do
2020-10-24 23:57:45 +05:30
let(:connection_status) { { connection_status: :unreachable, connection_error: :http_error } }
2022-08-27 11:52:29 +05:30
let(:error_message) { 'Timed out connecting to server' }
2019-09-04 21:01:54 +05:30
2020-10-24 23:57:45 +05:30
it { is_expected.to eq(**connection_status, **expected_nodes) }
2019-09-04 21:01:54 +05:30
end
context 'gateway timeout' do
2020-10-24 23:57:45 +05:30
let(:connection_status) { { connection_status: :unreachable, connection_error: :http_error } }
2022-08-27 11:52:29 +05:30
let(:error_message) { '504 Gateway Timeout for GET https://kubernetes.example.com/api/v1' }
2019-09-04 21:01:54 +05:30
2020-10-24 23:57:45 +05:30
it { is_expected.to eq(**connection_status, **expected_nodes) }
2019-09-04 21:01:54 +05:30
end
end
context 'an uncategorised error is raised' do
2020-10-24 23:57:45 +05:30
let(:connection_status) { { connection_status: :unknown_failure, connection_error: :unknown_error } }
2019-09-04 21:01:54 +05:30
before do
allow(cluster.kubeclient.core_client).to receive(:discover)
.and_raise(StandardError)
end
2020-10-24 23:57:45 +05:30
it { is_expected.to eq(**connection_status, **expected_nodes) }
2019-09-04 21:01:54 +05:30
it 'notifies Sentry' do
2020-01-01 13:55:28 +05:30
expect(Gitlab::ErrorTracking).to receive(:track_exception)
.with(instance_of(StandardError), hash_including(cluster_id: cluster.id))
2020-07-28 23:09:34 +05:30
.once
2019-09-04 21:01:54 +05:30
subject
end
end
end
end
2020-01-01 13:55:28 +05:30
2021-09-04 01:27:46 +05:30
describe '#integration_prometheus_available?' do
2021-04-29 21:17:54 +05:30
let_it_be_with_reload(:cluster) { create(:cluster, :project) }
2021-09-04 01:27:46 +05:30
subject { cluster.integration_prometheus_available? }
2021-04-29 21:17:54 +05:30
it { is_expected.to be_falsey }
2021-09-04 01:27:46 +05:30
context 'when integration is enabled' do
let!(:integration) { create(:clusters_integrations_prometheus, cluster: cluster) }
2021-04-29 21:17:54 +05:30
it { is_expected.to be_truthy }
end
2021-09-04 01:27:46 +05:30
context 'when integration is disabled' do
let!(:integration) { create(:clusters_integrations_prometheus, enabled: false, cluster: cluster) }
2021-04-29 21:17:54 +05:30
2021-09-04 01:27:46 +05:30
it { is_expected.to be_falsey }
2021-04-29 21:17:54 +05:30
end
end
describe '#prometheus_adapter' do
let_it_be_with_reload(:cluster) { create(:cluster, :project) }
it 'returns nothing' do
expect(cluster.prometheus_adapter).to be_nil
end
2021-09-04 01:27:46 +05:30
context 'has integration_prometheus' do
2021-04-29 21:17:54 +05:30
let_it_be(:integration) { create(:clusters_integrations_prometheus, cluster: cluster) }
it 'returns the integration' do
expect(cluster.prometheus_adapter).to eq(integration)
end
end
end
2020-01-01 13:55:28 +05:30
describe '#delete_cached_resources!' do
let!(:cluster) { create(:cluster, :project) }
let!(:staging_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, namespace: 'staging') }
let!(:production_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, namespace: 'production') }
subject { cluster.delete_cached_resources! }
it 'deletes associated namespace records' do
expect(cluster.kubernetes_namespaces).to match_array([staging_namespace, production_namespace])
subject
expect(cluster.kubernetes_namespaces).to be_empty
end
end
describe '#clusterable' do
subject { cluster.clusterable }
context 'project type' do
let(:cluster) { create(:cluster, :project) }
it { is_expected.to eq(cluster.project) }
end
context 'group type' do
let(:cluster) { create(:cluster, :group) }
it { is_expected.to eq(cluster.group) }
end
context 'instance type' do
let(:cluster) { create(:cluster, :instance) }
it { is_expected.to be_a(Clusters::Instance) }
end
context 'unknown type' do
let(:cluster) { create(:cluster, :project) }
before do
allow(cluster).to receive(:cluster_type).and_return('unknown_type')
end
it 'raises NotImplementedError' do
expect { subject }.to raise_error(NotImplementedError)
end
end
end
2023-04-23 21:23:45 +05:30
describe '#on_creation?' do
subject(:on_creation?) { cluster.on_creation? }
before do
allow(cluster).to receive(:provider).and_return(provider)
end
context 'without provider' do
let(:provider) {}
it { is_expected.to eq(false) }
end
context 'with provider' do
let(:provider) { instance_double(Clusters::Providers::Gcp, on_creation?: on_creation?) }
before do
allow(cluster).to receive(:provider).and_return(provider)
end
context 'with on_creation? set to true' do
let(:on_creation?) { true }
it { is_expected.to eq(true) }
end
context 'with on_creation? set to false' do
let(:on_creation?) { false }
it { is_expected.to eq(false) }
end
end
end
describe '#platform_kubernetes_active?' do
subject(:platform_kubernetes_active?) { cluster.platform_kubernetes_active? }
before do
allow(cluster).to receive(:platform_kubernetes).and_return(platform_kubernetes)
end
context 'without platform_kubernetes' do
let(:platform_kubernetes) {}
it { is_expected.to eq(false) }
end
context 'with platform_kubernetes' do
let(:platform_kubernetes) { instance_double(Clusters::Platforms::Kubernetes, active?: active?) }
context 'with active? set to true' do
let(:active?) { true }
it { is_expected.to eq(true) }
end
context 'with active? set to false' do
let(:active?) { false }
it { is_expected.to eq(false) }
end
end
end
describe '#platform_kubernetes_rbac?' do
subject(:platform_kubernetes_rbac?) { cluster.platform_kubernetes_rbac? }
before do
allow(cluster).to receive(:platform_kubernetes).and_return(platform_kubernetes)
end
context 'without platform_kubernetes' do
let(:platform_kubernetes) {}
it { is_expected.to eq(false) }
end
context 'with platform_kubernetes' do
let(:platform_kubernetes) { instance_double(Clusters::Platforms::Kubernetes, rbac?: rbac?) }
context 'with rbac? set to true' do
let(:rbac?) { true }
it { is_expected.to eq(true) }
end
context 'with rbac? set to false' do
let(:rbac?) { false }
it { is_expected.to eq(false) }
end
end
end
2018-03-17 18:26:18 +05:30
end