Skip to content

Commit cb3ff51

Browse files
authored
Fix issues search thread exhaustion (#1396)
* Log all HTTP errors * Cleaner project export progress log status * Catch RequestException and ConnectionError everywhere * Change http_error in error_msg * Quality pass * Fix redundant exception * Remove unused import HTTPError * Quality pass
1 parent ebf0911 commit cb3ff51

24 files changed

+252
-227
lines changed

cli/findings_export.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from queue import Queue
3333
import threading
3434
from threading import Thread
35-
from requests.exceptions import HTTPError
35+
from requests import HTTPError, RequestException
3636

3737
from cli import options
3838
from sonar.util.types import ConfigSettings
@@ -294,8 +294,8 @@ def __get_component_findings(queue: Queue[tuple[object, ConfigSettings]], write_
294294
findings_list = findings.export_findings(
295295
component.endpoint, component.key, branch=params.get("branch", None), pull_request=params.get("pullRequest", None)
296296
)
297-
except HTTPError as e:
298-
log.critical("Error %s while exporting findings of %s, skipped", str(e), str(component))
297+
except (ConnectionError, RequestException) as e:
298+
log.critical("%s while exporting findings of %s, skipped", util.error_msg(e), str(component))
299299
findings_list = {}
300300
write_queue.put([findings_list, False])
301301
else:
@@ -323,8 +323,8 @@ def __get_component_findings(queue: Queue[tuple[object, ConfigSettings]], write_
323323
if (i_statuses or not status_list) and (i_resols or not resol_list) and (i_types or not type_list) and (i_sevs or not sev_list):
324324
try:
325325
findings_list = component.get_issues(filters=new_params)
326-
except HTTPError as e:
327-
log.critical("Error %s while exporting issues of %s, skipped", str(e), str(component))
326+
except (ConnectionError, RequestException) as e:
327+
log.critical("%s while exporting issues of %s, skipped", util.error_msg(e), str(component))
328328
findings_list = {}
329329
else:
330330
log.debug("Status = %s, Types = %s, Resol = %s, Sev = %s", str(i_statuses), str(i_types), str(i_resols), str(i_sevs))
@@ -333,8 +333,8 @@ def __get_component_findings(queue: Queue[tuple[object, ConfigSettings]], write_
333333
if (h_statuses or not status_list) and (h_resols or not resol_list) and (h_types or not type_list) and (h_sevs or not sev_list):
334334
try:
335335
findings_list.update(component.get_hotspots(filters=new_params))
336-
except HTTPError as e:
337-
log.critical("Error %s while exporting hotspots of object key %s, skipped", str(e), str(component))
336+
except (ConnectionError, RequestException) as e:
337+
log.error("%s while exporting hotspots of object key %s, skipped", util.error_msg(e), str(component))
338338
else:
339339
log.debug("Status = %s, Types = %s, Resol = %s, Sev = %s", str(h_statuses), str(h_types), str(h_resols), str(h_sevs))
340340
log.info("Selected types, severities, resolutions or statuses disables issue search")
@@ -351,8 +351,8 @@ def store_findings(components_list: dict[str, object], params: ConfigSettings) -
351351
try:
352352
log.debug("Queue %s task %s put", str(my_queue), str(comp))
353353
my_queue.put((comp, params.copy()))
354-
except HTTPError as e:
355-
log.critical("Error %s while exporting findings of %s, skipped", str(e), str(comp))
354+
except (ConnectionError, RequestException) as e:
355+
log.critical("%s while exporting findings of %s, skipped", util.error_msg(e), str(comp))
356356

357357
threads = params.get(options.NBR_THREADS, 4)
358358
for i in range(min(threads, len(components_list))):

cli/loc.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import sys
2525
import csv
2626
import datetime
27-
from requests.exceptions import HTTPError
27+
from requests import RequestException
2828

2929
from cli import options
3030
import sonar.logging as log
@@ -51,8 +51,8 @@ def __get_csv_row(o: object, **kwargs) -> tuple[list[str], str]:
5151
"""Returns CSV row of object"""
5252
try:
5353
loc = o.loc()
54-
except HTTPError as e:
55-
log.warning("HTTP Error %s, LoC export of %s skipped", str(e), str(o))
54+
except (ConnectionError, RequestException) as e:
55+
log.warning("%s, LoC export of %s skipped", util.error_msg(e), str(o))
5656
loc = ""
5757
arr = [o.key, loc]
5858
obj_type = type(o).__name__.lower()
@@ -113,8 +113,8 @@ def __get_object_json_data(o: object, **kwargs) -> dict[str, str]:
113113
d = {parent_type: o.concerned_object.key, "branch": o.name, "ncloc": ""}
114114
try:
115115
d["ncloc"] = o.loc()
116-
except HTTPError as e:
117-
log.warning("HTTP Error %s, LoC export of %s skipped", str(e), str(o))
116+
except (ConnectionError, RequestException) as e:
117+
log.warning("%s, LoC export of %s skipped", util.error_msg(e), str(o))
118118
if kwargs[options.WITH_NAME]:
119119
d[f"{parent_type}Name"] = o.name
120120
if obj_type in ("branch", "applicationbranch"):

cli/measures_export.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@
2929

3030
from typing import Union
3131

32-
from http import HTTPStatus
33-
from requests.exceptions import HTTPError
32+
from requests import RequestException
3433
from sonar.util import types
3534
from cli import options
3635
import sonar.logging as log
@@ -56,12 +55,7 @@ def __last_analysis(component: object) -> str:
5655

5756
def __get_json_measures_history(obj: object, wanted_metrics: types.KeyList) -> dict[str, str]:
5857
"""Returns the measure history of an object (project, branch, application, portfolio)"""
59-
data = {}
60-
try:
61-
data["history"] = obj.get_measures_history(wanted_metrics)
62-
except HTTPError as e:
63-
log.error("HTTP Error %s, measures history export of %s skipped", str(e), str(obj))
64-
return data
58+
return {"history": obj.get_measures_history(wanted_metrics)}
6559

6660

6761
def __get_object_measures(obj: object, wanted_metrics: types.KeyList) -> dict[str, str]:
@@ -288,11 +282,8 @@ def __get_measures(obj: object, wanted_metrics: types.KeyList, hist: bool) -> Un
288282
data.update(__get_json_measures_history(obj, wanted_metrics))
289283
else:
290284
data.update(__get_object_measures(obj, wanted_metrics))
291-
except HTTPError as e:
292-
if e.response.status_code == HTTPStatus.FORBIDDEN:
293-
log.error("Insufficient permission to retrieve measures of %s, export skipped for this object", str(obj))
294-
else:
295-
log.error("HTTP Error %s while retrieving measures of %s, export skipped for this object", str(e), str(obj))
285+
except (ConnectionError, RequestException) as e:
286+
log.error("%s, measures export skipped for %s", util.error_msg(e), str(obj))
296287
return None
297288
return data
298289

sonar/app_branches.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
import json
2626
from http import HTTPStatus
27-
from requests.exceptions import HTTPError
27+
from requests import RequestException
2828
from requests.utils import quote
2929

3030
import sonar.logging as log
@@ -34,7 +34,7 @@
3434

3535
from sonar.applications import Application as App
3636
from sonar.branches import Branch
37-
from sonar import exceptions, projects
37+
from sonar import exceptions, projects, utilities
3838
import sonar.sqobject as sq
3939

4040
_OBJECTS = {}
@@ -109,9 +109,11 @@ def create(cls, app: App, name: str, project_branches: list[Branch]) -> Applicat
109109
params["projectBranch"].append(br_name)
110110
try:
111111
app.endpoint.post(APIS["create"], params=params)
112-
except HTTPError as e:
112+
except (ConnectionError, RequestException) as e:
113113
if e.response.status_code == HTTPStatus.BAD_REQUEST:
114114
raise exceptions.ObjectAlreadyExists(f"app.App {app.key} branch '{name}", e.response.text)
115+
log.critical("%s while creating branch '%s' of '%s'", utilities.error_msg(e), name, str(app))
116+
raise
115117
return ApplicationBranch(app=app, name=name, project_branches=project_branches)
116118

117119
@classmethod
@@ -182,9 +184,12 @@ def update(self, name: str, project_branches: list[Branch]) -> bool:
182184
params["projectBranch"].append(br_name)
183185
try:
184186
ok = self.endpoint.post(APIS["update"], params=params).ok
185-
except HTTPError as e:
187+
except (ConnectionError, RequestException) as e:
186188
if e.response.status_code == HTTPStatus.NOT_FOUND:
187189
raise exceptions.ObjectNotFound(str(self), e.response.text)
190+
log.error("%s while updating '%s'", utilities.error_msg(e), str(self))
191+
raise
192+
188193
self.name = name
189194
self._project_branches = project_branches
190195
return ok

sonar/applications.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from datetime import datetime
2727
from http import HTTPStatus
2828
from threading import Lock
29-
from requests.exceptions import HTTPError
29+
from requests import RequestException
3030

3131
import sonar.logging as log
3232
import sonar.platform as pf
@@ -90,9 +90,11 @@ def get_object(cls, endpoint: pf.Platform, key: str) -> Application:
9090
return _OBJECTS[uu]
9191
try:
9292
data = json.loads(endpoint.get(APIS["get"], params={"application": key}).text)["application"]
93-
except HTTPError as e:
93+
except (ConnectionError, RequestException) as e:
9494
if e.response.status_code == HTTPStatus.NOT_FOUND:
9595
raise exceptions.ObjectNotFound(key, f"Application key '{key}' not found")
96+
log.error("%s while getting app key '%s'", util.error_msg(e), key)
97+
raise
9698
return cls.load(endpoint, data)
9799

98100
@classmethod
@@ -131,9 +133,11 @@ def create(cls, endpoint: pf.Platform, key: str, name: str) -> Application:
131133
check_supported(endpoint)
132134
try:
133135
endpoint.post(APIS["create"], params={"key": key, "name": name})
134-
except HTTPError as e:
136+
except (ConnectionError, RequestException) as e:
135137
if e.response.status_code == HTTPStatus.BAD_REQUEST:
136138
raise exceptions.ObjectAlreadyExists(key, e.response.text)
139+
log.critical("%s while creating app key '%s'", util.error_msg(e), key)
140+
raise
137141
return Application(endpoint, key, name)
138142

139143
def refresh(self) -> None:
@@ -146,10 +150,11 @@ def refresh(self) -> None:
146150
try:
147151
self.reload(json.loads(self.get("navigation/component", params={"component": self.key}).text))
148152
self.reload(json.loads(self.get(APIS["get"], params=self.search_params()).text)["application"])
149-
except HTTPError as e:
153+
except (ConnectionError, RequestException) as e:
150154
if e.response.status_code == HTTPStatus.NOT_FOUND:
151155
_OBJECTS.pop(self.uuid(), None)
152156
raise exceptions.ObjectNotFound(self.key, f"{str(self)} not found")
157+
log.error("%s while refreshing %s", util.error_msg(e), str(self))
153158
raise
154159

155160
def __str__(self) -> str:
@@ -383,11 +388,12 @@ def add_projects(self, project_list: list[str]) -> bool:
383388
try:
384389
r = self.post("applications/add_project", params={"application": self.key, "project": proj})
385390
ok = ok and r.ok
386-
except HTTPError as e:
391+
except (ConnectionError, RequestException) as e:
387392
if e.response.status_code == HTTPStatus.NOT_FOUND:
388393
log.warning("Project '%s' not found, can't be added to %s", proj, self)
389394
ok = False
390395
else:
396+
log.error("%s while adding projects to %s", util.error_msg(e), str(self))
391397
raise
392398
return ok
393399

sonar/branches.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from http import HTTPStatus
2323
import json
2424
from urllib.parse import unquote
25-
from requests.exceptions import HTTPError
25+
from requests import HTTPError, RequestException
2626
import requests.utils
2727

2828
from sonar import platform
@@ -89,9 +89,11 @@ def get_object(cls, concerned_object: projects.Project, branch_name: str) -> Bra
8989
return _OBJECTS[uu]
9090
try:
9191
data = json.loads(concerned_object.endpoint.get(APIS["list"], params={"project": concerned_object.key}).text)
92-
except HTTPError as e:
93-
if e.response.status_code == HTTPStatus.NOT_FOUND:
92+
except (ConnectionError, RequestException) as e:
93+
if isinstance(HTTPError, e) and e.response.status_code == HTTPStatus.NOT_FOUND:
9494
raise exceptions.ObjectNotFound(concerned_object.key, f"Project '{concerned_object.key}' not found")
95+
log.critical("%s while getting branch '%s' of %s", util.error_msg(e), branch_name, str(concerned_object))
96+
raise
9597
for br in data.get("branches", []):
9698
if br["name"] == branch_name:
9799
return cls.load(concerned_object, branch_name, br)
@@ -127,9 +129,10 @@ def refresh(self) -> Branch:
127129
"""
128130
try:
129131
data = json.loads(self.get(APIS["list"], params={"project": self.concerned_object.key}).text)
130-
except HTTPError as e:
131-
if e.response.status_code == HTTPStatus.NOT_FOUND:
132+
except (ConnectionError, RequestException) as e:
133+
if isinstance(HTTPError, e) and e.response.status_code == HTTPStatus.NOT_FOUND:
132134
raise exceptions.ObjectNotFound(self.key, f"{str(self)} not found in SonarQube")
135+
log.error("%s while refreshing %s", util.error_msg(e), str(self))
133136
for br in data.get("branches", []):
134137
if br["name"] == self.name:
135138
self._load(br)
@@ -173,7 +176,7 @@ def is_main(self):
173176
self.refresh()
174177
return self._is_main
175178

176-
def delete(self):
179+
def delete(self) -> bool:
177180
"""Deletes a branch
178181
179182
:raises ObjectNotFound: Branch not found for deletion
@@ -182,9 +185,11 @@ def delete(self):
182185
"""
183186
try:
184187
return sq.delete_object(self, APIS["delete"], {"branch": self.name, "project": self.concerned_object.key}, _OBJECTS)
185-
except HTTPError as e:
186-
if e.response.status_code == HTTPStatus.BAD_REQUEST:
188+
except (ConnectionError, RequestException) as e:
189+
if isinstance(e, HTTPError) and e.response.status_code == HTTPStatus.BAD_REQUEST:
187190
log.warning("Can't delete %s, it's the main branch", str(self))
191+
else:
192+
log.error("%s while deleting %s", util.error_msg(e), str(self))
188193
return False
189194

190195
def new_code(self) -> str:
@@ -197,11 +202,10 @@ def new_code(self) -> str:
197202
elif self._new_code is None:
198203
try:
199204
data = json.loads(self.get(api=APIS["get_new_code"], params={"project": self.concerned_object.key}).text)
200-
except HTTPError as e:
201-
if e.response.status_code == HTTPStatus.NOT_FOUND:
205+
except (ConnectionError, RequestException) as e:
206+
if isinstance(e, HTTPError) and e.response.status_code == HTTPStatus.NOT_FOUND:
202207
raise exceptions.ObjectNotFound(self.concerned_object.key, f"{str(self.concerned_object)} not found")
203-
if e.response.status_code == HTTPStatus.FORBIDDEN:
204-
log.error("Error 403 when getting new code period of %s", {str(self)})
208+
log.error("%s while getting new code period of %s", util.error_msg(e), str(self))
205209
raise e
206210
for b in data["newCodePeriods"]:
207211
new_code = settings.new_code_to_string(b)
@@ -261,9 +265,11 @@ def rename(self, new_name):
261265
log.info("Renaming main branch of %s from '%s' to '%s'", str(self.concerned_object), self.name, new_name)
262266
try:
263267
self.post(APIS["rename"], params={"project": self.concerned_object.key, "name": new_name})
264-
except HTTPError as e:
265-
if e.response.status_code == HTTPStatus.NOT_FOUND:
268+
except (ConnectionError, RequestException) as e:
269+
if isinstance(HTTPError, e) and e.response.status_code == HTTPStatus.NOT_FOUND:
266270
raise exceptions.ObjectNotFound(self.concerned_object.key, f"str{self.concerned_object} not found")
271+
log.error("%s while renaming %s", util.error_msg(e), str(self))
272+
raise
267273
_OBJECTS.pop(self.uuid(), None)
268274
self.name = new_name
269275
_OBJECTS[self.uuid()] = self
@@ -358,11 +364,8 @@ def audit(self, audit_settings: types.ConfigSettings) -> list[Problem]:
358364
log.debug("Auditing %s", str(self))
359365
try:
360366
return self.__audit_last_analysis(audit_settings) + self.__audit_zero_loc() + self.__audit_never_analyzed()
361-
except HTTPError as e:
362-
if e.response.status_code == HTTPStatus.FORBIDDEN:
363-
log.error("Not enough permission to fully audit %s", str(self))
364-
else:
365-
log.error("HTTP error %s while auditing %s", str(e), str(self))
367+
except Exception as e:
368+
log.error("%s while auditing %s, audit skipped", util.error_msg(e), str(self))
366369
else:
367370
log.debug("Branch audit disabled, skipping audit of %s", str(self))
368371
return []

sonar/devops.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from http import HTTPStatus
2424
import json
2525

26-
from requests.exceptions import HTTPError
26+
from requests import RequestException
2727

2828
import sonar.logging as log
2929
from sonar.util import types
@@ -109,10 +109,11 @@ def create(cls, endpoint: platform.Platform, key: str, plt_type: str, url_or_wor
109109
elif plt_type == "bitbucketcloud":
110110
params.update({"clientSecret": _TO_BE_SET, "clientId": _TO_BE_SET, "workspace": url_or_workspace})
111111
endpoint.post(_CREATE_API_BBCLOUD, params=params)
112-
except HTTPError as e:
112+
except (ConnectionError, RequestException) as e:
113113
if e.response.status_code == HTTPStatus.BAD_REQUEST and endpoint.edition() in ("community", "developer"):
114114
log.warning("Can't set DevOps platform '%s', don't you have more that 1 of that type?", key)
115115
raise exceptions.UnsupportedOperation(f"Can't set DevOps platform '{key}', don't you have more that 1 of that type?")
116+
log.error("%s while creating devops platform %s/%s/%s", util.error_msg(e), key, plt_type, url_or_workspace)
116117
raise
117118
o = DevopsPlatform(endpoint=endpoint, key=key, platform_type=plt_type)
118119
o.refresh()

sonar/hotspots.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import json
2525
import re
2626
from http import HTTPStatus
27-
from requests.exceptions import HTTPError
27+
from requests import RequestException
2828
import requests.utils
2929

3030
import sonar.logging as log
@@ -402,12 +402,13 @@ def search(endpoint: pf.Platform, filters: types.ApiParams = None) -> dict[str,
402402
try:
403403
data = json.loads(endpoint.get(Hotspot.SEARCH_API, params=inline_filters, mute=(HTTPStatus.NOT_FOUND,)).text)
404404
nbr_hotspots = util.nbr_total_elements(data)
405-
except HTTPError as e:
405+
except (ConnectionError, RequestException) as e:
406406
if e.response.status_code == HTTPStatus.NOT_FOUND:
407407
log.warning("No hotspots found with search params %s", str(inline_filters))
408408
nbr_hotspots = 0
409409
return {}
410-
raise e
410+
log.error("%s while searching hotspots", util.error_msg(e))
411+
break
411412
nbr_pages = util.nbr_pages(data)
412413
log.debug("Number of hotspots: %d - Page: %d/%d", nbr_hotspots, inline_filters["p"], nbr_pages)
413414
if nbr_hotspots > Hotspot.MAX_SEARCH:
@@ -494,8 +495,10 @@ def post_search_filter(hotspots_dict: dict[str, Hotspot], filters: types.ApiPara
494495
lang = rules.get_object(endpoint=finding.endpoint, key=finding.rule).language
495496
if lang not in filters["languages"]:
496497
filtered_findings.pop(key, None)
498+
# pylint: disable-next=E0606
497499
if "createdAfter" in filters and finding.creation_date < min_date:
498500
filtered_findings.pop(key, None)
501+
# pylint: disable-next=E0606
499502
if "createdBefore" in filters and finding.creation_date > max_date:
500503
filtered_findings.pop(key, None)
501504

0 commit comments

Comments
 (0)