mlrun 1.7.0rc5__py3-none-any.whl → 1.7.0rc6__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.
Potentially problematic release.
This version of mlrun might be problematic. Click here for more details.
- mlrun/artifacts/base.py +2 -1
- mlrun/artifacts/plots.py +9 -5
- mlrun/config.py +8 -2
- mlrun/datastore/sources.py +5 -4
- mlrun/db/factory.py +1 -1
- mlrun/launcher/__init__.py +1 -1
- mlrun/launcher/base.py +1 -1
- mlrun/launcher/client.py +1 -1
- mlrun/launcher/factory.py +1 -1
- mlrun/launcher/local.py +1 -1
- mlrun/launcher/remote.py +1 -1
- mlrun/model_monitoring/api.py +6 -12
- mlrun/model_monitoring/application.py +21 -21
- mlrun/model_monitoring/applications/histogram_data_drift.py +130 -40
- mlrun/model_monitoring/batch.py +1 -42
- mlrun/model_monitoring/controller.py +1 -8
- mlrun/model_monitoring/features_drift_table.py +34 -22
- mlrun/model_monitoring/helpers.py +45 -4
- mlrun/model_monitoring/stream_processing.py +2 -0
- mlrun/projects/project.py +170 -16
- mlrun/run.py +69 -73
- mlrun/runtimes/__init__.py +35 -0
- mlrun/runtimes/base.py +1 -1
- mlrun/runtimes/nuclio/application/__init__.py +15 -0
- mlrun/runtimes/nuclio/application/application.py +283 -0
- mlrun/runtimes/nuclio/application/reverse_proxy.go +87 -0
- mlrun/runtimes/nuclio/function.py +50 -1
- mlrun/runtimes/pod.py +1 -1
- mlrun/serving/states.py +7 -19
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.0rc6.dist-info}/METADATA +1 -1
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.0rc6.dist-info}/RECORD +36 -33
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.0rc6.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.0rc6.dist-info}/WHEEL +0 -0
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.0rc6.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.0rc6.dist-info}/top_level.txt +0 -0
mlrun/runtimes/__init__.py
CHANGED
|
@@ -43,6 +43,8 @@ from .nuclio import (
|
|
|
43
43
|
new_v2_model_server,
|
|
44
44
|
nuclio_init_hook,
|
|
45
45
|
)
|
|
46
|
+
from .nuclio.application import ApplicationRuntime
|
|
47
|
+
from .nuclio.serving import serving_subkind
|
|
46
48
|
from .remotesparkjob import RemoteSparkRuntime
|
|
47
49
|
from .sparkjob import Spark3Runtime
|
|
48
50
|
|
|
@@ -101,6 +103,7 @@ class RuntimeKinds:
|
|
|
101
103
|
local = "local"
|
|
102
104
|
handler = "handler"
|
|
103
105
|
databricks = "databricks"
|
|
106
|
+
application = "application"
|
|
104
107
|
|
|
105
108
|
@staticmethod
|
|
106
109
|
def all():
|
|
@@ -115,6 +118,7 @@ class RuntimeKinds:
|
|
|
115
118
|
RuntimeKinds.mpijob,
|
|
116
119
|
RuntimeKinds.local,
|
|
117
120
|
RuntimeKinds.databricks,
|
|
121
|
+
RuntimeKinds.application,
|
|
118
122
|
]
|
|
119
123
|
|
|
120
124
|
@staticmethod
|
|
@@ -147,6 +151,7 @@ class RuntimeKinds:
|
|
|
147
151
|
RuntimeKinds.remote,
|
|
148
152
|
RuntimeKinds.nuclio,
|
|
149
153
|
RuntimeKinds.serving,
|
|
154
|
+
RuntimeKinds.application,
|
|
150
155
|
]
|
|
151
156
|
|
|
152
157
|
@staticmethod
|
|
@@ -211,6 +216,35 @@ class RuntimeKinds:
|
|
|
211
216
|
# both spark and remote spark uses different mechanism for assigning images
|
|
212
217
|
return kind not in [RuntimeKinds.spark, RuntimeKinds.remotespark]
|
|
213
218
|
|
|
219
|
+
@staticmethod
|
|
220
|
+
def resolve_nuclio_runtime(kind: str, sub_kind: str):
|
|
221
|
+
kind = kind.split(":")[0]
|
|
222
|
+
if kind not in RuntimeKinds.nuclio_runtimes():
|
|
223
|
+
raise ValueError(
|
|
224
|
+
f"Kind {kind} is not a nuclio runtime, available runtimes are {RuntimeKinds.nuclio_runtimes()}"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if sub_kind == serving_subkind:
|
|
228
|
+
return ServingRuntime()
|
|
229
|
+
|
|
230
|
+
if kind == RuntimeKinds.application:
|
|
231
|
+
return ApplicationRuntime()
|
|
232
|
+
|
|
233
|
+
runtime = RemoteRuntime()
|
|
234
|
+
runtime.spec.function_kind = sub_kind
|
|
235
|
+
return runtime
|
|
236
|
+
|
|
237
|
+
@staticmethod
|
|
238
|
+
def resolve_nuclio_sub_kind(kind):
|
|
239
|
+
is_nuclio = kind.startswith("nuclio")
|
|
240
|
+
sub_kind = kind[kind.find(":") + 1 :] if is_nuclio and ":" in kind else None
|
|
241
|
+
if kind == RuntimeKinds.serving:
|
|
242
|
+
is_nuclio = True
|
|
243
|
+
sub_kind = serving_subkind
|
|
244
|
+
elif kind == RuntimeKinds.application:
|
|
245
|
+
is_nuclio = True
|
|
246
|
+
return is_nuclio, sub_kind
|
|
247
|
+
|
|
214
248
|
|
|
215
249
|
def get_runtime_class(kind: str):
|
|
216
250
|
if kind == RuntimeKinds.mpijob:
|
|
@@ -228,6 +262,7 @@ def get_runtime_class(kind: str):
|
|
|
228
262
|
RuntimeKinds.local: LocalRuntime,
|
|
229
263
|
RuntimeKinds.remotespark: RemoteSparkRuntime,
|
|
230
264
|
RuntimeKinds.databricks: DatabricksRuntime,
|
|
265
|
+
RuntimeKinds.application: ApplicationRuntime,
|
|
231
266
|
}
|
|
232
267
|
|
|
233
268
|
return kind_runtime_map[kind]
|
mlrun/runtimes/base.py
CHANGED
|
@@ -786,7 +786,7 @@ class BaseRuntime(ModelObj):
|
|
|
786
786
|
requirements: Optional[list[str]] = None,
|
|
787
787
|
overwrite: bool = False,
|
|
788
788
|
prepare_image_for_deploy: bool = True,
|
|
789
|
-
requirements_file: str = "",
|
|
789
|
+
requirements_file: Optional[str] = "",
|
|
790
790
|
):
|
|
791
791
|
"""add package requirements from file or list to build spec.
|
|
792
792
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2024 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from .application import ApplicationRuntime
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# Copyright 2024 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import pathlib
|
|
15
|
+
|
|
16
|
+
import nuclio
|
|
17
|
+
|
|
18
|
+
import mlrun.errors
|
|
19
|
+
from mlrun.common.schemas import AuthInfo
|
|
20
|
+
from mlrun.runtimes import RemoteRuntime
|
|
21
|
+
from mlrun.runtimes.nuclio import min_nuclio_versions
|
|
22
|
+
from mlrun.runtimes.nuclio.function import NuclioSpec, NuclioStatus
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ApplicationSpec(NuclioSpec):
|
|
26
|
+
_dict_fields = NuclioSpec._dict_fields + [
|
|
27
|
+
"internal_application_port",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
command=None,
|
|
33
|
+
args=None,
|
|
34
|
+
image=None,
|
|
35
|
+
mode=None,
|
|
36
|
+
entry_points=None,
|
|
37
|
+
description=None,
|
|
38
|
+
replicas=None,
|
|
39
|
+
min_replicas=None,
|
|
40
|
+
max_replicas=None,
|
|
41
|
+
volumes=None,
|
|
42
|
+
volume_mounts=None,
|
|
43
|
+
env=None,
|
|
44
|
+
resources=None,
|
|
45
|
+
config=None,
|
|
46
|
+
base_spec=None,
|
|
47
|
+
no_cache=None,
|
|
48
|
+
source=None,
|
|
49
|
+
image_pull_policy=None,
|
|
50
|
+
function_kind=None,
|
|
51
|
+
build=None,
|
|
52
|
+
service_account=None,
|
|
53
|
+
readiness_timeout=None,
|
|
54
|
+
readiness_timeout_before_failure=None,
|
|
55
|
+
default_handler=None,
|
|
56
|
+
node_name=None,
|
|
57
|
+
node_selector=None,
|
|
58
|
+
affinity=None,
|
|
59
|
+
disable_auto_mount=False,
|
|
60
|
+
priority_class_name=None,
|
|
61
|
+
pythonpath=None,
|
|
62
|
+
workdir=None,
|
|
63
|
+
image_pull_secret=None,
|
|
64
|
+
tolerations=None,
|
|
65
|
+
preemption_mode=None,
|
|
66
|
+
security_context=None,
|
|
67
|
+
service_type=None,
|
|
68
|
+
add_templated_ingress_host_mode=None,
|
|
69
|
+
clone_target_dir=None,
|
|
70
|
+
state_thresholds=None,
|
|
71
|
+
disable_default_http_trigger=None,
|
|
72
|
+
internal_application_port=None,
|
|
73
|
+
):
|
|
74
|
+
super().__init__(
|
|
75
|
+
command=command,
|
|
76
|
+
args=args,
|
|
77
|
+
image=image,
|
|
78
|
+
mode=mode,
|
|
79
|
+
entry_points=entry_points,
|
|
80
|
+
description=description,
|
|
81
|
+
replicas=replicas,
|
|
82
|
+
min_replicas=min_replicas,
|
|
83
|
+
max_replicas=max_replicas,
|
|
84
|
+
volumes=volumes,
|
|
85
|
+
volume_mounts=volume_mounts,
|
|
86
|
+
env=env,
|
|
87
|
+
resources=resources,
|
|
88
|
+
config=config,
|
|
89
|
+
base_spec=base_spec,
|
|
90
|
+
no_cache=no_cache,
|
|
91
|
+
source=source,
|
|
92
|
+
image_pull_policy=image_pull_policy,
|
|
93
|
+
function_kind=function_kind,
|
|
94
|
+
build=build,
|
|
95
|
+
service_account=service_account,
|
|
96
|
+
readiness_timeout=readiness_timeout,
|
|
97
|
+
readiness_timeout_before_failure=readiness_timeout_before_failure,
|
|
98
|
+
default_handler=default_handler,
|
|
99
|
+
node_name=node_name,
|
|
100
|
+
node_selector=node_selector,
|
|
101
|
+
affinity=affinity,
|
|
102
|
+
disable_auto_mount=disable_auto_mount,
|
|
103
|
+
priority_class_name=priority_class_name,
|
|
104
|
+
pythonpath=pythonpath,
|
|
105
|
+
workdir=workdir,
|
|
106
|
+
image_pull_secret=image_pull_secret,
|
|
107
|
+
tolerations=tolerations,
|
|
108
|
+
preemption_mode=preemption_mode,
|
|
109
|
+
security_context=security_context,
|
|
110
|
+
service_type=service_type,
|
|
111
|
+
add_templated_ingress_host_mode=add_templated_ingress_host_mode,
|
|
112
|
+
clone_target_dir=clone_target_dir,
|
|
113
|
+
state_thresholds=state_thresholds,
|
|
114
|
+
disable_default_http_trigger=disable_default_http_trigger,
|
|
115
|
+
)
|
|
116
|
+
self.internal_application_port = internal_application_port or 8080
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def internal_application_port(self):
|
|
120
|
+
return self._internal_application_port
|
|
121
|
+
|
|
122
|
+
@internal_application_port.setter
|
|
123
|
+
def internal_application_port(self, port):
|
|
124
|
+
port = int(port)
|
|
125
|
+
if port < 0 or port > 65535:
|
|
126
|
+
raise ValueError("Port must be in the range 0-65535")
|
|
127
|
+
self._internal_application_port = port
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class ApplicationStatus(NuclioStatus):
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
state=None,
|
|
134
|
+
nuclio_name=None,
|
|
135
|
+
address=None,
|
|
136
|
+
internal_invocation_urls=None,
|
|
137
|
+
external_invocation_urls=None,
|
|
138
|
+
build_pod=None,
|
|
139
|
+
container_image=None,
|
|
140
|
+
application_image=None,
|
|
141
|
+
sidecar_name=None,
|
|
142
|
+
):
|
|
143
|
+
super().__init__(
|
|
144
|
+
state=state,
|
|
145
|
+
nuclio_name=nuclio_name,
|
|
146
|
+
address=address,
|
|
147
|
+
internal_invocation_urls=internal_invocation_urls,
|
|
148
|
+
external_invocation_urls=external_invocation_urls,
|
|
149
|
+
build_pod=build_pod,
|
|
150
|
+
container_image=container_image,
|
|
151
|
+
)
|
|
152
|
+
self.application_image = application_image or None
|
|
153
|
+
self.sidecar_name = sidecar_name or None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class ApplicationRuntime(RemoteRuntime):
|
|
157
|
+
kind = "application"
|
|
158
|
+
|
|
159
|
+
@min_nuclio_versions("1.12.7")
|
|
160
|
+
def __init__(self, spec=None, metadata=None):
|
|
161
|
+
super().__init__(spec=spec, metadata=metadata)
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def spec(self) -> ApplicationSpec:
|
|
165
|
+
return self._spec
|
|
166
|
+
|
|
167
|
+
@spec.setter
|
|
168
|
+
def spec(self, spec):
|
|
169
|
+
self._spec = self._verify_dict(spec, "spec", ApplicationSpec)
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def status(self) -> ApplicationStatus:
|
|
173
|
+
return self._status
|
|
174
|
+
|
|
175
|
+
@status.setter
|
|
176
|
+
def status(self, status):
|
|
177
|
+
self._status = self._verify_dict(status, "status", ApplicationStatus)
|
|
178
|
+
|
|
179
|
+
def set_internal_application_port(self, port: int):
|
|
180
|
+
self.spec.internal_application_port = port
|
|
181
|
+
|
|
182
|
+
def pre_deploy_validation(self):
|
|
183
|
+
super().pre_deploy_validation()
|
|
184
|
+
if not self.spec.config.get("spec.sidecars"):
|
|
185
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
186
|
+
"Application spec must include a sidecar configuration"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
sidecars = self.spec.config["spec.sidecars"]
|
|
190
|
+
for sidecar in sidecars:
|
|
191
|
+
if not sidecar.get("image"):
|
|
192
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
193
|
+
"Application sidecar spec must include an image"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if not sidecar.get("ports"):
|
|
197
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
198
|
+
"Application sidecar spec must include at least one port"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
ports = sidecar["ports"]
|
|
202
|
+
for port in ports:
|
|
203
|
+
if not port.get("containerPort"):
|
|
204
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
205
|
+
"Application sidecar port spec must include a containerPort"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if not port.get("name"):
|
|
209
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
210
|
+
"Application sidecar port spec must include a name"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if not sidecar.get("command") and sidecar.get("args"):
|
|
214
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
215
|
+
"Application sidecar spec must include a command if args are provided"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def deploy(
|
|
219
|
+
self,
|
|
220
|
+
project="",
|
|
221
|
+
tag="",
|
|
222
|
+
verbose=False,
|
|
223
|
+
auth_info: AuthInfo = None,
|
|
224
|
+
builder_env: dict = None,
|
|
225
|
+
force_build: bool = False,
|
|
226
|
+
):
|
|
227
|
+
self._ensure_reverse_proxy_configurations()
|
|
228
|
+
self._configure_application_sidecar()
|
|
229
|
+
super().deploy(
|
|
230
|
+
project,
|
|
231
|
+
tag,
|
|
232
|
+
verbose,
|
|
233
|
+
auth_info,
|
|
234
|
+
builder_env,
|
|
235
|
+
force_build,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _ensure_reverse_proxy_configurations(self):
|
|
239
|
+
if self.spec.build.functionSourceCode or self.status.container_image:
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
filename, handler = ApplicationRuntime.get_filename_and_handler()
|
|
243
|
+
name, spec, code = nuclio.build_file(
|
|
244
|
+
filename,
|
|
245
|
+
name=self.metadata.name,
|
|
246
|
+
handler=handler,
|
|
247
|
+
)
|
|
248
|
+
self.spec.function_handler = mlrun.utils.get_in(spec, "spec.handler")
|
|
249
|
+
self.spec.build.functionSourceCode = mlrun.utils.get_in(
|
|
250
|
+
spec, "spec.build.functionSourceCode"
|
|
251
|
+
)
|
|
252
|
+
self.spec.nuclio_runtime = mlrun.utils.get_in(spec, "spec.runtime")
|
|
253
|
+
|
|
254
|
+
def _configure_application_sidecar(self):
|
|
255
|
+
# Save the application image in the status to allow overriding it with the reverse proxy entry point
|
|
256
|
+
if self.spec.image and (
|
|
257
|
+
not self.status.application_image
|
|
258
|
+
or self.spec.image != self.status.container_image
|
|
259
|
+
):
|
|
260
|
+
self.status.application_image = self.spec.image
|
|
261
|
+
self.spec.image = ""
|
|
262
|
+
|
|
263
|
+
if self.status.container_image:
|
|
264
|
+
self.from_image(self.status.container_image)
|
|
265
|
+
# nuclio implementation detail - when providing the image and emptying out the source code,
|
|
266
|
+
# nuclio skips rebuilding the image and simply takes the prebuilt image
|
|
267
|
+
self.spec.build.functionSourceCode = ""
|
|
268
|
+
|
|
269
|
+
self.status.sidecar_name = f"{self.metadata.name}-sidecar"
|
|
270
|
+
self.with_sidecar(
|
|
271
|
+
name=self.status.sidecar_name,
|
|
272
|
+
image=self.status.application_image,
|
|
273
|
+
ports=self.spec.internal_application_port,
|
|
274
|
+
command=self.spec.command,
|
|
275
|
+
args=self.spec.args,
|
|
276
|
+
)
|
|
277
|
+
self.set_env("SIDECAR_PORT", self.spec.internal_application_port)
|
|
278
|
+
self.set_env("SIDECAR_HOST", "http://localhost")
|
|
279
|
+
|
|
280
|
+
@classmethod
|
|
281
|
+
def get_filename_and_handler(cls) -> (str, str):
|
|
282
|
+
reverse_proxy_file_path = pathlib.Path(__file__).parent / "reverse_proxy.go"
|
|
283
|
+
return str(reverse_proxy_file_path), "Handler"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Copyright 2024 Iguazio
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
package main
|
|
15
|
+
|
|
16
|
+
import (
|
|
17
|
+
"bytes"
|
|
18
|
+
"fmt"
|
|
19
|
+
"net/http"
|
|
20
|
+
"net/http/httptest"
|
|
21
|
+
"net/http/httputil"
|
|
22
|
+
"net/url"
|
|
23
|
+
"os"
|
|
24
|
+
"strings"
|
|
25
|
+
|
|
26
|
+
nuclio "github.com/nuclio/nuclio-sdk-go"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
func Handler(context *nuclio.Context, event nuclio.Event) (interface{}, error) {
|
|
30
|
+
reverseProxy := context.UserData.(map[string]interface{})["reverseProxy"].(*httputil.ReverseProxy)
|
|
31
|
+
sidecarUrl := context.UserData.(map[string]interface{})["server"].(string)
|
|
32
|
+
|
|
33
|
+
// populate reverse proxy http request
|
|
34
|
+
httpRequest, err := http.NewRequest(event.GetMethod(), event.GetPath(), bytes.NewReader(event.GetBody()))
|
|
35
|
+
if err != nil {
|
|
36
|
+
context.Logger.ErrorWith("Failed to create a reverse proxy request")
|
|
37
|
+
return nil, err
|
|
38
|
+
}
|
|
39
|
+
for k, v := range event.GetHeaders() {
|
|
40
|
+
httpRequest.Header[k] = []string{v.(string)}
|
|
41
|
+
}
|
|
42
|
+
recorder := httptest.NewRecorder()
|
|
43
|
+
reverseProxy.ServeHTTP(recorder, httpRequest)
|
|
44
|
+
|
|
45
|
+
// send request to sidecar
|
|
46
|
+
context.Logger.InfoWith("Forwarding request to sidecar", "sidecarUrl", sidecarUrl)
|
|
47
|
+
response := recorder.Result()
|
|
48
|
+
|
|
49
|
+
headers := make(map[string]interface{})
|
|
50
|
+
for key, value := range response.Header {
|
|
51
|
+
headers[key] = value[0]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// let the processor calculate the content length
|
|
55
|
+
delete(headers, "Content-Length")
|
|
56
|
+
return nuclio.Response{
|
|
57
|
+
StatusCode: response.StatusCode,
|
|
58
|
+
Body: recorder.Body.Bytes(),
|
|
59
|
+
ContentType: response.Header.Get("Content-Type"),
|
|
60
|
+
Headers: headers,
|
|
61
|
+
}, nil
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func InitContext(context *nuclio.Context) error {
|
|
65
|
+
sidecarHost := os.Getenv("SIDECAR_HOST")
|
|
66
|
+
sidecarPort := os.Getenv("SIDECAR_PORT")
|
|
67
|
+
if sidecarHost == "" {
|
|
68
|
+
sidecarHost = "http://localhost"
|
|
69
|
+
} else if !strings.Contains(sidecarHost, "://") {
|
|
70
|
+
sidecarHost = fmt.Sprintf("http://%s", sidecarHost)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// url for request forwarding
|
|
74
|
+
sidecarUrl := fmt.Sprintf("%s:%s", sidecarHost, sidecarPort)
|
|
75
|
+
parsedURL, err := url.Parse(sidecarUrl)
|
|
76
|
+
if err != nil {
|
|
77
|
+
context.Logger.ErrorWith("Failed to parse sidecar url", "sidecarUrl", sidecarUrl)
|
|
78
|
+
return err
|
|
79
|
+
}
|
|
80
|
+
reverseProxy := httputil.NewSingleHostReverseProxy(parsedURL)
|
|
81
|
+
|
|
82
|
+
context.UserData = map[string]interface{}{
|
|
83
|
+
"server": sidecarUrl,
|
|
84
|
+
"reverseProxy": reverseProxy,
|
|
85
|
+
}
|
|
86
|
+
return nil
|
|
87
|
+
}
|
|
@@ -291,6 +291,9 @@ class RemoteRuntime(KubeResource):
|
|
|
291
291
|
def status(self, status):
|
|
292
292
|
self._status = self._verify_dict(status, "status", NuclioStatus)
|
|
293
293
|
|
|
294
|
+
def pre_deploy_validation(self):
|
|
295
|
+
pass
|
|
296
|
+
|
|
294
297
|
def set_config(self, key, value):
|
|
295
298
|
self.spec.config[key] = value
|
|
296
299
|
return self
|
|
@@ -603,7 +606,6 @@ class RemoteRuntime(KubeResource):
|
|
|
603
606
|
return self.spec.command
|
|
604
607
|
|
|
605
608
|
def _wait_for_function_deployment(self, db, verbose=False):
|
|
606
|
-
text = ""
|
|
607
609
|
state = ""
|
|
608
610
|
last_log_timestamp = 1
|
|
609
611
|
while state not in ["ready", "error", "unhealthy"]:
|
|
@@ -958,6 +960,53 @@ class RemoteRuntime(KubeResource):
|
|
|
958
960
|
data = json.loads(data)
|
|
959
961
|
return data
|
|
960
962
|
|
|
963
|
+
def with_sidecar(
|
|
964
|
+
self,
|
|
965
|
+
name: str = None,
|
|
966
|
+
image: str = None,
|
|
967
|
+
ports: typing.Optional[typing.Union[int, list[int]]] = None,
|
|
968
|
+
command: typing.Optional[str] = None,
|
|
969
|
+
args: typing.Optional[list[str]] = None,
|
|
970
|
+
):
|
|
971
|
+
"""
|
|
972
|
+
Add a sidecar container to the function pod
|
|
973
|
+
:param name: Sidecar container name.
|
|
974
|
+
:param image: Sidecar container image.
|
|
975
|
+
:param ports: Sidecar container ports to expose. Can be a single port or a list of ports.
|
|
976
|
+
:param command: Sidecar container command instead of the image entrypoint.
|
|
977
|
+
:param args: Sidecar container command args (requires command to be set).
|
|
978
|
+
"""
|
|
979
|
+
name = name or f"{self.metadata.name}-sidecar"
|
|
980
|
+
sidecar = self._set_sidecar(name)
|
|
981
|
+
if image:
|
|
982
|
+
sidecar["image"] = image
|
|
983
|
+
|
|
984
|
+
ports = mlrun.utils.helpers.as_list(ports)
|
|
985
|
+
sidecar["ports"] = [
|
|
986
|
+
{
|
|
987
|
+
"name": "http",
|
|
988
|
+
"containerPort": port,
|
|
989
|
+
"protocol": "TCP",
|
|
990
|
+
}
|
|
991
|
+
for port in ports
|
|
992
|
+
]
|
|
993
|
+
|
|
994
|
+
if command:
|
|
995
|
+
sidecar["command"] = command
|
|
996
|
+
|
|
997
|
+
if args:
|
|
998
|
+
sidecar["args"] = args
|
|
999
|
+
|
|
1000
|
+
def _set_sidecar(self, name: str) -> dict:
|
|
1001
|
+
self.spec.config.setdefault("spec.sidecars", [])
|
|
1002
|
+
sidecars = self.spec.config["spec.sidecars"]
|
|
1003
|
+
for sidecar in sidecars:
|
|
1004
|
+
if sidecar["name"] == name:
|
|
1005
|
+
return sidecar
|
|
1006
|
+
|
|
1007
|
+
sidecars.append({"name": name})
|
|
1008
|
+
return sidecars[-1]
|
|
1009
|
+
|
|
961
1010
|
def _trigger_of_kind_exists(self, kind: str) -> bool:
|
|
962
1011
|
if not self.spec.config:
|
|
963
1012
|
return False
|
mlrun/runtimes/pod.py
CHANGED
mlrun/serving/states.py
CHANGED
|
@@ -1161,19 +1161,11 @@ class FlowStep(BaseStep):
|
|
|
1161
1161
|
if self._controller:
|
|
1162
1162
|
# async flow (using storey)
|
|
1163
1163
|
event._awaitable_result = None
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
return resp.await_result()
|
|
1170
|
-
else:
|
|
1171
|
-
resp_awaitable = self._controller.emit(
|
|
1172
|
-
event, await_result=self._wait_for_result
|
|
1173
|
-
)
|
|
1174
|
-
if self._wait_for_result:
|
|
1175
|
-
return resp_awaitable
|
|
1176
|
-
return self._await_and_return_id(resp_awaitable, event)
|
|
1164
|
+
resp = self._controller.emit(
|
|
1165
|
+
event, return_awaitable_result=self._wait_for_result
|
|
1166
|
+
)
|
|
1167
|
+
if self._wait_for_result and resp:
|
|
1168
|
+
return resp.await_result()
|
|
1177
1169
|
event = copy(event)
|
|
1178
1170
|
event.body = {"id": event.id}
|
|
1179
1171
|
return event
|
|
@@ -1568,12 +1560,8 @@ def _init_async_objects(context, steps):
|
|
|
1568
1560
|
source_args = context.get_param("source_args", {})
|
|
1569
1561
|
explicit_ack = is_explicit_ack_supported(context) and mlrun.mlconf.is_explicit_ack()
|
|
1570
1562
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
else:
|
|
1574
|
-
source_class = storey.AsyncEmitSource
|
|
1575
|
-
|
|
1576
|
-
default_source = source_class(
|
|
1563
|
+
# TODO: Change to AsyncEmitSource once we can drop support for nuclio<1.12.10
|
|
1564
|
+
default_source = storey.SyncEmitSource(
|
|
1577
1565
|
context=context,
|
|
1578
1566
|
explicit_ack=explicit_ack,
|
|
1579
1567
|
**source_args,
|
mlrun/utils/version/version.json
CHANGED