Repository: fedora-infra/bodhi

Issue:
Bodhi logs a traceback when Greenwave returns a 404
When I use the Bodhi CLI to query an update and it uses the Greenwave API, it logs a traceback if Greenwave returns a 404:

```
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]: 2018-08-16 13:16:36,435 ERROR [bodhi.server][waitress] Bodhi failed to send POST request to Greenwave at the following URL "https://greenwave-web-greenwave.app.os.fedoraproject.org/api/v1.0/decision". The status code was "404". The error was "{u'message': u'Cannot find any applicable policies for bodhi_update subjects at gating point bodhi_update_push_stable in fedora-28-test'}".
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]: 2018-08-16 13:16:36,436 ERROR [bodhi.server][waitress] Error querying greenwave for test results
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]: Traceback (most recent call last):
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/services/updates.py", line 614, in get_test_results
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     decision = update.get_test_gating_info()
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/models.py", line 1987, in get_test_gating_info
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     return bodhi.server.util.greenwave_api_post(api_url, data)
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/util.py", line 1351, in greenwave_api_post
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     data=data, retries=3)
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/util.py", line 1283, in call_api
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     return call_api(api_url, service_name, error_key, method, data, headers, retries - 1)
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/util.py", line 1283, in call_api
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     return call_api(api_url, service_name, error_key, method, data, headers, retries - 1)
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/util.py", line 1283, in call_api
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     return call_api(api_url, service_name, error_key, method, data, headers, retries - 1)
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/util.py", line 1303, in call_api
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     raise RuntimeError(error_msg)
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]: RuntimeError: Bodhi failed to send POST request to Greenwave at the following URL "https://greenwave-web-greenwave.app.os.fedoraproject.org/api/v1.0/decision". The status code was "404". The error was "{u'message': u'Cannot find any applicable policies for bodhi_update subjects at gating point bodhi_update_push_stable in fedora-28-test'}".
```

It's fine to log the 404, but it shouldn't be a traceback.
Bodhi logs a traceback when Greenwave returns a 404
When I use the Bodhi CLI to query an update and it uses the Greenwave API, it logs a traceback if Greenwave returns a 404:

```
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]: 2018-08-16 13:16:36,435 ERROR [bodhi.server][waitress] Bodhi failed to send POST request to Greenwave at the following URL "https://greenwave-web-greenwave.app.os.fedoraproject.org/api/v1.0/decision". The status code was "404". The error was "{u'message': u'Cannot find any applicable policies for bodhi_update subjects at gating point bodhi_update_push_stable in fedora-28-test'}".
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]: 2018-08-16 13:16:36,436 ERROR [bodhi.server][waitress] Error querying greenwave for test results
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]: Traceback (most recent call last):
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/services/updates.py", line 614, in get_test_results
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     decision = update.get_test_gating_info()
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/models.py", line 1987, in get_test_gating_info
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     return bodhi.server.util.greenwave_api_post(api_url, data)
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/util.py", line 1351, in greenwave_api_post
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     data=data, retries=3)
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/util.py", line 1283, in call_api
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     return call_api(api_url, service_name, error_key, method, data, headers, retries - 1)
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/util.py", line 1283, in call_api
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     return call_api(api_url, service_name, error_key, method, data, headers, retries - 1)
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/util.py", line 1283, in call_api
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     return call_api(api_url, service_name, error_key, method, data, headers, retries - 1)
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:   File "/home/vagrant/bodhi/bodhi/server/util.py", line 1303, in call_api
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]:     raise RuntimeError(error_msg)
Aug 16 13:16:36 bodhi-dev.example.com pserve[7077]: RuntimeError: Bodhi failed to send POST request to Greenwave at the following URL "https://greenwave-web-greenwave.app.os.fedoraproject.org/api/v1.0/decision". The status code was "404". The error was "{u'message': u'Cannot find any applicable policies for bodhi_update subjects at gating point bodhi_update_push_stable in fedora-28-test'}".
```

It's fine to log the 404, but it shouldn't be a traceback.


Discussion:

Resolving patch:
diff --git a/bodhi/server/services/updates.py b/bodhi/server/services/updates.py
--- a/bodhi/server/services/updates.py
+++ b/bodhi/server/services/updates.py
@@ -204,7 +204,7 @@ def set_request(request):
     try:
         update.set_request(request.db, action, request.user.name)
     except BodhiException as e:
-        log.exception("Failed to set the request")
+        log.error("Failed to set the request: %s", e)
         request.errors.add('body', 'request', str(e))
     except Exception as e:
         log.exception("Unhandled exception in set_request")
@@ -589,9 +589,10 @@ def waive_test_results(request):
     try:
         update.waive_test_results(request.user.name, comment, tests)
     except LockedUpdateException as e:
+        log.warning(str(e))
         request.errors.add('body', 'request', str(e))
     except BodhiException as e:
-        log.exception("Failed to waive the test results")
+        log.error("Failed to waive the test results: %s", e)
         request.errors.add('body', 'request', str(e))
     except Exception as e:
         log.exception("Unhandled exception in waive_test_results")
@@ -619,15 +620,15 @@ def get_test_results(request):
     try:
         decision = update.get_test_gating_info()
     except RequestsTimeout as e:
-        log.exception("Error querying greenwave for test results - timed out")
+        log.error("Error querying greenwave for test results - timed out")
         request.errors.add('body', 'request', str(e))
         request.errors.status = 504
     except (RequestException, RuntimeError) as e:
-        log.exception("Error querying greenwave for test results")
+        log.error("Error querying greenwave for test results: %s", e)
         request.errors.add('body', 'request', str(e))
         request.errors.status = 502
     except BodhiException as e:
-        log.exception("Failed to query greenwave for test results")
+        log.error("Failed to query greenwave for test results: %s", e)
         request.errors.add('body', 'request', str(e))
         request.errors.status = 501
     except Exception as e:


Test patch:
diff --git a/bodhi/tests/server/services/test_updates.py b/bodhi/tests/server/services/test_updates.py
--- a/bodhi/tests/server/services/test_updates.py
+++ b/bodhi/tests/server/services/test_updates.py
@@ -39,6 +39,7 @@
     UpdateSeverity, UpdateSuggestion, User, TestGatingStatus)
 from bodhi.server.util import call_api
 from bodhi.tests.server.base import BaseTestCase
+from bodhi.server.exceptions import BodhiException, LockedUpdateException
 
 
 YEAR = time.localtime().tm_year
@@ -796,6 +797,30 @@ def test_test_gating_status_passed(self):
         self.assertEqual(up.request, UpdateRequest.stable)
         self.assertEqual(res.json['update']['request'], 'stable')
 
+    @mock.patch(**mock_valid_requirements)
+    @mock.patch('bodhi.server.services.updates.Update.set_request',
+                side_effect=BodhiException('BodhiException. oops!'))
+    @mock.patch('bodhi.server.services.updates.Update.check_requirements',
+                return_value=(True, "a fake reason"))
+    @mock.patch('bodhi.server.services.updates.log.error')
+    def test_BodhiException_exception(self, log_error, check_requirements, send_request, *args):
+        """Ensure that an BodhiException Exception is handled by set_request()."""
+        nvr = u'bodhi-2.0-1.fc17'
+
+        up = self.db.query(Update).filter_by(title=nvr).one()
+        up.locked = False
+        up.release.state = ReleaseState.current
+
+        post_data = dict(update=nvr, request='stable',
+                         csrf_token=self.app.get('/csrf').json_body['csrf_token'])
+        res = self.app.post_json('/updates/%s/request' % str(nvr), post_data, status=400)
+
+        self.assertEqual(res.json_body['status'], 'error')
+        self.assertEqual(res.json_body['errors'][0]['description'],
+                         u'BodhiException. oops!')
+        log_error.assert_called_once()
+        self.assertEqual("Failed to set the request: %s", log_error.call_args_list[0][0][0])
+
     @mock.patch(**mock_valid_requirements)
     @mock.patch('bodhi.server.services.updates.Update.set_request',
                 side_effect=IOError('IOError. oops!'))
@@ -5220,6 +5245,53 @@ def test_cannot_waive_test_results_when_test_gating_is_off(self, *args):
         # The test gating status should not have been altered.
         self.assertEqual(up.test_gating_status, TestGatingStatus.failed)
 
+    @mock.patch('bodhi.server.services.updates.Update.waive_test_results',
+                side_effect=LockedUpdateException('LockedUpdateException. oops!'))
+    @mock.patch('bodhi.server.services.updates.log.warning')
+    def test_LockedUpdateException_exception(self, log_warning, waive_test_results, *args):
+        """Ensure that an LockedUpdateException Exception is handled by waive_test_results()."""
+        nvr = u'bodhi-2.0-1.fc17'
+
+        up = self.db.query(Update).filter_by(title=nvr).one()
+        up.test_gating_status = TestGatingStatus.failed
+        up.locked = False
+
+        post_data = dict(update=nvr, request='stable',
+                         csrf_token=self.app.get('/csrf').json_body['csrf_token'])
+        res = self.app.post_json('/updates/%s/waive-test-results' % str(nvr), post_data, status=400)
+
+        self.assertEqual(res.json_body['status'], 'error')
+        self.assertEqual(res.json_body['errors'][0]['description'],
+                         u'LockedUpdateException. oops!')
+        log_warning.assert_called_once_with('LockedUpdateException. oops!')
+        up = self.db.query(Update).filter_by(title=nvr).one()
+        # The test gating status should not have been altered.
+        self.assertEqual(up.test_gating_status, TestGatingStatus.failed)
+
+    @mock.patch('bodhi.server.services.updates.Update.waive_test_results',
+                side_effect=BodhiException('BodhiException. oops!'))
+    @mock.patch('bodhi.server.services.updates.log.error')
+    def test_BodhiException_exception(self, log_error, waive_test_results, *args):
+        """Ensure that an BodhiException Exception is handled by waive_test_results()."""
+        nvr = u'bodhi-2.0-1.fc17'
+
+        up = self.db.query(Update).filter_by(title=nvr).one()
+        up.test_gating_status = TestGatingStatus.failed
+        up.locked = False
+
+        post_data = dict(update=nvr, request='stable',
+                         csrf_token=self.app.get('/csrf').json_body['csrf_token'])
+        res = self.app.post_json('/updates/%s/waive-test-results' % str(nvr), post_data, status=400)
+
+        self.assertEqual(res.json_body['status'], 'error')
+        self.assertEqual(res.json_body['errors'][0]['description'],
+                         u'BodhiException. oops!')
+        log_error.assert_called_once()
+        self.assertEqual("Failed to waive the test results: %s", log_error.call_args_list[0][0][0])
+        up = self.db.query(Update).filter_by(title=nvr).one()
+        # The test gating status should not have been altered.
+        self.assertEqual(up.test_gating_status, TestGatingStatus.failed)
+
     @mock.patch('bodhi.server.services.updates.Update.waive_test_results',
                 side_effect=IOError('IOError. oops!'))
     @mock.patch('bodhi.server.services.updates.log.exception')
@@ -5666,6 +5738,70 @@ def test_cannot_get_test_results_when_no_greenwave_url(self, *args):
         self.assertEqual(res.json_body[u'errors'][0][u'description'],
                          "No greenwave_api_url specified")
 
+    @mock.patch('bodhi.server.services.updates.Update.get_test_gating_info',
+                side_effect=requests.Timeout('RequestsTimeout. oops!'))
+    @mock.patch('bodhi.server.services.updates.log.error')
+    def test_RequestsTimeout_exception(self, log_error, get_test_gating_info, *args):
+        """Ensure that an RequestsTimeout Exception is handled by get_test_results()."""
+        nvr = u'bodhi-2.0-1.fc17'
+
+        up = self.db.query(Update).filter_by(title=nvr).one()
+        up.locked = False
+
+        res = self.app.get('/updates/%s/get-test-results' % str(nvr), status=504)
+
+        self.assertEqual(res.status_code, 504)
+        self.assertEqual(res.json_body['status'], 'error')
+        self.assertEqual(res.json_body['errors'][0]['description'],
+                         u'RequestsTimeout. oops!')
+        log_error.assert_called_once()
+        self.assertEqual(
+            "Error querying greenwave for test results - timed out",
+            log_error.call_args_list[0][0][0],
+        )
+
+    @mock.patch('bodhi.server.services.updates.Update.get_test_gating_info',
+                side_effect=RuntimeError('RuntimeError. oops!'))
+    @mock.patch('bodhi.server.services.updates.log.error')
+    def test_RuntimeError_exception(self, log_error, get_test_gating_info, *args):
+        """Ensure that an RuntimeError Exception is handled by get_test_results()."""
+        nvr = u'bodhi-2.0-1.fc17'
+
+        up = self.db.query(Update).filter_by(title=nvr).one()
+        up.locked = False
+
+        res = self.app.get('/updates/%s/get-test-results' % str(nvr), status=502)
+
+        self.assertEqual(res.status_code, 502)
+        self.assertEqual(res.json_body['status'], 'error')
+        self.assertEqual(res.json_body['errors'][0]['description'],
+                         u'RuntimeError. oops!')
+        log_error.assert_called_once()
+        self.assertEqual(
+            "Error querying greenwave for test results: %s", log_error.call_args_list[0][0][0]
+        )
+
+    @mock.patch('bodhi.server.services.updates.Update.get_test_gating_info',
+                side_effect=BodhiException('BodhiException. oops!'))
+    @mock.patch('bodhi.server.services.updates.log.error')
+    def test_BodhiException_exception(self, log_error, get_test_gating_info, *args):
+        """Ensure that an BodhiException Exception is handled by get_test_results()."""
+        nvr = u'bodhi-2.0-1.fc17'
+
+        up = self.db.query(Update).filter_by(title=nvr).one()
+        up.locked = False
+
+        res = self.app.get('/updates/%s/get-test-results' % str(nvr), status=501)
+
+        self.assertEqual(res.status_code, 501)
+        self.assertEqual(res.json_body['status'], 'error')
+        self.assertEqual(res.json_body['errors'][0]['description'],
+                         u'BodhiException. oops!')
+        log_error.assert_called_once()
+        self.assertEqual(
+            "Failed to query greenwave for test results: %s", log_error.call_args_list[0][0][0]
+        )
+
     @mock.patch('bodhi.server.services.updates.Update.get_test_gating_info',
                 side_effect=IOError('IOError. oops!'))
     @mock.patch('bodhi.server.services.updates.log.exception')