diff options
Diffstat (limited to 'spec/models/clusters')
-rw-r--r-- | spec/models/clusters/agent_spec.rb | 12 | ||||
-rw-r--r-- | spec/models/clusters/applications/helm_spec.rb | 40 | ||||
-rw-r--r-- | spec/models/clusters/cluster_spec.rb | 6 | ||||
-rw-r--r-- | spec/models/clusters/platforms/kubernetes_spec.rb | 399 |
4 files changed, 404 insertions, 53 deletions
diff --git a/spec/models/clusters/agent_spec.rb b/spec/models/clusters/agent_spec.rb index 148bb3cf870..49f41570717 100644 --- a/spec/models/clusters/agent_spec.rb +++ b/spec/models/clusters/agent_spec.rb @@ -57,4 +57,16 @@ RSpec.describe Clusters::Agent do end end end + + describe '#has_access_to?' do + let(:agent) { build(:cluster_agent) } + + it 'has access to own project' do + expect(agent.has_access_to?(agent.project)).to be_truthy + end + + it 'does not have access to other projects' do + expect(agent.has_access_to?(create(:project))).to be_falsey + end + end end diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb index ad1ebd4966a..5212e321a55 100644 --- a/spec/models/clusters/applications/helm_spec.rb +++ b/spec/models/clusters/applications/helm_spec.rb @@ -19,35 +19,9 @@ RSpec.describe Clusters::Applications::Helm do end describe '#can_uninstall?' do - context "with other existing applications" do - Clusters::Cluster::APPLICATIONS.keys.each do |application_name| - next if application_name == 'helm' + subject(:application) { build(:clusters_applications_helm).can_uninstall? } - 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 - - it 'executes a single query only' do - cluster_application = create(:clusters_applications_ingress) - helm = cluster_application.cluster.application_helm - - 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 + it { is_expected.to eq true } end describe '#issue_client_cert' do @@ -135,14 +109,4 @@ RSpec.describe Clusters::Applications::Helm do end end end - - describe '#post_uninstall' do - let(:helm) { create(:clusters_applications_helm, :installed) } - - it do - expect(helm.cluster.kubeclient).to receive(:delete_namespace).with('gitlab-managed-apps') - - helm.post_uninstall - end - end end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index ed74a841044..a8f81cba285 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -262,14 +262,14 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do end end - describe '.with_project_alert_service_data' do - subject { described_class.with_project_alert_service_data(project_id) } + describe '.with_project_http_integrations' do + subject { described_class.with_project_http_integrations(project_id) } let!(:cluster) { create(:cluster, :project) } let!(:project_id) { cluster.first_project.id } context 'project has alert service data' do - let!(:alerts_service) { create(:alerts_service, project: cluster.clusterable) } + let!(:integration) { create(:alert_management_http_integration, project: cluster.clusterable) } it { is_expected.to include(cluster) } end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index e877ba2ac96..fb0613187c5 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.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) } @@ -406,32 +407,62 @@ RSpec.describe Clusters::Platforms::Kubernetes do end describe '#calculate_reactive_cache_for' do + let(:cluster) { create(:cluster, :project, platform_kubernetes: service) } let(:service) { create(:cluster_platform_kubernetes, :configured) } + let(:namespace) { 'project-namespace' } + let(:environment) { instance_double(Environment, deployment_namespace: namespace, project: cluster.project) } let(:expected_pod_cached_data) do kube_pod.tap { |kp| kp['metadata'].delete('namespace') } end - let(:namespace) { "project-namespace" } - let(:environment) { instance_double(Environment, deployment_namespace: namespace, project: service.cluster.project) } - subject { service.calculate_reactive_cache_for(environment) } - context 'when the kubernetes integration is disabled' do + context 'when kubernetes responds with valid deployments' do before do - allow(service).to receive(:enabled?).and_return(false) + stub_kubeclient_pods(namespace) + stub_kubeclient_deployments(namespace) + stub_kubeclient_ingresses(namespace) end - it { is_expected.to be_nil } + shared_examples 'successful deployment request' do + it { is_expected.to include(pods: [expected_pod_cached_data], deployments: [kube_deployment], ingresses: [kube_ingress]) } + end + + context 'on a project level cluster' do + let(:cluster) { create(:cluster, :project, platform_kubernetes: service) } + + include_examples 'successful deployment request' + end + + context 'on a group level cluster' do + let(:cluster) { create(:cluster, :group, platform_kubernetes: service) } + + include_examples 'successful deployment request' + end + + context 'on an instance level cluster' do + let(:cluster) { create(:cluster, :instance, platform_kubernetes: service) } + + include_examples 'successful deployment request' + end + + context 'when canary_ingress_weight_control feature flag is disabled' do + before do + stub_feature_flags(canary_ingress_weight_control: false) + end + + it 'does not fetch ingress data from kubernetes' do + expect(subject[:ingresses]).to be_empty + end + end end - context 'when kubernetes responds with valid pods and deployments' do + context 'when the kubernetes integration is disabled' do before do - stub_kubeclient_pods(namespace) - stub_kubeclient_deployments(namespace) - stub_kubeclient_ingresses(namespace) + allow(service).to receive(:enabled?).and_return(false) end - it { is_expected.to include(pods: [expected_pod_cached_data]) } + it { is_expected.to be_nil } end context 'when kubernetes responds with 500s' do @@ -451,7 +482,351 @@ RSpec.describe Clusters::Platforms::Kubernetes do stub_kubeclient_ingresses(namespace, status: 404) end - it { is_expected.to include(pods: []) } + it { is_expected.to eq(pods: [], deployments: [], ingresses: []) } + end + end + + describe '#rollout_status' do + let(:deployments) { [] } + let(:pods) { [] } + let(:ingresses) { [] } + let(:service) { create(:cluster_platform_kubernetes, :configured) } + let!(:cluster) { create(:cluster, :project, enabled: true, platform_kubernetes: service) } + let(:project) { cluster.project } + let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") } + let(:cache_data) { Hash(deployments: deployments, pods: pods, ingresses: ingresses) } + + subject(:rollout_status) { service.rollout_status(environment, cache_data) } + + context 'legacy deployments based on app label' do + let(:legacy_deployment) do + kube_deployment(name: 'legacy-deployment').tap do |deployment| + deployment['metadata']['annotations'].delete('app.gitlab.com/env') + deployment['metadata']['annotations'].delete('app.gitlab.com/app') + deployment['metadata']['labels']['app'] = environment.slug + end + end + + let(:legacy_pod) do + kube_pod(name: 'legacy-pod').tap do |pod| + pod['metadata']['annotations'].delete('app.gitlab.com/env') + pod['metadata']['annotations'].delete('app.gitlab.com/app') + pod['metadata']['labels']['app'] = environment.slug + end + end + + context 'only legacy deployments' do + let(:deployments) { [legacy_deployment] } + let(:pods) { [legacy_pod] } + + it 'contains nothing' do + expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus) + + expect(rollout_status.deployments).to eq([]) + end + end + + context 'deployment with no pods' do + let(:deployment) { kube_deployment(name: 'some-deployment', environment_slug: environment.slug, project_slug: project.full_path_slug) } + let(:deployments) { [deployment] } + let(:pods) { [] } + + it 'returns a valid status with matching deployments' do + expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus) + expect(rollout_status.deployments.map(&:name)).to contain_exactly('some-deployment') + end + end + + context 'new deployment based on annotations' do + let(:matched_deployment) { kube_deployment(name: 'matched-deployment', environment_slug: environment.slug, project_slug: project.full_path_slug) } + let(:matched_pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) } + let(:deployments) { [matched_deployment, legacy_deployment] } + let(:pods) { [matched_pod, legacy_pod] } + + it 'contains only matching deployments' do + expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus) + + expect(rollout_status.deployments.map(&:name)).to contain_exactly('matched-deployment') + end + end + end + + context 'with no deployments but there are pods' do + let(:deployments) do + [] + end + + let(:pods) do + [ + kube_pod(name: 'pod-1', environment_slug: environment.slug, project_slug: project.full_path_slug), + kube_pod(name: 'pod-2', environment_slug: environment.slug, project_slug: project.full_path_slug) + ] + end + + it 'returns an empty array' do + expect(rollout_status.instances).to eq([]) + end + end + + context 'with valid deployments' do + let(:matched_deployment) { kube_deployment(environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2) } + let(:unmatched_deployment) { kube_deployment } + let(:matched_pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: 'Pending') } + let(:unmatched_pod) { kube_pod(environment_slug: environment.slug + '-test', project_slug: project.full_path_slug) } + let(:deployments) { [matched_deployment, unmatched_deployment] } + let(:pods) { [matched_pod, unmatched_pod] } + + it 'creates a matching RolloutStatus' do + expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus) + expect(rollout_status.deployments.map(&:annotations)).to eq([ + { 'app.gitlab.com/app' => project.full_path_slug, 'app.gitlab.com/env' => 'env-000000' } + ]) + expect(rollout_status.instances).to eq([{ pod_name: "kube-pod", + stable: true, + status: "pending", + tooltip: "kube-pod (Pending)", + track: "stable" }, + { pod_name: "Not provided", + stable: true, + status: "pending", + tooltip: "Not provided (Pending)", + track: "stable" }]) + end + + context 'with canary ingress' do + let(:ingresses) { [kube_ingress(track: :canary)] } + + it 'has canary ingress' do + expect(rollout_status).to be_canary_ingress_exists + expect(rollout_status.canary_ingress.canary_weight).to eq(50) + end + end + end + + context 'with empty list of deployments' do + it 'creates a matching RolloutStatus' do + expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus) + expect(rollout_status).to be_not_found + end + end + + context 'when the pod track does not match the deployment track' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'weekly') + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'weekly'), + kube_pod(name: 'pod-a-2', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'daily') + ] + end + + it 'does not return the pod' do + expect(rollout_status.instances.map { |p| p[:pod_name] }).to eq(['pod-a-1']) + end + end + + context 'when the pod track is not stable' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'something') + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'something') + ] + end + + it 'the pod is not stable' do + expect(rollout_status.instances.map { |p| p.slice(:stable, :track) }).to eq([{ stable: false, track: 'something' }]) + end + end + + context 'when the pod track is stable' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'stable') + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'stable') + ] + end + + it 'the pod is stable' do + expect(rollout_status.instances.map { |p| p.slice(:stable, :track) }).to eq([{ stable: true, track: 'stable' }]) + end + end + + context 'when the pod track is not provided' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1) + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug) + ] + end + + it 'the pod is stable' do + expect(rollout_status.instances.map { |p| p.slice(:stable, :track) }).to eq([{ stable: true, track: 'stable' }]) + end + end + + context 'when the number of matching pods does not match the number of replicas' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 3) + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug) + ] + end + + it 'returns a pending pod for each missing replica' do + expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status) }).to eq([ + { pod_name: 'pod-a-1', status: 'running' }, + { pod_name: 'Not provided', status: 'pending' }, + { pod_name: 'Not provided', status: 'pending' } + ]) + end + end + + context 'when pending pods are returned for missing replicas' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2, track: 'canary'), + kube_deployment(name: 'deployment-b', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2, track: 'stable') + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'canary') + ] + end + + it 'returns the correct track for the pending pods' do + expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status, :track) }).to eq([ + { pod_name: 'pod-a-1', status: 'running', track: 'canary' }, + { pod_name: 'Not provided', status: 'pending', track: 'canary' }, + { pod_name: 'Not provided', status: 'pending', track: 'stable' }, + { pod_name: 'Not provided', status: 'pending', track: 'stable' } + ]) + end + end + + context 'when two deployments with the same track are missing instances' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'mytrack'), + kube_deployment(name: 'deployment-b', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'mytrack') + ] + end + + let(:pods) do + [] + end + + it 'returns the correct number of pending pods' do + expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status, :track) }).to eq([ + { pod_name: 'Not provided', status: 'pending', track: 'mytrack' }, + { pod_name: 'Not provided', status: 'pending', track: 'mytrack' } + ]) + end + end + + context 'with multiple matching deployments' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2), + kube_deployment(name: 'deployment-b', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2) + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug), + kube_pod(name: 'pod-a-2', environment_slug: environment.slug, project_slug: project.full_path_slug), + kube_pod(name: 'pod-b-1', environment_slug: environment.slug, project_slug: project.full_path_slug), + kube_pod(name: 'pod-b-2', environment_slug: environment.slug, project_slug: project.full_path_slug) + ] + end + + it 'returns each pod once' do + expect(rollout_status.instances.map { |p| p[:pod_name] }).to eq(['pod-a-1', 'pod-a-2', 'pod-b-1', 'pod-b-2']) + end + end + end + + describe '#ingresses' do + subject { service.ingresses(namespace) } + + let(:service) { create(:cluster_platform_kubernetes, :configured) } + let(:namespace) { 'project-namespace' } + + context 'when there is an ingress in the namespace' do + before do + stub_kubeclient_ingresses(namespace) + end + + it 'returns an ingress' do + expect(subject.count).to eq(1) + expect(subject.first).to be_kind_of(::Gitlab::Kubernetes::Ingress) + expect(subject.first.name).to eq('production-auto-deploy') + end + end + + context 'when there are no ingresss in the namespace' do + before do + allow(service.kubeclient).to receive(:get_ingresses) { raise Kubeclient::ResourceNotFoundError.new(404, 'Not found', nil) } + end + + it 'returns nothing' do + is_expected.to be_empty + end + end + end + + describe '#patch_ingress' do + subject { service.patch_ingress(namespace, ingress, data) } + + let(:service) { create(:cluster_platform_kubernetes, :configured) } + let(:namespace) { 'project-namespace' } + let(:ingress) { Gitlab::Kubernetes::Ingress.new(kube_ingress) } + let(:data) { { metadata: { annotations: { name: 'test' } } } } + + context 'when there is an ingress in the namespace' do + before do + stub_kubeclient_ingresses(namespace, method: :patch, resource_path: "/#{ingress.name}") + end + + it 'returns an ingress' do + expect(subject[:items][0][:metadata][:name]).to eq('production-auto-deploy') + end + end + + context 'when there are no ingresss in the namespace' do + before do + allow(service.kubeclient).to receive(:patch_ingress) { raise Kubeclient::ResourceNotFoundError.new(404, 'Not found', nil) } + end + + it 'raises an error' do + expect { subject }.to raise_error(Kubeclient::ResourceNotFoundError) + end end end end |