mlrun 1.8.0rc27__py3-none-any.whl → 1.8.0rc29__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.

@@ -12,6 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import concurrent.futures
15
16
  import datetime
16
17
  import json
17
18
  import os
@@ -138,7 +139,9 @@ class _BatchWindow:
138
139
 
139
140
 
140
141
  class _BatchWindowGenerator(AbstractContextManager):
141
- def __init__(self, project: str, endpoint_id: str, window_length: int) -> None:
142
+ def __init__(
143
+ self, project: str, endpoint_id: str, window_length: Optional[int] = None
144
+ ) -> None:
142
145
  """
143
146
  Initialize a batch window generator object that generates batch window objects
144
147
  for the monitoring functions.
@@ -165,6 +168,12 @@ class _BatchWindowGenerator(AbstractContextManager):
165
168
  exc_type=exc_type, exc_value=exc_value, traceback=traceback
166
169
  )
167
170
 
171
+ def get_application_list(self) -> set[str]:
172
+ return self._schedules_file.get_application_list()
173
+
174
+ def get_min_last_analyzed(self) -> Optional[int]:
175
+ return self._schedules_file.get_min_timestamp()
176
+
168
177
  @classmethod
169
178
  def _get_last_updated_time(
170
179
  cls, last_request: datetime.datetime, not_batch_endpoint: bool
@@ -234,8 +243,7 @@ class MonitoringApplicationController:
234
243
  def __init__(self) -> None:
235
244
  """Initialize Monitoring Application Controller"""
236
245
  self.project = cast(str, mlrun.mlconf.default_project)
237
- self.project_obj = mlrun.load_project(name=self.project, url=self.project)
238
-
246
+ self.project_obj = mlrun.get_run_db().get_project(name=self.project)
239
247
  logger.debug(f"Initializing {self.__class__.__name__}", project=self.project)
240
248
 
241
249
  self._window_length = _get_window_length()
@@ -255,8 +263,10 @@ class MonitoringApplicationController:
255
263
  return access_key
256
264
 
257
265
  @staticmethod
258
- def _should_monitor_endpoint(endpoint: mlrun.common.schemas.ModelEndpoint) -> bool:
259
- return (
266
+ def _should_monitor_endpoint(
267
+ endpoint: mlrun.common.schemas.ModelEndpoint, application_names: set
268
+ ) -> bool:
269
+ if (
260
270
  # Is the model endpoint monitored?
261
271
  endpoint.status.monitoring_mode == mm_constants.ModelMonitoringMode.enabled
262
272
  # Was the model endpoint called? I.e., are the first and last requests nonempty?
@@ -265,7 +275,40 @@ class MonitoringApplicationController:
265
275
  # Is the model endpoint not a router endpoint? Router endpoint has no feature stats
266
276
  and endpoint.metadata.endpoint_type.value
267
277
  != mm_constants.EndpointType.ROUTER.value
268
- )
278
+ ):
279
+ with _BatchWindowGenerator(
280
+ project=endpoint.metadata.project,
281
+ endpoint_id=endpoint.metadata.uid,
282
+ ) as batch_window_generator:
283
+ if application_names != batch_window_generator.get_application_list():
284
+ return True
285
+ elif (
286
+ not batch_window_generator.get_min_last_analyzed()
287
+ or batch_window_generator.get_min_last_analyzed()
288
+ <= int(endpoint.status.last_request.timestamp())
289
+ ):
290
+ return True
291
+ else:
292
+ logger.info(
293
+ "All the possible intervals were already analyzed, didn't push regular event",
294
+ endpoint_id=endpoint.metadata.uid,
295
+ last_analyzed=datetime.datetime.fromtimestamp(
296
+ batch_window_generator.get_min_last_analyzed(),
297
+ tz=datetime.timezone.utc,
298
+ ),
299
+ last_request=endpoint.status.last_request,
300
+ )
301
+ else:
302
+ logger.info(
303
+ "Should not monitor model endpoint, didn't push regular event",
304
+ endpoint_id=endpoint.metadata.uid,
305
+ endpoint_name=endpoint.metadata.name,
306
+ last_request=endpoint.status.last_request,
307
+ first_request=endpoint.status.first_request,
308
+ endpoint_type=endpoint.metadata.endpoint_type,
309
+ feature_set_uri=endpoint.spec.monitoring_feature_set_uri,
310
+ )
311
+ return False
269
312
 
270
313
  def run(self, event: nuclio_sdk.Event) -> None:
271
314
  """
@@ -314,7 +357,7 @@ class MonitoringApplicationController:
314
357
  )
315
358
  m_fs = fstore.get_feature_set(event[ControllerEvent.FEATURE_SET_URI])
316
359
  logger.info(
317
- "Starting analyzing for:", timestamp=event[ControllerEvent.TIMESTAMP]
360
+ "Starting analyzing for", timestamp=event[ControllerEvent.TIMESTAMP]
318
361
  )
319
362
  last_stream_timestamp = datetime.datetime.fromisoformat(
320
363
  event[ControllerEvent.TIMESTAMP]
@@ -370,7 +413,7 @@ class MonitoringApplicationController:
370
413
  current_time = mlrun.utils.datetime_now()
371
414
  if (
372
415
  current_time.timestamp()
373
- - batch_window_generator.batch_window._get_last_analyzed()
416
+ - batch_window_generator.get_min_last_analyzed()
374
417
  >= datetime.timedelta(minutes=base_period).total_seconds()
375
418
  and event[ControllerEvent.KIND] != ControllerEventKind.NOP_EVENT
376
419
  ):
@@ -399,6 +442,9 @@ class MonitoringApplicationController:
399
442
  event=event,
400
443
  endpoint_id=endpoint_id,
401
444
  )
445
+ logger.info(
446
+ "Finish analyze for", timestamp=event[ControllerEvent.TIMESTAMP]
447
+ )
402
448
 
403
449
  except Exception:
404
450
  logger.exception(
@@ -455,17 +501,14 @@ class MonitoringApplicationController:
455
501
  [data]
456
502
  )
457
503
 
458
- def push_regular_event_to_controller_stream(self, event: nuclio_sdk.Event) -> None:
504
+ def push_regular_event_to_controller_stream(self) -> None:
459
505
  """
460
506
  pushes a regular event to the controller stream.
461
507
  :param event: the nuclio trigger event
462
508
  """
463
509
  logger.info("Starting monitoring controller chief")
464
510
  applications_names = []
465
- db = mlrun.get_run_db()
466
- endpoints = db.list_model_endpoints(
467
- project=self.project, tsdb_metrics=True
468
- ).endpoints
511
+ endpoints = self.project_obj.list_model_endpoints(tsdb_metrics=True).endpoints
469
512
  if not endpoints:
470
513
  logger.info("No model endpoints found", project=self.project)
471
514
  return
@@ -505,48 +548,59 @@ class MonitoringApplicationController:
505
548
  // 60
506
549
  ),
507
550
  }
508
- for endpoint in endpoints:
509
- if self._should_monitor_endpoint(endpoint):
510
- logger.info(
511
- "Regular event is being pushed to controller stream for model endpoint",
512
- endpoint_id=endpoint.metadata.uid,
513
- endpoint_name=endpoint.metadata.name,
514
- timestamp=endpoint.status.last_request.isoformat(
515
- sep=" ", timespec="microseconds"
516
- ),
517
- first_request=endpoint.status.first_request.isoformat(
518
- sep=" ", timespec="microseconds"
519
- ),
520
- endpoint_type=endpoint.metadata.endpoint_type,
521
- feature_set_uri=endpoint.spec.monitoring_feature_set_uri,
522
- endpoint_policy=json.dumps(policy),
523
- )
524
- self.push_to_controller_stream(
525
- kind=mm_constants.ControllerEventKind.REGULAR_EVENT,
526
- project=self.project,
527
- endpoint_id=endpoint.metadata.uid,
528
- endpoint_name=endpoint.metadata.name,
529
- stream_access_key=self.v3io_access_key,
530
- timestamp=endpoint.status.last_request.isoformat(
531
- sep=" ", timespec="microseconds"
532
- ),
533
- first_request=endpoint.status.first_request.isoformat(
534
- sep=" ", timespec="microseconds"
535
- ),
536
- endpoint_type=endpoint.metadata.endpoint_type,
537
- feature_set_uri=endpoint.spec.monitoring_feature_set_uri,
538
- endpoint_policy=policy,
539
- )
540
- else:
541
- logger.info(
542
- "Should not monitor model endpoint, didn't push regular event",
543
- endpoint_id=endpoint.metadata.uid,
544
- endpoint_name=endpoint.metadata.name,
545
- timestamp=endpoint.status.last_request,
546
- first_request=endpoint.status.first_request,
547
- endpoint_type=endpoint.metadata.endpoint_type,
548
- feature_set_uri=endpoint.spec.monitoring_feature_set_uri,
551
+ with concurrent.futures.ThreadPoolExecutor(
552
+ max_workers=min(len(endpoints), 10)
553
+ ) as pool:
554
+ for endpoint in endpoints:
555
+ pool.submit(
556
+ MonitoringApplicationController.endpoint_to_regular_event,
557
+ endpoint,
558
+ policy,
559
+ set(applications_names),
560
+ self.v3io_access_key,
549
561
  )
562
+ logger.info("Finishing monitoring controller chief")
563
+
564
+ @staticmethod
565
+ def endpoint_to_regular_event(
566
+ endpoint: mlrun.common.schemas.ModelEndpoint,
567
+ policy: dict,
568
+ applications_names: set,
569
+ v3io_access_key: str,
570
+ ) -> None:
571
+ if MonitoringApplicationController._should_monitor_endpoint(
572
+ endpoint, set(applications_names)
573
+ ):
574
+ logger.info(
575
+ "Regular event is being pushed to controller stream for model endpoint",
576
+ endpoint_id=endpoint.metadata.uid,
577
+ endpoint_name=endpoint.metadata.name,
578
+ timestamp=endpoint.status.last_request.isoformat(
579
+ sep=" ", timespec="microseconds"
580
+ ),
581
+ first_request=endpoint.status.first_request.isoformat(
582
+ sep=" ", timespec="microseconds"
583
+ ),
584
+ endpoint_type=endpoint.metadata.endpoint_type,
585
+ feature_set_uri=endpoint.spec.monitoring_feature_set_uri,
586
+ endpoint_policy=json.dumps(policy),
587
+ )
588
+ MonitoringApplicationController.push_to_controller_stream(
589
+ kind=mm_constants.ControllerEventKind.REGULAR_EVENT,
590
+ project=endpoint.metadata.project,
591
+ endpoint_id=endpoint.metadata.uid,
592
+ endpoint_name=endpoint.metadata.name,
593
+ stream_access_key=v3io_access_key,
594
+ timestamp=endpoint.status.last_request.isoformat(
595
+ sep=" ", timespec="microseconds"
596
+ ),
597
+ first_request=endpoint.status.first_request.isoformat(
598
+ sep=" ", timespec="microseconds"
599
+ ),
600
+ endpoint_type=endpoint.metadata.endpoint_type.value,
601
+ feature_set_uri=endpoint.spec.monitoring_feature_set_uri,
602
+ endpoint_policy=policy,
603
+ )
550
604
 
551
605
  @staticmethod
552
606
  def push_to_controller_stream(
@@ -557,7 +611,7 @@ class MonitoringApplicationController:
557
611
  stream_access_key: str,
558
612
  timestamp: str,
559
613
  first_request: str,
560
- endpoint_type: str,
614
+ endpoint_type: int,
561
615
  feature_set_uri: str,
562
616
  endpoint_policy: dict[str, Any],
563
617
  ) -> None:
@@ -633,7 +687,13 @@ def handler(context: nuclio_sdk.Context, event: nuclio_sdk.Event) -> None:
633
687
 
634
688
  if event.trigger.kind == "http":
635
689
  # Runs controller chief:
636
- MonitoringApplicationController().push_regular_event_to_controller_stream(event)
690
+ context.user_data.monitor_app_controller.push_regular_event_to_controller_stream()
637
691
  else:
638
692
  # Runs controller worker:
639
- MonitoringApplicationController().run(event=event)
693
+ context.user_data.monitor_app_controller.run(event)
694
+
695
+
696
+ def init_context(context):
697
+ monitor_app_controller = MonitoringApplicationController()
698
+ setattr(context.user_data, "monitor_app_controller", monitor_app_controller)
699
+ context.logger.info("Monitoring application controller initialized")
@@ -140,6 +140,14 @@ class ModelMonitoringSchedulesFile(AbstractContextManager):
140
140
  self._check_open_schedules()
141
141
  self._schedules[application] = timestamp
142
142
 
143
+ def get_application_list(self) -> set[str]:
144
+ self._check_open_schedules()
145
+ return set(self._schedules.keys())
146
+
147
+ def get_min_timestamp(self) -> Optional[int]:
148
+ self._check_open_schedules()
149
+ return min(self._schedules.values(), default=None)
150
+
143
151
 
144
152
  def delete_model_monitoring_schedules_folder(project: str) -> None:
145
153
  """Delete the model monitoring schedules folder of the project"""
@@ -75,10 +75,11 @@ def get_tsdb_connector(
75
75
  :param secret_provider: An optional secret provider to get the connection string secret.
76
76
  :param profile: An optional profile to initialize the TSDB connector from.
77
77
 
78
- :return: `TSDBConnector` object. The main goal of this object is to handle different operations on the
78
+ :return: ``TSDBConnector`` object. The main goal of this object is to handle different operations on the
79
79
  TSDB connector such as updating drift metrics or write application record result.
80
- :raise: `MLRunInvalidMMStoreTypeError` if the user didn't provide TSDB connection
81
- or the provided TSDB connection is invalid.
80
+ :raise: ``MLRunNotFoundError`` if the user didn't set the TSDB datastore profile and didn't provide it through
81
+ the ``profile`` parameter.
82
+ :raise: ``MLRunInvalidMMStoreTypeError`` if the TSDB datastore profile is of an invalid type.
82
83
  """
83
84
  profile = profile or mlrun.model_monitoring.helpers._get_tsdb_profile(
84
85
  project=project, secret_provider=secret_provider
@@ -93,9 +94,15 @@ def get_tsdb_connector(
93
94
  tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.TDEngine
94
95
  kwargs["connection_string"] = profile.dsn()
95
96
  else:
97
+ extra_message = (
98
+ ""
99
+ if profile
100
+ else " by using `project.set_model_monitoring_credentials` API"
101
+ )
96
102
  raise mlrun.errors.MLRunInvalidMMStoreTypeError(
97
- "You must provide a valid tsdb store connection by using "
98
- "set_model_monitoring_credentials API."
103
+ "You must provide a valid TSDB datastore profile"
104
+ f"{extra_message}. "
105
+ f"Found an unexpected profile of class: {type(profile)}"
99
106
  )
100
107
 
101
108
  # Get connector type value from ObjectTSDBFactory enum class
@@ -378,7 +378,9 @@ class ProcessEndpointEvent(mlrun.feature_store.steps.MapClass):
378
378
  def do(self, full_event):
379
379
  event = full_event.body
380
380
  if event.get(ControllerEvent.KIND, "") == ControllerEventKind.NOP_EVENT:
381
- logger.info("Skipped nop event inside of ProcessEndpointEvent", event=event)
381
+ logger.debug(
382
+ "Skipped nop event inside of ProcessEndpointEvent", event=event
383
+ )
382
384
  return storey.Event(body=[event])
383
385
  # Getting model version and function uri from event
384
386
  # and use them for retrieving the endpoint_id
@@ -637,7 +639,6 @@ class MapFeatureNames(mlrun.feature_store.steps.MapClass):
637
639
 
638
640
  def do(self, event: dict):
639
641
  if event.get(ControllerEvent.KIND, "") == ControllerEventKind.NOP_EVENT:
640
- logger.info("Skipped nop event inside of MapFeatureNames", event=event)
641
642
  return event
642
643
  endpoint_id = event[EventFieldType.ENDPOINT_ID]
643
644
 
mlrun/projects/project.py CHANGED
@@ -31,6 +31,7 @@ from os import environ, makedirs, path
31
31
  from typing import Callable, Optional, Union, cast
32
32
  from urllib.parse import urlparse
33
33
 
34
+ import deprecated
34
35
  import dotenv
35
36
  import git
36
37
  import git.exc
@@ -1948,7 +1949,8 @@ class MlrunProject(ModelObj):
1948
1949
  kwargs={"extract_images": True}
1949
1950
  )
1950
1951
  :param upload: Whether to upload the artifact
1951
- :param labels: Key-value labels
1952
+ :param labels: Key-value labels. A 'source' label is automatically added using either
1953
+ local_path or target_path to facilitate easier document searching.
1952
1954
  :param target_path: Target file path
1953
1955
  :param kwargs: Additional keyword arguments
1954
1956
  :return: DocumentArtifact object
@@ -1978,13 +1980,24 @@ class MlrunProject(ModelObj):
1978
1980
  "The document loader is configured to not support downloads but the upload flag is set to True."
1979
1981
  "Either set loader.download_object=True or set upload=False"
1980
1982
  )
1983
+ original_source = local_path or target_path
1981
1984
  doc_artifact = DocumentArtifact(
1982
1985
  key=key,
1983
- original_source=local_path or target_path,
1986
+ original_source=original_source,
1984
1987
  document_loader_spec=document_loader_spec,
1985
1988
  collections=kwargs.pop("collections", None),
1986
1989
  **kwargs,
1987
1990
  )
1991
+
1992
+ # limit label to a max of 255 characters (for db reasons)
1993
+ max_length = 255
1994
+ labels = labels or {}
1995
+ labels["source"] = (
1996
+ original_source[: max_length - 3] + "..."
1997
+ if len(original_source) > max_length
1998
+ else original_source
1999
+ )
2000
+
1988
2001
  return self.log_artifact(
1989
2002
  item=doc_artifact,
1990
2003
  tag=tag,
@@ -2406,7 +2419,6 @@ class MlrunProject(ModelObj):
2406
2419
  *,
2407
2420
  deploy_histogram_data_drift_app: bool = True,
2408
2421
  wait_for_deployment: bool = False,
2409
- rebuild_images: bool = False,
2410
2422
  fetch_credentials_from_sys_config: bool = False,
2411
2423
  ) -> None:
2412
2424
  """
@@ -2428,7 +2440,6 @@ class MlrunProject(ModelObj):
2428
2440
  :param wait_for_deployment: If true, return only after the deployment is done on the backend.
2429
2441
  Otherwise, deploy the model monitoring infrastructure on the
2430
2442
  background, including the histogram data drift app if selected.
2431
- :param rebuild_images: If true, force rebuild of model monitoring infrastructure images.
2432
2443
  :param fetch_credentials_from_sys_config: If true, fetch the credentials from the system configuration.
2433
2444
  """
2434
2445
  if default_controller_image != "mlrun/mlrun":
@@ -2451,7 +2462,6 @@ class MlrunProject(ModelObj):
2451
2462
  image=image,
2452
2463
  base_period=base_period,
2453
2464
  deploy_histogram_data_drift_app=deploy_histogram_data_drift_app,
2454
- rebuild_images=rebuild_images,
2455
2465
  fetch_credentials_from_sys_config=fetch_credentials_from_sys_config,
2456
2466
  )
2457
2467
 
@@ -2839,6 +2849,13 @@ class MlrunProject(ModelObj):
2839
2849
 
2840
2850
  self.spec.set_function(name, function_object, func)
2841
2851
 
2852
+ # TODO: Remove this in 1.10.0
2853
+ @deprecated.deprecated(
2854
+ version="1.8.0",
2855
+ reason="'remove_function' is deprecated and will be removed in 1.10.0. "
2856
+ "Please use `delete_function` instead.",
2857
+ category=FutureWarning,
2858
+ )
2842
2859
  def remove_function(self, name):
2843
2860
  """remove the specified function from the project
2844
2861
 
@@ -2846,6 +2863,18 @@ class MlrunProject(ModelObj):
2846
2863
  """
2847
2864
  self.spec.remove_function(name)
2848
2865
 
2866
+ def delete_function(self, name, delete_from_db=False):
2867
+ """deletes the specified function from the project
2868
+
2869
+ :param name: name of the function (under the project)
2870
+ :param delete_from_db: default is False. If False, the function is removed
2871
+ only from the project's cache and spec.
2872
+ If True, the function is also removed from the database.
2873
+ """
2874
+ if delete_from_db:
2875
+ mlrun.db.get_run_db().delete_function(name=name, project=self.metadata.name)
2876
+ self.spec.remove_function(name)
2877
+
2849
2878
  def remove_model_monitoring_function(self, name: Union[str, list[str]]):
2850
2879
  """delete the specified model-monitoring-app function/s
2851
2880
 
@@ -3762,8 +3791,8 @@ class MlrunProject(ModelObj):
3762
3791
  "Please keep in mind that if you already had model monitoring functions "
3763
3792
  "/ model monitoring infra / tracked model server "
3764
3793
  "deployed on your project, you will need to redeploy them. "
3765
- "For redeploying the model monitoring infra, please use `enable_model_monitoring` API "
3766
- "and set `rebuild_images=True`"
3794
+ "For redeploying the model monitoring infra, first disable it using "
3795
+ "`project.disable_model_monitoring()` and then enable it using `project.enable_model_monitoring()`."
3767
3796
  )
3768
3797
 
3769
3798
  def list_model_endpoints(
@@ -3779,6 +3808,7 @@ class MlrunProject(ModelObj):
3779
3808
  top_level: bool = False,
3780
3809
  uids: Optional[list[str]] = None,
3781
3810
  latest_only: bool = False,
3811
+ tsdb_metrics: bool = True,
3782
3812
  ) -> mlrun.common.schemas.ModelEndpointList:
3783
3813
  """
3784
3814
  Returns a list of `ModelEndpoint` objects. Each `ModelEndpoint` object represents the current state of a
@@ -3829,6 +3859,7 @@ class MlrunProject(ModelObj):
3829
3859
  top_level=top_level,
3830
3860
  uids=uids,
3831
3861
  latest_only=latest_only,
3862
+ tsdb_metrics=tsdb_metrics,
3832
3863
  )
3833
3864
 
3834
3865
  def run_function(
mlrun/runtimes/base.py CHANGED
@@ -855,7 +855,7 @@ class BaseRuntime(ModelObj):
855
855
 
856
856
  def requires_build(self) -> bool:
857
857
  build = self.spec.build
858
- return (
858
+ return bool(
859
859
  build.commands
860
860
  or build.requirements
861
861
  or (build.source and not build.load_source_on_run)
@@ -182,7 +182,7 @@ class ListGenerator(TaskGenerator):
182
182
  yield newrun
183
183
 
184
184
 
185
- def get_run_copy(run):
185
+ def get_run_copy(run: RunObject):
186
186
  newrun = deepcopy(run)
187
187
  newrun.spec.hyperparams = None
188
188
  newrun.spec.param_file = None
@@ -599,6 +599,18 @@ class RemoteRuntime(KubeResource):
599
599
  # when a function is deployed, we wait for it to be ready by default
600
600
  # this also means that the function object will be updated with the function status
601
601
  self._wait_for_function_deployment(db, verbose=verbose)
602
+ # check if there are any background tasks related to creating model endpoints
603
+ model_endpoints_creation_background_tasks = (
604
+ mlrun.common.schemas.BackgroundTaskList(
605
+ **data.pop("background_tasks", {"background_tasks": []})
606
+ ).background_tasks
607
+ )
608
+ if model_endpoints_creation_background_tasks:
609
+ self._check_model_endpoint_task_state(
610
+ db=db,
611
+ background_task=model_endpoints_creation_background_tasks[0],
612
+ wait_for_completion=False,
613
+ )
602
614
 
603
615
  return self._enrich_command_from_status()
604
616
 
@@ -1285,6 +1297,33 @@ class RemoteRuntime(KubeResource):
1285
1297
  return mlrun.model.Credentials.generate_access_key
1286
1298
  return None
1287
1299
 
1300
+ def _check_model_endpoint_task_state(
1301
+ self,
1302
+ db: mlrun.db.RunDBInterface,
1303
+ background_task: mlrun.common.schemas.BackgroundTask,
1304
+ wait_for_completion: bool,
1305
+ ):
1306
+ if wait_for_completion:
1307
+ background_task = db._wait_for_background_task_to_reach_terminal_state(
1308
+ name=background_task.metadata.name, project=self.metadata.project
1309
+ )
1310
+ else:
1311
+ background_task = db.get_project_background_task(
1312
+ project=self.metadata.project, name=background_task.metadata.name
1313
+ )
1314
+ if (
1315
+ background_task.status.state
1316
+ in mlrun.common.schemas.BackgroundTaskState.terminal_states()
1317
+ ):
1318
+ logger.info(
1319
+ f"Model endpoint creation task completed with state {background_task.status.state}"
1320
+ )
1321
+ else:
1322
+ logger.warning(
1323
+ f"Model endpoint creation task is still running with state {background_task.status.state}"
1324
+ f"You can use the serving function, but it won't be monitored for the next few minutes"
1325
+ )
1326
+
1288
1327
 
1289
1328
  def parse_logs(logs):
1290
1329
  logs = json.loads(logs)
@@ -152,6 +152,7 @@ class ServingSpec(NuclioSpec):
152
152
  clone_target_dir=None,
153
153
  state_thresholds=None,
154
154
  disable_default_http_trigger=None,
155
+ model_endpoint_creation_task_name=None,
155
156
  ):
156
157
  super().__init__(
157
158
  command=command,
@@ -209,6 +210,7 @@ class ServingSpec(NuclioSpec):
209
210
  self.tracking_policy = tracking_policy
210
211
  self.secret_sources = secret_sources or []
211
212
  self.default_content_type = default_content_type
213
+ self.model_endpoint_creation_task_name = model_endpoint_creation_task_name
212
214
 
213
215
  @property
214
216
  def graph(self) -> Union[RouterStep, RootFlowStep]:
@@ -696,6 +698,7 @@ class ServingRuntime(RemoteRuntime):
696
698
  "track_models": self.spec.track_models,
697
699
  "tracking_policy": None,
698
700
  "default_content_type": self.spec.default_content_type,
701
+ "model_endpoint_creation_task_name": self.spec.model_endpoint_creation_task_name,
699
702
  }
700
703
 
701
704
  if self.spec.secret_sources:
mlrun/runtimes/pod.py CHANGED
@@ -214,9 +214,7 @@ class KubeResourceSpec(FunctionSpec):
214
214
  # default service account is set in mlrun.utils.process_function_service_account
215
215
  # due to project specific defaults
216
216
  self.service_account = service_account
217
- self.image_pull_secret = (
218
- image_pull_secret or mlrun.mlconf.function.spec.image_pull_secret.default
219
- )
217
+ self.image_pull_secret = image_pull_secret
220
218
  self.node_name = node_name
221
219
  self.node_selector = node_selector or {}
222
220
  self._affinity = affinity