Skip to content

Commit 3f1b67b

Browse files
author
ffaraoneim
authored
Merge pull request #19 from cloudblue/runner-improvements
Runner improvements
2 parents d91f94d + 6cefeb1 commit 3f1b67b

File tree

6 files changed

+282
-19
lines changed

6 files changed

+282
-19
lines changed

connect/eaas/extension.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ def __init__(self, status):
1717

1818
class ProcessingResponse(_Response):
1919

20-
def __init__(self, status, countdown=0, output=None):
20+
def __init__(self, status, countdown=30, output=None):
2121
super().__init__(status)
22-
self.countdown = countdown
22+
self.countdown = 30 if countdown < 30 else countdown
2323
self.output = output
2424

2525
@classmethod
@@ -34,23 +34,40 @@ def skip(cls, output=None):
3434
def reschedule(cls, countdown=30):
3535
return cls(ResultType.RESCHEDULE, countdown=countdown)
3636

37+
@classmethod
38+
def slow_process_reschedule(cls, countdown=300):
39+
return cls(
40+
ResultType.RESCHEDULE,
41+
countdown=300 if countdown < 300 else countdown,
42+
)
43+
44+
@classmethod
45+
def fail(cls, output=None):
46+
return cls(ResultType.FAIL, output=output)
47+
3748

3849
class ValidationResponse(_Response):
39-
def __init__(self, status, data):
50+
def __init__(self, status, data, output=None):
4051
super().__init__(status)
4152
self.data = data
53+
self.output = output
4254

4355
@classmethod
4456
def done(cls, data):
4557
return cls(ResultType.SUCCESS, data)
4658

59+
@classmethod
60+
def fail(cls, data=None, output=None):
61+
return cls(ResultType.FAIL, data=data, output=output)
62+
4763

4864
class _InteractiveTaskResponse(_Response):
49-
def __init__(self, status, http_status, headers, body):
65+
def __init__(self, status, http_status, headers, body, output):
5066
super().__init__(status)
5167
self.http_status = http_status
5268
self.headers = headers
5369
self.body = body
70+
self.output = output
5471

5572
@property
5673
def data(self):
@@ -62,7 +79,11 @@ def data(self):
6279

6380
@classmethod
6481
def done(cls, http_status=200, headers=None, body=None):
65-
return cls(ResultType.SUCCESS, http_status, headers, body)
82+
return cls(ResultType.SUCCESS, http_status, headers, body, None)
83+
84+
@classmethod
85+
def fail(cls, http_status=400, headers=None, body=None, output=None):
86+
return cls(ResultType.FAIL, http_status, headers, body, output)
6687

6788

6889
class CustomEventResponse(_InteractiveTaskResponse):

connect/eaas/manager.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
ResultType,
2525
TaskCategory,
2626
TaskPayload,
27+
TaskType,
2728
)
2829
from connect.eaas.extension import ProcessingResponse
2930

@@ -226,13 +227,16 @@ async def build_bg_response(self, task_data, future):
226227
result = await asyncio.wait_for(future, timeout=BACKGROUND_TASK_MAX_EXECUTION_TIME)
227228
except Exception as e:
228229
logger.warning(f'Got exception during execution of task {task_data.task_id}: {e}')
230+
self.worker.get_extension().logger.exception(
231+
f'Unhandled exception during execution of task {task_data.task_id}',
232+
)
229233
result_message.result = ResultType.RETRY
230-
result_message.output = str(e)
234+
result_message.output = str(e) or repr(e)
231235
return result_message
232236
logger.debug(f'result: {result}')
233237
result_message.result = result.status
234238

235-
if result.status == ResultType.SKIP:
239+
if result.status in (ResultType.SKIP, ResultType.FAIL):
236240
result_message.output = result.output
237241

238242
if result.status == ResultType.RESCHEDULE:
@@ -249,10 +253,24 @@ async def build_interactive_response(self, task_data, future):
249253
result = await asyncio.wait_for(future, timeout=INTERACTIVE_TASK_MAX_EXECUTION_TIME)
250254
except Exception as e:
251255
logger.warning(f'Got exception during execution of task {task_data.task_id}: {e}')
256+
self.worker.get_extension().logger.exception(
257+
f'Unhandled exception during execution of task {task_data.task_id}',
258+
)
252259
result_message.result = ResultType.FAIL
253-
result_message.output = str(e)
260+
result_message.output = str(e) or repr(e)
261+
if result_message.task_type in (
262+
TaskType.PRODUCT_ACTION_EXECUTION,
263+
TaskType.PRODUCT_CUSTOM_EVENT_PROCESSING,
264+
):
265+
result_message.data = {
266+
'http_status': 400,
267+
'headers': None,
268+
'body': result_message.output,
269+
}
270+
254271
return result_message
255272

256273
result_message.result = result.status
257274
result_message.data = result.data
275+
result_message.output = result.output
258276
return result_message

tests/conftest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,18 @@ async def _ws_server(handler):
3535

3636
@pytest.fixture
3737
def extension_cls():
38-
def _extension(method_name, result=None, async_impl=False):
38+
def _extension(method_name, result=None, async_impl=False, exception=None):
3939
class TestExtension(Extension):
4040
pass
4141

4242
def ext_method(self, request):
43+
if exception:
44+
raise exception
4345
return result or ProcessingResponse.done()
4446

4547
async def async_ext_method(self, request):
48+
if exception:
49+
raise exception
4650
return result or ProcessingResponse.done()
4751

4852
if async_impl:

tests/test_dataclasses.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from connect.eaas.dataclasses import (
2+
CapabilitiesPayload,
3+
ConfigurationPayload,
4+
Message,
5+
MessageType,
6+
7+
)
8+
9+
10+
def test_capabilities_payload():
11+
assert CapabilitiesPayload(
12+
{'cap1': 'val1'},
13+
'https://example.com/readme',
14+
'https://example.com/changelog',
15+
).to_json() == {
16+
'capabilities': {'cap1': 'val1'},
17+
'readme_url': 'https://example.com/readme',
18+
'changelog_url': 'https://example.com/changelog',
19+
}
20+
21+
22+
def test_configuration_payload():
23+
assert ConfigurationPayload(
24+
{'conf1': 'val1'},
25+
'logging-token',
26+
'environ-type',
27+
'log-level',
28+
'runner-log-level',
29+
).to_json() == {
30+
'configuration': {'conf1': 'val1'},
31+
'logging_api_key': 'logging-token',
32+
'environment_type': 'environ-type',
33+
'log_level': 'log-level',
34+
'runner_log_level': 'runner-log-level',
35+
}
36+
37+
38+
def test_message_capabilities():
39+
cap = CapabilitiesPayload(
40+
{'cap1': 'val1'},
41+
'https://example.com/readme',
42+
'https://example.com/changelog',
43+
)
44+
45+
msg = Message(
46+
MessageType.CAPABILITIES,
47+
cap.to_json(),
48+
)
49+
assert msg.data == cap
50+
51+
assert msg.to_json() == {
52+
'message_type': MessageType.CAPABILITIES,
53+
'data': cap.to_json(),
54+
}

tests/test_extension.py

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pytest
2+
13
from connect.eaas.dataclasses import ResultType
24
from connect.eaas.extension import (
35
CustomEventResponse,
@@ -25,26 +27,73 @@ def test_result_skip_with_output():
2527
assert skip.output == 'output'
2628

2729

28-
def test_result_reschedule():
29-
r = ProcessingResponse.reschedule(60)
30+
@pytest.mark.parametrize(
31+
('countdown', 'expected'),
32+
(
33+
(0, 30),
34+
(-1, 30),
35+
(1, 30),
36+
(30, 30),
37+
(31, 31),
38+
(100, 100),
39+
),
40+
)
41+
def test_result_reschedule(countdown, expected):
42+
r = ProcessingResponse.reschedule(countdown)
3043

3144
assert r.status == ResultType.RESCHEDULE
32-
assert r.countdown == 60
45+
assert r.countdown == expected
46+
47+
48+
@pytest.mark.parametrize(
49+
('countdown', 'expected'),
50+
(
51+
(0, 300),
52+
(-1, 300),
53+
(1, 300),
54+
(30, 300),
55+
(300, 300),
56+
(600, 600),
57+
),
58+
)
59+
def test_result_slow_reschedule(countdown, expected):
60+
r = ProcessingResponse.slow_process_reschedule(countdown)
61+
62+
assert r.status == ResultType.RESCHEDULE
63+
assert r.countdown == expected
64+
65+
66+
@pytest.mark.parametrize(
67+
'response_cls',
68+
(
69+
ProcessingResponse, ValidationResponse,
70+
CustomEventResponse, ProductActionResponse,
71+
),
72+
)
73+
def test_result_fail(response_cls):
74+
r = response_cls.fail(output='reason of failure')
75+
76+
assert r.status == ResultType.FAIL
77+
assert r.output == 'reason of failure'
3378

3479

3580
def test_custom_event():
3681
r = CustomEventResponse.done(headers={'X-Custom-Header': 'value'}, body='text')
3782

3883
assert r.status == ResultType.SUCCESS
39-
assert r.http_status == 200
40-
assert r.headers == {'X-Custom-Header': 'value'}
41-
assert r.body == 'text'
84+
assert r.data == {
85+
'http_status': 200,
86+
'headers': {'X-Custom-Header': 'value'},
87+
'body': 'text',
88+
}
4289

4390

4491
def test_product_action():
4592
r = ProductActionResponse.done(headers={'X-Custom-Header': 'value'}, body='text')
4693

4794
assert r.status == ResultType.SUCCESS
48-
assert r.http_status == 200
49-
assert r.headers == {'X-Custom-Header': 'value'}
50-
assert r.body == 'text'
95+
assert r.data == {
96+
'http_status': 200,
97+
'headers': {'X-Custom-Header': 'value'},
98+
'body': 'text',
99+
}

0 commit comments

Comments
 (0)