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.
- assemblyline_v4_service/VERSION +1 -1
- assemblyline_v4_service/common/api.py +0 -48
- assemblyline_v4_service/common/base.py +2 -6
- assemblyline_v4_service/common/ontology_helper.py +2 -0
- assemblyline_v4_service/common/request.py +4 -5
- assemblyline_v4_service/common/result.py +616 -3
- assemblyline_v4_service/common/task.py +6 -6
- assemblyline_v4_service/dev/updater.py +7 -2
- assemblyline_v4_service/healthz.py +18 -19
- {assemblyline_v4_service-4.6.1.dev231.dist-info → assemblyline_v4_service-4.7.0.dev25.dist-info}/METADATA +1 -1
- {assemblyline_v4_service-4.6.1.dev231.dist-info → assemblyline_v4_service-4.7.0.dev25.dist-info}/RECORD +15 -16
- test/test_common/test_api.py +0 -24
- assemblyline_v4_service/run_privileged_service.py +0 -337
- {assemblyline_v4_service-4.6.1.dev231.dist-info → assemblyline_v4_service-4.7.0.dev25.dist-info}/WHEEL +0 -0
- {assemblyline_v4_service-4.6.1.dev231.dist-info → assemblyline_v4_service-4.7.0.dev25.dist-info}/licenses/LICENCE.md +0 -0
- {assemblyline_v4_service-4.6.1.dev231.dist-info → assemblyline_v4_service-4.7.0.dev25.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
raise
|
|
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,22 +1,21 @@
|
|
|
1
|
-
assemblyline_v4_service/VERSION,sha256=
|
|
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=
|
|
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=
|
|
9
|
-
assemblyline_v4_service/common/base.py,sha256=
|
|
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=
|
|
13
|
-
assemblyline_v4_service/common/request.py,sha256=
|
|
14
|
-
assemblyline_v4_service/common/result.py,sha256=
|
|
15
|
-
assemblyline_v4_service/common/task.py,sha256=
|
|
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=
|
|
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.
|
|
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=
|
|
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.
|
|
44
|
-
assemblyline_v4_service-4.
|
|
45
|
-
assemblyline_v4_service-4.
|
|
46
|
-
assemblyline_v4_service-4.
|
|
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,,
|
test/test_common/test_api.py
CHANGED
|
@@ -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()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|