From ca639c9b824d6c8effb620bc71255eb0895ab2cc Mon Sep 17 00:00:00 2001
From: Kamil Trzcinski <ayufan@ayufan.eu>
Date: Sat, 19 Nov 2016 14:04:11 +0100
Subject: [PATCH] Allow to retry failed or canceled builds and fix cancel
 running specs failure

---
 app/models/ci/pipeline.rb         | 14 ++++---
 app/models/concerns/has_status.rb |  1 +
 spec/models/ci/pipeline_spec.rb   | 63 +++++++++++++++++++++++++++++--
 3 files changed, 69 insertions(+), 9 deletions(-)

diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 4eb85f62ee4..c0f2c8ba787 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -161,9 +161,7 @@ module Ci
     end
 
     def retryable?
-      builds.latest.any? do |build|
-        (build.failed? || build.canceled?) && build.retryable?
-      end
+      builds.latest.failed_or_canceled.any?(&:retryable?)
     end
 
     def cancelable?
@@ -171,12 +169,16 @@ module Ci
     end
 
     def cancel_running
-      statuses.cancelable.each(&:cancel)
+      Gitlab::OptimisticLocking.retry_lock(statuses.cancelable) do |cancelable|
+        cancelable.each(&:cancel)
+      end
     end
 
     def retry_failed(user)
-      builds.latest.failed.select(&:retryable?).each do |build|
-        Ci::Build.retry(build, user)
+      Gitlab::OptimisticLocking.retry_lock(builds.latest.failed_or_canceled) do |failed|
+        failed.select(&:retryable?).each do |build|
+          Ci::Build.retry(build, user)
+        end
       end
     end
 
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 1332743429d..2f5aa91a964 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -73,6 +73,7 @@ module HasStatus
     scope :skipped, -> { where(status: 'skipped')  }
     scope :running_or_pending, -> { where(status: [:running, :pending]) }
     scope :finished, -> { where(status: [:success, :failed, :canceled]) }
+    scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }
 
     scope :cancelable, -> do
       where(status: [:running, :pending, :created])
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 74579e0c832..af619a02ed9 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -455,17 +455,74 @@ describe Ci::Pipeline, models: true do
   end
 
   describe '#cancel_running' do
+    let(:latest_status) { pipeline.statuses.pluck(:status) }
+
     context 'when there is a running external job and created build' do
       before do
+        create(:ci_build, :running, pipeline: pipeline)
         create(:generic_commit_status, :running, pipeline: pipeline)
-        create(:ci_build, :created, pipeline: pipeline)
 
         pipeline.cancel_running
       end
 
       it 'cancels both jobs' do
-        expect(pipeline.statuses.pluck(:status)).
-          to contain_exactly('canceled', 'canceled')
+        expect(latest_status).to contain_exactly('canceled', 'canceled')
+      end
+    end
+
+    context 'when builds are in different stages' do
+      before do
+        create(:ci_build, :running, stage_idx: 0, pipeline: pipeline)
+        create(:ci_build, :running, stage_idx: 1, pipeline: pipeline)
+
+        pipeline.cancel_running
+      end
+
+      it 'cancels both jobs' do
+        expect(latest_status).to contain_exactly('canceled', 'canceled')
+      end
+    end
+  end
+
+  describe '#retry_failed' do
+    let(:latest_status) { pipeline.statuses.latest.pluck(:status) }
+
+    context 'when there is a failed build and failed external status' do
+      before do
+        create(:ci_build, :failed, name: 'build', pipeline: pipeline)
+        create(:generic_commit_status, :failed, name: 'jenkins', pipeline: pipeline)
+
+        pipeline.retry_failed(nil)
+      end
+
+      it 'retries only build' do
+        expect(latest_status).to contain_exactly('pending', 'failed')
+      end
+    end
+
+    context 'when builds are in different stages' do
+      before do
+        create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline)
+        create(:ci_build, :failed, name: 'jenkins', stage_idx: 1, pipeline: pipeline)
+
+        pipeline.retry_failed(nil)
+      end
+
+      it 'retries both builds' do
+        expect(latest_status).to contain_exactly('pending', 'pending')
+      end
+    end
+
+    context 'when there are canceled and failed' do
+      before do
+        create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline)
+        create(:ci_build, :canceled, name: 'jenkins', stage_idx: 1, pipeline: pipeline)
+
+        pipeline.retry_failed(nil)
+      end
+
+      it 'retries both builds' do
+        expect(latest_status).to contain_exactly('pending', 'pending')
       end
     end
   end
-- 
2.30.9