mlrun 1.6.4rc2__py3-none-any.whl → 1.7.0rc20__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 +26 -112
- mlrun/alerts/__init__.py +15 -0
- mlrun/alerts/alert.py +144 -0
- mlrun/api/schemas/__init__.py +5 -4
- mlrun/artifacts/__init__.py +8 -3
- mlrun/artifacts/base.py +46 -257
- mlrun/artifacts/dataset.py +11 -192
- mlrun/artifacts/manager.py +47 -48
- mlrun/artifacts/model.py +31 -159
- mlrun/artifacts/plots.py +23 -380
- mlrun/common/constants.py +69 -0
- mlrun/common/db/sql_session.py +2 -3
- mlrun/common/formatters/__init__.py +19 -0
- mlrun/common/formatters/artifact.py +21 -0
- mlrun/common/formatters/base.py +78 -0
- mlrun/common/formatters/function.py +41 -0
- mlrun/common/formatters/pipeline.py +53 -0
- mlrun/common/formatters/project.py +51 -0
- mlrun/common/helpers.py +1 -2
- mlrun/common/model_monitoring/helpers.py +9 -5
- mlrun/{runtimes → common/runtimes}/constants.py +37 -9
- mlrun/common/schemas/__init__.py +24 -4
- mlrun/common/schemas/alert.py +203 -0
- mlrun/common/schemas/api_gateway.py +148 -0
- mlrun/common/schemas/artifact.py +18 -8
- mlrun/common/schemas/auth.py +11 -5
- mlrun/common/schemas/background_task.py +1 -1
- mlrun/common/schemas/client_spec.py +4 -1
- mlrun/common/schemas/feature_store.py +16 -16
- mlrun/common/schemas/frontend_spec.py +8 -7
- mlrun/common/schemas/function.py +5 -1
- mlrun/common/schemas/hub.py +11 -18
- mlrun/common/schemas/memory_reports.py +2 -2
- mlrun/common/schemas/model_monitoring/__init__.py +18 -3
- mlrun/common/schemas/model_monitoring/constants.py +83 -26
- mlrun/common/schemas/model_monitoring/grafana.py +13 -9
- mlrun/common/schemas/model_monitoring/model_endpoints.py +99 -16
- mlrun/common/schemas/notification.py +4 -4
- mlrun/common/schemas/object.py +2 -2
- mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
- mlrun/common/schemas/pipeline.py +1 -10
- mlrun/common/schemas/project.py +24 -23
- mlrun/common/schemas/runtime_resource.py +8 -12
- mlrun/common/schemas/schedule.py +3 -3
- mlrun/common/schemas/tag.py +1 -2
- mlrun/common/schemas/workflow.py +2 -2
- mlrun/common/types.py +7 -1
- mlrun/config.py +54 -17
- mlrun/data_types/to_pandas.py +10 -12
- mlrun/datastore/__init__.py +5 -8
- mlrun/datastore/alibaba_oss.py +130 -0
- mlrun/datastore/azure_blob.py +17 -5
- mlrun/datastore/base.py +62 -39
- mlrun/datastore/datastore.py +28 -9
- mlrun/datastore/datastore_profile.py +146 -20
- mlrun/datastore/filestore.py +0 -1
- mlrun/datastore/google_cloud_storage.py +6 -2
- mlrun/datastore/hdfs.py +56 -0
- mlrun/datastore/inmem.py +2 -2
- mlrun/datastore/redis.py +6 -2
- mlrun/datastore/s3.py +9 -0
- mlrun/datastore/snowflake_utils.py +43 -0
- mlrun/datastore/sources.py +201 -96
- mlrun/datastore/spark_utils.py +1 -2
- mlrun/datastore/store_resources.py +7 -7
- mlrun/datastore/targets.py +358 -104
- mlrun/datastore/utils.py +72 -58
- mlrun/datastore/v3io.py +5 -1
- mlrun/db/base.py +185 -35
- mlrun/db/factory.py +1 -1
- mlrun/db/httpdb.py +614 -179
- mlrun/db/nopdb.py +210 -26
- mlrun/errors.py +12 -1
- mlrun/execution.py +41 -24
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +40 -72
- mlrun/feature_store/common.py +1 -1
- mlrun/feature_store/feature_set.py +76 -55
- mlrun/feature_store/feature_vector.py +28 -30
- mlrun/feature_store/ingestion.py +7 -6
- mlrun/feature_store/retrieval/base.py +16 -11
- mlrun/feature_store/retrieval/conversion.py +11 -13
- mlrun/feature_store/retrieval/dask_merger.py +2 -0
- mlrun/feature_store/retrieval/job.py +9 -3
- mlrun/feature_store/retrieval/local_merger.py +2 -0
- mlrun/feature_store/retrieval/spark_merger.py +34 -24
- mlrun/feature_store/steps.py +37 -34
- mlrun/features.py +9 -20
- mlrun/frameworks/_common/artifacts_library.py +9 -9
- mlrun/frameworks/_common/mlrun_interface.py +5 -5
- mlrun/frameworks/_common/model_handler.py +48 -48
- mlrun/frameworks/_common/plan.py +2 -3
- mlrun/frameworks/_common/producer.py +3 -4
- mlrun/frameworks/_common/utils.py +5 -5
- mlrun/frameworks/_dl_common/loggers/logger.py +6 -7
- mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +9 -9
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +23 -47
- mlrun/frameworks/_ml_common/artifacts_library.py +1 -2
- mlrun/frameworks/_ml_common/loggers/logger.py +3 -4
- mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +4 -5
- mlrun/frameworks/_ml_common/model_handler.py +24 -24
- mlrun/frameworks/_ml_common/pkl_model_server.py +2 -2
- mlrun/frameworks/_ml_common/plan.py +1 -1
- mlrun/frameworks/_ml_common/plans/calibration_curve_plan.py +2 -3
- mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +2 -3
- mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
- mlrun/frameworks/_ml_common/plans/feature_importance_plan.py +3 -3
- mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
- mlrun/frameworks/_ml_common/utils.py +4 -4
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +9 -9
- mlrun/frameworks/huggingface/model_server.py +4 -4
- mlrun/frameworks/lgbm/__init__.py +33 -33
- mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
- mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -5
- mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -5
- mlrun/frameworks/lgbm/mlrun_interfaces/booster_mlrun_interface.py +1 -3
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +6 -6
- mlrun/frameworks/lgbm/model_handler.py +10 -10
- mlrun/frameworks/lgbm/model_server.py +6 -6
- mlrun/frameworks/lgbm/utils.py +5 -5
- mlrun/frameworks/onnx/dataset.py +8 -8
- mlrun/frameworks/onnx/mlrun_interface.py +3 -3
- mlrun/frameworks/onnx/model_handler.py +6 -6
- mlrun/frameworks/onnx/model_server.py +7 -7
- mlrun/frameworks/parallel_coordinates.py +4 -3
- mlrun/frameworks/pytorch/__init__.py +18 -18
- mlrun/frameworks/pytorch/callbacks/callback.py +4 -5
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +17 -17
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +11 -11
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +23 -29
- mlrun/frameworks/pytorch/callbacks_handler.py +38 -38
- mlrun/frameworks/pytorch/mlrun_interface.py +20 -20
- mlrun/frameworks/pytorch/model_handler.py +17 -17
- mlrun/frameworks/pytorch/model_server.py +7 -7
- mlrun/frameworks/sklearn/__init__.py +13 -13
- mlrun/frameworks/sklearn/estimator.py +4 -4
- mlrun/frameworks/sklearn/metrics_library.py +14 -14
- mlrun/frameworks/sklearn/mlrun_interface.py +3 -6
- mlrun/frameworks/sklearn/model_handler.py +2 -2
- mlrun/frameworks/tf_keras/__init__.py +10 -7
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +15 -15
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +11 -11
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +19 -23
- mlrun/frameworks/tf_keras/mlrun_interface.py +9 -11
- mlrun/frameworks/tf_keras/model_handler.py +14 -14
- mlrun/frameworks/tf_keras/model_server.py +6 -6
- mlrun/frameworks/xgboost/__init__.py +13 -13
- mlrun/frameworks/xgboost/model_handler.py +6 -6
- mlrun/k8s_utils.py +14 -16
- mlrun/launcher/__init__.py +1 -1
- mlrun/launcher/base.py +16 -15
- mlrun/launcher/client.py +8 -6
- mlrun/launcher/factory.py +1 -1
- mlrun/launcher/local.py +17 -11
- mlrun/launcher/remote.py +16 -10
- mlrun/lists.py +7 -6
- mlrun/model.py +238 -73
- mlrun/model_monitoring/__init__.py +1 -1
- mlrun/model_monitoring/api.py +138 -315
- mlrun/model_monitoring/application.py +5 -296
- mlrun/model_monitoring/applications/__init__.py +24 -0
- mlrun/model_monitoring/applications/_application_steps.py +157 -0
- mlrun/model_monitoring/applications/base.py +282 -0
- mlrun/model_monitoring/applications/context.py +214 -0
- mlrun/model_monitoring/applications/evidently_base.py +211 -0
- mlrun/model_monitoring/applications/histogram_data_drift.py +349 -0
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +104 -84
- mlrun/model_monitoring/controller_handler.py +13 -5
- mlrun/model_monitoring/db/__init__.py +18 -0
- mlrun/model_monitoring/{stores → db/stores}/__init__.py +43 -36
- mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
- mlrun/model_monitoring/{stores/model_endpoint_store.py → db/stores/base/store.py} +64 -40
- mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
- mlrun/model_monitoring/{stores → db/stores/sqldb}/models/base.py +109 -5
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +88 -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 +684 -0
- mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
- mlrun/model_monitoring/{stores/kv_model_endpoint_store.py → db/stores/v3io_kv/kv_store.py} +310 -165
- mlrun/model_monitoring/db/tsdb/__init__.py +100 -0
- mlrun/model_monitoring/db/tsdb/base.py +329 -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 +240 -0
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +397 -0
- mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +630 -0
- mlrun/model_monitoring/evidently_application.py +6 -118
- mlrun/model_monitoring/features_drift_table.py +134 -106
- mlrun/model_monitoring/helpers.py +127 -28
- mlrun/model_monitoring/metrics/__init__.py +13 -0
- mlrun/model_monitoring/metrics/histogram_distance.py +127 -0
- mlrun/model_monitoring/model_endpoint.py +3 -2
- mlrun/model_monitoring/prometheus.py +1 -4
- mlrun/model_monitoring/stream_processing.py +62 -231
- mlrun/model_monitoring/tracking_policy.py +9 -2
- mlrun/model_monitoring/writer.py +152 -124
- mlrun/package/__init__.py +6 -6
- mlrun/package/context_handler.py +5 -5
- mlrun/package/packager.py +7 -7
- mlrun/package/packagers/default_packager.py +6 -6
- mlrun/package/packagers/numpy_packagers.py +15 -15
- mlrun/package/packagers/pandas_packagers.py +5 -5
- mlrun/package/packagers/python_standard_library_packagers.py +10 -10
- mlrun/package/packagers_manager.py +19 -23
- mlrun/package/utils/_formatter.py +6 -6
- mlrun/package/utils/_pickler.py +2 -2
- mlrun/package/utils/_supported_format.py +4 -4
- mlrun/package/utils/log_hint_utils.py +2 -2
- mlrun/package/utils/type_hint_utils.py +4 -9
- mlrun/platforms/__init__.py +11 -10
- mlrun/platforms/iguazio.py +24 -203
- mlrun/projects/operations.py +35 -21
- mlrun/projects/pipelines.py +68 -99
- mlrun/projects/project.py +830 -266
- mlrun/render.py +3 -11
- mlrun/run.py +162 -166
- mlrun/runtimes/__init__.py +62 -7
- mlrun/runtimes/base.py +39 -32
- mlrun/runtimes/daskjob.py +8 -8
- mlrun/runtimes/databricks_job/databricks_cancel_task.py +1 -1
- mlrun/runtimes/databricks_job/databricks_runtime.py +7 -7
- mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
- mlrun/runtimes/funcdoc.py +0 -28
- mlrun/runtimes/function_reference.py +1 -1
- mlrun/runtimes/kubejob.py +28 -122
- mlrun/runtimes/local.py +6 -3
- mlrun/runtimes/mpijob/__init__.py +0 -20
- mlrun/runtimes/mpijob/abstract.py +9 -10
- mlrun/runtimes/mpijob/v1.py +1 -1
- mlrun/{model_monitoring/stores/models/sqlite.py → runtimes/nuclio/__init__.py} +7 -9
- mlrun/runtimes/nuclio/api_gateway.py +709 -0
- mlrun/runtimes/nuclio/application/__init__.py +15 -0
- mlrun/runtimes/nuclio/application/application.py +523 -0
- mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
- mlrun/runtimes/{function.py → nuclio/function.py} +112 -73
- mlrun/runtimes/{nuclio.py → nuclio/nuclio.py} +6 -6
- mlrun/runtimes/{serving.py → nuclio/serving.py} +45 -51
- mlrun/runtimes/pod.py +286 -88
- mlrun/runtimes/remotesparkjob.py +2 -2
- mlrun/runtimes/sparkjob/spark3job.py +51 -34
- mlrun/runtimes/utils.py +7 -75
- mlrun/secrets.py +9 -5
- mlrun/serving/remote.py +2 -7
- mlrun/serving/routers.py +13 -10
- mlrun/serving/server.py +22 -26
- mlrun/serving/states.py +99 -25
- mlrun/serving/utils.py +3 -3
- mlrun/serving/v1_serving.py +6 -7
- mlrun/serving/v2_serving.py +59 -20
- mlrun/track/tracker.py +2 -1
- mlrun/track/tracker_manager.py +3 -3
- mlrun/track/trackers/mlflow_tracker.py +1 -2
- mlrun/utils/async_http.py +5 -7
- mlrun/utils/azure_vault.py +1 -1
- mlrun/utils/clones.py +1 -2
- mlrun/utils/condition_evaluator.py +3 -3
- mlrun/utils/db.py +3 -3
- mlrun/utils/helpers.py +183 -197
- mlrun/utils/http.py +2 -5
- mlrun/utils/logger.py +76 -14
- mlrun/utils/notifications/notification/__init__.py +17 -12
- mlrun/utils/notifications/notification/base.py +14 -2
- mlrun/utils/notifications/notification/console.py +2 -0
- mlrun/utils/notifications/notification/git.py +3 -1
- mlrun/utils/notifications/notification/ipython.py +3 -1
- mlrun/utils/notifications/notification/slack.py +101 -21
- mlrun/utils/notifications/notification/webhook.py +11 -1
- mlrun/utils/notifications/notification_pusher.py +155 -30
- mlrun/utils/retryer.py +208 -0
- mlrun/utils/singleton.py +1 -1
- mlrun/utils/v3io_clients.py +2 -4
- mlrun/utils/version/version.json +2 -2
- mlrun/utils/version/version.py +2 -6
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/METADATA +31 -19
- mlrun-1.7.0rc20.dist-info/RECORD +353 -0
- mlrun/kfpops.py +0 -868
- mlrun/model_monitoring/batch.py +0 -1095
- mlrun/model_monitoring/stores/models/__init__.py +0 -27
- mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -384
- mlrun/platforms/other.py +0 -306
- mlrun-1.6.4rc2.dist-info/RECORD +0 -314
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/LICENSE +0 -0
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/WHEEL +0 -0
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/entry_points.txt +0 -0
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/top_level.txt +0 -0
mlrun/runtimes/pod.py
CHANGED
|
@@ -15,12 +15,14 @@ import copy
|
|
|
15
15
|
import inspect
|
|
16
16
|
import os
|
|
17
17
|
import re
|
|
18
|
+
import time
|
|
18
19
|
import typing
|
|
19
20
|
from enum import Enum
|
|
20
21
|
|
|
21
22
|
import dotenv
|
|
22
|
-
import kfp.dsl
|
|
23
23
|
import kubernetes.client as k8s_client
|
|
24
|
+
import mlrun_pipelines.mounts
|
|
25
|
+
from mlrun_pipelines.mixins import KfpAdapterMixin
|
|
24
26
|
|
|
25
27
|
import mlrun.errors
|
|
26
28
|
import mlrun.utils.regex
|
|
@@ -40,7 +42,6 @@ from ..k8s_utils import (
|
|
|
40
42
|
from ..utils import logger, update_in
|
|
41
43
|
from .base import BaseRuntime, FunctionSpec, spec_fields
|
|
42
44
|
from .utils import (
|
|
43
|
-
apply_kfp,
|
|
44
45
|
get_gpu_from_resource_requirement,
|
|
45
46
|
get_item_name,
|
|
46
47
|
set_named_item,
|
|
@@ -105,6 +106,50 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
105
106
|
"security_context",
|
|
106
107
|
"state_thresholds",
|
|
107
108
|
]
|
|
109
|
+
_default_fields_to_strip = FunctionSpec._default_fields_to_strip + [
|
|
110
|
+
"volumes",
|
|
111
|
+
"volume_mounts",
|
|
112
|
+
"resources",
|
|
113
|
+
"replicas",
|
|
114
|
+
"image_pull_policy",
|
|
115
|
+
"service_account",
|
|
116
|
+
"image_pull_secret",
|
|
117
|
+
"node_name",
|
|
118
|
+
"node_selector",
|
|
119
|
+
"affinity",
|
|
120
|
+
"priority_class_name",
|
|
121
|
+
"tolerations",
|
|
122
|
+
"preemption_mode",
|
|
123
|
+
"security_context",
|
|
124
|
+
]
|
|
125
|
+
_k8s_fields_to_serialize = [
|
|
126
|
+
"volumes",
|
|
127
|
+
"volume_mounts",
|
|
128
|
+
"resources",
|
|
129
|
+
"env",
|
|
130
|
+
"image_pull_policy",
|
|
131
|
+
"service_account",
|
|
132
|
+
"image_pull_secret",
|
|
133
|
+
"node_name",
|
|
134
|
+
"node_selector",
|
|
135
|
+
"affinity",
|
|
136
|
+
"tolerations",
|
|
137
|
+
"security_context",
|
|
138
|
+
]
|
|
139
|
+
_fields_to_serialize = FunctionSpec._fields_to_serialize + _k8s_fields_to_serialize
|
|
140
|
+
_fields_to_enrich = FunctionSpec._fields_to_enrich + [
|
|
141
|
+
"env", # Removing sensitive data from env
|
|
142
|
+
]
|
|
143
|
+
_fields_to_skip_validation = FunctionSpec._fields_to_skip_validation + [
|
|
144
|
+
# TODO: affinity, tolerations and node_selector are skipped due to preemption mode transitions.
|
|
145
|
+
# Preemption mode 'none' depends on the previous mode while the default mode may enrich these values.
|
|
146
|
+
# When we allow 'None' values for these attributes we get their true values and they will undo the default
|
|
147
|
+
# enrichment when creating the runtime from dict.
|
|
148
|
+
# The enrichment should move to the server side and then this can be removed.
|
|
149
|
+
"affinity",
|
|
150
|
+
"tolerations",
|
|
151
|
+
"node_selector",
|
|
152
|
+
]
|
|
108
153
|
|
|
109
154
|
def __init__(
|
|
110
155
|
self,
|
|
@@ -222,7 +267,7 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
222
267
|
self._affinity = transform_attribute_to_k8s_class_instance("affinity", affinity)
|
|
223
268
|
|
|
224
269
|
@property
|
|
225
|
-
def tolerations(self) ->
|
|
270
|
+
def tolerations(self) -> list[k8s_client.V1Toleration]:
|
|
226
271
|
return self._tolerations
|
|
227
272
|
|
|
228
273
|
@tolerations.setter
|
|
@@ -264,15 +309,42 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
264
309
|
def termination_grace_period_seconds(self) -> typing.Optional[int]:
|
|
265
310
|
return self._termination_grace_period_seconds
|
|
266
311
|
|
|
267
|
-
def
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
312
|
+
def _serialize_field(
|
|
313
|
+
self, struct: dict, field_name: str = None, strip: bool = False
|
|
314
|
+
) -> typing.Any:
|
|
315
|
+
"""
|
|
316
|
+
Serialize a field to a dict, list, or primitive type.
|
|
317
|
+
If field_name is in _k8s_fields_to_serialize, we will apply k8s serialization
|
|
318
|
+
"""
|
|
319
|
+
k8s_api = k8s_client.ApiClient()
|
|
320
|
+
if field_name in self._k8s_fields_to_serialize:
|
|
321
|
+
return k8s_api.sanitize_for_serialization(getattr(self, field_name))
|
|
322
|
+
return super()._serialize_field(struct, field_name, strip)
|
|
323
|
+
|
|
324
|
+
def _enrich_field(
|
|
325
|
+
self, struct: dict, field_name: str = None, strip: bool = False
|
|
326
|
+
) -> typing.Any:
|
|
327
|
+
k8s_api = k8s_client.ApiClient()
|
|
328
|
+
if strip:
|
|
329
|
+
if field_name == "env":
|
|
330
|
+
# We first try to pull from struct because the field might have been already serialized and if not,
|
|
331
|
+
# we pull from self
|
|
332
|
+
envs = struct.get(field_name, None) or getattr(self, field_name, None)
|
|
333
|
+
if envs:
|
|
334
|
+
serialized_envs = k8s_api.sanitize_for_serialization(envs)
|
|
335
|
+
for env in serialized_envs:
|
|
336
|
+
if env["name"].startswith("V3IO_"):
|
|
337
|
+
env["value"] = ""
|
|
338
|
+
return serialized_envs
|
|
339
|
+
return super()._enrich_field(struct=struct, field_name=field_name, strip=strip)
|
|
340
|
+
|
|
341
|
+
def _apply_enrichment_before_to_dict_completion(
|
|
342
|
+
self, struct: dict, strip: bool = False
|
|
343
|
+
):
|
|
344
|
+
if strip:
|
|
345
|
+
# Reset this, since mounts and env variables were cleared.
|
|
346
|
+
struct["disable_auto_mount"] = False
|
|
347
|
+
return super()._apply_enrichment_before_to_dict_completion(struct, strip)
|
|
276
348
|
|
|
277
349
|
def update_vols_and_mounts(
|
|
278
350
|
self, volumes, volume_mounts, volume_mounts_field_name="_volume_mounts"
|
|
@@ -455,7 +527,7 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
455
527
|
return {}
|
|
456
528
|
return resources
|
|
457
529
|
|
|
458
|
-
def _merge_node_selector(self, node_selector:
|
|
530
|
+
def _merge_node_selector(self, node_selector: dict[str, str]):
|
|
459
531
|
if not node_selector:
|
|
460
532
|
return
|
|
461
533
|
|
|
@@ -464,7 +536,7 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
464
536
|
|
|
465
537
|
def _merge_tolerations(
|
|
466
538
|
self,
|
|
467
|
-
tolerations:
|
|
539
|
+
tolerations: list[k8s_client.V1Toleration],
|
|
468
540
|
tolerations_field_name: str,
|
|
469
541
|
):
|
|
470
542
|
if not tolerations:
|
|
@@ -649,7 +721,7 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
649
721
|
|
|
650
722
|
def _merge_node_selector_term_to_node_affinity(
|
|
651
723
|
self,
|
|
652
|
-
node_selector_terms:
|
|
724
|
+
node_selector_terms: list[k8s_client.V1NodeSelectorTerm],
|
|
653
725
|
affinity_field_name: str,
|
|
654
726
|
):
|
|
655
727
|
if not node_selector_terms:
|
|
@@ -694,7 +766,7 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
694
766
|
|
|
695
767
|
def _prune_affinity_node_selector_requirement(
|
|
696
768
|
self,
|
|
697
|
-
node_selector_requirements:
|
|
769
|
+
node_selector_requirements: list[k8s_client.V1NodeSelectorRequirement],
|
|
698
770
|
affinity_field_name: str = "affinity",
|
|
699
771
|
):
|
|
700
772
|
"""
|
|
@@ -749,20 +821,18 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
749
821
|
|
|
750
822
|
@staticmethod
|
|
751
823
|
def _prune_node_selector_requirements_from_node_selector_terms(
|
|
752
|
-
node_selector_terms:
|
|
753
|
-
node_selector_requirements_to_prune:
|
|
754
|
-
|
|
755
|
-
],
|
|
756
|
-
) -> typing.List[k8s_client.V1NodeSelectorTerm]:
|
|
824
|
+
node_selector_terms: list[k8s_client.V1NodeSelectorTerm],
|
|
825
|
+
node_selector_requirements_to_prune: list[k8s_client.V1NodeSelectorRequirement],
|
|
826
|
+
) -> list[k8s_client.V1NodeSelectorTerm]:
|
|
757
827
|
"""
|
|
758
828
|
Goes over each expression in all the terms provided and removes the expressions if it matches
|
|
759
829
|
one of the requirements provided to remove
|
|
760
830
|
|
|
761
831
|
:return: New list of terms without the provided node selector requirements
|
|
762
832
|
"""
|
|
763
|
-
new_node_selector_terms:
|
|
833
|
+
new_node_selector_terms: list[k8s_client.V1NodeSelectorTerm] = []
|
|
764
834
|
for term in node_selector_terms:
|
|
765
|
-
new_node_selector_requirements:
|
|
835
|
+
new_node_selector_requirements: list[
|
|
766
836
|
k8s_client.V1NodeSelectorRequirement
|
|
767
837
|
] = []
|
|
768
838
|
for node_selector_requirement in term.match_expressions:
|
|
@@ -791,7 +861,7 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
791
861
|
|
|
792
862
|
def _prune_tolerations(
|
|
793
863
|
self,
|
|
794
|
-
tolerations:
|
|
864
|
+
tolerations: list[k8s_client.V1Toleration],
|
|
795
865
|
tolerations_field_name: str = "tolerations",
|
|
796
866
|
):
|
|
797
867
|
"""
|
|
@@ -820,7 +890,7 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
820
890
|
|
|
821
891
|
def _prune_node_selector(
|
|
822
892
|
self,
|
|
823
|
-
node_selector:
|
|
893
|
+
node_selector: dict[str, str],
|
|
824
894
|
node_selector_field_name: str,
|
|
825
895
|
):
|
|
826
896
|
"""
|
|
@@ -865,12 +935,12 @@ class AutoMountType(str, Enum):
|
|
|
865
935
|
@classmethod
|
|
866
936
|
def all_mount_modifiers(cls):
|
|
867
937
|
return [
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
938
|
+
mlrun_pipelines.mounts.v3io_cred.__name__,
|
|
939
|
+
mlrun_pipelines.mounts.mount_v3io.__name__,
|
|
940
|
+
mlrun_pipelines.mounts.mount_pvc.__name__,
|
|
941
|
+
mlrun_pipelines.mounts.auto_mount.__name__,
|
|
942
|
+
mlrun_pipelines.mounts.mount_s3.__name__,
|
|
943
|
+
mlrun_pipelines.mounts.set_env_variables.__name__,
|
|
874
944
|
]
|
|
875
945
|
|
|
876
946
|
@classmethod
|
|
@@ -887,27 +957,27 @@ class AutoMountType(str, Enum):
|
|
|
887
957
|
def _get_auto_modifier():
|
|
888
958
|
# If we're running on Iguazio - use v3io_cred
|
|
889
959
|
if mlconf.igz_version != "":
|
|
890
|
-
return
|
|
960
|
+
return mlrun_pipelines.mounts.v3io_cred
|
|
891
961
|
# Else, either pvc mount if it's configured or do nothing otherwise
|
|
892
962
|
pvc_configured = (
|
|
893
963
|
"MLRUN_PVC_MOUNT" in os.environ
|
|
894
964
|
or "pvc_name" in mlconf.get_storage_auto_mount_params()
|
|
895
965
|
)
|
|
896
|
-
return
|
|
966
|
+
return mlrun_pipelines.mounts.mount_pvc if pvc_configured else None
|
|
897
967
|
|
|
898
968
|
def get_modifier(self):
|
|
899
969
|
return {
|
|
900
970
|
AutoMountType.none: None,
|
|
901
|
-
AutoMountType.v3io_credentials:
|
|
902
|
-
AutoMountType.v3io_fuse:
|
|
903
|
-
AutoMountType.pvc:
|
|
971
|
+
AutoMountType.v3io_credentials: mlrun_pipelines.mounts.v3io_cred,
|
|
972
|
+
AutoMountType.v3io_fuse: mlrun_pipelines.mounts.mount_v3io,
|
|
973
|
+
AutoMountType.pvc: mlrun_pipelines.mounts.mount_pvc,
|
|
904
974
|
AutoMountType.auto: self._get_auto_modifier(),
|
|
905
|
-
AutoMountType.s3:
|
|
906
|
-
AutoMountType.env:
|
|
975
|
+
AutoMountType.s3: mlrun_pipelines.mounts.mount_s3,
|
|
976
|
+
AutoMountType.env: mlrun_pipelines.mounts.set_env_variables,
|
|
907
977
|
}[self]
|
|
908
978
|
|
|
909
979
|
|
|
910
|
-
class KubeResource(BaseRuntime):
|
|
980
|
+
class KubeResource(BaseRuntime, KfpAdapterMixin):
|
|
911
981
|
"""
|
|
912
982
|
A parent class for runtimes that generate k8s resources when executing.
|
|
913
983
|
"""
|
|
@@ -916,7 +986,7 @@ class KubeResource(BaseRuntime):
|
|
|
916
986
|
_is_nested = True
|
|
917
987
|
|
|
918
988
|
def __init__(self, spec=None, metadata=None):
|
|
919
|
-
super().__init__(metadata, spec)
|
|
989
|
+
super().__init__(metadata=metadata, spec=spec)
|
|
920
990
|
self.verbose = False
|
|
921
991
|
|
|
922
992
|
@property
|
|
@@ -927,48 +997,6 @@ class KubeResource(BaseRuntime):
|
|
|
927
997
|
def spec(self, spec):
|
|
928
998
|
self._spec = self._verify_dict(spec, "spec", KubeResourceSpec)
|
|
929
999
|
|
|
930
|
-
def to_dict(self, fields=None, exclude=None, strip=False):
|
|
931
|
-
struct = super().to_dict(fields, exclude, strip=strip)
|
|
932
|
-
api = k8s_client.ApiClient()
|
|
933
|
-
struct = api.sanitize_for_serialization(struct)
|
|
934
|
-
if strip:
|
|
935
|
-
spec = struct["spec"]
|
|
936
|
-
for attr in [
|
|
937
|
-
"volumes",
|
|
938
|
-
"volume_mounts",
|
|
939
|
-
"driver_volume_mounts",
|
|
940
|
-
"executor_volume_mounts",
|
|
941
|
-
]:
|
|
942
|
-
if attr in spec:
|
|
943
|
-
del spec[attr]
|
|
944
|
-
if "env" in spec and spec["env"]:
|
|
945
|
-
for ev in spec["env"]:
|
|
946
|
-
if ev["name"].startswith("V3IO_"):
|
|
947
|
-
ev["value"] = ""
|
|
948
|
-
# Reset this, since mounts and env variables were cleared.
|
|
949
|
-
spec["disable_auto_mount"] = False
|
|
950
|
-
return struct
|
|
951
|
-
|
|
952
|
-
def apply(self, modify):
|
|
953
|
-
"""
|
|
954
|
-
Apply a modifier to the runtime which is used to change the runtimes k8s object's spec.
|
|
955
|
-
Modifiers can be either KFP modifiers or MLRun modifiers (which are compatible with KFP). All modifiers accept
|
|
956
|
-
a `kfp.dsl.ContainerOp` object, apply some changes on its spec and return it so modifiers can be chained
|
|
957
|
-
one after the other.
|
|
958
|
-
|
|
959
|
-
:param modify: a modifier runnable object
|
|
960
|
-
:return: the runtime (self) after the modifications
|
|
961
|
-
"""
|
|
962
|
-
|
|
963
|
-
# Kubeflow pipeline have a hook to add the component to the DAG on ContainerOp init
|
|
964
|
-
# we remove the hook to suppress kubeflow op registration and return it after the apply()
|
|
965
|
-
old_op_handler = kfp.dsl._container_op._register_op_handler
|
|
966
|
-
kfp.dsl._container_op._register_op_handler = lambda x: self.metadata.name
|
|
967
|
-
cop = kfp.dsl.ContainerOp("name", "image")
|
|
968
|
-
kfp.dsl._container_op._register_op_handler = old_op_handler
|
|
969
|
-
|
|
970
|
-
return apply_kfp(modify, cop, self)
|
|
971
|
-
|
|
972
1000
|
def set_env_from_secret(self, name, secret=None, secret_key=None):
|
|
973
1001
|
"""set pod environment var from secret"""
|
|
974
1002
|
secret_key = secret_key or name
|
|
@@ -1010,6 +1038,32 @@ class KubeResource(BaseRuntime):
|
|
|
1010
1038
|
return True
|
|
1011
1039
|
return False
|
|
1012
1040
|
|
|
1041
|
+
def enrich_runtime_spec(
|
|
1042
|
+
self,
|
|
1043
|
+
project_node_selector: dict[str, str],
|
|
1044
|
+
):
|
|
1045
|
+
"""
|
|
1046
|
+
Enriches the runtime spec with the project-level node selector.
|
|
1047
|
+
|
|
1048
|
+
This method merges the project-level node selector with the existing function node_selector.
|
|
1049
|
+
The merge logic used here combines the two dictionaries, giving precedence to
|
|
1050
|
+
the keys in the runtime node_selector. If there are conflicting keys between the
|
|
1051
|
+
two dictionaries, the values from self.spec.node_selector will overwrite the
|
|
1052
|
+
values from project_node_selector.
|
|
1053
|
+
|
|
1054
|
+
Example:
|
|
1055
|
+
Suppose self.spec.node_selector = {"type": "gpu", "zone": "us-east-1"}
|
|
1056
|
+
and project_node_selector = {"type": "cpu", "environment": "production"}.
|
|
1057
|
+
After the merge, the resulting node_selector will be:
|
|
1058
|
+
{"type": "gpu", "zone": "us-east-1", "environment": "production"}
|
|
1059
|
+
|
|
1060
|
+
Note:
|
|
1061
|
+
- The merge uses the ** operator, also known as the "unpacking" operator in Python,
|
|
1062
|
+
combining key-value pairs from each dictionary. Later dictionaries take precedence
|
|
1063
|
+
when there are conflicting keys.
|
|
1064
|
+
"""
|
|
1065
|
+
self.spec.node_selector = {**project_node_selector, **self.spec.node_selector}
|
|
1066
|
+
|
|
1013
1067
|
def _set_env(self, name, value=None, value_from=None):
|
|
1014
1068
|
new_var = k8s_client.V1EnvVar(name=name, value=value, value_from=value_from)
|
|
1015
1069
|
|
|
@@ -1065,7 +1119,7 @@ class KubeResource(BaseRuntime):
|
|
|
1065
1119
|
|
|
1066
1120
|
def set_state_thresholds(
|
|
1067
1121
|
self,
|
|
1068
|
-
state_thresholds:
|
|
1122
|
+
state_thresholds: dict[str, str],
|
|
1069
1123
|
patch: bool = True,
|
|
1070
1124
|
):
|
|
1071
1125
|
"""
|
|
@@ -1126,9 +1180,9 @@ class KubeResource(BaseRuntime):
|
|
|
1126
1180
|
def with_node_selection(
|
|
1127
1181
|
self,
|
|
1128
1182
|
node_name: typing.Optional[str] = None,
|
|
1129
|
-
node_selector: typing.Optional[
|
|
1183
|
+
node_selector: typing.Optional[dict[str, str]] = None,
|
|
1130
1184
|
affinity: typing.Optional[k8s_client.V1Affinity] = None,
|
|
1131
|
-
tolerations: typing.Optional[
|
|
1185
|
+
tolerations: typing.Optional[list[k8s_client.V1Toleration]] = None,
|
|
1132
1186
|
):
|
|
1133
1187
|
"""
|
|
1134
1188
|
Enables to control on which k8s node the job will run
|
|
@@ -1204,9 +1258,9 @@ class KubeResource(BaseRuntime):
|
|
|
1204
1258
|
from kubernetes import client as k8s_client
|
|
1205
1259
|
|
|
1206
1260
|
security_context = k8s_client.V1SecurityContext(
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1261
|
+
run_as_user=1000,
|
|
1262
|
+
run_as_group=3000,
|
|
1263
|
+
)
|
|
1210
1264
|
function.with_security_context(security_context)
|
|
1211
1265
|
|
|
1212
1266
|
More info:
|
|
@@ -1265,6 +1319,150 @@ class KubeResource(BaseRuntime):
|
|
|
1265
1319
|
|
|
1266
1320
|
self.spec.validate_service_account(allowed_service_accounts)
|
|
1267
1321
|
|
|
1322
|
+
def _configure_mlrun_build_with_source(
|
|
1323
|
+
self, source, workdir=None, handler=None, pull_at_runtime=True, target_dir=None
|
|
1324
|
+
):
|
|
1325
|
+
mlrun.utils.helpers.validate_builder_source(source, pull_at_runtime, workdir)
|
|
1326
|
+
|
|
1327
|
+
self.spec.build.source = source
|
|
1328
|
+
if handler:
|
|
1329
|
+
self.spec.default_handler = handler
|
|
1330
|
+
if workdir:
|
|
1331
|
+
self.spec.workdir = workdir
|
|
1332
|
+
if target_dir:
|
|
1333
|
+
self.spec.build.source_code_target_dir = target_dir
|
|
1334
|
+
|
|
1335
|
+
self.spec.build.load_source_on_run = pull_at_runtime
|
|
1336
|
+
if (
|
|
1337
|
+
self.spec.build.base_image
|
|
1338
|
+
and not self.spec.build.commands
|
|
1339
|
+
and pull_at_runtime
|
|
1340
|
+
and not self.spec.image
|
|
1341
|
+
):
|
|
1342
|
+
# if we load source from repo and don't need a full build use the base_image as the image
|
|
1343
|
+
self.spec.image = self.spec.build.base_image
|
|
1344
|
+
elif not pull_at_runtime:
|
|
1345
|
+
# clear the image so build will not be skipped
|
|
1346
|
+
self.spec.build.base_image = self.spec.build.base_image or self.spec.image
|
|
1347
|
+
self.spec.image = ""
|
|
1348
|
+
|
|
1349
|
+
def _resolve_build_with_mlrun(self, with_mlrun: typing.Optional[bool] = None):
|
|
1350
|
+
build = self.spec.build
|
|
1351
|
+
if with_mlrun is None:
|
|
1352
|
+
if build.with_mlrun is not None:
|
|
1353
|
+
with_mlrun = build.with_mlrun
|
|
1354
|
+
else:
|
|
1355
|
+
with_mlrun = build.base_image and not (
|
|
1356
|
+
build.base_image.startswith("mlrun/")
|
|
1357
|
+
or "/mlrun/" in build.base_image
|
|
1358
|
+
)
|
|
1359
|
+
if (
|
|
1360
|
+
not build.source
|
|
1361
|
+
and not build.commands
|
|
1362
|
+
and not build.requirements
|
|
1363
|
+
and not build.extra
|
|
1364
|
+
and with_mlrun
|
|
1365
|
+
):
|
|
1366
|
+
logger.info(
|
|
1367
|
+
"Running build to add mlrun package, set "
|
|
1368
|
+
"with_mlrun=False to skip if its already in the image"
|
|
1369
|
+
)
|
|
1370
|
+
return with_mlrun
|
|
1371
|
+
|
|
1372
|
+
def _build_image(
|
|
1373
|
+
self,
|
|
1374
|
+
builder_env,
|
|
1375
|
+
force_build,
|
|
1376
|
+
mlrun_version_specifier,
|
|
1377
|
+
show_on_failure,
|
|
1378
|
+
skip_deployed,
|
|
1379
|
+
watch,
|
|
1380
|
+
is_kfp,
|
|
1381
|
+
with_mlrun,
|
|
1382
|
+
):
|
|
1383
|
+
# When we're in pipelines context we must watch otherwise the pipelines pod will exit before the operation
|
|
1384
|
+
# is actually done. (when a pipelines pod exits, the pipeline step marked as done)
|
|
1385
|
+
if is_kfp:
|
|
1386
|
+
watch = True
|
|
1387
|
+
|
|
1388
|
+
db = self._get_db()
|
|
1389
|
+
data = db.remote_builder(
|
|
1390
|
+
self,
|
|
1391
|
+
with_mlrun,
|
|
1392
|
+
mlrun_version_specifier,
|
|
1393
|
+
skip_deployed,
|
|
1394
|
+
builder_env=builder_env,
|
|
1395
|
+
force_build=force_build,
|
|
1396
|
+
)
|
|
1397
|
+
self.status = data["data"].get("status", None)
|
|
1398
|
+
self.spec.image = mlrun.utils.get_in(
|
|
1399
|
+
data, "data.spec.image"
|
|
1400
|
+
) or mlrun.utils.get_in(data, "data.spec.build.image")
|
|
1401
|
+
self.spec.build.base_image = self.spec.build.base_image or mlrun.utils.get_in(
|
|
1402
|
+
data, "data.spec.build.base_image"
|
|
1403
|
+
)
|
|
1404
|
+
# Get the source target dir in case it was enriched due to loading source
|
|
1405
|
+
self.spec.build.source_code_target_dir = mlrun.utils.get_in(
|
|
1406
|
+
data, "data.spec.build.source_code_target_dir"
|
|
1407
|
+
) or mlrun.utils.get_in(data, "data.spec.clone_target_dir")
|
|
1408
|
+
ready = data.get("ready", False)
|
|
1409
|
+
if not ready:
|
|
1410
|
+
logger.info(
|
|
1411
|
+
f"Started building image: {data.get('data', {}).get('spec', {}).get('build', {}).get('image')}"
|
|
1412
|
+
)
|
|
1413
|
+
if watch and not ready:
|
|
1414
|
+
state = self._build_watch(
|
|
1415
|
+
watch=watch,
|
|
1416
|
+
show_on_failure=show_on_failure,
|
|
1417
|
+
)
|
|
1418
|
+
ready = state == "ready"
|
|
1419
|
+
self.status.state = state
|
|
1420
|
+
|
|
1421
|
+
if watch and not ready:
|
|
1422
|
+
raise mlrun.errors.MLRunRuntimeError("Deploy failed")
|
|
1423
|
+
return ready
|
|
1424
|
+
|
|
1425
|
+
def _build_watch(
|
|
1426
|
+
self,
|
|
1427
|
+
watch: bool = True,
|
|
1428
|
+
logs: bool = True,
|
|
1429
|
+
show_on_failure: bool = False,
|
|
1430
|
+
):
|
|
1431
|
+
db = self._get_db()
|
|
1432
|
+
offset = 0
|
|
1433
|
+
try:
|
|
1434
|
+
text, _ = db.get_builder_status(self, 0, logs=logs)
|
|
1435
|
+
except mlrun.db.RunDBError:
|
|
1436
|
+
raise ValueError("function or build process not found")
|
|
1437
|
+
|
|
1438
|
+
def print_log(text):
|
|
1439
|
+
if text and (
|
|
1440
|
+
not show_on_failure
|
|
1441
|
+
or self.status.state == mlrun.common.schemas.FunctionState.error
|
|
1442
|
+
):
|
|
1443
|
+
print(text, end="")
|
|
1444
|
+
|
|
1445
|
+
print_log(text)
|
|
1446
|
+
offset += len(text)
|
|
1447
|
+
if watch:
|
|
1448
|
+
while self.status.state in [
|
|
1449
|
+
mlrun.common.schemas.FunctionState.pending,
|
|
1450
|
+
mlrun.common.schemas.FunctionState.running,
|
|
1451
|
+
]:
|
|
1452
|
+
time.sleep(2)
|
|
1453
|
+
if show_on_failure:
|
|
1454
|
+
text = ""
|
|
1455
|
+
db.get_builder_status(self, 0, logs=False)
|
|
1456
|
+
if self.status.state == mlrun.common.schemas.FunctionState.error:
|
|
1457
|
+
# re-read the full log on failure
|
|
1458
|
+
text, _ = db.get_builder_status(self, offset, logs=logs)
|
|
1459
|
+
else:
|
|
1460
|
+
text, _ = db.get_builder_status(self, offset, logs=logs)
|
|
1461
|
+
print_log(text)
|
|
1462
|
+
offset += len(text)
|
|
1463
|
+
|
|
1464
|
+
return self.status.state
|
|
1465
|
+
|
|
1268
1466
|
|
|
1269
1467
|
def _resolve_if_type_sanitized(attribute_name, attribute):
|
|
1270
1468
|
attribute_config = sanitized_attributes[attribute_name]
|
mlrun/runtimes/remotesparkjob.py
CHANGED
|
@@ -15,11 +15,11 @@ import re
|
|
|
15
15
|
from subprocess import run
|
|
16
16
|
|
|
17
17
|
import kubernetes.client
|
|
18
|
+
from mlrun_pipelines.mounts import mount_v3io, mount_v3iod
|
|
18
19
|
|
|
19
20
|
import mlrun.errors
|
|
20
21
|
from mlrun.config import config
|
|
21
22
|
|
|
22
|
-
from ..platforms.iguazio import mount_v3io, mount_v3iod
|
|
23
23
|
from .kubejob import KubejobRuntime
|
|
24
24
|
from .pod import KubeResourceSpec
|
|
25
25
|
|
|
@@ -92,7 +92,7 @@ class RemoteSparkSpec(KubeResourceSpec):
|
|
|
92
92
|
self.provider = provider
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
class RemoteSparkProviders
|
|
95
|
+
class RemoteSparkProviders:
|
|
96
96
|
iguazio = "iguazio"
|
|
97
97
|
|
|
98
98
|
|