From 6321a29006ede22a9e71031a901f3057a42bb1af Mon Sep 17 00:00:00 2001
From: Tan Le <tle@gitlab.com>
Date: Thu, 1 Dec 2022 21:53:56 +1100
Subject: [PATCH] Only retry on network error on suggested reviewers

We would like to track only network errors when calling the suggested
reviewers client. These types of errors will deplete the error budget.

Other application errors that are deep in the stack, that are not
network-related are not retriable but will be tracked and reported
internally in the suggested reviewers monitoring infrastructure.
---
 .../fetch_suggested_reviewers_worker.rb       |  8 +++++
 ee/lib/gitlab/applied_ml/errors.rb            |  1 +
 .../applied_ml/suggested_reviewers/client.rb  |  7 +++++
 .../suggested_reviewers/client_spec.rb        | 16 ++++++++--
 .../fetch_suggested_reviewers_worker_spec.rb  | 30 +++++++++++++++++--
 5 files changed, 58 insertions(+), 4 deletions(-)

diff --git a/ee/app/workers/merge_requests/fetch_suggested_reviewers_worker.rb b/ee/app/workers/merge_requests/fetch_suggested_reviewers_worker.rb
index 6918953c6936a..43acf000a1028 100644
--- a/ee/app/workers/merge_requests/fetch_suggested_reviewers_worker.rb
+++ b/ee/app/workers/merge_requests/fetch_suggested_reviewers_worker.rb
@@ -31,6 +31,14 @@ def perform(merge_request_id)
       else
         logger.error(structured_payload({ result: result }))
       end
+    rescue Gitlab::AppliedMl::Errors::ResourceNotAvailable => e
+      Gitlab::ErrorTracking.log_exception(
+        e,
+        project_id: merge_request.project.id,
+        merge_request_id: merge_request.id
+      )
+
+      nil
     end
   end
 end
diff --git a/ee/lib/gitlab/applied_ml/errors.rb b/ee/lib/gitlab/applied_ml/errors.rb
index 36616635cc747..76e2383411c4e 100644
--- a/ee/lib/gitlab/applied_ml/errors.rb
+++ b/ee/lib/gitlab/applied_ml/errors.rb
@@ -8,6 +8,7 @@ module Errors
       ProjectAlreadyExists = Class.new(BaseError)
       ResourceNotAvailable = Class.new(BaseError)
       ConfigurationError = Class.new(BaseError)
+      ConnectionFailed = Class.new(BaseError)
     end
   end
 end
diff --git a/ee/lib/gitlab/applied_ml/suggested_reviewers/client.rb b/ee/lib/gitlab/applied_ml/suggested_reviewers/client.rb
index 839616956b582..f293e4c3ac256 100644
--- a/ee/lib/gitlab/applied_ml/suggested_reviewers/client.rb
+++ b/ee/lib/gitlab/applied_ml/suggested_reviewers/client.rb
@@ -15,6 +15,11 @@ class Client
         SECRET_NAME = "SUGGESTED_REVIEWERS_SECRET"
         SECRET_LENGTH = 64
 
+        NETWORK_ERRORS = [
+          GRPC::DeadlineExceeded,
+          GRPC::Unavailable
+        ].freeze
+
         def self.default_rpc_url
           if Gitlab.dev_or_test_env?
             'suggested-reviewer.dev:443'
@@ -48,6 +53,8 @@ def suggested_reviewers(merge_request_iid:, project_id:, changes:, author_userna
             top_n: response.top_n,
             reviewers: response.reviewers
           }
+        rescue *NETWORK_ERRORS => e
+          raise Errors::ConnectionFailed, e
         rescue GRPC::BadStatus => e
           raise Errors::ResourceNotAvailable, e
         end
diff --git a/ee/spec/lib/gitlab/applied_ml/suggested_reviewers/client_spec.rb b/ee/spec/lib/gitlab/applied_ml/suggested_reviewers/client_spec.rb
index 448d2428947cb..5f08fefbbac19 100644
--- a/ee/spec/lib/gitlab/applied_ml/suggested_reviewers/client_spec.rb
+++ b/ee/spec/lib/gitlab/applied_ml/suggested_reviewers/client_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe Gitlab::AppliedMl::SuggestedReviewers::Client do
+RSpec.describe Gitlab::AppliedMl::SuggestedReviewers::Client, feature_category: :workflow_automation do
   let(:stub_class) { Gitlab::AppliedMl::SuggestedReviewers::RecommenderServicesPb::Stub }
 
   let(:rpc_url) { 'example.org:1234' }
@@ -141,13 +141,25 @@
       it_behaves_like 'respecting channel credentials'
     end
 
-    context 'when a grpc bad status is received' do
+    context 'when a grpc connection error is received' do
       before do
         allow_next_instance_of(stub_class) do |stub|
           allow(stub).to receive(:merge_request_recommendations_v2).and_raise(GRPC::Unavailable)
         end
       end
 
+      it 'raises a new error' do
+        expect { subject }.to raise_error(Gitlab::AppliedMl::Errors::ConnectionFailed)
+      end
+    end
+
+    context 'when a grpc bad status is received' do
+      before do
+        allow_next_instance_of(stub_class) do |stub|
+          allow(stub).to receive(:merge_request_recommendations_v2).and_raise(GRPC::Internal)
+        end
+      end
+
       it 'raises a new error' do
         expect { subject }.to raise_error(Gitlab::AppliedMl::Errors::ResourceNotAvailable)
       end
diff --git a/ee/spec/workers/merge_requests/fetch_suggested_reviewers_worker_spec.rb b/ee/spec/workers/merge_requests/fetch_suggested_reviewers_worker_spec.rb
index 63fe6b44ab84c..3db443b3c185a 100644
--- a/ee/spec/workers/merge_requests/fetch_suggested_reviewers_worker_spec.rb
+++ b/ee/spec/workers/merge_requests/fetch_suggested_reviewers_worker_spec.rb
@@ -2,7 +2,9 @@
 
 require 'spec_helper'
 
-RSpec.describe MergeRequests::FetchSuggestedReviewersWorker do
+RSpec.describe MergeRequests::FetchSuggestedReviewersWorker, feature_category: :workflow_automation do
+  include AfterNextHelpers
+
   let_it_be(:merge_request) { create(:merge_request) }
 
   subject { described_class.new }
@@ -19,7 +21,7 @@
     context 'when merge request does not have changes' do
       let_it_be(:merge_request) { create(:merge_request, :without_diffs) }
 
-      it 'returns without calling the fetch suggested reviewer service', :aggregate_failures do
+      it 'returns without calling the fetch suggested reviewer service' do
         expect(MergeRequests::FetchSuggestedReviewersService).not_to receive(:new)
 
         subject.perform(merge_request.id)
@@ -88,6 +90,30 @@
           expect(merge_request2.predictions&.suggested_reviewers).to be_nil
         end
       end
+
+      context 'when exceptions are raised' do
+        it 're-raises exception when it is retriable' do
+          allow_next(::MergeRequests::FetchSuggestedReviewersService)
+            .to receive(:execute)
+            .and_raise(Gitlab::AppliedMl::Errors::ConnectionFailed)
+
+          expect { subject.perform(merge_request.id) }.to raise_error(Gitlab::AppliedMl::Errors::ConnectionFailed)
+        end
+
+        it 'does not raise but logs exception when it is swallowable', :aggregate_failures do
+          allow_next(::MergeRequests::FetchSuggestedReviewersService)
+            .to receive(:execute)
+            .and_raise(Gitlab::AppliedMl::Errors::ResourceNotAvailable)
+
+          expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
+            Gitlab::AppliedMl::Errors::ResourceNotAvailable,
+            project_id: merge_request.project.id,
+            merge_request_id: merge_request.id
+          )
+
+          expect { subject.perform(merge_request.id) }.not_to raise_error
+        end
+      end
     end
   end
 end
-- 
GitLab