mlrun 1.10.0rc13__py3-none-any.whl → 1.10.0rc42__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/__init__.py +22 -2
- mlrun/artifacts/base.py +0 -31
- mlrun/artifacts/document.py +6 -1
- mlrun/artifacts/llm_prompt.py +123 -25
- mlrun/artifacts/manager.py +0 -5
- mlrun/artifacts/model.py +3 -3
- mlrun/common/constants.py +10 -1
- mlrun/common/formatters/artifact.py +1 -0
- mlrun/common/model_monitoring/helpers.py +86 -0
- mlrun/common/schemas/__init__.py +3 -0
- mlrun/common/schemas/auth.py +2 -0
- mlrun/common/schemas/function.py +10 -0
- mlrun/common/schemas/hub.py +30 -18
- mlrun/common/schemas/model_monitoring/__init__.py +3 -0
- mlrun/common/schemas/model_monitoring/constants.py +30 -6
- mlrun/common/schemas/model_monitoring/functions.py +14 -5
- mlrun/common/schemas/model_monitoring/model_endpoints.py +21 -0
- mlrun/common/schemas/pipeline.py +1 -1
- mlrun/common/schemas/serving.py +3 -0
- mlrun/common/schemas/workflow.py +3 -1
- mlrun/common/secrets.py +22 -1
- mlrun/config.py +33 -11
- mlrun/datastore/__init__.py +11 -3
- mlrun/datastore/azure_blob.py +162 -47
- mlrun/datastore/datastore.py +9 -4
- mlrun/datastore/datastore_profile.py +61 -5
- mlrun/datastore/model_provider/huggingface_provider.py +363 -0
- mlrun/datastore/model_provider/mock_model_provider.py +87 -0
- mlrun/datastore/model_provider/model_provider.py +230 -65
- mlrun/datastore/model_provider/openai_provider.py +295 -42
- mlrun/datastore/s3.py +24 -2
- mlrun/datastore/storeytargets.py +2 -3
- mlrun/datastore/utils.py +15 -3
- mlrun/db/base.py +47 -19
- mlrun/db/httpdb.py +120 -56
- mlrun/db/nopdb.py +38 -10
- mlrun/execution.py +70 -19
- mlrun/hub/__init__.py +15 -0
- mlrun/hub/module.py +181 -0
- mlrun/k8s_utils.py +105 -16
- mlrun/launcher/base.py +13 -6
- mlrun/launcher/local.py +15 -0
- mlrun/model.py +24 -3
- mlrun/model_monitoring/__init__.py +1 -0
- mlrun/model_monitoring/api.py +66 -27
- mlrun/model_monitoring/applications/__init__.py +1 -1
- mlrun/model_monitoring/applications/base.py +509 -117
- mlrun/model_monitoring/applications/context.py +2 -4
- mlrun/model_monitoring/applications/results.py +4 -7
- mlrun/model_monitoring/controller.py +239 -101
- mlrun/model_monitoring/db/_schedules.py +116 -33
- mlrun/model_monitoring/db/_stats.py +4 -3
- mlrun/model_monitoring/db/tsdb/base.py +100 -9
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +11 -6
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +191 -50
- mlrun/model_monitoring/db/tsdb/tdengine/writer_graph_steps.py +51 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +17 -4
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +259 -40
- mlrun/model_monitoring/helpers.py +54 -9
- mlrun/model_monitoring/stream_processing.py +45 -14
- mlrun/model_monitoring/writer.py +220 -1
- mlrun/platforms/__init__.py +3 -2
- mlrun/platforms/iguazio.py +7 -3
- mlrun/projects/operations.py +6 -1
- mlrun/projects/pipelines.py +46 -26
- mlrun/projects/project.py +166 -58
- mlrun/run.py +94 -17
- mlrun/runtimes/__init__.py +18 -0
- mlrun/runtimes/base.py +14 -6
- mlrun/runtimes/daskjob.py +7 -0
- mlrun/runtimes/local.py +5 -2
- mlrun/runtimes/mounts.py +20 -2
- mlrun/runtimes/mpijob/abstract.py +6 -0
- mlrun/runtimes/mpijob/v1.py +6 -0
- mlrun/runtimes/nuclio/__init__.py +1 -0
- mlrun/runtimes/nuclio/application/application.py +149 -17
- mlrun/runtimes/nuclio/function.py +76 -27
- mlrun/runtimes/nuclio/serving.py +97 -15
- mlrun/runtimes/pod.py +234 -21
- mlrun/runtimes/remotesparkjob.py +6 -0
- mlrun/runtimes/sparkjob/spark3job.py +6 -0
- mlrun/runtimes/utils.py +49 -11
- mlrun/secrets.py +54 -13
- mlrun/serving/__init__.py +2 -0
- mlrun/serving/remote.py +79 -6
- mlrun/serving/routers.py +23 -41
- mlrun/serving/server.py +320 -80
- mlrun/serving/states.py +725 -157
- mlrun/serving/steps.py +62 -0
- mlrun/serving/system_steps.py +200 -119
- mlrun/serving/v2_serving.py +9 -10
- mlrun/utils/helpers.py +288 -88
- mlrun/utils/logger.py +3 -1
- mlrun/utils/notifications/notification/base.py +18 -0
- mlrun/utils/notifications/notification/git.py +2 -4
- mlrun/utils/notifications/notification/slack.py +2 -4
- mlrun/utils/notifications/notification/webhook.py +2 -5
- mlrun/utils/notifications/notification_pusher.py +1 -1
- mlrun/utils/retryer.py +15 -2
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.10.0rc13.dist-info → mlrun-1.10.0rc42.dist-info}/METADATA +45 -51
- {mlrun-1.10.0rc13.dist-info → mlrun-1.10.0rc42.dist-info}/RECORD +106 -101
- mlrun/api/schemas/__init__.py +0 -259
- {mlrun-1.10.0rc13.dist-info → mlrun-1.10.0rc42.dist-info}/WHEEL +0 -0
- {mlrun-1.10.0rc13.dist-info → mlrun-1.10.0rc42.dist-info}/entry_points.txt +0 -0
- {mlrun-1.10.0rc13.dist-info → mlrun-1.10.0rc42.dist-info}/licenses/LICENSE +0 -0
- {mlrun-1.10.0rc13.dist-info → mlrun-1.10.0rc42.dist-info}/top_level.txt +0 -0
|
@@ -22,19 +22,23 @@ import mlrun.errors
|
|
|
22
22
|
import mlrun.run
|
|
23
23
|
from mlrun.common.runtimes.constants import NuclioIngressAddTemplatedIngressModes
|
|
24
24
|
from mlrun.runtimes import RemoteRuntime
|
|
25
|
-
from mlrun.runtimes.nuclio import
|
|
25
|
+
from mlrun.runtimes.nuclio import (
|
|
26
|
+
min_nuclio_versions,
|
|
27
|
+
multiple_port_sidecar_is_supported,
|
|
28
|
+
)
|
|
26
29
|
from mlrun.runtimes.nuclio.api_gateway import (
|
|
27
30
|
APIGateway,
|
|
28
31
|
APIGatewayMetadata,
|
|
29
32
|
APIGatewaySpec,
|
|
30
33
|
)
|
|
31
34
|
from mlrun.runtimes.nuclio.function import NuclioSpec, NuclioStatus
|
|
32
|
-
from mlrun.utils import logger, update_in
|
|
35
|
+
from mlrun.utils import is_valid_port, logger, update_in
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
class ApplicationSpec(NuclioSpec):
|
|
36
39
|
_dict_fields = NuclioSpec._dict_fields + [
|
|
37
40
|
"internal_application_port",
|
|
41
|
+
"application_ports",
|
|
38
42
|
]
|
|
39
43
|
|
|
40
44
|
def __init__(
|
|
@@ -78,7 +82,12 @@ class ApplicationSpec(NuclioSpec):
|
|
|
78
82
|
add_templated_ingress_host_mode=None,
|
|
79
83
|
state_thresholds=None,
|
|
80
84
|
disable_default_http_trigger=None,
|
|
85
|
+
serving_spec=None,
|
|
86
|
+
graph=None,
|
|
87
|
+
parameters=None,
|
|
88
|
+
track_models=None,
|
|
81
89
|
internal_application_port=None,
|
|
90
|
+
application_ports=None,
|
|
82
91
|
):
|
|
83
92
|
super().__init__(
|
|
84
93
|
command=command,
|
|
@@ -118,6 +127,10 @@ class ApplicationSpec(NuclioSpec):
|
|
|
118
127
|
security_context=security_context,
|
|
119
128
|
service_type=service_type,
|
|
120
129
|
add_templated_ingress_host_mode=add_templated_ingress_host_mode,
|
|
130
|
+
serving_spec=serving_spec,
|
|
131
|
+
graph=graph,
|
|
132
|
+
parameters=parameters,
|
|
133
|
+
track_models=track_models,
|
|
121
134
|
state_thresholds=state_thresholds,
|
|
122
135
|
disable_default_http_trigger=disable_default_http_trigger,
|
|
123
136
|
)
|
|
@@ -126,11 +139,60 @@ class ApplicationSpec(NuclioSpec):
|
|
|
126
139
|
self.min_replicas = min_replicas or 1
|
|
127
140
|
self.max_replicas = max_replicas or 1
|
|
128
141
|
|
|
142
|
+
# initializing internal application port and application ports
|
|
143
|
+
self._internal_application_port = None
|
|
144
|
+
self._application_ports = []
|
|
145
|
+
|
|
146
|
+
application_ports = application_ports or []
|
|
147
|
+
|
|
148
|
+
# if internal_application_port is not provided, use the first application port
|
|
149
|
+
if not internal_application_port and len(application_ports) > 0:
|
|
150
|
+
internal_application_port = application_ports[0]
|
|
151
|
+
|
|
152
|
+
# the port of application sidecar to which traffic will be routed from a nuclio function
|
|
129
153
|
self.internal_application_port = (
|
|
130
154
|
internal_application_port
|
|
131
155
|
or mlrun.mlconf.function.application.default_sidecar_internal_port
|
|
132
156
|
)
|
|
133
157
|
|
|
158
|
+
# all exposed ports by the application sidecar
|
|
159
|
+
self.application_ports = application_ports
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def application_ports(self):
|
|
163
|
+
return self._application_ports
|
|
164
|
+
|
|
165
|
+
@application_ports.setter
|
|
166
|
+
def application_ports(self, ports):
|
|
167
|
+
"""
|
|
168
|
+
Set the application ports for the application sidecar.
|
|
169
|
+
The internal application port is always included and always first.
|
|
170
|
+
"""
|
|
171
|
+
# Handle None / single int
|
|
172
|
+
if ports is None:
|
|
173
|
+
ports = []
|
|
174
|
+
elif isinstance(ports, int):
|
|
175
|
+
ports = [ports]
|
|
176
|
+
elif not isinstance(ports, list):
|
|
177
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
178
|
+
"Application ports must be a list of integers"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Validate and normalize
|
|
182
|
+
cleaned_ports = []
|
|
183
|
+
for port in ports:
|
|
184
|
+
is_valid_port(port, raise_on_error=True)
|
|
185
|
+
if port != self.internal_application_port:
|
|
186
|
+
cleaned_ports.append(port)
|
|
187
|
+
|
|
188
|
+
application_ports = [self.internal_application_port] + cleaned_ports
|
|
189
|
+
|
|
190
|
+
# ensure multiple ports are supported in Nuclio
|
|
191
|
+
if len(application_ports) > 1:
|
|
192
|
+
multiple_port_sidecar_is_supported()
|
|
193
|
+
|
|
194
|
+
self._application_ports = application_ports
|
|
195
|
+
|
|
134
196
|
@property
|
|
135
197
|
def internal_application_port(self):
|
|
136
198
|
return self._internal_application_port
|
|
@@ -138,10 +200,20 @@ class ApplicationSpec(NuclioSpec):
|
|
|
138
200
|
@internal_application_port.setter
|
|
139
201
|
def internal_application_port(self, port):
|
|
140
202
|
port = int(port)
|
|
141
|
-
|
|
142
|
-
raise ValueError("Port must be in the range 0-65535")
|
|
203
|
+
is_valid_port(port, raise_on_error=True)
|
|
143
204
|
self._internal_application_port = port
|
|
144
205
|
|
|
206
|
+
# If when internal application port is being set, length of self._application_ports is 1,
|
|
207
|
+
# it means that it consist of [old_port] only
|
|
208
|
+
# so in this case, we rewrite the list completely, by setting value to [new_value]
|
|
209
|
+
if len(self.application_ports) == 1:
|
|
210
|
+
self._application_ports = [port]
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
# when setting new internal application port, ensure that it is included in the application ports
|
|
214
|
+
# it just triggers setter logic, so setting to the same value is a no-op
|
|
215
|
+
self.application_ports = self._application_ports
|
|
216
|
+
|
|
145
217
|
|
|
146
218
|
class ApplicationStatus(NuclioStatus):
|
|
147
219
|
def __init__(
|
|
@@ -222,6 +294,32 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
222
294
|
def set_internal_application_port(self, port: int):
|
|
223
295
|
self.spec.internal_application_port = port
|
|
224
296
|
|
|
297
|
+
def with_sidecar(
|
|
298
|
+
self,
|
|
299
|
+
name: typing.Optional[str] = None,
|
|
300
|
+
image: typing.Optional[str] = None,
|
|
301
|
+
ports: typing.Optional[typing.Union[int, list[int]]] = None,
|
|
302
|
+
command: typing.Optional[str] = None,
|
|
303
|
+
args: typing.Optional[list[str]] = None,
|
|
304
|
+
):
|
|
305
|
+
# wraps with_sidecar just to set the application ports
|
|
306
|
+
super().with_sidecar(
|
|
307
|
+
name=name,
|
|
308
|
+
image=image,
|
|
309
|
+
ports=ports,
|
|
310
|
+
command=command,
|
|
311
|
+
args=args,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if ports:
|
|
315
|
+
if self.spec.internal_application_port != ports[0]:
|
|
316
|
+
logger.info(
|
|
317
|
+
f"Setting internal application port to the first port from the sidecar: {ports[0]}. "
|
|
318
|
+
f"If this is not intended, please set the internal_application_port explicitly."
|
|
319
|
+
)
|
|
320
|
+
self.spec.internal_application_port = ports[0]
|
|
321
|
+
self.spec.application_ports = ports
|
|
322
|
+
|
|
225
323
|
def pre_deploy_validation(self):
|
|
226
324
|
super().pre_deploy_validation()
|
|
227
325
|
if not self.spec.config.get("spec.sidecars"):
|
|
@@ -302,6 +400,7 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
302
400
|
|
|
303
401
|
:return: The default API gateway URL if created or True if the function is ready (deployed)
|
|
304
402
|
"""
|
|
403
|
+
|
|
305
404
|
if (self.requires_build() and not self.spec.image) or force_build:
|
|
306
405
|
self._fill_credentials()
|
|
307
406
|
self._build_application_image(
|
|
@@ -315,8 +414,7 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
315
414
|
show_on_failure=show_on_failure,
|
|
316
415
|
)
|
|
317
416
|
|
|
318
|
-
|
|
319
|
-
self._ensure_reverse_proxy_configurations(self)
|
|
417
|
+
self._ensure_reverse_proxy_configurations()
|
|
320
418
|
self._configure_application_sidecar()
|
|
321
419
|
|
|
322
420
|
# We only allow accessing the application via the API Gateway
|
|
@@ -400,8 +498,10 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
400
498
|
# nuclio implementation detail - when providing the image and emptying out the source code and build source,
|
|
401
499
|
# nuclio skips rebuilding the image and simply takes the prebuilt image
|
|
402
500
|
self.spec.build.functionSourceCode = ""
|
|
501
|
+
self.spec.config.pop("spec.build.functionSourceCode", None)
|
|
403
502
|
self.status.application_source = self.spec.build.source
|
|
404
503
|
self.spec.build.source = ""
|
|
504
|
+
self.spec.config.pop("spec.build.source", None)
|
|
405
505
|
|
|
406
506
|
# save the image in the status, so we won't repopulate the function source code
|
|
407
507
|
self.status.container_image = image
|
|
@@ -429,6 +529,7 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
429
529
|
ssl_redirect: typing.Optional[bool] = None,
|
|
430
530
|
set_as_default: bool = False,
|
|
431
531
|
gateway_timeout: typing.Optional[int] = None,
|
|
532
|
+
port: typing.Optional[int] = None,
|
|
432
533
|
):
|
|
433
534
|
"""
|
|
434
535
|
Create the application API gateway. Once the application is deployed, the API gateway can be created.
|
|
@@ -445,6 +546,8 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
445
546
|
:param set_as_default: Set the API gateway as the default for the application (`status.api_gateway`)
|
|
446
547
|
:param gateway_timeout: nginx ingress timeout in sec (request timeout, when will the gateway return an
|
|
447
548
|
error)
|
|
549
|
+
:param port: The API gateway port, used only when direct_port_access=True
|
|
550
|
+
|
|
448
551
|
:return: The API gateway URL
|
|
449
552
|
"""
|
|
450
553
|
if not name:
|
|
@@ -465,7 +568,15 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
465
568
|
"Authentication credentials not provided"
|
|
466
569
|
)
|
|
467
570
|
|
|
468
|
-
|
|
571
|
+
if direct_port_access and port:
|
|
572
|
+
logger.warning(
|
|
573
|
+
"Ignoring 'port' because 'direct_port_access' is enabled. "
|
|
574
|
+
"The 'port' setting is only applicable when 'direct_port_access' is disabled."
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
ports = (
|
|
578
|
+
port or self.spec.internal_application_port if direct_port_access else []
|
|
579
|
+
)
|
|
469
580
|
|
|
470
581
|
api_gateway = APIGateway(
|
|
471
582
|
APIGatewayMetadata(
|
|
@@ -593,6 +704,12 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
593
704
|
"""
|
|
594
705
|
# create a function that includes only the reverse proxy, without the application
|
|
595
706
|
|
|
707
|
+
if not mlrun.get_current_project(silent=True):
|
|
708
|
+
raise mlrun.errors.MLRunMissingProjectError(
|
|
709
|
+
"An active project is required to run deploy_reverse_proxy_image(). "
|
|
710
|
+
"Use `mlrun.get_or_create_project()` or set an active project first."
|
|
711
|
+
)
|
|
712
|
+
|
|
596
713
|
reverse_proxy_func = mlrun.run.new_function(
|
|
597
714
|
name="reverse-proxy-temp", kind="remote"
|
|
598
715
|
)
|
|
@@ -682,27 +799,42 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
682
799
|
with_mlrun=with_mlrun,
|
|
683
800
|
)
|
|
684
801
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
802
|
+
def _ensure_reverse_proxy_configurations(self):
|
|
803
|
+
# If an HTTP trigger already exists in the spec,
|
|
804
|
+
# it means the user explicitly defined a custom configuration,
|
|
805
|
+
# so, skip automatic creation.
|
|
806
|
+
skip_http_trigger_creation = False
|
|
807
|
+
for key, value in self.spec.config.items():
|
|
808
|
+
if key.startswith("spec.triggers"):
|
|
809
|
+
if isinstance(value, dict):
|
|
810
|
+
if value.get("kind") == "http":
|
|
811
|
+
skip_http_trigger_creation = True
|
|
812
|
+
break
|
|
813
|
+
if not skip_http_trigger_creation:
|
|
814
|
+
self.with_http(
|
|
815
|
+
workers=mlrun.mlconf.function.application.default_worker_number,
|
|
816
|
+
trigger_name="application-http",
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
if self.spec.build.functionSourceCode or self.status.container_image:
|
|
688
820
|
return
|
|
689
821
|
|
|
690
822
|
filename, handler = ApplicationRuntime.get_filename_and_handler()
|
|
691
823
|
name, spec, code = nuclio.build_file(
|
|
692
824
|
filename,
|
|
693
|
-
name=
|
|
825
|
+
name=self.metadata.name,
|
|
694
826
|
handler=handler,
|
|
695
827
|
)
|
|
696
|
-
|
|
697
|
-
|
|
828
|
+
self.spec.function_handler = mlrun.utils.get_in(spec, "spec.handler")
|
|
829
|
+
self.spec.build.functionSourceCode = mlrun.utils.get_in(
|
|
698
830
|
spec, "spec.build.functionSourceCode"
|
|
699
831
|
)
|
|
700
|
-
|
|
832
|
+
self.spec.nuclio_runtime = mlrun.utils.get_in(spec, "spec.runtime")
|
|
701
833
|
|
|
702
834
|
# default the reverse proxy logger level to info
|
|
703
835
|
logger_sinks_key = "spec.loggerSinks"
|
|
704
|
-
if not
|
|
705
|
-
|
|
836
|
+
if not self.spec.config.get(logger_sinks_key):
|
|
837
|
+
self.set_config(
|
|
706
838
|
logger_sinks_key, [{"level": "info", "sink": "myStdoutLoggerSink"}]
|
|
707
839
|
)
|
|
708
840
|
|
|
@@ -726,7 +858,7 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
726
858
|
self.with_sidecar(
|
|
727
859
|
name=self.status.sidecar_name,
|
|
728
860
|
image=self.status.application_image,
|
|
729
|
-
ports=self.spec.
|
|
861
|
+
ports=self.spec.application_ports,
|
|
730
862
|
command=self.spec.command,
|
|
731
863
|
args=self.spec.args,
|
|
732
864
|
)
|
|
@@ -16,6 +16,7 @@ import asyncio
|
|
|
16
16
|
import copy
|
|
17
17
|
import json
|
|
18
18
|
import typing
|
|
19
|
+
import warnings
|
|
19
20
|
from datetime import datetime
|
|
20
21
|
from time import sleep
|
|
21
22
|
|
|
@@ -29,6 +30,7 @@ from kubernetes import client
|
|
|
29
30
|
from nuclio.deploy import find_dashboard_url, get_deploy_status
|
|
30
31
|
from nuclio.triggers import V3IOStreamTrigger
|
|
31
32
|
|
|
33
|
+
import mlrun.common.constants
|
|
32
34
|
import mlrun.db
|
|
33
35
|
import mlrun.errors
|
|
34
36
|
import mlrun.k8s_utils
|
|
@@ -155,6 +157,9 @@ class NuclioSpec(KubeResourceSpec):
|
|
|
155
157
|
state_thresholds=None,
|
|
156
158
|
disable_default_http_trigger=None,
|
|
157
159
|
serving_spec=None,
|
|
160
|
+
graph=None,
|
|
161
|
+
parameters=None,
|
|
162
|
+
track_models=None,
|
|
158
163
|
):
|
|
159
164
|
super().__init__(
|
|
160
165
|
command=command,
|
|
@@ -185,6 +190,9 @@ class NuclioSpec(KubeResourceSpec):
|
|
|
185
190
|
security_context=security_context,
|
|
186
191
|
state_thresholds=state_thresholds,
|
|
187
192
|
serving_spec=serving_spec,
|
|
193
|
+
graph=graph,
|
|
194
|
+
parameters=parameters,
|
|
195
|
+
track_models=track_models,
|
|
188
196
|
)
|
|
189
197
|
|
|
190
198
|
self.base_spec = base_spec or {}
|
|
@@ -416,6 +424,18 @@ class RemoteRuntime(KubeResource):
|
|
|
416
424
|
)
|
|
417
425
|
"""
|
|
418
426
|
self.spec.build.source = source
|
|
427
|
+
|
|
428
|
+
code = (
|
|
429
|
+
self.spec.build.functionSourceCode if hasattr(self.spec, "build") else None
|
|
430
|
+
)
|
|
431
|
+
if code:
|
|
432
|
+
# Warn and clear any inline code so the archive is actually used
|
|
433
|
+
logger.warning(
|
|
434
|
+
"Cannot specify both code and source archive. Removing the code so the provided "
|
|
435
|
+
"source archive will be used instead."
|
|
436
|
+
)
|
|
437
|
+
self.spec.build.functionSourceCode = None
|
|
438
|
+
|
|
419
439
|
# update handler in function_handler if needed
|
|
420
440
|
if handler:
|
|
421
441
|
self.spec.function_handler = handler
|
|
@@ -824,7 +844,8 @@ class RemoteRuntime(KubeResource):
|
|
|
824
844
|
def _get_runtime_env(self):
|
|
825
845
|
# for runtime specific env var enrichment (before deploy)
|
|
826
846
|
runtime_env = {
|
|
827
|
-
|
|
847
|
+
mlrun.common.constants.MLRUN_ACTIVE_PROJECT: self.metadata.project
|
|
848
|
+
or mlconf.active_project,
|
|
828
849
|
}
|
|
829
850
|
if mlconf.httpdb.api_url:
|
|
830
851
|
runtime_env["MLRUN_DBPATH"] = mlconf.httpdb.api_url
|
|
@@ -960,24 +981,6 @@ class RemoteRuntime(KubeResource):
|
|
|
960
981
|
self._mock_server = None
|
|
961
982
|
|
|
962
983
|
if "://" not in path:
|
|
963
|
-
if not self.status.address:
|
|
964
|
-
# here we check that if default http trigger is disabled, function contains a custom http trigger
|
|
965
|
-
# Otherwise, the function is not invokable, so we raise an error
|
|
966
|
-
if (
|
|
967
|
-
not self._trigger_of_kind_exists(kind="http")
|
|
968
|
-
and self.spec.disable_default_http_trigger
|
|
969
|
-
):
|
|
970
|
-
raise mlrun.errors.MLRunPreconditionFailedError(
|
|
971
|
-
"Default http trigger creation is disabled and there is no any other custom http trigger, "
|
|
972
|
-
"so function can not be invoked via http. Either enable default http trigger creation or "
|
|
973
|
-
"create custom http trigger"
|
|
974
|
-
)
|
|
975
|
-
state, _, _ = self._get_state()
|
|
976
|
-
if state not in ["ready", "scaledToZero"]:
|
|
977
|
-
logger.warning(f"Function is in the {state} state")
|
|
978
|
-
if not self.status.address:
|
|
979
|
-
raise ValueError("no function address first run .deploy()")
|
|
980
|
-
|
|
981
984
|
path = self._resolve_invocation_url(path, force_external_address)
|
|
982
985
|
|
|
983
986
|
if headers is None:
|
|
@@ -1037,6 +1040,9 @@ class RemoteRuntime(KubeResource):
|
|
|
1037
1040
|
sidecar["image"] = image
|
|
1038
1041
|
|
|
1039
1042
|
ports = mlrun.utils.helpers.as_list(ports)
|
|
1043
|
+
if len(ports) > 1:
|
|
1044
|
+
mlrun.runtimes.nuclio.multiple_port_sidecar_is_supported()
|
|
1045
|
+
|
|
1040
1046
|
# according to RFC-6335, port name should be less than 15 characters,
|
|
1041
1047
|
# so we truncate it if needed and leave room for the index
|
|
1042
1048
|
port_name = name[:13].rstrip("-_") if len(name) > 13 else name
|
|
@@ -1217,19 +1223,54 @@ class RemoteRuntime(KubeResource):
|
|
|
1217
1223
|
# internal / external invocation urls is a nuclio >= 1.6.x feature
|
|
1218
1224
|
# try to infer the invocation url from the internal and if not exists, use external.
|
|
1219
1225
|
# $$$$ we do not want to use the external invocation url (e.g.: ingress, nodePort, etc.)
|
|
1226
|
+
|
|
1227
|
+
# if none of urls is set, function was deployed with watch=False
|
|
1228
|
+
# and status wasn't fetched with Nuclio
|
|
1229
|
+
# _get_state fetches the state and updates url
|
|
1230
|
+
if (
|
|
1231
|
+
not self.status.address
|
|
1232
|
+
and not self.status.internal_invocation_urls
|
|
1233
|
+
and not self.status.external_invocation_urls
|
|
1234
|
+
):
|
|
1235
|
+
state, _, _ = self._get_state()
|
|
1236
|
+
if state not in ["ready", "scaledToZero"]:
|
|
1237
|
+
logger.warning(f"Function is in the {state} state")
|
|
1238
|
+
|
|
1239
|
+
# prefer internal invocation url if running inside k8s cluster
|
|
1220
1240
|
if (
|
|
1221
1241
|
not force_external_address
|
|
1222
1242
|
and self.status.internal_invocation_urls
|
|
1223
1243
|
and mlrun.k8s_utils.is_running_inside_kubernetes_cluster()
|
|
1224
1244
|
):
|
|
1225
|
-
|
|
1245
|
+
url = mlrun.utils.helpers.join_urls(
|
|
1226
1246
|
f"http://{self.status.internal_invocation_urls[0]}", path
|
|
1227
1247
|
)
|
|
1248
|
+
logger.debug(
|
|
1249
|
+
f"Using internal invocation url {url}. Make sure you have network access to the k8s cluster. "
|
|
1250
|
+
f"Otherwise, set force_external_address to True"
|
|
1251
|
+
)
|
|
1252
|
+
return url
|
|
1228
1253
|
|
|
1229
1254
|
if self.status.external_invocation_urls:
|
|
1230
1255
|
return mlrun.utils.helpers.join_urls(
|
|
1231
1256
|
f"http://{self.status.external_invocation_urls[0]}", path
|
|
1232
1257
|
)
|
|
1258
|
+
|
|
1259
|
+
if not self.status.address:
|
|
1260
|
+
# if there is no address
|
|
1261
|
+
# here we check that if default http trigger is disabled, function contains a custom http trigger
|
|
1262
|
+
# Otherwise, the function is not invokable, so we raise an error
|
|
1263
|
+
if (
|
|
1264
|
+
not self._trigger_of_kind_exists(kind="http")
|
|
1265
|
+
and self.spec.disable_default_http_trigger
|
|
1266
|
+
):
|
|
1267
|
+
raise mlrun.errors.MLRunPreconditionFailedError(
|
|
1268
|
+
"Default http trigger creation is disabled and there is no any other custom http trigger, "
|
|
1269
|
+
"so function can not be invoked via http. Either enable default http trigger creation or "
|
|
1270
|
+
"create custom http trigger"
|
|
1271
|
+
)
|
|
1272
|
+
else:
|
|
1273
|
+
raise ValueError("no function address first run .deploy()")
|
|
1233
1274
|
else:
|
|
1234
1275
|
return mlrun.utils.helpers.join_urls(f"http://{self.status.address}", path)
|
|
1235
1276
|
|
|
@@ -1283,6 +1324,8 @@ class RemoteRuntime(KubeResource):
|
|
|
1283
1324
|
def get_url(
|
|
1284
1325
|
self,
|
|
1285
1326
|
force_external_address: bool = False,
|
|
1327
|
+
# leaving auth_info for BC
|
|
1328
|
+
# TODO: remove in 1.12.0
|
|
1286
1329
|
auth_info: AuthInfo = None,
|
|
1287
1330
|
):
|
|
1288
1331
|
"""
|
|
@@ -1293,13 +1336,12 @@ class RemoteRuntime(KubeResource):
|
|
|
1293
1336
|
|
|
1294
1337
|
:return: returns function's url
|
|
1295
1338
|
"""
|
|
1296
|
-
if
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1339
|
+
if auth_info:
|
|
1340
|
+
warnings.warn(
|
|
1341
|
+
"'auth_info' is deprecated in 1.10.0 and will be removed in 1.12.0.",
|
|
1342
|
+
# TODO: Remove this in 1.12.0
|
|
1343
|
+
FutureWarning,
|
|
1344
|
+
)
|
|
1303
1345
|
return self._resolve_invocation_url("", force_external_address)
|
|
1304
1346
|
|
|
1305
1347
|
@staticmethod
|
|
@@ -1450,3 +1492,10 @@ def enrich_nuclio_function_from_headers(
|
|
|
1450
1492
|
else []
|
|
1451
1493
|
)
|
|
1452
1494
|
func.status.container_image = headers.get("x-mlrun-container-image", "")
|
|
1495
|
+
|
|
1496
|
+
|
|
1497
|
+
@min_nuclio_versions("1.14.14")
|
|
1498
|
+
def multiple_port_sidecar_is_supported():
|
|
1499
|
+
# multiple ports are supported from nuclio version 1.14.14
|
|
1500
|
+
# this method exists only for running the min_nuclio_versions decorator
|
|
1501
|
+
return True
|
mlrun/runtimes/nuclio/serving.py
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import json
|
|
15
15
|
import os
|
|
16
16
|
import warnings
|
|
17
|
+
from base64 import b64decode
|
|
17
18
|
from copy import deepcopy
|
|
18
19
|
from typing import Optional, Union
|
|
19
20
|
|
|
@@ -22,6 +23,8 @@ from nuclio import KafkaTrigger
|
|
|
22
23
|
|
|
23
24
|
import mlrun
|
|
24
25
|
import mlrun.common.schemas as schemas
|
|
26
|
+
import mlrun.common.secrets
|
|
27
|
+
import mlrun.datastore.datastore_profile as ds_profile
|
|
25
28
|
from mlrun.datastore import get_kafka_brokers_from_dict, parse_kafka_url
|
|
26
29
|
from mlrun.model import ObjectList
|
|
27
30
|
from mlrun.runtimes.function_reference import FunctionReference
|
|
@@ -633,7 +636,12 @@ class ServingRuntime(RemoteRuntime):
|
|
|
633
636
|
|
|
634
637
|
:returns: The Runtime (function) object
|
|
635
638
|
"""
|
|
636
|
-
|
|
639
|
+
if kind == "azure_vault" and isinstance(source, dict):
|
|
640
|
+
candidate_secret_name = (source.get("k8s_secret") or "").strip()
|
|
641
|
+
if candidate_secret_name:
|
|
642
|
+
mlrun.common.secrets.validate_not_forbidden_secret(
|
|
643
|
+
candidate_secret_name
|
|
644
|
+
)
|
|
637
645
|
if kind == "vault" and isinstance(source, list):
|
|
638
646
|
source = {"project": self.metadata.project, "secrets": source}
|
|
639
647
|
|
|
@@ -657,6 +665,7 @@ class ServingRuntime(RemoteRuntime):
|
|
|
657
665
|
:param builder_env: env vars dict for source archive config/credentials e.g. builder_env={"GIT_TOKEN": token}
|
|
658
666
|
:param force_build: set True for force building the image
|
|
659
667
|
"""
|
|
668
|
+
|
|
660
669
|
load_mode = self.spec.load_mode
|
|
661
670
|
if load_mode and load_mode not in ["sync", "async"]:
|
|
662
671
|
raise ValueError(f"illegal model loading mode {load_mode}")
|
|
@@ -677,6 +686,21 @@ class ServingRuntime(RemoteRuntime):
|
|
|
677
686
|
f"function {function} is used in steps and is not defined, "
|
|
678
687
|
"use the .add_child_function() to specify child function attributes"
|
|
679
688
|
)
|
|
689
|
+
if (
|
|
690
|
+
isinstance(self.spec.graph, RootFlowStep)
|
|
691
|
+
and any(
|
|
692
|
+
isinstance(step_type, mlrun.serving.states.ModelRunnerStep)
|
|
693
|
+
for step_type in self.spec.graph.steps.values()
|
|
694
|
+
)
|
|
695
|
+
and self.spec.build.functionSourceCode
|
|
696
|
+
):
|
|
697
|
+
# Add import for LLModel
|
|
698
|
+
decoded_code = b64decode(self.spec.build.functionSourceCode).decode("utf-8")
|
|
699
|
+
import_llmodel_code = "\nfrom mlrun.serving.states import LLModel\n"
|
|
700
|
+
if import_llmodel_code not in decoded_code:
|
|
701
|
+
decoded_code += import_llmodel_code
|
|
702
|
+
encoded_code = mlrun.utils.helpers.encode_user_code(decoded_code)
|
|
703
|
+
self.spec.build.functionSourceCode = encoded_code
|
|
680
704
|
|
|
681
705
|
# Handle secret processing before handling child functions, since secrets are transferred to them
|
|
682
706
|
if self.spec.secret_sources:
|
|
@@ -720,6 +744,7 @@ class ServingRuntime(RemoteRuntime):
|
|
|
720
744
|
"track_models": self.spec.track_models,
|
|
721
745
|
"default_content_type": self.spec.default_content_type,
|
|
722
746
|
"model_endpoint_creation_task_name": self.spec.model_endpoint_creation_task_name,
|
|
747
|
+
# TODO: find another way to pass this (needed for local run)
|
|
723
748
|
"filename": getattr(self.spec, "filename", None),
|
|
724
749
|
}
|
|
725
750
|
|
|
@@ -739,6 +764,7 @@ class ServingRuntime(RemoteRuntime):
|
|
|
739
764
|
current_function="*",
|
|
740
765
|
track_models=False,
|
|
741
766
|
workdir=None,
|
|
767
|
+
stream_profile: Optional[ds_profile.DatastoreProfile] = None,
|
|
742
768
|
**kwargs,
|
|
743
769
|
) -> GraphServer:
|
|
744
770
|
"""create mock server object for local testing/emulation
|
|
@@ -747,6 +773,7 @@ class ServingRuntime(RemoteRuntime):
|
|
|
747
773
|
:param current_function: specify if you want to simulate a child function, * for all functions
|
|
748
774
|
:param track_models: allow model tracking (disabled by default in the mock server)
|
|
749
775
|
:param workdir: working directory to locate the source code (if not the current one)
|
|
776
|
+
:param stream_profile: stream profile to use for the mock server output stream.
|
|
750
777
|
"""
|
|
751
778
|
|
|
752
779
|
# set the namespaces/modules to look for the steps code in
|
|
@@ -786,19 +813,16 @@ class ServingRuntime(RemoteRuntime):
|
|
|
786
813
|
logger=logger,
|
|
787
814
|
is_mock=True,
|
|
788
815
|
monitoring_mock=self.spec.track_models,
|
|
816
|
+
stream_profile=stream_profile,
|
|
789
817
|
)
|
|
790
818
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
server.
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
self.spec.track_models,
|
|
799
|
-
server.context,
|
|
800
|
-
self.spec,
|
|
801
|
-
)
|
|
819
|
+
server.graph = add_system_steps_to_graph(
|
|
820
|
+
server.project,
|
|
821
|
+
server.graph,
|
|
822
|
+
self.spec.track_models,
|
|
823
|
+
server.context,
|
|
824
|
+
self.spec,
|
|
825
|
+
)
|
|
802
826
|
|
|
803
827
|
if workdir:
|
|
804
828
|
os.chdir(old_workdir)
|
|
@@ -838,8 +862,20 @@ class ServingRuntime(RemoteRuntime):
|
|
|
838
862
|
)
|
|
839
863
|
self._mock_server = self.to_mock_server()
|
|
840
864
|
|
|
841
|
-
def to_job(self) -> KubejobRuntime:
|
|
842
|
-
"""Convert this ServingRuntime to a KubejobRuntime, so that the graph can be run as a standalone job.
|
|
865
|
+
def to_job(self, func_name: Optional[str] = None) -> KubejobRuntime:
|
|
866
|
+
"""Convert this ServingRuntime to a KubejobRuntime, so that the graph can be run as a standalone job.
|
|
867
|
+
|
|
868
|
+
Args:
|
|
869
|
+
func_name: Optional custom name for the job function. If not provided, automatically
|
|
870
|
+
appends '-batch' suffix to the serving function name to prevent database collision.
|
|
871
|
+
|
|
872
|
+
Returns:
|
|
873
|
+
KubejobRuntime configured to execute the serving graph as a batch job.
|
|
874
|
+
|
|
875
|
+
Note:
|
|
876
|
+
The job will have a different name than the serving function to prevent database collision.
|
|
877
|
+
The original serving function remains unchanged and can still be invoked after running the job.
|
|
878
|
+
"""
|
|
843
879
|
if self.spec.function_refs:
|
|
844
880
|
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
845
881
|
f"Cannot convert function '{self.metadata.name}' to a job because it has child functions"
|
|
@@ -858,6 +894,7 @@ class ServingRuntime(RemoteRuntime):
|
|
|
858
894
|
description=self.spec.description,
|
|
859
895
|
workdir=self.spec.workdir,
|
|
860
896
|
image_pull_secret=self.spec.image_pull_secret,
|
|
897
|
+
build=self.spec.build,
|
|
861
898
|
node_name=self.spec.node_name,
|
|
862
899
|
node_selector=self.spec.node_selector,
|
|
863
900
|
affinity=self.spec.affinity,
|
|
@@ -868,9 +905,54 @@ class ServingRuntime(RemoteRuntime):
|
|
|
868
905
|
security_context=self.spec.security_context,
|
|
869
906
|
state_thresholds=self.spec.state_thresholds,
|
|
870
907
|
serving_spec=self._get_serving_spec(),
|
|
908
|
+
track_models=self.spec.track_models,
|
|
909
|
+
parameters=self.spec.parameters,
|
|
910
|
+
graph=self.spec.graph,
|
|
871
911
|
)
|
|
912
|
+
|
|
913
|
+
job_metadata = deepcopy(self.metadata)
|
|
914
|
+
original_name = job_metadata.name
|
|
915
|
+
|
|
916
|
+
if func_name:
|
|
917
|
+
# User provided explicit job name
|
|
918
|
+
job_metadata.name = func_name
|
|
919
|
+
logger.debug(
|
|
920
|
+
"Creating job from serving function with custom name",
|
|
921
|
+
new_name=func_name,
|
|
922
|
+
)
|
|
923
|
+
else:
|
|
924
|
+
job_metadata.name, was_renamed, suffix = (
|
|
925
|
+
mlrun.utils.helpers.ensure_batch_job_suffix(job_metadata.name)
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
# Check if the resulting name exceeds Kubernetes length limit
|
|
929
|
+
if (
|
|
930
|
+
len(job_metadata.name)
|
|
931
|
+
> mlrun.common.constants.K8S_DNS_1123_LABEL_MAX_LENGTH
|
|
932
|
+
):
|
|
933
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
934
|
+
f"Cannot convert serving function '{original_name}' to batch job: "
|
|
935
|
+
f"the resulting name '{job_metadata.name}' ({len(job_metadata.name)} characters) "
|
|
936
|
+
f"exceeds Kubernetes limit of {mlrun.common.constants.K8S_DNS_1123_LABEL_MAX_LENGTH} characters. "
|
|
937
|
+
f"Please provide a custom name via the func_name parameter, "
|
|
938
|
+
f"with at most {mlrun.common.constants.K8S_DNS_1123_LABEL_MAX_LENGTH} characters."
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
if was_renamed:
|
|
942
|
+
logger.info(
|
|
943
|
+
"Creating job from serving function (auto-appended suffix to prevent collision)",
|
|
944
|
+
new_name=job_metadata.name,
|
|
945
|
+
suffix=suffix,
|
|
946
|
+
)
|
|
947
|
+
else:
|
|
948
|
+
logger.debug(
|
|
949
|
+
"Creating job from serving function (name already has suffix)",
|
|
950
|
+
name=original_name,
|
|
951
|
+
suffix=suffix,
|
|
952
|
+
)
|
|
953
|
+
|
|
872
954
|
job = KubejobRuntime(
|
|
873
955
|
spec=spec,
|
|
874
|
-
metadata=
|
|
956
|
+
metadata=job_metadata,
|
|
875
957
|
)
|
|
876
958
|
return job
|