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.

Files changed (107) hide show
  1. mlrun/__init__.py +22 -2
  2. mlrun/artifacts/base.py +0 -31
  3. mlrun/artifacts/document.py +6 -1
  4. mlrun/artifacts/llm_prompt.py +123 -25
  5. mlrun/artifacts/manager.py +0 -5
  6. mlrun/artifacts/model.py +3 -3
  7. mlrun/common/constants.py +10 -1
  8. mlrun/common/formatters/artifact.py +1 -0
  9. mlrun/common/model_monitoring/helpers.py +86 -0
  10. mlrun/common/schemas/__init__.py +3 -0
  11. mlrun/common/schemas/auth.py +2 -0
  12. mlrun/common/schemas/function.py +10 -0
  13. mlrun/common/schemas/hub.py +30 -18
  14. mlrun/common/schemas/model_monitoring/__init__.py +3 -0
  15. mlrun/common/schemas/model_monitoring/constants.py +30 -6
  16. mlrun/common/schemas/model_monitoring/functions.py +14 -5
  17. mlrun/common/schemas/model_monitoring/model_endpoints.py +21 -0
  18. mlrun/common/schemas/pipeline.py +1 -1
  19. mlrun/common/schemas/serving.py +3 -0
  20. mlrun/common/schemas/workflow.py +3 -1
  21. mlrun/common/secrets.py +22 -1
  22. mlrun/config.py +33 -11
  23. mlrun/datastore/__init__.py +11 -3
  24. mlrun/datastore/azure_blob.py +162 -47
  25. mlrun/datastore/datastore.py +9 -4
  26. mlrun/datastore/datastore_profile.py +61 -5
  27. mlrun/datastore/model_provider/huggingface_provider.py +363 -0
  28. mlrun/datastore/model_provider/mock_model_provider.py +87 -0
  29. mlrun/datastore/model_provider/model_provider.py +230 -65
  30. mlrun/datastore/model_provider/openai_provider.py +295 -42
  31. mlrun/datastore/s3.py +24 -2
  32. mlrun/datastore/storeytargets.py +2 -3
  33. mlrun/datastore/utils.py +15 -3
  34. mlrun/db/base.py +47 -19
  35. mlrun/db/httpdb.py +120 -56
  36. mlrun/db/nopdb.py +38 -10
  37. mlrun/execution.py +70 -19
  38. mlrun/hub/__init__.py +15 -0
  39. mlrun/hub/module.py +181 -0
  40. mlrun/k8s_utils.py +105 -16
  41. mlrun/launcher/base.py +13 -6
  42. mlrun/launcher/local.py +15 -0
  43. mlrun/model.py +24 -3
  44. mlrun/model_monitoring/__init__.py +1 -0
  45. mlrun/model_monitoring/api.py +66 -27
  46. mlrun/model_monitoring/applications/__init__.py +1 -1
  47. mlrun/model_monitoring/applications/base.py +509 -117
  48. mlrun/model_monitoring/applications/context.py +2 -4
  49. mlrun/model_monitoring/applications/results.py +4 -7
  50. mlrun/model_monitoring/controller.py +239 -101
  51. mlrun/model_monitoring/db/_schedules.py +116 -33
  52. mlrun/model_monitoring/db/_stats.py +4 -3
  53. mlrun/model_monitoring/db/tsdb/base.py +100 -9
  54. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +11 -6
  55. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +191 -50
  56. mlrun/model_monitoring/db/tsdb/tdengine/writer_graph_steps.py +51 -0
  57. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +17 -4
  58. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +259 -40
  59. mlrun/model_monitoring/helpers.py +54 -9
  60. mlrun/model_monitoring/stream_processing.py +45 -14
  61. mlrun/model_monitoring/writer.py +220 -1
  62. mlrun/platforms/__init__.py +3 -2
  63. mlrun/platforms/iguazio.py +7 -3
  64. mlrun/projects/operations.py +6 -1
  65. mlrun/projects/pipelines.py +46 -26
  66. mlrun/projects/project.py +166 -58
  67. mlrun/run.py +94 -17
  68. mlrun/runtimes/__init__.py +18 -0
  69. mlrun/runtimes/base.py +14 -6
  70. mlrun/runtimes/daskjob.py +7 -0
  71. mlrun/runtimes/local.py +5 -2
  72. mlrun/runtimes/mounts.py +20 -2
  73. mlrun/runtimes/mpijob/abstract.py +6 -0
  74. mlrun/runtimes/mpijob/v1.py +6 -0
  75. mlrun/runtimes/nuclio/__init__.py +1 -0
  76. mlrun/runtimes/nuclio/application/application.py +149 -17
  77. mlrun/runtimes/nuclio/function.py +76 -27
  78. mlrun/runtimes/nuclio/serving.py +97 -15
  79. mlrun/runtimes/pod.py +234 -21
  80. mlrun/runtimes/remotesparkjob.py +6 -0
  81. mlrun/runtimes/sparkjob/spark3job.py +6 -0
  82. mlrun/runtimes/utils.py +49 -11
  83. mlrun/secrets.py +54 -13
  84. mlrun/serving/__init__.py +2 -0
  85. mlrun/serving/remote.py +79 -6
  86. mlrun/serving/routers.py +23 -41
  87. mlrun/serving/server.py +320 -80
  88. mlrun/serving/states.py +725 -157
  89. mlrun/serving/steps.py +62 -0
  90. mlrun/serving/system_steps.py +200 -119
  91. mlrun/serving/v2_serving.py +9 -10
  92. mlrun/utils/helpers.py +288 -88
  93. mlrun/utils/logger.py +3 -1
  94. mlrun/utils/notifications/notification/base.py +18 -0
  95. mlrun/utils/notifications/notification/git.py +2 -4
  96. mlrun/utils/notifications/notification/slack.py +2 -4
  97. mlrun/utils/notifications/notification/webhook.py +2 -5
  98. mlrun/utils/notifications/notification_pusher.py +1 -1
  99. mlrun/utils/retryer.py +15 -2
  100. mlrun/utils/version/version.json +2 -2
  101. {mlrun-1.10.0rc13.dist-info → mlrun-1.10.0rc42.dist-info}/METADATA +45 -51
  102. {mlrun-1.10.0rc13.dist-info → mlrun-1.10.0rc42.dist-info}/RECORD +106 -101
  103. mlrun/api/schemas/__init__.py +0 -259
  104. {mlrun-1.10.0rc13.dist-info → mlrun-1.10.0rc42.dist-info}/WHEEL +0 -0
  105. {mlrun-1.10.0rc13.dist-info → mlrun-1.10.0rc42.dist-info}/entry_points.txt +0 -0
  106. {mlrun-1.10.0rc13.dist-info → mlrun-1.10.0rc42.dist-info}/licenses/LICENSE +0 -0
  107. {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 min_nuclio_versions
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
- if port < 0 or port > 65535:
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
- # This is a class method that accepts a function instance, so we pass self as the function instance
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
- ports = self.spec.internal_application_port if direct_port_access else []
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
- @staticmethod
686
- def _ensure_reverse_proxy_configurations(function: RemoteRuntime):
687
- if function.spec.build.functionSourceCode or function.status.container_image:
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=function.metadata.name,
825
+ name=self.metadata.name,
694
826
  handler=handler,
695
827
  )
696
- function.spec.function_handler = mlrun.utils.get_in(spec, "spec.handler")
697
- function.spec.build.functionSourceCode = mlrun.utils.get_in(
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
- function.spec.nuclio_runtime = mlrun.utils.get_in(spec, "spec.runtime")
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 function.spec.config.get(logger_sinks_key):
705
- function.set_config(
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.internal_application_port,
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
- "MLRUN_ACTIVE_PROJECT": self.metadata.project or mlconf.active_project,
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
- return mlrun.utils.helpers.join_urls(
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 not self.status.address:
1297
- state, _, _ = self._get_state(auth_info=auth_info)
1298
- if state != "ready" or not self.status.address:
1299
- raise ValueError(
1300
- "no function address or not ready, first run .deploy()"
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
@@ -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
- if (
792
- isinstance(self.spec.graph, RootFlowStep)
793
- and self.spec.graph.include_monitored_step()
794
- ):
795
- server.graph = add_system_steps_to_graph(
796
- server.project,
797
- server.graph,
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=self.metadata,
956
+ metadata=job_metadata,
875
957
  )
876
958
  return job