Commit c67abeaa authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'canary-deployments' into 'master'

Read track from deployment and visualise canary deployments

See merge request !1551
parents 644abaf2 48a551e8
......@@ -163,7 +163,8 @@ export default {
<template v-for="instance in deployBoardData.instances">
<instance-component
:status="instance.status"
:tooltipText="instance.tooltip"/>
:tooltip-text="instance.tooltip"
:stable="instance.stable" />
</template>
</div>
</section>
......
......@@ -7,6 +7,9 @@
* see more information about this in
* https://gitlab.com/gitlab-org/gitlab-ee/uploads/5fff049fd88336d9ee0c6ef77b1ba7e3/monitoring__deployboard--key.png
*
* An instance can represent a normal deploy or a canary deploy. In the latter we need to provide
* this information in the tooltip and the colors.
* Mockup is https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1551#note_26595150
*/
export default {
......@@ -28,11 +31,23 @@ export default {
required: false,
default: '',
},
stable: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
cssClass() {
return `deploy-board-instance-${this.status}`;
let cssClassName = `deploy-board-instance-${this.status}`;
if (!this.stable) {
cssClassName = `${cssClassName} deploy-board-instance-canary`;
}
return cssClassName;
},
},
......
......@@ -240,6 +240,9 @@
border-width: 1px;
border-style: solid;
margin: 1px;
display: flex;
justify-content: center;
align-items: center;
&-finished {
background-color: $green-100;
......@@ -270,6 +273,17 @@
background-color: $white-light;
border-color: $border-color;
}
&.deploy-board-instance-canary {
&::after {
width: 7px;
height: 7px;
border: 1px solid $white-light;
background-color: $orange-300;
border-radius: 50%;
content: "";
}
}
}
.deploy-board-icon i {
......
......@@ -116,6 +116,8 @@ class Project < ActiveRecord::Base
has_one :prometheus_service, dependent: :destroy, inverse_of: :project
has_one :index_status, dependent: :destroy
has_one :mock_ci_service, dependent: :destroy
has_one :mock_deployment_service, dependent: :destroy
has_one :mock_monitoring_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
......
class MockDeploymentService < DeploymentService
def title
'Mock deployment'
end
def description
'Mock deployment service'
end
def self.to_param
'mock_deployment'
end
# No terminals support
def terminals(environment)
[]
end
def rollout_status(environment)
OpenStruct.new(
instances: rollout_status_instances,
completion: 80,
valid?: true,
complete?: true
)
end
private
def rollout_status_instances
JSON.parse(Rails.root.join('spec', 'fixtures', 'rollout_status_instances.json'))
end
end
class MockMonitoringService < MonitoringService
def title
'Mock monitoring'
end
def description
'Mock monitoring service'
end
def self.to_param
'mock_monitoring'
end
def metrics(environment)
JSON.parse(Rails.root.join('spec', 'fixtures', 'metrics.json'))
end
end
......@@ -240,7 +240,9 @@ class Service < ActiveRecord::Base
slack
teamcity
]
service_names << 'mock_ci' if Rails.env.development?
if Rails.env.development?
service_names += %w[mock_ci mock_deployment mock_monitoring]
end
service_names.sort_by(&:downcase)
end
......
---
title: Added mock data for Deployboard
merge_request:
author:
---
title: Visualise Canary Deployments
merge_request:
author:
---
title: Added mock deployment and monitoring service with environments fixtures
merge_request:
author:
require './spec/support/sidekiq'
class Gitlab::Seeder::Environments
def initialize(project)
@project = project
end
def seed!
@project.create_mock_deployment_service!(active: true)
@project.create_mock_monitoring_service!(active: true)
create_master_deployments!('production')
create_master_deployments!('staging')
create_merge_request_review_deployments!
end
private
def create_master_deployments!(name)
@project.repository.commits('master', limit: 4).map do |commit|
create_deployment!(
@project,
name,
'master',
commit.id
)
end
end
def create_merge_request_review_deployments!
@project.merge_requests.sample(4).map do |merge_request|
next unless merge_request.diff_head_sha
create_deployment!(
merge_request.source_project,
"review/#{merge_request.source_branch}",
merge_request.source_branch,
merge_request.diff_head_sha
)
end
end
def create_deployment!(project, name, ref, sha)
environment = find_or_create_environment!(project, name)
environment.deployments.create!(
project: project,
ref: ref,
sha: sha,
tag: false,
deployable: find_deployable(project, name)
)
end
def find_or_create_environment!(project, name)
project.environments.find_or_create_by!(name: name).tap do |environment|
environment.update(external_url: "https://google.com/#{name}")
end
end
def find_deployable(project, environment)
project.builds.where(environment: environment).sample
end
end
Gitlab::Seeder.quiet do
Project.all.sample(5).each do |project|
project_environments = Gitlab::Seeder::Environments.new(project)
project_environments.seed!
end
end
......@@ -610,8 +610,14 @@ module API
desc: 'URL to the mock service'
}
]
services['mock-deployment'] = []
services['mock-monitoring'] = []
service_classes << MockCiService
service_classes += [
MockCiService,
MockDeploymentService,
MockMonitoringService,
]
end
trigger_services = {
......
......@@ -10,7 +10,15 @@ module Gitlab
end
def labels
metadata['labels']
metadata.fetch('labels', {})
end
def track
labels.fetch('track', 'stable')
end
def stable?
track == 'stable'
end
def outdated?
......@@ -52,7 +60,12 @@ module Gitlab
end
def deployment_instance(n, name, status)
{ status: status, tooltip: "#{name} (pod #{n}) #{status.capitalize}" }
{
status: status,
tooltip: "#{name} (pod #{n}) #{status.capitalize}",
track: track,
stable: stable?
}
end
def metadata
......
This diff is collapsed.
[
{
"status": "finished",
"tooltip": "production (pod 0) Finished",
"track": "stable",
"stable": true
},
{
"status": "deploying",
"tooltip": "production (pod 1) Deploying",
"track": "stable",
"stable": true
},
{
"status": "failed",
"tooltip": "production (pod 2) Failed",
"track": "stable",
"stable": true
},
{
"status": "ready",
"tooltip": "production (pod 3) Ready",
"track": "stable",
"stable": true
},
{
"status": "preparing",
"tooltip": "production (pod 4) Preparing",
"track": "stable",
"stable": true
},
{
"status": "waiting",
"tooltip": "production (pod 5) Waiting",
"track": "stable",
"stable": true
},
{
"status": "finished",
"tooltip": "production-canary (pod 0) Finished",
"track": "canary",
"stable": false
},
{
"status": "deploying",
"tooltip": "production-canary (pod 1) Deploying",
"track": "canary",
"stable": false
},
{
"status": "failed",
"tooltip": "production-canary (pod 2) Failed",
"track": "canary",
"stable": false
},
{
"status": "ready",
"tooltip": "production-canary (pod 3) Ready",
"track": "canary",
"stable": false
},
{
"status": "preparing",
"tooltip": "production-canary (pod 4) Preparing",
"track": "canary",
"stable": false
},
{
"status": "waiting",
"tooltip": "production-canary (pod 5) Waiting",
"track": "canary",
"stable": false
}
]
......@@ -30,4 +30,15 @@ describe('Deploy Board Instance', () => {
expect(component.$el.classList.contains('deploy-board-instance-deploying')).toBe(true);
expect(component.$el.getAttribute('data-title')).toEqual('');
});
it('should render a div with canary class when stable prop is provided as false', () => {
const component = new DeployBoardInstanceComponent({
propsData: {
status: 'deploying',
stable: false,
},
}).$mount();
expect(component.$el.classList.contains('deploy-board-instance-canary')).toBe(true);
});
});
......@@ -164,6 +164,8 @@ project:
- external_wiki_service
- kubernetes_service
- mock_ci_service
- mock_deployment_service
- mock_monitoring_service
- forked_project_link
- forked_from_project
- forked_project_links
......
......@@ -5,60 +5,70 @@ describe Gitlab::Kubernetes::Deployment do
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) }
it { expect(deployment.outdated?).to be_truthy }
end
context 'when up to date' do
let(:params) { generation(2, 2) }
it { expect(deployment.outdated?).to be_falsy }
end
context 'when ahead of latest' do
let(:params) { generation(1, 2) }
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) }
end
describe '#finished_replicas' do
let(:params) { make('status', 'availableReplicas' => :selected) }
it { expect(deployment.finished_replicas).to eq(:selected) }
end
describe '#deploying_replicas' do
let(:params) { make('status', 'availableReplicas' => 2, 'updatedReplicas' => 4) }
it { expect(deployment.deploying_replicas).to eq(2) }
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) }
it 'returns all instances as unknown and waiting' do
expected = [
{ status: 'waiting', tooltip: 'unknown (pod 0) Waiting' },
{ status: 'waiting', tooltip: 'unknown (pod 1) Waiting' },
{ status: 'waiting', tooltip: 'unknown (pod 2) Waiting' },
{ status: 'waiting', tooltip: 'unknown (pod 3) Waiting' },
{ 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 },
]
expect(deployment.instances).to eq(expected)
......@@ -67,12 +77,13 @@ describe Gitlab::Kubernetes::Deployment do
context 'when outdated' do
let(:params) { combine(named('foo'), generation(1, 0), instances) }
it 'returns all instances as named and waiting' do
expected = [
{ status: 'waiting', tooltip: 'foo (pod 0) Waiting' },
{ status: 'waiting', tooltip: 'foo (pod 1) Waiting' },
{ status: 'waiting', tooltip: 'foo (pod 2) Waiting' },
{ status: 'waiting', tooltip: 'foo (pod 3) Waiting' },
{ 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 },
]
expect(deployment.instances).to eq(expected)
......@@ -84,15 +95,44 @@ describe Gitlab::Kubernetes::Deployment do
it 'returns all instances' do
expected = [
{ status: 'finished', tooltip: 'foo (pod 0) Finished' },
{ status: 'deploying', tooltip: 'foo (pod 1) Deploying' },
{ status: 'waiting', tooltip: 'foo (pod 2) Waiting' },
{ status: 'waiting', tooltip: 'foo (pod 3) Waiting' },
{ 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 },
]
expect(deployment.instances).to eq(expected)
end
end
context 'with track label' do
let(:labels) { { 'track' => track } }
let(:params) { combine(named('foo', labels), generation(1, 0), instances(1, 1, 1, labels)) }
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 },
]
expect(deployment.instances).to eq(expected)
end
end
context 'when marked as canary' do
let(:track) { 'canary' }
it 'returns all instances' do
expected = [
{ status: 'waiting', tooltip: 'foo (pod 0) Waiting', track: 'canary', stable: false },
]
expect(deployment.instances).to eq(expected)
end
end
end
end
def generation(expected, observed)
......@@ -102,14 +142,14 @@ describe Gitlab::Kubernetes::Deployment do
)
end
def named(name = "foo")
make('metadata', 'name' => name)
def named(name = "foo", labels = {})
make('metadata', 'name' => name, 'labels' => labels)
end
def instances
def instances(replicas = 4, available = 1, updated = 2, labels = {})
combine(
make('spec', 'replicas' => 4),
make('status', 'availableReplicas' => 1, 'updatedReplicas' => 2),
make('spec', 'replicas' => replicas),
make('status', 'availableReplicas' => available, 'updatedReplicas' => updated),
)
end
......
......@@ -2,16 +2,25 @@ require 'spec_helper'
describe Gitlab::Kubernetes::RolloutStatus do
include KubernetesHelpers
let(:specs_all_finished) { [kube_deployment(name: 'one'), kube_deployment(name: 'two')] }
let(:specs_half_finished) do
let(:track) { nil }
let(:specs) { specs_all_finished }
let(:specs_none) { [] }
let(:specs_all_finished) do
[
kube_deployment(name: 'one'),
kube_deployment(name: 'two').deep_merge('status' => { 'availableReplicas' => 0 })
kube_deployment(name: 'two', track: track)
]
end
let(:specs) { specs_all_finished }
let(:specs_none) { [] }
let(:specs_half_finished) 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) }
......@@ -24,17 +33,36 @@ describe Gitlab::Kubernetes::RolloutStatus do
end
describe '#instances' do
it 'stores the union of deployment instances' do
expected = [
{ status: 'finished', tooltip: 'one (pod 0) Finished' },
{ status: 'finished', tooltip: 'one (pod 1) Finished' },
{ status: 'finished', tooltip: 'one (pod 2) Finished' },
{ status: 'finished', tooltip: 'two (pod 0) Finished' },
{ status: 'finished', tooltip: 'two (pod 1) Finished' },
{ status: 'finished', tooltip: 'two (pod 2) Finished' },
]
expect(rollout_status.instances).to eq(expected)
context 'for stable track' do
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 },
]
expect(rollout_status.instances).to eq(expected)
end
end
context 'for stable track' do
let(:track) { 'canary' }
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: '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 },
]
expect(rollout_status.instances).to eq(expected)
end
end
end
......@@ -47,6 +75,7 @@ describe Gitlab::Kubernetes::RolloutStatus do
context 'when half of the instances are finished' do
let(:specs) { specs_half_finished }
it { is_expected.to eq(50) }
end
end
......@@ -60,6 +89,7 @@ describe Gitlab::Kubernetes::RolloutStatus do
context 'when half of the instances are finished' do
let(:specs) { specs_half_finished }
it { is_expected.to be_falsy}
end
end
......
......@@ -85,12 +85,15 @@ module KubernetesHelpers
}
end
def kube_deployment(name: "kube-deployment", app: "valid-deployment-label")
def kube_deployment(name: "kube-deployment", app: "valid-deployment-label", track: nil)
{
"metadata" => {
"name" => name,
"generation" => 4,
"labels" => { "app" => app },
"labels" => {
"app" => app,
"track" => track
}.compact,
},
"spec" => { "replicas" => 3 },
"status" => {
......
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