Commit 83396a95 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'issue_5242' into 'master'

Show pod name for each deploy replica

Closes #5242

See merge request gitlab-org/gitlab-ee!5523
parents 8ba3b6d4 b155898d
......@@ -229,12 +229,12 @@
justify-content: center;
align-items: center;
&-finished {
&-running {
background-color: $green-100;
border-color: $green-400;
}
&-deploying {
&-succeeded {
background-color: $green-50;
border-color: $green-400;
}
......@@ -244,17 +244,12 @@
border-color: $red-500;
}
&-ready {
background-color: lighten($border-color, 1%);
border-color: $border-color;
}
&-preparing {
background-color: lighten($border-color, 5%);
border-color: $border-color;
&-unknown {
background-color: $red-200;
border-color: $red-500;
}
&-waiting {
&-pending {
background-color: $white-light;
border-color: $border-color;
}
......
......@@ -2,9 +2,10 @@ module EE
module KubernetesService
def rollout_status(environment)
result = with_reactive_cache do |data|
specs = filter_by_label(data[:deployments], app: environment.slug)
deployments = filter_by_label(data[:deployments], app: environment.slug)
pods = filter_by_label(data[:pods], app: environment.slug) if data[:pods]&.any?
::Gitlab::Kubernetes::RolloutStatus.from_specs(*specs)
::Gitlab::Kubernetes::RolloutStatus.from_deployments(*deployments, pods: pods)
end
result || ::Gitlab::Kubernetes::RolloutStatus.loading
end
......
---
title: Show pod name for each instance on deploy boards
merge_request:
author:
type: changed
module Gitlab
module Kubernetes
class Deployment
def initialize(attributes = {})
include Gitlab::Utils::StrongMemoize
STABLE_TRACK_VALUE = 'stable'.freeze
def initialize(attributes = {}, pods: {})
@attributes = attributes
@pods = pods
end
def name
metadata['name']
metadata['name'] || 'unknown'
end
def labels
......@@ -14,7 +19,7 @@ module Gitlab
end
def track
labels.fetch('track', 'stable')
labels.fetch('track', STABLE_TRACK_VALUE)
end
def stable?
......@@ -29,49 +34,58 @@ module Gitlab
observed_generation < generation
end
def wanted_replicas
def wanted_instances
spec.fetch('replicas', 0)
end
def finished_replicas
status.fetch('availableReplicas', 0)
def created_instances
filtered_pods_by_track.map do |pod|
metadata = pod.fetch('metadata', {})
pod_name = metadata['name'] || metadata['generate_name']
pod_status = pod.dig('status', 'phase')
deployment_instance(pod_name: pod_name, pod_status: pod_status)
end
end
def deploying_replicas
updated_replicas - finished_replicas
# These are replicas that did not get created yet,
# So they still do not have any associated pod,
# these are marked as pending instances.
def not_created_instances
pending_instances_count = wanted_instances - filtered_pods_by_track.count
return [] if pending_instances_count <= 0
Array.new(pending_instances_count, deployment_instance(pod_name: 'Not provided', pod_status: 'Pending'))
end
def waiting_replicas
wanted_replicas - updated_replicas
def filtered_pods_by_track
strong_memoize(:filtered_pods_by_track) do
@pods.select { |pod| has_same_track?(pod) }
end
end
def instances
return deployment_instances(wanted_replicas, 'unknown', 'waiting') if name.nil?
return deployment_instances(wanted_replicas, name, 'waiting') if outdated?
out = deployment_instances(finished_replicas, name, 'finished')
out.push(*deployment_instances(deploying_replicas, name, 'deploying', out.size))
out.push(*deployment_instances(waiting_replicas, name, 'waiting', out.size))
out
created_instances + not_created_instances
end
private
def deployment_instances(n, name, status, offset = 0)
return [] if n < 0
Array.new(n) { |idx| deployment_instance(idx + offset, name, status) }
end
def deployment_instance(n, name, status)
def deployment_instance(pod_name:, pod_status:)
{
status: status,
tooltip: "#{name} (pod #{n}) #{status.capitalize}",
status: pod_status&.downcase,
tooltip: "#{name} (#{pod_name}) #{pod_status}",
track: track,
stable: stable?
}
end
def has_same_track?(pod)
pod_track = pod.dig('metadata', 'labels', 'track') || STABLE_TRACK_VALUE
pod_track == track
end
def metadata
@attributes.fetch('metadata', {})
end
......@@ -84,10 +98,6 @@ module Gitlab
@attributes.fetch('status', {})
end
def updated_replicas
status.fetch('updatedReplicas', 0)
end
def generation
metadata.fetch('generation', 0)
end
......
......@@ -8,6 +8,14 @@ module Gitlab
class RolloutStatus
attr_reader :deployments, :instances, :completion, :status
STATUS_MAP = {
running: 'running',
failed: 'failed',
unkonw: 'unknown',
succeeded: 'succeeded',
pending: 'pending'
}.freeze
def complete?
completion == 100
end
......@@ -24,10 +32,10 @@ module Gitlab
@status == :found
end
def self.from_specs(*specs)
return new([], status: :not_found) if specs.empty?
def self.from_deployments(*deployments, pods: {})
return new([], status: :not_found) if deployments.empty?
deployments = specs.map { |spec| ::Gitlab::Kubernetes::Deployment.new(spec) }
deployments = deployments.map { |deploy| ::Gitlab::Kubernetes::Deployment.new(deploy, pods: pods) }
deployments.sort_by!(&:order)
new(deployments)
end
......@@ -45,7 +53,7 @@ module Gitlab
if @instances.empty?
100
else
finished = @instances.select { |instance| instance[:status] == 'finished' }.count
finished = @instances.select { |instance| instance[:status] == STATUS_MAP[:running] }.count
(finished / @instances.count.to_f * 100).to_i
end
......
require 'spec_helper'
describe Gitlab::Kubernetes::Deployment do
subject(:deployment) { described_class.new(params) }
include KubernetesHelpers
let(:pods) { {} }
subject(:deployment) { described_class.new(params, pods: pods) }
describe '#name' do
let(:params) { named(:selected) }
......@@ -17,58 +21,61 @@ describe Gitlab::Kubernetes::Deployment do
describe '#outdated?' do
context 'when outdated' do
let(:params) { generation(2, 1) }
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) }
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) }
let(:params) { generation(1, 2, 0) }
it { expect(deployment.outdated?).to be_falsy }
end
end
describe '#wanted_replicas' do
let(:params) { make('spec', 'replicas' => :selected ) }
it { expect(deployment.wanted_replicas).to eq(:selected) }
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
describe '#finished_replicas' do
let(:params) { make('status', 'availableReplicas' => :selected) }
let(:params) { combine(generation(1, 1, 4)) }
it { expect(deployment.finished_replicas).to eq(:selected) }
end
describe '#deploying_replicas' do
let(:params) { make('status', 'availableReplicas' => 2, 'updatedReplicas' => 4) }
it 'returns all pods with generated names and pending' do
expected = [
{ status: 'pending', tooltip: 'unknown (generated-name-with-suffix) Pending', track: 'stable', stable: true },
{ status: 'pending', tooltip: 'unknown (generated-name-with-suffix) Pending', track: 'stable', stable: true },
{ status: 'pending', tooltip: 'unknown (generated-name-with-suffix) Pending', track: 'stable', stable: true },
{ status: 'pending', tooltip: 'unknown (generated-name-with-suffix) Pending', track: 'stable', stable: true }
]
it { expect(deployment.deploying_replicas).to eq(2) }
expect(deployment.instances).to eq(expected)
end
describe '#waiting_replicas' do
let(:params) { combine(make('spec', 'replicas' => 4), make('status', 'updatedReplicas' => 2)) }
it { expect(deployment.waiting_replicas).to eq(2) }
end
describe '#instances' do
context 'when unnamed' do
let(:params) { combine(generation(1, 1), instances) }
# 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 all instances as unknown and waiting' do
it 'returns not spawned pods as pending and unknown and running' do
expected = [
{ status: 'waiting', tooltip: 'unknown (pod 0) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'unknown (pod 1) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'unknown (pod 2) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'unknown (pod 3) Waiting', track: 'stable', stable: true }
{ status: 'running', tooltip: 'unknown (generated-name-with-suffix) Running', track: 'stable', stable: true },
{ status: 'pending', tooltip: 'unknown (Not provided) Pending', track: 'stable', stable: true },
{ status: 'pending', tooltip: 'unknown (Not provided) Pending', track: 'stable', stable: true }
]
expect(deployment.instances).to eq(expected)
......@@ -76,14 +83,23 @@ describe Gitlab::Kubernetes::Deployment do
end
context 'when outdated' do
let(:params) { combine(named('foo'), generation(1, 0), instances) }
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: 'waiting', tooltip: 'foo (pod 0) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'foo (pod 1) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'foo (pod 2) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'foo (pod 3) Waiting', track: 'stable', stable: true }
{ status: 'pending', tooltip: 'foo (kube-pod) Pending', track: 'stable', stable: true },
{ status: 'pending', tooltip: 'foo (kube-pod1) Pending', track: 'stable', stable: true },
{ status: 'pending', tooltip: 'foo (kube-pod2) Pending', track: 'stable', stable: true },
{ status: 'pending', tooltip: 'foo (kube-pod3) Pending', track: 'stable', stable: true }
]
expect(deployment.instances).to eq(expected)
......@@ -91,14 +107,23 @@ describe Gitlab::Kubernetes::Deployment do
end
context 'with pods of each type' do
let(:params) { combine(named('foo'), generation(1, 1), instances) }
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: 'finished', tooltip: 'foo (pod 0) Finished', track: 'stable', stable: true },
{ status: 'deploying', tooltip: 'foo (pod 1) Deploying', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'foo (pod 2) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'foo (pod 3) Waiting', track: 'stable', stable: true }
{ status: 'succeeded', tooltip: 'foo (kube-pod) Succeeded', track: 'stable', stable: true },
{ status: 'running', tooltip: 'foo (kube-pod1) Running', track: 'stable', stable: true },
{ status: 'pending', tooltip: 'foo (kube-pod2) Pending', track: 'stable', stable: true },
{ status: 'pending', tooltip: 'foo (kube-pod3) Pending', track: 'stable', stable: true }
]
expect(deployment.instances).to eq(expected)
......@@ -106,15 +131,16 @@ describe Gitlab::Kubernetes::Deployment do
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), instances(1, 1, 1, labels)) }
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: 'waiting', tooltip: 'foo (pod 0) Waiting', track: 'stable', stable: true }
{ status: 'pending', tooltip: 'foo (kube-pod) Pending', track: 'stable', stable: true }
]
expect(deployment.instances).to eq(expected)
......@@ -123,10 +149,11 @@ describe Gitlab::Kubernetes::Deployment do
context 'when marked as canary' do
let(:track) { 'canary' }
let(:pods) { [kube_pod(status: 'Pending', track: track)] }
it 'returns all instances' do
expected = [
{ status: 'waiting', tooltip: 'foo (pod 0) Waiting', track: 'canary', stable: false }
{ status: 'pending', tooltip: 'foo (kube-pod) Pending', track: 'canary', stable: false }
]
expect(deployment.instances).to eq(expected)
......@@ -135,10 +162,11 @@ describe Gitlab::Kubernetes::Deployment do
end
end
def generation(expected, observed)
def generation(expected, observed, replicas)
combine(
make('metadata', 'generation' => expected),
make('status', 'observedGeneration' => observed)
make('status', 'observedGeneration' => observed),
make('spec', 'replicas' => replicas)
)
end
......@@ -146,13 +174,6 @@ describe Gitlab::Kubernetes::Deployment do
make('metadata', 'name' => name, 'labels' => labels)
end
def instances(replicas = 4, available = 1, updated = 2, labels = {})
combine(
make('spec', 'replicas' => replicas),
make('status', 'availableReplicas' => available, 'updatedReplicas' => updated)
)
end
def make(key, values = {})
hsh = {}
hsh[key] = values
......
......@@ -7,6 +7,10 @@ describe Gitlab::Kubernetes::RolloutStatus do
let(:specs) { specs_all_finished }
let(:specs_none) { [] }
let(:pods) do
create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: "canary")
end
let(:specs_all_finished) do
[
kube_deployment(name: 'one'),
......@@ -18,11 +22,10 @@ describe Gitlab::Kubernetes::RolloutStatus do
[
kube_deployment(name: 'one'),
kube_deployment(name: 'two', track: track)
.deep_merge('status' => { 'availableReplicas' => 0 })
]
end
subject(:rollout_status) { described_class.from_specs(*specs) }
subject(:rollout_status) { described_class.from_deployments(*specs, pods: pods) }
describe '#deployments' do
it 'stores the deployments' do
......@@ -34,14 +37,20 @@ describe Gitlab::Kubernetes::RolloutStatus do
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: 'finished', tooltip: 'one (pod 0) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'one (pod 1) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'one (pod 2) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'two (pod 0) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'two (pod 1) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'two (pod 2) Finished', track: 'stable', stable: true }
{ status: 'running', tooltip: 'two (two) Running', track: 'any', stable: false },
{ status: 'running', tooltip: 'two (two) Running', track: 'any', stable: false },
{ status: 'running', tooltip: 'two (two) Running', track: 'any', stable: false },
{ status: 'running', tooltip: 'one (one) Running', track: 'stable', stable: true },
{ status: 'running', tooltip: 'one (one) Running', track: 'stable', stable: true },
{ status: 'running', tooltip: 'one (one) Running', track: 'stable', stable: true }
]
expect(rollout_status.instances).to eq(expected)
......@@ -51,14 +60,18 @@ describe Gitlab::Kubernetes::RolloutStatus do
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 'stores the union of deployment instances' do
expected = [
{ status: 'finished', tooltip: 'two (pod 0) Finished', track: 'canary', stable: false },
{ status: 'finished', tooltip: 'two (pod 1) Finished', track: 'canary', stable: false },
{ status: 'finished', tooltip: 'two (pod 2) Finished', track: 'canary', stable: false },
{ status: 'finished', tooltip: 'one (pod 0) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'one (pod 1) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'one (pod 2) Finished', track: 'stable', stable: true }
{ status: 'running', tooltip: 'two (two) Running', track: 'canary', stable: false },
{ status: 'running', tooltip: 'two (two) Running', track: 'canary', stable: false },
{ status: 'running', tooltip: 'two (two) Running', track: 'canary', stable: false },
{ status: 'running', tooltip: 'one (one) Running', track: 'stable', stable: true },
{ status: 'running', tooltip: 'one (one) Running', track: 'stable', stable: true },
{ status: 'running', tooltip: 'one (one) Running', track: 'stable', stable: true }
]
expect(rollout_status.instances).to eq(expected)
......@@ -74,6 +87,12 @@ describe Gitlab::Kubernetes::RolloutStatus do
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) }
......@@ -88,6 +107,12 @@ describe Gitlab::Kubernetes::RolloutStatus do
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}
......@@ -123,4 +148,8 @@ describe Gitlab::Kubernetes::RolloutStatus do
it { is_expected.to be_loading }
end
def create_pods(name:, count:, track: nil, status: 'Running' )
Array.new(count, kube_pod(name: name, status: status, track: track))
end
end
......@@ -16,7 +16,8 @@ describe KubernetesService, models: true, use_clean_rails_memory_store_caching:
before do
stub_reactive_cache(
service,
deployments: [kube_deployment(app: environment.slug), kube_deployment]
deployments: [kube_deployment(app: environment.slug), kube_deployment],
pods: [kube_pod(app: environment.slug), kube_pod(app: environment.slug, status: 'Pending')]
)
end
......
......@@ -98,12 +98,16 @@ module KubernetesHelpers
# This is a partial response, it will have many more elements in reality but
# these are the ones we care about at the moment
def kube_pod(name: "kube-pod", app: "valid-pod-label")
def kube_pod(name: "kube-pod", app: "valid-pod-label", status: "Running", track: nil)
{
"metadata" => {
"name" => name,
"generate_name" => "generated-name-with-suffix",
"creationTimestamp" => "2016-11-25T19:55:19Z",
"labels" => { "app" => app }
"labels" => {
"app" => app,
"track" => track
}
},
"spec" => {
"containers" => [
......@@ -111,7 +115,7 @@ module KubernetesHelpers
{ "name" => "container-1" }
]
},
"status" => { "phase" => "Running" }
"status" => { "phase" => status }
}
end
......@@ -154,10 +158,10 @@ module KubernetesHelpers
end
def kube_deployment_rollout_status
::Gitlab::Kubernetes::RolloutStatus.from_specs(kube_deployment)
::Gitlab::Kubernetes::RolloutStatus.from_deployments(kube_deployment)
end
def empty_deployment_rollout_status
::Gitlab::Kubernetes::RolloutStatus.from_specs()
::Gitlab::Kubernetes::RolloutStatus.from_deployments()
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