mlrun 1.7.0rc28__py3-none-any.whl → 1.7.0rc55__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/__main__.py +4 -2
- mlrun/alerts/alert.py +75 -8
- mlrun/artifacts/base.py +1 -0
- mlrun/artifacts/manager.py +9 -2
- mlrun/common/constants.py +4 -1
- mlrun/common/db/sql_session.py +3 -2
- mlrun/common/formatters/__init__.py +1 -0
- mlrun/common/formatters/artifact.py +1 -0
- mlrun/{model_monitoring/application.py → common/formatters/feature_set.py} +20 -6
- mlrun/common/formatters/run.py +3 -0
- mlrun/common/helpers.py +0 -1
- mlrun/common/schemas/__init__.py +3 -1
- mlrun/common/schemas/alert.py +15 -12
- mlrun/common/schemas/api_gateway.py +6 -6
- mlrun/common/schemas/auth.py +5 -0
- mlrun/common/schemas/client_spec.py +0 -1
- mlrun/common/schemas/common.py +7 -4
- mlrun/common/schemas/frontend_spec.py +7 -0
- mlrun/common/schemas/function.py +7 -0
- mlrun/common/schemas/model_monitoring/__init__.py +4 -3
- mlrun/common/schemas/model_monitoring/constants.py +41 -26
- mlrun/common/schemas/model_monitoring/model_endpoints.py +23 -47
- mlrun/common/schemas/notification.py +69 -12
- mlrun/common/schemas/project.py +45 -12
- mlrun/common/schemas/workflow.py +10 -2
- mlrun/common/types.py +1 -0
- mlrun/config.py +91 -35
- mlrun/data_types/data_types.py +6 -1
- mlrun/data_types/spark.py +2 -2
- mlrun/data_types/to_pandas.py +57 -25
- mlrun/datastore/__init__.py +1 -0
- mlrun/datastore/alibaba_oss.py +3 -2
- mlrun/datastore/azure_blob.py +125 -37
- mlrun/datastore/base.py +42 -21
- mlrun/datastore/datastore.py +4 -2
- mlrun/datastore/datastore_profile.py +1 -1
- mlrun/datastore/dbfs_store.py +3 -7
- mlrun/datastore/filestore.py +1 -3
- mlrun/datastore/google_cloud_storage.py +85 -29
- mlrun/datastore/inmem.py +4 -1
- mlrun/datastore/redis.py +1 -0
- mlrun/datastore/s3.py +25 -12
- mlrun/datastore/sources.py +76 -4
- mlrun/datastore/spark_utils.py +30 -0
- mlrun/datastore/storeytargets.py +151 -0
- mlrun/datastore/targets.py +102 -131
- mlrun/datastore/v3io.py +1 -0
- mlrun/db/base.py +15 -6
- mlrun/db/httpdb.py +57 -28
- mlrun/db/nopdb.py +29 -5
- mlrun/errors.py +20 -3
- mlrun/execution.py +46 -5
- mlrun/feature_store/api.py +25 -1
- mlrun/feature_store/common.py +6 -11
- mlrun/feature_store/feature_vector.py +3 -1
- mlrun/feature_store/retrieval/job.py +4 -1
- mlrun/feature_store/retrieval/spark_merger.py +10 -39
- mlrun/feature_store/steps.py +8 -0
- mlrun/frameworks/_common/plan.py +3 -3
- mlrun/frameworks/_ml_common/plan.py +1 -1
- mlrun/frameworks/parallel_coordinates.py +2 -3
- mlrun/frameworks/sklearn/mlrun_interface.py +13 -3
- mlrun/k8s_utils.py +48 -2
- mlrun/launcher/client.py +6 -6
- mlrun/launcher/local.py +2 -2
- mlrun/model.py +215 -34
- mlrun/model_monitoring/api.py +38 -24
- mlrun/model_monitoring/applications/__init__.py +1 -2
- mlrun/model_monitoring/applications/_application_steps.py +60 -29
- mlrun/model_monitoring/applications/base.py +2 -174
- mlrun/model_monitoring/applications/context.py +197 -70
- mlrun/model_monitoring/applications/evidently_base.py +11 -85
- mlrun/model_monitoring/applications/histogram_data_drift.py +21 -16
- mlrun/model_monitoring/applications/results.py +4 -4
- mlrun/model_monitoring/controller.py +110 -282
- mlrun/model_monitoring/db/stores/__init__.py +8 -3
- mlrun/model_monitoring/db/stores/base/store.py +3 -0
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +9 -7
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +18 -3
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +43 -23
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +48 -35
- mlrun/model_monitoring/db/tsdb/__init__.py +7 -2
- mlrun/model_monitoring/db/tsdb/base.py +147 -15
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +94 -55
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -3
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +144 -38
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +44 -3
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +246 -57
- mlrun/model_monitoring/helpers.py +70 -50
- mlrun/model_monitoring/stream_processing.py +96 -195
- mlrun/model_monitoring/writer.py +13 -5
- mlrun/package/packagers/default_packager.py +2 -2
- mlrun/projects/operations.py +16 -8
- mlrun/projects/pipelines.py +126 -115
- mlrun/projects/project.py +286 -129
- mlrun/render.py +3 -3
- mlrun/run.py +38 -19
- mlrun/runtimes/__init__.py +19 -8
- mlrun/runtimes/base.py +4 -1
- mlrun/runtimes/daskjob.py +1 -1
- mlrun/runtimes/funcdoc.py +1 -1
- mlrun/runtimes/kubejob.py +6 -6
- mlrun/runtimes/local.py +12 -5
- mlrun/runtimes/nuclio/api_gateway.py +68 -8
- mlrun/runtimes/nuclio/application/application.py +307 -70
- mlrun/runtimes/nuclio/function.py +63 -14
- mlrun/runtimes/nuclio/serving.py +10 -10
- mlrun/runtimes/pod.py +25 -19
- mlrun/runtimes/remotesparkjob.py +2 -5
- mlrun/runtimes/sparkjob/spark3job.py +16 -17
- mlrun/runtimes/utils.py +34 -0
- mlrun/serving/routers.py +2 -5
- mlrun/serving/server.py +37 -19
- mlrun/serving/states.py +30 -3
- mlrun/serving/v2_serving.py +44 -35
- mlrun/track/trackers/mlflow_tracker.py +5 -0
- mlrun/utils/async_http.py +1 -1
- mlrun/utils/db.py +18 -0
- mlrun/utils/helpers.py +150 -36
- mlrun/utils/http.py +1 -1
- mlrun/utils/notifications/notification/__init__.py +0 -1
- mlrun/utils/notifications/notification/webhook.py +8 -1
- mlrun/utils/notifications/notification_pusher.py +1 -1
- mlrun/utils/v3io_clients.py +2 -2
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc28.dist-info → mlrun-1.7.0rc55.dist-info}/METADATA +153 -66
- {mlrun-1.7.0rc28.dist-info → mlrun-1.7.0rc55.dist-info}/RECORD +131 -134
- {mlrun-1.7.0rc28.dist-info → mlrun-1.7.0rc55.dist-info}/WHEEL +1 -1
- mlrun/feature_store/retrieval/conversion.py +0 -271
- mlrun/model_monitoring/controller_handler.py +0 -37
- mlrun/model_monitoring/evidently_application.py +0 -20
- mlrun/model_monitoring/prometheus.py +0 -216
- {mlrun-1.7.0rc28.dist-info → mlrun-1.7.0rc55.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc28.dist-info → mlrun-1.7.0rc55.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc28.dist-info → mlrun-1.7.0rc55.dist-info}/top_level.txt +0 -0
|
@@ -15,9 +15,11 @@ 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
|
|
22
|
+
import mlrun.run
|
|
21
23
|
from mlrun.common.runtimes.constants import NuclioIngressAddTemplatedIngressModes
|
|
22
24
|
from mlrun.runtimes import RemoteRuntime
|
|
23
25
|
from mlrun.runtimes.nuclio import min_nuclio_versions
|
|
@@ -27,7 +29,7 @@ from mlrun.runtimes.nuclio.api_gateway import (
|
|
|
27
29
|
APIGatewaySpec,
|
|
28
30
|
)
|
|
29
31
|
from mlrun.runtimes.nuclio.function import NuclioSpec, NuclioStatus
|
|
30
|
-
from mlrun.utils import logger
|
|
32
|
+
from mlrun.utils import logger, update_in
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
class ApplicationSpec(NuclioSpec):
|
|
@@ -121,6 +123,11 @@ class ApplicationSpec(NuclioSpec):
|
|
|
121
123
|
state_thresholds=state_thresholds,
|
|
122
124
|
disable_default_http_trigger=disable_default_http_trigger,
|
|
123
125
|
)
|
|
126
|
+
|
|
127
|
+
# Override default min/max replicas (don't assume application is stateless)
|
|
128
|
+
self.min_replicas = min_replicas or 1
|
|
129
|
+
self.max_replicas = max_replicas or 1
|
|
130
|
+
|
|
124
131
|
self.internal_application_port = (
|
|
125
132
|
internal_application_port
|
|
126
133
|
or mlrun.mlconf.function.application.default_sidecar_internal_port
|
|
@@ -149,6 +156,7 @@ class ApplicationStatus(NuclioStatus):
|
|
|
149
156
|
build_pod=None,
|
|
150
157
|
container_image=None,
|
|
151
158
|
application_image=None,
|
|
159
|
+
application_source=None,
|
|
152
160
|
sidecar_name=None,
|
|
153
161
|
api_gateway_name=None,
|
|
154
162
|
api_gateway=None,
|
|
@@ -164,14 +172,16 @@ class ApplicationStatus(NuclioStatus):
|
|
|
164
172
|
container_image=container_image,
|
|
165
173
|
)
|
|
166
174
|
self.application_image = application_image or None
|
|
175
|
+
self.application_source = application_source or None
|
|
167
176
|
self.sidecar_name = sidecar_name or None
|
|
168
177
|
self.api_gateway_name = api_gateway_name or None
|
|
169
|
-
self.api_gateway = api_gateway or None
|
|
178
|
+
self.api_gateway: typing.Optional[APIGateway] = api_gateway or None
|
|
170
179
|
self.url = url or None
|
|
171
180
|
|
|
172
181
|
|
|
173
182
|
class ApplicationRuntime(RemoteRuntime):
|
|
174
183
|
kind = "application"
|
|
184
|
+
reverse_proxy_image = None
|
|
175
185
|
|
|
176
186
|
@min_nuclio_versions("1.13.1")
|
|
177
187
|
def __init__(self, spec=None, metadata=None):
|
|
@@ -250,6 +260,15 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
250
260
|
"Application sidecar spec must include a command if args are provided"
|
|
251
261
|
)
|
|
252
262
|
|
|
263
|
+
def prepare_image_for_deploy(self):
|
|
264
|
+
if self.spec.build.source and self.spec.build.load_source_on_run:
|
|
265
|
+
logger.warning(
|
|
266
|
+
"Application runtime requires loading the source into the application image. "
|
|
267
|
+
f"Even though {self.spec.build.load_source_on_run=}, loading on build will be forced."
|
|
268
|
+
)
|
|
269
|
+
self.spec.build.load_source_on_run = False
|
|
270
|
+
super().prepare_image_for_deploy()
|
|
271
|
+
|
|
253
272
|
def deploy(
|
|
254
273
|
self,
|
|
255
274
|
project="",
|
|
@@ -263,31 +282,31 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
263
282
|
is_kfp=False,
|
|
264
283
|
mlrun_version_specifier=None,
|
|
265
284
|
show_on_failure: bool = False,
|
|
266
|
-
|
|
267
|
-
authentication_mode: schemas.APIGatewayAuthenticationMode = None,
|
|
268
|
-
authentication_creds: tuple[str] = None,
|
|
285
|
+
create_default_api_gateway: bool = True,
|
|
269
286
|
):
|
|
270
287
|
"""
|
|
271
288
|
Deploy function, builds the application image if required (self.requires_build()) or force_build is True,
|
|
272
289
|
Once the image is built, the function is deployed.
|
|
273
|
-
|
|
274
|
-
:param
|
|
275
|
-
:param
|
|
276
|
-
:param
|
|
277
|
-
:param
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
:param
|
|
281
|
-
:param
|
|
282
|
-
:param
|
|
283
|
-
:param
|
|
284
|
-
:param
|
|
285
|
-
:param
|
|
286
|
-
:param
|
|
287
|
-
|
|
288
|
-
|
|
290
|
+
|
|
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)
|
|
289
308
|
"""
|
|
290
|
-
if self.requires_build() or force_build:
|
|
309
|
+
if (self.requires_build() and not self.spec.image) or force_build:
|
|
291
310
|
self._fill_credentials()
|
|
292
311
|
self._build_application_image(
|
|
293
312
|
builder_env=builder_env,
|
|
@@ -300,38 +319,57 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
300
319
|
show_on_failure=show_on_failure,
|
|
301
320
|
)
|
|
302
321
|
|
|
303
|
-
self
|
|
322
|
+
# This is a class method that accepts a function instance, so we pass self as the function instance
|
|
323
|
+
self._ensure_reverse_proxy_configurations(self)
|
|
304
324
|
self._configure_application_sidecar()
|
|
305
325
|
|
|
306
|
-
#
|
|
307
|
-
name_tag = tag or self.metadata.tag
|
|
308
|
-
self.status.api_gateway_name = (
|
|
309
|
-
f"{self.metadata.name}-{name_tag}" if name_tag else self.metadata.name
|
|
310
|
-
)
|
|
326
|
+
# We only allow accessing the application via the API Gateway
|
|
311
327
|
self.spec.add_templated_ingress_host_mode = (
|
|
312
328
|
NuclioIngressAddTemplatedIngressModes.never
|
|
313
329
|
)
|
|
314
330
|
|
|
315
331
|
super().deploy(
|
|
316
|
-
project,
|
|
317
|
-
tag,
|
|
318
|
-
verbose,
|
|
319
|
-
auth_info,
|
|
320
|
-
builder_env,
|
|
332
|
+
project=project,
|
|
333
|
+
tag=tag,
|
|
334
|
+
verbose=verbose,
|
|
335
|
+
auth_info=auth_info,
|
|
336
|
+
builder_env=builder_env,
|
|
321
337
|
)
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
self.create_api_gateway(
|
|
325
|
-
name=self.status.api_gateway_name,
|
|
326
|
-
ports=ports,
|
|
327
|
-
authentication_mode=authentication_mode,
|
|
328
|
-
authentication_creds=authentication_creds,
|
|
338
|
+
logger.info(
|
|
339
|
+
"Successfully deployed function.",
|
|
329
340
|
)
|
|
330
341
|
|
|
342
|
+
# Restore the source in case it was removed to make nuclio not consider it when building
|
|
343
|
+
if not self.spec.build.source and self.status.application_source:
|
|
344
|
+
self.spec.build.source = self.status.application_source
|
|
345
|
+
self.save(versioned=False)
|
|
346
|
+
|
|
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
|
|
364
|
+
|
|
331
365
|
def with_source_archive(
|
|
332
|
-
self,
|
|
366
|
+
self,
|
|
367
|
+
source,
|
|
368
|
+
workdir=None,
|
|
369
|
+
pull_at_runtime: bool = False,
|
|
370
|
+
target_dir: str = None,
|
|
333
371
|
):
|
|
334
|
-
"""load the code from git/tar/zip archive at
|
|
372
|
+
"""load the code from git/tar/zip archive at build
|
|
335
373
|
|
|
336
374
|
:param source: valid absolute path or URL to git, zip, or tar file, e.g.
|
|
337
375
|
git://github.com/mlrun/something.git
|
|
@@ -339,18 +377,50 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
339
377
|
note path source must exist on the image or exist locally when run is local
|
|
340
378
|
(it is recommended to use 'workdir' when source is a filepath instead)
|
|
341
379
|
:param workdir: working dir relative to the archive root (e.g. './subdir') or absolute to the image root
|
|
342
|
-
:param pull_at_runtime:
|
|
380
|
+
:param pull_at_runtime: currently not supported, source must be loaded into the image during the build process
|
|
343
381
|
:param target_dir: target dir on runtime pod or repo clone / archive extraction
|
|
344
382
|
"""
|
|
383
|
+
if pull_at_runtime:
|
|
384
|
+
logger.warning(
|
|
385
|
+
f"{pull_at_runtime=} is currently not supported for application runtime "
|
|
386
|
+
"and will be overridden to False",
|
|
387
|
+
pull_at_runtime=pull_at_runtime,
|
|
388
|
+
)
|
|
389
|
+
|
|
345
390
|
self._configure_mlrun_build_with_source(
|
|
346
391
|
source=source,
|
|
347
392
|
workdir=workdir,
|
|
348
|
-
pull_at_runtime=
|
|
393
|
+
pull_at_runtime=False,
|
|
349
394
|
target_dir=target_dir,
|
|
350
395
|
)
|
|
351
396
|
|
|
352
|
-
|
|
353
|
-
|
|
397
|
+
def from_image(self, image):
|
|
398
|
+
"""
|
|
399
|
+
Deploy the function with an existing nuclio processor image.
|
|
400
|
+
This applies only for the reverse proxy and not the application image.
|
|
401
|
+
|
|
402
|
+
:param image: image name
|
|
403
|
+
"""
|
|
404
|
+
super().from_image(image)
|
|
405
|
+
# nuclio implementation detail - when providing the image and emptying out the source code and build source,
|
|
406
|
+
# nuclio skips rebuilding the image and simply takes the prebuilt image
|
|
407
|
+
self.spec.build.functionSourceCode = ""
|
|
408
|
+
self.status.application_source = self.spec.build.source
|
|
409
|
+
self.spec.build.source = ""
|
|
410
|
+
|
|
411
|
+
# save the image in the status, so we won't repopulate the function source code
|
|
412
|
+
self.status.container_image = image
|
|
413
|
+
|
|
414
|
+
# ensure golang runtime and handler for the reverse proxy
|
|
415
|
+
self.spec.nuclio_runtime = "golang"
|
|
416
|
+
update_in(
|
|
417
|
+
self.spec.base_spec,
|
|
418
|
+
"spec.handler",
|
|
419
|
+
"main:Handler",
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
@staticmethod
|
|
423
|
+
def get_filename_and_handler() -> (str, str):
|
|
354
424
|
reverse_proxy_file_path = pathlib.Path(__file__).parent / "reverse_proxy.go"
|
|
355
425
|
return str(reverse_proxy_file_path), "Handler"
|
|
356
426
|
|
|
@@ -358,16 +428,55 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
358
428
|
self,
|
|
359
429
|
name: str = None,
|
|
360
430
|
path: str = None,
|
|
361
|
-
|
|
431
|
+
direct_port_access: bool = False,
|
|
362
432
|
authentication_mode: schemas.APIGatewayAuthenticationMode = None,
|
|
363
|
-
authentication_creds: tuple[str] = None,
|
|
433
|
+
authentication_creds: tuple[str, str] = None,
|
|
434
|
+
ssl_redirect: bool = None,
|
|
435
|
+
set_as_default: bool = False,
|
|
436
|
+
gateway_timeout: typing.Optional[int] = None,
|
|
364
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
|
+
|
|
442
|
+
:param name: The name of the API gateway
|
|
443
|
+
:param path: Optional path of the API gateway, default value is "/".
|
|
444
|
+
The given path should be supported by the deployed application
|
|
445
|
+
:param direct_port_access: Set True to allow direct port access to the application sidecar
|
|
446
|
+
:param authentication_mode: API Gateway authentication mode
|
|
447
|
+
:param authentication_creds: API Gateway basic authentication credentials as a tuple (username, password)
|
|
448
|
+
:param ssl_redirect: Set True to force SSL redirect, False to disable. Defaults to
|
|
449
|
+
mlrun.mlconf.force_api_gateway_ssl_redirect()
|
|
450
|
+
:param set_as_default: Set the API gateway as the default for the application (`status.api_gateway`)
|
|
451
|
+
:param gateway_timeout: nginx ingress timeout in sec (request timeout, when will the gateway return an
|
|
452
|
+
error)
|
|
453
|
+
:return: The API gateway URL
|
|
454
|
+
"""
|
|
455
|
+
if not name:
|
|
456
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
457
|
+
"API gateway name must be specified."
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
if not set_as_default and name == self.resolve_default_api_gateway_name():
|
|
461
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
462
|
+
f"Non-default API gateway cannot use the default gateway name, {name=}."
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
if (
|
|
466
|
+
authentication_mode == schemas.APIGatewayAuthenticationMode.basic
|
|
467
|
+
and not authentication_creds
|
|
468
|
+
):
|
|
469
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
470
|
+
"Authentication credentials not provided"
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
ports = self.spec.internal_application_port if direct_port_access else []
|
|
474
|
+
|
|
365
475
|
api_gateway = APIGateway(
|
|
366
476
|
APIGatewayMetadata(
|
|
367
477
|
name=name,
|
|
368
478
|
namespace=self.metadata.namespace,
|
|
369
|
-
labels=self.metadata.labels,
|
|
370
|
-
annotations=self.metadata.annotations,
|
|
479
|
+
labels=self.metadata.labels.copy(),
|
|
371
480
|
),
|
|
372
481
|
APIGatewaySpec(
|
|
373
482
|
functions=[self],
|
|
@@ -377,6 +486,14 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
377
486
|
),
|
|
378
487
|
)
|
|
379
488
|
|
|
489
|
+
api_gateway.with_gateway_timeout(gateway_timeout)
|
|
490
|
+
if ssl_redirect is None:
|
|
491
|
+
ssl_redirect = mlrun.mlconf.force_api_gateway_ssl_redirect()
|
|
492
|
+
if ssl_redirect:
|
|
493
|
+
# Force ssl redirect so that the application is only accessible via https
|
|
494
|
+
api_gateway.with_force_ssl_redirect()
|
|
495
|
+
|
|
496
|
+
# Add authentication if required
|
|
380
497
|
authentication_mode = (
|
|
381
498
|
authentication_mode
|
|
382
499
|
or mlrun.mlconf.function.application.default_authentication_mode
|
|
@@ -390,28 +507,64 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
390
507
|
api_gateway_scheme = db.store_api_gateway(
|
|
391
508
|
api_gateway=api_gateway.to_scheme(), project=self.metadata.project
|
|
392
509
|
)
|
|
393
|
-
|
|
510
|
+
|
|
511
|
+
if set_as_default:
|
|
394
512
|
self.status.api_gateway_name = api_gateway_scheme.metadata.name
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
513
|
+
self.status.api_gateway = APIGateway.from_scheme(api_gateway_scheme)
|
|
514
|
+
self.status.api_gateway.wait_for_readiness()
|
|
515
|
+
self.url = self.status.api_gateway.invoke_url
|
|
516
|
+
url = self.url
|
|
517
|
+
else:
|
|
518
|
+
api_gateway = APIGateway.from_scheme(api_gateway_scheme)
|
|
519
|
+
api_gateway.wait_for_readiness()
|
|
520
|
+
url = api_gateway.invoke_url
|
|
521
|
+
# Update application status (enriches invocation url)
|
|
522
|
+
self._get_state(raise_on_exception=False)
|
|
523
|
+
|
|
524
|
+
logger.info("Successfully created API gateway", url=url)
|
|
525
|
+
return url
|
|
526
|
+
|
|
527
|
+
def delete_api_gateway(self, name: str):
|
|
528
|
+
"""
|
|
529
|
+
Delete API gateway by name.
|
|
530
|
+
Refreshes the application status to update api gateway and invocation URLs.
|
|
531
|
+
:param name: The API gateway name
|
|
532
|
+
"""
|
|
533
|
+
self._get_db().delete_api_gateway(name=name, project=self.metadata.project)
|
|
534
|
+
if name == self.status.api_gateway_name:
|
|
535
|
+
self.status.api_gateway_name = None
|
|
536
|
+
self.status.api_gateway = None
|
|
537
|
+
self._get_state()
|
|
398
538
|
|
|
399
539
|
def invoke(
|
|
400
540
|
self,
|
|
401
|
-
path: str,
|
|
402
|
-
body: typing.Union[str, bytes, dict] = None,
|
|
541
|
+
path: str = "",
|
|
542
|
+
body: typing.Optional[typing.Union[str, bytes, dict]] = None,
|
|
403
543
|
method: str = None,
|
|
404
544
|
headers: dict = None,
|
|
405
545
|
dashboard: str = "",
|
|
406
546
|
force_external_address: bool = False,
|
|
407
547
|
auth_info: schemas.AuthInfo = None,
|
|
408
548
|
mock: bool = None,
|
|
549
|
+
credentials: tuple[str, str] = None,
|
|
409
550
|
**http_client_kwargs,
|
|
410
551
|
):
|
|
411
552
|
self._sync_api_gateway()
|
|
553
|
+
|
|
412
554
|
# If the API Gateway is not ready or not set, try to invoke the function directly (without the API Gateway)
|
|
413
555
|
if not self.status.api_gateway:
|
|
414
|
-
|
|
556
|
+
logger.warning(
|
|
557
|
+
"Default API gateway is not configured, invoking function invocation URL."
|
|
558
|
+
)
|
|
559
|
+
# create a requests auth object if credentials are provided and not already set in the http client kwargs
|
|
560
|
+
auth = http_client_kwargs.pop("auth", None) or (
|
|
561
|
+
nuclio.auth.AuthInfo(
|
|
562
|
+
username=credentials[0], password=credentials[1]
|
|
563
|
+
).to_requests_auth()
|
|
564
|
+
if credentials
|
|
565
|
+
else None
|
|
566
|
+
)
|
|
567
|
+
return super().invoke(
|
|
415
568
|
path,
|
|
416
569
|
body,
|
|
417
570
|
method,
|
|
@@ -420,21 +573,84 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
420
573
|
force_external_address,
|
|
421
574
|
auth_info,
|
|
422
575
|
mock,
|
|
576
|
+
auth=auth,
|
|
423
577
|
**http_client_kwargs,
|
|
424
578
|
)
|
|
425
579
|
|
|
426
|
-
credentials = (auth_info.username, auth_info.password) if auth_info else None
|
|
427
|
-
|
|
428
580
|
if not method:
|
|
429
581
|
method = "POST" if body else "GET"
|
|
582
|
+
|
|
430
583
|
return self.status.api_gateway.invoke(
|
|
431
584
|
method=method,
|
|
432
585
|
headers=headers,
|
|
433
586
|
credentials=credentials,
|
|
434
587
|
path=path,
|
|
588
|
+
body=body,
|
|
435
589
|
**http_client_kwargs,
|
|
436
590
|
)
|
|
437
591
|
|
|
592
|
+
@classmethod
|
|
593
|
+
def deploy_reverse_proxy_image(cls):
|
|
594
|
+
"""
|
|
595
|
+
Build the reverse proxy image and save it.
|
|
596
|
+
The reverse proxy image is used to route requests to the application sidecar.
|
|
597
|
+
This is useful when you want to decrease build time by building the application image only once.
|
|
598
|
+
|
|
599
|
+
:param use_cache: Use the cache when building the image
|
|
600
|
+
"""
|
|
601
|
+
# create a function that includes only the reverse proxy, without the application
|
|
602
|
+
|
|
603
|
+
reverse_proxy_func = mlrun.run.new_function(
|
|
604
|
+
name="reverse-proxy-temp", kind="remote"
|
|
605
|
+
)
|
|
606
|
+
# default max replicas is 4, we only need one replica for the reverse proxy
|
|
607
|
+
reverse_proxy_func.spec.max_replicas = 1
|
|
608
|
+
|
|
609
|
+
# the reverse proxy image should not be based on another image
|
|
610
|
+
reverse_proxy_func.set_config("spec.build.baseImage", None)
|
|
611
|
+
reverse_proxy_func.spec.image = ""
|
|
612
|
+
reverse_proxy_func.spec.build.base_image = ""
|
|
613
|
+
|
|
614
|
+
cls._ensure_reverse_proxy_configurations(reverse_proxy_func)
|
|
615
|
+
reverse_proxy_func.deploy()
|
|
616
|
+
|
|
617
|
+
# save the created container image
|
|
618
|
+
cls.reverse_proxy_image = reverse_proxy_func.status.container_image
|
|
619
|
+
|
|
620
|
+
# delete the function to avoid cluttering the project
|
|
621
|
+
mlrun.get_run_db().delete_function(
|
|
622
|
+
reverse_proxy_func.metadata.name, reverse_proxy_func.metadata.project
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
def resolve_default_api_gateway_name(self):
|
|
626
|
+
return (
|
|
627
|
+
f"{self.metadata.name}-{self.metadata.tag}"
|
|
628
|
+
if self.metadata.tag
|
|
629
|
+
else self.metadata.name
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
@min_nuclio_versions("1.13.1")
|
|
633
|
+
def disable_default_http_trigger(
|
|
634
|
+
self,
|
|
635
|
+
):
|
|
636
|
+
raise mlrun.runtimes.RunError(
|
|
637
|
+
"Application runtime does not support disabling the default HTTP trigger"
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
@min_nuclio_versions("1.13.1")
|
|
641
|
+
def enable_default_http_trigger(
|
|
642
|
+
self,
|
|
643
|
+
):
|
|
644
|
+
pass
|
|
645
|
+
|
|
646
|
+
def _run(self, runobj: "mlrun.RunObject", execution):
|
|
647
|
+
raise mlrun.runtimes.RunError(
|
|
648
|
+
"Application runtime .run() is not yet supported. Use .invoke() instead."
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
def _enrich_command_from_status(self):
|
|
652
|
+
pass
|
|
653
|
+
|
|
438
654
|
def _build_application_image(
|
|
439
655
|
self,
|
|
440
656
|
builder_env: dict = None,
|
|
@@ -454,6 +670,13 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
454
670
|
args=self.spec.args,
|
|
455
671
|
)
|
|
456
672
|
|
|
673
|
+
if self.spec.build.source in [".", "./"]:
|
|
674
|
+
logger.info(
|
|
675
|
+
"The application is configured to use the project's source. "
|
|
676
|
+
"Application runtime requires loading the source into the application image. "
|
|
677
|
+
"Loading on build will be forced regardless of whether 'pull_at_runtime=True' was configured."
|
|
678
|
+
)
|
|
679
|
+
|
|
457
680
|
with_mlrun = self._resolve_build_with_mlrun(with_mlrun)
|
|
458
681
|
return self._build_image(
|
|
459
682
|
builder_env=builder_env,
|
|
@@ -466,21 +689,29 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
466
689
|
with_mlrun=with_mlrun,
|
|
467
690
|
)
|
|
468
691
|
|
|
469
|
-
|
|
470
|
-
|
|
692
|
+
@staticmethod
|
|
693
|
+
def _ensure_reverse_proxy_configurations(function: RemoteRuntime):
|
|
694
|
+
if function.spec.build.functionSourceCode or function.status.container_image:
|
|
471
695
|
return
|
|
472
696
|
|
|
473
697
|
filename, handler = ApplicationRuntime.get_filename_and_handler()
|
|
474
698
|
name, spec, code = nuclio.build_file(
|
|
475
699
|
filename,
|
|
476
|
-
name=
|
|
700
|
+
name=function.metadata.name,
|
|
477
701
|
handler=handler,
|
|
478
702
|
)
|
|
479
|
-
|
|
480
|
-
|
|
703
|
+
function.spec.function_handler = mlrun.utils.get_in(spec, "spec.handler")
|
|
704
|
+
function.spec.build.functionSourceCode = mlrun.utils.get_in(
|
|
481
705
|
spec, "spec.build.functionSourceCode"
|
|
482
706
|
)
|
|
483
|
-
|
|
707
|
+
function.spec.nuclio_runtime = mlrun.utils.get_in(spec, "spec.runtime")
|
|
708
|
+
|
|
709
|
+
# default the reverse proxy logger level to info
|
|
710
|
+
logger_sinks_key = "spec.loggerSinks"
|
|
711
|
+
if not function.spec.config.get(logger_sinks_key):
|
|
712
|
+
function.set_config(
|
|
713
|
+
logger_sinks_key, [{"level": "info", "sink": "myStdoutLoggerSink"}]
|
|
714
|
+
)
|
|
484
715
|
|
|
485
716
|
def _configure_application_sidecar(self):
|
|
486
717
|
# Save the application image in the status to allow overriding it with the reverse proxy entry point
|
|
@@ -491,11 +722,12 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
491
722
|
self.status.application_image = self.spec.image
|
|
492
723
|
self.spec.image = ""
|
|
493
724
|
|
|
494
|
-
if
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
725
|
+
# reuse the reverse proxy image if it was built before
|
|
726
|
+
if (
|
|
727
|
+
reverse_proxy_image := self.status.container_image
|
|
728
|
+
or self.reverse_proxy_image
|
|
729
|
+
):
|
|
730
|
+
self.from_image(reverse_proxy_image)
|
|
499
731
|
|
|
500
732
|
self.status.sidecar_name = f"{self.metadata.name}-sidecar"
|
|
501
733
|
self.with_sidecar(
|
|
@@ -508,6 +740,11 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
508
740
|
self.set_env("SIDECAR_PORT", self.spec.internal_application_port)
|
|
509
741
|
self.set_env("SIDECAR_HOST", "http://localhost")
|
|
510
742
|
|
|
743
|
+
# configure the sidecar container as the default container for logging purposes
|
|
744
|
+
self.metadata.annotations["kubectl.kubernetes.io/default-container"] = (
|
|
745
|
+
self.status.sidecar_name
|
|
746
|
+
)
|
|
747
|
+
|
|
511
748
|
def _sync_api_gateway(self):
|
|
512
749
|
if not self.status.api_gateway_name:
|
|
513
750
|
return
|