diff options
Diffstat (limited to 'spec/lib/gitlab/kubernetes')
-rw-r--r-- | spec/lib/gitlab/kubernetes/deployment_spec.rb | 190 | ||||
-rw-r--r-- | spec/lib/gitlab/kubernetes/helm/v2/reset_command_spec.rb | 26 | ||||
-rw-r--r-- | spec/lib/gitlab/kubernetes/ingress_spec.rb | 57 | ||||
-rw-r--r-- | spec/lib/gitlab/kubernetes/rollout_instances_spec.rb | 128 | ||||
-rw-r--r-- | spec/lib/gitlab/kubernetes/rollout_status_spec.rb | 271 |
5 files changed, 650 insertions, 22 deletions
diff --git a/spec/lib/gitlab/kubernetes/deployment_spec.rb b/spec/lib/gitlab/kubernetes/deployment_spec.rb new file mode 100644 index 00000000000..2433e854e5b --- /dev/null +++ b/spec/lib/gitlab/kubernetes/deployment_spec.rb @@ -0,0 +1,190 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Kubernetes::Deployment do + include KubernetesHelpers + + let(:pods) { {} } + + subject(:deployment) { described_class.new(params, pods: pods) } + + describe '#name' do + let(:params) { named(:selected) } + + it { expect(deployment.name).to eq(:selected) } + end + + describe '#labels' do + let(:params) { make('metadata', 'labels' => :selected) } + + it { expect(deployment.labels).to eq(:selected) } + end + + describe '#outdated?' do + context 'when outdated' do + let(:params) { generation(2, 1, 0) } + + it { expect(deployment.outdated?).to be_truthy } + end + + context 'when up to date' do + let(:params) { generation(2, 2, 0) } + + it { expect(deployment.outdated?).to be_falsy } + end + + context 'when ahead of latest' do + let(:params) { generation(1, 2, 0) } + + it { expect(deployment.outdated?).to be_falsy } + end + end + + describe '#instances' do + context 'when unnamed' do + let(:pods) do + [ + kube_pod(name: nil, status: 'Pending'), + kube_pod(name: nil, status: 'Pending'), + kube_pod(name: nil, status: 'Pending'), + kube_pod(name: nil, status: 'Pending') + ] + end + + let(:params) { combine(generation(1, 1, 4)) } + + it 'returns all pods with generated names and pending' do + expected = [ + { status: 'pending', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Pending)', track: 'stable', stable: true } + ] + + expect(deployment.instances).to eq(expected) + end + end + + # When replica count is higher than pods it is considered that pod was not + # able to spawn for some reason like limited resources. + context 'when number of pods is less than wanted replicas' do + let(:wanted_replicas) { 3 } + let(:pods) { [kube_pod(name: nil, status: 'Running')] } + let(:params) { combine(generation(1, 1, wanted_replicas)) } + + it 'returns not spawned pods as pending and unknown and running' do + expected = [ + { status: 'running', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Running)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'Not provided', tooltip: 'Not provided (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'Not provided', tooltip: 'Not provided (Pending)', track: 'stable', stable: true } + ] + + expect(deployment.instances).to eq(expected) + end + end + + context 'when outdated' do + let(:pods) do + [ + kube_pod(status: 'Pending'), + kube_pod(name: 'kube-pod1', status: 'Pending'), + kube_pod(name: 'kube-pod2', status: 'Pending'), + kube_pod(name: 'kube-pod3', status: 'Pending') + ] + end + + let(:params) { combine(named('foo'), generation(1, 0, 4)) } + + it 'returns all instances as named and waiting' do + expected = [ + { status: 'pending', pod_name: 'kube-pod', tooltip: 'kube-pod (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'kube-pod1', tooltip: 'kube-pod1 (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'kube-pod2', tooltip: 'kube-pod2 (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'kube-pod3', tooltip: 'kube-pod3 (Pending)', track: 'stable', stable: true } + ] + + expect(deployment.instances).to eq(expected) + end + end + + context 'with pods of each type' do + let(:pods) do + [ + kube_pod(status: 'Succeeded'), + kube_pod(name: 'kube-pod1', status: 'Running'), + kube_pod(name: 'kube-pod2', status: 'Pending'), + kube_pod(name: 'kube-pod3', status: 'Pending') + ] + end + + let(:params) { combine(named('foo'), generation(1, 1, 4)) } + + it 'returns all instances' do + expected = [ + { status: 'succeeded', pod_name: 'kube-pod', tooltip: 'kube-pod (Succeeded)', track: 'stable', stable: true }, + { status: 'running', pod_name: 'kube-pod1', tooltip: 'kube-pod1 (Running)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'kube-pod2', tooltip: 'kube-pod2 (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'kube-pod3', tooltip: 'kube-pod3 (Pending)', track: 'stable', stable: true } + ] + + expect(deployment.instances).to eq(expected) + end + end + + context 'with track label' do + let(:pods) { [kube_pod(status: 'Pending')] } + let(:labels) { { 'track' => track } } + let(:params) { combine(named('foo', labels), generation(1, 0, 1)) } + + context 'when marked as stable' do + let(:track) { 'stable' } + + it 'returns all instances' do + expected = [ + { status: 'pending', pod_name: 'kube-pod', tooltip: 'kube-pod (Pending)', track: 'stable', stable: true } + ] + + expect(deployment.instances).to eq(expected) + end + end + + context 'when marked as canary' do + let(:track) { 'canary' } + let(:pods) { [kube_pod(status: 'Pending', track: track)] } + + it 'returns all instances' do + expected = [ + { status: 'pending', pod_name: 'kube-pod', tooltip: 'kube-pod (Pending)', track: 'canary', stable: false } + ] + + expect(deployment.instances).to eq(expected) + end + end + end + end + + def generation(expected, observed, replicas) + combine( + make('metadata', 'generation' => expected), + make('status', 'observedGeneration' => observed), + make('spec', 'replicas' => replicas) + ) + end + + def named(name = "foo", labels = {}) + make('metadata', 'name' => name, 'labels' => labels) + end + + def make(key, values = {}) + hsh = {} + hsh[key] = values + hsh + end + + def combine(*hashes) + out = {} + hashes.each { |hsh| out = out.deep_merge(hsh) } + out + end +end diff --git a/spec/lib/gitlab/kubernetes/helm/v2/reset_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/v2/reset_command_spec.rb index 9e580cea397..2a3a4cec2b0 100644 --- a/spec/lib/gitlab/kubernetes/helm/v2/reset_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/v2/reset_command_spec.rb @@ -12,32 +12,14 @@ RSpec.describe Gitlab::Kubernetes::Helm::V2::ResetCommand do it_behaves_like 'helm command generator' do let(:commands) do <<~EOS - helm reset - kubectl delete replicaset -n gitlab-managed-apps -l name\\=tiller - kubectl delete clusterrolebinding tiller-admin + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only + helm reset --force EOS end end - context 'when there is a ca.pem file' do - let(:files) { { 'ca.pem': 'some file content' } } - - it_behaves_like 'helm command generator' do - let(:commands) do - <<~EOS1.squish + "\n" + <<~EOS2 - helm reset - --tls - --tls-ca-cert /data/helm/helm/config/ca.pem - --tls-cert /data/helm/helm/config/cert.pem - --tls-key /data/helm/helm/config/key.pem - EOS1 - kubectl delete replicaset -n gitlab-managed-apps -l name\\=tiller - kubectl delete clusterrolebinding tiller-admin - EOS2 - end - end - end - describe '#pod_name' do subject { reset_command.pod_name } diff --git a/spec/lib/gitlab/kubernetes/ingress_spec.rb b/spec/lib/gitlab/kubernetes/ingress_spec.rb new file mode 100644 index 00000000000..e4d6bf4086f --- /dev/null +++ b/spec/lib/gitlab/kubernetes/ingress_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Kubernetes::Ingress do + include KubernetesHelpers + + let(:ingress) { described_class.new(params) } + + describe '#canary?' do + subject { ingress.canary? } + + context 'with canary ingress parameters' do + let(:params) { canary_metadata } + + it { is_expected.to be_truthy } + end + + context 'with stable ingress parameters' do + let(:params) { stable_metadata } + + it { is_expected.to be_falsey } + end + end + + describe '#canary_weight' do + subject { ingress.canary_weight } + + context 'with canary ingress parameters' do + let(:params) { canary_metadata } + + it { is_expected.to eq(50) } + end + + context 'with stable ingress parameters' do + let(:params) { stable_metadata } + + it { is_expected.to be_nil } + end + end + + describe '#name' do + subject { ingress.name } + + let(:params) { stable_metadata } + + it { is_expected.to eq('production-auto-deploy') } + end + + def stable_metadata + kube_ingress(track: :stable) + end + + def canary_metadata + kube_ingress(track: :canary) + end +end diff --git a/spec/lib/gitlab/kubernetes/rollout_instances_spec.rb b/spec/lib/gitlab/kubernetes/rollout_instances_spec.rb new file mode 100644 index 00000000000..3ac97ddc75d --- /dev/null +++ b/spec/lib/gitlab/kubernetes/rollout_instances_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Kubernetes::RolloutInstances do + include KubernetesHelpers + + def setup(deployments_attrs, pods_attrs) + deployments = deployments_attrs.map do |attrs| + ::Gitlab::Kubernetes::Deployment.new(attrs, pods: pods_attrs) + end + + pods = pods_attrs.map do |attrs| + ::Gitlab::Kubernetes::Pod.new(attrs) + end + + [deployments, pods] + end + + describe '#pod_instances' do + it 'returns an instance for a deployment with one pod' do + deployments, pods = setup( + [kube_deployment(name: 'one', track: 'stable', replicas: 1)], + [kube_pod(name: 'one', status: 'Running', track: 'stable')] + ) + rollout_instances = described_class.new(deployments, pods) + + expect(rollout_instances.pod_instances).to eq([{ + pod_name: 'one', + stable: true, + status: 'running', + tooltip: 'one (Running)', + track: 'stable' + }]) + end + + it 'returns a pending pod for a missing replica' do + deployments, pods = setup( + [kube_deployment(name: 'one', track: 'stable', replicas: 1)], + [] + ) + rollout_instances = described_class.new(deployments, pods) + + expect(rollout_instances.pod_instances).to eq([{ + pod_name: 'Not provided', + stable: true, + status: 'pending', + tooltip: 'Not provided (Pending)', + track: 'stable' + }]) + end + + it 'returns instances when there are two stable deployments' do + deployments, pods = setup([ + kube_deployment(name: 'one', track: 'stable', replicas: 1), + kube_deployment(name: 'two', track: 'stable', replicas: 1) + ], [ + kube_pod(name: 'one', status: 'Running', track: 'stable'), + kube_pod(name: 'two', status: 'Running', track: 'stable') + ]) + rollout_instances = described_class.new(deployments, pods) + + expect(rollout_instances.pod_instances).to eq([{ + pod_name: 'one', + stable: true, + status: 'running', + tooltip: 'one (Running)', + track: 'stable' + }, { + pod_name: 'two', + stable: true, + status: 'running', + tooltip: 'two (Running)', + track: 'stable' + }]) + end + + it 'returns instances for two deployments with different tracks' do + deployments, pods = setup([ + kube_deployment(name: 'one', track: 'mytrack', replicas: 1), + kube_deployment(name: 'two', track: 'othertrack', replicas: 1) + ], [ + kube_pod(name: 'one', status: 'Running', track: 'mytrack'), + kube_pod(name: 'two', status: 'Running', track: 'othertrack') + ]) + rollout_instances = described_class.new(deployments, pods) + + expect(rollout_instances.pod_instances).to eq([{ + pod_name: 'one', + stable: false, + status: 'running', + tooltip: 'one (Running)', + track: 'mytrack' + }, { + pod_name: 'two', + stable: false, + status: 'running', + tooltip: 'two (Running)', + track: 'othertrack' + }]) + end + + it 'sorts stable tracks after canary tracks' do + deployments, pods = setup([ + kube_deployment(name: 'one', track: 'stable', replicas: 1), + kube_deployment(name: 'two', track: 'canary', replicas: 1) + ], [ + kube_pod(name: 'one', status: 'Running', track: 'stable'), + kube_pod(name: 'two', status: 'Running', track: 'canary') + ]) + rollout_instances = described_class.new(deployments, pods) + + expect(rollout_instances.pod_instances).to eq([{ + pod_name: 'two', + stable: false, + status: 'running', + tooltip: 'two (Running)', + track: 'canary' + }, { + pod_name: 'one', + stable: true, + status: 'running', + tooltip: 'one (Running)', + track: 'stable' + }]) + end + end +end diff --git a/spec/lib/gitlab/kubernetes/rollout_status_spec.rb b/spec/lib/gitlab/kubernetes/rollout_status_spec.rb new file mode 100644 index 00000000000..8ed9fdd799c --- /dev/null +++ b/spec/lib/gitlab/kubernetes/rollout_status_spec.rb @@ -0,0 +1,271 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Kubernetes::RolloutStatus do + include KubernetesHelpers + + let(:track) { nil } + let(:specs) { specs_all_finished } + + let(:pods) do + create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: "canary") + end + + let(:ingresses) { [] } + + let(:specs_all_finished) do + [ + kube_deployment(name: 'one'), + kube_deployment(name: 'two', track: track) + ] + end + + let(:specs_half_finished) do + [ + kube_deployment(name: 'one'), + kube_deployment(name: 'two', track: track) + ] + end + + subject(:rollout_status) { described_class.from_deployments(*specs, pods_attrs: pods, ingresses: ingresses) } + + describe '#deployments' do + it 'stores the deployments' do + expect(rollout_status.deployments).to be_kind_of(Array) + expect(rollout_status.deployments.size).to eq(2) + expect(rollout_status.deployments.first).to be_kind_of(::Gitlab::Kubernetes::Deployment) + end + end + + describe '#instances' do + context 'for stable track' do + let(:track) { "any" } + + let(:pods) do + create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: "any") + end + + it 'stores the union of deployment instances' do + expected = [ + { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'any', stable: false }, + { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'any', stable: false }, + { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'any', stable: false }, + { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true }, + { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true }, + { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true } + ] + + expect(rollout_status.instances).to eq(expected) + end + end + + context 'for stable track' do + let(:track) { 'canary' } + + let(:pods) do + create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track) + end + + it 'sorts stable instances last' do + expected = [ + { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'canary', stable: false }, + { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'canary', stable: false }, + { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'canary', stable: false }, + { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true }, + { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true }, + { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true } + ] + + expect(rollout_status.instances).to eq(expected) + end + end + end + + describe '#completion' do + subject { rollout_status.completion } + + context 'when all instances are finished' do + let(:track) { 'canary' } + + it { is_expected.to eq(100) } + end + + context 'when half of the instances are finished' do + let(:track) { "canary" } + + let(:pods) do + create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track, status: "Pending") + end + + let(:specs) { specs_half_finished } + + it { is_expected.to eq(50) } + end + + context 'with one deployment' do + it 'sets the completion percentage when a deployment has more running pods than desired' do + deployments = [kube_deployment(name: 'one', track: 'one', replicas: 2)] + pods = create_pods(name: 'one', track: 'one', count: 3) + rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods) + + expect(rollout_status.completion).to eq(100) + end + end + + context 'with two deployments on different tracks' do + it 'sets the completion percentage when all pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'one', replicas: 2), + kube_deployment(name: 'two', track: 'two', replicas: 2) + ] + pods = create_pods(name: 'one', track: 'one', count: 2) + create_pods(name: 'two', track: 'two', count: 2) + rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods) + + expect(rollout_status.completion).to eq(100) + end + end + + context 'with two deployments that both have track set to "stable"' do + it 'sets the completion percentage when all pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'stable', replicas: 2), + kube_deployment(name: 'two', track: 'stable', replicas: 2) + ] + pods = create_pods(name: 'one', track: 'stable', count: 2) + create_pods(name: 'two', track: 'stable', count: 2) + rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods) + + expect(rollout_status.completion).to eq(100) + end + + it 'sets the completion percentage when no pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'stable', replicas: 3), + kube_deployment(name: 'two', track: 'stable', replicas: 7) + ] + rollout_status = described_class.from_deployments(*deployments, pods_attrs: []) + + expect(rollout_status.completion).to eq(0) + end + + it 'sets the completion percentage when a quarter of the pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'stable', replicas: 6), + kube_deployment(name: 'two', track: 'stable', replicas: 2) + ] + pods = create_pods(name: 'one', track: 'stable', count: 2) + rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods) + + expect(rollout_status.completion).to eq(25) + end + end + + context 'with two deployments, one with track set to "stable" and one with no track label' do + it 'sets the completion percentage when all pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'stable', replicas: 3), + kube_deployment(name: 'two', track: nil, replicas: 3) + ] + pods = create_pods(name: 'one', track: 'stable', count: 3) + create_pods(name: 'two', track: nil, count: 3) + rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods) + + expect(rollout_status.completion).to eq(100) + end + + it 'sets the completion percentage when no pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'stable', replicas: 1), + kube_deployment(name: 'two', track: nil, replicas: 1) + ] + rollout_status = described_class.from_deployments(*deployments, pods_attrs: []) + + expect(rollout_status.completion).to eq(0) + end + + it 'sets the completion percentage when a third of the pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'stable', replicas: 2), + kube_deployment(name: 'two', track: nil, replicas: 7) + ] + pods = create_pods(name: 'one', track: 'stable', count: 2) + create_pods(name: 'two', track: nil, count: 1) + rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods) + + expect(rollout_status.completion).to eq(33) + end + end + end + + describe '#complete?' do + subject { rollout_status.complete? } + + context 'when all instances are finished' do + let(:track) { 'canary' } + + it { is_expected.to be_truthy } + end + + context 'when half of the instances are finished' do + let(:track) { "canary" } + + let(:pods) do + create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track, status: "Pending") + end + + let(:specs) { specs_half_finished } + + it { is_expected.to be_falsy} + end + end + + describe '#found?' do + context 'when the specs are passed' do + it { is_expected.to be_found } + end + + context 'when list of specs is empty' do + let(:specs) { [] } + + it { is_expected.not_to be_found } + end + end + + describe '.loading' do + subject { described_class.loading } + + it { is_expected.to be_loading } + end + + describe '#not_found?' do + context 'when the specs are passed' do + it { is_expected.not_to be_not_found } + end + + context 'when list of specs is empty' do + let(:specs) { [] } + + it { is_expected.to be_not_found } + end + end + + describe '#canary_ingress_exists?' do + context 'when canary ingress exists' do + let(:ingresses) { [kube_ingress(track: :canary)] } + + it 'returns true' do + expect(rollout_status.canary_ingress_exists?).to eq(true) + end + end + + context 'when canary ingress does not exist' do + let(:ingresses) { [kube_ingress(track: :stable)] } + + it 'returns false' do + expect(rollout_status.canary_ingress_exists?).to eq(false) + end + end + end + + def create_pods(name:, count:, track: nil, status: 'Running' ) + Array.new(count, kube_pod(name: name, status: status, track: track)) + end +end |