8db382b055
There are two cluster hierarchies one for the deployment platform and one for controllers. The main difference is that deployment platforms do not check user permissions and only return the first match.
666 lines
19 KiB
Ruby
666 lines
19 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
describe Clusters::Cluster do
|
|
it_behaves_like 'having unique enum values'
|
|
|
|
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(:kubernetes_namespace) }
|
|
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(:status_name).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 }
|
|
|
|
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 '#created?' do
|
|
let(:cluster) { create(:cluster, :provided_by_gcp) }
|
|
|
|
subject { cluster.created? }
|
|
|
|
context 'when status_name is :created' do
|
|
before do
|
|
allow(cluster).to receive_message_chain(:provider, :status_name).and_return(:created)
|
|
end
|
|
|
|
it { is_expected.to eq(true) }
|
|
end
|
|
|
|
context 'when status_name is not :created' do
|
|
before do
|
|
allow(cluster).to receive_message_chain(:provider, :status_name).and_return(:creating)
|
|
end
|
|
|
|
it { is_expected.to eq(false) }
|
|
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
|
|
context 'with a project 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')
|
|
|
|
it { is_expected.to eq('global_domain.com') }
|
|
end
|
|
end
|
|
|
|
context 'with domain set on ProjectAutoDevops' do
|
|
before do
|
|
auto_devops = project.build_auto_devops(domain: 'legacy-ado-domain.com')
|
|
auto_devops.save
|
|
end
|
|
|
|
it { is_expected.to eq('legacy-ado-domain.com') }
|
|
end
|
|
|
|
context 'with domain set as environment variable on project' do
|
|
before do
|
|
variable = project.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'project-ado-domain.com')
|
|
variable.save
|
|
end
|
|
|
|
it { is_expected.to eq('project-ado-domain.com') }
|
|
end
|
|
|
|
context 'with domain set as environment variable on the group project' do
|
|
let(:group) { create(:group) }
|
|
|
|
before do
|
|
project.update(parent_id: group.id)
|
|
variable = group.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'group-ado-domain.com')
|
|
variable.save
|
|
end
|
|
|
|
it { is_expected.to eq('group-ado-domain.com') }
|
|
end
|
|
end
|
|
|
|
context 'with a group cluster' do
|
|
let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
|
|
|
|
context 'with domain set as environment variable for the group' do
|
|
let(:group) { cluster.group }
|
|
|
|
before do
|
|
variable = group.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'group-ado-domain.com')
|
|
variable.save
|
|
end
|
|
|
|
it { is_expected.to eq('group-ado-domain.com') }
|
|
end
|
|
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
|
|
end
|