Skip to content
Snippets Groups Projects
Unverified Commit f2a4420d authored by Kamil Trzcińśki's avatar Kamil Trzcińśki
Browse files

Store retried in database for CI builds

parent 6ad3814e
No related branches found
No related tags found
No related merge requests found
Showing
with 158 additions and 41 deletions
Loading
Loading
@@ -124,8 +124,8 @@ module Ci
success? || failed? || canceled?
end
 
def retried?
!self.pipeline.statuses.latest.include?(self)
def latest?
!retried?
end
 
def expanded_environment_name
Loading
Loading
Loading
Loading
@@ -18,13 +18,7 @@ class CommitStatus < ActiveRecord::Base
validates :name, presence: true
 
alias_attribute :author, :user
scope :latest, -> do
max_id = unscope(:select).select("max(#{quoted_table_name}.id)")
where(id: max_id.group(:name, :commit_id))
end
scope :failed_but_allowed, -> do
where(allow_failure: true, status: [:failed, :canceled])
end
Loading
Loading
@@ -37,7 +31,8 @@ class CommitStatus < ActiveRecord::Base
false, all_state_names - [:failed, :canceled, :manual])
end
 
scope :retried, -> { where.not(id: latest) }
scope :latest, -> { where(retried: false) }
scope :retried, -> { where(retried: true) }
scope :ordered, -> { order(:name) }
scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
Loading
Loading
Loading
Loading
@@ -5,6 +5,8 @@ module Ci
def execute(pipeline)
@pipeline = pipeline
 
update_retried
new_builds =
stage_indexes_of_created_builds.map do |index|
process_stage(index)
Loading
Loading
@@ -71,5 +73,23 @@ module Ci
def created_builds
pipeline.builds.created
end
# This method is for compatibility and data consistency and should be removed with 9.3 version of GitLab
# This replicates what is db/post_migrate/20170416103934_upate_retried_for_ci_build.rb
# and ensures that functionality will not be broken before migration is run
# this updates only when there are data that needs to be updated, there are two groups with no retried flag
def update_retried
# find the latest builds for each name
latest_statuses = pipeline.statuses.latest
.group(:name)
.having('count(*) > 1')
.pluck('max(id)', 'name')
# mark builds that are retried
pipeline.statuses.latest
.where(name: latest_statuses.map(&:second))
.where.not(id: latest_statuses.map(&:first))
.update_all(retried: true) if latest_statuses.any?
end
end
end
Loading
Loading
@@ -6,7 +6,7 @@ module Ci
description tag_list].freeze
 
def execute(build)
reprocess(build).tap do |new_build|
reprocess!(build).tap do |new_build|
build.pipeline.mark_as_processable_after_stage(build.stage_idx)
 
new_build.enqueue!
Loading
Loading
@@ -17,7 +17,7 @@ module Ci
end
end
 
def reprocess(build)
def reprocess!(build)
unless can?(current_user, :update_build, build)
raise Gitlab::Access::AccessDeniedError
end
Loading
Loading
@@ -28,7 +28,14 @@ module Ci
 
attributes.push([:user, current_user])
 
project.builds.create(Hash[attributes])
Ci::Build.transaction do
# mark all other builds of that name as retried
build.pipeline.builds.where(name: build.name)
.where.not(retried: true)
.update_all(retried: true)
project.builds.create!(Hash[attributes])
end
end
end
end
Loading
Loading
@@ -11,7 +11,7 @@ module Ci
next unless can?(current_user, :update_build, build)
 
Ci::RetryBuildService.new(project, current_user)
.reprocess(build)
.reprocess!(build)
end
 
pipeline.builds.latest.skipped.find_each do |skipped|
Loading
Loading
---
title: Store retried in database for CI Builds
merge_request:
author:
class AddRetriedToCiBuild < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:ci_builds, :retried, :boolean, default: false)
end
def down
remove_column(:ci_builds, :retried)
end
end
class UpateRetriedForCiBuild < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
disable_statement_timeout
latest_id = <<-SQL.strip_heredoc
SELECT MAX(ci_builds2.id)
FROM ci_builds ci_builds2
WHERE ci_builds.commit_id=ci_builds2.commit_id
AND ci_builds.name=ci_builds2.name
SQL
# This is slow update as it does single-row query
# This is designed to be run as idle, or a post deployment migration
is_retried = Arel.sql("((#{latest_id}) != ci_builds.id)")
update_column_in_batches(:ci_builds, :retried, is_retried) do |table, query|
query.where(table[:retried].eq(false))
end
end
def down
end
end
Loading
Loading
@@ -232,6 +232,7 @@ ActiveRecord::Schema.define(version: 20170504102911) do
t.integer "lock_version"
t.string "coverage_regex"
t.integer "auto_canceled_by_id"
t.boolean "retried", default: false, null: false
end
 
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
Loading
Loading
Loading
Loading
@@ -229,6 +229,7 @@ CommitStatus:
- lock_version
- coverage_regex
- auto_canceled_by_id
- retried
Ci::Variable:
- id
- project_id
Loading
Loading
Loading
Loading
@@ -59,8 +59,8 @@ describe Ci::Pipeline, models: true do
subject { pipeline.retried }
 
before do
@build1 = FactoryGirl.create :ci_build, pipeline: pipeline, name: 'deploy'
@build2 = FactoryGirl.create :ci_build, pipeline: pipeline, name: 'deploy'
@build1 = create(:ci_build, pipeline: pipeline, name: 'deploy', retried: true)
@build2 = create(:ci_build, pipeline: pipeline, name: 'deploy')
end
 
it 'returns old builds' do
Loading
Loading
@@ -69,31 +69,31 @@ describe Ci::Pipeline, models: true do
end
 
describe "coverage" do
let(:project) { FactoryGirl.create :empty_project, build_coverage_regex: "/.*/" }
let(:pipeline) { FactoryGirl.create :ci_empty_pipeline, project: project }
let(:project) { create(:empty_project, build_coverage_regex: "/.*/") }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
 
it "calculates average when there are two builds with coverage" do
FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline)
create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline)
expect(pipeline.coverage).to eq("35.00")
end
 
it "calculates average when there are two builds with coverage and one with nil" do
FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
FactoryGirl.create :ci_build, pipeline: pipeline
create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline)
create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline)
create(:ci_build, pipeline: pipeline)
expect(pipeline.coverage).to eq("35.00")
end
 
it "calculates average when there are two builds with coverage and one is retried" do
FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
FactoryGirl.create :ci_build, name: "rubocop", coverage: 30, pipeline: pipeline
FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline)
create(:ci_build, name: "rubocop", coverage: 30, pipeline: pipeline, retried: true)
create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline)
expect(pipeline.coverage).to eq("35.00")
end
 
it "calculates average when there is one build without coverage" do
FactoryGirl.create :ci_build, pipeline: pipeline
FactoryGirl.create(:ci_build, pipeline: pipeline)
expect(pipeline.coverage).to be_nil
end
end
Loading
Loading
@@ -221,13 +221,15 @@ describe Ci::Pipeline, models: true do
%w(deploy running)])
end
 
context 'when commit status is retried' do
context 'when commit status is retried' do
before do
create(:commit_status, pipeline: pipeline,
stage: 'build',
name: 'mac',
stage_idx: 0,
status: 'success')
pipeline.process!
end
 
it 'ignores the previous state' do
Loading
Loading
@@ -488,6 +490,10 @@ describe Ci::Pipeline, models: true do
context 'there are multiple of the same name' do
let!(:manual2) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }
 
before do
manual.update(retried: true)
end
it 'returns latest one' do
is_expected.to contain_exactly(manual2)
end
Loading
Loading
Loading
Loading
@@ -102,6 +102,10 @@ describe Ci::Stage, models: true do
context 'and builds are retried' do
let!(:new_build) { create_job(:ci_build, status: :success) }
 
before do
stage_build.update(retried: true)
end
it "returns status of latest build" do
is_expected.to eq('success')
end
Loading
Loading
Loading
Loading
@@ -157,9 +157,9 @@ describe CommitStatus, :models do
subject { described_class.latest.order(:id) }
 
let(:statuses) do
[create_status(name: 'aa', ref: 'bb', status: 'running'),
create_status(name: 'cc', ref: 'cc', status: 'pending'),
create_status(name: 'aa', ref: 'cc', status: 'success'),
[create_status(name: 'aa', ref: 'bb', status: 'running', retried: true),
create_status(name: 'cc', ref: 'cc', status: 'pending', retried: true),
create_status(name: 'aa', ref: 'cc', status: 'success', retried: true),
create_status(name: 'cc', ref: 'bb', status: 'success'),
create_status(name: 'aa', ref: 'bb', status: 'success')]
end
Loading
Loading
@@ -169,6 +169,22 @@ describe CommitStatus, :models do
end
end
 
describe '.retried' do
subject { described_class.retried.order(:id) }
let(:statuses) do
[create_status(name: 'aa', ref: 'bb', status: 'running', retried: true),
create_status(name: 'cc', ref: 'cc', status: 'pending', retried: true),
create_status(name: 'aa', ref: 'cc', status: 'success', retried: true),
create_status(name: 'cc', ref: 'bb', status: 'success'),
create_status(name: 'aa', ref: 'bb', status: 'success')]
end
it 'returns unique statuses' do
is_expected.to contain_exactly(*statuses.values_at(0, 1, 2))
end
end
describe '.running_or_pending' do
subject { described_class.running_or_pending.order(:id) }
 
Loading
Loading
@@ -181,7 +197,7 @@ describe CommitStatus, :models do
end
 
it 'returns statuses that are running or pending' do
is_expected.to eq(statuses.values_at(0, 1))
is_expected.to contain_exactly(*statuses.values_at(0, 1))
end
end
 
Loading
Loading
Loading
Loading
@@ -26,8 +26,8 @@ describe API::CommitStatuses do
create(:commit_status, { pipeline: commit, ref: commit.ref }.merge(opts))
end
 
let!(:status1) { create_status(master, status: 'running') }
let!(:status2) { create_status(master, name: 'coverage', status: 'pending') }
let!(:status1) { create_status(master, status: 'running', retried: true) }
let!(:status2) { create_status(master, name: 'coverage', status: 'pending', retried: true) }
let!(:status3) { create_status(develop, status: 'running', allow_failure: true) }
let!(:status4) { create_status(master, name: 'coverage', status: 'success') }
let!(:status5) { create_status(develop, name: 'coverage', status: 'success') }
Loading
Loading
Loading
Loading
@@ -425,6 +425,21 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end
end
 
context 'updates a list of retried builds' do
subject { described_class.retried.order(:id) }
let!(:build_retried) { create_build('build') }
let!(:build) { create_build('build') }
let!(:test) { create_build('test') }
it 'returns unique statuses' do
process_pipeline
expect(all_builds.latest).to contain_exactly(build, test)
expect(all_builds.retried).to contain_exactly(build_retried)
end
end
def process_pipeline
described_class.new(pipeline.project, user).execute(pipeline)
end
Loading
Loading
Loading
Loading
@@ -22,7 +22,7 @@ describe Ci::RetryBuildService, :services do
%i[type lock_version target_url base_tags
commit_id deployments erased_by_id last_deployment project_id
runner_id tag_taggings taggings tags trigger_request_id
user_id auto_canceled_by_id].freeze
user_id auto_canceled_by_id retried].freeze
 
shared_examples 'build duplication' do
let(:build) do
Loading
Loading
@@ -115,7 +115,7 @@ describe Ci::RetryBuildService, :services do
end
 
describe '#reprocess' do
let(:new_build) { service.reprocess(build) }
let(:new_build) { service.reprocess!(build) }
 
context 'when user has ability to execute build' do
before do
Loading
Loading
@@ -131,11 +131,16 @@ describe Ci::RetryBuildService, :services do
it 'does not enqueue the new build' do
expect(new_build).to be_created
end
it 'does mark old build as retried' do
expect(new_build).to be_latest
expect(build.reload).to be_retried
end
end
 
context 'when user does not have ability to execute build' do
it 'raises an error' do
expect { service.reprocess(build) }
expect { service.reprocess!(build) }
.to raise_error Gitlab::Access::AccessDeniedError
end
end
Loading
Loading
Loading
Loading
@@ -13,7 +13,7 @@ describe Ci::RetryPipelineService, '#execute', :services do
 
context 'when there are already retried jobs present' do
before do
create_build('rspec', :canceled, 0)
create_build('rspec', :canceled, 0, retried: true)
create_build('rspec', :failed, 0)
end
 
Loading
Loading
Loading
Loading
@@ -39,9 +39,8 @@ describe 'projects/pipelines/_stage', :view do
 
context 'when there are retried builds present' do
before do
create_list(:ci_build, 2, name: 'test:build',
stage: stage.name,
pipeline: pipeline)
create(:ci_build, name: 'test:build', stage: stage.name, pipeline: pipeline, retried: true)
create(:ci_build, name: 'test:build', stage: stage.name, pipeline: pipeline)
end
 
it 'shows only latest builds' do
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment