assemblyline-v4-service 4.6.1.dev231__py3-none-any.whl → 4.7.0.dev25__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,11 +2,14 @@ import importlib
2
2
  import inspect
3
3
  import json
4
4
  import os
5
+ import shutil
5
6
  import tempfile
6
7
  import threading
7
8
 
8
9
  from assemblyline.common.isotime import now_as_iso
9
10
  from assemblyline.odm.models.service import SIGNATURE_DELIMITERS
11
+ from assemblyline.odm.models.signature import Signature
12
+
10
13
  from assemblyline_v4_service.common.base import ServiceBase
11
14
  from assemblyline_v4_service.updater.client import (
12
15
  BadlistClient,
@@ -30,7 +33,9 @@ class TestSignatureClient(SignatureClient):
30
33
  def add_update_many(self, source, sig_type, data, dedup_name=True):
31
34
  os.makedirs(os.path.join(self.output_directory, sig_type, source), exist_ok=True)
32
35
  for d in data:
33
- with open(os.path.join(self.output_directory, sig_type, source, d['name']), 'w') as f:
36
+ if isinstance(d, Signature):
37
+ d = d.as_primitives()
38
+ with open(os.path.join(self.output_directory, sig_type, source, d['signature_id']), 'w') as f:
34
39
  json.dump(d, f)
35
40
 
36
41
  return {'success': len(data)}
@@ -126,7 +131,7 @@ def load_rules(service: ServiceBase):
126
131
 
127
132
  if self.delimiter != "file":
128
133
  # Render the response when calling `client.signature.download`
129
- os.removedirs(sourcepath)
134
+ shutil.rmtree(sourcepath)
130
135
  with open(os.path.join(self.latest_updates_dir, source), 'w') as f:
131
136
  f.write(SIGNATURE_DELIMITERS[self.delimiter].join(signaure_data))
132
137
 
@@ -1,34 +1,33 @@
1
+ """Utility script for reading pod health status for kubernetes deployments."""
2
+ from os import environ, path
3
+
1
4
  import requests
2
5
 
3
- from os import environ, path
4
- from assemblyline.common import forge
5
- from sys import exit
6
6
 
7
7
  UPDATES_CA = environ.get('UPDATES_CA', '/etc/assemblyline/ssl/al_root-ca.crt')
8
8
  SERVICE_SERVER_CA = environ.get('SERVICE_SERVER_CA', '/etc/assemblyline/ssl/al_root-ca.crt')
9
+ REQUEST_TIMEOUT = 30
9
10
 
10
11
 
11
- # Intended for use with Kubernetes deployments
12
12
  def perform_check():
13
- # If the service is privileged, test connectivity to core
14
- if environ.get('PRIVILEGED', 'false').lower() == 'true':
15
- forge.get_datastore()
16
- forge.get_filestore(connection_attempts=1)
17
- forge.get_service_queue(service=environ['AL_SERVICE_NAME'])
18
- else:
19
- service_api_host = environ['SERVICE_API_HOST']
20
- verify = None if not service_api_host.startswith('https') else SERVICE_SERVER_CA
21
- # Otherwise, perform a test for service-server availability
22
- if not requests.get(f"{service_api_host}/healthz/live", verify=verify).ok:
23
- raise Exception('Unable to reach service-server')
13
+ """
14
+ Test prerequisites for this service being healthy.
15
+
16
+ Intended for use with Kubernetes deployments.
17
+ """
18
+
19
+ # Probe service server
20
+ service_api_host = environ['SERVICE_API_HOST']
21
+ verify = None if not service_api_host.startswith('https') else SERVICE_SERVER_CA
22
+ if not requests.get(f"{service_api_host}/healthz/live", verify=verify, timeout=REQUEST_TIMEOUT).ok:
23
+ raise RuntimeError('Unable to reach service-server')
24
24
 
25
25
  # If running with an updater, check for availability. Make sure test doesn't run on the actual updater.
26
26
  if environ.get('updates_host') and not environ['HOSTNAME'].startswith(environ['updates_host']):
27
27
  scheme, verify = ("http", None) if not path.exists(UPDATES_CA) else ("https", UPDATES_CA)
28
- if not requests.get(f"{scheme}://{environ['updates_host']}:{environ['updates_port']}/healthz/live",
29
- verify=verify).ok:
30
- raise Exception('Unable to reach local update server')
31
- exit()
28
+ updater_url = f"{scheme}://{environ['updates_host']}:{environ['updates_port']}/healthz/live"
29
+ if not requests.get(updater_url, verify=verify, timeout=REQUEST_TIMEOUT).ok:
30
+ raise RuntimeError('Unable to reach local update server')
32
31
 
33
32
 
34
33
  if __name__ == '__main__':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: assemblyline-v4-service
3
- Version: 4.6.1.dev231
3
+ Version: 4.7.0.dev25
4
4
  Summary: Assemblyline 4 - Service base
5
5
  Home-page: https://github.com/CybercentreCanada/assemblyline-v4-service/
6
6
  Author: CCCS Assemblyline development team
@@ -1,22 +1,21 @@
1
- assemblyline_v4_service/VERSION,sha256=8DtAyK44jTf2L83IBmIrZvpo7UPaxJnP23exVsiQSO8,13
1
+ assemblyline_v4_service/VERSION,sha256=86rnx2n5DlVlISBAT0O8ZIOeE-g_gwhj06QGH74PUGQ,12
2
2
  assemblyline_v4_service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- assemblyline_v4_service/healthz.py,sha256=3QGBg0EZuXC6UN411HFwpLNEop9UvS9feFhvBUTP-k4,1576
3
+ assemblyline_v4_service/healthz.py,sha256=2Xu9NAQwrb_0mIMgNmpppDvYYq3HYz-ofLIv4tGP8Ns,1406
4
4
  assemblyline_v4_service/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- assemblyline_v4_service/run_privileged_service.py,sha256=un2zcZjQVKYwMWihLLmeUc3IMJ6ALnFbR1FPeMW1U2A,14486
6
5
  assemblyline_v4_service/run_service.py,sha256=XfdABk3hEZsIw31tmFcJc-FbcxvBF9tiDIlg9oHCtZA,5900
7
6
  assemblyline_v4_service/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- assemblyline_v4_service/common/api.py,sha256=Xzp8j4HCCfjPvNSGKiZl5ttH2_Itg47cjlH0NXNtth0,6849
9
- assemblyline_v4_service/common/base.py,sha256=psivTxiOeN2jqL3G3I26oY9JFK-qPuwrg5y_y_d7xYs,14127
7
+ assemblyline_v4_service/common/api.py,sha256=hY1l-REWN77nflIYVhkDX7JtMFzUwsTzNhnbr3df29I,5189
8
+ assemblyline_v4_service/common/base.py,sha256=jdxGX9Wd2B_SvOjfxRHdycTq_1Upb9KiDQ5ZaCqQsqw,13926
10
9
  assemblyline_v4_service/common/helper.py,sha256=xs9quuf-M1JOdKieBqOmWaOece0CtzXFhhe85xQYmuY,3289
11
10
  assemblyline_v4_service/common/ocr.py,sha256=NgkFqAq2lRzIveYUulKJmiiWYqwf4siYbL59n1Ow02o,8350
12
- assemblyline_v4_service/common/ontology_helper.py,sha256=9Ad81qbddg_pRMupT8o_KzxbKgpodaRqpc3mPoEKLtw,8494
13
- assemblyline_v4_service/common/request.py,sha256=W7fqC2xQE3i5i2jlCDyUDp3ZqJQQqSshNW0mQfJMkFg,11792
14
- assemblyline_v4_service/common/result.py,sha256=9AqM6qCYiia_Bpyn_fBFhzNQMcqJbtFSiGjp57fXW2E,32713
15
- assemblyline_v4_service/common/task.py,sha256=dJsvRpW0x88CCF_LW6w87jQ_UKTVaOs2Gb117IDNiU8,14233
11
+ assemblyline_v4_service/common/ontology_helper.py,sha256=XxZ488-B7fCP4cMSMfeflzTG_cSQ3GwfH0nM_7enCiY,8496
12
+ assemblyline_v4_service/common/request.py,sha256=Okj7X7MlNPTGitiQpuMMACsDsrgOd33ajWbIiFNXEjk,11729
13
+ assemblyline_v4_service/common/result.py,sha256=rnjJJCZnXU2lmZwWoLIQwWGKa4l-Zx5NkT5lXyJfMa4,54924
14
+ assemblyline_v4_service/common/task.py,sha256=yTNdQ3C07g3zzK3fxDdOeeZknn_SUBNPAbRLwFZsrzc,14168
16
15
  assemblyline_v4_service/common/utils.py,sha256=FDFsFcI6wt-pWyeQYnDWivsPbtme5RqVyofmNiggh6Y,3922
17
16
  assemblyline_v4_service/dev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
17
  assemblyline_v4_service/dev/run_service_once.py,sha256=W9kR49IUbkt8tNXjCT40ZMh-8p5W_odxlkDx6nhTAYM,10656
19
- assemblyline_v4_service/dev/updater.py,sha256=b-FK6XPRZbETbl-SIYEhnYGT-W7EcQhnxwD6x2NMC7g,6411
18
+ assemblyline_v4_service/dev/updater.py,sha256=uumFNJo6WSW6ngv9p9qZPD83jXF2jX4aYJs_IdpzmHo,6569
20
19
  assemblyline_v4_service/updater/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
20
  assemblyline_v4_service/updater/__main__.py,sha256=9Os-u8Tf7MD73JSrUSPmOaErTgfvesNLiEeszU4ujXA,133
22
21
  assemblyline_v4_service/updater/app.py,sha256=Mtmx4bkXfP4nFqqa5q15jW8QIXr4JK84lCovxAVyvPs,3317
@@ -24,14 +23,14 @@ assemblyline_v4_service/updater/client.py,sha256=tLY84gaGdFBVIDaMgRHIEa7x2S8jBl7
24
23
  assemblyline_v4_service/updater/gunicorn_config.py,sha256=p3j2KPBeD5jvMw9O5i7vAtlRgPSVVxIG9AO0DfN82J8,1247
25
24
  assemblyline_v4_service/updater/helper.py,sha256=OTV6WA77wBDOSVWaxijNg-HpwvEwnZozH03S3Q4oUns,10764
26
25
  assemblyline_v4_service/updater/updater.py,sha256=XiqabDp89-t_J6C3U33R-RvA5lMIahFW_MsAVUGyXok,31876
27
- assemblyline_v4_service-4.6.1.dev231.dist-info/licenses/LICENCE.md,sha256=NSkYo9EH8h5oOkzg4VhjAHF4339MqPP2cQ8msTPgl-c,1396
26
+ assemblyline_v4_service-4.7.0.dev25.dist-info/licenses/LICENCE.md,sha256=NSkYo9EH8h5oOkzg4VhjAHF4339MqPP2cQ8msTPgl-c,1396
28
27
  test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
28
  test/conftest.py,sha256=W3SieQpZsZpGEmtLqY4aIlxREDSsHceyCrFcFsWUM0U,1851
30
29
  test/test_healthz.py,sha256=DkeLUlrb7rGx3nZ04aADU9HXXu5mZTf_DBwT0xhzIv4,7
31
30
  test/test_run_privileged_service.py,sha256=DkeLUlrb7rGx3nZ04aADU9HXXu5mZTf_DBwT0xhzIv4,7
32
31
  test/test_run_service.py,sha256=DkeLUlrb7rGx3nZ04aADU9HXXu5mZTf_DBwT0xhzIv4,7
33
32
  test/test_common/__init__.py,sha256=RkOm3vnVp5L947mD1jTo4bdOgLTZJ24_NX-kqfMn5a8,1259
34
- test/test_common/test_api.py,sha256=7wlo7wgB12T23zMLbwjJ3GIomLHqE_Qvs3xkibSsR1U,4902
33
+ test/test_common/test_api.py,sha256=jkgQxVZ6zvGkGL2yCePr8ebvsmH93awfCJm11o11XWA,4231
35
34
  test/test_common/test_base.py,sha256=fuJSSlPxIDHq6HU1xbvaMFitw2z1spOZNHD2SJ4UUic,13346
36
35
  test/test_common/test_helper.py,sha256=sO6YAiBhKTqaxlpLhFYDuy2ZdbuF2cg07Ylzo83ZzQs,2575
37
36
  test/test_common/test_ocr.py,sha256=X_Y3c_yfRljD0o2SRUHuotKLTTX0lD5zW68mzQ7LKu4,1250
@@ -40,7 +39,7 @@ test/test_common/test_request.py,sha256=HiDU1n4Rjso_U0qDME4ohA_9j7rpfqLSD1-e2Rfq
40
39
  test/test_common/test_result.py,sha256=ZtLUddBDA_BTIjG3Jasbq78_AdEjCRe4cb85XLBwH5o,43585
41
40
  test/test_common/test_task.py,sha256=P44mNcSe-3tJgDk9ppN3KbM7oN4LBVIuhONG-Gveh74,19007
42
41
  test/test_common/test_utils.py,sha256=TbnBxqpS_ZC5ptXR9XJX3xtbItD0mTbtiBxxdyP8J5k,5904
43
- assemblyline_v4_service-4.6.1.dev231.dist-info/METADATA,sha256=IonPDpmHPmwNmIfb2yW_EC1DFE7kVDXGmjz0BufwTbI,5625
44
- assemblyline_v4_service-4.6.1.dev231.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
- assemblyline_v4_service-4.6.1.dev231.dist-info/top_level.txt,sha256=LpTOEaVCatkrvbVq3EZseMSIa2PQZU-2rhuO_FTpZgY,29
46
- assemblyline_v4_service-4.6.1.dev231.dist-info/RECORD,,
42
+ assemblyline_v4_service-4.7.0.dev25.dist-info/METADATA,sha256=WiWDzeRFZ7tNT7MunyO5y73UTKHxbnqJHpp6l0Re2dk,5624
43
+ assemblyline_v4_service-4.7.0.dev25.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
44
+ assemblyline_v4_service-4.7.0.dev25.dist-info/top_level.txt,sha256=LpTOEaVCatkrvbVq3EZseMSIa2PQZU-2rhuO_FTpZgY,29
45
+ assemblyline_v4_service-4.7.0.dev25.dist-info/RECORD,,
@@ -118,27 +118,3 @@ def test_serviceapi_lookup_safelist():
118
118
 
119
119
  # TODO
120
120
  # SafelistClient requires forge access
121
-
122
- # def test_privilegedserviceapi_init():
123
- # log = logging.getLogger('assemblyline')
124
- # psa = PrivilegedServiceAPI(log)
125
- # assert psa.log == log
126
- # assert isinstance(psa.safelist_client, SafelistClient)
127
-
128
-
129
- # def test_privilegedserviceapi_get_safelist():
130
- # log = logging.getLogger('assemblyline')
131
- # psa = PrivilegedServiceAPI(log)
132
- # assert psa.get_safelist() == {}
133
-
134
- # # TODO
135
- # # Test not in development mode
136
-
137
-
138
- # def test_privilegedserviceapi_lookup_safelist():
139
- # log = logging.getLogger('assemblyline')
140
- # psa = PrivilegedServiceAPI(log)
141
- # assert psa.lookup_safelist("qhash") is None
142
-
143
- # TODO
144
- # Test not in development mode
@@ -1,337 +0,0 @@
1
- import copy
2
- import json
3
- import os
4
- import shutil
5
- import tempfile
6
- from io import BytesIO
7
- from json import JSONDecodeError
8
-
9
- import yaml
10
- from assemblyline_core.server_base import ServerBase
11
- from assemblyline_core.tasking_client import TaskingClient
12
-
13
- from assemblyline.common.digests import get_sha256_for_file
14
- from assemblyline.common.importing import load_module_by_path
15
- from assemblyline.common.metrics import MetricsFactory
16
- from assemblyline.common.str_utils import StringTable
17
- from assemblyline.common.version import BUILD_MINOR, FRAMEWORK_VERSION, SYSTEM_VERSION
18
- from assemblyline.filestore import FileStoreException
19
- from assemblyline.odm.messages.service_heartbeat import Metrics
20
- from assemblyline.odm.messages.task import Task as ServiceTask
21
- from assemblyline.remote.datatypes import get_client
22
- from assemblyline_v4_service.common.base import is_recoverable_runtime_error
23
-
24
- SERVICE_PATH = os.environ['SERVICE_PATH']
25
- SERVICE_TAG = os.environ.get("SERVICE_TAG", f"{FRAMEWORK_VERSION}.{SYSTEM_VERSION}.{BUILD_MINOR}.dev0").encode("utf-8")
26
- SERVICE_MANIFEST = os.path.join(os.getcwd(), os.environ.get('MANIFEST_FOLDER', ''), 'service_manifest.yml')
27
- TMP_SERVICE_MANIFEST = f"/tmp/{os.environ.get('RUNTIME_PREFIX', 'service')}_manifest.yml"
28
- REGISTER_ONLY = os.environ.get('REGISTER_ONLY', 'False').lower() == 'true'
29
-
30
- SERVICE_NAME = SERVICE_PATH.split(".")[-1].lower()
31
- SHUTDOWN_SECONDS_LIMIT = 10
32
- TASK_REQUEST_TIMEOUT = 30
33
-
34
- STATUSES = StringTable('STATUSES', [
35
- ('INITIALIZING', 0),
36
- ('WAITING_FOR_TASK', 1),
37
- ('DOWNLOADING_FILE', 2),
38
- ('PROCESSING', 3),
39
- ('RESULT_FOUND', 4),
40
- ('ERROR_FOUND', 5),
41
- ('STOPPING', 6),
42
- ('FILE_NOT_FOUND', 7),
43
- ])
44
-
45
-
46
- class RunPrivilegedService(ServerBase):
47
- def __init__(self, shutdown_timeout: int = SHUTDOWN_SECONDS_LIMIT):
48
- super(RunPrivilegedService, self).__init__(
49
- f'assemblyline.service.{SERVICE_NAME}', shutdown_timeout=shutdown_timeout)
50
-
51
- self.client_id = os.environ.get('HOSTNAME', 'dev-service')
52
-
53
- self.redis = get_client(
54
- host=self.config.core.redis.nonpersistent.host,
55
- port=self.config.core.redis.nonpersistent.port,
56
- private=False,
57
- )
58
-
59
- self.redis_persist = get_client(
60
- host=self.config.core.redis.persistent.host,
61
- port=self.config.core.redis.persistent.port,
62
- private=False,
63
- )
64
-
65
- self.tasking_client = TaskingClient(redis=self.redis, redis_persist=self.redis_persist,
66
- register_only=REGISTER_ONLY)
67
- self.tasking_dir = os.environ.get('TASKING_DIR', tempfile.gettempdir())
68
-
69
- self.service = None
70
- self.service_config = {}
71
- self.service_name = None
72
- self.service_tool_version = None
73
-
74
- self.status = STATUSES.INITIALIZING
75
- self.metric_factory = None
76
-
77
- def _load_manifest(self):
78
- bio = BytesIO()
79
- with open(SERVICE_MANIFEST, "rb") as srv_manifest:
80
- for line in srv_manifest.readlines():
81
- bio.write(line.replace(b"$SERVICE_TAG", SERVICE_TAG))
82
- bio.flush()
83
- bio.seek(0)
84
-
85
- return yaml.safe_load(bio)
86
-
87
- def _update_manifest(self, data):
88
- with open(SERVICE_MANIFEST, 'r') as yml_fh:
89
- manifest = yaml.safe_load(yml_fh)
90
- manifest.update(data)
91
-
92
- with open(TMP_SERVICE_MANIFEST, 'w') as yml_fh:
93
- yaml.safe_dump(manifest, yml_fh)
94
-
95
- data = manifest
96
-
97
- def _cleanup_working_directory(self):
98
- # Make the tasking dir if it does not exists
99
- if not os.path.exists(self.tasking_dir):
100
- os.makedirs(self.tasking_dir)
101
-
102
- for file in os.listdir(self.tasking_dir):
103
- file_path = os.path.join(self.tasking_dir, file)
104
- try:
105
- if file_path == TMP_SERVICE_MANIFEST:
106
- # We'd like to keep the service_manifest file committed to disk
107
- continue
108
-
109
- if os.path.isfile(file_path):
110
- os.unlink(file_path)
111
- elif os.path.isdir(file_path):
112
- shutil.rmtree(file_path)
113
- except Exception:
114
- pass
115
-
116
- def try_run(self):
117
- self.status = STATUSES.INITIALIZING
118
-
119
- # Try to load service class
120
- try:
121
- service_class = load_module_by_path(SERVICE_PATH)
122
- except Exception:
123
- self.log.error("Could not find service in path.")
124
- raise
125
-
126
- # Load on-disk manifest for bootstrap/registration
127
- service_manifest = self._load_manifest()
128
- file_required = service_manifest.get('file_required', True)
129
-
130
- # Register the service
131
- registration = self.tasking_client.register_service(service_manifest)
132
-
133
- # Are we just registering?
134
- if not registration['keep_alive'] or REGISTER_ONLY:
135
- self.status = STATUSES.STOPPING
136
- self.stop()
137
- return
138
-
139
- # Commit service manifest changes back to disk
140
- self._update_manifest(registration.get('service_config', {}))
141
-
142
- # Instantiate the service based of the registration results
143
- self.service_config = registration.get('service_config', {})
144
- self.service = service_class(config=self.service_config.get('config'))
145
- self.service_name = self.service_config['name']
146
- self.service_tool_version = self.service.get_tool_version()
147
- self.metric_factory = MetricsFactory('service', Metrics, name=self.service_name,
148
- export_zero=False, redis=self.redis)
149
-
150
- # Start the service
151
- self.service.start_service()
152
-
153
- while self.running:
154
- # Cleanup the working directory
155
- self._cleanup_working_directory()
156
-
157
- # Get a task
158
- self.status = STATUSES.WAITING_FOR_TASK
159
- task, _ = self.tasking_client.get_task(self.client_id, self.service_name, self.service_config['version'],
160
- self.service_tool_version, self.metric_factory)
161
-
162
- if not task:
163
- continue
164
-
165
- # Load Task
166
- try:
167
- # Inspect task to ensure submission parameters are given, add defaults where necessary
168
- params = {x['name']: x['default'] for x in service_manifest.get('submission_params', [])}
169
- params.update(task['service_config'])
170
- task['service_config'] = params
171
- service_task = ServiceTask(task)
172
- self.log.info(f"[{service_task.sid}] New task received")
173
- except ValueError as e:
174
- self.log.error(f"Invalid task received: {str(e)}")
175
- continue
176
-
177
- # Download file if needed
178
- if file_required:
179
- self.status = STATUSES.DOWNLOADING_FILE
180
- file_path = os.path.join(self.tasking_dir, service_task.fileinfo.sha256)
181
- received_file_sha256 = None
182
- self.log.info(f"[{service_task.sid}] Downloading file: {service_task.fileinfo.sha256}")
183
- try:
184
- self.tasking_client.filestore.download(service_task.fileinfo.sha256, file_path)
185
- received_file_sha256 = get_sha256_for_file(file_path)
186
- except FileStoreException:
187
- self.status = STATUSES.FILE_NOT_FOUND
188
- self.log.error(
189
- f"[{service_task.sid}] Requested file not found in the system: {service_task.fileinfo.sha256}")
190
-
191
- # If the file retrieved is different from what we requested, report the error
192
- if received_file_sha256 and received_file_sha256 != service_task.fileinfo.sha256:
193
- self.status = STATUSES.ERROR_FOUND
194
- self.log.error(f"[{service_task.sid}] Downloaded ({received_file_sha256}) doesn't match "
195
- f"requested ({service_task.fileinfo.sha256})")
196
-
197
- # Process if we're not already in error
198
- if self.status not in [STATUSES.ERROR_FOUND, STATUSES.FILE_NOT_FOUND]:
199
- self.status = STATUSES.PROCESSING
200
- self.service.handle_task(service_task)
201
-
202
- # Check for the response from the service
203
- result_json = os.path.join(
204
- self.tasking_dir, f"{service_task.sid}_{service_task.fileinfo.sha256}_result.json")
205
- error_json = os.path.join(
206
- self.tasking_dir, f"{service_task.sid}_{service_task.fileinfo.sha256}_error.json")
207
- if os.path.exists(result_json):
208
- self.status = STATUSES.RESULT_FOUND
209
- elif os.path.exists(error_json):
210
- self.status = STATUSES.ERROR_FOUND
211
- else:
212
- self.status = STATUSES.ERROR_FOUND
213
- error_json = None
214
-
215
- # Handle the service response
216
- if self.status == STATUSES.RESULT_FOUND:
217
- self.log.info(f"[{service_task.sid}] Task successfully completed")
218
- try:
219
- self._handle_task_result(result_json, service_task)
220
- except RuntimeError as re:
221
- if is_recoverable_runtime_error(re):
222
- self.log.info(f"[{service_task.sid}] Service trying to use a threadpool during shutdown, "
223
- "sending recoverable error.")
224
- self._handle_task_error(service_task)
225
- else:
226
- raise
227
- elif self.status == STATUSES.ERROR_FOUND:
228
- self.log.info(f"[{service_task.sid}] Task completed with errors")
229
- self._handle_task_error(service_task, error_json_path=error_json)
230
- elif self.status == STATUSES.FILE_NOT_FOUND:
231
- self.log.info(f"[{service_task.sid}] Task completed with errors due to missing file from filestore")
232
- self._handle_task_error(service_task, status="FAIL_NONRECOVERABLE", error_type="EXCEPTION")
233
-
234
- def _handle_task_result(self, result_json_path: str, task: ServiceTask):
235
- with open(result_json_path, 'r') as f:
236
- result = json.load(f)
237
-
238
- # Map of file info by SHA256
239
- result_files = {}
240
- for file in result['response']['extracted'] + result['response']['supplementary']:
241
- result_files[file['sha256']] = copy.deepcopy(file)
242
- file.pop('path', None)
243
-
244
- new_tool_version = result.get('response', {}).get('service_tool_version', None)
245
- if new_tool_version is not None and self.service_tool_version != new_tool_version:
246
- self.service_tool_version = new_tool_version
247
-
248
- resp = {'success': False, 'missing_files': []}
249
- freshen = True
250
-
251
- while not resp['success']:
252
- for f_sha256 in resp['missing_files']:
253
- file_info = result_files[f_sha256]
254
- self.log.info(f"[{task.sid}] Uploading file {file_info['path']} [{file_info['sha256']}]")
255
-
256
- self.tasking_client.upload_file(
257
- file_info['path'],
258
- file_info['classification'],
259
- task.ttl,
260
- file_info.get('is_section_image', False),
261
- file_info.get('is_supplementary', False),
262
- expected_sha256=file_info['sha256']
263
- )
264
-
265
- try:
266
- resp = self.tasking_client.task_finished(
267
- dict(task=task.as_primitives(), result=result, freshen=freshen), self.client_id,
268
- self.service_name, self.metric_factory)
269
- if resp is None:
270
- self._handle_task_error(task, message="No result or error provided by service.",
271
- error_type='EXCEPTION', status='FAIL_NONRECOVERABLE')
272
- return
273
- except ValueError as e:
274
- self._handle_task_error(task, message=str(e), error_type='EXCEPTION', status='FAIL_NONRECOVERABLE')
275
- return
276
-
277
- freshen = False
278
-
279
- def _handle_task_error(self, task: ServiceTask, error_json_path=None,
280
- message=None, error_type=None, status=None):
281
- if task is None:
282
- return
283
-
284
- if self.service:
285
- version = self.service_config['version']
286
- else:
287
- version = '0'
288
-
289
- error = dict(
290
- response=dict(
291
- message=message or "The service instance processing this task has terminated unexpectedly.",
292
- service_name=task.service_name,
293
- service_version=version,
294
- status=status or 'FAIL_RECOVERABLE',
295
- ),
296
- sha256=task.fileinfo.sha256,
297
- type=error_type or 'UNKNOWN',
298
- )
299
-
300
- if error_json_path:
301
- try:
302
- with open(error_json_path, 'r') as f:
303
- error = json.load(f)
304
- except (IOError, JSONDecodeError, OSError):
305
- self.log.exception(f"[{task.sid}] An error occurred while loading service error file.")
306
-
307
- try:
308
- resp = self.tasking_client.task_finished(
309
- dict(task=task.as_primitives(), error=error), self.client_id,
310
- self.service_name, self.metric_factory)
311
- if resp is None:
312
- self.log.error(f"[{task.sid}] The tasking client failed to detect the error data.")
313
- except ValueError as e:
314
- self.log.exception(
315
- f"[{task.sid}] An error occured while trying to save the result error in the system: '{str(e)}'")
316
-
317
- def stop(self):
318
- if self.status == STATUSES.WAITING_FOR_TASK:
319
- # A task request was sent and a task might be received, so shutdown after giving service time to process it
320
- self._shutdown_timeout = TASK_REQUEST_TIMEOUT + self.service_config.get('timeout', SHUTDOWN_SECONDS_LIMIT)
321
- elif self.status not in [STATUSES.INITIALIZING, STATUSES.STOPPING]:
322
- # A task is currently running, so wait until service timeout before doing a hard stop
323
- self._shutdown_timeout = self.service_config.get('timeout', SHUTDOWN_SECONDS_LIMIT)
324
- else:
325
- # Already the default
326
- self._shutdown_timeout = SHUTDOWN_SECONDS_LIMIT
327
-
328
- if self.service:
329
- self.service.stop_service()
330
-
331
- self.tasking_client.stop()
332
-
333
- super().stop()
334
-
335
-
336
- if __name__ == '__main__':
337
- RunPrivilegedService().serve_forever()