Skip to content

Commit 4610e64

Browse files
bug-2031736: handle complex query and connection timeout ES errors (#7185)
1 parent 1c0576b commit 4610e64

2 files changed

Lines changed: 86 additions & 11 deletions

File tree

socorro/external/es/supersearch.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,19 @@
22
# License, v. 2.0. If a copy of the MPL was not distributed with this
33
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
from collections import defaultdict
6-
from contextlib import suppress
75
import datetime
86
import re
7+
from collections import defaultdict
8+
from contextlib import suppress
99

10-
from elasticsearch.exceptions import NotFoundError, BadRequestError
11-
from elasticsearch.dsl import A, Q, Search, AggResponse
10+
from elasticsearch.dsl import A, AggResponse, Q, Search
11+
from elasticsearch.exceptions import BadRequestError, ConnectionTimeout, NotFoundError
1212

1313
from socorro.external.es.base import generate_list_of_indexes
1414
from socorro.external.es.search_common import SearchBase
1515
from socorro.external.es.super_search_fields import get_search_key
1616
from socorro.lib import BadArgumentError, MissingArgumentError, libdatetime
1717

18-
1918
BAD_INDEX_REGEX = re.compile(r"\[\[(.*)\] missing\]")
2019
ELASTICSEARCH_PARSE_EXCEPTION_REGEX = re.compile(r"\[([^\]]+)\] could not be parsed")
2120

@@ -485,9 +484,31 @@ def get(self, **kwargs):
485484
if value == bad_input:
486485
raise BadArgumentError(key) from exc
487486

487+
with suppress(KeyError):
488+
if (
489+
exc.body["error"]["caused_by"]["type"]
490+
== "too_complex_to_determinize_exception"
491+
):
492+
raise BadArgumentError(
493+
"query",
494+
msg=(
495+
"Search query is too complex to execute. "
496+
"Simplify the search pattern and try again."
497+
),
498+
) from exc
499+
488500
# Re-raise the original exception
489501
raise
490502

503+
except ConnectionTimeout as exc:
504+
raise BadArgumentError(
505+
"query",
506+
msg=(
507+
"Search query timed out. "
508+
"Simplify the search pattern and try again."
509+
),
510+
) from exc
511+
491512
if shards and shards.failed:
492513
# Some shards failed. We want to explain what happened in the
493514
# results, so the client can decide what to do.

socorro/tests/external/es/test_supersearch.py

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

55
import datetime
6-
import json
76
import http.server
7+
import json
88
import threading
99
from contextlib import contextmanager
1010
from copy import deepcopy
@@ -13,23 +13,26 @@
1313

1414
from socorro import settings
1515
from socorro.external.es import search_common
16-
from socorro.external.es.supersearch import SuperSearch
1716
from socorro.external.es.super_search_fields import FIELDS
17+
from socorro.external.es.supersearch import SuperSearch
1818
from socorro.lib import BadArgumentError, libdatetime
19-
20-
from socorro.libclass import build_instance
2119
from socorro.lib.libdatetime import utc_now
2220
from socorro.lib.libooid import create_new_ooid
21+
from socorro.libclass import build_instance
2322

2423

2524
@contextmanager
26-
def mock_es_server(ip, port, post_response):
25+
def mock_es_server(ip, port, post_response, status_code=200, delay=0):
2726
class MockHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
2827
"""mock request handler"""
2928

3029
def do_POST(self): # pylint: disable=invalid-name
3130
"""Handle GET requests"""
32-
self.send_response(200)
31+
if delay:
32+
import time
33+
34+
time.sleep(delay)
35+
self.send_response(status_code)
3336
self.send_header("Content-Type", "application/json")
3437
self.send_header("X-Elastic-Product", "Elasticsearch")
3538
self.end_headers()
@@ -1884,3 +1887,54 @@ def test_get_with_failing_shards(self, es_helper):
18841887
{"type": "shards", "index": "other_index", "shards_count": 1},
18851888
]
18861889
assert res["errors"] == errors_exp
1890+
1891+
def test_get_with_too_complex_query(self, es_helper):
1892+
ip, port = "127.0.0.1", 9998
1893+
too_complex_response = {
1894+
"error": {
1895+
"root_cause": [
1896+
{
1897+
"type": "too_complex_to_determinize_exception",
1898+
"reason": (
1899+
"Determinizing automaton with 501 states and 125250 "
1900+
"transitions would require more than 10000 effort."
1901+
),
1902+
}
1903+
],
1904+
"type": "search_phase_execution_exception",
1905+
"reason": "all shards failed",
1906+
"phase": "query",
1907+
"grouped": True,
1908+
"failed_shards": [],
1909+
"caused_by": {
1910+
"type": "too_complex_to_determinize_exception",
1911+
"reason": (
1912+
"Determinizing automaton with 501 states and 125250 "
1913+
"transitions would require more than 10000 effort."
1914+
),
1915+
},
1916+
},
1917+
"status": 400,
1918+
}
1919+
with settings.override(**{"ES_STORAGE.options.url": f"http://{ip}:{port}"}):
1920+
crashstorage = self.build_crashstorage()
1921+
api = SuperSearchWithFields(crashstorage=crashstorage)
1922+
1923+
with mock_es_server(ip, port, too_complex_response, status_code=400):
1924+
with pytest.raises(BadArgumentError):
1925+
api.get(signature="@a?a?a?a?a?")
1926+
1927+
def test_get_with_connection_timeout(self, es_helper):
1928+
ip, port = "127.0.0.1", 9999
1929+
with settings.override(
1930+
**{
1931+
"ES_STORAGE.options.url": f"http://{ip}:{port}",
1932+
"ES_STORAGE.options.timeout": 0.05,
1933+
}
1934+
):
1935+
crashstorage = self.build_crashstorage()
1936+
api = SuperSearchWithFields(crashstorage=crashstorage)
1937+
1938+
with mock_es_server(ip, port, {}, delay=0.2):
1939+
with pytest.raises(BadArgumentError):
1940+
api.get(signature="@a?a?a?a?a?")

0 commit comments

Comments
 (0)