Commit 7ee20cb9 authored by Thong Kuah's avatar Thong Kuah

Merge branch 'remove-dedupe-instances-feature-flag' into 'master'

Rollout Deploy Board Duplicate Instances Fix to Self-Managed

See merge request gitlab-org/gitlab!46374
parents 18cbc266 264a850a
---
name: deploy_boards_dedupe_instances
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40768
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258214
type: development
group: group::progressive delivery
default_enabled: false
...@@ -41,9 +41,9 @@ knowledge. In particular, you should be familiar with: ...@@ -41,9 +41,9 @@ knowledge. In particular, you should be familiar with:
- [Kubernetes namespaces](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) - [Kubernetes namespaces](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/)
- [Kubernetes canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments) - [Kubernetes canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
NOTE: **Note:** In GitLab 13.5 and earlier, apps that consist of multiple deployments are shown as
Apps that consist of multiple deployments are shown as duplicates on the deploy board. duplicates on the deploy board. This is [fixed](https://gitlab.com/gitlab-org/gitlab/-/issues/8463)
Follow [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/8463) for details. in GitLab 13.6.
## Use cases ## Use cases
......
---
title: Fix duplicate instances in deploy boards when multiple deployments have the same track
merge_request: 46374
author:
type: fixed
...@@ -54,13 +54,7 @@ module Gitlab ...@@ -54,13 +54,7 @@ module Gitlab
def initialize(deployments, pods: [], ingresses: [], status: :found) def initialize(deployments, pods: [], ingresses: [], status: :found)
@status = status @status = status
@deployments = deployments @deployments = deployments
@instances = RolloutInstances.new(deployments, pods).pod_instances
@instances = if ::Feature.enabled?(:deploy_boards_dedupe_instances)
RolloutInstances.new(deployments, pods).pod_instances
else
deployments.flat_map(&:instances)
end
@canary_ingress = ingresses.find(&:canary?) @canary_ingress = ingresses.find(&:canary?)
@completion = @completion =
......
...@@ -30,257 +30,237 @@ RSpec.describe Gitlab::Kubernetes::RolloutStatus do ...@@ -30,257 +30,237 @@ RSpec.describe Gitlab::Kubernetes::RolloutStatus do
subject(:rollout_status) { described_class.from_deployments(*specs, pods_attrs: pods, ingresses: ingresses) } subject(:rollout_status) { described_class.from_deployments(*specs, pods_attrs: pods, ingresses: ingresses) }
shared_examples 'rollout status' do describe '#deployments' do
describe '#deployments' do it 'stores the deployments' do
it 'stores the deployments' do expect(rollout_status.deployments).to be_kind_of(Array)
expect(rollout_status.deployments).to be_kind_of(Array) expect(rollout_status.deployments.size).to eq(2)
expect(rollout_status.deployments.size).to eq(2) expect(rollout_status.deployments.first).to be_kind_of(::Gitlab::Kubernetes::Deployment)
expect(rollout_status.deployments.first).to be_kind_of(::Gitlab::Kubernetes::Deployment)
end
end end
end
describe '#instances' do
context 'for stable track' do
let(:track) { "any" }
describe '#instances' do let(:pods) do
context 'for stable track' do create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: "any")
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 end
context 'for stable track' do it 'stores the union of deployment instances' do
let(:track) { 'canary' } expected = [
{ status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'any', stable: false },
let(:pods) do { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'any', stable: false },
create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track) { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'any', stable: false },
end { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true },
{ status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true },
it 'sorts stable instances last' do { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true }
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 }, expect(rollout_status.instances).to eq(expected)
{ 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
end end
describe '#completion' do context 'for stable track' do
subject { rollout_status.completion } let(:track) { 'canary' }
context 'when all instances are finished' do let(:pods) do
let(:track) { 'canary' } create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track)
end
it { is_expected.to eq(100) } 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
end
context 'when half of the instances are finished' do describe '#completion' do
let(:track) { "canary" } subject { rollout_status.completion }
let(:pods) do context 'when all instances are finished' do
create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track, status: "Pending") let(:track) { 'canary' }
end
let(:specs) { specs_half_finished } it { is_expected.to eq(100) }
end
it { is_expected.to eq(50) } 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 end
context 'with one deployment' do let(:specs) { specs_half_finished }
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) it { is_expected.to eq(50) }
end end
end
context 'with two deployments on different tracks' do context 'with one deployment' do
it 'sets the completion percentage when all pods are complete' do it 'sets the completion percentage when a deployment has more running pods than desired' do
deployments = [ deployments = [kube_deployment(name: 'one', track: 'one', replicas: 2)]
kube_deployment(name: 'one', track: 'one', replicas: 2), pods = create_pods(name: 'one', track: 'one', count: 3)
kube_deployment(name: 'two', track: 'two', replicas: 2) rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods)
]
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 expect(rollout_status.completion).to eq(100)
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
end 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)
context 'with two deployments, one with track set to "stable" and one with no track label' do expect(rollout_status.completion).to eq(100)
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
end end
end end
describe '#complete?' do context 'with two deployments that both have track set to "stable"' do
subject { rollout_status.complete? } it 'sets the completion percentage when all pods are complete' do
deployments = [
context 'when all instances are finished' do kube_deployment(name: 'one', track: 'stable', replicas: 2),
let(:track) { 'canary' } 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)
it { is_expected.to be_truthy } expect(rollout_status.completion).to eq(100)
end end
context 'when half of the instances are finished' do it 'sets the completion percentage when no pods are complete' do
let(:track) { "canary" } 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: [])
let(:pods) do expect(rollout_status.completion).to eq(0)
create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track, status: "Pending") end
end
let(:specs) { specs_half_finished } 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)
it { is_expected.to be_falsy} expect(rollout_status.completion).to eq(25)
end end
end end
describe '#found?' do context 'with two deployments, one with track set to "stable" and one with no track label' do
context 'when the specs are passed' do it 'sets the completion percentage when all pods are complete' do
it { is_expected.to be_found } 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 end
context 'when list of specs is empty' do it 'sets the completion percentage when no pods are complete' do
let(:specs) { [] } 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: [])
it { is_expected.not_to be_found } expect(rollout_status.completion).to eq(0)
end end
end
describe '.loading' do it 'sets the completion percentage when a third of the pods are complete' do
subject { described_class.loading } 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)
it { is_expected.to be_loading } expect(rollout_status.completion).to eq(33)
end
end end
end
describe '#not_found?' do describe '#complete?' do
context 'when the specs are passed' do subject { rollout_status.complete? }
it { is_expected.not_to be_not_found }
end
context 'when list of specs is empty' do context 'when all instances are finished' do
let(:specs) { [] } let(:track) { 'canary' }
it { is_expected.to be_not_found } it { is_expected.to be_truthy }
end
end end
describe '#canary_ingress_exists?' do context 'when half of the instances are finished' do
context 'when canary ingress exists' do let(:track) { "canary" }
let(:ingresses) { [kube_ingress(track: :canary)] }
it 'returns true' do let(:pods) do
expect(rollout_status.canary_ingress_exists?).to eq(true) create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track, status: "Pending")
end
end end
context 'when canary ingress does not exist' do let(:specs) { specs_half_finished }
let(:ingresses) { [kube_ingress(track: :stable)] }
it 'returns false' do it { is_expected.to be_falsy}
expect(rollout_status.canary_ingress_exists?).to eq(false)
end
end
end end
end end
context 'deploy_boards_dedupe_instances is disabled' do describe '#found?' do
before do context 'when the specs are passed' do
stub_feature_flags(deploy_boards_dedupe_instances: false) it { is_expected.to be_found }
end end
it_behaves_like 'rollout status' 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 end
context 'deploy_boards_dedupe_instances is enabled' do describe '#not_found?' do
before do context 'when the specs are passed' do
stub_feature_flags(deploy_boards_dedupe_instances: true) it { is_expected.not_to be_not_found }
end end
it_behaves_like 'rollout status' context 'when list of specs is empty' do
let(:specs) { [] }
describe '#completion' do it { is_expected.to be_not_found }
it 'sets the completion percentage when a quarter of the pods are complete' do end
deployments = [ end
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) 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
end
it 'sets the completion percentage when a third of the pods are complete' do context 'when canary ingress does not exist' do
deployments = [ let(:ingresses) { [kube_ingress(track: :stable)] }
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) it 'returns false' do
expect(rollout_status.canary_ingress_exists?).to eq(false)
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment