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.
Files changed (23) hide show
  1. indexify/executor/function_executor/function_executor.py +30 -25
  2. indexify/executor/function_executor_controller/__init__.py +7 -4
  3. indexify/executor/function_executor_controller/create_function_executor.py +125 -27
  4. indexify/executor/function_executor_controller/destroy_function_executor.py +1 -1
  5. indexify/executor/function_executor_controller/events.py +10 -14
  6. indexify/executor/function_executor_controller/function_executor_controller.py +108 -66
  7. indexify/executor/function_executor_controller/function_executor_startup_output.py +21 -0
  8. indexify/executor/function_executor_controller/loggers.py +55 -7
  9. indexify/executor/function_executor_controller/message_validators.py +16 -1
  10. indexify/executor/function_executor_controller/prepare_task.py +3 -3
  11. indexify/executor/function_executor_controller/run_task.py +19 -27
  12. indexify/executor/function_executor_controller/task_info.py +2 -3
  13. indexify/executor/function_executor_controller/task_output.py +12 -24
  14. indexify/executor/function_executor_controller/upload_task_output.py +7 -7
  15. indexify/executor/state_reconciler.py +5 -33
  16. indexify/executor/state_reporter.py +46 -56
  17. indexify/proto/executor_api.proto +34 -17
  18. indexify/proto/executor_api_pb2.py +46 -42
  19. indexify/proto/executor_api_pb2.pyi +50 -8
  20. {indexify-0.4.6.dist-info → indexify-0.4.8.dist-info}/METADATA +2 -2
  21. {indexify-0.4.6.dist-info → indexify-0.4.8.dist-info}/RECORD +23 -22
  22. {indexify-0.4.6.dist-info → indexify-0.4.8.dist-info}/WHEEL +0 -0
  23. {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
- Task,
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 .loggers import function_executor_logger, task_logger
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._function_executor_description: FunctionExecutorDescription = (
83
+ self._fe_description: FunctionExecutorDescription = (
81
84
  function_executor_description
82
85
  )
83
- self._function_executor_server_factory: FunctionExecutorServerFactory = (
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._function_executor: Optional[FunctionExecutor] = None
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._function_executor_description.id
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 add_task(self, task: Task, allocation_id: str) -> None:
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 = task_logger(task, self._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
- task=task, allocation_id=allocation_id, start_time=time.monotonic()
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 = task_logger(task_info.task, self._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._function_executor_description,
216
- function_executor_server_factory=self._function_executor_server_factory,
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
- termination_reason=FunctionExecutorTerminationReason.FUNCTION_EXECUTOR_TERMINATION_REASON_INTERNAL_ERROR,
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(termination_reason),
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
- new_fe_state = FunctionExecutorState(
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=task_logger(task_info.task, self._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(event.termination_reason)
430
- if event.function_error is not None:
431
- # TODO: Save stdout and stderr of customer code that ran during FE creation into BLOBs
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._function_executor = event.function_executor
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._function_executor.health_checker().start(
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._set_status(
464
- FunctionExecutorStatus.FUNCTION_EXECUTOR_STATUS_TERMINATED,
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
- task=task_info.task, allocation_id=task_info.allocation_id
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._function_executor,
556
- logger=task_logger(task_info.task, self._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
- task_logger(task_info.task, self._logger).error(
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=task_logger(task_info.task, self._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=task_logger(task_info.task, self._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.add_completed_task_output(task_info.output)
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._function_executor,
672
+ function_executor=self._fe,
660
673
  termination_reason=termination_reason,
661
674
  logger=self._logger,
662
675
  )
663
- self._function_executor = None
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._function_executor,
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.remove_function_executor_info(self.function_executor_id())
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
- function_executor_id=(
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
- graph_name=(
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
- function_name=(
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 task_logger(task: Task, logger: Any) -> Any:
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
- graph_name=task.graph_name if task.HasField("graph_name") else None,
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
- function_name=task.function_name if task.HasField("function_name") else None,
54
- graph_invocation_id=(
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 validate_task(task: Task) -> None:
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
  )