indexify 0.2.47__py3-none-any.whl → 0.3.0__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/{cli.py → cli/cli.py} +75 -82
- indexify/executor/README.md +35 -0
- indexify/executor/api_objects.py +9 -3
- indexify/executor/downloader.py +5 -5
- indexify/executor/executor.py +35 -22
- indexify/executor/function_executor/function_executor.py +14 -3
- indexify/executor/function_executor/function_executor_state.py +13 -10
- indexify/executor/function_executor/invocation_state_client.py +2 -1
- indexify/executor/function_executor/server/subprocess_function_executor_server_factory.py +22 -10
- indexify/executor/function_executor/single_task_runner.py +43 -26
- indexify/executor/function_executor/task_input.py +1 -3
- indexify/executor/task_fetcher.py +5 -7
- indexify/executor/task_reporter.py +3 -5
- indexify/executor/task_runner.py +30 -23
- indexify/function_executor/README.md +18 -0
- indexify/function_executor/handlers/run_function/function_inputs_loader.py +13 -14
- indexify/function_executor/handlers/run_function/handler.py +16 -40
- indexify/function_executor/handlers/run_function/request_validator.py +7 -5
- indexify/function_executor/handlers/run_function/response_helper.py +6 -8
- indexify/function_executor/initialize_request_validator.py +1 -2
- indexify/function_executor/invocation_state/invocation_state_proxy_server.py +1 -1
- indexify/function_executor/invocation_state/proxied_invocation_state.py +1 -3
- indexify/function_executor/main.py +50 -0
- indexify/function_executor/proto/configuration.py +8 -0
- indexify/function_executor/proto/function_executor.proto +9 -4
- indexify/function_executor/proto/function_executor_pb2.py +24 -24
- indexify/function_executor/proto/function_executor_pb2.pyi +24 -4
- indexify/function_executor/server.py +4 -6
- indexify/function_executor/{function_executor_service.py → service.py} +35 -18
- indexify/utils/README.md +3 -0
- indexify/{common_util.py → utils/http_client.py} +2 -2
- indexify/{logging.py → utils/logging.py} +36 -2
- indexify-0.3.0.dist-info/METADATA +38 -0
- indexify-0.3.0.dist-info/RECORD +44 -0
- {indexify-0.2.47.dist-info → indexify-0.3.0.dist-info}/WHEEL +1 -1
- indexify-0.3.0.dist-info/entry_points.txt +4 -0
- indexify/__init__.py +0 -31
- indexify/data_loaders/__init__.py +0 -58
- indexify/data_loaders/local_directory_loader.py +0 -37
- indexify/data_loaders/url_loader.py +0 -52
- indexify/error.py +0 -8
- indexify/functions_sdk/data_objects.py +0 -27
- indexify/functions_sdk/graph.py +0 -364
- indexify/functions_sdk/graph_definition.py +0 -63
- indexify/functions_sdk/graph_validation.py +0 -70
- indexify/functions_sdk/image.py +0 -210
- indexify/functions_sdk/indexify_functions.py +0 -354
- indexify/functions_sdk/invocation_state/invocation_state.py +0 -22
- indexify/functions_sdk/invocation_state/local_invocation_state.py +0 -30
- indexify/functions_sdk/object_serializer.py +0 -68
- indexify/functions_sdk/pipeline.py +0 -33
- indexify/http_client.py +0 -379
- indexify/remote_graph.py +0 -138
- indexify/remote_pipeline.py +0 -25
- indexify/settings.py +0 -1
- indexify-0.2.47.dist-info/LICENSE.txt +0 -201
- indexify-0.2.47.dist-info/METADATA +0 -154
- indexify-0.2.47.dist-info/RECORD +0 -60
- indexify-0.2.47.dist-info/entry_points.txt +0 -3
@@ -12,7 +12,7 @@ from indexify.function_executor.proto.function_executor_pb2_grpc import (
|
|
12
12
|
)
|
13
13
|
|
14
14
|
from ..api_objects import Task
|
15
|
-
from .function_executor import FunctionExecutor
|
15
|
+
from .function_executor import CustomerError, FunctionExecutor
|
16
16
|
from .function_executor_state import FunctionExecutorState
|
17
17
|
from .server.function_executor_server_factory import (
|
18
18
|
FunctionExecutorServerConfiguration,
|
@@ -49,8 +49,18 @@ class SingleTaskRunner:
|
|
49
49
|
Raises an exception if an error occured."""
|
50
50
|
self._state.check_locked()
|
51
51
|
|
52
|
+
if self._state.is_shutdown:
|
53
|
+
raise RuntimeError("Function Executor state is shutting down.")
|
54
|
+
|
52
55
|
if self._state.function_executor is None:
|
53
|
-
|
56
|
+
try:
|
57
|
+
await self._create_function_executor()
|
58
|
+
except CustomerError as e:
|
59
|
+
return TaskOutput(
|
60
|
+
task=self._task_input.task,
|
61
|
+
stderr=str(e),
|
62
|
+
success=False,
|
63
|
+
)
|
54
64
|
|
55
65
|
return await self._run()
|
56
66
|
|
@@ -58,36 +68,37 @@ class SingleTaskRunner:
|
|
58
68
|
function_executor: FunctionExecutor = FunctionExecutor(
|
59
69
|
server_factory=self._factory, logger=self._logger
|
60
70
|
)
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
image_uri=self._task_input.task.image_uri,
|
65
|
-
)
|
66
|
-
)
|
67
|
-
initialize_request: InitializeRequest = InitializeRequest(
|
68
|
-
namespace=self._task_input.task.namespace,
|
69
|
-
graph_name=self._task_input.task.compute_graph,
|
70
|
-
graph_version=self._task_input.task.graph_version,
|
71
|
-
function_name=self._task_input.task.compute_fn,
|
72
|
-
graph=self._task_input.graph,
|
71
|
+
config: FunctionExecutorServerConfiguration = (
|
72
|
+
FunctionExecutorServerConfiguration(
|
73
|
+
image_uri=self._task_input.task.image_uri,
|
73
74
|
)
|
75
|
+
)
|
76
|
+
initialize_request: InitializeRequest = InitializeRequest(
|
77
|
+
namespace=self._task_input.task.namespace,
|
78
|
+
graph_name=self._task_input.task.compute_graph,
|
79
|
+
graph_version=self._task_input.task.graph_version,
|
80
|
+
function_name=self._task_input.task.compute_fn,
|
81
|
+
graph=self._task_input.graph,
|
82
|
+
)
|
83
|
+
|
84
|
+
try:
|
74
85
|
await function_executor.initialize(
|
75
86
|
config=config,
|
76
87
|
initialize_request=initialize_request,
|
77
88
|
base_url=self._base_url,
|
78
89
|
config_path=self._config_path,
|
79
90
|
)
|
80
|
-
|
81
|
-
except Exception
|
82
|
-
self._logger.error(
|
83
|
-
"failed to initialize function executor",
|
84
|
-
exc_info=e,
|
85
|
-
)
|
91
|
+
self._state.function_executor = function_executor
|
92
|
+
except Exception:
|
86
93
|
await function_executor.destroy()
|
87
94
|
raise
|
88
95
|
|
89
96
|
async def _run(self) -> TaskOutput:
|
90
97
|
request: RunTaskRequest = RunTaskRequest(
|
98
|
+
namespace=self._task_input.task.namespace,
|
99
|
+
graph_name=self._task_input.task.compute_graph,
|
100
|
+
graph_version=self._task_input.task.graph_version,
|
101
|
+
function_name=self._task_input.task.compute_fn,
|
91
102
|
graph_invocation_id=self._task_input.task.invocation_id,
|
92
103
|
task_id=self._task_input.task.id,
|
93
104
|
function_input=self._task_input.input,
|
@@ -97,7 +108,9 @@ class SingleTaskRunner:
|
|
97
108
|
channel: grpc.aio.Channel = self._state.function_executor.channel()
|
98
109
|
|
99
110
|
async with _RunningTaskContextManager(
|
100
|
-
|
111
|
+
invocation_id=self._task_input.task.invocation_id,
|
112
|
+
task_id=self._task_input.task.id,
|
113
|
+
function_executor_state=self._state,
|
101
114
|
):
|
102
115
|
response: RunTaskResponse = await FunctionExecutorStub(channel).run_task(
|
103
116
|
request
|
@@ -109,16 +122,20 @@ class _RunningTaskContextManager:
|
|
109
122
|
"""Performs all the actions required before and after running a task."""
|
110
123
|
|
111
124
|
def __init__(
|
112
|
-
self,
|
125
|
+
self,
|
126
|
+
invocation_id: str,
|
127
|
+
task_id: str,
|
128
|
+
function_executor_state: FunctionExecutorState,
|
113
129
|
):
|
114
|
-
self.
|
130
|
+
self._invocation_id: str = invocation_id
|
131
|
+
self._task_id: str = task_id
|
115
132
|
self._state: FunctionExecutorState = function_executor_state
|
116
133
|
|
117
134
|
async def __aenter__(self):
|
118
135
|
self._state.increment_running_tasks()
|
119
136
|
self._state.function_executor.invocation_state_client().add_task_to_invocation_id_entry(
|
120
|
-
task_id=self.
|
121
|
-
invocation_id=self.
|
137
|
+
task_id=self._task_id,
|
138
|
+
invocation_id=self._invocation_id,
|
122
139
|
)
|
123
140
|
# Unlock the state so other tasks can act depending on it.
|
124
141
|
self._state.lock.release()
|
@@ -128,7 +145,7 @@ class _RunningTaskContextManager:
|
|
128
145
|
await self._state.lock.acquire()
|
129
146
|
self._state.decrement_running_tasks()
|
130
147
|
self._state.function_executor.invocation_state_client().remove_task_to_invocation_id_entry(
|
131
|
-
task_id=self.
|
148
|
+
task_id=self._task_id
|
132
149
|
)
|
133
150
|
|
134
151
|
|
@@ -1,13 +1,13 @@
|
|
1
1
|
import json
|
2
2
|
from importlib.metadata import version
|
3
|
-
from typing import AsyncGenerator, Optional
|
3
|
+
from typing import AsyncGenerator, List, Optional
|
4
4
|
|
5
5
|
import structlog
|
6
6
|
from httpx_sse import aconnect_sse
|
7
7
|
|
8
|
-
from indexify.
|
8
|
+
from indexify.utils.http_client import get_httpx_client
|
9
9
|
|
10
|
-
from .api_objects import ExecutorMetadata, Task
|
10
|
+
from .api_objects import ExecutorMetadata, FunctionURI, Task
|
11
11
|
from .runtime_probes import ProbeInfo, RuntimeProbes
|
12
12
|
|
13
13
|
|
@@ -19,8 +19,7 @@ class TaskFetcher:
|
|
19
19
|
protocol: str,
|
20
20
|
indexify_server_addr: str,
|
21
21
|
executor_id: str,
|
22
|
-
|
23
|
-
image_hash: Optional[int] = None,
|
22
|
+
function_allowlist: Optional[List[FunctionURI]],
|
24
23
|
config_path: Optional[str] = None,
|
25
24
|
):
|
26
25
|
self._protocol: str = protocol
|
@@ -33,8 +32,7 @@ class TaskFetcher:
|
|
33
32
|
id=executor_id,
|
34
33
|
executor_version=version("indexify"),
|
35
34
|
addr="",
|
36
|
-
|
37
|
-
image_hash=(probe_info.image_hash if image_hash is None else image_hash),
|
35
|
+
function_allowlist=function_allowlist,
|
38
36
|
labels=probe_info.labels,
|
39
37
|
)
|
40
38
|
|
@@ -4,12 +4,10 @@ from typing import Any, List, Optional, Tuple
|
|
4
4
|
import nanoid
|
5
5
|
from httpx import Timeout
|
6
6
|
|
7
|
-
from indexify.
|
8
|
-
from indexify.
|
9
|
-
from indexify.function_executor.proto.function_executor_pb2 import (
|
10
|
-
FunctionOutput,
|
11
|
-
)
|
7
|
+
from indexify.function_executor.proto.function_executor_pb2 import FunctionOutput
|
8
|
+
from indexify.utils.http_client import get_httpx_client
|
12
9
|
|
10
|
+
from .api_objects import RouterOutput, Task, TaskResult
|
13
11
|
from .task_runner import TaskOutput
|
14
12
|
|
15
13
|
|
indexify/executor/task_runner.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import asyncio
|
1
2
|
from typing import Any, Dict, Optional
|
2
3
|
|
3
4
|
from .api_objects import Task
|
@@ -24,7 +25,9 @@ class TaskRunner:
|
|
24
25
|
self._factory: FunctionExecutorServerFactory = function_executor_server_factory
|
25
26
|
self._base_url: str = base_url
|
26
27
|
self._config_path: Optional[str] = config_path
|
27
|
-
#
|
28
|
+
# The fields below are protected by the lock.
|
29
|
+
self._lock: asyncio.Lock = asyncio.Lock()
|
30
|
+
self._is_shutdown: bool = False
|
28
31
|
self._function_executor_states: Dict[str, FunctionExecutorState] = {}
|
29
32
|
|
30
33
|
async def run(self, task_input: TaskInput, logger: Any) -> TaskOutput:
|
@@ -33,17 +36,31 @@ class TaskRunner:
|
|
33
36
|
return await self._run(task_input, logger)
|
34
37
|
except Exception as e:
|
35
38
|
logger.error(
|
36
|
-
"failed running the task",
|
39
|
+
"failed running the task:",
|
37
40
|
exc_info=e,
|
38
41
|
)
|
39
42
|
return TaskOutput.internal_error(task_input.task)
|
40
43
|
|
41
44
|
async def _run(self, task_input: TaskInput, logger: Any) -> TaskOutput:
|
42
|
-
state = self._get_or_create_state(task_input.task)
|
45
|
+
state = await self._get_or_create_state(task_input.task)
|
43
46
|
async with state.lock:
|
44
47
|
await self._run_task_policy(state, task_input.task)
|
45
48
|
return await self._run_task(state, task_input, logger)
|
46
49
|
|
50
|
+
async def _get_or_create_state(self, task: Task) -> FunctionExecutorState:
|
51
|
+
async with self._lock:
|
52
|
+
if self._is_shutdown:
|
53
|
+
raise RuntimeError("Task runner is shutting down.")
|
54
|
+
|
55
|
+
id = _function_id_without_version(task)
|
56
|
+
if id not in self._function_executor_states:
|
57
|
+
state = FunctionExecutorState(
|
58
|
+
function_id_with_version=_function_id_with_version(task),
|
59
|
+
function_id_without_version=id,
|
60
|
+
)
|
61
|
+
self._function_executor_states[id] = state
|
62
|
+
return self._function_executor_states[id]
|
63
|
+
|
47
64
|
async def _run_task_policy(self, state: FunctionExecutorState, task: Task) -> None:
|
48
65
|
# Current policy for running tasks:
|
49
66
|
# - There can only be a single Function Executor per function regardless of function versions.
|
@@ -60,16 +77,6 @@ class TaskRunner:
|
|
60
77
|
# At this point the state belongs to the version of the function from the task
|
61
78
|
# and there are no running tasks in the Function Executor.
|
62
79
|
|
63
|
-
def _get_or_create_state(self, task: Task) -> FunctionExecutorState:
|
64
|
-
id = _function_id_without_version(task)
|
65
|
-
if id not in self._function_executor_states:
|
66
|
-
state = FunctionExecutorState(
|
67
|
-
function_id_with_version=_function_id_with_version(task),
|
68
|
-
function_id_without_version=id,
|
69
|
-
)
|
70
|
-
self._function_executor_states[id] = state
|
71
|
-
return self._function_executor_states[id]
|
72
|
-
|
73
80
|
async def _run_task(
|
74
81
|
self, state: FunctionExecutorState, task_input: TaskInput, logger: Any
|
75
82
|
) -> TaskOutput:
|
@@ -84,16 +91,16 @@ class TaskRunner:
|
|
84
91
|
return await runner.run()
|
85
92
|
|
86
93
|
async def shutdown(self) -> None:
|
87
|
-
#
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
94
|
+
# Function Executors are outside the Executor process
|
95
|
+
# so they need to get cleaned up explicitly and reliably.
|
96
|
+
async with self._lock:
|
97
|
+
self._is_shutdown = True # No new Function Executor States can be created.
|
98
|
+
while self._function_executor_states:
|
99
|
+
id, state = self._function_executor_states.popitem()
|
100
|
+
# Only ongoing tasks who have a reference to the state already can see it.
|
101
|
+
async with state.lock:
|
102
|
+
await state.shutdown()
|
103
|
+
# The task running inside the Function Executor will fail because it's destroyed.
|
97
104
|
|
98
105
|
|
99
106
|
def _function_id_with_version(task: Task) -> str:
|
@@ -0,0 +1,18 @@
|
|
1
|
+
## Overview
|
2
|
+
|
3
|
+
Function Executor is a process with an API that allows to load and run a customer Function in Indexify.
|
4
|
+
Each function run is a task. The tasks can be executed concurrently. The API client controls
|
5
|
+
the desired concurrency. Killing the process allows to free all the resources that a loaded customer
|
6
|
+
functon is using. This is helpful because the SDK doesn't provide any callbacks to customer code to free
|
7
|
+
resources it's using. Even if there was such callback customer code still might misbehave.
|
8
|
+
|
9
|
+
## Deployment
|
10
|
+
|
11
|
+
A Function Executor is created and destroyed by another component called Executor. It also calls the
|
12
|
+
Function Executor APIs. The server is not expected to be deployed or managed manually by Indexify users
|
13
|
+
as it's a low level component.
|
14
|
+
|
15
|
+
## Threat model
|
16
|
+
|
17
|
+
Customer code is assumed to be not trusted. Function Executor must not obtain any credentials that grant
|
18
|
+
access to resources not owned by the customer who owns the function.
|
@@ -1,18 +1,17 @@
|
|
1
1
|
from typing import Optional
|
2
2
|
|
3
|
-
from
|
3
|
+
from tensorlake.functions_sdk.data_objects import TensorlakeData
|
4
|
+
from tensorlake.functions_sdk.object_serializer import get_serializer
|
4
5
|
|
5
|
-
from
|
6
|
-
RunTaskRequest,
|
7
|
-
SerializedObject,
|
8
|
-
)
|
9
|
-
from indexify.functions_sdk.data_objects import IndexifyData
|
10
|
-
from indexify.functions_sdk.object_serializer import get_serializer
|
6
|
+
from ...proto.function_executor_pb2 import RunTaskRequest, SerializedObject
|
11
7
|
|
12
8
|
|
13
|
-
class FunctionInputs
|
14
|
-
|
15
|
-
|
9
|
+
class FunctionInputs:
|
10
|
+
def __init__(
|
11
|
+
self, input: TensorlakeData, init_value: Optional[TensorlakeData] = None
|
12
|
+
):
|
13
|
+
self.input = input
|
14
|
+
self.init_value = init_value
|
16
15
|
|
17
16
|
|
18
17
|
class FunctionInputsLoader:
|
@@ -25,12 +24,12 @@ class FunctionInputsLoader:
|
|
25
24
|
init_value=self._accumulator_input(),
|
26
25
|
)
|
27
26
|
|
28
|
-
def _function_input(self) ->
|
27
|
+
def _function_input(self) -> TensorlakeData:
|
29
28
|
return _to_indexify_data(
|
30
29
|
self._request.graph_invocation_id, self._request.function_input
|
31
30
|
)
|
32
31
|
|
33
|
-
def _accumulator_input(self) -> Optional[
|
32
|
+
def _accumulator_input(self) -> Optional[TensorlakeData]:
|
34
33
|
return (
|
35
34
|
_to_indexify_data(
|
36
35
|
self._request.graph_invocation_id, self._request.function_init_value
|
@@ -42,8 +41,8 @@ class FunctionInputsLoader:
|
|
42
41
|
|
43
42
|
def _to_indexify_data(
|
44
43
|
input_id: str, serialized_object: SerializedObject
|
45
|
-
) ->
|
46
|
-
return
|
44
|
+
) -> TensorlakeData:
|
45
|
+
return TensorlakeData(
|
47
46
|
input_id=input_id,
|
48
47
|
payload=(
|
49
48
|
serialized_object.bytes
|
@@ -2,25 +2,19 @@ import io
|
|
2
2
|
import sys
|
3
3
|
import traceback
|
4
4
|
from contextlib import redirect_stderr, redirect_stdout
|
5
|
-
from typing import Any,
|
5
|
+
from typing import Any, Union
|
6
6
|
|
7
|
-
from
|
8
|
-
RunTaskRequest,
|
9
|
-
RunTaskResponse,
|
10
|
-
)
|
11
|
-
from indexify.functions_sdk.indexify_functions import (
|
7
|
+
from tensorlake.functions_sdk.functions import (
|
12
8
|
FunctionCallResult,
|
13
9
|
GraphInvocationContext,
|
14
|
-
IndexifyFunction,
|
15
|
-
IndexifyFunctionWrapper,
|
16
|
-
IndexifyRouter,
|
17
10
|
RouterCallResult,
|
11
|
+
TensorlakeCompute,
|
12
|
+
TensorlakeFunctionWrapper,
|
13
|
+
TensorlakeRouter,
|
18
14
|
)
|
19
|
-
from
|
20
|
-
InvocationState,
|
21
|
-
)
|
22
|
-
from indexify.http_client import IndexifyClient
|
15
|
+
from tensorlake.functions_sdk.invocation_state.invocation_state import InvocationState
|
23
16
|
|
17
|
+
from ...proto.function_executor_pb2 import RunTaskRequest, RunTaskResponse
|
24
18
|
from .function_inputs_loader import FunctionInputs, FunctionInputsLoader
|
25
19
|
from .response_helper import ResponseHelper
|
26
20
|
|
@@ -30,9 +24,9 @@ class Handler:
|
|
30
24
|
self,
|
31
25
|
request: RunTaskRequest,
|
32
26
|
graph_name: str,
|
33
|
-
graph_version:
|
27
|
+
graph_version: str,
|
34
28
|
function_name: str,
|
35
|
-
function: Union[
|
29
|
+
function: Union[TensorlakeCompute, TensorlakeCompute],
|
36
30
|
invocation_state: InvocationState,
|
37
31
|
logger: Any,
|
38
32
|
):
|
@@ -48,12 +42,12 @@ class Handler:
|
|
48
42
|
self._func_stdout: io.StringIO = io.StringIO()
|
49
43
|
self._func_stderr: io.StringIO = io.StringIO()
|
50
44
|
|
51
|
-
self._function_wrapper:
|
45
|
+
self._function_wrapper: TensorlakeFunctionWrapper = TensorlakeFunctionWrapper(
|
52
46
|
indexify_function=function,
|
53
47
|
context=GraphInvocationContext(
|
54
48
|
invocation_id=request.graph_invocation_id,
|
55
49
|
graph_name=graph_name,
|
56
|
-
graph_version=
|
50
|
+
graph_version=graph_version,
|
57
51
|
invocation_state=invocation_state,
|
58
52
|
),
|
59
53
|
)
|
@@ -116,35 +110,17 @@ class Handler:
|
|
116
110
|
sys.stderr.flush()
|
117
111
|
|
118
112
|
|
119
|
-
def
|
120
|
-
logger: Any,
|
121
|
-
namespace: str,
|
122
|
-
indexify_server_addr: str,
|
123
|
-
config_path: Optional[str],
|
124
|
-
) -> IndexifyClient:
|
125
|
-
# This client is required to implement key/value store functionality for customer functions.
|
126
|
-
protocol: str = "http"
|
127
|
-
if config_path:
|
128
|
-
logger.info("TLS is enabled")
|
129
|
-
protocol = "https"
|
130
|
-
return IndexifyClient(
|
131
|
-
service_url=f"{protocol}://{indexify_server_addr}",
|
132
|
-
namespace=namespace,
|
133
|
-
config_path=config_path,
|
134
|
-
)
|
135
|
-
|
136
|
-
|
137
|
-
def _is_router(func_wrapper: IndexifyFunctionWrapper) -> bool:
|
113
|
+
def _is_router(func_wrapper: TensorlakeFunctionWrapper) -> bool:
|
138
114
|
"""Determines if the function is a router.
|
139
115
|
|
140
|
-
A function is a router if it is an instance of
|
116
|
+
A function is a router if it is an instance of TensorlakeRouter or if it is an TensorlakeRouter class.
|
141
117
|
"""
|
142
118
|
return str(
|
143
119
|
type(func_wrapper.indexify_function)
|
144
|
-
) == "<class '
|
145
|
-
func_wrapper.indexify_function,
|
120
|
+
) == "<class 'tensorlake.functions_sdk.functions.TensorlakeRouter'>" or isinstance(
|
121
|
+
func_wrapper.indexify_function, TensorlakeRouter
|
146
122
|
)
|
147
123
|
|
148
124
|
|
149
|
-
def _func_is_reducer(func_wrapper:
|
125
|
+
def _func_is_reducer(func_wrapper: TensorlakeFunctionWrapper) -> bool:
|
150
126
|
return func_wrapper.indexify_function.accumulate is not None
|
@@ -1,9 +1,7 @@
|
|
1
1
|
from typing import Any
|
2
2
|
|
3
|
-
from
|
4
|
-
|
5
|
-
)
|
6
|
-
from indexify.function_executor.proto.message_validator import MessageValidator
|
3
|
+
from ...proto.function_executor_pb2 import RunTaskRequest
|
4
|
+
from ...proto.message_validator import MessageValidator
|
7
5
|
|
8
6
|
|
9
7
|
class RequestValidator:
|
@@ -17,7 +15,11 @@ class RequestValidator:
|
|
17
15
|
Raises: ValueError: If the request is invalid.
|
18
16
|
"""
|
19
17
|
(
|
20
|
-
self._message_validator.required_field("
|
18
|
+
self._message_validator.required_field("namespace")
|
19
|
+
.required_field("graph_name")
|
20
|
+
.required_field("graph_version")
|
21
|
+
.required_field("function_name")
|
22
|
+
.required_field("graph_invocation_id")
|
21
23
|
.required_field("task_id")
|
22
24
|
.required_serialized_object("function_input")
|
23
25
|
.optional_serialized_object("function_init_value")
|
@@ -1,17 +1,15 @@
|
|
1
1
|
from typing import List
|
2
2
|
|
3
|
-
from
|
3
|
+
from tensorlake.functions_sdk.data_objects import TensorlakeData
|
4
|
+
from tensorlake.functions_sdk.functions import FunctionCallResult, RouterCallResult
|
5
|
+
from tensorlake.functions_sdk.object_serializer import get_serializer
|
6
|
+
|
7
|
+
from ...proto.function_executor_pb2 import (
|
4
8
|
FunctionOutput,
|
5
9
|
RouterOutput,
|
6
10
|
RunTaskResponse,
|
7
11
|
SerializedObject,
|
8
12
|
)
|
9
|
-
from indexify.functions_sdk.data_objects import IndexifyData
|
10
|
-
from indexify.functions_sdk.indexify_functions import (
|
11
|
-
FunctionCallResult,
|
12
|
-
RouterCallResult,
|
13
|
-
)
|
14
|
-
from indexify.functions_sdk.object_serializer import get_serializer
|
15
13
|
|
16
14
|
|
17
15
|
class ResponseHelper:
|
@@ -81,7 +79,7 @@ class ResponseHelper:
|
|
81
79
|
success=False,
|
82
80
|
)
|
83
81
|
|
84
|
-
def _to_function_output(self, outputs: List[
|
82
|
+
def _to_function_output(self, outputs: List[TensorlakeData]) -> FunctionOutput:
|
85
83
|
output = FunctionOutput(outputs=[])
|
86
84
|
for ix_data in outputs:
|
87
85
|
serialized_object: SerializedObject = SerializedObject(
|
@@ -1,8 +1,6 @@
|
|
1
1
|
from typing import Any, Optional
|
2
2
|
|
3
|
-
from
|
4
|
-
InvocationState,
|
5
|
-
)
|
3
|
+
from tensorlake.functions_sdk.invocation_state.invocation_state import InvocationState
|
6
4
|
|
7
5
|
from .invocation_state_proxy_server import InvocationStateProxyServer
|
8
6
|
|
@@ -0,0 +1,50 @@
|
|
1
|
+
from indexify.utils.logging import (
|
2
|
+
configure_development_mode_logging,
|
3
|
+
configure_logging_early,
|
4
|
+
configure_production_mode_logging,
|
5
|
+
)
|
6
|
+
|
7
|
+
configure_logging_early()
|
8
|
+
|
9
|
+
import argparse
|
10
|
+
|
11
|
+
import structlog
|
12
|
+
|
13
|
+
from .server import Server
|
14
|
+
from .service import Service
|
15
|
+
|
16
|
+
logger = structlog.get_logger(module=__name__)
|
17
|
+
|
18
|
+
|
19
|
+
def validate_args(args):
|
20
|
+
if args.address is None:
|
21
|
+
logger.error("--address argument is required")
|
22
|
+
exit(1)
|
23
|
+
|
24
|
+
|
25
|
+
def main():
|
26
|
+
parser = argparse.ArgumentParser(
|
27
|
+
description="Runs Function Executor with the specified API server address"
|
28
|
+
)
|
29
|
+
parser.add_argument("--address", help="API server address to listen on", type=str)
|
30
|
+
parser.add_argument(
|
31
|
+
"-d", "--dev", help="Run in development mode", action="store_true"
|
32
|
+
)
|
33
|
+
args = parser.parse_args()
|
34
|
+
|
35
|
+
if args.dev:
|
36
|
+
configure_development_mode_logging()
|
37
|
+
else:
|
38
|
+
configure_production_mode_logging()
|
39
|
+
validate_args(args)
|
40
|
+
|
41
|
+
logger.info("starting function executor server", address=args.address)
|
42
|
+
|
43
|
+
Server(
|
44
|
+
server_address=args.address,
|
45
|
+
service=Service(),
|
46
|
+
).run()
|
47
|
+
|
48
|
+
|
49
|
+
if __name__ == "__main__":
|
50
|
+
main()
|
@@ -4,10 +4,18 @@
|
|
4
4
|
# This is due to internal hard gRPC limits. When we want to increase the message sizes
|
5
5
|
# we'll have to implement chunking for large messages.
|
6
6
|
_MAX_GRPC_MESSAGE_LENGTH = -1
|
7
|
+
# Disable port reuse: fail if multiple Function Executor Servers attempt to bind to the
|
8
|
+
# same port. This happens when Indexify users misconfigure the Servers. Disabling the port
|
9
|
+
# reuse results in a clear error message on Server startup instead of obscure errors later
|
10
|
+
# while Indexify cluster is serving tasks.
|
11
|
+
# If we don't disable port reuse then a random Server gets the requests so wrong tasks get
|
12
|
+
# routed to wrong servers.
|
13
|
+
_REUSE_SERVER_PORT = 0
|
7
14
|
|
8
15
|
GRPC_SERVER_OPTIONS = [
|
9
16
|
("grpc.max_receive_message_length", _MAX_GRPC_MESSAGE_LENGTH),
|
10
17
|
("grpc.max_send_message_length", _MAX_GRPC_MESSAGE_LENGTH),
|
18
|
+
("grpc.so_reuseport", _REUSE_SERVER_PORT),
|
11
19
|
]
|
12
20
|
|
13
21
|
GRPC_CHANNEL_OPTIONS = GRPC_SERVER_OPTIONS
|
@@ -24,13 +24,14 @@ message SerializedObject {
|
|
24
24
|
message InitializeRequest {
|
25
25
|
optional string namespace = 1;
|
26
26
|
optional string graph_name = 2;
|
27
|
-
optional
|
27
|
+
optional string graph_version = 3;
|
28
28
|
optional string function_name = 5;
|
29
29
|
optional SerializedObject graph = 7;
|
30
30
|
}
|
31
31
|
|
32
32
|
message InitializeResponse {
|
33
33
|
optional bool success = 1;
|
34
|
+
optional string customer_error = 2;
|
34
35
|
}
|
35
36
|
|
36
37
|
message SetInvocationStateRequest {
|
@@ -85,10 +86,14 @@ message RouterOutput {
|
|
85
86
|
}
|
86
87
|
|
87
88
|
message RunTaskRequest {
|
88
|
-
optional string
|
89
|
+
optional string namespace = 1;
|
90
|
+
optional string graph_name = 2;
|
91
|
+
optional string graph_version = 3;
|
92
|
+
optional string function_name = 4;
|
93
|
+
optional string graph_invocation_id = 5;
|
89
94
|
optional string task_id = 6;
|
90
|
-
optional SerializedObject function_input =
|
91
|
-
optional SerializedObject function_init_value =
|
95
|
+
optional SerializedObject function_input = 7;
|
96
|
+
optional SerializedObject function_init_value = 8;
|
92
97
|
}
|
93
98
|
|
94
99
|
message RunTaskResponse {
|