Commit 057c0d7a authored by Lin Jen-Shin's avatar Lin Jen-Shin

Also track auto-cancelling in jobs, detail:

Not only tracking auto-cancelling in pipelines,
we'll also track this in jobs because pipelines
could be retried and the information would get lost
when this happened. Also erase auto-cancelling
relation for pipelines when they're retried.
parent 98a4aca6
...@@ -11,6 +11,8 @@ class Pipeline < ActiveRecord::Base ...@@ -11,6 +11,8 @@ class Pipeline < ActiveRecord::Base
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id' has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id'
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
has_many :builds, foreign_key: :commit_id has_many :builds, foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
...@@ -93,6 +95,10 @@ class Pipeline < ActiveRecord::Base ...@@ -93,6 +95,10 @@ class Pipeline < ActiveRecord::Base
PipelineNotificationWorker.perform_async(pipeline.id) PipelineNotificationWorker.perform_async(pipeline.id)
end end
end end
after_transition :canceled => any - [:canceled] do |pipeline|
pipeline.update(auto_canceled_by: nil)
end
end end
# ref can't be HEAD or SHA, can only be branch/tag name # ref can't be HEAD or SHA, can only be branch/tag name
...@@ -226,10 +232,21 @@ def auto_canceled? ...@@ -226,10 +232,21 @@ def auto_canceled?
def cancel_running def cancel_running
Gitlab::OptimisticLocking.retry_lock( Gitlab::OptimisticLocking.retry_lock(
statuses.cancelable) do |cancelable| statuses.cancelable) do |cancelable|
cancelable.find_each(&:cancel) cancelable.find_each do |job|
yield(job) if block_given?
job.cancel
end
end end
end end
def auto_cancel_running(pipeline)
update(auto_canceled_by: pipeline)
cancel_running do |job|
job.auto_canceled_by = pipeline
end
end
def retry_failed(current_user) def retry_failed(current_user)
Ci::RetryPipelineService.new(project, current_user) Ci::RetryPipelineService.new(project, current_user)
.execute(self) .execute(self)
......
...@@ -7,6 +7,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -7,6 +7,7 @@ class CommitStatus < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :user belongs_to :user
delegate :commit, to: :pipeline delegate :commit, to: :pipeline
...@@ -137,6 +138,10 @@ def has_trace? ...@@ -137,6 +138,10 @@ def has_trace?
false false
end end
def auto_canceled?
canceled? && auto_canceled_by_id?
end
# Added in 9.0 to keep backward compatibility for projects exported in 8.17 # Added in 9.0 to keep backward compatibility for projects exported in 8.17
# and prior. # and prior.
def gl_project_id def gl_project_id
......
...@@ -13,8 +13,8 @@ def erased_by_name ...@@ -13,8 +13,8 @@ def erased_by_name
end end
def status_title def status_title
if canceled? && pipeline.auto_canceled? if auto_canceled?
"Job is redundant and is auto-canceled by Pipeline ##{pipeline.auto_canceled_by_id}" "Job is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
end end
end end
end end
......
...@@ -3,7 +3,9 @@ class PipelinePresenter < Gitlab::View::Presenter::Delegated ...@@ -3,7 +3,9 @@ class PipelinePresenter < Gitlab::View::Presenter::Delegated
presents :pipeline presents :pipeline
def status_title def status_title
"Pipeline is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}" if auto_canceled? if auto_canceled?
"Pipeline is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
end
end end
end end
end end
...@@ -68,8 +68,7 @@ def skip_ci? ...@@ -68,8 +68,7 @@ def skip_ci?
def cancel_pending_pipelines def cancel_pending_pipelines
Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines) do |cancelables| Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines) do |cancelables|
cancelables.find_each do |cancelable| cancelables.find_each do |cancelable|
cancelable.cancel_running cancelable.auto_cancel_running(pipeline)
cancelable.update_attributes(auto_canceled_by_id: pipeline.id)
end end
end end
end end
......
class AddAutoCanceledByIdToCiBuilds < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :ci_builds, :auto_canceled_by_id, :integer
end
end
class AddAutoCanceledByIdForeignKeyToCiBuilds < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
on_delete =
if Gitlab::Database.mysql?
:nullify
else
'SET NULL'
end
add_concurrent_foreign_key :ci_builds, :ci_pipelines, column: :auto_canceled_by_id, on_delete: on_delete
end
def down
remove_foreign_key :ci_builds, column: :auto_canceled_by_id
end
end
...@@ -223,6 +223,7 @@ ...@@ -223,6 +223,7 @@
t.string "token" t.string "token"
t.integer "lock_version" t.integer "lock_version"
t.string "coverage_regex" t.string "coverage_regex"
t.integer "auto_canceled_by_id"
end 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 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
...@@ -1300,6 +1301,7 @@ ...@@ -1300,6 +1301,7 @@
add_foreign_key "boards", "projects" add_foreign_key "boards", "projects"
add_foreign_key "chat_teams", "namespaces", on_delete: :cascade add_foreign_key "chat_teams", "namespaces", on_delete: :cascade
add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify
add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "issue_metrics", "issues", on_delete: :cascade add_foreign_key "issue_metrics", "issues", on_delete: :cascade
......
...@@ -91,10 +91,12 @@ pipelines: ...@@ -91,10 +91,12 @@ pipelines:
- trigger_requests - trigger_requests
- auto_canceled_by - auto_canceled_by
- auto_canceled_pipelines - auto_canceled_pipelines
- auto_canceled_jobs
statuses: statuses:
- project - project
- pipeline - pipeline
- user - user
- auto_canceled_by
variables: variables:
- project - project
triggers: triggers:
......
...@@ -224,6 +224,7 @@ CommitStatus: ...@@ -224,6 +224,7 @@ CommitStatus:
- token - token
- lock_version - lock_version
- coverage_regex - coverage_regex
- auto_canceled_by_id
Ci::Variable: Ci::Variable:
- id - id
- project_id - project_id
......
...@@ -136,6 +136,43 @@ def create_build(name, status) ...@@ -136,6 +136,43 @@ def create_build(name, status)
end end
end end
describe '#auto_canceled?' do
subject { pipeline.auto_canceled? }
context 'when it is canceled' do
before do
pipeline.cancel
end
context 'when there is auto_canceled_by' do
before do
pipeline.update(auto_canceled_by: create(:ci_empty_pipeline))
end
it 'is auto canceled' do
is_expected.to be_truthy
end
end
context 'when there is no auto_canceled_by' do
it 'is not auto canceled' do
is_expected.to be_falsey
end
end
context 'when it is retried and canceled manually' do
before do
pipeline.enqueue
pipeline.cancel
end
it 'is not auto canceled' do
is_expected.to be_falsey
end
end
end
end
describe 'pipeline stages' do describe 'pipeline stages' do
before do before do
create(:commit_status, pipeline: pipeline, create(:commit_status, pipeline: pipeline,
......
...@@ -101,6 +101,32 @@ def create_status(args = {}) ...@@ -101,6 +101,32 @@ def create_status(args = {})
end end
end end
describe '#auto_canceled?' do
subject { commit_status.auto_canceled? }
context 'when it is canceled' do
before do
commit_status.cancel
end
context 'when there is auto_canceled_by' do
before do
commit_status.update(auto_cancel_by: create(:ci_empty_pipeline))
end
it 'is auto canceled' do
is_expected.to be_truthy
end
end
context 'when there is no auto_canceled_by' do
it 'is not auto canceled' do
is_expected.to be_falsey
end
end
end
end
describe '#duration' do describe '#duration' do
subject { commit_status.duration } subject { commit_status.duration }
......
...@@ -58,33 +58,27 @@ ...@@ -58,33 +58,27 @@
end end
describe '#status_title' do describe '#status_title' do
context 'when build is canceled' do context 'when build is auto-canceled' do
before do before do
expect(presenter).to receive(:canceled?).and_return(true) expect(build).to receive(:auto_canceled?).and_return(true)
expect(build).to receive(:auto_canceled_by_id).and_return(1)
end end
context 'when pipeline is auto-canceled' do it 'shows that the job is auto-canceled' do
before do status_title = presenter.status_title
expect(pipeline).to receive(:auto_canceled?).and_return(true)
expect(pipeline).to receive(:auto_canceled_by_id).and_return(1)
end
it 'shows that the job is auto-canceled' do expect(status_title).to include('auto-canceled')
status_title = presenter.status_title expect(status_title).to include('Pipeline #1')
expect(status_title).to include('auto-canceled')
expect(status_title).to include('Pipeline #1')
end
end end
end
context 'when pipeline is not auto-canceled' do context 'when build is not auto-canceled' do
before do before do
expect(pipeline).to receive(:auto_canceled?).and_return(false) expect(build).to receive(:auto_canceled?).and_return(false)
end end
it 'shows that the job is auto-canceled' do it 'does not have a status title' do
expect(presenter.status_title).to be_nil expect(presenter.status_title).to be_nil
end
end end
end end
end end
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
expect(pipeline).to receive(:auto_canceled?).and_return(false) expect(pipeline).to receive(:auto_canceled?).and_return(false)
end end
it 'shows that the job is auto-canceled' do it 'does not have a status title' do
expect(presenter.status_title).to be_nil expect(presenter.status_title).to be_nil
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