mlrun 1.7.0rc5__py3-none-any.whl → 1.7.2__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/__init__.py +11 -1
- mlrun/__main__.py +39 -121
- mlrun/{datastore/helpers.py → alerts/__init__.py} +2 -5
- mlrun/alerts/alert.py +248 -0
- mlrun/api/schemas/__init__.py +4 -3
- mlrun/artifacts/__init__.py +8 -3
- mlrun/artifacts/base.py +39 -254
- mlrun/artifacts/dataset.py +9 -190
- mlrun/artifacts/manager.py +73 -46
- mlrun/artifacts/model.py +30 -158
- mlrun/artifacts/plots.py +23 -380
- mlrun/common/constants.py +73 -2
- mlrun/common/db/sql_session.py +3 -2
- mlrun/common/formatters/__init__.py +21 -0
- mlrun/common/formatters/artifact.py +46 -0
- mlrun/common/formatters/base.py +113 -0
- mlrun/common/formatters/feature_set.py +44 -0
- mlrun/common/formatters/function.py +46 -0
- mlrun/common/formatters/pipeline.py +53 -0
- mlrun/common/formatters/project.py +51 -0
- mlrun/common/formatters/run.py +29 -0
- mlrun/common/helpers.py +11 -1
- mlrun/{runtimes → common/runtimes}/constants.py +32 -4
- mlrun/common/schemas/__init__.py +21 -4
- mlrun/common/schemas/alert.py +202 -0
- mlrun/common/schemas/api_gateway.py +113 -2
- mlrun/common/schemas/artifact.py +28 -1
- mlrun/common/schemas/auth.py +11 -0
- mlrun/common/schemas/client_spec.py +2 -1
- mlrun/common/schemas/common.py +7 -4
- mlrun/common/schemas/constants.py +3 -0
- mlrun/common/schemas/feature_store.py +58 -28
- mlrun/common/schemas/frontend_spec.py +8 -0
- mlrun/common/schemas/function.py +11 -0
- mlrun/common/schemas/hub.py +7 -9
- mlrun/common/schemas/model_monitoring/__init__.py +21 -4
- mlrun/common/schemas/model_monitoring/constants.py +136 -42
- mlrun/common/schemas/model_monitoring/grafana.py +9 -5
- mlrun/common/schemas/model_monitoring/model_endpoints.py +89 -41
- mlrun/common/schemas/notification.py +69 -12
- mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
- mlrun/common/schemas/pipeline.py +7 -0
- mlrun/common/schemas/project.py +67 -16
- mlrun/common/schemas/runs.py +17 -0
- mlrun/common/schemas/schedule.py +1 -1
- mlrun/common/schemas/workflow.py +10 -2
- mlrun/common/types.py +14 -1
- mlrun/config.py +224 -58
- mlrun/data_types/data_types.py +11 -1
- mlrun/data_types/spark.py +5 -4
- mlrun/data_types/to_pandas.py +75 -34
- mlrun/datastore/__init__.py +8 -10
- mlrun/datastore/alibaba_oss.py +131 -0
- mlrun/datastore/azure_blob.py +131 -43
- mlrun/datastore/base.py +107 -47
- mlrun/datastore/datastore.py +17 -7
- mlrun/datastore/datastore_profile.py +91 -7
- mlrun/datastore/dbfs_store.py +3 -7
- mlrun/datastore/filestore.py +1 -3
- mlrun/datastore/google_cloud_storage.py +92 -32
- mlrun/datastore/hdfs.py +5 -0
- mlrun/datastore/inmem.py +6 -3
- mlrun/datastore/redis.py +3 -2
- mlrun/datastore/s3.py +30 -12
- mlrun/datastore/snowflake_utils.py +45 -0
- mlrun/datastore/sources.py +274 -59
- mlrun/datastore/spark_utils.py +30 -0
- mlrun/datastore/store_resources.py +9 -7
- mlrun/datastore/storeytargets.py +151 -0
- mlrun/datastore/targets.py +374 -102
- mlrun/datastore/utils.py +68 -5
- mlrun/datastore/v3io.py +28 -50
- mlrun/db/auth_utils.py +152 -0
- mlrun/db/base.py +231 -22
- mlrun/db/factory.py +1 -4
- mlrun/db/httpdb.py +864 -228
- mlrun/db/nopdb.py +268 -16
- mlrun/errors.py +35 -5
- mlrun/execution.py +111 -38
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +46 -53
- mlrun/feature_store/common.py +6 -11
- mlrun/feature_store/feature_set.py +48 -23
- mlrun/feature_store/feature_vector.py +13 -2
- mlrun/feature_store/ingestion.py +7 -6
- mlrun/feature_store/retrieval/base.py +9 -4
- mlrun/feature_store/retrieval/dask_merger.py +2 -0
- mlrun/feature_store/retrieval/job.py +13 -4
- mlrun/feature_store/retrieval/local_merger.py +2 -0
- mlrun/feature_store/retrieval/spark_merger.py +24 -32
- mlrun/feature_store/steps.py +38 -19
- mlrun/features.py +6 -14
- mlrun/frameworks/_common/plan.py +3 -3
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +7 -12
- mlrun/frameworks/_ml_common/plan.py +1 -1
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +2 -2
- mlrun/frameworks/lgbm/__init__.py +1 -1
- mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
- mlrun/frameworks/lgbm/model_handler.py +1 -1
- mlrun/frameworks/parallel_coordinates.py +4 -4
- mlrun/frameworks/pytorch/__init__.py +2 -2
- mlrun/frameworks/sklearn/__init__.py +1 -1
- mlrun/frameworks/sklearn/mlrun_interface.py +13 -3
- mlrun/frameworks/tf_keras/__init__.py +5 -2
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +1 -1
- mlrun/frameworks/tf_keras/mlrun_interface.py +2 -2
- mlrun/frameworks/xgboost/__init__.py +1 -1
- mlrun/k8s_utils.py +57 -12
- mlrun/launcher/__init__.py +1 -1
- mlrun/launcher/base.py +6 -5
- mlrun/launcher/client.py +13 -11
- mlrun/launcher/factory.py +1 -1
- mlrun/launcher/local.py +15 -5
- mlrun/launcher/remote.py +10 -3
- mlrun/lists.py +6 -2
- mlrun/model.py +297 -48
- mlrun/model_monitoring/__init__.py +1 -1
- mlrun/model_monitoring/api.py +152 -357
- mlrun/model_monitoring/applications/__init__.py +10 -0
- mlrun/model_monitoring/applications/_application_steps.py +190 -0
- mlrun/model_monitoring/applications/base.py +108 -0
- mlrun/model_monitoring/applications/context.py +341 -0
- mlrun/model_monitoring/{evidently_application.py → applications/evidently_base.py} +27 -22
- mlrun/model_monitoring/applications/histogram_data_drift.py +227 -91
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +130 -303
- mlrun/model_monitoring/{stores/models/sqlite.py → db/__init__.py} +5 -10
- mlrun/model_monitoring/db/stores/__init__.py +136 -0
- mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
- mlrun/model_monitoring/db/stores/base/store.py +213 -0
- mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +190 -0
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +103 -0
- mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +659 -0
- mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +726 -0
- mlrun/model_monitoring/db/tsdb/__init__.py +105 -0
- mlrun/model_monitoring/db/tsdb/base.py +448 -0
- mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
- mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +298 -0
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +42 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +522 -0
- mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +158 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +849 -0
- mlrun/model_monitoring/features_drift_table.py +34 -22
- mlrun/model_monitoring/helpers.py +177 -39
- mlrun/model_monitoring/model_endpoint.py +3 -2
- mlrun/model_monitoring/stream_processing.py +165 -398
- mlrun/model_monitoring/tracking_policy.py +7 -1
- mlrun/model_monitoring/writer.py +161 -125
- mlrun/package/packagers/default_packager.py +2 -2
- mlrun/package/packagers_manager.py +1 -0
- mlrun/package/utils/_formatter.py +2 -2
- mlrun/platforms/__init__.py +11 -10
- mlrun/platforms/iguazio.py +67 -228
- mlrun/projects/__init__.py +6 -1
- mlrun/projects/operations.py +47 -20
- mlrun/projects/pipelines.py +396 -249
- mlrun/projects/project.py +1125 -414
- mlrun/render.py +28 -22
- mlrun/run.py +207 -180
- mlrun/runtimes/__init__.py +76 -11
- mlrun/runtimes/base.py +40 -14
- mlrun/runtimes/daskjob.py +9 -2
- mlrun/runtimes/databricks_job/databricks_runtime.py +1 -0
- mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
- mlrun/runtimes/funcdoc.py +1 -29
- mlrun/runtimes/kubejob.py +34 -128
- mlrun/runtimes/local.py +39 -10
- mlrun/runtimes/mpijob/__init__.py +0 -20
- mlrun/runtimes/mpijob/abstract.py +8 -8
- mlrun/runtimes/mpijob/v1.py +1 -1
- mlrun/runtimes/nuclio/api_gateway.py +646 -177
- mlrun/runtimes/nuclio/application/__init__.py +15 -0
- mlrun/runtimes/nuclio/application/application.py +758 -0
- mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
- mlrun/runtimes/nuclio/function.py +188 -68
- mlrun/runtimes/nuclio/serving.py +57 -60
- mlrun/runtimes/pod.py +191 -58
- mlrun/runtimes/remotesparkjob.py +11 -8
- mlrun/runtimes/sparkjob/spark3job.py +17 -18
- mlrun/runtimes/utils.py +40 -73
- mlrun/secrets.py +6 -2
- mlrun/serving/__init__.py +8 -1
- mlrun/serving/remote.py +2 -3
- mlrun/serving/routers.py +89 -64
- mlrun/serving/server.py +54 -26
- mlrun/serving/states.py +187 -56
- mlrun/serving/utils.py +19 -11
- mlrun/serving/v2_serving.py +136 -63
- mlrun/track/tracker.py +2 -1
- mlrun/track/trackers/mlflow_tracker.py +5 -0
- mlrun/utils/async_http.py +26 -6
- mlrun/utils/db.py +18 -0
- mlrun/utils/helpers.py +375 -105
- mlrun/utils/http.py +2 -2
- mlrun/utils/logger.py +75 -9
- mlrun/utils/notifications/notification/__init__.py +14 -10
- mlrun/utils/notifications/notification/base.py +48 -0
- mlrun/utils/notifications/notification/console.py +2 -0
- mlrun/utils/notifications/notification/git.py +24 -1
- mlrun/utils/notifications/notification/ipython.py +2 -0
- mlrun/utils/notifications/notification/slack.py +96 -21
- mlrun/utils/notifications/notification/webhook.py +63 -2
- mlrun/utils/notifications/notification_pusher.py +146 -16
- mlrun/utils/regex.py +9 -0
- mlrun/utils/retryer.py +3 -2
- mlrun/utils/v3io_clients.py +2 -3
- mlrun/utils/version/version.json +2 -2
- mlrun-1.7.2.dist-info/METADATA +390 -0
- mlrun-1.7.2.dist-info/RECORD +351 -0
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/WHEEL +1 -1
- mlrun/feature_store/retrieval/conversion.py +0 -271
- mlrun/kfpops.py +0 -868
- mlrun/model_monitoring/application.py +0 -310
- mlrun/model_monitoring/batch.py +0 -974
- mlrun/model_monitoring/controller_handler.py +0 -37
- mlrun/model_monitoring/prometheus.py +0 -216
- mlrun/model_monitoring/stores/__init__.py +0 -111
- mlrun/model_monitoring/stores/kv_model_endpoint_store.py +0 -574
- mlrun/model_monitoring/stores/model_endpoint_store.py +0 -145
- mlrun/model_monitoring/stores/models/__init__.py +0 -27
- mlrun/model_monitoring/stores/models/base.py +0 -84
- mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -382
- mlrun/platforms/other.py +0 -305
- mlrun-1.7.0rc5.dist-info/METADATA +0 -269
- mlrun-1.7.0rc5.dist-info/RECORD +0 -323
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/top_level.txt +0 -0
|
@@ -12,221 +12,272 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import base64
|
|
15
|
+
import typing
|
|
15
16
|
from typing import Optional, Union
|
|
16
17
|
from urllib.parse import urljoin
|
|
17
18
|
|
|
18
19
|
import requests
|
|
20
|
+
from nuclio.auth import AuthInfo as NuclioAuthInfo
|
|
21
|
+
from nuclio.auth import AuthKinds as NuclioAuthKinds
|
|
19
22
|
|
|
20
23
|
import mlrun
|
|
21
|
-
import mlrun.common.
|
|
24
|
+
import mlrun.common.constants as mlrun_constants
|
|
25
|
+
import mlrun.common.helpers
|
|
26
|
+
import mlrun.common.schemas as schemas
|
|
27
|
+
import mlrun.common.types
|
|
28
|
+
from mlrun.model import ModelObj
|
|
29
|
+
from mlrun.platforms.iguazio import min_iguazio_versions
|
|
30
|
+
from mlrun.utils import logger
|
|
22
31
|
|
|
23
|
-
from .function import
|
|
24
|
-
from .serving import ServingRuntime
|
|
32
|
+
from .function import min_nuclio_versions
|
|
25
33
|
|
|
26
|
-
NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH = "basicAuth"
|
|
27
|
-
NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_NONE = "none"
|
|
28
|
-
PROJECT_NAME_LABEL = "nuclio.io/project-name"
|
|
29
34
|
|
|
35
|
+
class Authenticator(typing.Protocol):
|
|
36
|
+
@property
|
|
37
|
+
def authentication_mode(self) -> str:
|
|
38
|
+
return schemas.APIGatewayAuthenticationMode.none.value
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_scheme(cls, api_gateway_spec: schemas.APIGatewaySpec):
|
|
42
|
+
if (
|
|
43
|
+
api_gateway_spec.authenticationMode
|
|
44
|
+
== schemas.APIGatewayAuthenticationMode.basic.value
|
|
45
|
+
):
|
|
46
|
+
if api_gateway_spec.authentication:
|
|
47
|
+
return BasicAuth(
|
|
48
|
+
username=api_gateway_spec.authentication.get("username", ""),
|
|
49
|
+
password=api_gateway_spec.authentication.get("password", ""),
|
|
50
|
+
)
|
|
51
|
+
else:
|
|
52
|
+
return BasicAuth()
|
|
53
|
+
elif (
|
|
54
|
+
api_gateway_spec.authenticationMode
|
|
55
|
+
== schemas.APIGatewayAuthenticationMode.access_key.value
|
|
56
|
+
):
|
|
57
|
+
return AccessKeyAuth()
|
|
58
|
+
else:
|
|
59
|
+
return NoneAuth()
|
|
60
|
+
|
|
61
|
+
def to_scheme(
|
|
62
|
+
self,
|
|
63
|
+
) -> Optional[dict[str, Optional[schemas.APIGatewayBasicAuth]]]:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class APIGatewayAuthenticator(Authenticator, ModelObj):
|
|
68
|
+
_dict_fields = ["authentication_mode"]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class NoneAuth(APIGatewayAuthenticator):
|
|
72
|
+
"""
|
|
73
|
+
An API gateway authenticator with no authentication.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class BasicAuth(APIGatewayAuthenticator):
|
|
80
|
+
"""
|
|
81
|
+
An API gateway authenticator with basic authentication.
|
|
82
|
+
|
|
83
|
+
:param username: (str) The username for basic authentication.
|
|
84
|
+
:param password: (str) The password for basic authentication.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(self, username=None, password=None):
|
|
88
|
+
self._username = username
|
|
89
|
+
self._password = password
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def authentication_mode(self) -> str:
|
|
93
|
+
return schemas.APIGatewayAuthenticationMode.basic.value
|
|
94
|
+
|
|
95
|
+
def to_scheme(
|
|
96
|
+
self,
|
|
97
|
+
) -> Optional[dict[str, Optional[schemas.APIGatewayBasicAuth]]]:
|
|
98
|
+
return {
|
|
99
|
+
"basicAuth": schemas.APIGatewayBasicAuth(
|
|
100
|
+
username=self._username, password=self._password
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class AccessKeyAuth(APIGatewayAuthenticator):
|
|
106
|
+
"""
|
|
107
|
+
An API gateway authenticator with access key authentication.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def authentication_mode(self) -> str:
|
|
112
|
+
return schemas.APIGatewayAuthenticationMode.access_key.value
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class APIGatewayMetadata(ModelObj):
|
|
116
|
+
_dict_fields = ["name", "namespace", "labels", "annotations", "creation_timestamp"]
|
|
30
117
|
|
|
31
|
-
class APIGateway:
|
|
32
118
|
def __init__(
|
|
33
119
|
self,
|
|
34
|
-
project,
|
|
35
120
|
name: str,
|
|
121
|
+
namespace: str = None,
|
|
122
|
+
labels: dict = None,
|
|
123
|
+
annotations: dict = None,
|
|
124
|
+
creation_timestamp: str = None,
|
|
125
|
+
):
|
|
126
|
+
"""
|
|
127
|
+
:param name: The name of the API gateway
|
|
128
|
+
:param namespace: The namespace of the API gateway
|
|
129
|
+
:param labels: The labels of the API gateway
|
|
130
|
+
:param annotations: The annotations of the API gateway
|
|
131
|
+
:param creation_timestamp: The creation timestamp of the API gateway
|
|
132
|
+
"""
|
|
133
|
+
self.name = name
|
|
134
|
+
self.namespace = namespace
|
|
135
|
+
self.labels = labels or {}
|
|
136
|
+
self.annotations = annotations or {}
|
|
137
|
+
self.creation_timestamp = creation_timestamp
|
|
138
|
+
|
|
139
|
+
if not self.name:
|
|
140
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
141
|
+
"API Gateway name cannot be empty"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class APIGatewaySpec(ModelObj):
|
|
146
|
+
_dict_fields = [
|
|
147
|
+
"functions",
|
|
148
|
+
"project",
|
|
149
|
+
"name",
|
|
150
|
+
"description",
|
|
151
|
+
"host",
|
|
152
|
+
"path",
|
|
153
|
+
"authentication",
|
|
154
|
+
"canary",
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
def __init__(
|
|
158
|
+
self,
|
|
36
159
|
functions: Union[
|
|
37
|
-
list[
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
],
|
|
45
|
-
Union[RemoteRuntime, ServingRuntime],
|
|
160
|
+
list[
|
|
161
|
+
Union[
|
|
162
|
+
str,
|
|
163
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
164
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
165
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
166
|
+
]
|
|
46
167
|
],
|
|
168
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
169
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
170
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
47
171
|
],
|
|
172
|
+
project: str = None,
|
|
48
173
|
description: str = "",
|
|
174
|
+
host: str = None,
|
|
49
175
|
path: str = "/",
|
|
50
|
-
|
|
51
|
-
str
|
|
52
|
-
] = NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_NONE,
|
|
53
|
-
host: Optional[str] = None,
|
|
176
|
+
authentication: Optional[APIGatewayAuthenticator] = NoneAuth(),
|
|
54
177
|
canary: Optional[list[int]] = None,
|
|
55
|
-
|
|
56
|
-
password: Optional[str] = None,
|
|
178
|
+
ports: Optional[list[int]] = None,
|
|
57
179
|
):
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
180
|
+
"""
|
|
181
|
+
:param functions: The list of functions associated with the API gateway
|
|
182
|
+
Can be a list of function names (["my-func1", "my-func2"])
|
|
183
|
+
or a list or a single entity of
|
|
184
|
+
:py:class:`~mlrun.runtimes.nuclio.function.RemoteRuntime` OR
|
|
185
|
+
:py:class:`~mlrun.runtimes.nuclio.serving.ServingRuntime` OR
|
|
186
|
+
:py:class:`~mlrun.runtimes.nuclio.application.ApplicationRuntime`
|
|
187
|
+
:param project: The project name
|
|
188
|
+
:param description: Optional description of the API gateway
|
|
189
|
+
:param path: Optional path of the API gateway, default value is "/"
|
|
190
|
+
:param authentication: The authentication for the API gateway of type
|
|
191
|
+
:py:class:`~mlrun.runtimes.nuclio.api_gateway.BasicAuth`
|
|
192
|
+
:param host: The host of the API gateway (optional). If not set, it will be automatically generated
|
|
193
|
+
:param canary: The canary percents for the API gateway of type list[int]; for instance: [20,80] (optional)
|
|
194
|
+
:param ports: The ports of the API gateway, as a list of integers that correspond to the functions in the
|
|
195
|
+
functions list. for instance: [8050] or [8050, 8081] (optional)
|
|
196
|
+
"""
|
|
197
|
+
self.description = description
|
|
69
198
|
self.host = host
|
|
70
|
-
|
|
71
199
|
self.path = path
|
|
72
|
-
self.
|
|
73
|
-
self.
|
|
74
|
-
authentication_mode
|
|
75
|
-
if authentication_mode
|
|
76
|
-
else self._enrich_authentication_mode(username=username, password=password)
|
|
77
|
-
)
|
|
200
|
+
self.authentication = authentication
|
|
201
|
+
self.functions = functions
|
|
78
202
|
self.canary = canary
|
|
79
|
-
self.
|
|
80
|
-
self.
|
|
81
|
-
|
|
82
|
-
def invoke(
|
|
83
|
-
self,
|
|
84
|
-
method="POST",
|
|
85
|
-
headers: dict = {},
|
|
86
|
-
auth: Optional[tuple[str, str]] = None,
|
|
87
|
-
**kwargs,
|
|
88
|
-
):
|
|
89
|
-
if not self.invoke_url:
|
|
90
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
91
|
-
"Invocation url is not set. Set up gateway's `invoke_url` attribute."
|
|
92
|
-
)
|
|
93
|
-
if (
|
|
94
|
-
self.authentication_mode
|
|
95
|
-
== NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
|
|
96
|
-
and not auth
|
|
97
|
-
):
|
|
98
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
99
|
-
"API Gateway invocation requires authentication. Please pass credentials"
|
|
100
|
-
)
|
|
101
|
-
if auth:
|
|
102
|
-
headers["Authorization"] = self._generate_basic_auth(*auth)
|
|
103
|
-
return requests.request(
|
|
104
|
-
method=method, url=self.invoke_url, headers=headers, **kwargs
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
@classmethod
|
|
108
|
-
def from_scheme(cls, api_gateway: mlrun.common.schemas.APIGateway):
|
|
109
|
-
project = api_gateway.metadata.labels.get(PROJECT_NAME_LABEL)
|
|
110
|
-
functions, canary = cls._resolve_canary(api_gateway.spec.upstreams)
|
|
111
|
-
return cls(
|
|
112
|
-
project=project,
|
|
113
|
-
description=api_gateway.spec.description,
|
|
114
|
-
name=api_gateway.spec.name,
|
|
115
|
-
host=api_gateway.spec.host,
|
|
116
|
-
path=api_gateway.spec.path,
|
|
117
|
-
authentication_mode=str(api_gateway.spec.authenticationMode),
|
|
118
|
-
functions=functions,
|
|
119
|
-
canary=canary,
|
|
120
|
-
)
|
|
203
|
+
self.project = project
|
|
204
|
+
self.ports = ports
|
|
121
205
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
[
|
|
125
|
-
mlrun.common.schemas.APIGatewayUpstream(
|
|
126
|
-
nucliofunction={"name": function_name},
|
|
127
|
-
percentage=percentage,
|
|
128
|
-
)
|
|
129
|
-
for function_name, percentage in zip(self.functions, self.canary)
|
|
130
|
-
]
|
|
131
|
-
if self.canary
|
|
132
|
-
else [
|
|
133
|
-
mlrun.common.schemas.APIGatewayUpstream(
|
|
134
|
-
nucliofunction={"name": function_name},
|
|
135
|
-
)
|
|
136
|
-
for function_name in self.functions
|
|
137
|
-
]
|
|
138
|
-
)
|
|
139
|
-
api_gateway = mlrun.common.schemas.APIGateway(
|
|
140
|
-
metadata=mlrun.common.schemas.APIGatewayMetadata(name=self.name, labels={}),
|
|
141
|
-
spec=mlrun.common.schemas.APIGatewaySpec(
|
|
142
|
-
name=self.name,
|
|
143
|
-
description=self.description,
|
|
144
|
-
path=self.path,
|
|
145
|
-
authentication_mode=mlrun.common.schemas.APIGatewayAuthenticationMode.from_str(
|
|
146
|
-
self.authentication_mode
|
|
147
|
-
),
|
|
148
|
-
upstreams=upstreams,
|
|
149
|
-
),
|
|
150
|
-
)
|
|
151
|
-
if (
|
|
152
|
-
self.authentication_mode
|
|
153
|
-
is NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
|
|
154
|
-
):
|
|
155
|
-
api_gateway.spec.authentication = mlrun.common.schemas.APIGatewayBasicAuth(
|
|
156
|
-
username=self._username, password=self._password
|
|
157
|
-
)
|
|
158
|
-
return api_gateway
|
|
206
|
+
self.enrich()
|
|
207
|
+
self.validate(project=project, functions=functions, canary=canary, ports=ports)
|
|
159
208
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
):
|
|
164
|
-
return urljoin(self.host, self.path)
|
|
209
|
+
def enrich(self):
|
|
210
|
+
if self.path and not self.path.startswith("/"):
|
|
211
|
+
self.path = f"/{self.path}"
|
|
165
212
|
|
|
166
|
-
def
|
|
213
|
+
def validate(
|
|
167
214
|
self,
|
|
168
|
-
name: str,
|
|
169
215
|
project: str,
|
|
170
216
|
functions: Union[
|
|
171
|
-
list[
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
],
|
|
179
|
-
Union[RemoteRuntime, ServingRuntime],
|
|
217
|
+
list[
|
|
218
|
+
Union[
|
|
219
|
+
str,
|
|
220
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
221
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
222
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
223
|
+
]
|
|
180
224
|
],
|
|
225
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
226
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
227
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
181
228
|
],
|
|
182
229
|
canary: Optional[list[int]] = None,
|
|
183
|
-
|
|
184
|
-
password: Optional[str] = None,
|
|
230
|
+
ports: Optional[list[int]] = None,
|
|
185
231
|
):
|
|
186
|
-
if not name:
|
|
187
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
188
|
-
"API Gateway name cannot be empty"
|
|
189
|
-
)
|
|
190
|
-
|
|
191
232
|
self.functions = self._validate_functions(project=project, functions=functions)
|
|
192
233
|
|
|
193
234
|
# validating canary
|
|
194
235
|
if canary:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
236
|
+
self.canary = self._validate_canary(canary)
|
|
237
|
+
|
|
238
|
+
# validating ports
|
|
239
|
+
if ports:
|
|
240
|
+
self.ports = self._validate_ports(ports)
|
|
241
|
+
|
|
242
|
+
def _validate_canary(self, canary: list[int]):
|
|
243
|
+
if len(self.functions) != len(canary):
|
|
244
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
245
|
+
"Function and canary lists lengths do not match"
|
|
246
|
+
)
|
|
247
|
+
for canary_percent in canary:
|
|
248
|
+
if canary_percent < 0 or canary_percent > 100:
|
|
205
249
|
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
206
|
-
"The
|
|
250
|
+
"The percentage value must be in the range from 0 to 100"
|
|
207
251
|
)
|
|
252
|
+
if sum(canary) != 100:
|
|
253
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
254
|
+
"The sum of canary function percents should be equal to 100"
|
|
255
|
+
)
|
|
256
|
+
return canary
|
|
208
257
|
|
|
209
|
-
|
|
210
|
-
if
|
|
211
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
258
|
+
def _validate_ports(self, ports):
|
|
259
|
+
if len(self.functions) != len(ports):
|
|
260
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
261
|
+
"Function and port lists lengths do not match"
|
|
262
|
+
)
|
|
212
263
|
|
|
213
|
-
|
|
214
|
-
raise mlrun.errors.MLRunInvalidArgumentError("Username is not specified")
|
|
264
|
+
return ports
|
|
215
265
|
|
|
216
266
|
@staticmethod
|
|
217
267
|
def _validate_functions(
|
|
218
268
|
project: str,
|
|
219
269
|
functions: Union[
|
|
220
|
-
list[
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
],
|
|
228
|
-
Union[RemoteRuntime, ServingRuntime],
|
|
270
|
+
list[
|
|
271
|
+
Union[
|
|
272
|
+
str,
|
|
273
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
274
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
275
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
276
|
+
]
|
|
229
277
|
],
|
|
278
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
279
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
280
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
230
281
|
],
|
|
231
282
|
):
|
|
232
283
|
if not isinstance(functions, list):
|
|
@@ -242,7 +293,21 @@ class APIGateway:
|
|
|
242
293
|
function_names = []
|
|
243
294
|
for func in functions:
|
|
244
295
|
if isinstance(func, str):
|
|
245
|
-
|
|
296
|
+
# check whether the function was passed as a URI or just a name
|
|
297
|
+
parsed_project, function_name, _, _ = (
|
|
298
|
+
mlrun.common.helpers.parse_versioned_object_uri(func)
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
if parsed_project and function_name:
|
|
302
|
+
# check that parsed project and passed project are the same
|
|
303
|
+
if parsed_project != project:
|
|
304
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
305
|
+
"Function doesn't belong to passed project"
|
|
306
|
+
)
|
|
307
|
+
function_uri = func
|
|
308
|
+
else:
|
|
309
|
+
function_uri = mlrun.utils.generate_object_uri(project, func)
|
|
310
|
+
function_names.append(function_uri)
|
|
246
311
|
continue
|
|
247
312
|
|
|
248
313
|
function_name = (
|
|
@@ -257,16 +322,372 @@ class APIGateway:
|
|
|
257
322
|
f"input function {function_name} "
|
|
258
323
|
f"does not belong to this project"
|
|
259
324
|
)
|
|
260
|
-
|
|
325
|
+
function_uri = mlrun.utils.generate_object_uri(
|
|
326
|
+
project,
|
|
327
|
+
function_name,
|
|
328
|
+
func.metadata.tag,
|
|
329
|
+
func.metadata.hash,
|
|
330
|
+
)
|
|
331
|
+
function_names.append(function_uri)
|
|
261
332
|
return function_names
|
|
262
333
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
334
|
+
|
|
335
|
+
class APIGatewayStatus(ModelObj):
|
|
336
|
+
def __init__(self, state: Optional[schemas.APIGatewayState] = None):
|
|
337
|
+
self.state = state or schemas.APIGatewayState.none
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class APIGateway(ModelObj):
|
|
341
|
+
_dict_fields = [
|
|
342
|
+
"metadata",
|
|
343
|
+
"spec",
|
|
344
|
+
"state",
|
|
345
|
+
]
|
|
346
|
+
|
|
347
|
+
@min_nuclio_versions("1.13.1")
|
|
348
|
+
def __init__(
|
|
349
|
+
self,
|
|
350
|
+
metadata: APIGatewayMetadata,
|
|
351
|
+
spec: APIGatewaySpec,
|
|
352
|
+
status: Optional[APIGatewayStatus] = None,
|
|
353
|
+
):
|
|
354
|
+
"""
|
|
355
|
+
Initialize the APIGateway instance.
|
|
356
|
+
|
|
357
|
+
:param metadata: (APIGatewayMetadata) The metadata of the API gateway.
|
|
358
|
+
:param spec: (APIGatewaySpec) The spec of the API gateway.
|
|
359
|
+
:param status: (APIGatewayStatus) The status of the API gateway.
|
|
360
|
+
"""
|
|
361
|
+
self.metadata = metadata
|
|
362
|
+
self.spec = spec
|
|
363
|
+
self.status = status
|
|
364
|
+
|
|
365
|
+
@property
|
|
366
|
+
def metadata(self) -> APIGatewayMetadata:
|
|
367
|
+
return self._metadata
|
|
368
|
+
|
|
369
|
+
@metadata.setter
|
|
370
|
+
def metadata(self, metadata):
|
|
371
|
+
self._metadata = self._verify_dict(metadata, "metadata", APIGatewayMetadata)
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def spec(self) -> APIGatewaySpec:
|
|
375
|
+
return self._spec
|
|
376
|
+
|
|
377
|
+
@spec.setter
|
|
378
|
+
def spec(self, spec):
|
|
379
|
+
self._spec = self._verify_dict(spec, "spec", APIGatewaySpec)
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def status(self) -> APIGatewayStatus:
|
|
383
|
+
return self._status
|
|
384
|
+
|
|
385
|
+
@status.setter
|
|
386
|
+
def status(self, status):
|
|
387
|
+
self._status = self._verify_dict(status, "status", APIGatewayStatus)
|
|
388
|
+
|
|
389
|
+
def invoke(
|
|
390
|
+
self,
|
|
391
|
+
method="POST",
|
|
392
|
+
headers: dict = None,
|
|
393
|
+
credentials: Optional[tuple[str, str]] = None,
|
|
394
|
+
path: Optional[str] = None,
|
|
395
|
+
body: Optional[Union[str, bytes, dict]] = None,
|
|
396
|
+
**kwargs,
|
|
397
|
+
):
|
|
398
|
+
"""
|
|
399
|
+
Invoke the API gateway.
|
|
400
|
+
|
|
401
|
+
:param method: (str, optional) The HTTP method for the invocation.
|
|
402
|
+
:param headers: (dict, optional) The HTTP headers for the invocation.
|
|
403
|
+
:param credentials: (Optional[tuple[str, str]], optional) The (username,password) for the invocation if required
|
|
404
|
+
can also be set by the environment variable (_, V3IO_ACCESS_KEY) for access key authentication.
|
|
405
|
+
:param path: (str, optional) The sub-path for the invocation.
|
|
406
|
+
:param body: (Optional[Union[str, bytes, dict]]) The body of the invocation.
|
|
407
|
+
:param kwargs: (dict) Additional keyword arguments.
|
|
408
|
+
|
|
409
|
+
:return: The response from the API gateway invocation.
|
|
410
|
+
"""
|
|
411
|
+
if not self.invoke_url:
|
|
412
|
+
# try to resolve invoke_url before fail
|
|
413
|
+
self.sync()
|
|
414
|
+
if not self.invoke_url:
|
|
415
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
416
|
+
"Invocation url is not set. Set up gateway's `invoke_url` attribute."
|
|
417
|
+
)
|
|
418
|
+
if not self.is_ready():
|
|
419
|
+
raise mlrun.errors.MLRunPreconditionFailedError(
|
|
420
|
+
f"API gateway is not ready. " f"Current state: {self.status.state}"
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
auth = None
|
|
424
|
+
|
|
425
|
+
if (
|
|
426
|
+
self.spec.authentication.authentication_mode
|
|
427
|
+
== schemas.APIGatewayAuthenticationMode.basic.value
|
|
428
|
+
):
|
|
429
|
+
if not credentials:
|
|
430
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
431
|
+
"API Gateway invocation requires authentication. Please pass credentials"
|
|
432
|
+
)
|
|
433
|
+
auth = NuclioAuthInfo(
|
|
434
|
+
username=credentials[0], password=credentials[1]
|
|
435
|
+
).to_requests_auth()
|
|
436
|
+
|
|
437
|
+
if (
|
|
438
|
+
self.spec.authentication.authentication_mode
|
|
439
|
+
== schemas.APIGatewayAuthenticationMode.access_key.value
|
|
440
|
+
):
|
|
441
|
+
# inject access key from env
|
|
442
|
+
if credentials:
|
|
443
|
+
auth = NuclioAuthInfo(
|
|
444
|
+
username=credentials[0],
|
|
445
|
+
password=credentials[1],
|
|
446
|
+
mode=NuclioAuthKinds.iguazio,
|
|
447
|
+
).to_requests_auth()
|
|
448
|
+
else:
|
|
449
|
+
auth = NuclioAuthInfo().from_envvar().to_requests_auth()
|
|
450
|
+
if not auth:
|
|
451
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
452
|
+
"API Gateway invocation requires authentication. Please set V3IO_ACCESS_KEY env var"
|
|
453
|
+
)
|
|
454
|
+
url = urljoin(self.invoke_url, path or "")
|
|
455
|
+
|
|
456
|
+
# Determine the correct keyword argument for the body
|
|
457
|
+
if isinstance(body, dict):
|
|
458
|
+
kwargs["json"] = body
|
|
459
|
+
elif isinstance(body, (str, bytes)):
|
|
460
|
+
kwargs["data"] = body
|
|
461
|
+
|
|
462
|
+
return requests.request(
|
|
463
|
+
method=method,
|
|
464
|
+
url=url,
|
|
465
|
+
headers=headers or {},
|
|
466
|
+
auth=auth,
|
|
467
|
+
**kwargs,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
def wait_for_readiness(self, max_wait_time=90):
|
|
471
|
+
"""
|
|
472
|
+
Wait for the API gateway to become ready within the maximum wait time.
|
|
473
|
+
|
|
474
|
+
Parameters:
|
|
475
|
+
max_wait_time: int - Maximum time to wait in seconds (default is 90 seconds).
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
bool: True if the entity becomes ready within the maximum wait time, False otherwise
|
|
479
|
+
"""
|
|
480
|
+
|
|
481
|
+
def _ensure_ready():
|
|
482
|
+
if not self.is_ready():
|
|
483
|
+
raise AssertionError(
|
|
484
|
+
f"Waiting for gateway readiness is taking more than {max_wait_time} seconds"
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
return mlrun.utils.helpers.retry_until_successful(
|
|
488
|
+
3, max_wait_time, logger, False, _ensure_ready
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
def is_ready(self):
|
|
492
|
+
if self.status.state is not schemas.api_gateway.APIGatewayState.ready:
|
|
493
|
+
# try to sync the state
|
|
494
|
+
self.sync()
|
|
495
|
+
return self.status.state == schemas.api_gateway.APIGatewayState.ready
|
|
496
|
+
|
|
497
|
+
def sync(self):
|
|
498
|
+
"""
|
|
499
|
+
Synchronize the API gateway from the server.
|
|
500
|
+
"""
|
|
501
|
+
synced_gateway = mlrun.get_run_db().get_api_gateway(
|
|
502
|
+
self.metadata.name, self.spec.project
|
|
269
503
|
)
|
|
504
|
+
synced_gateway = self.from_scheme(synced_gateway)
|
|
505
|
+
|
|
506
|
+
self.spec.host = synced_gateway.spec.host
|
|
507
|
+
self.spec.path = synced_gateway.spec.path
|
|
508
|
+
self.spec.authentication = synced_gateway.spec.authentication
|
|
509
|
+
self.spec.functions = synced_gateway.spec.functions
|
|
510
|
+
self.spec.canary = synced_gateway.spec.canary
|
|
511
|
+
self.spec.description = synced_gateway.spec.description
|
|
512
|
+
self.status.state = synced_gateway.status.state
|
|
513
|
+
|
|
514
|
+
def with_basic_auth(self, username: str, password: str):
|
|
515
|
+
"""
|
|
516
|
+
Set basic authentication for the API gateway.
|
|
517
|
+
|
|
518
|
+
:param username: (str) The username for basic authentication.
|
|
519
|
+
:param password: (str) The password for basic authentication.
|
|
520
|
+
"""
|
|
521
|
+
self.spec.authentication = BasicAuth(username=username, password=password)
|
|
522
|
+
|
|
523
|
+
@min_iguazio_versions("3.5.5")
|
|
524
|
+
def with_access_key_auth(self):
|
|
525
|
+
"""
|
|
526
|
+
Set access key authentication for the API gateway.
|
|
527
|
+
"""
|
|
528
|
+
self.spec.authentication = AccessKeyAuth()
|
|
529
|
+
|
|
530
|
+
def with_canary(
|
|
531
|
+
self,
|
|
532
|
+
functions: Union[
|
|
533
|
+
list[
|
|
534
|
+
Union[
|
|
535
|
+
str,
|
|
536
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
537
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
538
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
539
|
+
]
|
|
540
|
+
],
|
|
541
|
+
"mlrun.runtimes.nuclio.function.RemoteRuntime",
|
|
542
|
+
"mlrun.runtimes.nuclio.serving.ServingRuntime",
|
|
543
|
+
"mlrun.runtimes.nuclio.application.ApplicationRuntime",
|
|
544
|
+
],
|
|
545
|
+
canary: list[int],
|
|
546
|
+
):
|
|
547
|
+
"""
|
|
548
|
+
Set canary function for the API gateway
|
|
549
|
+
|
|
550
|
+
:param functions: The list of functions associated with the API gateway
|
|
551
|
+
Can be a list of function names (["my-func1", "my-func2"])
|
|
552
|
+
or a list of nuclio functions of types
|
|
553
|
+
:py:class:`~mlrun.runtimes.nuclio.function.RemoteRuntime` OR
|
|
554
|
+
:py:class:`~mlrun.runtimes.nuclio.serving.ServingRuntime` OR
|
|
555
|
+
:py:class:`~mlrun.runtimes.nuclio.application.ApplicationRuntime`
|
|
556
|
+
:param canary: The canary percents for the API gateway of type list[int]; for instance: [20,80]
|
|
557
|
+
|
|
558
|
+
"""
|
|
559
|
+
if len(functions) != 2:
|
|
560
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
561
|
+
f"Gateway with canary can be created only with two functions, "
|
|
562
|
+
f"the number of functions passed is {len(functions)}"
|
|
563
|
+
)
|
|
564
|
+
self.spec.validate(
|
|
565
|
+
project=self.spec.project, functions=functions, canary=canary
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
def with_ports(self, ports: list[int]):
|
|
569
|
+
"""
|
|
570
|
+
Set ports for the API gateway
|
|
571
|
+
|
|
572
|
+
:param ports: The ports of the API gateway, as a list of integers that correspond to the functions in the
|
|
573
|
+
functions list. for instance: [8050] or [8050, 8081]
|
|
574
|
+
"""
|
|
575
|
+
self.spec.validate(
|
|
576
|
+
project=self.spec.project, functions=self.spec.functions, ports=ports
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
def with_force_ssl_redirect(self):
|
|
580
|
+
"""
|
|
581
|
+
Set SSL redirect annotation for the API gateway.
|
|
582
|
+
"""
|
|
583
|
+
self.metadata.annotations["nginx.ingress.kubernetes.io/force-ssl-redirect"] = (
|
|
584
|
+
"true"
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
def with_gateway_timeout(self, gateway_timeout: int):
|
|
588
|
+
"""
|
|
589
|
+
Set gateway proxy connect/read/send timeout annotations
|
|
590
|
+
:param gateway_timeout: The timeout in seconds
|
|
591
|
+
"""
|
|
592
|
+
mlrun.runtimes.utils.enrich_gateway_timeout_annotations(
|
|
593
|
+
self.metadata.annotations, gateway_timeout
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
def with_annotations(self, annotations: dict):
|
|
597
|
+
"""set a key/value annotations in the metadata of the api gateway"""
|
|
598
|
+
for key, value in annotations.items():
|
|
599
|
+
self.metadata.annotations[key] = str(value)
|
|
600
|
+
return self
|
|
601
|
+
|
|
602
|
+
@classmethod
|
|
603
|
+
def from_scheme(cls, api_gateway: schemas.APIGateway):
|
|
604
|
+
project = api_gateway.metadata.labels.get(
|
|
605
|
+
mlrun_constants.MLRunInternalLabels.nuclio_project_name
|
|
606
|
+
)
|
|
607
|
+
functions, canary = cls._resolve_canary(api_gateway.spec.upstreams)
|
|
608
|
+
state = (
|
|
609
|
+
api_gateway.status.state
|
|
610
|
+
if api_gateway.status
|
|
611
|
+
else schemas.APIGatewayState.none
|
|
612
|
+
)
|
|
613
|
+
new_api_gateway = cls(
|
|
614
|
+
metadata=APIGatewayMetadata(
|
|
615
|
+
name=api_gateway.spec.name,
|
|
616
|
+
annotations=api_gateway.metadata.annotations,
|
|
617
|
+
labels=api_gateway.metadata.labels,
|
|
618
|
+
),
|
|
619
|
+
spec=APIGatewaySpec(
|
|
620
|
+
project=project,
|
|
621
|
+
description=api_gateway.spec.description,
|
|
622
|
+
host=api_gateway.spec.host,
|
|
623
|
+
path=api_gateway.spec.path,
|
|
624
|
+
authentication=APIGatewayAuthenticator.from_scheme(api_gateway.spec),
|
|
625
|
+
functions=functions,
|
|
626
|
+
canary=canary,
|
|
627
|
+
),
|
|
628
|
+
status=APIGatewayStatus(state=state),
|
|
629
|
+
)
|
|
630
|
+
return new_api_gateway
|
|
631
|
+
|
|
632
|
+
def to_scheme(self) -> schemas.APIGateway:
|
|
633
|
+
upstreams = (
|
|
634
|
+
[
|
|
635
|
+
schemas.APIGatewayUpstream(
|
|
636
|
+
nucliofunction={"name": self.spec.functions[0]},
|
|
637
|
+
percentage=self.spec.canary[0],
|
|
638
|
+
),
|
|
639
|
+
schemas.APIGatewayUpstream(
|
|
640
|
+
# do not set percent for the second function,
|
|
641
|
+
# so we can define which function to display as a primary one in UI
|
|
642
|
+
nucliofunction={"name": self.spec.functions[1]},
|
|
643
|
+
),
|
|
644
|
+
]
|
|
645
|
+
if self.spec.canary
|
|
646
|
+
else [
|
|
647
|
+
schemas.APIGatewayUpstream(
|
|
648
|
+
nucliofunction={"name": function_name},
|
|
649
|
+
)
|
|
650
|
+
for function_name in self.spec.functions
|
|
651
|
+
]
|
|
652
|
+
)
|
|
653
|
+
if self.spec.ports:
|
|
654
|
+
for i, port in enumerate(self.spec.ports):
|
|
655
|
+
upstreams[i].port = port
|
|
656
|
+
|
|
657
|
+
api_gateway = schemas.APIGateway(
|
|
658
|
+
metadata=schemas.APIGatewayMetadata(
|
|
659
|
+
name=self.metadata.name,
|
|
660
|
+
labels=self.metadata.labels,
|
|
661
|
+
annotations=self.metadata.annotations,
|
|
662
|
+
),
|
|
663
|
+
spec=schemas.APIGatewaySpec(
|
|
664
|
+
name=self.metadata.name,
|
|
665
|
+
description=self.spec.description,
|
|
666
|
+
host=self.spec.host,
|
|
667
|
+
path=self.spec.path,
|
|
668
|
+
authenticationMode=schemas.APIGatewayAuthenticationMode.from_str(
|
|
669
|
+
self.spec.authentication.authentication_mode
|
|
670
|
+
),
|
|
671
|
+
upstreams=upstreams,
|
|
672
|
+
),
|
|
673
|
+
status=schemas.APIGatewayStatus(state=self.status.state),
|
|
674
|
+
)
|
|
675
|
+
api_gateway.spec.authentication = self.spec.authentication.to_scheme()
|
|
676
|
+
return api_gateway
|
|
677
|
+
|
|
678
|
+
@property
|
|
679
|
+
def invoke_url(
|
|
680
|
+
self,
|
|
681
|
+
):
|
|
682
|
+
"""
|
|
683
|
+
Get the invoke URL.
|
|
684
|
+
|
|
685
|
+
:return: (str) The invoke URL.
|
|
686
|
+
"""
|
|
687
|
+
host = self.spec.host
|
|
688
|
+
if not self.spec.host.startswith("http"):
|
|
689
|
+
host = f"https://{self.spec.host}"
|
|
690
|
+
return urljoin(host, self.spec.path).rstrip("/")
|
|
270
691
|
|
|
271
692
|
@staticmethod
|
|
272
693
|
def _generate_basic_auth(username: str, password: str):
|
|
@@ -275,7 +696,7 @@ class APIGateway:
|
|
|
275
696
|
|
|
276
697
|
@staticmethod
|
|
277
698
|
def _resolve_canary(
|
|
278
|
-
upstreams: list[
|
|
699
|
+
upstreams: list[schemas.APIGatewayUpstream],
|
|
279
700
|
) -> tuple[Union[list[str], None], Union[list[int], None]]:
|
|
280
701
|
if len(upstreams) == 1:
|
|
281
702
|
return [upstreams[0].nucliofunction.get("name")], None
|
|
@@ -298,3 +719,51 @@ class APIGateway:
|
|
|
298
719
|
else:
|
|
299
720
|
# Nuclio only supports 1 or 2 upstream functions
|
|
300
721
|
return None, None
|
|
722
|
+
|
|
723
|
+
@property
|
|
724
|
+
def name(self):
|
|
725
|
+
return self.metadata.name
|
|
726
|
+
|
|
727
|
+
@name.setter
|
|
728
|
+
def name(self, value):
|
|
729
|
+
self.metadata.name = value
|
|
730
|
+
|
|
731
|
+
@property
|
|
732
|
+
def project(self):
|
|
733
|
+
return self.spec.project
|
|
734
|
+
|
|
735
|
+
@project.setter
|
|
736
|
+
def project(self, value):
|
|
737
|
+
self.spec.project = value
|
|
738
|
+
|
|
739
|
+
@property
|
|
740
|
+
def description(self):
|
|
741
|
+
return self.spec.description
|
|
742
|
+
|
|
743
|
+
@description.setter
|
|
744
|
+
def description(self, value):
|
|
745
|
+
self.spec.description = value
|
|
746
|
+
|
|
747
|
+
@property
|
|
748
|
+
def host(self):
|
|
749
|
+
return self.spec.host
|
|
750
|
+
|
|
751
|
+
@host.setter
|
|
752
|
+
def host(self, value):
|
|
753
|
+
self.spec.host = value
|
|
754
|
+
|
|
755
|
+
@property
|
|
756
|
+
def path(self):
|
|
757
|
+
return self.spec.path
|
|
758
|
+
|
|
759
|
+
@path.setter
|
|
760
|
+
def path(self, value):
|
|
761
|
+
self.spec.path = value
|
|
762
|
+
|
|
763
|
+
@property
|
|
764
|
+
def authentication(self):
|
|
765
|
+
return self.spec.authentication
|
|
766
|
+
|
|
767
|
+
@authentication.setter
|
|
768
|
+
def authentication(self, value):
|
|
769
|
+
self.spec.authentication = value
|