mlrun 1.7.0rc38__py3-none-any.whl → 1.7.0rc41__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 (59) hide show
  1. mlrun/alerts/alert.py +30 -27
  2. mlrun/common/constants.py +3 -0
  3. mlrun/common/helpers.py +0 -1
  4. mlrun/common/schemas/alert.py +3 -0
  5. mlrun/common/schemas/model_monitoring/model_endpoints.py +0 -1
  6. mlrun/common/schemas/notification.py +1 -0
  7. mlrun/config.py +1 -1
  8. mlrun/data_types/to_pandas.py +9 -9
  9. mlrun/datastore/alibaba_oss.py +3 -2
  10. mlrun/datastore/azure_blob.py +7 -9
  11. mlrun/datastore/base.py +13 -1
  12. mlrun/datastore/dbfs_store.py +3 -7
  13. mlrun/datastore/filestore.py +1 -3
  14. mlrun/datastore/google_cloud_storage.py +84 -29
  15. mlrun/datastore/redis.py +1 -0
  16. mlrun/datastore/s3.py +3 -2
  17. mlrun/datastore/sources.py +54 -0
  18. mlrun/datastore/storeytargets.py +147 -0
  19. mlrun/datastore/targets.py +76 -122
  20. mlrun/datastore/v3io.py +1 -0
  21. mlrun/db/httpdb.py +6 -1
  22. mlrun/errors.py +8 -0
  23. mlrun/execution.py +7 -0
  24. mlrun/feature_store/api.py +5 -0
  25. mlrun/feature_store/retrieval/job.py +1 -0
  26. mlrun/model.py +24 -3
  27. mlrun/model_monitoring/api.py +10 -2
  28. mlrun/model_monitoring/applications/_application_steps.py +52 -34
  29. mlrun/model_monitoring/applications/context.py +206 -70
  30. mlrun/model_monitoring/applications/histogram_data_drift.py +15 -13
  31. mlrun/model_monitoring/controller.py +15 -12
  32. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +17 -8
  33. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +19 -9
  34. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +85 -47
  35. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +46 -10
  36. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +38 -24
  37. mlrun/model_monitoring/helpers.py +54 -18
  38. mlrun/model_monitoring/stream_processing.py +10 -29
  39. mlrun/projects/pipelines.py +19 -30
  40. mlrun/projects/project.py +86 -67
  41. mlrun/run.py +8 -6
  42. mlrun/runtimes/__init__.py +4 -0
  43. mlrun/runtimes/nuclio/api_gateway.py +18 -0
  44. mlrun/runtimes/nuclio/application/application.py +150 -59
  45. mlrun/runtimes/nuclio/function.py +5 -11
  46. mlrun/runtimes/nuclio/serving.py +2 -2
  47. mlrun/runtimes/utils.py +16 -0
  48. mlrun/serving/routers.py +1 -1
  49. mlrun/serving/server.py +19 -5
  50. mlrun/serving/states.py +8 -0
  51. mlrun/serving/v2_serving.py +34 -26
  52. mlrun/utils/helpers.py +33 -2
  53. mlrun/utils/version/version.json +2 -2
  54. {mlrun-1.7.0rc38.dist-info → mlrun-1.7.0rc41.dist-info}/METADATA +9 -12
  55. {mlrun-1.7.0rc38.dist-info → mlrun-1.7.0rc41.dist-info}/RECORD +59 -58
  56. {mlrun-1.7.0rc38.dist-info → mlrun-1.7.0rc41.dist-info}/WHEEL +1 -1
  57. {mlrun-1.7.0rc38.dist-info → mlrun-1.7.0rc41.dist-info}/LICENSE +0 -0
  58. {mlrun-1.7.0rc38.dist-info → mlrun-1.7.0rc41.dist-info}/entry_points.txt +0 -0
  59. {mlrun-1.7.0rc38.dist-info → mlrun-1.7.0rc41.dist-info}/top_level.txt +0 -0
@@ -15,6 +15,7 @@ import pathlib
15
15
  import typing
16
16
 
17
17
  import nuclio
18
+ import nuclio.auth
18
19
 
19
20
  import mlrun.common.schemas as schemas
20
21
  import mlrun.errors
@@ -281,34 +282,29 @@ class ApplicationRuntime(RemoteRuntime):
281
282
  is_kfp=False,
282
283
  mlrun_version_specifier=None,
283
284
  show_on_failure: bool = False,
284
- direct_port_access: bool = False,
285
- authentication_mode: schemas.APIGatewayAuthenticationMode = None,
286
- authentication_creds: tuple[str] = None,
287
- ssl_redirect: bool = None,
285
+ create_default_api_gateway: bool = True,
288
286
  ):
289
287
  """
290
288
  Deploy function, builds the application image if required (self.requires_build()) or force_build is True,
291
289
  Once the image is built, the function is deployed.
292
290
 
293
- :param project: Project name
294
- :param tag: Function tag
295
- :param verbose: Set True for verbose logging
296
- :param auth_info: Service AuthInfo (deprecated and ignored)
297
- :param builder_env: Env vars dict for source archive config/credentials
298
- e.g. builder_env={"GIT_TOKEN": token}
299
- :param force_build: Set True for force building the application image
300
- :param with_mlrun: Add the current mlrun package to the container build
301
- :param skip_deployed: Skip the build if we already have an image for the function
302
- :param is_kfp: Deploy as part of a kfp pipeline
303
- :param mlrun_version_specifier: Which mlrun package version to include (if not current)
304
- :param show_on_failure: Show logs only in case of build failure
305
- :param direct_port_access: Set True to allow direct port access to the application sidecar
306
- :param authentication_mode: API Gateway authentication mode
307
- :param authentication_creds: API Gateway authentication credentials as a tuple (username, password)
308
- :param ssl_redirect: Set True to force SSL redirect, False to disable. Defaults to
309
- mlrun.mlconf.force_api_gateway_ssl_redirect()
310
-
311
- :return: True if the function is ready (deployed)
291
+ :param project: Project name
292
+ :param tag: Function tag
293
+ :param verbose: Set True for verbose logging
294
+ :param auth_info: Service AuthInfo (deprecated and ignored)
295
+ :param builder_env: Env vars dict for source archive config/credentials
296
+ e.g. builder_env={"GIT_TOKEN": token}
297
+ :param force_build: Set True for force building the application image
298
+ :param with_mlrun: Add the current mlrun package to the container build
299
+ :param skip_deployed: Skip the build if we already have an image for the function
300
+ :param is_kfp: Deploy as part of a kfp pipeline
301
+ :param mlrun_version_specifier: Which mlrun package version to include (if not current)
302
+ :param show_on_failure: Show logs only in case of build failure
303
+ :param create_default_api_gateway: When deploy finishes the default API gateway will be created for the
304
+ application. Disabling this flag means that the application will not be
305
+ accessible until an API gateway is created for it.
306
+
307
+ :return: The default API gateway URL if created or True if the function is ready (deployed)
312
308
  """
313
309
  if (self.requires_build() and not self.spec.image) or force_build:
314
310
  self._fill_credentials()
@@ -328,10 +324,6 @@ class ApplicationRuntime(RemoteRuntime):
328
324
  self._configure_application_sidecar()
329
325
 
330
326
  # We only allow accessing the application via the API Gateway
331
- name_tag = tag or self.metadata.tag
332
- self.status.api_gateway_name = (
333
- f"{self.metadata.name}-{name_tag}" if name_tag else self.metadata.name
334
- )
335
327
  self.spec.add_templated_ingress_host_mode = (
336
328
  NuclioIngressAddTemplatedIngressModes.never
337
329
  )
@@ -344,9 +336,7 @@ class ApplicationRuntime(RemoteRuntime):
344
336
  builder_env=builder_env,
345
337
  )
346
338
  logger.info(
347
- "Successfully deployed function, creating API gateway",
348
- api_gateway_name=self.status.api_gateway_name,
349
- authentication_mode=authentication_mode,
339
+ "Successfully deployed function.",
350
340
  )
351
341
 
352
342
  # Restore the source in case it was removed to make nuclio not consider it when building
@@ -354,14 +344,23 @@ class ApplicationRuntime(RemoteRuntime):
354
344
  self.spec.build.source = self.status.application_source
355
345
  self.save(versioned=False)
356
346
 
357
- ports = self.spec.internal_application_port if direct_port_access else []
358
- return self.create_api_gateway(
359
- name=self.status.api_gateway_name,
360
- ports=ports,
361
- authentication_mode=authentication_mode,
362
- authentication_creds=authentication_creds,
363
- ssl_redirect=ssl_redirect,
364
- )
347
+ if create_default_api_gateway:
348
+ try:
349
+ api_gateway_name = self.resolve_default_api_gateway_name()
350
+ return self.create_api_gateway(api_gateway_name, set_as_default=True)
351
+ except Exception as exc:
352
+ logger.warning(
353
+ "Failed to create default API gateway, application may not be accessible. "
354
+ "Use the `create_api_gateway` method to make it accessible",
355
+ exc=mlrun.errors.err_to_str(exc),
356
+ )
357
+ elif not self.status.api_gateway:
358
+ logger.warning(
359
+ "Application is online but may not be accessible since default gateway creation was not requested."
360
+ "Use the `create_api_gateway` method to make it accessible."
361
+ )
362
+
363
+ return True
365
364
 
366
365
  def with_source_archive(
367
366
  self,
@@ -429,17 +428,54 @@ class ApplicationRuntime(RemoteRuntime):
429
428
  self,
430
429
  name: str = None,
431
430
  path: str = None,
432
- ports: list[int] = None,
431
+ direct_port_access: bool = False,
433
432
  authentication_mode: schemas.APIGatewayAuthenticationMode = None,
434
- authentication_creds: tuple[str] = None,
433
+ authentication_creds: tuple[str, str] = None,
435
434
  ssl_redirect: bool = None,
435
+ set_as_default: bool = False,
436
+ gateway_timeout: typing.Optional[int] = None,
436
437
  ):
438
+ """
439
+ Create the application API gateway. Once the application is deployed, the API gateway can be created.
440
+ An application without an API gateway is not accessible.
441
+ :param name: The name of the API gateway, defaults to <function-name>-<function-tag>
442
+ :param path: Optional path of the API gateway, default value is "/"
443
+ :param direct_port_access: Set True to allow direct port access to the application sidecar
444
+ :param authentication_mode: API Gateway authentication mode
445
+ :param authentication_creds: API Gateway basic authentication credentials as a tuple (username, password)
446
+ :param ssl_redirect: Set True to force SSL redirect, False to disable. Defaults to
447
+ mlrun.mlconf.force_api_gateway_ssl_redirect()
448
+ :param set_as_default: Set the API gateway as the default for the application (`status.api_gateway`)
449
+ :param gateway_timeout: nginx ingress timeout in sec (request timeout, when will the gateway return an
450
+ error)
451
+
452
+ :return: The API gateway URL
453
+ """
454
+ if not name:
455
+ raise mlrun.errors.MLRunInvalidArgumentError(
456
+ "API gateway name must be specified."
457
+ )
458
+
459
+ if not set_as_default and name == self.resolve_default_api_gateway_name():
460
+ raise mlrun.errors.MLRunInvalidArgumentError(
461
+ f"Non-default API gateway cannot use the default gateway name, {name=}."
462
+ )
463
+
464
+ if (
465
+ authentication_mode == schemas.APIGatewayAuthenticationMode.basic
466
+ and not authentication_creds
467
+ ):
468
+ raise mlrun.errors.MLRunInvalidArgumentError(
469
+ "Authentication credentials not provided"
470
+ )
471
+
472
+ ports = self.spec.internal_application_port if direct_port_access else []
473
+
437
474
  api_gateway = APIGateway(
438
475
  APIGatewayMetadata(
439
476
  name=name,
440
477
  namespace=self.metadata.namespace,
441
- labels=self.metadata.labels,
442
- annotations=self.metadata.annotations,
478
+ labels=self.metadata.labels.copy(),
443
479
  ),
444
480
  APIGatewaySpec(
445
481
  functions=[self],
@@ -449,13 +485,14 @@ class ApplicationRuntime(RemoteRuntime):
449
485
  ),
450
486
  )
451
487
 
488
+ api_gateway.with_gateway_timeout(gateway_timeout)
452
489
  if ssl_redirect is None:
453
490
  ssl_redirect = mlrun.mlconf.force_api_gateway_ssl_redirect()
454
491
  if ssl_redirect:
455
- # force ssl redirect so that the application is only accessible via https
492
+ # Force ssl redirect so that the application is only accessible via https
456
493
  api_gateway.with_force_ssl_redirect()
457
494
 
458
- # add authentication if required
495
+ # Add authentication if required
459
496
  authentication_mode = (
460
497
  authentication_mode
461
498
  or mlrun.mlconf.function.application.default_authentication_mode
@@ -469,31 +506,64 @@ class ApplicationRuntime(RemoteRuntime):
469
506
  api_gateway_scheme = db.store_api_gateway(
470
507
  api_gateway=api_gateway.to_scheme(), project=self.metadata.project
471
508
  )
472
- if not self.status.api_gateway_name:
473
- self.status.api_gateway_name = api_gateway_scheme.metadata.name
474
- self.status.api_gateway = APIGateway.from_scheme(api_gateway_scheme)
475
- self.status.api_gateway.wait_for_readiness()
476
- self.url = self.status.api_gateway.invoke_url
477
509
 
478
- logger.info("Successfully created API gateway", url=self.url)
479
- return self.url
510
+ if set_as_default:
511
+ self.status.api_gateway_name = api_gateway_scheme.metadata.name
512
+ self.status.api_gateway = APIGateway.from_scheme(api_gateway_scheme)
513
+ self.status.api_gateway.wait_for_readiness()
514
+ self.url = self.status.api_gateway.invoke_url
515
+ url = self.url
516
+ else:
517
+ api_gateway = APIGateway.from_scheme(api_gateway_scheme)
518
+ api_gateway.wait_for_readiness()
519
+ url = api_gateway.invoke_url
520
+ # Update application status (enriches invocation url)
521
+ self._get_state(raise_on_exception=False)
522
+
523
+ logger.info("Successfully created API gateway", url=url)
524
+ return url
525
+
526
+ def delete_api_gateway(self, name: str):
527
+ """
528
+ Delete API gateway by name.
529
+ Refreshes the application status to update api gateway and invocation URLs.
530
+ :param name: The API gateway name
531
+ """
532
+ self._get_db().delete_api_gateway(name=name, project=self.metadata.project)
533
+ if name == self.status.api_gateway_name:
534
+ self.status.api_gateway_name = None
535
+ self.status.api_gateway = None
536
+ self._get_state()
480
537
 
481
538
  def invoke(
482
539
  self,
483
- path: str,
484
- body: typing.Union[str, bytes, dict] = None,
540
+ path: str = "",
541
+ body: typing.Optional[typing.Union[str, bytes, dict]] = None,
485
542
  method: str = None,
486
543
  headers: dict = None,
487
544
  dashboard: str = "",
488
545
  force_external_address: bool = False,
489
546
  auth_info: schemas.AuthInfo = None,
490
547
  mock: bool = None,
548
+ credentials: tuple[str, str] = None,
491
549
  **http_client_kwargs,
492
550
  ):
493
551
  self._sync_api_gateway()
552
+
494
553
  # If the API Gateway is not ready or not set, try to invoke the function directly (without the API Gateway)
495
554
  if not self.status.api_gateway:
496
- super().invoke(
555
+ logger.warning(
556
+ "Default API gateway is not configured, invoking function invocation URL."
557
+ )
558
+ # create a requests auth object if credentials are provided and not already set in the http client kwargs
559
+ auth = http_client_kwargs.pop("auth", None) or (
560
+ nuclio.auth.AuthInfo(
561
+ username=credentials[0], password=credentials[1]
562
+ ).to_requests_auth()
563
+ if credentials
564
+ else None
565
+ )
566
+ return super().invoke(
497
567
  path,
498
568
  body,
499
569
  method,
@@ -502,18 +572,19 @@ class ApplicationRuntime(RemoteRuntime):
502
572
  force_external_address,
503
573
  auth_info,
504
574
  mock,
575
+ auth=auth,
505
576
  **http_client_kwargs,
506
577
  )
507
578
 
508
- credentials = (auth_info.username, auth_info.password) if auth_info else None
509
-
510
579
  if not method:
511
580
  method = "POST" if body else "GET"
581
+
512
582
  return self.status.api_gateway.invoke(
513
583
  method=method,
514
584
  headers=headers,
515
585
  credentials=credentials,
516
586
  path=path,
587
+ body=body,
517
588
  **http_client_kwargs,
518
589
  )
519
590
 
@@ -550,6 +621,27 @@ class ApplicationRuntime(RemoteRuntime):
550
621
  reverse_proxy_func.metadata.name, reverse_proxy_func.metadata.project
551
622
  )
552
623
 
624
+ def resolve_default_api_gateway_name(self):
625
+ return (
626
+ f"{self.metadata.name}-{self.metadata.tag}"
627
+ if self.metadata.tag
628
+ else self.metadata.name
629
+ )
630
+
631
+ @min_nuclio_versions("1.13.1")
632
+ def disable_default_http_trigger(
633
+ self,
634
+ ):
635
+ raise mlrun.runtimes.RunError(
636
+ "Application runtime does not support disabling the default HTTP trigger"
637
+ )
638
+
639
+ @min_nuclio_versions("1.13.1")
640
+ def enable_default_http_trigger(
641
+ self,
642
+ ):
643
+ pass
644
+
553
645
  def _run(self, runobj: "mlrun.RunObject", execution):
554
646
  raise mlrun.runtimes.RunError(
555
647
  "Application runtime .run() is not yet supported. Use .invoke() instead."
@@ -648,9 +740,8 @@ class ApplicationRuntime(RemoteRuntime):
648
740
  self.set_env("SIDECAR_HOST", "http://localhost")
649
741
 
650
742
  # configure the sidecar container as the default container for logging purposes
651
- self.set_config(
652
- "metadata.annotations",
653
- {"kubectl.kubernetes.io/default-container": self.status.sidecar_name},
743
+ self.metadata.annotations["kubectl.kubernetes.io/default-container"] = (
744
+ self.status.sidecar_name
654
745
  )
655
746
 
656
747
  def _sync_api_gateway(self):
@@ -418,14 +418,8 @@ class RemoteRuntime(KubeResource):
418
418
  raise ValueError(
419
419
  "gateway timeout must be greater than the worker timeout"
420
420
  )
421
- annotations["nginx.ingress.kubernetes.io/proxy-connect-timeout"] = (
422
- f"{gateway_timeout}"
423
- )
424
- annotations["nginx.ingress.kubernetes.io/proxy-read-timeout"] = (
425
- f"{gateway_timeout}"
426
- )
427
- annotations["nginx.ingress.kubernetes.io/proxy-send-timeout"] = (
428
- f"{gateway_timeout}"
421
+ mlrun.runtimes.utils.enrich_gateway_timeout_annotations(
422
+ annotations, gateway_timeout
429
423
  )
430
424
 
431
425
  trigger = nuclio.HttpTrigger(
@@ -698,7 +692,7 @@ class RemoteRuntime(KubeResource):
698
692
  """
699
693
  self.spec.disable_default_http_trigger = True
700
694
 
701
- @min_nuclio_versions("1.12.8")
695
+ @min_nuclio_versions("1.13.1")
702
696
  def enable_default_http_trigger(
703
697
  self,
704
698
  ):
@@ -753,7 +747,7 @@ class RemoteRuntime(KubeResource):
753
747
  return state, text, last_log_timestamp
754
748
 
755
749
  try:
756
- text, last_log_timestamp = self._get_db().get_builder_status(
750
+ text, last_log_timestamp = self._get_db().get_nuclio_deploy_status(
757
751
  self, last_log_timestamp=last_log_timestamp, verbose=verbose
758
752
  )
759
753
  except mlrun.db.RunDBError:
@@ -1004,7 +998,7 @@ class RemoteRuntime(KubeResource):
1004
998
  if command and not command.startswith("http"):
1005
999
  sidecar["command"] = mlrun.utils.helpers.as_list(command)
1006
1000
 
1007
- if args and sidecar["command"]:
1001
+ if args and sidecar.get("command"):
1008
1002
  sidecar["args"] = mlrun.utils.helpers.as_list(args)
1009
1003
 
1010
1004
  # populate the sidecar resources from the function spec
@@ -676,7 +676,6 @@ class ServingRuntime(RemoteRuntime):
676
676
  """create mock server object for local testing/emulation
677
677
 
678
678
  :param namespace: one or list of namespaces/modules to search the steps classes/functions in
679
- :param log_level: log level (error | info | debug)
680
679
  :param current_function: specify if you want to simulate a child function, * for all functions
681
680
  :param track_models: allow model tracking (disabled by default in the mock server)
682
681
  :param workdir: working directory to locate the source code (if not the current one)
@@ -704,7 +703,7 @@ class ServingRuntime(RemoteRuntime):
704
703
  verbose=self.verbose,
705
704
  current_function=current_function,
706
705
  graph_initializer=self.spec.graph_initializer,
707
- track_models=track_models and self.spec.track_models,
706
+ track_models=self.spec.track_models,
708
707
  function_uri=self._function_uri(),
709
708
  secret_sources=self.spec.secret_sources,
710
709
  default_content_type=self.spec.default_content_type,
@@ -715,6 +714,7 @@ class ServingRuntime(RemoteRuntime):
715
714
  namespace=namespace,
716
715
  logger=logger,
717
716
  is_mock=True,
717
+ monitoring_mock=track_models,
718
718
  )
719
719
 
720
720
  if workdir:
mlrun/runtimes/utils.py CHANGED
@@ -463,3 +463,19 @@ def resolve_node_selectors(
463
463
  instance_node_selector,
464
464
  )
465
465
  return instance_node_selector
466
+
467
+
468
+ def enrich_gateway_timeout_annotations(annotations: dict, gateway_timeout: int):
469
+ """
470
+ Set gateway proxy connect/read/send timeout annotations
471
+ :param annotations: The annotations to enrich
472
+ :param gateway_timeout: The timeout to set
473
+ """
474
+ if not gateway_timeout:
475
+ return
476
+ gateway_timeout_str = str(gateway_timeout)
477
+ annotations["nginx.ingress.kubernetes.io/proxy-connect-timeout"] = (
478
+ gateway_timeout_str
479
+ )
480
+ annotations["nginx.ingress.kubernetes.io/proxy-read-timeout"] = gateway_timeout_str
481
+ annotations["nginx.ingress.kubernetes.io/proxy-send-timeout"] = gateway_timeout_str
mlrun/serving/routers.py CHANGED
@@ -615,7 +615,7 @@ class VotingEnsemble(ParallelRun):
615
615
  logger.warn("GraphServer not initialized for VotingEnsemble instance")
616
616
  return
617
617
 
618
- if not self.context.is_mock or self.context.server.track_models:
618
+ if not self.context.is_mock or self.context.monitoring_mock:
619
619
  self.model_endpoint_uid = _init_endpoint_record(server, self)
620
620
 
621
621
  self._update_weights(self.weights)
mlrun/serving/server.py CHANGED
@@ -22,10 +22,14 @@ import traceback
22
22
  import uuid
23
23
  from typing import Optional, Union
24
24
 
25
+ from nuclio import Context as NuclioContext
26
+ from nuclio.request import Logger as NuclioLogger
27
+
25
28
  import mlrun
26
29
  import mlrun.common.constants
27
30
  import mlrun.common.helpers
28
31
  import mlrun.model_monitoring
32
+ import mlrun.utils
29
33
  from mlrun.config import config
30
34
  from mlrun.errors import err_to_str
31
35
  from mlrun.secrets import SecretsStore
@@ -150,6 +154,7 @@ class GraphServer(ModelObj):
150
154
  resource_cache: ResourceCache = None,
151
155
  logger=None,
152
156
  is_mock=False,
157
+ monitoring_mock=False,
153
158
  ):
154
159
  """for internal use, initialize all steps (recursively)"""
155
160
 
@@ -162,6 +167,7 @@ class GraphServer(ModelObj):
162
167
 
163
168
  context = GraphContext(server=self, nuclio_context=context, logger=logger)
164
169
  context.is_mock = is_mock
170
+ context.monitoring_mock = monitoring_mock
165
171
  context.root = self.graph
166
172
 
167
173
  context.stream = _StreamContext(
@@ -387,7 +393,9 @@ def v2_serving_handler(context, event, get_body=False):
387
393
 
388
394
  # original path is saved in stream_path so it can be used by explicit ack, but path is reset to / as a
389
395
  # workaround for NUC-178
390
- event.stream_path = event.path
396
+ # nuclio 1.12.12 added the topic attribute, and we must use it as part of the fix for NUC-233
397
+ # TODO: Remove fallback on event.path once support for nuclio<1.12.12 is dropped
398
+ event.stream_path = getattr(event, "topic", event.path)
391
399
  if hasattr(event, "trigger") and event.trigger.kind in (
392
400
  "kafka",
393
401
  "kafka-cluster",
@@ -483,7 +491,13 @@ class Response:
483
491
  class GraphContext:
484
492
  """Graph context object"""
485
493
 
486
- def __init__(self, level="info", logger=None, server=None, nuclio_context=None):
494
+ def __init__(
495
+ self,
496
+ level="info", # Unused argument
497
+ logger=None,
498
+ server=None,
499
+ nuclio_context: Optional[NuclioContext] = None,
500
+ ) -> None:
487
501
  self.state = None
488
502
  self.logger = logger
489
503
  self.worker_id = 0
@@ -493,7 +507,7 @@ class GraphContext:
493
507
  self.root = None
494
508
 
495
509
  if nuclio_context:
496
- self.logger = nuclio_context.logger
510
+ self.logger: NuclioLogger = nuclio_context.logger
497
511
  self.Response = nuclio_context.Response
498
512
  if hasattr(nuclio_context, "trigger") and hasattr(
499
513
  nuclio_context.trigger, "kind"
@@ -503,7 +517,7 @@ class GraphContext:
503
517
  if hasattr(nuclio_context, "platform"):
504
518
  self.platform = nuclio_context.platform
505
519
  elif not logger:
506
- self.logger = mlrun.utils.helpers.logger
520
+ self.logger: mlrun.utils.Logger = mlrun.utils.logger
507
521
 
508
522
  self._server = server
509
523
  self.current_function = None
@@ -516,7 +530,7 @@ class GraphContext:
516
530
  return self._server
517
531
 
518
532
  @property
519
- def project(self):
533
+ def project(self) -> str:
520
534
  """current project name (for the current function)"""
521
535
  project, _, _, _ = mlrun.common.helpers.parse_versioned_object_uri(
522
536
  self._server.function_uri
mlrun/serving/states.py CHANGED
@@ -84,6 +84,9 @@ _task_step_fields = [
84
84
  ]
85
85
 
86
86
 
87
+ MAX_ALLOWED_STEPS = 4500
88
+
89
+
87
90
  def new_model_endpoint(class_name, model_path, handler=None, **class_args):
88
91
  class_args = deepcopy(class_args)
89
92
  class_args["model_path"] = model_path
@@ -733,6 +736,11 @@ class RouterStep(TaskStep):
733
736
  if not route:
734
737
  route = TaskStep(class_name, class_args, handler=handler)
735
738
  route.function = function or route.function
739
+
740
+ if len(self._routes) >= MAX_ALLOWED_STEPS:
741
+ raise mlrun.errors.MLRunInvalidArgumentError(
742
+ f"Cannot create the serving graph: the maximum number of steps is {MAX_ALLOWED_STEPS}"
743
+ )
736
744
  route = self._routes.update(key, route)
737
745
  route.set_parent(self)
738
746
  return route
@@ -18,8 +18,9 @@ import traceback
18
18
  from typing import Optional, Union
19
19
 
20
20
  import mlrun.artifacts
21
- import mlrun.common.model_monitoring
21
+ import mlrun.common.model_monitoring.helpers
22
22
  import mlrun.common.schemas.model_monitoring
23
+ import mlrun.model_monitoring
23
24
  from mlrun.errors import err_to_str
24
25
  from mlrun.utils import logger, now_date
25
26
 
@@ -147,7 +148,7 @@ class V2ModelServer(StepToDict):
147
148
  logger.warn("GraphServer not initialized for VotingEnsemble instance")
148
149
  return
149
150
 
150
- if not self.context.is_mock or self.context.server.track_models:
151
+ if not self.context.is_mock or self.context.monitoring_mock:
151
152
  self.model_endpoint_uid = _init_endpoint_record(
152
153
  graph_server=server, model=self
153
154
  )
@@ -554,13 +555,13 @@ def _init_endpoint_record(
554
555
  except mlrun.errors.MLRunNotFoundError:
555
556
  model_ep = None
556
557
  except mlrun.errors.MLRunBadRequestError as err:
557
- logger.debug(
558
- f"Cant reach to model endpoints store, due to : {err}",
558
+ logger.info(
559
+ "Cannot get the model endpoints store", err=mlrun.errors.err_to_str(err)
559
560
  )
560
561
  return
561
562
 
562
563
  if model.context.server.track_models and not model_ep:
563
- logger.debug("Creating a new model endpoint record", endpoint_id=uid)
564
+ logger.info("Creating a new model endpoint record", endpoint_id=uid)
564
565
  model_endpoint = mlrun.common.schemas.ModelEndpoint(
565
566
  metadata=mlrun.common.schemas.ModelEndpointMetadata(
566
567
  project=project, labels=model.labels, uid=uid
@@ -586,28 +587,35 @@ def _init_endpoint_record(
586
587
  model_endpoint=model_endpoint.dict(),
587
588
  )
588
589
 
589
- elif (
590
- model_ep
591
- and (
590
+ elif model_ep:
591
+ attributes = {}
592
+ old_model_uri = model_ep.spec.model_uri
593
+ mlrun.model_monitoring.helpers.enrich_model_endpoint_with_model_uri(
594
+ model_endpoint=model_ep,
595
+ model_obj=model.model_spec,
596
+ )
597
+ if model_ep.spec.model_uri != old_model_uri:
598
+ attributes["model_uri"] = model_ep.spec.model_uri
599
+ if (
592
600
  model_ep.spec.monitoring_mode
593
601
  == mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled
594
- )
595
- != model.context.server.track_models
596
- ):
597
- monitoring_mode = (
598
- mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled
599
- if model.context.server.track_models
600
- else mlrun.common.schemas.model_monitoring.ModelMonitoringMode.disabled
601
- )
602
- db = mlrun.get_run_db()
603
- db.patch_model_endpoint(
604
- project=project,
605
- endpoint_id=uid,
606
- attributes={"monitoring_mode": monitoring_mode},
607
- )
608
- logger.debug(
609
- f"Updating model endpoint monitoring_mode to {monitoring_mode}",
610
- endpoint_id=uid,
611
- )
602
+ ) != model.context.server.track_models:
603
+ attributes["monitoring_mode"] = (
604
+ mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled
605
+ if model.context.server.track_models
606
+ else mlrun.common.schemas.model_monitoring.ModelMonitoringMode.disabled
607
+ )
608
+ if attributes:
609
+ db = mlrun.get_run_db()
610
+ db.patch_model_endpoint(
611
+ project=project,
612
+ endpoint_id=uid,
613
+ attributes=attributes,
614
+ )
615
+ logger.info(
616
+ "Updating model endpoint attributes",
617
+ attributes=attributes,
618
+ endpoint_id=uid,
619
+ )
612
620
 
613
621
  return uid