diff options
Diffstat (limited to 'spec/models/clusters')
| -rw-r--r-- | spec/models/clusters/applications/cert_manager_spec.rb | 52 | ||||
| -rw-r--r-- | spec/models/clusters/applications/helm_spec.rb | 67 | ||||
| -rw-r--r-- | spec/models/clusters/applications/ingress_spec.rb | 16 | ||||
| -rw-r--r-- | spec/models/clusters/applications/jupyter_spec.rb | 2 | ||||
| -rw-r--r-- | spec/models/clusters/applications/knative_spec.rb | 48 | ||||
| -rw-r--r-- | spec/models/clusters/applications/prometheus_spec.rb | 17 | ||||
| -rw-r--r-- | spec/models/clusters/applications/runner_spec.rb | 33 | ||||
| -rw-r--r-- | spec/models/clusters/cluster_spec.rb | 124 | ||||
| -rw-r--r-- | spec/models/clusters/clusters_hierarchy_spec.rb | 73 | ||||
| -rw-r--r-- | spec/models/clusters/kubernetes_namespace_spec.rb | 84 | ||||
| -rw-r--r-- | spec/models/clusters/platforms/kubernetes_spec.rb | 247 |
11 files changed, 448 insertions, 315 deletions
diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb index 8d853a04e33..93050e80b07 100644 --- a/spec/models/clusters/applications/cert_manager_spec.rb +++ b/spec/models/clusters/applications/cert_manager_spec.rb @@ -3,17 +3,17 @@ require 'rails_helper' describe Clusters::Applications::CertManager do - let(:cert_manager) { create(:clusters_applications_cert_managers) } + let(:cert_manager) { create(:clusters_applications_cert_manager) } - include_examples 'cluster application core specs', :clusters_applications_cert_managers - include_examples 'cluster application status specs', :clusters_applications_cert_managers - include_examples 'cluster application version specs', :clusters_applications_cert_managers + include_examples 'cluster application core specs', :clusters_applications_cert_manager + include_examples 'cluster application status specs', :clusters_applications_cert_manager + include_examples 'cluster application version specs', :clusters_applications_cert_manager include_examples 'cluster application initial status specs' describe '#can_uninstall?' do subject { cert_manager.can_uninstall? } - it { is_expected.to be_falsey } + it { is_expected.to be_truthy } end describe '#install_command' do @@ -48,7 +48,7 @@ describe Clusters::Applications::CertManager do expect(subject.version).to eq('v0.5.2') expect(subject).to be_rbac expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file)) - expect(subject.postinstall).to eq(['/usr/bin/kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml']) + expect(subject.postinstall).to eq(['kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml']) end context 'for a specific user' do @@ -72,7 +72,7 @@ describe Clusters::Applications::CertManager do end context 'application failed to install previously' do - let(:cert_manager) { create(:clusters_applications_cert_managers, :errored, version: '0.0.1') } + let(:cert_manager) { create(:clusters_applications_cert_manager, :errored, version: '0.0.1') } it 'is initialized with the locked version' do expect(subject.version).to eq('v0.5.2') @@ -80,6 +80,44 @@ describe Clusters::Applications::CertManager do end end + describe '#uninstall_command' do + subject { cert_manager.uninstall_command } + + it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) } + + it 'is initialized with cert_manager arguments' do + expect(subject.name).to eq('certmanager') + expect(subject).to be_rbac + expect(subject.files).to eq(cert_manager.files) + end + + it 'specifies a post delete command to remove custom resource definitions' do + expect(subject.postdelete).to eq([ + "kubectl delete secret -n gitlab-managed-apps letsencrypt-prod --ignore-not-found", + 'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found', + 'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found', + 'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found' + ]) + end + + context 'secret key name is not found' do + before do + allow(File).to receive(:read).and_call_original + expect(File).to receive(:read) + .with(Rails.root.join('vendor', 'cert_manager', 'cluster_issuer.yaml')) + .and_return('key: value') + end + + it 'does not try and delete the secret' do + expect(subject.postdelete).to eq([ + 'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found', + 'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found', + 'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found' + ]) + end + end + end + describe '#files' do let(:application) { cert_manager } let(:values) { subject[:'values.yaml'] } diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb index 6ea6c110d62..00b5c72a3d3 100644 --- a/spec/models/clusters/applications/helm_spec.rb +++ b/spec/models/clusters/applications/helm_spec.rb @@ -19,11 +19,35 @@ describe Clusters::Applications::Helm do end describe '#can_uninstall?' do - let(:helm) { create(:clusters_applications_helm) } + context "with other existing applications" do + Clusters::Cluster::APPLICATIONS.keys.each do |application_name| + next if application_name == 'helm' + + it "is false when #{application_name} is installed" do + cluster_application = create("clusters_applications_#{application_name}".to_sym) + + helm = cluster_application.cluster.application_helm + + expect(helm.allowed_to_uninstall?).to be_falsy + end + end - subject { helm.can_uninstall? } + it 'executes a single query only' do + cluster_application = create(:clusters_applications_ingress) + helm = cluster_application.cluster.application_helm - it { is_expected.to be_falsey } + query_count = ActiveRecord::QueryRecorder.new { helm.allowed_to_uninstall? }.count + expect(query_count).to eq(1) + end + end + + context "without other existing applications" do + subject { helm.can_uninstall? } + + let(:helm) { create(:clusters_applications_helm) } + + it { is_expected.to be_truthy } + end end describe '#issue_client_cert' do @@ -73,4 +97,41 @@ describe Clusters::Applications::Helm do end end end + + describe '#uninstall_command' do + let(:helm) { create(:clusters_applications_helm) } + + subject { helm.uninstall_command } + + it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::ResetCommand) } + + it 'has name' do + expect(subject.name).to eq('helm') + end + + it 'has cert files' do + expect(subject.files[:'ca.pem']).to be_present + expect(subject.files[:'ca.pem']).to eq(helm.ca_cert) + + expect(subject.files[:'cert.pem']).to be_present + expect(subject.files[:'key.pem']).to be_present + + cert = OpenSSL::X509::Certificate.new(subject.files[:'cert.pem']) + expect(cert.not_after).to be > 999.years.from_now + end + + describe 'rbac' do + context 'rbac cluster' do + it { expect(subject).to be_rbac } + end + + context 'non rbac cluster' do + before do + helm.cluster.platform_kubernetes.abac! + end + + it { expect(subject).not_to be_rbac } + end + end + end end diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index 292ddabd2d8..057517d3820 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -21,7 +21,21 @@ describe Clusters::Applications::Ingress do describe '#can_uninstall?' do subject { ingress.can_uninstall? } - it { is_expected.to be_falsey } + it 'returns true if application_jupyter_nil_or_installable? AND external_ip_or_hostname? are true' do + ingress.external_ip = 'IP' + + is_expected.to be_truthy + end + + it 'returns false if application_jupyter_nil_or_installable? is false' do + create(:clusters_applications_jupyter, :installed, cluster: ingress.cluster) + + is_expected.to be_falsey + end + + it 'returns false if external_ip_or_hostname? is false' do + is_expected.to be_falsey + end end describe '#make_installed!' do diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb index 43fa1010b2b..3ff66a074e4 100644 --- a/spec/models/clusters/applications/jupyter_spec.rb +++ b/spec/models/clusters/applications/jupyter_spec.rb @@ -16,7 +16,7 @@ describe Clusters::Applications::Jupyter do subject { jupyter.can_uninstall? } - it { is_expected.to be_falsey } + it { is_expected.to be_truthy } end describe '#set_initial_status' do diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index b38cf96de7e..334f10526cb 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -39,7 +39,7 @@ describe Clusters::Applications::Knative do describe '#can_uninstall?' do subject { knative.can_uninstall? } - it { is_expected.to be_falsey } + it { is_expected.to be_truthy } end describe '#schedule_status_update with external_ip' do @@ -91,7 +91,7 @@ describe Clusters::Applications::Knative do end it 'does not install metrics for prometheus' do - expect(subject.postinstall).to be_nil + expect(subject.postinstall).to be_empty end context 'with prometheus installed' do @@ -101,7 +101,7 @@ describe Clusters::Applications::Knative do subject { knative.install_command } it 'installs metrics' do - expect(subject.postinstall).not_to be_nil + expect(subject.postinstall).not_to be_empty expect(subject.postinstall.length).to be(1) expect(subject.postinstall[0]).to eql("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}") end @@ -112,7 +112,7 @@ describe Clusters::Applications::Knative do subject { knative.install_command } it 'is initialized with latest version' do - expect(subject.version).to eq('0.5.0') + expect(subject.version).to eq('0.6.0') end it_behaves_like 'a command' @@ -129,6 +129,46 @@ describe Clusters::Applications::Knative do it_behaves_like 'a command' end + describe '#uninstall_command' do + subject { knative.uninstall_command } + + it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) } + + it "removes knative deployed services before uninstallation" do + 2.times do |i| + cluster_project = create(:cluster_project, cluster: knative.cluster) + + create(:cluster_kubernetes_namespace, + cluster: cluster_project.cluster, + cluster_project: cluster_project, + project: cluster_project.project, + namespace: "namespace_#{i}") + end + + remove_namespaced_services_script = [ + "kubectl delete ksvc --all -n #{knative.cluster.kubernetes_namespaces.first.namespace}", + "kubectl delete ksvc --all -n #{knative.cluster.kubernetes_namespaces.second.namespace}" + ] + + expect(subject.predelete).to match_array(remove_namespaced_services_script) + end + + it "initializes command with all necessary postdelete script" do + api_resources = YAML.safe_load(File.read(Rails.root.join(Clusters::Applications::Knative::API_RESOURCES_PATH))) + + remove_knative_istio_leftovers_script = [ + "kubectl delete --ignore-not-found ns knative-serving", + "kubectl delete --ignore-not-found ns knative-build" + ] + + full_delete_commands_size = api_resources.size + remove_knative_istio_leftovers_script.size + + expect(subject.postdelete).to include(*remove_knative_istio_leftovers_script) + expect(subject.postdelete.size).to eq(full_delete_commands_size) + expect(subject.postdelete[2]).to eq("kubectl delete --ignore-not-found crd #{api_resources[0]}") + end + end + describe '#files' do let(:application) { knative } let(:values) { subject[:'values.yaml'] } diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 26267c64112..eb6ccba5584 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -86,16 +86,15 @@ describe Clusters::Applications::Prometheus do project: cluster.cluster_project.project) end - it 'creates proxy prometheus rest client' do - expect(subject.prometheus_client).to be_instance_of(RestClient::Resource) + it 'creates proxy prometheus_client' do + expect(subject.prometheus_client).to be_instance_of(Gitlab::PrometheusClient) end - it 'creates proper url' do - expect(subject.prometheus_client.url).to eq("#{kubernetes_url}/api/v1/namespaces/gitlab-managed-apps/services/prometheus-prometheus-server:80/proxy") - end - - it 'copies options and headers from kube client to proxy client' do - expect(subject.prometheus_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers)) + it 'copies proxy_url, options and headers from kube client to prometheus_client' do + expect(Gitlab::PrometheusClient) + .to(receive(:new)) + .with(a_valid_url, kube_client.rest_client.options.merge(headers: kube_client.headers)) + subject.prometheus_client end context 'when cluster is not reachable' do @@ -142,7 +141,7 @@ describe Clusters::Applications::Prometheus do end it 'does not install knative metrics' do - expect(subject.postinstall).to be_nil + expect(subject.postinstall).to be_empty end context 'with knative installed' do diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 4f0cd0efe9c..4abe45a2152 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -18,7 +18,7 @@ describe Clusters::Applications::Runner do subject { gitlab_runner.can_uninstall? } - it { is_expected.to be_falsey } + it { is_expected.to be_truthy } end describe '#install_command' do @@ -156,4 +156,35 @@ describe Clusters::Applications::Runner do end end end + + describe '#prepare_uninstall' do + it 'pauses associated runner' do + active_runner = create(:ci_runner, contacted_at: 1.second.ago) + + expect(active_runner.status).to eq(:online) + + application_runner = create(:clusters_applications_runner, :scheduled, runner: active_runner) + application_runner.prepare_uninstall + + expect(active_runner.status).to eq(:paused) + end + end + + describe '#make_uninstalling!' do + subject { create(:clusters_applications_runner, :scheduled, runner: ci_runner) } + + it 'calls prepare_uninstall' do + expect_any_instance_of(described_class).to receive(:prepare_uninstall).and_call_original + + subject.make_uninstalling! + end + end + + describe '#post_uninstall' do + it 'destroys its runner' do + application_runner = create(:clusters_applications_runner, :scheduled, runner: ci_runner) + + expect { application_runner.post_uninstall }.to change { Ci::Runner.count }.by(-1) + end + end end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index f206bb41f45..9afbe6328ca 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -38,11 +38,6 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do it { is_expected.to respond_to :project } - it do - expect(subject.knative_services_finder(subject.project)) - .to be_instance_of(Clusters::KnativeServicesFinder) - end - describe '.enabled' do subject { described_class.enabled } @@ -121,26 +116,6 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do 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? } @@ -342,7 +317,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do end end - context 'when sub-group has configured kubernetes cluster', :nested_groups do + context 'when sub-group has configured kubernetes cluster' 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) } @@ -423,31 +398,6 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do 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 } @@ -496,7 +446,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do 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!(:cert_manager) { create(:clusters_applications_cert_manager, 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) } @@ -514,19 +464,43 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do subject { cluster.allow_user_defined_namespace? } context 'project type cluster' do - it { is_expected.to be_truthy } + context 'gitlab managed' do + it { is_expected.to be_truthy } + end + + context 'not managed' do + let(:cluster) { create(:cluster, :provided_by_gcp, managed: false) } + + it { is_expected.to be_truthy } + end end context 'group type cluster' do - let(:cluster) { create(:cluster, :provided_by_gcp, :group) } + context 'gitlab managed' do + let(:cluster) { create(:cluster, :provided_by_gcp, :group) } + + it { is_expected.to be_falsey } + end + + context 'not managed' do + let(:cluster) { create(:cluster, :provided_by_gcp, :group, managed: false) } - it { is_expected.to be_falsey } + it { is_expected.to be_truthy } + end end context 'instance type cluster' do - let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } + context 'gitlab managed' do + let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } + + it { is_expected.to be_falsey } + end + + context 'not managed' do + let(:cluster) { create(:cluster, :provided_by_gcp, :instance, managed: false) } - it { is_expected.to be_falsey } + it { is_expected.to be_truthy } + end end end @@ -555,6 +529,42 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do end end + describe '#kubernetes_namespace_for' do + let(:cluster) { create(:cluster, :group) } + let(:environment) { create(:environment) } + + subject { cluster.kubernetes_namespace_for(environment) } + + before do + expect(Clusters::KubernetesNamespaceFinder).to receive(:new) + .with(cluster, project: environment.project, environment_slug: environment.slug) + .and_return(double(execute: persisted_namespace)) + end + + context 'a persisted namespace exists' do + let(:persisted_namespace) { create(:cluster_kubernetes_namespace) } + + it { is_expected.to eq persisted_namespace.namespace } + end + + context 'no persisted namespace exists' do + let(:persisted_namespace) { nil } + let(:namespace_generator) { double } + let(:default_namespace) { 'a-default-namespace' } + + before do + expect(Gitlab::Kubernetes::DefaultNamespace).to receive(:new) + .with(cluster, project: environment.project) + .and_return(namespace_generator) + expect(namespace_generator).to receive(:from_environment_slug) + .with(environment.slug) + .and_return(default_namespace) + end + + it { is_expected.to eq default_namespace } + end + end + describe '#predefined_variables' do subject { cluster.predefined_variables } diff --git a/spec/models/clusters/clusters_hierarchy_spec.rb b/spec/models/clusters/clusters_hierarchy_spec.rb new file mode 100644 index 00000000000..0470ebe17ea --- /dev/null +++ b/spec/models/clusters/clusters_hierarchy_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::ClustersHierarchy do + describe '#base_and_ancestors' do + def base_and_ancestors(clusterable) + described_class.new(clusterable).base_and_ancestors + end + + context 'project in nested group with clusters at every level' do + let!(:cluster) { create(:cluster, :project, projects: [project]) } + let!(:child) { create(:cluster, :group, groups: [child_group]) } + let!(:parent) { create(:cluster, :group, groups: [parent_group]) } + let!(:ancestor) { create(:cluster, :group, groups: [ancestor_group]) } + + let(:ancestor_group) { create(:group) } + let(:parent_group) { create(:group, parent: ancestor_group) } + let(:child_group) { create(:group, parent: parent_group) } + let(:project) { create(:project, group: child_group) } + + it 'returns clusters for project' do + expect(base_and_ancestors(project)).to eq([cluster, child, parent, ancestor]) + end + + it 'returns clusters for child_group' do + expect(base_and_ancestors(child_group)).to eq([child, parent, ancestor]) + end + + it 'returns clusters for parent_group' do + expect(base_and_ancestors(parent_group)).to eq([parent, ancestor]) + end + + it 'returns clusters for ancestor_group' do + expect(base_and_ancestors(ancestor_group)).to eq([ancestor]) + end + end + + context 'project in a namespace' do + let!(:cluster) { create(:cluster, :project) } + + it 'returns clusters for project' do + expect(base_and_ancestors(cluster.project)).to eq([cluster]) + end + end + + context 'project in nested group with clusters at some levels' do + let!(:child) { create(:cluster, :group, groups: [child_group]) } + let!(:ancestor) { create(:cluster, :group, groups: [ancestor_group]) } + + let(:ancestor_group) { create(:group) } + let(:parent_group) { create(:group, parent: ancestor_group) } + let(:child_group) { create(:group, parent: parent_group) } + let(:project) { create(:project, group: child_group) } + + it 'returns clusters for project' do + expect(base_and_ancestors(project)).to eq([child, ancestor]) + end + + it 'returns clusters for child_group' do + expect(base_and_ancestors(child_group)).to eq([child, ancestor]) + end + + it 'returns clusters for parent_group' do + expect(base_and_ancestors(parent_group)).to eq([ancestor]) + end + + it 'returns clusters for ancestor_group' do + expect(base_and_ancestors(ancestor_group)).to eq([ancestor]) + end + end + end +end diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb index b5cba80b806..d4e3a0ac84d 100644 --- a/spec/models/clusters/kubernetes_namespace_spec.rb +++ b/spec/models/clusters/kubernetes_namespace_spec.rb @@ -24,70 +24,60 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do end end - describe 'namespace uniqueness validation' do - let(:cluster_project) { create(:cluster_project) } - let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') } + describe '.with_environment_slug' do + let(:cluster) { create(:cluster, :group) } + let(:environment) { create(:environment, slug: slug) } - subject { kubernetes_namespace } + let(:slug) { 'production' } - context 'when cluster is using the namespace' do - before do - create(:cluster_kubernetes_namespace, - cluster: kubernetes_namespace.cluster, - namespace: 'my-namespace') - end + subject { described_class.with_environment_slug(slug) } - it { is_expected.not_to be_valid } - end + context 'there is no associated environment' do + let!(:namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, project: environment.project) } - context 'when cluster is not using the namespace' do - it { is_expected.to be_valid } + it { is_expected.to be_empty } end - end - describe '#set_defaults' do - let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace) } - let(:cluster) { kubernetes_namespace.cluster } - let(:platform) { kubernetes_namespace.platform_kubernetes } - - subject { kubernetes_namespace.set_defaults } - - describe '#namespace' do - before do - platform.update_column(:namespace, namespace) + context 'there is an assicated environment' do + let!(:namespace) do + create( + :cluster_kubernetes_namespace, + cluster: cluster, + project: environment.project, + environment: environment + ) end - context 'when platform has a namespace assigned' do - let(:namespace) { 'platform-namespace' } - - it 'copies the namespace' do - subject - - expect(kubernetes_namespace.namespace).to eq('platform-namespace') - end + context 'with a matching slug' do + it { is_expected.to eq [namespace] } end - context 'when platform does not have namespace assigned' do - let(:project) { kubernetes_namespace.project } - let(:namespace) { nil } - let(:project_slug) { "#{project.path}-#{project.id}" } - - it 'fallbacks to project namespace' do - subject + context 'without a matching slug' do + let(:environment) { create(:environment, slug: 'staging') } - expect(kubernetes_namespace.namespace).to eq(project_slug) - end + it { is_expected.to be_empty } end end + end - describe '#service_account_name' do - let(:service_account_name) { "#{kubernetes_namespace.namespace}-service-account" } + describe 'namespace uniqueness validation' do + let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') } - it 'sets a service account name based on namespace' do - subject + subject { kubernetes_namespace } - expect(kubernetes_namespace.service_account_name).to eq(service_account_name) + context 'when cluster is using the namespace' do + before do + create(:cluster_kubernetes_namespace, + cluster: kubernetes_namespace.cluster, + environment: kubernetes_namespace.environment, + namespace: 'my-namespace') end + + it { is_expected.not_to be_valid } + end + + context 'when cluster is not using the namespace' do + it { is_expected.to be_valid } end end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index c485850c16e..0c4cf291d20 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -2,13 +2,11 @@ require 'spec_helper' -describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching do +describe Clusters::Platforms::Kubernetes do include KubernetesHelpers - include ReactiveCachingHelpers it { is_expected.to belong_to(:cluster) } it { is_expected.to be_kind_of(Gitlab::Kubernetes) } - it { is_expected.to be_kind_of(ReactiveCaching) } it { is_expected.to respond_to :ca_pem } it { is_expected.to validate_exclusion_of(:namespace).in_array(%w(gitlab-managed-apps)) } @@ -108,7 +106,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching before do allow(ApplicationSetting) .to receive(:current) - .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true)) + .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_web_hooks_and_services: true)) end it { expect(kubernetes.save).to be_truthy } @@ -207,194 +205,100 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { is_expected.to be_truthy } end - describe '#kubernetes_namespace_for' do - let(:cluster) { create(:cluster, :project) } - let(:project) { cluster.project } - - let(:platform) do - create(:cluster_platform_kubernetes, - cluster: cluster, - namespace: namespace) - end - - subject { platform.kubernetes_namespace_for(project) } - - context 'with a namespace assigned' do - let(:namespace) { 'namespace-123' } - - it { is_expected.to eq(namespace) } - end - - context 'with no namespace assigned' do - let(:namespace) { nil } - - context 'when kubernetes namespace is present' do - let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) } - - before do - kubernetes_namespace - end - - it { is_expected.to eq(kubernetes_namespace.namespace) } - end - - context 'when kubernetes namespace is not present' do - it { is_expected.to eq("#{project.path}-#{project.id}") } - end - end - end - describe '#predefined_variables' do - let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) } - let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem) } - let(:api_url) { 'https://kube.domain.com' } - let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) } - - subject { kubernetes.predefined_variables(project: cluster.project) } - - shared_examples 'setting variables' do - it 'sets the variables' do - expect(subject).to include( - { key: 'KUBE_URL', value: api_url, public: true }, - { key: 'KUBE_CA_PEM', value: ca_pem, public: true }, - { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true } - ) - end - end + let(:project) { create(:project) } + let(:cluster) { create(:cluster, :group, platform_kubernetes: platform) } + let(:platform) { create(:cluster_platform_kubernetes) } + let(:persisted_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster) } - context 'kubernetes namespace is created with no service account token' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) } + let(:environment_name) { 'env/production' } + let(:environment_slug) { Gitlab::Slug::Environment.new(environment_name).generate } - it_behaves_like 'setting variables' + subject { platform.predefined_variables(project: project, environment_name: environment_name) } - it 'sets KUBE_TOKEN' do - expect(subject).to include( - { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true } - ) - end + before do + allow(Clusters::KubernetesNamespaceFinder).to receive(:new) + .with(cluster, project: project, environment_slug: environment_slug) + .and_return(double(execute: persisted_namespace)) end - context 'kubernetes namespace is created with no service account token' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster) } + it { is_expected.to include(key: 'KUBE_URL', value: platform.api_url, public: true) } - it_behaves_like 'setting variables' + context 'platform has a CA certificate' do + let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) } + let(:platform) { create(:cluster_platform_kubernetes, ca_cert: ca_pem) } - it 'sets KUBE_TOKEN' do - expect(subject).to include( - { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true } - ) - end + it { is_expected.to include(key: 'KUBE_CA_PEM', value: ca_pem, public: true) } + it { is_expected.to include(key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true) } end - context 'namespace is provided' do - let(:namespace) { 'my-project' } + context 'kubernetes namespace exists' do + let(:variable) { Hash(key: :fake_key, value: 'fake_value') } + let(:namespace_variables) { Gitlab::Ci::Variables::Collection.new([variable]) } before do - kubernetes.namespace = namespace + expect(persisted_namespace).to receive(:predefined_variables).and_return(namespace_variables) end - it_behaves_like 'setting variables' - - it 'sets KUBE_TOKEN' do - expect(subject).to include( - { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true } - ) - end - end - - context 'no namespace provided' do - it_behaves_like 'setting variables' - - it 'sets KUBE_TOKEN' do - expect(subject).to include( - { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true } - ) - end + it { is_expected.to include(variable) } end - context 'group level cluster' do - let!(:cluster) { create(:cluster, :group, platform_kubernetes: kubernetes) } - - let(:project) { create(:project, group: cluster.group) } - - subject { kubernetes.predefined_variables(project: project) } - - context 'no kubernetes namespace for the project' do - it_behaves_like 'setting variables' + context 'kubernetes namespace does not exist' do + let(:persisted_namespace) { nil } + let(:namespace) { 'kubernetes-namespace' } + let(:kubeconfig) { 'kubeconfig' } - it 'does not return KUBE_TOKEN' do - expect(subject).not_to include( - { key: 'KUBE_TOKEN', value: kubernetes.token, public: false } - ) - end - - context 'the cluster is not managed' do - let!(:cluster) { create(:cluster, :group, :not_managed, platform_kubernetes: kubernetes) } - - it_behaves_like 'setting variables' - - it 'sets KUBE_TOKEN' do - expect(subject).to include( - { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true } - ) - end - end + before do + allow(Gitlab::Kubernetes::DefaultNamespace).to receive(:new) + .with(cluster, project: project).and_return(double(from_environment_name: namespace)) + allow(platform).to receive(:kubeconfig).with(namespace).and_return(kubeconfig) end - context 'kubernetes namespace exists for the project' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster, project: project) } + it { is_expected.not_to include(key: 'KUBE_TOKEN', value: platform.token, public: false, masked: true) } + it { is_expected.not_to include(key: 'KUBE_NAMESPACE', value: namespace) } + it { is_expected.not_to include(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true) } - it_behaves_like 'setting variables' + context 'cluster is unmanaged' do + let(:cluster) { create(:cluster, :group, :not_managed, platform_kubernetes: platform) } - it 'sets KUBE_TOKEN' do - expect(subject).to include( - { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true } - ) - end + it { is_expected.to include(key: 'KUBE_TOKEN', value: platform.token, public: false, masked: true) } + it { is_expected.to include(key: 'KUBE_NAMESPACE', value: namespace) } + it { is_expected.to include(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true) } end end - context 'with a domain' do - let!(:cluster) do - create(:cluster, :provided_by_gcp, :with_domain, - platform_kubernetes: kubernetes) - end + context 'cluster variables' do + let(:variable) { Hash(key: :fake_key, value: 'fake_value') } + let(:cluster_variables) { Gitlab::Ci::Variables::Collection.new([variable]) } - it 'sets KUBE_INGRESS_BASE_DOMAIN' do - expect(subject).to include( - { key: 'KUBE_INGRESS_BASE_DOMAIN', value: cluster.domain, public: true } - ) + before do + expect(cluster).to receive(:predefined_variables).and_return(cluster_variables) end + + it { is_expected.to include(variable) } end end describe '#terminals' do - subject { service.terminals(environment) } + subject { service.terminals(environment, pods: pods) } let!(:cluster) { create(:cluster, :project, platform_kubernetes: service) } let(:project) { cluster.project } let(:service) { create(:cluster_platform_kubernetes, :configured) } let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") } + let(:pods) { [{ "bad" => "pod" }] } context 'with invalid pods' do it 'returns no terminals' do - stub_reactive_cache(service, pods: [{ "bad" => "pod" }]) - is_expected.to be_empty end end context 'with valid pods' do - let(:pod) { kube_pod(environment_slug: environment.slug, namespace: cluster.kubernetes_namespace_for(project), project_slug: project.full_path_slug) } + let(:pod) { kube_pod(environment_slug: environment.slug, namespace: cluster.kubernetes_namespace_for(environment), project_slug: project.full_path_slug) } let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") } let(:terminals) { kube_terminals(service, pod) } - - before do - stub_reactive_cache( - service, - pods: [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")] - ) - end + let(:pods) { [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")] } it 'returns terminals' do is_expected.to eq(terminals + terminals) @@ -409,16 +313,18 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching end end - describe '#calculate_reactive_cache' do - subject { service.calculate_reactive_cache } - - let!(:cluster) { create(:cluster, :project, enabled: enabled, platform_kubernetes: service) } + describe '#calculate_reactive_cache_for' do let(:service) { create(:cluster_platform_kubernetes, :configured) } - let(:enabled) { true } - let(:namespace) { cluster.kubernetes_namespace_for(cluster.project) } + let(:pod) { kube_pod } + let(:namespace) { pod["metadata"]["namespace"] } + let(:environment) { instance_double(Environment, deployment_namespace: namespace) } - context 'when cluster is disabled' do - let(:enabled) { false } + subject { service.calculate_reactive_cache_for(environment) } + + context 'when the kubernetes integration is disabled' do + before do + allow(service).to receive(:enabled?).and_return(false) + end it { is_expected.to be_nil } end @@ -429,7 +335,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching stub_kubeclient_deployments(namespace) end - it { is_expected.to include(pods: [kube_pod]) } + it { is_expected.to include(pods: [pod]) } end context 'when kubernetes responds with 500s' do @@ -449,34 +355,5 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { is_expected.to include(pods: []) } end - - context 'when the cluster is not project level' do - let(:cluster) { create(:cluster, :group, platform_kubernetes: service) } - - it { is_expected.to include(pods: []) } - end - end - - describe '#update_kubernetes_namespace' do - let(:cluster) { create(:cluster, :provided_by_gcp) } - let(:platform) { cluster.platform } - - context 'when namespace is updated' do - it 'calls ConfigureWorker' do - expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id).once - - platform.namespace = 'new-namespace' - platform.save - end - end - - context 'when namespace is not updated' do - it 'does not call ConfigureWorker' do - expect(ClusterConfigureWorker).not_to receive(:perform_async) - - platform.username = "new-username" - platform.save - end - end end end |
