indexify 0.4.6__py3-none-any.whl → 0.4.8__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.
- indexify/executor/function_executor/function_executor.py +30 -25
- indexify/executor/function_executor_controller/__init__.py +7 -4
- indexify/executor/function_executor_controller/create_function_executor.py +125 -27
- indexify/executor/function_executor_controller/destroy_function_executor.py +1 -1
- indexify/executor/function_executor_controller/events.py +10 -14
- indexify/executor/function_executor_controller/function_executor_controller.py +108 -66
- indexify/executor/function_executor_controller/function_executor_startup_output.py +21 -0
- indexify/executor/function_executor_controller/loggers.py +55 -7
- indexify/executor/function_executor_controller/message_validators.py +16 -1
- indexify/executor/function_executor_controller/prepare_task.py +3 -3
- indexify/executor/function_executor_controller/run_task.py +19 -27
- indexify/executor/function_executor_controller/task_info.py +2 -3
- indexify/executor/function_executor_controller/task_output.py +12 -24
- indexify/executor/function_executor_controller/upload_task_output.py +7 -7
- indexify/executor/state_reconciler.py +5 -33
- indexify/executor/state_reporter.py +46 -56
- indexify/proto/executor_api.proto +34 -17
- indexify/proto/executor_api_pb2.py +46 -42
- indexify/proto/executor_api_pb2.pyi +50 -8
- {indexify-0.4.6.dist-info → indexify-0.4.8.dist-info}/METADATA +2 -2
- {indexify-0.4.6.dist-info → indexify-0.4.8.dist-info}/RECORD +23 -22
- {indexify-0.4.6.dist-info → indexify-0.4.8.dist-info}/WHEEL +0 -0
- {indexify-0.4.6.dist-info → indexify-0.4.8.dist-info}/entry_points.txt +0 -0
@@ -16,7 +16,9 @@ from indexify.proto.executor_api_pb2 import (
|
|
16
16
|
FunctionExecutorState,
|
17
17
|
FunctionExecutorStatus,
|
18
18
|
FunctionExecutorTerminationReason,
|
19
|
-
|
19
|
+
FunctionExecutorUpdate,
|
20
|
+
TaskAllocation,
|
21
|
+
TaskResult,
|
20
22
|
)
|
21
23
|
|
22
24
|
from .completed_task_metrics import emit_completed_task_metrics
|
@@ -38,7 +40,8 @@ from .events import (
|
|
38
40
|
TaskOutputUploadFinished,
|
39
41
|
TaskPreparationFinished,
|
40
42
|
)
|
41
|
-
from .
|
43
|
+
from .function_executor_startup_output import FunctionExecutorStartupOutput
|
44
|
+
from .loggers import function_executor_logger, task_allocation_logger
|
42
45
|
from .metrics.function_executor_controller import (
|
43
46
|
METRIC_FUNCTION_EXECUTORS_WITH_STATUS_LABEL_PENDING,
|
44
47
|
METRIC_FUNCTION_EXECUTORS_WITH_STATUS_LABEL_RUNNING,
|
@@ -77,10 +80,10 @@ class FunctionExecutorController:
|
|
77
80
|
using validate_function_executor_description().
|
78
81
|
"""
|
79
82
|
self._executor_id: str = executor_id
|
80
|
-
self.
|
83
|
+
self._fe_description: FunctionExecutorDescription = (
|
81
84
|
function_executor_description
|
82
85
|
)
|
83
|
-
self.
|
86
|
+
self._fe_server_factory: FunctionExecutorServerFactory = (
|
84
87
|
function_executor_server_factory
|
85
88
|
)
|
86
89
|
self._state_reporter: ExecutorStateReporter = state_reporter
|
@@ -93,7 +96,10 @@ class FunctionExecutorController:
|
|
93
96
|
)
|
94
97
|
# Mutable state. No lock needed as it's modified by async tasks running in
|
95
98
|
# the same event loop.
|
96
|
-
self.
|
99
|
+
self._fe: Optional[FunctionExecutor] = None
|
100
|
+
self._fe_termination_reason: FunctionExecutorTerminationReason = (
|
101
|
+
None # Optional
|
102
|
+
)
|
97
103
|
# FE Status reported to Server.
|
98
104
|
self._status: FunctionExecutorStatus = (
|
99
105
|
FunctionExecutorStatus.FUNCTION_EXECUTOR_STATUS_UNKNOWN
|
@@ -116,7 +122,7 @@ class FunctionExecutorController:
|
|
116
122
|
self._running_task: Optional[TaskInfo] = None
|
117
123
|
|
118
124
|
def function_executor_id(self) -> str:
|
119
|
-
return self.
|
125
|
+
return self._fe_description.id
|
120
126
|
|
121
127
|
def status(self) -> FunctionExecutorStatus:
|
122
128
|
"""Returns the current status of the Function Executor.
|
@@ -125,13 +131,13 @@ class FunctionExecutorController:
|
|
125
131
|
"""
|
126
132
|
return self._status
|
127
133
|
|
128
|
-
def
|
134
|
+
def add_task_allocation(self, task_allocation: TaskAllocation) -> None:
|
129
135
|
"""Adds a task to the Function Executor.
|
130
136
|
|
131
137
|
Not blocking. Never raises exceptions.
|
132
138
|
"""
|
133
|
-
logger =
|
134
|
-
if self.has_task(task.id):
|
139
|
+
logger = task_allocation_logger(task_allocation, self._logger)
|
140
|
+
if self.has_task(task_allocation.task.id):
|
135
141
|
logger.warning(
|
136
142
|
"attempted to add already added task to Function Executor",
|
137
143
|
)
|
@@ -139,9 +145,9 @@ class FunctionExecutorController:
|
|
139
145
|
|
140
146
|
metric_tasks_fetched.inc()
|
141
147
|
task_info: TaskInfo = TaskInfo(
|
142
|
-
|
148
|
+
allocation=task_allocation, start_time=time.monotonic()
|
143
149
|
)
|
144
|
-
self._tasks[task.id] = task_info
|
150
|
+
self._tasks[task_allocation.task.id] = task_info
|
145
151
|
next_aio = prepare_task(
|
146
152
|
task_info=task_info,
|
147
153
|
blob_store=self._blob_store,
|
@@ -187,7 +193,7 @@ class FunctionExecutorController:
|
|
187
193
|
return # Server processed the completed task outputs, we can forget it now.
|
188
194
|
|
189
195
|
# Task cancellation is required as the task is not completed yet.
|
190
|
-
logger =
|
196
|
+
logger = task_allocation_logger(task_info.allocation, self._logger)
|
191
197
|
task_info.is_cancelled = True
|
192
198
|
logger.info(
|
193
199
|
"cancelling task",
|
@@ -212,8 +218,8 @@ class FunctionExecutorController:
|
|
212
218
|
)
|
213
219
|
self._set_status(FunctionExecutorStatus.FUNCTION_EXECUTOR_STATUS_PENDING)
|
214
220
|
next_aio = create_function_executor(
|
215
|
-
function_executor_description=self.
|
216
|
-
function_executor_server_factory=self.
|
221
|
+
function_executor_description=self._fe_description,
|
222
|
+
function_executor_server_factory=self._fe_server_factory,
|
217
223
|
blob_store=self._blob_store,
|
218
224
|
executor_id=self._executor_id,
|
219
225
|
base_url=self._base_url,
|
@@ -225,7 +231,10 @@ class FunctionExecutorController:
|
|
225
231
|
aio=next_aio,
|
226
232
|
on_exception=FunctionExecutorCreated(
|
227
233
|
function_executor=None,
|
228
|
-
|
234
|
+
output=FunctionExecutorStartupOutput(
|
235
|
+
function_executor_description=self._fe_description,
|
236
|
+
termination_reason=FunctionExecutorTerminationReason.FUNCTION_EXECUTOR_TERMINATION_REASON_INTERNAL_ERROR,
|
237
|
+
),
|
229
238
|
),
|
230
239
|
)
|
231
240
|
|
@@ -254,7 +263,6 @@ class FunctionExecutorController:
|
|
254
263
|
def _set_status(
|
255
264
|
self,
|
256
265
|
status: FunctionExecutorStatus,
|
257
|
-
termination_reason: FunctionExecutorTerminationReason = None, # type: Optional[FunctionExecutorTerminationReason]
|
258
266
|
) -> None:
|
259
267
|
"""Sets Function Executor status and reports it to the Server.
|
260
268
|
|
@@ -267,7 +275,9 @@ class FunctionExecutorController:
|
|
267
275
|
"function executor status changed",
|
268
276
|
old_status=FunctionExecutorStatus.Name(old_status),
|
269
277
|
new_status=FunctionExecutorStatus.Name(new_status),
|
270
|
-
termination_reason=_termination_reason_to_short_name(
|
278
|
+
termination_reason=_termination_reason_to_short_name(
|
279
|
+
self._fe_termination_reason
|
280
|
+
),
|
271
281
|
)
|
272
282
|
metric_function_executors_with_status.labels(
|
273
283
|
status=_to_fe_status_metric_label(old_status, self._logger)
|
@@ -276,15 +286,25 @@ class FunctionExecutorController:
|
|
276
286
|
status=_to_fe_status_metric_label(new_status, self._logger)
|
277
287
|
).inc()
|
278
288
|
|
279
|
-
|
280
|
-
description=self._function_executor_description, status=new_status
|
281
|
-
)
|
282
|
-
if termination_reason is not None:
|
283
|
-
new_fe_state.termination_reason = termination_reason
|
284
|
-
self._state_reporter.update_function_executor_state(new_fe_state)
|
289
|
+
self._state_reporter.update_function_executor_state(self._current_state())
|
285
290
|
# Report the status change to the Server asap to reduce latency in the system.
|
286
291
|
self._state_reporter.schedule_state_report()
|
287
292
|
|
293
|
+
def _current_state(self) -> FunctionExecutorState:
|
294
|
+
"""Returns the current state of the Function Executor.
|
295
|
+
|
296
|
+
Not blocking. Never raises exceptions.
|
297
|
+
"""
|
298
|
+
termination_reason: Optional[FunctionExecutorTerminationReason] = None
|
299
|
+
if self._fe_termination_reason is not None:
|
300
|
+
termination_reason = self._fe_termination_reason
|
301
|
+
|
302
|
+
return FunctionExecutorState(
|
303
|
+
description=self._fe_description,
|
304
|
+
status=self._status,
|
305
|
+
termination_reason=termination_reason,
|
306
|
+
)
|
307
|
+
|
288
308
|
async def _control_loop(self) -> None:
|
289
309
|
"""Runs control loop that coordinates all the work done by the Function Executor.
|
290
310
|
|
@@ -355,7 +375,7 @@ class FunctionExecutorController:
|
|
355
375
|
aio=aio,
|
356
376
|
task_info=task_info,
|
357
377
|
on_exception=on_exception,
|
358
|
-
logger=
|
378
|
+
logger=task_allocation_logger(task_info.allocation, self._logger),
|
359
379
|
)
|
360
380
|
|
361
381
|
def _spawn_aio_for_fe(
|
@@ -425,24 +445,25 @@ class FunctionExecutorController:
|
|
425
445
|
|
426
446
|
Doesn't raise any exceptions. Doesn't block.
|
427
447
|
"""
|
448
|
+
self._state_reporter.add_function_executor_update(
|
449
|
+
FunctionExecutorUpdate(
|
450
|
+
description=self._fe_description,
|
451
|
+
startup_stdout=event.output.stdout,
|
452
|
+
startup_stderr=event.output.stderr,
|
453
|
+
)
|
454
|
+
)
|
455
|
+
self._state_reporter.schedule_state_report()
|
456
|
+
|
428
457
|
if event.function_executor is None:
|
429
|
-
self._destroy_function_executor_before_termination(
|
430
|
-
|
431
|
-
|
432
|
-
# so customers can debug their function initialization errors.
|
433
|
-
# https://github.com/tensorlakeai/indexify/issues/1426
|
434
|
-
self._logger.error(
|
435
|
-
"failed to create function executor due to error in customer code",
|
436
|
-
exc_info=event.function_error,
|
437
|
-
)
|
458
|
+
self._destroy_function_executor_before_termination(
|
459
|
+
event.output.termination_reason
|
460
|
+
)
|
438
461
|
return
|
439
462
|
|
440
|
-
self.
|
463
|
+
self._fe = event.function_executor
|
441
464
|
self._set_status(FunctionExecutorStatus.FUNCTION_EXECUTOR_STATUS_RUNNING)
|
442
465
|
# Health checker starts after FE creation and gets automatically stopped on FE destroy.
|
443
|
-
self.
|
444
|
-
self._health_check_failed_callback
|
445
|
-
)
|
466
|
+
self._fe.health_checker().start(self._health_check_failed_callback)
|
446
467
|
self._add_event(
|
447
468
|
ScheduleTaskExecution(),
|
448
469
|
source="_handle_event_function_executor_created",
|
@@ -460,10 +481,8 @@ class FunctionExecutorController:
|
|
460
481
|
"Function Executor destroy failed unexpectedly, this should never happen",
|
461
482
|
)
|
462
483
|
# Set the status only after the FE got destroyed because Server assumes that all FE resources are freed when the status changes.
|
463
|
-
self.
|
464
|
-
|
465
|
-
termination_reason=event.termination_reason,
|
466
|
-
)
|
484
|
+
self._fe_termination_reason = event.termination_reason
|
485
|
+
self._set_status(FunctionExecutorStatus.FUNCTION_EXECUTOR_STATUS_TERMINATED)
|
467
486
|
# Invoke the scheduler so it can fail runnable tasks with FE Terminated error.
|
468
487
|
self._add_event(
|
469
488
|
ScheduleTaskExecution(),
|
@@ -489,22 +508,18 @@ class FunctionExecutorController:
|
|
489
508
|
task_info: TaskInfo = event.task_info
|
490
509
|
|
491
510
|
if task_info.is_cancelled:
|
492
|
-
task_info.output = TaskOutput.task_cancelled(
|
493
|
-
task=task_info.task, allocation_id=task_info.allocation_id
|
494
|
-
)
|
511
|
+
task_info.output = TaskOutput.task_cancelled(task_info.allocation)
|
495
512
|
self._start_task_output_upload(task_info)
|
496
513
|
return
|
497
514
|
if not event.is_success:
|
498
|
-
task_info.output = TaskOutput.internal_error(
|
499
|
-
task=task_info.task, allocation_id=task_info.allocation_id
|
500
|
-
)
|
515
|
+
task_info.output = TaskOutput.internal_error(task_info.allocation)
|
501
516
|
self._start_task_output_upload(task_info)
|
502
517
|
return
|
503
518
|
|
504
519
|
task_info.prepared_time = time.monotonic()
|
505
520
|
metric_runnable_tasks.inc()
|
506
521
|
metric_runnable_tasks_per_function_name.labels(
|
507
|
-
task_info.task.function_name
|
522
|
+
task_info.allocation.task.function_name
|
508
523
|
).inc()
|
509
524
|
self._runnable_tasks.append(task_info)
|
510
525
|
self._add_event(
|
@@ -539,21 +554,19 @@ class FunctionExecutorController:
|
|
539
554
|
)
|
540
555
|
|
541
556
|
if task_info.is_cancelled:
|
542
|
-
task_info.output = TaskOutput.task_cancelled(
|
543
|
-
task=task_info.task, allocation_id=task_info.allocation_id
|
544
|
-
)
|
557
|
+
task_info.output = TaskOutput.task_cancelled(task_info.allocation)
|
545
558
|
self._start_task_output_upload(task_info)
|
546
559
|
elif self._status == FunctionExecutorStatus.FUNCTION_EXECUTOR_STATUS_TERMINATED:
|
547
560
|
task_info.output = TaskOutput.function_executor_terminated(
|
548
|
-
|
561
|
+
task_info.allocation
|
549
562
|
)
|
550
563
|
self._start_task_output_upload(task_info)
|
551
564
|
elif self._status == FunctionExecutorStatus.FUNCTION_EXECUTOR_STATUS_RUNNING:
|
552
565
|
self._running_task = task_info
|
553
566
|
next_aio = run_task_on_function_executor(
|
554
567
|
task_info=task_info,
|
555
|
-
function_executor=self.
|
556
|
-
logger=
|
568
|
+
function_executor=self._fe,
|
569
|
+
logger=task_allocation_logger(task_info.allocation, self._logger),
|
557
570
|
)
|
558
571
|
self._spawn_aio_for_task(
|
559
572
|
aio=next_aio,
|
@@ -564,7 +577,7 @@ class FunctionExecutorController:
|
|
564
577
|
),
|
565
578
|
)
|
566
579
|
else:
|
567
|
-
|
580
|
+
task_allocation_logger(task_info.allocation, self._logger).error(
|
568
581
|
"failed to schedule task execution, this should never happen"
|
569
582
|
)
|
570
583
|
|
@@ -573,7 +586,7 @@ class FunctionExecutorController:
|
|
573
586
|
metric_schedule_task_latency.observe(time.monotonic() - task_info.prepared_time)
|
574
587
|
metric_runnable_tasks.dec()
|
575
588
|
metric_runnable_tasks_per_function_name.labels(
|
576
|
-
task_info.task.function_name
|
589
|
+
task_info.allocation.task.function_name
|
577
590
|
).dec()
|
578
591
|
return task_info
|
579
592
|
|
@@ -608,7 +621,7 @@ class FunctionExecutorController:
|
|
608
621
|
next_aio = upload_task_output(
|
609
622
|
task_info=task_info,
|
610
623
|
blob_store=self._blob_store,
|
611
|
-
logger=
|
624
|
+
logger=task_allocation_logger(task_info.allocation, self._logger),
|
612
625
|
)
|
613
626
|
self._spawn_aio_for_task(
|
614
627
|
aio=next_aio,
|
@@ -628,9 +641,7 @@ class FunctionExecutorController:
|
|
628
641
|
# Ignore task cancellation because we need to report it to the server anyway.
|
629
642
|
task_info: TaskInfo = event.task_info
|
630
643
|
if not event.is_success:
|
631
|
-
task_info.output = TaskOutput.internal_error(
|
632
|
-
task=task_info.task, allocation_id=task_info.allocation_id
|
633
|
-
)
|
644
|
+
task_info.output = TaskOutput.internal_error(task_info.allocation)
|
634
645
|
|
635
646
|
self._complete_task(event.task_info)
|
636
647
|
|
@@ -642,10 +653,12 @@ class FunctionExecutorController:
|
|
642
653
|
task_info.is_completed = True
|
643
654
|
emit_completed_task_metrics(
|
644
655
|
task_info=task_info,
|
645
|
-
logger=
|
656
|
+
logger=task_allocation_logger(task_info.allocation, self._logger),
|
646
657
|
)
|
647
658
|
# Reconciler will call .remove_task() once Server signals that it processed this update.
|
648
|
-
self._state_reporter.
|
659
|
+
self._state_reporter.add_completed_task_result(
|
660
|
+
_to_task_result_proto(task_info.output)
|
661
|
+
)
|
649
662
|
self._state_reporter.schedule_state_report()
|
650
663
|
|
651
664
|
def _destroy_function_executor_before_termination(
|
@@ -656,11 +669,11 @@ class FunctionExecutorController:
|
|
656
669
|
Doesn't raise any exceptions. Doesn't block.
|
657
670
|
"""
|
658
671
|
next_aio = destroy_function_executor(
|
659
|
-
function_executor=self.
|
672
|
+
function_executor=self._fe,
|
660
673
|
termination_reason=termination_reason,
|
661
674
|
logger=self._logger,
|
662
675
|
)
|
663
|
-
self.
|
676
|
+
self._fe = None
|
664
677
|
self._spawn_aio_for_fe(
|
665
678
|
aio=next_aio,
|
666
679
|
on_exception=FunctionExecutorDestroyed(
|
@@ -707,13 +720,16 @@ class FunctionExecutorController:
|
|
707
720
|
if self._status != FunctionExecutorStatus.FUNCTION_EXECUTOR_STATUS_TERMINATED:
|
708
721
|
self._handle_event_function_executor_destroyed(
|
709
722
|
await destroy_function_executor(
|
710
|
-
function_executor=self.
|
723
|
+
function_executor=self._fe,
|
711
724
|
termination_reason=event.termination_reason,
|
712
725
|
logger=self._logger,
|
713
726
|
)
|
714
727
|
)
|
728
|
+
metric_function_executors_with_status.labels(
|
729
|
+
status=_to_fe_status_metric_label(self._status, self._logger)
|
730
|
+
).dec()
|
715
731
|
|
716
|
-
self._state_reporter.
|
732
|
+
self._state_reporter.remove_function_executor_state(self.function_executor_id())
|
717
733
|
self._state_reporter.schedule_state_report()
|
718
734
|
|
719
735
|
self._logger.info("function executor controller control loop finished")
|
@@ -757,3 +773,29 @@ def _termination_reason_to_short_name(value: FunctionExecutorTerminationReason)
|
|
757
773
|
return "None"
|
758
774
|
|
759
775
|
return _termination_reason_to_short_name_map.get(value, "UNEXPECTED")
|
776
|
+
|
777
|
+
|
778
|
+
def _to_task_result_proto(output: TaskOutput) -> TaskResult:
|
779
|
+
task_result = TaskResult(
|
780
|
+
task_id=output.allocation.task.id,
|
781
|
+
allocation_id=output.allocation.allocation_id,
|
782
|
+
namespace=output.allocation.task.namespace,
|
783
|
+
graph_name=output.allocation.task.graph_name,
|
784
|
+
graph_version=output.allocation.task.graph_version,
|
785
|
+
function_name=output.allocation.task.function_name,
|
786
|
+
graph_invocation_id=output.allocation.task.graph_invocation_id,
|
787
|
+
reducer=output.reducer,
|
788
|
+
outcome_code=output.outcome_code,
|
789
|
+
next_functions=(output.router_output.edges if output.router_output else []),
|
790
|
+
function_outputs=output.uploaded_data_payloads,
|
791
|
+
)
|
792
|
+
if output.failure_reason is not None:
|
793
|
+
task_result.failure_reason = output.failure_reason
|
794
|
+
if output.uploaded_stdout is not None:
|
795
|
+
task_result.stdout.CopyFrom(output.uploaded_stdout)
|
796
|
+
if output.uploaded_stderr is not None:
|
797
|
+
task_result.stderr.CopyFrom(output.uploaded_stderr)
|
798
|
+
if output.router_output is not None:
|
799
|
+
task_result.routing.next_functions[:] = output.router_output.edges
|
800
|
+
|
801
|
+
return task_result
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from indexify.proto.executor_api_pb2 import (
|
4
|
+
DataPayload,
|
5
|
+
FunctionExecutorDescription,
|
6
|
+
FunctionExecutorTerminationReason,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
class FunctionExecutorStartupOutput:
|
11
|
+
def __init__(
|
12
|
+
self,
|
13
|
+
function_executor_description: FunctionExecutorDescription,
|
14
|
+
termination_reason: FunctionExecutorTerminationReason = None, # None if FE created successfully (wasn't terminated)
|
15
|
+
stdout: Optional[DataPayload] = None,
|
16
|
+
stderr: Optional[DataPayload] = None,
|
17
|
+
):
|
18
|
+
self.function_executor_description = function_executor_description
|
19
|
+
self.termination_reason = termination_reason
|
20
|
+
self.stdout = stdout
|
21
|
+
self.stderr = stderr
|
@@ -3,6 +3,8 @@ from typing import Any
|
|
3
3
|
from indexify.proto.executor_api_pb2 import (
|
4
4
|
FunctionExecutorDescription,
|
5
5
|
Task,
|
6
|
+
TaskAllocation,
|
7
|
+
TaskResult,
|
6
8
|
)
|
7
9
|
|
8
10
|
|
@@ -13,7 +15,7 @@ def function_executor_logger(
|
|
13
15
|
|
14
16
|
The function assumes that the FE might be invalid."""
|
15
17
|
return logger.bind(
|
16
|
-
|
18
|
+
fn_executor_id=(
|
17
19
|
function_executor_description.id
|
18
20
|
if function_executor_description.HasField("id")
|
19
21
|
else None
|
@@ -23,7 +25,7 @@ def function_executor_logger(
|
|
23
25
|
if function_executor_description.HasField("namespace")
|
24
26
|
else None
|
25
27
|
),
|
26
|
-
|
28
|
+
graph=(
|
27
29
|
function_executor_description.graph_name
|
28
30
|
if function_executor_description.HasField("graph_name")
|
29
31
|
else None
|
@@ -33,7 +35,7 @@ def function_executor_logger(
|
|
33
35
|
if function_executor_description.HasField("graph_version")
|
34
36
|
else None
|
35
37
|
),
|
36
|
-
|
38
|
+
fn=(
|
37
39
|
function_executor_description.function_name
|
38
40
|
if function_executor_description.HasField("function_name")
|
39
41
|
else None
|
@@ -41,17 +43,63 @@ def function_executor_logger(
|
|
41
43
|
)
|
42
44
|
|
43
45
|
|
44
|
-
def
|
46
|
+
def task_allocation_logger(task_allocation: TaskAllocation, logger: Any) -> Any:
|
47
|
+
"""Returns a logger for the given TaskAllocation.
|
48
|
+
|
49
|
+
Doesn't assume that the supplied TaskAllocation is valid.
|
50
|
+
"""
|
51
|
+
if task_allocation.HasField("task"):
|
52
|
+
logger = _task_logger(task_allocation.task, logger)
|
53
|
+
return logger.bind(
|
54
|
+
allocation_id=(
|
55
|
+
task_allocation.allocation_id
|
56
|
+
if task_allocation.HasField("allocation_id")
|
57
|
+
else None
|
58
|
+
),
|
59
|
+
fn_executor_id=(
|
60
|
+
task_allocation.function_executor_id
|
61
|
+
if task_allocation.HasField("function_executor_id")
|
62
|
+
else None
|
63
|
+
),
|
64
|
+
)
|
65
|
+
|
66
|
+
|
67
|
+
def task_result_logger(task_result: TaskResult, logger: Any) -> Any:
|
68
|
+
"""Returns a logger bound with the task result's metadata.
|
69
|
+
|
70
|
+
The function assumes that the task result might be invalid."""
|
71
|
+
return logger.bind(
|
72
|
+
task_id=task_result.task_id if task_result.HasField("task_id") else None,
|
73
|
+
allocation_id=(
|
74
|
+
task_result.allocation_id if task_result.HasField("allocation_id") else None
|
75
|
+
),
|
76
|
+
namespace=task_result.namespace if task_result.HasField("namespace") else None,
|
77
|
+
graph=(task_result.graph_name if task_result.HasField("graph_name") else None),
|
78
|
+
graph_version=(
|
79
|
+
task_result.graph_version if task_result.HasField("graph_version") else None
|
80
|
+
),
|
81
|
+
fn=(
|
82
|
+
task_result.function_name if task_result.HasField("function_name") else None
|
83
|
+
),
|
84
|
+
invocation_id=(
|
85
|
+
task_result.graph_invocation_id
|
86
|
+
if task_result.HasField("graph_invocation_id")
|
87
|
+
else None
|
88
|
+
),
|
89
|
+
)
|
90
|
+
|
91
|
+
|
92
|
+
def _task_logger(task: Task, logger: Any) -> Any:
|
45
93
|
"""Returns a logger bound with the task's metadata.
|
46
94
|
|
47
95
|
The function assumes that the task might be invalid."""
|
48
96
|
return logger.bind(
|
49
97
|
task_id=task.id if task.HasField("id") else None,
|
50
98
|
namespace=task.namespace if task.HasField("namespace") else None,
|
51
|
-
|
99
|
+
graph=task.graph_name if task.HasField("graph_name") else None,
|
52
100
|
graph_version=task.graph_version if task.HasField("graph_version") else None,
|
53
|
-
|
54
|
-
|
101
|
+
fn=task.function_name if task.HasField("function_name") else None,
|
102
|
+
invocation_id=(
|
55
103
|
task.graph_invocation_id if task.HasField("graph_invocation_id") else None
|
56
104
|
),
|
57
105
|
)
|
@@ -4,6 +4,7 @@ from indexify.proto.executor_api_pb2 import (
|
|
4
4
|
DataPayload,
|
5
5
|
FunctionExecutorDescription,
|
6
6
|
Task,
|
7
|
+
TaskAllocation,
|
7
8
|
)
|
8
9
|
|
9
10
|
|
@@ -25,6 +26,7 @@ def validate_function_executor_description(
|
|
25
26
|
validator.required_field("customer_code_timeout_ms")
|
26
27
|
validator.required_field("graph")
|
27
28
|
validator.required_field("resources")
|
29
|
+
validator.required_field("output_payload_uri_prefix")
|
28
30
|
|
29
31
|
_validate_data_payload(function_executor_description.graph)
|
30
32
|
|
@@ -39,7 +41,20 @@ def validate_function_executor_description(
|
|
39
41
|
validator.required_field("model")
|
40
42
|
|
41
43
|
|
42
|
-
def
|
44
|
+
def validate_task_allocation(task_allocation: TaskAllocation) -> None:
|
45
|
+
"""Validates the supplied TaskAllocation.
|
46
|
+
|
47
|
+
Raises ValueError if the TaskAllocation is not valid.
|
48
|
+
"""
|
49
|
+
validator = MessageValidator(task_allocation)
|
50
|
+
validator.required_field("function_executor_id")
|
51
|
+
validator.required_field("allocation_id")
|
52
|
+
if not task_allocation.HasField("task"):
|
53
|
+
raise ValueError("TaskAllocation must have a 'task' field.")
|
54
|
+
_validate_task(task_allocation.task)
|
55
|
+
|
56
|
+
|
57
|
+
def _validate_task(task: Task) -> None:
|
43
58
|
"""Validates the supplied Task.
|
44
59
|
|
45
60
|
Raises ValueError if the Task is not valid.
|
@@ -17,14 +17,14 @@ async def prepare_task(
|
|
17
17
|
logger = logger.bind(module=__name__)
|
18
18
|
try:
|
19
19
|
task_info.input = await download_input(
|
20
|
-
data_payload=task_info.task.input,
|
20
|
+
data_payload=task_info.allocation.task.input,
|
21
21
|
blob_store=blob_store,
|
22
22
|
logger=logger,
|
23
23
|
)
|
24
24
|
|
25
|
-
if task_info.task.HasField("reducer_input"):
|
25
|
+
if task_info.allocation.task.HasField("reducer_input"):
|
26
26
|
task_info.init_value = await download_init_value(
|
27
|
-
data_payload=task_info.task.reducer_input,
|
27
|
+
data_payload=task_info.allocation.task.reducer_input,
|
28
28
|
blob_store=blob_store,
|
29
29
|
logger=logger,
|
30
30
|
)
|