mlrun 1.7.0rc16__py3-none-any.whl → 1.7.0rc17__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 +26 -23
- mlrun/artifacts/model.py +1 -1
- mlrun/common/schemas/__init__.py +7 -1
- mlrun/common/schemas/alert.py +18 -1
- mlrun/common/schemas/model_monitoring/constants.py +1 -0
- mlrun/common/schemas/project.py +3 -1
- mlrun/config.py +7 -3
- mlrun/db/base.py +1 -1
- mlrun/db/nopdb.py +5 -2
- mlrun/lists.py +2 -0
- mlrun/model.py +8 -6
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +38 -0
- mlrun/model_monitoring/writer.py +4 -4
- mlrun/projects/project.py +7 -3
- mlrun/runtimes/__init__.py +1 -0
- mlrun/runtimes/nuclio/api_gateway.py +97 -77
- mlrun/runtimes/nuclio/application/application.py +160 -7
- mlrun/runtimes/nuclio/function.py +18 -12
- mlrun/track/tracker.py +2 -1
- mlrun/utils/helpers.py +8 -2
- mlrun/utils/logger.py +11 -6
- mlrun/utils/notifications/notification_pusher.py +7 -1
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc16.dist-info → mlrun-1.7.0rc17.dist-info}/METADATA +2 -2
- {mlrun-1.7.0rc16.dist-info → mlrun-1.7.0rc17.dist-info}/RECORD +29 -29
- {mlrun-1.7.0rc16.dist-info → mlrun-1.7.0rc17.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc16.dist-info → mlrun-1.7.0rc17.dist-info}/WHEEL +0 -0
- {mlrun-1.7.0rc16.dist-info → mlrun-1.7.0rc17.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc16.dist-info → mlrun-1.7.0rc17.dist-info}/top_level.txt +0 -0
|
@@ -18,33 +18,30 @@ from urllib.parse import urljoin
|
|
|
18
18
|
|
|
19
19
|
import requests
|
|
20
20
|
from nuclio.auth import AuthInfo as NuclioAuthInfo
|
|
21
|
-
from
|
|
21
|
+
from nuclio.auth import AuthKinds as NuclioAuthKinds
|
|
22
22
|
|
|
23
23
|
import mlrun
|
|
24
|
-
import mlrun.common.schemas
|
|
24
|
+
import mlrun.common.schemas as schemas
|
|
25
|
+
import mlrun.common.types
|
|
26
|
+
from mlrun.model import ModelObj
|
|
25
27
|
from mlrun.platforms.iguazio import min_iguazio_versions
|
|
28
|
+
from mlrun.utils import logger
|
|
26
29
|
|
|
27
|
-
from
|
|
28
|
-
from ..utils import logger
|
|
29
|
-
from .function import RemoteRuntime, get_fullname, min_nuclio_versions
|
|
30
|
-
from .serving import ServingRuntime
|
|
30
|
+
from .function import get_fullname, min_nuclio_versions
|
|
31
31
|
|
|
32
|
-
NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH = "basicAuth"
|
|
33
|
-
NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_NONE = "none"
|
|
34
|
-
NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_ACCESS_KEY = "accessKey"
|
|
35
32
|
PROJECT_NAME_LABEL = "nuclio.io/project-name"
|
|
36
33
|
|
|
37
34
|
|
|
38
35
|
class APIGatewayAuthenticator(typing.Protocol):
|
|
39
36
|
@property
|
|
40
37
|
def authentication_mode(self) -> str:
|
|
41
|
-
return
|
|
38
|
+
return schemas.APIGatewayAuthenticationMode.none.value
|
|
42
39
|
|
|
43
40
|
@classmethod
|
|
44
|
-
def from_scheme(cls, api_gateway_spec:
|
|
41
|
+
def from_scheme(cls, api_gateway_spec: schemas.APIGatewaySpec):
|
|
45
42
|
if (
|
|
46
43
|
api_gateway_spec.authenticationMode
|
|
47
|
-
==
|
|
44
|
+
== schemas.APIGatewayAuthenticationMode.basic.value
|
|
48
45
|
):
|
|
49
46
|
if api_gateway_spec.authentication:
|
|
50
47
|
return BasicAuth(
|
|
@@ -55,7 +52,7 @@ class APIGatewayAuthenticator(typing.Protocol):
|
|
|
55
52
|
return BasicAuth()
|
|
56
53
|
elif (
|
|
57
54
|
api_gateway_spec.authenticationMode
|
|
58
|
-
==
|
|
55
|
+
== schemas.APIGatewayAuthenticationMode.access_key.value
|
|
59
56
|
):
|
|
60
57
|
return AccessKeyAuth()
|
|
61
58
|
else:
|
|
@@ -63,7 +60,7 @@ class APIGatewayAuthenticator(typing.Protocol):
|
|
|
63
60
|
|
|
64
61
|
def to_scheme(
|
|
65
62
|
self,
|
|
66
|
-
) -> Optional[dict[str, Optional[
|
|
63
|
+
) -> Optional[dict[str, Optional[schemas.APIGatewayBasicAuth]]]:
|
|
67
64
|
return None
|
|
68
65
|
|
|
69
66
|
|
|
@@ -89,13 +86,13 @@ class BasicAuth(APIGatewayAuthenticator):
|
|
|
89
86
|
|
|
90
87
|
@property
|
|
91
88
|
def authentication_mode(self) -> str:
|
|
92
|
-
return
|
|
89
|
+
return schemas.APIGatewayAuthenticationMode.basic.value
|
|
93
90
|
|
|
94
91
|
def to_scheme(
|
|
95
92
|
self,
|
|
96
|
-
) -> Optional[dict[str, Optional[
|
|
93
|
+
) -> Optional[dict[str, Optional[schemas.APIGatewayBasicAuth]]]:
|
|
97
94
|
return {
|
|
98
|
-
"basicAuth":
|
|
95
|
+
"basicAuth": schemas.APIGatewayBasicAuth(
|
|
99
96
|
username=self._username, password=self._password
|
|
100
97
|
)
|
|
101
98
|
}
|
|
@@ -108,7 +105,7 @@ class AccessKeyAuth(APIGatewayAuthenticator):
|
|
|
108
105
|
|
|
109
106
|
@property
|
|
110
107
|
def authentication_mode(self) -> str:
|
|
111
|
-
return
|
|
108
|
+
return schemas.APIGatewayAuthenticationMode.access_key.value
|
|
112
109
|
|
|
113
110
|
|
|
114
111
|
class APIGatewayMetadata(ModelObj):
|
|
@@ -156,17 +153,17 @@ class APIGatewaySpec(ModelObj):
|
|
|
156
153
|
def __init__(
|
|
157
154
|
self,
|
|
158
155
|
functions: Union[
|
|
159
|
-
list[
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
],
|
|
167
|
-
RemoteRuntime,
|
|
168
|
-
ServingRuntime,
|
|
156
|
+
list[
|
|
157
|
+
Union[
|
|
158
|
+
str,
|
|
159
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
160
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
161
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
162
|
+
]
|
|
169
163
|
],
|
|
164
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
165
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
166
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
170
167
|
],
|
|
171
168
|
project: str = None,
|
|
172
169
|
description: str = "",
|
|
@@ -181,7 +178,8 @@ class APIGatewaySpec(ModelObj):
|
|
|
181
178
|
Can be a list of function names (["my-func1", "my-func2"])
|
|
182
179
|
or a list or a single entity of
|
|
183
180
|
:py:class:`~mlrun.runtimes.nuclio.function.RemoteRuntime` OR
|
|
184
|
-
:py:class:`~mlrun.runtimes.nuclio.serving.ServingRuntime`
|
|
181
|
+
:py:class:`~mlrun.runtimes.nuclio.serving.ServingRuntime` OR
|
|
182
|
+
:py:class:`~mlrun.runtimes.nuclio.application.ApplicationRuntime`
|
|
185
183
|
:param project: The project name
|
|
186
184
|
:param description: Optional description of the API gateway
|
|
187
185
|
:param path: Optional path of the API gateway, default value is "/"
|
|
@@ -207,17 +205,17 @@ class APIGatewaySpec(ModelObj):
|
|
|
207
205
|
self,
|
|
208
206
|
project: str,
|
|
209
207
|
functions: Union[
|
|
210
|
-
list[
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
],
|
|
218
|
-
RemoteRuntime,
|
|
219
|
-
ServingRuntime,
|
|
208
|
+
list[
|
|
209
|
+
Union[
|
|
210
|
+
str,
|
|
211
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
212
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
213
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
214
|
+
]
|
|
220
215
|
],
|
|
216
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
217
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
218
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
221
219
|
],
|
|
222
220
|
canary: Optional[list[int]] = None,
|
|
223
221
|
ports: Optional[list[int]] = None,
|
|
@@ -260,16 +258,17 @@ class APIGatewaySpec(ModelObj):
|
|
|
260
258
|
def _validate_functions(
|
|
261
259
|
project: str,
|
|
262
260
|
functions: Union[
|
|
263
|
-
list[
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
],
|
|
271
|
-
Union[RemoteRuntime, ServingRuntime],
|
|
261
|
+
list[
|
|
262
|
+
Union[
|
|
263
|
+
str,
|
|
264
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
265
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
266
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
267
|
+
]
|
|
272
268
|
],
|
|
269
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
270
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
271
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
273
272
|
],
|
|
274
273
|
):
|
|
275
274
|
if not isinstance(functions, list):
|
|
@@ -348,7 +347,8 @@ class APIGateway(ModelObj):
|
|
|
348
347
|
self,
|
|
349
348
|
method="POST",
|
|
350
349
|
headers: dict = None,
|
|
351
|
-
|
|
350
|
+
credentials: Optional[tuple[str, str]] = None,
|
|
351
|
+
path: Optional[str] = None,
|
|
352
352
|
**kwargs,
|
|
353
353
|
):
|
|
354
354
|
"""
|
|
@@ -356,7 +356,9 @@ class APIGateway(ModelObj):
|
|
|
356
356
|
|
|
357
357
|
:param method: (str, optional) The HTTP method for the invocation.
|
|
358
358
|
:param headers: (dict, optional) The HTTP headers for the invocation.
|
|
359
|
-
:param
|
|
359
|
+
:param credentials: (Optional[tuple[str, str]], optional) The (username,password) for the invocation if required
|
|
360
|
+
can also be set by the environment variable (_, V3IO_ACCESS_KEY) for access key authentication.
|
|
361
|
+
:param path: (str, optional) The sub-path for the invocation.
|
|
360
362
|
:param kwargs: (dict) Additional keyword arguments.
|
|
361
363
|
|
|
362
364
|
:return: The response from the API gateway invocation.
|
|
@@ -373,29 +375,44 @@ class APIGateway(ModelObj):
|
|
|
373
375
|
f"API gateway is not ready. " f"Current state: {self.state}"
|
|
374
376
|
)
|
|
375
377
|
|
|
378
|
+
auth = None
|
|
379
|
+
|
|
376
380
|
if (
|
|
377
381
|
self.spec.authentication.authentication_mode
|
|
378
|
-
==
|
|
382
|
+
== schemas.APIGatewayAuthenticationMode.basic.value
|
|
379
383
|
):
|
|
380
|
-
if not
|
|
384
|
+
if not credentials:
|
|
381
385
|
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
382
386
|
"API Gateway invocation requires authentication. Please pass credentials"
|
|
383
387
|
)
|
|
384
|
-
auth =
|
|
388
|
+
auth = NuclioAuthInfo(
|
|
389
|
+
username=credentials[0], password=credentials[1]
|
|
390
|
+
).to_requests_auth()
|
|
385
391
|
|
|
386
392
|
if (
|
|
387
393
|
self.spec.authentication.authentication_mode
|
|
388
|
-
==
|
|
394
|
+
== schemas.APIGatewayAuthenticationMode.access_key.value
|
|
389
395
|
):
|
|
390
396
|
# inject access key from env
|
|
391
|
-
|
|
392
|
-
|
|
397
|
+
if credentials:
|
|
398
|
+
auth = NuclioAuthInfo(
|
|
399
|
+
username=credentials[0],
|
|
400
|
+
password=credentials[1],
|
|
401
|
+
mode=NuclioAuthKinds.iguazio,
|
|
402
|
+
).to_requests_auth()
|
|
403
|
+
else:
|
|
404
|
+
auth = NuclioAuthInfo().from_envvar().to_requests_auth()
|
|
405
|
+
if not auth:
|
|
406
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
407
|
+
"API Gateway invocation requires authentication. Please set V3IO_ACCESS_KEY env var"
|
|
408
|
+
)
|
|
409
|
+
url = urljoin(self.invoke_url, path or "")
|
|
393
410
|
return requests.request(
|
|
394
411
|
method=method,
|
|
395
|
-
url=
|
|
412
|
+
url=url,
|
|
396
413
|
headers=headers or {},
|
|
397
|
-
**kwargs,
|
|
398
414
|
auth=auth,
|
|
415
|
+
**kwargs,
|
|
399
416
|
)
|
|
400
417
|
|
|
401
418
|
def wait_for_readiness(self, max_wait_time=90):
|
|
@@ -420,10 +437,10 @@ class APIGateway(ModelObj):
|
|
|
420
437
|
)
|
|
421
438
|
|
|
422
439
|
def is_ready(self):
|
|
423
|
-
if self.state is not
|
|
440
|
+
if self.state is not schemas.api_gateway.APIGatewayState.ready:
|
|
424
441
|
# try to sync the state
|
|
425
442
|
self.sync()
|
|
426
|
-
return self.state ==
|
|
443
|
+
return self.state == schemas.api_gateway.APIGatewayState.ready
|
|
427
444
|
|
|
428
445
|
def sync(self):
|
|
429
446
|
"""
|
|
@@ -461,13 +478,17 @@ class APIGateway(ModelObj):
|
|
|
461
478
|
def with_canary(
|
|
462
479
|
self,
|
|
463
480
|
functions: Union[
|
|
464
|
-
list[str],
|
|
465
481
|
list[
|
|
466
482
|
Union[
|
|
467
|
-
|
|
468
|
-
|
|
483
|
+
str,
|
|
484
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
485
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
486
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
469
487
|
]
|
|
470
488
|
],
|
|
489
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
490
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
491
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
471
492
|
],
|
|
472
493
|
canary: list[int],
|
|
473
494
|
):
|
|
@@ -478,7 +499,8 @@ class APIGateway(ModelObj):
|
|
|
478
499
|
Can be a list of function names (["my-func1", "my-func2"])
|
|
479
500
|
or a list of nuclio functions of types
|
|
480
501
|
:py:class:`~mlrun.runtimes.nuclio.function.RemoteRuntime` OR
|
|
481
|
-
:py:class:`~mlrun.runtimes.nuclio.serving.ServingRuntime`
|
|
502
|
+
:py:class:`~mlrun.runtimes.nuclio.serving.ServingRuntime` OR
|
|
503
|
+
:py:class:`~mlrun.runtimes.nuclio.application.ApplicationRuntime`
|
|
482
504
|
:param canary: The canary percents for the API gateway of type list[int]; for instance: [20,80]
|
|
483
505
|
|
|
484
506
|
"""
|
|
@@ -503,13 +525,13 @@ class APIGateway(ModelObj):
|
|
|
503
525
|
)
|
|
504
526
|
|
|
505
527
|
@classmethod
|
|
506
|
-
def from_scheme(cls, api_gateway:
|
|
528
|
+
def from_scheme(cls, api_gateway: schemas.APIGateway):
|
|
507
529
|
project = api_gateway.metadata.labels.get(PROJECT_NAME_LABEL)
|
|
508
530
|
functions, canary = cls._resolve_canary(api_gateway.spec.upstreams)
|
|
509
531
|
state = (
|
|
510
532
|
api_gateway.status.state
|
|
511
533
|
if api_gateway.status
|
|
512
|
-
else
|
|
534
|
+
else schemas.APIGatewayState.none
|
|
513
535
|
)
|
|
514
536
|
new_api_gateway = cls(
|
|
515
537
|
metadata=APIGatewayMetadata(
|
|
@@ -528,14 +550,14 @@ class APIGateway(ModelObj):
|
|
|
528
550
|
new_api_gateway.state = state
|
|
529
551
|
return new_api_gateway
|
|
530
552
|
|
|
531
|
-
def to_scheme(self) ->
|
|
553
|
+
def to_scheme(self) -> schemas.APIGateway:
|
|
532
554
|
upstreams = (
|
|
533
555
|
[
|
|
534
|
-
|
|
556
|
+
schemas.APIGatewayUpstream(
|
|
535
557
|
nucliofunction={"name": self.spec.functions[0]},
|
|
536
558
|
percentage=self.spec.canary[0],
|
|
537
559
|
),
|
|
538
|
-
|
|
560
|
+
schemas.APIGatewayUpstream(
|
|
539
561
|
# do not set percent for the second function,
|
|
540
562
|
# so we can define which function to display as a primary one in UI
|
|
541
563
|
nucliofunction={"name": self.spec.functions[1]},
|
|
@@ -543,7 +565,7 @@ class APIGateway(ModelObj):
|
|
|
543
565
|
]
|
|
544
566
|
if self.spec.canary
|
|
545
567
|
else [
|
|
546
|
-
|
|
568
|
+
schemas.APIGatewayUpstream(
|
|
547
569
|
nucliofunction={"name": function_name},
|
|
548
570
|
)
|
|
549
571
|
for function_name in self.spec.functions
|
|
@@ -553,16 +575,14 @@ class APIGateway(ModelObj):
|
|
|
553
575
|
for i, port in enumerate(self.spec.ports):
|
|
554
576
|
upstreams[i].port = port
|
|
555
577
|
|
|
556
|
-
api_gateway =
|
|
557
|
-
metadata=
|
|
558
|
-
|
|
559
|
-
),
|
|
560
|
-
spec=mlrun.common.schemas.APIGatewaySpec(
|
|
578
|
+
api_gateway = schemas.APIGateway(
|
|
579
|
+
metadata=schemas.APIGatewayMetadata(name=self.metadata.name, labels={}),
|
|
580
|
+
spec=schemas.APIGatewaySpec(
|
|
561
581
|
name=self.metadata.name,
|
|
562
582
|
description=self.spec.description,
|
|
563
583
|
host=self.spec.host,
|
|
564
584
|
path=self.spec.path,
|
|
565
|
-
authenticationMode=
|
|
585
|
+
authenticationMode=schemas.APIGatewayAuthenticationMode.from_str(
|
|
566
586
|
self.spec.authentication.authentication_mode
|
|
567
587
|
),
|
|
568
588
|
upstreams=upstreams,
|
|
@@ -592,7 +612,7 @@ class APIGateway(ModelObj):
|
|
|
592
612
|
|
|
593
613
|
@staticmethod
|
|
594
614
|
def _resolve_canary(
|
|
595
|
-
upstreams: list[
|
|
615
|
+
upstreams: list[schemas.APIGatewayUpstream],
|
|
596
616
|
) -> tuple[Union[list[str], None], Union[list[int], None]]:
|
|
597
617
|
if len(upstreams) == 1:
|
|
598
618
|
return [upstreams[0].nucliofunction.get("name")], None
|
|
@@ -12,13 +12,20 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import pathlib
|
|
15
|
+
import typing
|
|
15
16
|
|
|
16
17
|
import nuclio
|
|
17
18
|
|
|
19
|
+
import mlrun.common.schemas as schemas
|
|
18
20
|
import mlrun.errors
|
|
19
|
-
from mlrun.common.
|
|
21
|
+
from mlrun.common.runtimes.constants import NuclioIngressAddTemplatedIngressModes
|
|
20
22
|
from mlrun.runtimes import RemoteRuntime
|
|
21
23
|
from mlrun.runtimes.nuclio import min_nuclio_versions
|
|
24
|
+
from mlrun.runtimes.nuclio.api_gateway import (
|
|
25
|
+
APIGateway,
|
|
26
|
+
APIGatewayMetadata,
|
|
27
|
+
APIGatewaySpec,
|
|
28
|
+
)
|
|
22
29
|
from mlrun.runtimes.nuclio.function import NuclioSpec, NuclioStatus
|
|
23
30
|
|
|
24
31
|
|
|
@@ -113,7 +120,10 @@ class ApplicationSpec(NuclioSpec):
|
|
|
113
120
|
state_thresholds=state_thresholds,
|
|
114
121
|
disable_default_http_trigger=disable_default_http_trigger,
|
|
115
122
|
)
|
|
116
|
-
self.internal_application_port =
|
|
123
|
+
self.internal_application_port = (
|
|
124
|
+
internal_application_port
|
|
125
|
+
or mlrun.mlconf.function.application.default_sidecar_internal_port
|
|
126
|
+
)
|
|
117
127
|
|
|
118
128
|
@property
|
|
119
129
|
def internal_application_port(self):
|
|
@@ -139,6 +149,9 @@ class ApplicationStatus(NuclioStatus):
|
|
|
139
149
|
container_image=None,
|
|
140
150
|
application_image=None,
|
|
141
151
|
sidecar_name=None,
|
|
152
|
+
api_gateway_name=None,
|
|
153
|
+
api_gateway=None,
|
|
154
|
+
url=None,
|
|
142
155
|
):
|
|
143
156
|
super().__init__(
|
|
144
157
|
state=state,
|
|
@@ -151,6 +164,9 @@ class ApplicationStatus(NuclioStatus):
|
|
|
151
164
|
)
|
|
152
165
|
self.application_image = application_image or None
|
|
153
166
|
self.sidecar_name = sidecar_name or None
|
|
167
|
+
self.api_gateway_name = api_gateway_name or None
|
|
168
|
+
self.api_gateway = api_gateway or None
|
|
169
|
+
self.url = url or None
|
|
154
170
|
|
|
155
171
|
|
|
156
172
|
class ApplicationRuntime(RemoteRuntime):
|
|
@@ -176,6 +192,24 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
176
192
|
def status(self, status):
|
|
177
193
|
self._status = self._verify_dict(status, "status", ApplicationStatus)
|
|
178
194
|
|
|
195
|
+
@property
|
|
196
|
+
def api_gateway(self):
|
|
197
|
+
return self.status.api_gateway
|
|
198
|
+
|
|
199
|
+
@api_gateway.setter
|
|
200
|
+
def api_gateway(self, api_gateway: APIGateway):
|
|
201
|
+
self.status.api_gateway = api_gateway
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def url(self):
|
|
205
|
+
if not self.status.api_gateway:
|
|
206
|
+
self._sync_api_gateway()
|
|
207
|
+
return self.status.api_gateway.invoke_url
|
|
208
|
+
|
|
209
|
+
@url.setter
|
|
210
|
+
def url(self, url):
|
|
211
|
+
self.status.url = url
|
|
212
|
+
|
|
179
213
|
def set_internal_application_port(self, port: int):
|
|
180
214
|
self.spec.internal_application_port = port
|
|
181
215
|
|
|
@@ -220,7 +254,7 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
220
254
|
project="",
|
|
221
255
|
tag="",
|
|
222
256
|
verbose=False,
|
|
223
|
-
auth_info: AuthInfo = None,
|
|
257
|
+
auth_info: schemas.AuthInfo = None,
|
|
224
258
|
builder_env: dict = None,
|
|
225
259
|
force_build: bool = False,
|
|
226
260
|
with_mlrun=None,
|
|
@@ -228,6 +262,10 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
228
262
|
is_kfp=False,
|
|
229
263
|
mlrun_version_specifier=None,
|
|
230
264
|
show_on_failure: bool = False,
|
|
265
|
+
skip_access_key_auth: bool = False,
|
|
266
|
+
direct_port_access: bool = False,
|
|
267
|
+
authentication_mode: schemas.APIGatewayAuthenticationMode = None,
|
|
268
|
+
authentication_creds: tuple[str] = None,
|
|
231
269
|
):
|
|
232
270
|
"""
|
|
233
271
|
Deploy function, builds the application image if required (self.requires_build()) or force_build is True,
|
|
@@ -244,6 +282,10 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
244
282
|
:param is_kfp: Deploy as part of a kfp pipeline
|
|
245
283
|
:param mlrun_version_specifier: Which mlrun package version to include (if not current)
|
|
246
284
|
:param show_on_failure: Show logs only in case of build failure
|
|
285
|
+
:param skip_access_key_auth: Skip adding access key auth to the API Gateway
|
|
286
|
+
:param direct_port_access: Set True to allow direct port access to the application sidecar
|
|
287
|
+
:param authentication_mode: API Gateway authentication mode
|
|
288
|
+
:param authentication_creds: API Gateway authentication credentials as a tuple (username, password)
|
|
247
289
|
:return: True if the function is ready (deployed)
|
|
248
290
|
"""
|
|
249
291
|
if self.requires_build() or force_build:
|
|
@@ -261,6 +303,16 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
261
303
|
|
|
262
304
|
self._ensure_reverse_proxy_configurations()
|
|
263
305
|
self._configure_application_sidecar()
|
|
306
|
+
|
|
307
|
+
# we only allow accessing the application via the API Gateway
|
|
308
|
+
name_tag = tag or self.metadata.tag
|
|
309
|
+
self.status.api_gateway_name = (
|
|
310
|
+
f"{self.metadata.name}-{name_tag}" if name_tag else self.metadata.name
|
|
311
|
+
)
|
|
312
|
+
self.spec.add_templated_ingress_host_mode = (
|
|
313
|
+
NuclioIngressAddTemplatedIngressModes.never
|
|
314
|
+
)
|
|
315
|
+
|
|
264
316
|
super().deploy(
|
|
265
317
|
project,
|
|
266
318
|
tag,
|
|
@@ -269,6 +321,14 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
269
321
|
builder_env,
|
|
270
322
|
)
|
|
271
323
|
|
|
324
|
+
ports = self.spec.internal_application_port if direct_port_access else []
|
|
325
|
+
self.create_api_gateway(
|
|
326
|
+
name=self.status.api_gateway_name,
|
|
327
|
+
ports=ports,
|
|
328
|
+
authentication_mode=authentication_mode,
|
|
329
|
+
authentication_creds=authentication_creds,
|
|
330
|
+
)
|
|
331
|
+
|
|
272
332
|
def with_source_archive(
|
|
273
333
|
self, source, workdir=None, pull_at_runtime=True, target_dir=None
|
|
274
334
|
):
|
|
@@ -290,6 +350,92 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
290
350
|
target_dir=target_dir,
|
|
291
351
|
)
|
|
292
352
|
|
|
353
|
+
@classmethod
|
|
354
|
+
def get_filename_and_handler(cls) -> (str, str):
|
|
355
|
+
reverse_proxy_file_path = pathlib.Path(__file__).parent / "reverse_proxy.go"
|
|
356
|
+
return str(reverse_proxy_file_path), "Handler"
|
|
357
|
+
|
|
358
|
+
def create_api_gateway(
|
|
359
|
+
self,
|
|
360
|
+
name: str = None,
|
|
361
|
+
path: str = None,
|
|
362
|
+
ports: list[int] = None,
|
|
363
|
+
authentication_mode: schemas.APIGatewayAuthenticationMode = None,
|
|
364
|
+
authentication_creds: tuple[str] = None,
|
|
365
|
+
):
|
|
366
|
+
api_gateway = APIGateway(
|
|
367
|
+
APIGatewayMetadata(
|
|
368
|
+
name=name,
|
|
369
|
+
namespace=self.metadata.namespace,
|
|
370
|
+
labels=self.metadata.labels,
|
|
371
|
+
annotations=self.metadata.annotations,
|
|
372
|
+
),
|
|
373
|
+
APIGatewaySpec(
|
|
374
|
+
functions=[self],
|
|
375
|
+
project=self.metadata.project,
|
|
376
|
+
path=path,
|
|
377
|
+
ports=mlrun.utils.helpers.as_list(ports) if ports else None,
|
|
378
|
+
),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
authentication_mode = (
|
|
382
|
+
authentication_mode
|
|
383
|
+
or mlrun.mlconf.function.application.default_authentication_mode
|
|
384
|
+
)
|
|
385
|
+
if authentication_mode == schemas.APIGatewayAuthenticationMode.access_key:
|
|
386
|
+
api_gateway.with_access_key_auth()
|
|
387
|
+
elif authentication_mode == schemas.APIGatewayAuthenticationMode.basic:
|
|
388
|
+
api_gateway.with_basic_auth(*authentication_creds)
|
|
389
|
+
|
|
390
|
+
db = mlrun.get_run_db()
|
|
391
|
+
api_gateway_scheme = db.store_api_gateway(
|
|
392
|
+
api_gateway=api_gateway.to_scheme(), project=self.metadata.project
|
|
393
|
+
)
|
|
394
|
+
if not self.status.api_gateway_name:
|
|
395
|
+
self.status.api_gateway_name = api_gateway_scheme.metadata.name
|
|
396
|
+
self.status.api_gateway = APIGateway.from_scheme(api_gateway_scheme)
|
|
397
|
+
self.status.api_gateway.wait_for_readiness()
|
|
398
|
+
self.url = self.status.api_gateway.invoke_url
|
|
399
|
+
|
|
400
|
+
def invoke(
|
|
401
|
+
self,
|
|
402
|
+
path: str,
|
|
403
|
+
body: typing.Union[str, bytes, dict] = None,
|
|
404
|
+
method: str = None,
|
|
405
|
+
headers: dict = None,
|
|
406
|
+
dashboard: str = "",
|
|
407
|
+
force_external_address: bool = False,
|
|
408
|
+
auth_info: schemas.AuthInfo = None,
|
|
409
|
+
mock: bool = None,
|
|
410
|
+
**http_client_kwargs,
|
|
411
|
+
):
|
|
412
|
+
self._sync_api_gateway()
|
|
413
|
+
# If the API Gateway is not ready or not set, try to invoke the function directly (without the API Gateway)
|
|
414
|
+
if not self.status.api_gateway:
|
|
415
|
+
super().invoke(
|
|
416
|
+
path,
|
|
417
|
+
body,
|
|
418
|
+
method,
|
|
419
|
+
headers,
|
|
420
|
+
dashboard,
|
|
421
|
+
force_external_address,
|
|
422
|
+
auth_info,
|
|
423
|
+
mock,
|
|
424
|
+
**http_client_kwargs,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
credentials = (auth_info.username, auth_info.password) if auth_info else None
|
|
428
|
+
|
|
429
|
+
if not method:
|
|
430
|
+
method = "POST" if body else "GET"
|
|
431
|
+
return self.status.api_gateway.invoke(
|
|
432
|
+
method=method,
|
|
433
|
+
headers=headers,
|
|
434
|
+
credentials=credentials,
|
|
435
|
+
path=path,
|
|
436
|
+
**http_client_kwargs,
|
|
437
|
+
)
|
|
438
|
+
|
|
293
439
|
def _build_application_image(
|
|
294
440
|
self,
|
|
295
441
|
builder_env: dict = None,
|
|
@@ -355,7 +501,14 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
355
501
|
self.set_env("SIDECAR_PORT", self.spec.internal_application_port)
|
|
356
502
|
self.set_env("SIDECAR_HOST", "http://localhost")
|
|
357
503
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
504
|
+
def _sync_api_gateway(self):
|
|
505
|
+
if not self.status.api_gateway_name:
|
|
506
|
+
return
|
|
507
|
+
|
|
508
|
+
db = mlrun.get_run_db()
|
|
509
|
+
api_gateway_scheme = db.get_api_gateway(
|
|
510
|
+
name=self.status.api_gateway_name, project=self.metadata.project
|
|
511
|
+
)
|
|
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
|
|
@@ -547,7 +547,6 @@ class RemoteRuntime(KubeResource):
|
|
|
547
547
|
if tag:
|
|
548
548
|
self.metadata.tag = tag
|
|
549
549
|
|
|
550
|
-
save_record = False
|
|
551
550
|
# Attempt auto-mounting, before sending to remote build
|
|
552
551
|
self.try_auto_mount_based_on_config()
|
|
553
552
|
self._fill_credentials()
|
|
@@ -565,15 +564,18 @@ class RemoteRuntime(KubeResource):
|
|
|
565
564
|
# now, functions can be not exposed (using service type ClusterIP) and hence
|
|
566
565
|
# for BC we first try to populate the external invocation url, and then
|
|
567
566
|
# if not exists, take the internal invocation url
|
|
568
|
-
if
|
|
567
|
+
if (
|
|
568
|
+
self.status.external_invocation_urls
|
|
569
|
+
and self.status.external_invocation_urls[0] != ""
|
|
570
|
+
):
|
|
569
571
|
self.spec.command = f"http://{self.status.external_invocation_urls[0]}"
|
|
570
|
-
|
|
571
|
-
|
|
572
|
+
elif (
|
|
573
|
+
self.status.internal_invocation_urls
|
|
574
|
+
and self.status.internal_invocation_urls[0] != ""
|
|
575
|
+
):
|
|
572
576
|
self.spec.command = f"http://{self.status.internal_invocation_urls[0]}"
|
|
573
|
-
|
|
574
|
-
elif self.status.address:
|
|
577
|
+
elif self.status.address and self.status.address != "":
|
|
575
578
|
self.spec.command = f"http://{self.status.address}"
|
|
576
|
-
save_record = True
|
|
577
579
|
|
|
578
580
|
logger.info(
|
|
579
581
|
"Successfully deployed function",
|
|
@@ -581,8 +583,7 @@ class RemoteRuntime(KubeResource):
|
|
|
581
583
|
external_invocation_urls=self.status.external_invocation_urls,
|
|
582
584
|
)
|
|
583
585
|
|
|
584
|
-
|
|
585
|
-
self.save(versioned=False)
|
|
586
|
+
self.save(versioned=False)
|
|
586
587
|
|
|
587
588
|
return self.spec.command
|
|
588
589
|
|
|
@@ -966,19 +967,24 @@ class RemoteRuntime(KubeResource):
|
|
|
966
967
|
sidecar["image"] = image
|
|
967
968
|
|
|
968
969
|
ports = mlrun.utils.helpers.as_list(ports)
|
|
970
|
+
# according to RFC-6335, port name should be less than 15 characters,
|
|
971
|
+
# so we truncate it if needed and leave room for the index
|
|
972
|
+
port_name = name[:13].rstrip("-_") if len(name) > 13 else name
|
|
969
973
|
sidecar["ports"] = [
|
|
970
974
|
{
|
|
971
|
-
"name": f"{
|
|
975
|
+
"name": f"{port_name}-{i}",
|
|
972
976
|
"containerPort": port,
|
|
973
977
|
"protocol": "TCP",
|
|
974
978
|
}
|
|
975
979
|
for i, port in enumerate(ports)
|
|
976
980
|
]
|
|
977
981
|
|
|
978
|
-
if command
|
|
982
|
+
# if it is a redeploy, 'command' might be set with the previous invocation url.
|
|
983
|
+
# in this case, we don't want to use it as the sidecar command
|
|
984
|
+
if command and not command.startswith("http"):
|
|
979
985
|
sidecar["command"] = mlrun.utils.helpers.as_list(command)
|
|
980
986
|
|
|
981
|
-
if args:
|
|
987
|
+
if args and sidecar["command"]:
|
|
982
988
|
sidecar["args"] = mlrun.utils.helpers.as_list(args)
|
|
983
989
|
|
|
984
990
|
def _set_sidecar(self, name: str) -> dict:
|