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.
- mlrun/alerts/alert.py +30 -27
- mlrun/common/constants.py +3 -0
- mlrun/common/helpers.py +0 -1
- mlrun/common/schemas/alert.py +3 -0
- mlrun/common/schemas/model_monitoring/model_endpoints.py +0 -1
- mlrun/common/schemas/notification.py +1 -0
- mlrun/config.py +1 -1
- mlrun/data_types/to_pandas.py +9 -9
- mlrun/datastore/alibaba_oss.py +3 -2
- mlrun/datastore/azure_blob.py +7 -9
- mlrun/datastore/base.py +13 -1
- mlrun/datastore/dbfs_store.py +3 -7
- mlrun/datastore/filestore.py +1 -3
- mlrun/datastore/google_cloud_storage.py +84 -29
- mlrun/datastore/redis.py +1 -0
- mlrun/datastore/s3.py +3 -2
- mlrun/datastore/sources.py +54 -0
- mlrun/datastore/storeytargets.py +147 -0
- mlrun/datastore/targets.py +76 -122
- mlrun/datastore/v3io.py +1 -0
- mlrun/db/httpdb.py +6 -1
- mlrun/errors.py +8 -0
- mlrun/execution.py +7 -0
- mlrun/feature_store/api.py +5 -0
- mlrun/feature_store/retrieval/job.py +1 -0
- mlrun/model.py +24 -3
- mlrun/model_monitoring/api.py +10 -2
- mlrun/model_monitoring/applications/_application_steps.py +52 -34
- mlrun/model_monitoring/applications/context.py +206 -70
- mlrun/model_monitoring/applications/histogram_data_drift.py +15 -13
- mlrun/model_monitoring/controller.py +15 -12
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +17 -8
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +19 -9
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +85 -47
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +46 -10
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +38 -24
- mlrun/model_monitoring/helpers.py +54 -18
- mlrun/model_monitoring/stream_processing.py +10 -29
- mlrun/projects/pipelines.py +19 -30
- mlrun/projects/project.py +86 -67
- mlrun/run.py +8 -6
- mlrun/runtimes/__init__.py +4 -0
- mlrun/runtimes/nuclio/api_gateway.py +18 -0
- mlrun/runtimes/nuclio/application/application.py +150 -59
- mlrun/runtimes/nuclio/function.py +5 -11
- mlrun/runtimes/nuclio/serving.py +2 -2
- mlrun/runtimes/utils.py +16 -0
- mlrun/serving/routers.py +1 -1
- mlrun/serving/server.py +19 -5
- mlrun/serving/states.py +8 -0
- mlrun/serving/v2_serving.py +34 -26
- mlrun/utils/helpers.py +33 -2
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc38.dist-info → mlrun-1.7.0rc41.dist-info}/METADATA +9 -12
- {mlrun-1.7.0rc38.dist-info → mlrun-1.7.0rc41.dist-info}/RECORD +59 -58
- {mlrun-1.7.0rc38.dist-info → mlrun-1.7.0rc41.dist-info}/WHEEL +1 -1
- {mlrun-1.7.0rc38.dist-info → mlrun-1.7.0rc41.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc38.dist-info → mlrun-1.7.0rc41.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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:
|
|
294
|
-
:param tag:
|
|
295
|
-
:param verbose:
|
|
296
|
-
:param auth_info:
|
|
297
|
-
:param builder_env:
|
|
298
|
-
|
|
299
|
-
:param force_build:
|
|
300
|
-
:param with_mlrun:
|
|
301
|
-
:param skip_deployed:
|
|
302
|
-
:param is_kfp:
|
|
303
|
-
:param mlrun_version_specifier:
|
|
304
|
-
:param show_on_failure:
|
|
305
|
-
:param
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
492
|
+
# Force ssl redirect so that the application is only accessible via https
|
|
456
493
|
api_gateway.with_force_ssl_redirect()
|
|
457
494
|
|
|
458
|
-
#
|
|
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
|
-
|
|
479
|
-
|
|
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
|
-
|
|
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.
|
|
652
|
-
|
|
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
|
-
|
|
422
|
-
|
|
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.
|
|
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().
|
|
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
|
|
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
|
mlrun/runtimes/nuclio/serving.py
CHANGED
|
@@ -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=
|
|
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.
|
|
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
|
-
|
|
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__(
|
|
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.
|
|
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
|
mlrun/serving/v2_serving.py
CHANGED
|
@@ -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.
|
|
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.
|
|
558
|
-
|
|
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.
|
|
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
|
-
|
|
591
|
-
|
|
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
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|