diff --git a/swh/search/elasticsearch.py b/swh/search/elasticsearch.py --- a/swh/search/elasticsearch.py +++ b/swh/search/elasticsearch.py @@ -4,6 +4,7 @@ # See top-level LICENSE file for more information import base64 +from textwrap import dedent from typing import Any, Dict, Iterable, Iterator, List, Optional from elasticsearch import Elasticsearch, helpers @@ -168,22 +169,24 @@ (origin_identifier(document), document) for document in documents ) # painless script that will be executed when updating an origin document - update_script = """ - // backup current visit_types field value - List visit_types = ctx._source.getOrDefault("visit_types", []); - - // update origin document with new field values - ctx._source.putAll(params); - - // restore previous visit types after visit_types field overriding - if (ctx._source.containsKey("visit_types")) { - for (int i = 0; i < visit_types.length; ++i) { - if (!ctx._source.visit_types.contains(visit_types[i])) { - ctx._source.visit_types.add(visit_types[i]); + update_script = dedent( + """ + // backup current visit_types field value + List visit_types = ctx._source.getOrDefault("visit_types", []); + + // update origin document with new field values + ctx._source.putAll(params); + + // restore previous visit types after visit_types field overriding + if (ctx._source.containsKey("visit_types")) { + for (int i = 0; i < visit_types.length; ++i) { + if (!ctx._source.visit_types.contains(visit_types[i])) { + ctx._source.visit_types.add(visit_types[i]); + } } } - } - """ + """ + ) actions = [ { diff --git a/swh/search/tests/test_elasticsearch.py b/swh/search/tests/test_elasticsearch.py --- a/swh/search/tests/test_elasticsearch.py +++ b/swh/search/tests/test_elasticsearch.py @@ -3,8 +3,10 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information +import types import unittest +from elasticsearch.helpers.errors import BulkIndexError import pytest from swh.search.metrics import OPERATIONS_METRIC @@ -19,6 +21,33 @@ self.search = swh_search self.mocker = mocker + # override self.search.origin_update to catch painless script errors + # and pretty print them + origin_update = self.search.origin_update + + def _origin_update(self, *args, **kwargs): + script_error = False + error_detail = "" + try: + origin_update(*args, **kwargs) + except BulkIndexError as e: + error = e.errors[0].get("update", {}).get("error", {}).get("caused_by") + if error and "script_stack" in error: + script_error = True + error_detail = ( + f"Painless update script failed ({error['reason']}).\n" + ) + error_detail += f"error type: {error['caused_by']['type']}\n" + error_detail += f"error reason: {error['caused_by']['reason']}\n" + error_detail += "script stack:\n\n" + "\n".join( + error["script_stack"] + ) + else: + raise e + assert script_error is False, error_detail + + self.search.origin_update = types.MethodType(_origin_update, self.search) + def reset(self): self.search.deinitialize() self.search.initialize()