mlrun 1.10.0rc18__py3-none-any.whl → 1.11.0rc16__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 +24 -3
- mlrun/__main__.py +0 -4
- mlrun/artifacts/dataset.py +2 -2
- mlrun/artifacts/document.py +6 -1
- mlrun/artifacts/llm_prompt.py +21 -15
- mlrun/artifacts/model.py +3 -3
- mlrun/artifacts/plots.py +1 -1
- mlrun/{model_monitoring/db/tsdb/tdengine → auth}/__init__.py +2 -3
- mlrun/auth/nuclio.py +89 -0
- mlrun/auth/providers.py +429 -0
- mlrun/auth/utils.py +415 -0
- mlrun/common/constants.py +14 -0
- mlrun/common/model_monitoring/helpers.py +123 -0
- mlrun/common/runtimes/constants.py +28 -0
- mlrun/common/schemas/__init__.py +14 -3
- mlrun/common/schemas/alert.py +2 -2
- mlrun/common/schemas/api_gateway.py +3 -0
- mlrun/common/schemas/auth.py +12 -10
- mlrun/common/schemas/client_spec.py +4 -0
- mlrun/common/schemas/constants.py +25 -0
- mlrun/common/schemas/frontend_spec.py +1 -8
- mlrun/common/schemas/function.py +34 -0
- mlrun/common/schemas/hub.py +33 -20
- mlrun/common/schemas/model_monitoring/__init__.py +2 -1
- mlrun/common/schemas/model_monitoring/constants.py +12 -15
- mlrun/common/schemas/model_monitoring/functions.py +13 -4
- mlrun/common/schemas/model_monitoring/model_endpoints.py +11 -0
- mlrun/common/schemas/pipeline.py +1 -1
- mlrun/common/schemas/secret.py +17 -2
- mlrun/common/secrets.py +95 -1
- mlrun/common/types.py +10 -10
- mlrun/config.py +69 -19
- mlrun/data_types/infer.py +2 -2
- mlrun/datastore/__init__.py +12 -5
- mlrun/datastore/azure_blob.py +162 -47
- mlrun/datastore/base.py +274 -10
- mlrun/datastore/datastore.py +7 -2
- mlrun/datastore/datastore_profile.py +84 -22
- mlrun/datastore/model_provider/huggingface_provider.py +225 -41
- mlrun/datastore/model_provider/mock_model_provider.py +87 -0
- mlrun/datastore/model_provider/model_provider.py +206 -74
- mlrun/datastore/model_provider/openai_provider.py +226 -66
- mlrun/datastore/s3.py +39 -18
- mlrun/datastore/sources.py +1 -1
- mlrun/datastore/store_resources.py +4 -4
- mlrun/datastore/storeytargets.py +17 -12
- mlrun/datastore/targets.py +1 -1
- mlrun/datastore/utils.py +25 -6
- mlrun/datastore/v3io.py +1 -1
- mlrun/db/base.py +63 -32
- mlrun/db/httpdb.py +373 -153
- mlrun/db/nopdb.py +54 -21
- mlrun/errors.py +4 -2
- mlrun/execution.py +66 -25
- mlrun/feature_store/api.py +1 -1
- mlrun/feature_store/common.py +1 -1
- mlrun/feature_store/feature_vector_utils.py +1 -1
- mlrun/feature_store/steps.py +8 -6
- mlrun/frameworks/_common/utils.py +3 -3
- mlrun/frameworks/_dl_common/loggers/logger.py +1 -1
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +2 -1
- mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +1 -1
- mlrun/frameworks/_ml_common/utils.py +2 -1
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +4 -3
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +2 -1
- mlrun/frameworks/onnx/dataset.py +2 -1
- mlrun/frameworks/onnx/mlrun_interface.py +2 -1
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +5 -4
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +2 -1
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +2 -1
- mlrun/frameworks/pytorch/utils.py +2 -1
- mlrun/frameworks/sklearn/metric.py +2 -1
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +5 -4
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +2 -1
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +2 -1
- mlrun/hub/__init__.py +52 -0
- mlrun/hub/base.py +142 -0
- mlrun/hub/module.py +172 -0
- mlrun/hub/step.py +113 -0
- mlrun/k8s_utils.py +105 -16
- mlrun/launcher/base.py +15 -7
- mlrun/launcher/local.py +4 -1
- mlrun/model.py +14 -4
- mlrun/model_monitoring/__init__.py +0 -1
- mlrun/model_monitoring/api.py +65 -28
- mlrun/model_monitoring/applications/__init__.py +1 -1
- mlrun/model_monitoring/applications/base.py +299 -128
- mlrun/model_monitoring/applications/context.py +2 -4
- mlrun/model_monitoring/controller.py +132 -58
- mlrun/model_monitoring/db/_schedules.py +38 -29
- mlrun/model_monitoring/db/_stats.py +6 -16
- mlrun/model_monitoring/db/tsdb/__init__.py +9 -7
- mlrun/model_monitoring/db/tsdb/base.py +29 -9
- mlrun/model_monitoring/db/tsdb/preaggregate.py +234 -0
- mlrun/model_monitoring/db/tsdb/stream_graph_steps.py +63 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_metrics_queries.py +414 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_predictions_queries.py +376 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_results_queries.py +590 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connection.py +434 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connector.py +541 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_operations.py +808 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_schema.py +502 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream.py +163 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream_graph_steps.py +60 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_dataframe_processor.py +141 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_query_builder.py +585 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/writer_graph_steps.py +73 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +20 -9
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +235 -51
- mlrun/model_monitoring/features_drift_table.py +2 -1
- mlrun/model_monitoring/helpers.py +30 -6
- mlrun/model_monitoring/stream_processing.py +34 -28
- mlrun/model_monitoring/writer.py +224 -4
- mlrun/package/__init__.py +2 -1
- mlrun/platforms/__init__.py +0 -43
- mlrun/platforms/iguazio.py +8 -4
- mlrun/projects/operations.py +17 -11
- mlrun/projects/pipelines.py +2 -2
- mlrun/projects/project.py +187 -123
- mlrun/run.py +95 -21
- mlrun/runtimes/__init__.py +2 -186
- mlrun/runtimes/base.py +103 -25
- mlrun/runtimes/constants.py +225 -0
- mlrun/runtimes/daskjob.py +5 -2
- mlrun/runtimes/databricks_job/databricks_runtime.py +2 -1
- mlrun/runtimes/local.py +5 -2
- mlrun/runtimes/mounts.py +20 -2
- mlrun/runtimes/nuclio/__init__.py +12 -7
- mlrun/runtimes/nuclio/api_gateway.py +36 -6
- mlrun/runtimes/nuclio/application/application.py +339 -40
- mlrun/runtimes/nuclio/function.py +222 -72
- mlrun/runtimes/nuclio/serving.py +132 -42
- mlrun/runtimes/pod.py +213 -21
- mlrun/runtimes/utils.py +49 -9
- mlrun/secrets.py +99 -14
- mlrun/serving/__init__.py +2 -0
- mlrun/serving/remote.py +84 -11
- mlrun/serving/routers.py +26 -44
- mlrun/serving/server.py +138 -51
- mlrun/serving/serving_wrapper.py +6 -2
- mlrun/serving/states.py +997 -283
- mlrun/serving/steps.py +62 -0
- mlrun/serving/system_steps.py +149 -95
- mlrun/serving/v2_serving.py +9 -10
- mlrun/track/trackers/mlflow_tracker.py +29 -31
- mlrun/utils/helpers.py +292 -94
- mlrun/utils/http.py +9 -2
- mlrun/utils/notifications/notification/base.py +18 -0
- mlrun/utils/notifications/notification/git.py +3 -5
- mlrun/utils/notifications/notification/mail.py +39 -16
- mlrun/utils/notifications/notification/slack.py +2 -4
- mlrun/utils/notifications/notification/webhook.py +2 -5
- mlrun/utils/notifications/notification_pusher.py +3 -3
- mlrun/utils/version/version.json +2 -2
- mlrun/utils/version/version.py +3 -4
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/METADATA +63 -74
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/RECORD +161 -143
- mlrun/api/schemas/__init__.py +0 -259
- mlrun/db/auth_utils.py +0 -152
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +0 -344
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -75
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +0 -281
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +0 -1266
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/WHEEL +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/entry_points.txt +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/licenses/LICENSE +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/top_level.txt +0 -0
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
+
import copy
|
|
14
15
|
import pathlib
|
|
15
16
|
import typing
|
|
16
17
|
|
|
@@ -20,21 +21,20 @@ import nuclio.auth
|
|
|
20
21
|
import mlrun.common.schemas as schemas
|
|
21
22
|
import mlrun.errors
|
|
22
23
|
import mlrun.run
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
from mlrun.runtimes.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
APIGatewaySpec,
|
|
24
|
+
import mlrun.runtimes.nuclio.api_gateway as nuclio_api_gateway
|
|
25
|
+
import mlrun.runtimes.nuclio.function as nuclio_function
|
|
26
|
+
from mlrun.common.runtimes.constants import (
|
|
27
|
+
NuclioIngressAddTemplatedIngressModes,
|
|
28
|
+
ProbeTimeConfig,
|
|
29
|
+
ProbeType,
|
|
30
30
|
)
|
|
31
|
-
from mlrun.
|
|
32
|
-
from mlrun.utils import logger, update_in
|
|
31
|
+
from mlrun.utils import is_valid_port, logger, update_in
|
|
33
32
|
|
|
34
33
|
|
|
35
|
-
class ApplicationSpec(NuclioSpec):
|
|
36
|
-
_dict_fields = NuclioSpec._dict_fields + [
|
|
34
|
+
class ApplicationSpec(nuclio_function.NuclioSpec):
|
|
35
|
+
_dict_fields = nuclio_function.NuclioSpec._dict_fields + [
|
|
37
36
|
"internal_application_port",
|
|
37
|
+
"application_ports",
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
def __init__(
|
|
@@ -78,7 +78,13 @@ class ApplicationSpec(NuclioSpec):
|
|
|
78
78
|
add_templated_ingress_host_mode=None,
|
|
79
79
|
state_thresholds=None,
|
|
80
80
|
disable_default_http_trigger=None,
|
|
81
|
+
serving_spec=None,
|
|
82
|
+
graph=None,
|
|
83
|
+
parameters=None,
|
|
84
|
+
track_models=None,
|
|
81
85
|
internal_application_port=None,
|
|
86
|
+
application_ports=None,
|
|
87
|
+
auth=None,
|
|
82
88
|
):
|
|
83
89
|
super().__init__(
|
|
84
90
|
command=command,
|
|
@@ -118,19 +124,73 @@ class ApplicationSpec(NuclioSpec):
|
|
|
118
124
|
security_context=security_context,
|
|
119
125
|
service_type=service_type,
|
|
120
126
|
add_templated_ingress_host_mode=add_templated_ingress_host_mode,
|
|
127
|
+
serving_spec=serving_spec,
|
|
128
|
+
graph=graph,
|
|
129
|
+
parameters=parameters,
|
|
130
|
+
track_models=track_models,
|
|
121
131
|
state_thresholds=state_thresholds,
|
|
122
132
|
disable_default_http_trigger=disable_default_http_trigger,
|
|
133
|
+
auth=auth,
|
|
123
134
|
)
|
|
124
135
|
|
|
125
136
|
# Override default min/max replicas (don't assume application is stateless)
|
|
126
137
|
self.min_replicas = min_replicas or 1
|
|
127
138
|
self.max_replicas = max_replicas or 1
|
|
128
139
|
|
|
140
|
+
# initializing internal application port and application ports
|
|
141
|
+
self._internal_application_port = None
|
|
142
|
+
self._application_ports = []
|
|
143
|
+
|
|
144
|
+
application_ports = application_ports or []
|
|
145
|
+
|
|
146
|
+
# if internal_application_port is not provided, use the first application port
|
|
147
|
+
if not internal_application_port and len(application_ports) > 0:
|
|
148
|
+
internal_application_port = application_ports[0]
|
|
149
|
+
|
|
150
|
+
# the port of application sidecar to which traffic will be routed from a nuclio function
|
|
129
151
|
self.internal_application_port = (
|
|
130
152
|
internal_application_port
|
|
131
153
|
or mlrun.mlconf.function.application.default_sidecar_internal_port
|
|
132
154
|
)
|
|
133
155
|
|
|
156
|
+
# all exposed ports by the application sidecar
|
|
157
|
+
self.application_ports = application_ports
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def application_ports(self):
|
|
161
|
+
return self._application_ports
|
|
162
|
+
|
|
163
|
+
@application_ports.setter
|
|
164
|
+
def application_ports(self, ports):
|
|
165
|
+
"""
|
|
166
|
+
Set the application ports for the application sidecar.
|
|
167
|
+
The internal application port is always included and always first.
|
|
168
|
+
"""
|
|
169
|
+
# Handle None / single int
|
|
170
|
+
if ports is None:
|
|
171
|
+
ports = []
|
|
172
|
+
elif isinstance(ports, int):
|
|
173
|
+
ports = [ports]
|
|
174
|
+
elif not isinstance(ports, list):
|
|
175
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
176
|
+
"Application ports must be a list of integers"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Validate and normalize
|
|
180
|
+
cleaned_ports = []
|
|
181
|
+
for port in ports:
|
|
182
|
+
is_valid_port(port, raise_on_error=True)
|
|
183
|
+
if port != self.internal_application_port:
|
|
184
|
+
cleaned_ports.append(port)
|
|
185
|
+
|
|
186
|
+
application_ports = [self.internal_application_port] + cleaned_ports
|
|
187
|
+
|
|
188
|
+
# ensure multiple ports are supported in Nuclio
|
|
189
|
+
if len(application_ports) > 1:
|
|
190
|
+
nuclio_function.multiple_port_sidecar_is_supported()
|
|
191
|
+
|
|
192
|
+
self._application_ports = application_ports
|
|
193
|
+
|
|
134
194
|
@property
|
|
135
195
|
def internal_application_port(self):
|
|
136
196
|
return self._internal_application_port
|
|
@@ -138,12 +198,22 @@ class ApplicationSpec(NuclioSpec):
|
|
|
138
198
|
@internal_application_port.setter
|
|
139
199
|
def internal_application_port(self, port):
|
|
140
200
|
port = int(port)
|
|
141
|
-
|
|
142
|
-
raise ValueError("Port must be in the range 0-65535")
|
|
201
|
+
is_valid_port(port, raise_on_error=True)
|
|
143
202
|
self._internal_application_port = port
|
|
144
203
|
|
|
204
|
+
# If when internal application port is being set, length of self._application_ports is 1,
|
|
205
|
+
# it means that it consist of [old_port] only
|
|
206
|
+
# so in this case, we rewrite the list completely, by setting value to [new_value]
|
|
207
|
+
if len(self.application_ports) == 1:
|
|
208
|
+
self._application_ports = [port]
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
# when setting new internal application port, ensure that it is included in the application ports
|
|
212
|
+
# it just triggers setter logic, so setting to the same value is a no-op
|
|
213
|
+
self.application_ports = self._application_ports
|
|
145
214
|
|
|
146
|
-
|
|
215
|
+
|
|
216
|
+
class ApplicationStatus(nuclio_function.NuclioStatus):
|
|
147
217
|
def __init__(
|
|
148
218
|
self,
|
|
149
219
|
state=None,
|
|
@@ -173,15 +243,17 @@ class ApplicationStatus(NuclioStatus):
|
|
|
173
243
|
self.application_source = application_source or None
|
|
174
244
|
self.sidecar_name = sidecar_name or None
|
|
175
245
|
self.api_gateway_name = api_gateway_name or None
|
|
176
|
-
self.api_gateway: typing.Optional[APIGateway] =
|
|
246
|
+
self.api_gateway: typing.Optional[nuclio_api_gateway.APIGateway] = (
|
|
247
|
+
api_gateway or None
|
|
248
|
+
)
|
|
177
249
|
self.url = url or None
|
|
178
250
|
|
|
179
251
|
|
|
180
|
-
class ApplicationRuntime(RemoteRuntime):
|
|
252
|
+
class ApplicationRuntime(nuclio_function.RemoteRuntime):
|
|
181
253
|
kind = "application"
|
|
182
254
|
reverse_proxy_image = None
|
|
183
255
|
|
|
184
|
-
@min_nuclio_versions("1.13.1")
|
|
256
|
+
@nuclio_function.min_nuclio_versions("1.13.1")
|
|
185
257
|
def __init__(self, spec=None, metadata=None):
|
|
186
258
|
super().__init__(spec=spec, metadata=metadata)
|
|
187
259
|
|
|
@@ -206,7 +278,7 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
206
278
|
return self.status.api_gateway
|
|
207
279
|
|
|
208
280
|
@api_gateway.setter
|
|
209
|
-
def api_gateway(self, api_gateway: APIGateway):
|
|
281
|
+
def api_gateway(self, api_gateway: nuclio_api_gateway.APIGateway):
|
|
210
282
|
self.status.api_gateway = api_gateway
|
|
211
283
|
|
|
212
284
|
@property
|
|
@@ -222,6 +294,122 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
222
294
|
def set_internal_application_port(self, port: int):
|
|
223
295
|
self.spec.internal_application_port = port
|
|
224
296
|
|
|
297
|
+
def set_probe(
|
|
298
|
+
self,
|
|
299
|
+
type: str,
|
|
300
|
+
initial_delay_seconds: int | None = None,
|
|
301
|
+
period_seconds: int | None = None,
|
|
302
|
+
failure_threshold: int | None = None,
|
|
303
|
+
timeout_seconds: int | None = None,
|
|
304
|
+
http_path: str | None = None,
|
|
305
|
+
http_port: int | None = None,
|
|
306
|
+
http_scheme: str | None = None,
|
|
307
|
+
config: dict | None = None,
|
|
308
|
+
):
|
|
309
|
+
"""Set a Kubernetes probe configuration for the sidecar container
|
|
310
|
+
|
|
311
|
+
The config parameter serves as the base configuration, and individual parameters
|
|
312
|
+
override values in config. If http_path is provided without http_port and config
|
|
313
|
+
is not provided, the port will be enriched from the internal application port
|
|
314
|
+
just before deployment.
|
|
315
|
+
|
|
316
|
+
:param type: Probe type - one of "readiness", "liveness", "startup"
|
|
317
|
+
:param initial_delay_seconds: Number of seconds after the container has started before probes are initiated
|
|
318
|
+
:param period_seconds: How often (in seconds) to perform the probe
|
|
319
|
+
:param failure_threshold: Minimum consecutive failures for the probe to be considered failed
|
|
320
|
+
:param timeout_seconds: Number of seconds after which the probe times out
|
|
321
|
+
:param http_path: If provided, use an HTTP probe with this path
|
|
322
|
+
:param http_port: If HTTP probe is used and no port provided,
|
|
323
|
+
the internal application port will be used
|
|
324
|
+
:param http_scheme: "http" or "https" for HTTP probe. Defaults to "http"
|
|
325
|
+
:param config: A full dict with the probe configuration
|
|
326
|
+
(used as base, overridden by individual parameters)
|
|
327
|
+
|
|
328
|
+
:return: function object (self)
|
|
329
|
+
"""
|
|
330
|
+
self._validate_set_probes_input(locals())
|
|
331
|
+
type = ProbeType(type)
|
|
332
|
+
|
|
333
|
+
# Start with config as base
|
|
334
|
+
probe_config = copy.deepcopy(config) if config else {}
|
|
335
|
+
|
|
336
|
+
# Build HTTP probe configuration if http_path is provided
|
|
337
|
+
# Note: If http_path is None, all HTTP-related parameters (http_port, http_scheme) are ignored
|
|
338
|
+
if http_path:
|
|
339
|
+
http_probe = probe_config.get("httpGet", {})
|
|
340
|
+
http_probe["path"] = http_path
|
|
341
|
+
if http_port is not None:
|
|
342
|
+
http_probe["port"] = http_port
|
|
343
|
+
http_probe["scheme"] = http_scheme or http_probe.get("scheme", "HTTP")
|
|
344
|
+
probe_config["httpGet"] = http_probe
|
|
345
|
+
|
|
346
|
+
# Override timing parameters from explicit arguments
|
|
347
|
+
probe_config.update(
|
|
348
|
+
{
|
|
349
|
+
config.value: value
|
|
350
|
+
for config, value in {
|
|
351
|
+
ProbeTimeConfig.INITIAL_DELAY_SECONDS: initial_delay_seconds,
|
|
352
|
+
ProbeTimeConfig.PERIOD_SECONDS: period_seconds,
|
|
353
|
+
ProbeTimeConfig.FAILURE_THRESHOLD: failure_threshold,
|
|
354
|
+
ProbeTimeConfig.TIMEOUT_SECONDS: timeout_seconds,
|
|
355
|
+
}.items()
|
|
356
|
+
if value is not None
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Store probe configuration in the sidecar
|
|
361
|
+
sidecar = self._set_sidecar(self._get_sidecar_name())
|
|
362
|
+
sidecar[type.key] = probe_config
|
|
363
|
+
|
|
364
|
+
return self
|
|
365
|
+
|
|
366
|
+
def delete_probe(
|
|
367
|
+
self,
|
|
368
|
+
type: str,
|
|
369
|
+
):
|
|
370
|
+
"""Delete a Kubernetes probe configuration from the sidecar container
|
|
371
|
+
|
|
372
|
+
:param type: Probe type - one of "readiness", "liveness", "startup"
|
|
373
|
+
|
|
374
|
+
:return: function object (self)
|
|
375
|
+
"""
|
|
376
|
+
# Validate probe type
|
|
377
|
+
ProbeType.is_valid(type, raise_on_error=True)
|
|
378
|
+
type = ProbeType(type)
|
|
379
|
+
|
|
380
|
+
sidecar = self._get_sidecar()
|
|
381
|
+
if sidecar:
|
|
382
|
+
if type.key in sidecar:
|
|
383
|
+
del sidecar[type.key]
|
|
384
|
+
|
|
385
|
+
return self
|
|
386
|
+
|
|
387
|
+
def with_sidecar(
|
|
388
|
+
self,
|
|
389
|
+
name: typing.Optional[str] = None,
|
|
390
|
+
image: typing.Optional[str] = None,
|
|
391
|
+
ports: typing.Optional[typing.Union[int, list[int]]] = None,
|
|
392
|
+
command: typing.Optional[str] = None,
|
|
393
|
+
args: typing.Optional[list[str]] = None,
|
|
394
|
+
):
|
|
395
|
+
# wraps with_sidecar just to set the application ports
|
|
396
|
+
super().with_sidecar(
|
|
397
|
+
name=name,
|
|
398
|
+
image=image,
|
|
399
|
+
ports=ports,
|
|
400
|
+
command=command,
|
|
401
|
+
args=args,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
if ports:
|
|
405
|
+
if self.spec.internal_application_port != ports[0]:
|
|
406
|
+
logger.info(
|
|
407
|
+
f"Setting internal application port to the first port from the sidecar: {ports[0]}. "
|
|
408
|
+
f"If this is not intended, please set the internal_application_port explicitly."
|
|
409
|
+
)
|
|
410
|
+
self.spec.internal_application_port = ports[0]
|
|
411
|
+
self.spec.application_ports = ports
|
|
412
|
+
|
|
225
413
|
def pre_deploy_validation(self):
|
|
226
414
|
super().pre_deploy_validation()
|
|
227
415
|
if not self.spec.config.get("spec.sidecars"):
|
|
@@ -302,6 +490,7 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
302
490
|
|
|
303
491
|
:return: The default API gateway URL if created or True if the function is ready (deployed)
|
|
304
492
|
"""
|
|
493
|
+
|
|
305
494
|
if (self.requires_build() and not self.spec.image) or force_build:
|
|
306
495
|
self._fill_credentials()
|
|
307
496
|
self._build_application_image(
|
|
@@ -315,14 +504,14 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
315
504
|
show_on_failure=show_on_failure,
|
|
316
505
|
)
|
|
317
506
|
|
|
318
|
-
|
|
319
|
-
self._ensure_reverse_proxy_configurations(self)
|
|
507
|
+
self._ensure_reverse_proxy_configurations()
|
|
320
508
|
self._configure_application_sidecar()
|
|
321
509
|
|
|
322
510
|
# We only allow accessing the application via the API Gateway
|
|
323
511
|
self.spec.add_templated_ingress_host_mode = (
|
|
324
512
|
NuclioIngressAddTemplatedIngressModes.never
|
|
325
513
|
)
|
|
514
|
+
self._enrich_sidecar_probe_ports()
|
|
326
515
|
|
|
327
516
|
super().deploy(
|
|
328
517
|
project=project,
|
|
@@ -431,6 +620,7 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
431
620
|
ssl_redirect: typing.Optional[bool] = None,
|
|
432
621
|
set_as_default: bool = False,
|
|
433
622
|
gateway_timeout: typing.Optional[int] = None,
|
|
623
|
+
port: typing.Optional[int] = None,
|
|
434
624
|
):
|
|
435
625
|
"""
|
|
436
626
|
Create the application API gateway. Once the application is deployed, the API gateway can be created.
|
|
@@ -447,6 +637,8 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
447
637
|
:param set_as_default: Set the API gateway as the default for the application (`status.api_gateway`)
|
|
448
638
|
:param gateway_timeout: nginx ingress timeout in sec (request timeout, when will the gateway return an
|
|
449
639
|
error)
|
|
640
|
+
:param port: The API gateway port, used only when direct_port_access=True
|
|
641
|
+
|
|
450
642
|
:return: The API gateway URL
|
|
451
643
|
"""
|
|
452
644
|
if not name:
|
|
@@ -467,15 +659,23 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
467
659
|
"Authentication credentials not provided"
|
|
468
660
|
)
|
|
469
661
|
|
|
470
|
-
|
|
662
|
+
if not direct_port_access and port:
|
|
663
|
+
logger.warning(
|
|
664
|
+
"Ignoring 'port' because 'direct_port_access' is not enabled. "
|
|
665
|
+
"The 'port' setting is only applicable when 'direct_port_access' is enabled."
|
|
666
|
+
)
|
|
471
667
|
|
|
472
|
-
|
|
473
|
-
|
|
668
|
+
ports = (
|
|
669
|
+
port or self.spec.internal_application_port if direct_port_access else []
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
api_gateway = nuclio_api_gateway.APIGateway(
|
|
673
|
+
nuclio_api_gateway.APIGatewayMetadata(
|
|
474
674
|
name=name,
|
|
475
675
|
namespace=self.metadata.namespace,
|
|
476
676
|
labels=self.metadata.labels.copy(),
|
|
477
677
|
),
|
|
478
|
-
APIGatewaySpec(
|
|
678
|
+
nuclio_api_gateway.APIGatewaySpec(
|
|
479
679
|
functions=[self],
|
|
480
680
|
project=self.metadata.project,
|
|
481
681
|
path=path,
|
|
@@ -499,6 +699,8 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
499
699
|
api_gateway.with_access_key_auth()
|
|
500
700
|
elif authentication_mode == schemas.APIGatewayAuthenticationMode.basic:
|
|
501
701
|
api_gateway.with_basic_auth(*authentication_creds)
|
|
702
|
+
elif authentication_mode == schemas.APIGatewayAuthenticationMode.iguazio:
|
|
703
|
+
api_gateway.with_iguazio_auth()
|
|
502
704
|
|
|
503
705
|
db = self._get_db()
|
|
504
706
|
api_gateway_scheme = db.store_api_gateway(
|
|
@@ -507,12 +709,14 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
507
709
|
|
|
508
710
|
if set_as_default:
|
|
509
711
|
self.status.api_gateway_name = api_gateway_scheme.metadata.name
|
|
510
|
-
self.status.api_gateway = APIGateway.from_scheme(
|
|
712
|
+
self.status.api_gateway = nuclio_api_gateway.APIGateway.from_scheme(
|
|
713
|
+
api_gateway_scheme
|
|
714
|
+
)
|
|
511
715
|
self.status.api_gateway.wait_for_readiness()
|
|
512
716
|
self.url = self.status.api_gateway.invoke_url
|
|
513
717
|
url = self.url
|
|
514
718
|
else:
|
|
515
|
-
api_gateway = APIGateway.from_scheme(api_gateway_scheme)
|
|
719
|
+
api_gateway = nuclio_api_gateway.APIGateway.from_scheme(api_gateway_scheme)
|
|
516
720
|
api_gateway.wait_for_readiness()
|
|
517
721
|
url = api_gateway.invoke_url
|
|
518
722
|
# Update application status (enriches invocation url)
|
|
@@ -536,7 +740,7 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
536
740
|
def invoke(
|
|
537
741
|
self,
|
|
538
742
|
path: str = "",
|
|
539
|
-
body: typing.Optional[typing.Union[str, bytes, dict]] = None,
|
|
743
|
+
body: typing.Optional[typing.Union[str, bytes, dict, list]] = None,
|
|
540
744
|
method: typing.Optional[str] = None,
|
|
541
745
|
headers: typing.Optional[dict] = None,
|
|
542
746
|
force_external_address: bool = False,
|
|
@@ -595,6 +799,12 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
595
799
|
"""
|
|
596
800
|
# create a function that includes only the reverse proxy, without the application
|
|
597
801
|
|
|
802
|
+
if not mlrun.get_current_project(silent=True):
|
|
803
|
+
raise mlrun.errors.MLRunMissingProjectError(
|
|
804
|
+
"An active project is required to run deploy_reverse_proxy_image(). "
|
|
805
|
+
"Use `mlrun.get_or_create_project()` or set an active project first."
|
|
806
|
+
)
|
|
807
|
+
|
|
598
808
|
reverse_proxy_func = mlrun.run.new_function(
|
|
599
809
|
name="reverse-proxy-temp", kind="remote"
|
|
600
810
|
)
|
|
@@ -624,7 +834,7 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
624
834
|
else self.metadata.name
|
|
625
835
|
)
|
|
626
836
|
|
|
627
|
-
@min_nuclio_versions("1.13.1")
|
|
837
|
+
@nuclio_function.min_nuclio_versions("1.13.1")
|
|
628
838
|
def disable_default_http_trigger(
|
|
629
839
|
self,
|
|
630
840
|
):
|
|
@@ -632,7 +842,7 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
632
842
|
"Application runtime does not support disabling the default HTTP trigger"
|
|
633
843
|
)
|
|
634
844
|
|
|
635
|
-
@min_nuclio_versions("1.13.1")
|
|
845
|
+
@nuclio_function.min_nuclio_versions("1.13.1")
|
|
636
846
|
def enable_default_http_trigger(
|
|
637
847
|
self,
|
|
638
848
|
):
|
|
@@ -684,27 +894,42 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
684
894
|
with_mlrun=with_mlrun,
|
|
685
895
|
)
|
|
686
896
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
897
|
+
def _ensure_reverse_proxy_configurations(self):
|
|
898
|
+
# If an HTTP trigger already exists in the spec,
|
|
899
|
+
# it means the user explicitly defined a custom configuration,
|
|
900
|
+
# so, skip automatic creation.
|
|
901
|
+
skip_http_trigger_creation = False
|
|
902
|
+
for key, value in self.spec.config.items():
|
|
903
|
+
if key.startswith("spec.triggers"):
|
|
904
|
+
if isinstance(value, dict):
|
|
905
|
+
if value.get("kind") == "http":
|
|
906
|
+
skip_http_trigger_creation = True
|
|
907
|
+
break
|
|
908
|
+
if not skip_http_trigger_creation:
|
|
909
|
+
self.with_http(
|
|
910
|
+
workers=mlrun.mlconf.function.application.default_worker_number,
|
|
911
|
+
trigger_name="application-http",
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
if self.spec.build.functionSourceCode or self.status.container_image:
|
|
690
915
|
return
|
|
691
916
|
|
|
692
917
|
filename, handler = ApplicationRuntime.get_filename_and_handler()
|
|
693
918
|
name, spec, code = nuclio.build_file(
|
|
694
919
|
filename,
|
|
695
|
-
name=
|
|
920
|
+
name=self.metadata.name,
|
|
696
921
|
handler=handler,
|
|
697
922
|
)
|
|
698
|
-
|
|
699
|
-
|
|
923
|
+
self.spec.function_handler = mlrun.utils.get_in(spec, "spec.handler")
|
|
924
|
+
self.spec.build.functionSourceCode = mlrun.utils.get_in(
|
|
700
925
|
spec, "spec.build.functionSourceCode"
|
|
701
926
|
)
|
|
702
|
-
|
|
927
|
+
self.spec.nuclio_runtime = mlrun.utils.get_in(spec, "spec.runtime")
|
|
703
928
|
|
|
704
929
|
# default the reverse proxy logger level to info
|
|
705
930
|
logger_sinks_key = "spec.loggerSinks"
|
|
706
|
-
if not
|
|
707
|
-
|
|
931
|
+
if not self.spec.config.get(logger_sinks_key):
|
|
932
|
+
self.set_config(
|
|
708
933
|
logger_sinks_key, [{"level": "info", "sink": "myStdoutLoggerSink"}]
|
|
709
934
|
)
|
|
710
935
|
|
|
@@ -728,7 +953,7 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
728
953
|
self.with_sidecar(
|
|
729
954
|
name=self.status.sidecar_name,
|
|
730
955
|
image=self.status.application_image,
|
|
731
|
-
ports=self.spec.
|
|
956
|
+
ports=self.spec.application_ports,
|
|
732
957
|
command=self.spec.command,
|
|
733
958
|
args=self.spec.args,
|
|
734
959
|
)
|
|
@@ -748,6 +973,80 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
748
973
|
api_gateway_scheme = db.get_api_gateway(
|
|
749
974
|
name=self.status.api_gateway_name, project=self.metadata.project
|
|
750
975
|
)
|
|
751
|
-
self.status.api_gateway = APIGateway.from_scheme(
|
|
976
|
+
self.status.api_gateway = nuclio_api_gateway.APIGateway.from_scheme(
|
|
977
|
+
api_gateway_scheme
|
|
978
|
+
)
|
|
752
979
|
self.status.api_gateway.wait_for_readiness()
|
|
753
980
|
self.url = self.status.api_gateway.invoke_url
|
|
981
|
+
|
|
982
|
+
def _enrich_sidecar_probe_ports(self):
|
|
983
|
+
"""Enrich sidecar probe configurations with internal application port if needed
|
|
984
|
+
|
|
985
|
+
This method is called just before deployment to automatically enrich HTTP probes
|
|
986
|
+
in the sidecar container that were configured without an explicit port.
|
|
987
|
+
|
|
988
|
+
Enrichment logic:
|
|
989
|
+
- Only enriches HTTP probes (httpGet) that don't already have a port specified
|
|
990
|
+
- If the user explicitly provided http_port in set_probe(), enrichment is skipped
|
|
991
|
+
- If the user provided a port in the config dict, enrichment is skipped
|
|
992
|
+
- Enrichment happens just before deployment to ensure the latest internal_application_port
|
|
993
|
+
value is used, even if it was modified after set_probe() was called
|
|
994
|
+
"""
|
|
995
|
+
# Check each probe type and enrich missing HTTP ports
|
|
996
|
+
sidecar = self._get_sidecar()
|
|
997
|
+
if not sidecar:
|
|
998
|
+
return
|
|
999
|
+
|
|
1000
|
+
for probe_type in ProbeType:
|
|
1001
|
+
probe_config = sidecar.get(probe_type.key)
|
|
1002
|
+
|
|
1003
|
+
if probe_config and isinstance(probe_config, dict):
|
|
1004
|
+
http_get = probe_config.get("httpGet")
|
|
1005
|
+
if http_get and isinstance(http_get, dict) and "port" not in http_get:
|
|
1006
|
+
if self.spec.internal_application_port is None:
|
|
1007
|
+
raise ValueError(
|
|
1008
|
+
f"Cannot enrich {probe_type.value} probe: HTTP probe requires a port, "
|
|
1009
|
+
"but internal_application_port is not set. "
|
|
1010
|
+
"Please set the internal_application_port or provide http_port in set_probe()."
|
|
1011
|
+
)
|
|
1012
|
+
http_get["port"] = self.spec.internal_application_port
|
|
1013
|
+
logger.debug(
|
|
1014
|
+
"Enriched sidecar probe port",
|
|
1015
|
+
probe_type=probe_type.value,
|
|
1016
|
+
port=http_get["port"],
|
|
1017
|
+
application_name=self.metadata.name,
|
|
1018
|
+
)
|
|
1019
|
+
sidecar[probe_type.key] = probe_config
|
|
1020
|
+
|
|
1021
|
+
def _get_sidecar(self) -> dict | None:
|
|
1022
|
+
"""Get the sidecar container for ApplicationRuntime
|
|
1023
|
+
|
|
1024
|
+
Returns the sidecar dict if found, None otherwise.
|
|
1025
|
+
"""
|
|
1026
|
+
sidecar_name = self._get_sidecar_name()
|
|
1027
|
+
if not hasattr(self.spec, "config") or "spec.sidecars" not in self.spec.config:
|
|
1028
|
+
return None
|
|
1029
|
+
|
|
1030
|
+
sidecars = self.spec.config["spec.sidecars"]
|
|
1031
|
+
for sidecar in sidecars:
|
|
1032
|
+
if sidecar.get("name") == sidecar_name:
|
|
1033
|
+
return sidecar
|
|
1034
|
+
|
|
1035
|
+
return None
|
|
1036
|
+
|
|
1037
|
+
def _get_sidecar_name(self) -> str:
|
|
1038
|
+
return f"{self.metadata.name}-sidecar"
|
|
1039
|
+
|
|
1040
|
+
@staticmethod
|
|
1041
|
+
def _validate_set_probes_input(params: dict):
|
|
1042
|
+
# Validate probe type
|
|
1043
|
+
ProbeType.is_valid(params.get("type"), raise_on_error=True)
|
|
1044
|
+
|
|
1045
|
+
# At least one optional parameter must be provided
|
|
1046
|
+
optional_params = {
|
|
1047
|
+
k: v for k, v in params.items() if (k != "type" and k != "self")
|
|
1048
|
+
}
|
|
1049
|
+
if all(v is None for v in optional_params.values()):
|
|
1050
|
+
raise ValueError(
|
|
1051
|
+
"Empty probe configuration: at least one parameter must be set"
|
|
1052
|
+
)
|